diff options
Diffstat (limited to 'src')
98 files changed, 2061 insertions, 875 deletions
diff --git a/src/Makefile.test.include b/src/Makefile.test.include index b4ca01b1fe..a8845b4c7d 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -4,11 +4,16 @@ FUZZ_TARGETS = \ + test/fuzz/addr_info_deserialize \ test/fuzz/address_deserialize \ test/fuzz/addrman_deserialize \ test/fuzz/banentry_deserialize \ test/fuzz/bech32 \ + test/fuzz/block \ test/fuzz/block_deserialize \ + test/fuzz/block_file_info_deserialize \ + test/fuzz/block_filter_deserialize \ + test/fuzz/block_header_and_short_txids_deserialize \ test/fuzz/blockheader_deserialize \ test/fuzz/blocklocator_deserialize \ test/fuzz/blockmerkleroot \ @@ -20,16 +25,37 @@ FUZZ_TARGETS = \ test/fuzz/descriptor_parse \ test/fuzz/diskblockindex_deserialize \ test/fuzz/eval_script \ + test/fuzz/fee_rate_deserialize \ + test/fuzz/flat_file_pos_deserialize \ + test/fuzz/integer \ test/fuzz/inv_deserialize \ + test/fuzz/key_origin_info_deserialize \ + test/fuzz/merkle_block_deserialize \ test/fuzz/messageheader_deserialize \ test/fuzz/netaddr_deserialize \ + test/fuzz/out_point_deserialize \ + test/fuzz/parse_hd_keypath \ test/fuzz/parse_iso8601 \ + test/fuzz/partial_merkle_tree_deserialize \ + test/fuzz/partially_signed_transaction_deserialize \ + test/fuzz/prefilled_transaction_deserialize \ + test/fuzz/parse_numbers \ + test/fuzz/parse_script \ + test/fuzz/parse_univalue \ test/fuzz/psbt \ + test/fuzz/psbt_input_deserialize \ + test/fuzz/psbt_output_deserialize \ + test/fuzz/pub_key_deserialize \ test/fuzz/script \ + test/fuzz/script_deserialize \ test/fuzz/script_flags \ test/fuzz/service_deserialize \ test/fuzz/spanparsing \ + test/fuzz/sub_net_deserialize \ test/fuzz/transaction \ + test/fuzz/tx_in \ + test/fuzz/tx_in_deserialize \ + test/fuzz/tx_out \ test/fuzz/txoutcompressor_deserialize \ test/fuzz/txundo_deserialize @@ -73,6 +99,7 @@ FUZZ_SUITE_LD_COMMON = \ $(LIBTEST_UTIL) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ + $(LIBBITCOIN_CLI) \ $(LIBUNIVALUE) \ $(LIBLEVELDB) \ $(LIBLEVELDB_SSE42) \ @@ -203,6 +230,12 @@ test_test_bitcoin_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif if ENABLE_FUZZ +test_fuzz_block_SOURCES = $(FUZZ_SUITE) test/fuzz/block.cpp +test_fuzz_block_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_block_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_block_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_block_LDADD = $(FUZZ_SUITE_LD_COMMON) + test_fuzz_block_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp test_fuzz_block_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DBLOCK_DESERIALIZE=1 test_fuzz_block_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) @@ -347,6 +380,12 @@ test_fuzz_eval_script_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_eval_script_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_eval_script_LDADD = $(FUZZ_SUITE_LD_COMMON) +test_fuzz_integer_SOURCES = $(FUZZ_SUITE) test/fuzz/integer.cpp +test_fuzz_integer_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_integer_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_integer_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_integer_LDADD = $(FUZZ_SUITE_LD_COMMON) + test_fuzz_txoutcompressor_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp test_fuzz_txoutcompressor_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DTXOUTCOMPRESSOR_DESERIALIZE=1 test_fuzz_txoutcompressor_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) @@ -371,6 +410,150 @@ test_fuzz_transaction_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_transaction_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_transaction_LDADD = $(FUZZ_SUITE_LD_COMMON) +test_fuzz_addr_info_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_addr_info_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DADDR_INFO_DESERIALIZE=1 +test_fuzz_addr_info_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_addr_info_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_addr_info_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_block_file_info_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_block_file_info_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DBLOCK_FILE_INFO_DESERIALIZE=1 +test_fuzz_block_file_info_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_block_file_info_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_block_file_info_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_block_filter_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_block_filter_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DBLOCK_FILTER_DESERIALIZE=1 +test_fuzz_block_filter_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_block_filter_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_block_filter_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_block_header_and_short_txids_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_block_header_and_short_txids_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DBLOCK_HEADER_AND_SHORT_TXIDS_DESERIALIZE=1 +test_fuzz_block_header_and_short_txids_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_block_header_and_short_txids_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_block_header_and_short_txids_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_fee_rate_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_fee_rate_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DFEE_RATE_DESERIALIZE=1 +test_fuzz_fee_rate_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_fee_rate_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_fee_rate_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_flat_file_pos_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_flat_file_pos_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DFLAT_FILE_POS_DESERIALIZE=1 +test_fuzz_flat_file_pos_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_flat_file_pos_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_flat_file_pos_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_key_origin_info_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_key_origin_info_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DKEY_ORIGIN_INFO_DESERIALIZE=1 +test_fuzz_key_origin_info_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_key_origin_info_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_key_origin_info_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_merkle_block_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_merkle_block_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DMERKLE_BLOCK_DESERIALIZE=1 +test_fuzz_merkle_block_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_merkle_block_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_merkle_block_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_out_point_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_out_point_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DOUT_POINT_DESERIALIZE=1 +test_fuzz_out_point_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_out_point_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_out_point_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_partially_signed_transaction_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_partially_signed_transaction_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DPARTIALLY_SIGNED_TRANSACTION_DESERIALIZE=1 +test_fuzz_partially_signed_transaction_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_partially_signed_transaction_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_partially_signed_transaction_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_partial_merkle_tree_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_partial_merkle_tree_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DPARTIAL_MERKLE_TREE_DESERIALIZE=1 +test_fuzz_partial_merkle_tree_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_partial_merkle_tree_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_partial_merkle_tree_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_prefilled_transaction_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_prefilled_transaction_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DPREFILLED_TRANSACTION_DESERIALIZE=1 +test_fuzz_prefilled_transaction_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_prefilled_transaction_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_prefilled_transaction_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_psbt_input_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_psbt_input_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DPSBT_INPUT_DESERIALIZE=1 +test_fuzz_psbt_input_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_psbt_input_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_psbt_input_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_psbt_output_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_psbt_output_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DPSBT_OUTPUT_DESERIALIZE=1 +test_fuzz_psbt_output_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_psbt_output_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_psbt_output_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_pub_key_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_pub_key_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DPUB_KEY_DESERIALIZE=1 +test_fuzz_pub_key_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_pub_key_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_pub_key_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_script_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_script_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DSCRIPT_DESERIALIZE=1 +test_fuzz_script_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_script_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_script_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_sub_net_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_sub_net_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DSUB_NET_DESERIALIZE=1 +test_fuzz_sub_net_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_sub_net_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_sub_net_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_tx_in_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_tx_in_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DTX_IN_DESERIALIZE=1 +test_fuzz_tx_in_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_tx_in_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_tx_in_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_tx_in_SOURCES = $(FUZZ_SUITE) test/fuzz/tx_in.cpp +test_fuzz_tx_in_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_tx_in_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_tx_in_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_tx_in_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_tx_out_SOURCES = $(FUZZ_SUITE) test/fuzz/tx_out.cpp +test_fuzz_tx_out_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_tx_out_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_tx_out_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_tx_out_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_parse_hd_keypath_SOURCES = $(FUZZ_SUITE) test/fuzz/parse_hd_keypath.cpp +test_fuzz_parse_hd_keypath_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_parse_hd_keypath_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_parse_hd_keypath_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_parse_hd_keypath_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_parse_script_SOURCES = $(FUZZ_SUITE) test/fuzz/parse_script.cpp +test_fuzz_parse_script_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_parse_script_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_parse_script_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_parse_script_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_parse_numbers_SOURCES = $(FUZZ_SUITE) test/fuzz/parse_numbers.cpp +test_fuzz_parse_numbers_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_parse_numbers_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_parse_numbers_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_parse_numbers_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_parse_univalue_SOURCES = $(FUZZ_SUITE) test/fuzz/parse_univalue.cpp +test_fuzz_parse_univalue_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_parse_univalue_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_parse_univalue_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_parse_univalue_LDADD = $(FUZZ_SUITE_LD_COMMON) + endif # ENABLE_FUZZ nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES) diff --git a/src/base58.cpp b/src/base58.cpp index e3d2853399..17d3f86ba8 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -7,10 +7,13 @@ #include <hash.h> #include <uint256.h> #include <util/strencodings.h> +#include <util/string.h> #include <assert.h> #include <string.h> +#include <limits> + /** All alphanumeric characters except for "0", "I", "O", and "l" */ static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; static const int8_t mapBase58[256] = { @@ -32,7 +35,7 @@ static const int8_t mapBase58[256] = { -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, }; -bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch) +bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch, int max_ret_len) { // Skip leading spaces. while (*psz && IsSpace(*psz)) @@ -42,6 +45,7 @@ bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch) int length = 0; while (*psz == '1') { zeroes++; + if (zeroes > max_ret_len) return false; psz++; } // Allocate enough space in big-endian base256 representation. @@ -62,6 +66,7 @@ bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch) } assert(carry == 0); length = i; + if (length + zeroes > max_ret_len) return false; psz++; } // Skip trailing spaces. @@ -71,8 +76,6 @@ bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch) return false; // Skip leading zeroes in b256. std::vector<unsigned char>::iterator it = b256.begin() + (size - length); - while (it != b256.end() && *it == 0) - it++; // Copy result into output vector. vch.reserve(zeroes + (b256.end() - it)); vch.assign(zeroes, 0x00); @@ -126,9 +129,12 @@ std::string EncodeBase58(const std::vector<unsigned char>& vch) return EncodeBase58(vch.data(), vch.data() + vch.size()); } -bool DecodeBase58(const std::string& str, std::vector<unsigned char>& vchRet) +bool DecodeBase58(const std::string& str, std::vector<unsigned char>& vchRet, int max_ret_len) { - return DecodeBase58(str.c_str(), vchRet); + if (!ValidAsCString(str)) { + return false; + } + return DecodeBase58(str.c_str(), vchRet, max_ret_len); } std::string EncodeBase58Check(const std::vector<unsigned char>& vchIn) @@ -140,9 +146,9 @@ std::string EncodeBase58Check(const std::vector<unsigned char>& vchIn) return EncodeBase58(vch); } -bool DecodeBase58Check(const char* psz, std::vector<unsigned char>& vchRet) +bool DecodeBase58Check(const char* psz, std::vector<unsigned char>& vchRet, int max_ret_len) { - if (!DecodeBase58(psz, vchRet) || + if (!DecodeBase58(psz, vchRet, max_ret_len > std::numeric_limits<int>::max() - 4 ? std::numeric_limits<int>::max() : max_ret_len + 4) || (vchRet.size() < 4)) { vchRet.clear(); return false; @@ -157,7 +163,10 @@ bool DecodeBase58Check(const char* psz, std::vector<unsigned char>& vchRet) return true; } -bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet) +bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet, int max_ret) { - return DecodeBase58Check(str.c_str(), vchRet); + if (!ValidAsCString(str)) { + return false; + } + return DecodeBase58Check(str.c_str(), vchRet, max_ret); } diff --git a/src/base58.h b/src/base58.h index d6e0299a1e..90eded4992 100644 --- a/src/base58.h +++ b/src/base58.h @@ -35,13 +35,13 @@ std::string EncodeBase58(const std::vector<unsigned char>& vch); * return true if decoding is successful. * psz cannot be nullptr. */ -NODISCARD bool DecodeBase58(const char* psz, std::vector<unsigned char>& vchRet); +NODISCARD bool DecodeBase58(const char* psz, std::vector<unsigned char>& vchRet, int max_ret_len); /** * Decode a base58-encoded string (str) into a byte vector (vchRet). * return true if decoding is successful. */ -NODISCARD bool DecodeBase58(const std::string& str, std::vector<unsigned char>& vchRet); +NODISCARD bool DecodeBase58(const std::string& str, std::vector<unsigned char>& vchRet, int max_ret_len); /** * Encode a byte vector into a base58-encoded string, including checksum @@ -52,12 +52,12 @@ std::string EncodeBase58Check(const std::vector<unsigned char>& vchIn); * Decode a base58-encoded string (psz) that includes a checksum into a byte * vector (vchRet), return true if decoding is successful */ -NODISCARD bool DecodeBase58Check(const char* psz, std::vector<unsigned char>& vchRet); +NODISCARD bool DecodeBase58Check(const char* psz, std::vector<unsigned char>& vchRet, int max_ret_len); /** * Decode a base58-encoded string (str) that includes a checksum into a byte * vector (vchRet), return true if decoding is successful */ -NODISCARD bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet); +NODISCARD bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet, int max_ret_len); #endif // BITCOIN_BASE58_H diff --git a/src/bench/base58.cpp b/src/bench/base58.cpp index 40a7b5e320..d8c9b2e01a 100644 --- a/src/bench/base58.cpp +++ b/src/bench/base58.cpp @@ -47,7 +47,7 @@ static void Base58Decode(benchmark::State& state) const char* addr = "17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem"; std::vector<unsigned char> vch; while (state.KeepRunning()) { - (void) DecodeBase58(addr, vch); + (void) DecodeBase58(addr, vch, 64); } } diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 592fcbe8dd..1035e730b8 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -256,7 +256,6 @@ public: return batch[ID_BLOCKCHAININFO]; } result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]); - result.pushKV("protocolversion", batch[ID_NETWORKINFO]["result"]["protocolversion"]); result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]); result.pushKV("headers", batch[ID_BLOCKCHAININFO]["result"]["headers"]); result.pushKV("verificationprogress", batch[ID_BLOCKCHAININFO]["result"]["verificationprogress"]); @@ -266,9 +265,7 @@ public: result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]); result.pushKV("chain", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"])); if (!batch[ID_WALLETINFO]["result"].isNull()) { - result.pushKV("walletversion", batch[ID_WALLETINFO]["result"]["walletversion"]); result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]); - result.pushKV("keypoololdest", batch[ID_WALLETINFO]["result"]["keypoololdest"]); result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]); if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) { result.pushKV("unlocked_until", batch[ID_WALLETINFO]["result"]["unlocked_until"]); diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index 917ecd71c5..a2013edc05 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -41,8 +41,8 @@ static bool WalletAppInit(int argc, char* argv[]) } if (argc < 2 || HelpRequested(gArgs)) { std::string usage = strprintf("%s bitcoin-wallet version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n\n" + - "wallet-tool is an offline tool for creating and interacting with Bitcoin Core wallet files.\n" + - "By default wallet-tool will act on wallets in the default mainnet wallet directory in the datadir.\n" + + "bitcoin-wallet is an offline tool for creating and interacting with Bitcoin Core wallet files.\n" + + "By default bitcoin-wallet will act on wallets in the default mainnet wallet directory in the datadir.\n" + "To change the target wallet, use the -datadir, -wallet and -testnet/-regtest arguments.\n\n" + "Usage:\n" + " bitcoin-wallet [options] <command>\n\n" + diff --git a/src/blockfilter.cpp b/src/blockfilter.cpp index 787390be31..5ad22d46eb 100644 --- a/src/blockfilter.cpp +++ b/src/blockfilter.cpp @@ -4,6 +4,7 @@ #include <mutex> #include <sstream> +#include <set> #include <blockfilter.h> #include <crypto/siphash.h> @@ -221,15 +222,14 @@ bool BlockFilterTypeByName(const std::string& name, BlockFilterType& filter_type return false; } -const std::vector<BlockFilterType>& AllBlockFilterTypes() +const std::set<BlockFilterType>& AllBlockFilterTypes() { - static std::vector<BlockFilterType> types; + static std::set<BlockFilterType> types; static std::once_flag flag; std::call_once(flag, []() { - types.reserve(g_filter_types.size()); for (auto entry : g_filter_types) { - types.push_back(entry.first); + types.insert(entry.first); } }); diff --git a/src/blockfilter.h b/src/blockfilter.h index 914b94fec1..828204b875 100644 --- a/src/blockfilter.h +++ b/src/blockfilter.h @@ -7,6 +7,7 @@ #include <stdint.h> #include <string> +#include <set> #include <unordered_set> #include <vector> @@ -97,7 +98,7 @@ const std::string& BlockFilterTypeName(BlockFilterType filter_type); bool BlockFilterTypeByName(const std::string& name, BlockFilterType& filter_type); /** Get a list of known filter types. */ -const std::vector<BlockFilterType>& AllBlockFilterTypes(); +const std::set<BlockFilterType>& AllBlockFilterTypes(); /** Get a comma-separated list of known filter type names. */ const std::string& ListBlockFilterTypes(); diff --git a/src/consensus/validation.h b/src/consensus/validation.h index e602b9d5f3..3401eb64ca 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -114,7 +114,7 @@ inline ValidationState::~ValidationState() {}; class TxValidationState : public ValidationState { private: - TxValidationResult m_result; + TxValidationResult m_result = TxValidationResult::TX_RESULT_UNSET; public: bool Invalid(TxValidationResult result, const std::string &reject_reason="", @@ -129,7 +129,7 @@ public: class BlockValidationState : public ValidationState { private: - BlockValidationResult m_result; + BlockValidationResult m_result = BlockValidationResult::BLOCK_RESULT_UNSET; public: bool Invalid(BlockValidationResult result, const std::string &reject_reason="", diff --git a/src/cuckoocache.h b/src/cuckoocache.h index 674f47b956..4ad5818cdc 100644 --- a/src/cuckoocache.h +++ b/src/cuckoocache.h @@ -6,11 +6,11 @@ #define BITCOIN_CUCKOOCACHE_H #include <array> -#include <algorithm> #include <atomic> -#include <cstring> #include <cmath> +#include <cstring> #include <memory> +#include <utility> #include <vector> @@ -11,7 +11,6 @@ #include <ext/stdio_filebuf.h> #endif -#define BOOST_FILESYSTEM_NO_DEPRECATED #include <boost/filesystem.hpp> #include <boost/filesystem/fstream.hpp> diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 0437f0c7de..77e09bd5c7 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -15,8 +15,13 @@ #include <util/translation.h> #include <walletinitinterface.h> +#include <algorithm> +#include <iterator> +#include <map> #include <memory> #include <stdio.h> +#include <set> +#include <string> #include <boost/algorithm/string.hpp> // boost::trim @@ -64,6 +69,9 @@ private: static std::string strRPCUserColonPass; /* Stored RPC timer interface (for unregistration) */ static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface; +/* RPC Auth Whitelist */ +static std::map<std::string, std::set<std::string>> g_rpc_whitelist; +static bool g_rpc_whitelist_default = false; static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id) { @@ -183,18 +191,45 @@ static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &) jreq.URI = req->GetURI(); std::string strReply; + bool user_has_whitelist = g_rpc_whitelist.count(jreq.authUser); + if (!user_has_whitelist && g_rpc_whitelist_default) { + LogPrintf("RPC User %s not allowed to call any methods\n", jreq.authUser); + req->WriteReply(HTTP_FORBIDDEN); + return false; + // singleton request - if (valRequest.isObject()) { + } else if (valRequest.isObject()) { jreq.parse(valRequest); - + if (user_has_whitelist && !g_rpc_whitelist[jreq.authUser].count(jreq.strMethod)) { + LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, jreq.strMethod); + req->WriteReply(HTTP_FORBIDDEN); + return false; + } UniValue result = tableRPC.execute(jreq); // Send reply strReply = JSONRPCReply(result, NullUniValue, jreq.id); // array of requests - } else if (valRequest.isArray()) + } else if (valRequest.isArray()) { + if (user_has_whitelist) { + for (unsigned int reqIdx = 0; reqIdx < valRequest.size(); reqIdx++) { + if (!valRequest[reqIdx].isObject()) { + throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); + } else { + const UniValue& request = valRequest[reqIdx].get_obj(); + // Parse method + std::string strMethod = find_value(request, "method").get_str(); + if (!g_rpc_whitelist[jreq.authUser].count(strMethod)) { + LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, strMethod); + req->WriteReply(HTTP_FORBIDDEN); + return false; + } + } + } + } strReply = JSONRPCExecBatch(jreq, valRequest.get_array()); + } else throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); @@ -229,6 +264,27 @@ static bool InitRPCAuthentication() { LogPrintf("Using rpcauth authentication.\n"); } + + g_rpc_whitelist_default = gArgs.GetBoolArg("-rpcwhitelistdefault", gArgs.IsArgSet("-rpcwhitelist")); + for (const std::string& strRPCWhitelist : gArgs.GetArgs("-rpcwhitelist")) { + auto pos = strRPCWhitelist.find(':'); + std::string strUser = strRPCWhitelist.substr(0, pos); + bool intersect = g_rpc_whitelist.count(strUser); + std::set<std::string>& whitelist = g_rpc_whitelist[strUser]; + if (pos != std::string::npos) { + std::string strWhitelist = strRPCWhitelist.substr(pos + 1); + std::set<std::string> new_whitelist; + boost::split(new_whitelist, strWhitelist, boost::is_any_of(", ")); + if (intersect) { + std::set<std::string> tmp_whitelist; + std::set_intersection(new_whitelist.begin(), new_whitelist.end(), + whitelist.begin(), whitelist.end(), std::inserter(tmp_whitelist, tmp_whitelist.end())); + new_whitelist = std::move(tmp_whitelist); + } + whitelist = std::move(new_whitelist); + } + } + return true; } diff --git a/src/init.cpp b/src/init.cpp index 60209eb60a..ab5e3b9342 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -58,6 +58,7 @@ #include <stdint.h> #include <stdio.h> +#include <set> #ifndef WIN32 #include <attributes.h> @@ -281,7 +282,6 @@ void Shutdown(NodeContext& node) node.chain_clients.clear(); UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); - GetMainSignals().UnregisterWithMempoolSignals(mempool); globalVerifyHandle.reset(); ECC_Stop(); if (node.mempool) node.mempool = nullptr; @@ -498,7 +498,7 @@ void SetupServerArgs() gArgs.AddArg("-logtimestamps", strprintf("Prepend debug output with timestamp (default: %u)", DEFAULT_LOGTIMESTAMPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-logthreadnames", strprintf("Prepend debug output with name of the originating thread (only available on platforms supporting thread_local) (default: %u)", DEFAULT_LOGTHREADNAMES), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-logtimemicros", strprintf("Add microsecond precision to debug timestamps (default: %u)", DEFAULT_LOGTIMEMICROS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-mocktime=<n>", "Replace actual time with <n> seconds since epoch (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-printpriority", strprintf("Log transaction fee per kB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); @@ -535,6 +535,8 @@ void SetupServerArgs() gArgs.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); gArgs.AddArg("-rpcthreads=<n>", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcwhitelist=<whitelist>", "Set a whitelist to filter incoming RPC calls for a specific user. The field <whitelist> comes in the format: <USERNAME>:<rpc 1>,<rpc 2>,...,<rpc n>. If multiple whitelists are set for a given user, they are set-intersected. See -rpcwhitelistdefault documentation for information on default whitelist behavior.", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcwhitelistdefault", "Sets default behavior for rpc whitelisting. Unless rpcwhitelistdefault is set to 0, if any -rpcwhitelist is set, the rpc server acts as if all rpc users are subject to empty-unless-otherwise-specified whitelists. If rpcwhitelistdefault is set to 1 and no -rpcwhitelist is set, rpc server acts as if all rpc users are subject to empty whitelists.", ArgsManager::ALLOW_BOOL, OptionsCategory::RPC); gArgs.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); gArgs.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); @@ -847,7 +849,7 @@ int nUserMaxConnections; int nFD; ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED); int64_t peer_connect_timeout; -std::vector<BlockFilterType> g_enabled_filter_types; +std::set<BlockFilterType> g_enabled_filter_types; } // namespace @@ -935,13 +937,12 @@ bool AppInitParameterInteraction() g_enabled_filter_types = AllBlockFilterTypes(); } else if (blockfilterindex_value != "0") { const std::vector<std::string> names = gArgs.GetArgs("-blockfilterindex"); - g_enabled_filter_types.reserve(names.size()); for (const auto& name : names) { BlockFilterType filter_type; if (!BlockFilterTypeByName(name, filter_type)) { return InitError(strprintf(_("Unknown -blockfilterindex value %s.").translated, name)); } - g_enabled_filter_types.push_back(filter_type); + g_enabled_filter_types.insert(filter_type); } } @@ -1263,7 +1264,6 @@ bool AppInitMain(NodeContext& node) }, 60000); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); - GetMainSignals().RegisterWithMempoolSignals(mempool); // Create client interfaces for wallets that are supposed to be loaded // according to -wallet and -disablewallet options. This only constructs diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 701a748e55..94c6e8c7b7 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -18,8 +18,9 @@ #include <wallet/feebumper.h> #include <wallet/fees.h> #include <wallet/ismine.h> -#include <wallet/rpcwallet.h> #include <wallet/load.h> +#include <wallet/psbtwallet.h> +#include <wallet/rpcwallet.h> #include <wallet/wallet.h> #include <memory> @@ -116,8 +117,22 @@ public: std::string error; return m_wallet->GetNewDestination(type, label, dest, error); } - bool getPubKey(const CKeyID& address, CPubKey& pub_key) override { return m_wallet->GetLegacyScriptPubKeyMan()->GetPubKey(address, pub_key); } - bool getPrivKey(const CKeyID& address, CKey& key) override { return m_wallet->GetLegacyScriptPubKeyMan()->GetKey(address, key); } + bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) override + { + const SigningProvider* provider = m_wallet->GetSigningProvider(script); + if (provider) { + return provider->GetPubKey(address, pub_key); + } + return false; + } + bool getPrivKey(const CScript& script, const CKeyID& address, CKey& key) override + { + const SigningProvider* provider = m_wallet->GetSigningProvider(script); + if (provider) { + return provider->GetKey(address, key); + } + return false; + } bool isSpendable(const CTxDestination& dest) override { return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; } bool haveWatchOnly() override { @@ -343,6 +358,14 @@ public: } return {}; } + TransactionError fillPSBT(PartiallySignedTransaction& psbtx, + bool& complete, + int sighash_type = 1 /* SIGHASH_ALL */, + bool sign = true, + bool bip32derivs = false) override + { + return FillPSBT(m_wallet.get(), psbtx, complete, sighash_type, sign, bip32derivs); + } WalletBalances getBalances() override { const auto bal = m_wallet->GetBalance(); diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index a96b93b4c3..8d2b8a2eca 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -14,6 +14,7 @@ #include <functional> #include <map> #include <memory> +#include <psbt.h> #include <stdint.h> #include <string> #include <tuple> @@ -81,10 +82,10 @@ public: virtual bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) = 0; //! Get public key. - virtual bool getPubKey(const CKeyID& address, CPubKey& pub_key) = 0; + virtual bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) = 0; //! Get private key. - virtual bool getPrivKey(const CKeyID& address, CKey& key) = 0; + virtual bool getPrivKey(const CScript& script, const CKeyID& address, CKey& key) = 0; //! Return whether wallet has private key. virtual bool isSpendable(const CTxDestination& dest) = 0; @@ -194,6 +195,13 @@ public: bool& in_mempool, int& num_blocks) = 0; + //! Fill PSBT. + virtual TransactionError fillPSBT(PartiallySignedTransaction& psbtx, + bool& complete, + int sighash_type = 1 /* SIGHASH_ALL */, + bool sign = true, + bool bip32derivs = false) = 0; + //! Get balances. virtual WalletBalances getBalances() = 0; diff --git a/src/key_io.cpp b/src/key_io.cpp index 363055d6b3..af06db7343 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -73,7 +73,7 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par { std::vector<unsigned char> data; uint160 hash; - if (DecodeBase58Check(str, data)) { + if (DecodeBase58Check(str, data, 21)) { // base58-encoded Bitcoin addresses. // Public-key-hash-addresses have version 0 (or 111 testnet). // The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. @@ -133,7 +133,7 @@ CKey DecodeSecret(const std::string& str) { CKey key; std::vector<unsigned char> data; - if (DecodeBase58Check(str, data)) { + if (DecodeBase58Check(str, data, 34)) { const std::vector<unsigned char>& privkey_prefix = Params().Base58Prefix(CChainParams::SECRET_KEY); if ((data.size() == 32 + privkey_prefix.size() || (data.size() == 33 + privkey_prefix.size() && data.back() == 1)) && std::equal(privkey_prefix.begin(), privkey_prefix.end(), data.begin())) { @@ -164,7 +164,7 @@ CExtPubKey DecodeExtPubKey(const std::string& str) { CExtPubKey key; std::vector<unsigned char> data; - if (DecodeBase58Check(str, data)) { + if (DecodeBase58Check(str, data, 78)) { const std::vector<unsigned char>& prefix = Params().Base58Prefix(CChainParams::EXT_PUBLIC_KEY); if (data.size() == BIP32_EXTKEY_SIZE + prefix.size() && std::equal(prefix.begin(), prefix.end(), data.begin())) { key.Decode(data.data() + prefix.size()); @@ -187,7 +187,7 @@ CExtKey DecodeExtKey(const std::string& str) { CExtKey key; std::vector<unsigned char> data; - if (DecodeBase58Check(str, data)) { + if (DecodeBase58Check(str, data, 78)) { const std::vector<unsigned char>& prefix = Params().Base58Prefix(CChainParams::EXT_SECRET_KEY); if (data.size() == BIP32_EXTKEY_SIZE + prefix.size() && std::equal(prefix.begin(), prefix.end(), data.begin())) { key.Decode(data.data() + prefix.size()); diff --git a/src/net.cpp b/src/net.cpp index 84692d2a79..99dae88bab 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -16,6 +16,7 @@ #include <crypto/sha256.h> #include <netbase.h> #include <net_permissions.h> +#include <random.h> #include <scheduler.h> #include <ui_interface.h> #include <util/strencodings.h> @@ -445,6 +446,9 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false, block_relay_only); pnode->AddRef(); + // We're making a new connection, harvest entropy from the time (and our peer count) + RandAddEvent((uint32_t)id); + return pnode; } @@ -693,6 +697,9 @@ CNetMessage V1TransportDeserializer::GetMessage(const CMessageHeader::MessageSta msg.m_message_size = hdr.nMessageSize; msg.m_raw_message_size = hdr.nMessageSize + CMessageHeader::HEADER_SIZE; + // We just received a message off the wire, harvest entropy from the time (and the message checksum) + RandAddEvent(ReadLE32(hash.begin())); + msg.m_valid_checksum = (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) == 0); if (!msg.m_valid_checksum) { LogPrint(BCLog::NET, "CHECKSUM ERROR (%s, %u bytes), expected %s was %s\n", @@ -1017,6 +1024,9 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { LOCK(cs_vNodes); vNodes.push_back(pnode); } + + // We received a new connection, harvest entropy from the time (and our peer count) + RandAddEvent((uint32_t)id); } void CConnman::DisconnectNodes() diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index 12559c5a5f..9a30c3f083 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -7,6 +7,7 @@ #include <node/psbt.h> #include <policy/policy.h> #include <policy/settings.h> +#include <tinyformat.h> #include <numeric> @@ -39,6 +40,11 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) calc_fee = false; } + if (!utxo.IsNull() && utxo.scriptPubKey.IsUnspendable()) { + result.SetInvalid(strprintf("PSBT is not valid. Input %u spends unspendable output", i)); + return result; + } + // Check if it is final if (!utxo.IsNull() && !PSBTInputSigned(input)) { input_analysis.is_final = false; diff --git a/src/node/psbt.h b/src/node/psbt.h index e04366a20f..7384dc415c 100644 --- a/src/node/psbt.h +++ b/src/node/psbt.h @@ -30,6 +30,17 @@ struct PSBTAnalysis { Optional<CAmount> fee; //!< Amount of fee being paid by the transaction std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next + std::string error; //!< Error message + + void SetInvalid(std::string err_msg) + { + estimated_vsize = nullopt; + estimated_feerate = nullopt; + fee = nullopt; + inputs.clear(); + next = PSBTRole::CREATOR; + error = err_msg; + } }; /** diff --git a/src/prevector.h b/src/prevector.h index d307495fbe..4fb07688ff 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -13,6 +13,7 @@ #include <algorithm> #include <cstddef> #include <type_traits> +#include <utility> #pragma pack(push, 1) /** Implements a drop-in replacement for std::vector<T> which stores up to N diff --git a/src/protocol.h b/src/protocol.h index 3032310fa1..db07efb9f9 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -237,6 +237,7 @@ const std::vector<std::string> &getAllNetMessageTypes(); /** nServices flags */ enum ServiceFlags : uint64_t { + // NOTE: When adding here, be sure to update qt/guiutil.cpp's formatServicesStr too // Nothing NODE_NONE = 0, // NODE_NETWORK means that the node is capable of serving the complete block chain. It is currently diff --git a/src/psbt.cpp b/src/psbt.cpp index c306079b1e..9ede62efdf 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -348,6 +348,7 @@ TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector std::string PSBTRoleName(PSBTRole role) { switch (role) { + case PSBTRole::CREATOR: return "creator"; case PSBTRole::UPDATER: return "updater"; case PSBTRole::SIGNER: return "signer"; case PSBTRole::FINALIZER: return "finalizer"; diff --git a/src/psbt.h b/src/psbt.h index bcff66f7a1..d507d5b6b7 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -560,6 +560,7 @@ struct PartiallySignedTransaction }; enum class PSBTRole { + CREATOR, UPDATER, SIGNER, FINALIZER, diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 10953adc35..21e51a380d 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -171,6 +171,7 @@ bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchS return false; secp256k1_pubkey pubkey; secp256k1_ecdsa_signature sig; + assert(secp256k1_context_verify && "secp256k1_context_verify must be initialized to use CPubKey."); if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pubkey, vch, size())) { return false; } @@ -190,6 +191,7 @@ bool CPubKey::RecoverCompact(const uint256 &hash, const std::vector<unsigned cha bool fComp = ((vchSig[0] - 27) & 4) != 0; secp256k1_pubkey pubkey; secp256k1_ecdsa_recoverable_signature sig; + assert(secp256k1_context_verify && "secp256k1_context_verify must be initialized to use CPubKey."); if (!secp256k1_ecdsa_recoverable_signature_parse_compact(secp256k1_context_verify, &sig, &vchSig[1], recid)) { return false; } @@ -207,6 +209,7 @@ bool CPubKey::IsFullyValid() const { if (!IsValid()) return false; secp256k1_pubkey pubkey; + assert(secp256k1_context_verify && "secp256k1_context_verify must be initialized to use CPubKey."); return secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pubkey, vch, size()); } @@ -214,6 +217,7 @@ bool CPubKey::Decompress() { if (!IsValid()) return false; secp256k1_pubkey pubkey; + assert(secp256k1_context_verify && "secp256k1_context_verify must be initialized to use CPubKey."); if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pubkey, vch, size())) { return false; } @@ -232,6 +236,7 @@ bool CPubKey::Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChi BIP32Hash(cc, nChild, *begin(), begin()+1, out); memcpy(ccChild.begin(), out+32, 32); secp256k1_pubkey pubkey; + assert(secp256k1_context_verify && "secp256k1_context_verify must be initialized to use CPubKey."); if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pubkey, vch, size())) { return false; } @@ -273,6 +278,7 @@ bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const { /* static */ bool CPubKey::CheckLowS(const std::vector<unsigned char>& vchSig) { secp256k1_ecdsa_signature sig; + assert(secp256k1_context_verify && "secp256k1_context_verify must be initialized to use CPubKey."); if (!ecdsa_signature_parse_der_lax(secp256k1_context_verify, &sig, vchSig.data(), vchSig.size())) { return false; } diff --git a/src/qt/bantablemodel.cpp b/src/qt/bantablemodel.cpp index 48201b420e..b6c6984b10 100644 --- a/src/qt/bantablemodel.cpp +++ b/src/qt/bantablemodel.cpp @@ -8,7 +8,7 @@ #include <net_types.h> // For banmap_t #include <qt/clientmodel.h> -#include <algorithm> +#include <utility> #include <QDebug> #include <QList> diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 2aeba6d82c..8444984b27 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -341,6 +341,7 @@ void BitcoinGUI::createActions() m_close_wallet_action->setStatusTip(tr("Close wallet")); m_create_wallet_action = new QAction(tr("Create Wallet..."), this); + m_create_wallet_action->setEnabled(false); m_create_wallet_action->setStatusTip(tr("Create a new wallet")); showHelpMessageAction = new QAction(tr("&Command-line options"), this); @@ -618,6 +619,7 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller) m_wallet_controller = wallet_controller; + m_create_wallet_action->setEnabled(true); m_open_wallet_action->setEnabled(true); m_open_wallet_action->setMenu(m_open_wallet_menu); diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index f928f1ca2a..3302dde4ed 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -468,7 +468,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) { CPubKey pubkey; PKHash *pkhash = boost::get<PKHash>(&address); - if (pkhash && model->wallet().getPubKey(CKeyID(*pkhash), pubkey)) + if (pkhash && model->wallet().getPubKey(out.txout.scriptPubKey, CKeyID(*pkhash), pubkey)) { nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); } diff --git a/src/qt/forms/modaloverlay.ui b/src/qt/forms/modaloverlay.ui index b5a69c578d..da19a6fa2e 100644 --- a/src/qt/forms/modaloverlay.ui +++ b/src/qt/forms/modaloverlay.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ModalOverlay</class> - <widget class="ModalOverlay" name="ModalOverlay"> + <widget class="QWidget" name="ModalOverlay"> <property name="geometry"> <rect> <x>0</x> @@ -369,14 +369,6 @@ QLabel { color: rgb(40,40,40); }</string> </item> </layout> </widget> - <customwidgets> - <customwidget> - <class>ModalOverlay</class> - <extends>QWidget</extends> - <header>qt/modaloverlay.h</header> - <container>1</container> - </customwidget> - </customwidgets> <resources/> <connections/> </ui> diff --git a/src/qt/forms/openuridialog.ui b/src/qt/forms/openuridialog.ui index 2acec314fd..1b7291ab9d 100644 --- a/src/qt/forms/openuridialog.ui +++ b/src/qt/forms/openuridialog.ui @@ -24,7 +24,11 @@ </widget> </item> <item> - <widget class="QValidatedLineEdit" name="uriEdit"/> + <widget class="QValidatedLineEdit" name="uriEdit"> + <property name="placeholderText"> + <string notr="true">bitcoin:</string> + </property> + </widget> </item> </layout> </item> diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 240a7a7e92..fea759dee0 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -685,6 +685,9 @@ <property name="toolTip"> <string>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 |.</string> </property> + <property name="placeholderText"> + <string notr="true">https://example.com/tx/%s</string> + </property> </widget> </item> </layout> diff --git a/src/qt/forms/receivecoinsdialog.ui b/src/qt/forms/receivecoinsdialog.ui index 64f52fcf45..7dbee6d689 100644 --- a/src/qt/forms/receivecoinsdialog.ui +++ b/src/qt/forms/receivecoinsdialog.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ReceiveCoinsDialog</class> - <widget class="QWidget" name="ReceiveCoinsDialog"> + <widget class="QDialog" name="ReceiveCoinsDialog"> <property name="geometry"> <rect> <x>0</x> diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 7190d59240..cfd4bf33d4 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -1190,7 +1190,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <number>3</number> </property> <item> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="labelBalanceName"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <horstretch>0</horstretch> diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index 843d909f68..934363af1f 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -144,6 +144,9 @@ <property name="toolTip"> <string>Enter a label for this address to add it to the list of used addresses</string> </property> + <property name="placeholderText"> + <string>Enter a label for this address to add it to the list of used addresses</string> + </property> </widget> </item> <item row="2" column="0"> diff --git a/src/qt/forms/signverifymessagedialog.ui b/src/qt/forms/signverifymessagedialog.ui index 202edf27d4..f42d19093b 100644 --- a/src/qt/forms/signverifymessagedialog.ui +++ b/src/qt/forms/signverifymessagedialog.ui @@ -121,6 +121,9 @@ </property> <item> <widget class="QLineEdit" name="signatureOut_SM"> + <property name="placeholderText"> + <string>Click "Sign Message" to generate signature</string> + </property> <property name="font"> <font> <italic>true</italic> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 31e62ab63d..843de651e5 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -54,10 +54,7 @@ #include <QUrlQuery> #if defined(Q_OS_MAC) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#include <CoreServices/CoreServices.h> #include <QProcess> void ForceActivation(); @@ -691,87 +688,6 @@ bool SetStartOnSystemStartup(bool fAutoStart) return true; } - -#elif defined(Q_OS_MAC) && defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED <= 101100 -// based on: https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m - -LSSharedFileListItemRef findStartupItemInList(CFArrayRef listSnapshot, LSSharedFileListRef list, CFURLRef findUrl) -{ - if (listSnapshot == nullptr) { - return nullptr; - } - - // loop through the list of startup items and try to find the bitcoin app - for(int i = 0; i < CFArrayGetCount(listSnapshot); i++) { - LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(listSnapshot, i); - UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; - CFURLRef currentItemURL = nullptr; - -#if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED >= 10100 - if(&LSSharedFileListItemCopyResolvedURL) - currentItemURL = LSSharedFileListItemCopyResolvedURL(item, resolutionFlags, nullptr); -#if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 10100 - else - LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, nullptr); -#endif -#else - LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, nullptr); -#endif - - if(currentItemURL) { - if (CFEqual(currentItemURL, findUrl)) { - // found - CFRelease(currentItemURL); - return item; - } - CFRelease(currentItemURL); - } - } - return nullptr; -} - -bool GetStartOnSystemStartup() -{ - CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); - if (bitcoinAppUrl == nullptr) { - return false; - } - - LSSharedFileListRef loginItems = LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems, nullptr); - CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(loginItems, nullptr); - bool res = (findStartupItemInList(listSnapshot, loginItems, bitcoinAppUrl) != nullptr); - CFRelease(bitcoinAppUrl); - CFRelease(loginItems); - CFRelease(listSnapshot); - return res; -} - -bool SetStartOnSystemStartup(bool fAutoStart) -{ - CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); - if (bitcoinAppUrl == nullptr) { - return false; - } - - LSSharedFileListRef loginItems = LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems, nullptr); - CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(loginItems, nullptr); - LSSharedFileListItemRef foundItem = findStartupItemInList(listSnapshot, loginItems, bitcoinAppUrl); - - if(fAutoStart && !foundItem) { - // add bitcoin app to startup item list - LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, nullptr, nullptr, bitcoinAppUrl, nullptr, nullptr); - } - else if(!fAutoStart && foundItem) { - // remove item - LSSharedFileListItemRemove(loginItems, foundItem); - } - - CFRelease(bitcoinAppUrl); - CFRelease(loginItems); - CFRelease(listSnapshot); - return true; -} -#pragma GCC diagnostic pop #else bool GetStartOnSystemStartup() { return false; } @@ -815,32 +731,33 @@ QString formatDurationStr(int secs) return strList.join(" "); } +QString serviceFlagToStr(const quint64 mask, const int bit) +{ + switch (ServiceFlags(mask)) { + case NODE_NONE: abort(); // impossible + case NODE_NETWORK: return "NETWORK"; + case NODE_GETUTXO: return "GETUTXO"; + case NODE_BLOOM: return "BLOOM"; + case NODE_WITNESS: return "WITNESS"; + case NODE_NETWORK_LIMITED: return "NETWORK_LIMITED"; + // Not using default, so we get warned when a case is missing + } + if (bit < 8) { + return QString("%1[%2]").arg("UNKNOWN").arg(mask); + } else { + return QString("%1[2^%2]").arg("UNKNOWN").arg(bit); + } +} + QString formatServicesStr(quint64 mask) { QStringList strList; - // Just scan the last 8 bits for now. - for (int i = 0; i < 8; i++) { - uint64_t check = 1 << i; + for (int i = 0; i < 64; i++) { + uint64_t check = 1LL << i; if (mask & check) { - switch (check) - { - case NODE_NETWORK: - strList.append("NETWORK"); - break; - case NODE_GETUTXO: - strList.append("GETUTXO"); - break; - case NODE_BLOOM: - strList.append("BLOOM"); - break; - case NODE_WITNESS: - strList.append("WITNESS"); - break; - default: - strList.append(QString("%1[%2]").arg("UNKNOWN").arg(check)); - } + strList.append(serviceFlagToStr(check, i)); } } diff --git a/src/qt/openuridialog.cpp b/src/qt/openuridialog.cpp index 6a0143ac7e..9dc64bb23a 100644 --- a/src/qt/openuridialog.cpp +++ b/src/qt/openuridialog.cpp @@ -15,7 +15,6 @@ OpenURIDialog::OpenURIDialog(QWidget *parent) : ui(new Ui::OpenURIDialog) { ui->setupUi(this); - ui->uriEdit->setPlaceholderText("bitcoin:"); } OpenURIDialog::~OpenURIDialog() diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 57cafaaac0..3fffb8a288 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -71,17 +71,17 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : #ifdef Q_OS_MAC /* remove Window tab on Mac */ ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWindow)); -#if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED > 101100 - /* hide launch at startup option if compiled against macOS > 10.11 (removed API) */ + /* hide launch at startup option on macOS */ ui->bitcoinAtStartup->setVisible(false); ui->verticalLayout_Main->removeWidget(ui->bitcoinAtStartup); ui->verticalLayout_Main->removeItem(ui->horizontalSpacer_0_Main); #endif -#endif - /* remove Wallet tab in case of -disablewallet */ + /* remove Wallet tab and 3rd party-URL textbox in case of -disablewallet */ if (!enableWallet) { ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWallet)); + ui->thirdPartyTxUrlsLabel->setVisible(false); + ui->thirdPartyTxUrls->setVisible(false); } /* Display elements init */ @@ -110,8 +110,6 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : ui->lang->addItem(locale.nativeLanguageName() + QString(" (") + langStr + QString(")"), QVariant(langStr)); } } - ui->thirdPartyTxUrls->setPlaceholderText("https://example.com/tx/%s"); - ui->unit->setModel(new BitcoinUnits(this)); /* Widget-to-option mapper */ diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index af2a1bb0e5..514ff35bcd 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -10,7 +10,7 @@ #include <interfaces/node.h> -#include <algorithm> +#include <utility> #include <QDebug> #include <QList> diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp index 6d933f46d6..18fa5f417f 100644 --- a/src/qt/recentrequeststablemodel.cpp +++ b/src/qt/recentrequeststablemodel.cpp @@ -12,8 +12,7 @@ #include <clientversion.h> #include <streams.h> -#include <algorithm> - +#include <utility> RecentRequestsTableModel::RecentRequestsTableModel(WalletModel *parent) : QAbstractTableModel(parent), walletModel(parent) @@ -214,10 +213,10 @@ void RecentRequestsTableModel::updateDisplayUnit() updateAmountColumnTitle(); } -bool RecentRequestEntryLessThan::operator()(RecentRequestEntry &left, RecentRequestEntry &right) const +bool RecentRequestEntryLessThan::operator()(const RecentRequestEntry& left, const RecentRequestEntry& right) const { - RecentRequestEntry *pLeft = &left; - RecentRequestEntry *pRight = &right; + const RecentRequestEntry* pLeft = &left; + const RecentRequestEntry* pRight = &right; if (order == Qt::DescendingOrder) std::swap(pLeft, pRight); diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h index 30578b7a03..f5085f7268 100644 --- a/src/qt/recentrequeststablemodel.h +++ b/src/qt/recentrequeststablemodel.h @@ -45,7 +45,7 @@ class RecentRequestEntryLessThan public: RecentRequestEntryLessThan(int nColumn, Qt::SortOrder fOrder): column(nColumn), order(fOrder) {} - bool operator()(RecentRequestEntry &left, RecentRequestEntry &right) const; + bool operator()(const RecentRequestEntry& left, const RecentRequestEntry& right) const; private: int column; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 8edcca684d..f1ea3e23e5 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -21,11 +21,13 @@ #include <chainparams.h> #include <interfaces/node.h> #include <key_io.h> -#include <wallet/coincontrol.h> -#include <ui_interface.h> -#include <txmempool.h> #include <policy/fees.h> +#include <txmempool.h> +#include <ui_interface.h> +#include <wallet/coincontrol.h> #include <wallet/fees.h> +#include <wallet/psbtwallet.h> +#include <wallet/wallet.h> #include <QFontMetrics> #include <QScrollBar> @@ -186,6 +188,11 @@ void SendCoinsDialog::setModel(WalletModel *_model) // set default rbf checkbox state ui->optInRBF->setCheckState(Qt::Checked); + if (model->privateKeysDisabled()) { + ui->sendButton->setText(tr("Cr&eate Unsigned")); + ui->sendButton->setToolTip(tr("Creates a Partially Signed Bitcoin Transaction (PSBT) for use with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME)); + } + // set the smartfee-sliders default value (wallets default conf.target or last stored value) QSettings settings; if (settings.value("nSmartFeeSliderPosition").toInt() != 0) { @@ -305,9 +312,19 @@ void SendCoinsDialog::on_sendButton_clicked() formatted.append(recipientElement); } - QString questionString = tr("Are you sure you want to send?"); + QString questionString; + if (model->privateKeysDisabled()) { + questionString.append(tr("Do you want to draft this transaction?")); + } else { + questionString.append(tr("Are you sure you want to send?")); + } + questionString.append("<br /><span style='font-size:10pt;'>"); - questionString.append(tr("Please, review your transaction.")); + if (model->privateKeysDisabled()) { + questionString.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME)); + } else { + questionString.append(tr("Please, review your transaction.")); + } questionString.append("</span>%1"); if(txFee > 0) @@ -358,8 +375,9 @@ void SendCoinsDialog::on_sendButton_clicked() } else { questionString = questionString.arg("<br /><br />" + formatted.at(0)); } - - SendConfirmationDialog confirmationDialog(tr("Confirm send coins"), questionString, informative_text, detailed_text, SEND_CONFIRM_DELAY, this); + const QString confirmation = model->privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins"); + const QString confirmButtonText = model->privateKeysDisabled() ? tr("Copy PSBT to clipboard") : tr("Send"); + SendConfirmationDialog confirmationDialog(confirmation, questionString, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this); confirmationDialog.exec(); QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result()); @@ -369,17 +387,35 @@ void SendCoinsDialog::on_sendButton_clicked() return; } - // now send the prepared transaction - WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); - // process sendStatus and on error generate message shown to user - processSendCoinsReturn(sendStatus); + bool send_failure = false; + if (model->privateKeysDisabled()) { + CMutableTransaction mtx = CMutableTransaction{*(currentTransaction.getWtx())}; + PartiallySignedTransaction psbtx(mtx); + bool complete = false; + const TransactionError err = model->wallet().fillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */); + assert(!complete); + assert(err == TransactionError::OK); + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); + Q_EMIT message(tr("PSBT copied"), "Copied to clipboard", CClientUIInterface::MSG_INFORMATION); + } else { + // now send the prepared transaction + WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); + // process sendStatus and on error generate message shown to user + processSendCoinsReturn(sendStatus); - if (sendStatus.status == WalletModel::OK) - { + if (sendStatus.status == WalletModel::OK) { + Q_EMIT coinsSent(currentTransaction.getWtx()->GetHash()); + } else { + send_failure = true; + } + } + if (!send_failure) { accept(); CoinControlDialog::coinControl()->UnSelectAll(); coinControlUpdateLabels(); - Q_EMIT coinsSent(currentTransaction.getWtx()->GetHash()); } fNewRecipientAllowed = true; } @@ -526,7 +562,12 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances) { if(model && model->getOptionsModel()) { - ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balances.balance)); + CAmount balance = balances.balance; + if (model->privateKeysDisabled()) { + balance = balances.watch_only_balance; + ui->labelBalanceName->setText(tr("Watch-only balance:")); + } + ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance)); } } @@ -611,6 +652,9 @@ void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry) coin_control = *CoinControlDialog::coinControl(); } + // Include watch-only for wallets without private key + coin_control.fAllowWatchOnly = model->privateKeysDisabled(); + // Calculate available amount to send. CAmount amount = model->wallet().getAvailableBalance(coin_control); for (int i = 0; i < ui->entries->count(); ++i) { @@ -663,6 +707,8 @@ void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl) // Either custom fee will be used or if not selected, the confirmation target from dropdown box ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex()); ctrl.m_signal_bip125_rbf = ui->optInRBF->isChecked(); + // Include watch-only for wallets without private key + ctrl.fAllowWatchOnly = model->privateKeysDisabled(); } void SendCoinsDialog::updateSmartFeeLabel() @@ -870,8 +916,8 @@ void SendCoinsDialog::coinControlUpdateLabels() } } -SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, QWidget* parent) - : QMessageBox(parent), secDelay(_secDelay) +SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, const QString& _confirmButtonText, QWidget* parent) + : QMessageBox(parent), secDelay(_secDelay), confirmButtonText(_confirmButtonText) { setIcon(QMessageBox::Question); setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines). @@ -908,11 +954,11 @@ void SendConfirmationDialog::updateYesButton() if(secDelay > 0) { yesButton->setEnabled(false); - yesButton->setText(tr("Send") + " (" + QString::number(secDelay) + ")"); + yesButton->setText(confirmButtonText + " (" + QString::number(secDelay) + ")"); } else { yesButton->setEnabled(true); - yesButton->setText(tr("Send")); + yesButton->setText(confirmButtonText); } } diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index c6c1816877..ccd8494613 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -108,7 +108,7 @@ class SendConfirmationDialog : public QMessageBox Q_OBJECT public: - SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, QWidget* parent = nullptr); + SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, const QString& confirmText = "Send", QWidget* parent = nullptr); int exec(); private Q_SLOTS: @@ -119,6 +119,7 @@ private: QAbstractButton *yesButton; QTimer countDownTimer; int secDelay; + QString confirmButtonText; }; #endif // BITCOIN_QT_SENDCOINSDIALOG_H diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index ad37e09114..610dfbb85a 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -37,7 +37,6 @@ SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *par if (platformStyle->getUseExtraSpacing()) ui->payToLayout->setSpacing(4); - ui->addAsLabel->setPlaceholderText(tr("Enter a label for this address to add it to your address book")); // normal bitcoin address field GUIUtil::setupAddressWidget(ui->payTo, this); diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 1d0e1323bc..81e9d33a60 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -35,8 +35,6 @@ SignVerifyMessageDialog::SignVerifyMessageDialog(const PlatformStyle *_platformS ui->verifyMessageButton_VM->setIcon(platformStyle->SingleColorIcon(":/icons/transaction_0")); ui->clearButton_VM->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); - ui->signatureOut_SM->setPlaceholderText(tr("Click \"Sign Message\" to generate signature")); - GUIUtil::setupAddressWidget(ui->addressIn_SM, this); GUIUtil::setupAddressWidget(ui->addressIn_VM, this); @@ -136,7 +134,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() } CKey key; - if (!model->wallet().getPrivKey(CKeyID(*pkhash), key)) + if (!model->wallet().getPrivKey(GetScriptForDestination(destination), CKeyID(*pkhash), key)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText(tr("Private key for the entered address is not available.")); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 7139042041..f6d2816ff8 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -171,6 +171,16 @@ 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(); + int unit = walletModel.getOptionsModel()->getDisplayUnit(); + CAmount balance = walletModel.wallet().getBalance(); + QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::separatorAlways); + QCOMPARE(balanceText, balanceComparison); + } + // Send two transactions, and verify they are added to transaction list. TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel(); QCOMPARE(transactionTableModel->rowCount({}), 105); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 33801d3907..fb92e29f21 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -183,7 +183,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact std::string strFailReason; auto& newTx = transaction.getWtx(); - newTx = m_wallet->createTransaction(vecSend, coinControl, true /* sign */, nChangePosRet, nFeeRequired, strFailReason); + newTx = m_wallet->createTransaction(vecSend, coinControl, !privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, strFailReason); transaction.setTransactionFee(nFeeRequired); if (fSubtractFeeFromAmount && newTx) transaction.reassignAmounts(nChangePosRet); diff --git a/src/random.cpp b/src/random.cpp index 47d76d8700..99927fc5d2 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -6,6 +6,7 @@ #include <random.h> #include <compat/cpuid.h> +#include <crypto/sha256.h> #include <crypto/sha512.h> #include <support/cleanse.h> #ifdef WIN32 @@ -178,7 +179,7 @@ static uint64_t GetRdSeed() noexcept /* Access to other hardware random number generators could be added here later, * assuming it is sufficiently fast (in the order of a few hundred CPU cycles). * Slower sources should probably be invoked separately, and/or only from - * RandAddSeedSleep (which is called during idle background operation). + * RandAddPeriodic (which is called once a minute). */ static void InitHardwareRand() {} static void ReportHardwareRand() {} @@ -359,6 +360,9 @@ class RNGState { uint64_t m_counter GUARDED_BY(m_mutex) = 0; bool m_strongly_seeded GUARDED_BY(m_mutex) = false; + Mutex m_events_mutex; + CSHA256 m_events_hasher GUARDED_BY(m_events_mutex); + public: RNGState() noexcept { @@ -369,6 +373,35 @@ public: { } + void AddEvent(uint32_t event_info) noexcept + { + LOCK(m_events_mutex); + + m_events_hasher.Write((const unsigned char *)&event_info, sizeof(event_info)); + // Get the low four bytes of the performance counter. This translates to roughly the + // subsecond part. + uint32_t perfcounter = (GetPerformanceCounter() & 0xffffffff); + m_events_hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter)); + } + + /** + * Feed (the hash of) all events added through AddEvent() to hasher. + */ + void SeedEvents(CSHA512& hasher) noexcept + { + // We use only SHA256 for the events hashing to get the ASM speedups we have for SHA256, + // since we want it to be fast as network peers may be able to trigger it repeatedly. + LOCK(m_events_mutex); + + unsigned char events_hash[32]; + m_events_hasher.Finalize(events_hash); + hasher.Write(events_hash, 32); + + // Re-initialize the hasher with the finalized state to use later. + m_events_hasher.Reset(); + m_events_hasher.Write(events_hash, 32); + } + /** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher. * * If this function has never been called with strong_seed = true, false is returned. @@ -415,17 +448,7 @@ RNGState& GetRNGState() noexcept /* A note on the use of noexcept in the seeding functions below: * - * None of the RNG code should ever throw any exception, with the sole exception - * of MilliSleep in SeedSleep, which can (and does) support interruptions which - * cause a boost::thread_interrupted to be thrown. - * - * This means that SeedSleep, and all functions that invoke it are throwing. - * However, we know that GetRandBytes() and GetStrongRandBytes() never trigger - * this sleeping logic, so they are noexcept. The same is true for all the - * GetRand*() functions that use GetRandBytes() indirectly. - * - * TODO: After moving away from interruptible boost-based thread management, - * everything can become noexcept here. + * None of the RNG code should ever throw any exception. */ static void SeedTimestamp(CSHA512& hasher) noexcept @@ -449,7 +472,7 @@ static void SeedFast(CSHA512& hasher) noexcept SeedTimestamp(hasher); } -static void SeedSlow(CSHA512& hasher) noexcept +static void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept { unsigned char buffer[32]; @@ -460,6 +483,9 @@ static void SeedSlow(CSHA512& hasher) noexcept GetOSRand(buffer); hasher.Write(buffer, sizeof(buffer)); + // Add the events hasher into the mix + rng.SeedEvents(hasher); + // High-precision timestamp. // // Note that we also commit to a timestamp in the Fast seeder, so we indirectly commit to a @@ -477,7 +503,7 @@ static void SeedStrengthen(CSHA512& hasher, RNGState& rng, int microseconds) noe Strengthen(strengthen_seed, microseconds, hasher); } -static void SeedPeriodic(CSHA512& hasher, RNGState& rng) +static void SeedPeriodic(CSHA512& hasher, RNGState& rng) noexcept { // Everything that the 'fast' seeder includes SeedFast(hasher); @@ -485,10 +511,13 @@ static void SeedPeriodic(CSHA512& hasher, RNGState& rng) // High-precision timestamp SeedTimestamp(hasher); + // Add the events hasher into the mix + rng.SeedEvents(hasher); + // Dynamic environment data (performance monitoring, ...) auto old_size = hasher.Size(); RandAddDynamicEnv(hasher); - LogPrintf("Feeding %i bytes of dynamic environment data into RNG\n", hasher.Size() - old_size); + LogPrint(BCLog::RAND, "Feeding %i bytes of dynamic environment data into RNG\n", hasher.Size() - old_size); // Strengthen for 10 ms SeedStrengthen(hasher, rng, 10000); @@ -500,7 +529,7 @@ static void SeedStartup(CSHA512& hasher, RNGState& rng) noexcept SeedHardwareSlow(hasher); // Everything that the 'slow' seeder includes. - SeedSlow(hasher); + SeedSlow(hasher, rng); // Dynamic environment data (performance monitoring, ...) auto old_size = hasher.Size(); @@ -508,7 +537,7 @@ static void SeedStartup(CSHA512& hasher, RNGState& rng) noexcept // Static environment data RandAddStaticEnv(hasher); - LogPrintf("Feeding %i bytes of environment data into RNG\n", hasher.Size() - old_size); + LogPrint(BCLog::RAND, "Feeding %i bytes of environment data into RNG\n", hasher.Size() - old_size); // Strengthen for 100 ms SeedStrengthen(hasher, rng, 100000); @@ -520,7 +549,7 @@ enum class RNGLevel { PERIODIC, //!< Called by RandAddPeriodic() }; -static void ProcRand(unsigned char* out, int num, RNGLevel level) +static void ProcRand(unsigned char* out, int num, RNGLevel level) noexcept { // Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available). RNGState& rng = GetRNGState(); @@ -533,7 +562,7 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level) SeedFast(hasher); break; case RNGLevel::SLOW: - SeedSlow(hasher); + SeedSlow(hasher, rng); break; case RNGLevel::PERIODIC: SeedPeriodic(hasher, rng); @@ -551,7 +580,8 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level) void GetRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::FAST); } void GetStrongRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::SLOW); } -void RandAddPeriodic() { ProcRand(nullptr, 0, RNGLevel::PERIODIC); } +void RandAddPeriodic() noexcept { ProcRand(nullptr, 0, RNGLevel::PERIODIC); } +void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); } bool g_mock_deterministic_tests{false}; diff --git a/src/random.h b/src/random.h index 2d8ab085e3..e1b105168d 100644 --- a/src/random.h +++ b/src/random.h @@ -40,17 +40,17 @@ * These entropy sources are slower, but designed to make sure the RNG state contains * fresh data that is unpredictable to attackers. * - * - RandAddSeedSleep() seeds everything that fast seeding includes, but additionally: - * - A high-precision timestamp before and after sleeping 1ms. - * - (On Windows) Once every 10 minutes, performance monitoring data from the OS. - - - Once every minute, strengthen the entropy for 10 ms using repeated SHA512. - * These just exploit the fact the system is idle to improve the quality of the RNG - * slightly. + * - RandAddPeriodic() seeds everything that fast seeding includes, but additionally: + * - A high-precision timestamp + * - Dynamic environment data (performance monitoring, ...) + * - Strengthen the entropy for 10 ms using repeated SHA512. + * This is run once every minute. * * On first use of the RNG (regardless of what function is called first), all entropy * sources used in the 'slow' seeder are included, but also: * - 256 bits from the hardware RNG (rdseed or rdrand) when available. - * - (On Windows) Performance monitoring data from the OS. + * - Dynamic environment data (performance monitoring, ...) + * - Static environment data * - Strengthen the entropy for 100 ms using repeated SHA512. * * When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and @@ -87,7 +87,15 @@ void GetStrongRandBytes(unsigned char* buf, int num) noexcept; * * Thread-safe. */ -void RandAddPeriodic(); +void RandAddPeriodic() noexcept; + +/** + * Gathers entropy from the low bits of the time at which events occur. Should + * be called with a uint32_t describing the event at the time an event occurs. + * + * Thread-safe. + */ +void RandAddEvent(const uint32_t event_info) noexcept; /** * Fast randomness source. This is seeded once with secure random data, but diff --git a/src/randomenv.cpp b/src/randomenv.cpp index ec42ddabc3..6992c720ff 100644 --- a/src/randomenv.cpp +++ b/src/randomenv.cpp @@ -197,19 +197,30 @@ void AddAllCPUID(CSHA512& hasher) // Iterate over all standard leaves AddCPUID(hasher, 0, 0, ax, bx, cx, dx); // Returns max leaf in ax uint32_t max = ax; - for (uint32_t leaf = 1; leaf <= max; ++leaf) { - for (uint32_t subleaf = 0;; ++subleaf) { + for (uint32_t leaf = 1; leaf <= max && leaf <= 0xFF; ++leaf) { + uint32_t maxsub = 0; + for (uint32_t subleaf = 0; subleaf <= 0xFF; ++subleaf) { AddCPUID(hasher, leaf, subleaf, ax, bx, cx, dx); - // Iterate over subleaves for leaf 4, 11, 13 - if (leaf != 4 && leaf != 11 && leaf != 13) break; - if ((leaf == 4 || leaf == 13) && ax == 0) break; - if (leaf == 11 && (cx & 0xFF00) == 0) break; + // Iterate subleafs for leaf values 4, 7, 11, 13 + if (leaf == 4) { + if ((ax & 0x1f) == 0) break; + } else if (leaf == 7) { + if (subleaf == 0) maxsub = ax; + if (subleaf == maxsub) break; + } else if (leaf == 11) { + if ((cx & 0xff00) == 0) break; + } else if (leaf == 13) { + if (ax == 0 && bx == 0 && cx == 0 && dx == 0) break; + } else { + // For any other leaf, stop after subleaf 0. + break; + } } } // Iterate over all extended leaves AddCPUID(hasher, 0x80000000, 0, ax, bx, cx, dx); // Returns max extended leaf in ax uint32_t ext_max = ax; - for (uint32_t leaf = 0x80000001; leaf <= ext_max; ++leaf) { + for (uint32_t leaf = 0x80000001; leaf <= ext_max && leaf <= 0x800000FF; ++leaf) { AddCPUID(hasher, leaf, 0, ax, bx, cx, dx); } } diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 044a84dabf..02f610d841 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -745,8 +745,8 @@ static UniValue getblockheader(const JSONRPCRequest& request) " \"version\" : n, (numeric) The block version\n" " \"versionHex\" : \"00000000\", (string) The block version formatted in hexadecimal\n" " \"merkleroot\" : \"xxxx\", (string) The merkle root\n" - " \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n" - " \"mediantime\" : ttt, (numeric) The median block time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"time\" : ttt, (numeric) The block time expressed in " + UNIX_EPOCH_TIME + "\n" + " \"mediantime\" : ttt, (numeric) The median block time expressed in " + UNIX_EPOCH_TIME + "\n" " \"nonce\" : n, (numeric) The nonce\n" " \"bits\" : \"1d00ffff\", (string) The bits\n" " \"difficulty\" : x.xxx, (numeric) The difficulty\n" @@ -857,8 +857,8 @@ static UniValue getblock(const JSONRPCRequest& request) " \"transactionid\" (string) The transaction id\n" " ,...\n" " ],\n" - " \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n" - " \"mediantime\" : ttt, (numeric) The median block time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"time\" : ttt, (numeric) The block time expressed in " + UNIX_EPOCH_TIME + "\n" + " \"mediantime\" : ttt, (numeric) The median block time expressed in " + UNIX_EPOCH_TIME + "\n" " \"nonce\" : n, (numeric) The nonce\n" " \"bits\" : \"1d00ffff\", (string) The bits\n" " \"difficulty\" : x.xxx, (numeric) The difficulty\n" @@ -924,7 +924,7 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) { RPCHelpMan{"pruneblockchain", "", { - {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block height to prune up to. May be set to a discrete height, or a unix timestamp\n" + {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block height to prune up to. May be set to a discrete height, or to a " + UNIX_EPOCH_TIME + "\n" " to prune blocks whose block time is at least 2 hours older than the provided timestamp."}, }, RPCResult{ @@ -1577,7 +1577,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) }, RPCResult{ "{\n" - " \"time\": xxxxx, (numeric) The timestamp for the final block in the window in UNIX format.\n" + " \"time\": xxxxx, (numeric) The timestamp for the final block in the window, expressed in " + UNIX_EPOCH_TIME + ".\n" " \"txcount\": xxxxx, (numeric) The total number of transactions in the chain up to that point.\n" " \"window_final_block_hash\": \"...\", (string) The hash of the final block in the window.\n" " \"window_final_block_height\": xxxxx, (numeric) The height of the final block in the window.\n" @@ -2061,7 +2061,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) " \"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::NO, "Array of scan objects\n" + {"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"}, @@ -2121,6 +2121,11 @@ UniValue scantxoutset(const JSONRPCRequest& request) if (!reserver.reserve()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); } + + if (request.params.size() < 2) { + throw JSONRPCError(RPC_MISC_ERROR, "scanobjects argument is required for the start action"); + } + std::set<CScript> needles; std::map<CScript, std::string> descriptors; CAmount total_in = 0; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index bb78aaac64..5017e5b9d9 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -380,7 +380,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) " \"coinbasevalue\" : n, (numeric) maximum allowable input to coinbase transaction, including the generation award and transaction fees (in satoshis)\n" " \"coinbasetxn\" : { ... }, (json object) information for coinbase transaction\n" " \"target\" : \"xxxx\", (string) The hash target\n" - " \"mintime\" : xxx, (numeric) The minimum timestamp appropriate for next block time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"mintime\" : xxx, (numeric) The minimum timestamp appropriate for the next block time, expressed in " + UNIX_EPOCH_TIME + "\n" " \"mutable\" : [ (array of string) list of ways the block template may be changed \n" " \"value\" (string) A way the block template may be changed, e.g. 'time', 'transactions', 'prevblock'\n" " ,...\n" @@ -389,7 +389,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) " \"sigoplimit\" : n, (numeric) limit of sigops in blocks\n" " \"sizelimit\" : n, (numeric) limit of block size\n" " \"weightlimit\" : n, (numeric) limit of block weight\n" - " \"curtime\" : ttt, (numeric) current timestamp in seconds since epoch (Jan 1 1970 GMT)\n" + " \"curtime\" : ttt, (numeric) current timestamp in " + UNIX_EPOCH_TIME + "\n" " \"bits\" : \"xxxxxxxx\", (string) compressed target of next block\n" " \"height\" : n (numeric) The height of the next block\n" "}\n" diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index d73dd6e52d..d967d2bcca 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -343,7 +343,7 @@ static UniValue setmocktime(const JSONRPCRequest& request) RPCHelpMan{"setmocktime", "\nSet the local time to given timestamp (-regtest only)\n", { - {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Unix seconds-since-epoch timestamp\n" + {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n" " Pass 0 to go back to using the system time."}, }, RPCResults{}, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index f1dcc9b607..6ee91f139d 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -89,11 +89,11 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) " ...\n" " ],\n" " \"relaytxes\":true|false, (boolean) Whether peer has asked us to relay transactions to it\n" - " \"lastsend\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last send\n" - " \"lastrecv\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last receive\n" + " \"lastsend\": ttt, (numeric) The " + UNIX_EPOCH_TIME + " of the last send\n" + " \"lastrecv\": ttt, (numeric) The " + UNIX_EPOCH_TIME + " of the last receive\n" " \"bytessent\": n, (numeric) The total bytes sent\n" " \"bytesrecv\": n, (numeric) The total bytes received\n" - " \"conntime\": ttt, (numeric) The connection time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"conntime\": ttt, (numeric) The " + UNIX_EPOCH_TIME + " of the connection\n" " \"timeoffset\": ttt, (numeric) The time offset in seconds\n" " \"pingtime\": n, (numeric) ping time (if available)\n" " \"minping\": n, (numeric) minimum observed ping time (if any at all)\n" @@ -534,7 +534,7 @@ static UniValue setban(const JSONRPCRequest& request) {"subnet", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP/Subnet (see getpeerinfo for nodes IP) with an optional netmask (default is /32 = single IP)"}, {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "'add' to add an IP/Subnet to the list, 'remove' to remove an IP/Subnet from the list"}, {"bantime", RPCArg::Type::NUM, /* default */ "0", "time in seconds how long (or until when if [absolute] is set) the IP is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)"}, - {"absolute", RPCArg::Type::BOOL, /* default */ "false", "If set, the bantime must be an absolute timestamp in seconds since epoch (Jan 1 1970 GMT)"}, + {"absolute", RPCArg::Type::BOOL, /* default */ "false", "If set, the bantime must be an absolute timestamp expressed in " + UNIX_EPOCH_TIME}, }, RPCResults{}, RPCExamples{ @@ -691,7 +691,7 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request) RPCResult{ "[\n" " {\n" - " \"time\": ttt, (numeric) Timestamp in seconds since epoch (Jan 1 1970 GMT) keeping track of when the node was last seen\n" + " \"time\": ttt, (numeric) The " + UNIX_EPOCH_TIME + " of when the node was last seen\n" " \"services\": n, (numeric) The services offered\n" " \"address\": \"host\", (string) The address of the node\n" " \"port\": n (numeric) The port of the node\n" diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index fc6fc70392..5be7acce1c 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -139,7 +139,7 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) " ],\n" " \"blockhash\" : \"hash\", (string) the block hash\n" " \"confirmations\" : n, (numeric) The confirmations\n" - " \"blocktime\" : ttt (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"blocktime\" : ttt (numeric) The block time expressed in " + UNIX_EPOCH_TIME + "\n" " \"time\" : ttt, (numeric) Same as \"blocktime\"\n" "}\n" }, @@ -764,7 +764,9 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) // Parse the prevtxs array ParsePrevouts(request.params[2], &keystore, coins); - return SignTransaction(mtx, &keystore, coins, request.params[3]); + UniValue result(UniValue::VOBJ); + SignTransaction(mtx, &keystore, coins, request.params[3], result); + return result; } static UniValue sendrawtransaction(const JSONRPCRequest& request) @@ -1675,6 +1677,7 @@ UniValue analyzepsbt(const JSONRPCRequest& request) " \"estimated_feerate\" : feerate (numeric, optional) Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kB. Shown only if all UTXO slots in the PSBT have been filled.\n" " \"fee\" : fee (numeric, optional) The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled.\n" " \"next\" : \"role\" (string) Role of the next person that this psbt needs to go to\n" + " \"error\" : \"error\" (string) Error message if there is one\n" "}\n" }, RPCExamples { @@ -1727,7 +1730,7 @@ UniValue analyzepsbt(const JSONRPCRequest& request) } inputs_result.push_back(input_univ); } - result.pushKV("inputs", inputs_result); + if (!inputs_result.empty()) result.pushKV("inputs", inputs_result); if (psbta.estimated_vsize != nullopt) { result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize); @@ -1739,6 +1742,9 @@ UniValue analyzepsbt(const JSONRPCRequest& request) result.pushKV("fee", ValueFromAmount(*psbta.fee)); } result.pushKV("next", PSBTRoleName(psbta.next)); + if (!psbta.error.empty()) { + result.pushKV("error", psbta.error); + } return result; } diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index fe98fff4bb..40334883c5 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -268,7 +268,7 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst } } -UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType) +void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result) { int nHashType = ParseSighashString(hashType); @@ -319,12 +319,12 @@ UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keysto } bool fComplete = vErrors.empty(); - UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(CTransaction(mtx))); result.pushKV("complete", fComplete); if (!vErrors.empty()) { + if (result.exists("errors")) { + vErrors.push_backV(result["errors"].getValues()); + } result.pushKV("errors", vErrors); } - - return result; } diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 1936998ff3..0b7712b83c 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -21,9 +21,9 @@ class SigningProvider; * @param keystore Temporary keystore containing signing keys * @param coins Map of unspent outputs * @param hashType The signature hash type - * @returns JSON object with details of signed transaction + * @param result JSON object where signed transaction results accumulate */ -UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType); +void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result); /** * Parse a prevtxs UniValue array and get the map of coins from it diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index cfa3509c65..78586c22f9 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -13,6 +13,8 @@ #include <tuple> +const std::string UNIX_EPOCH_TIME = "UNIX epoch time"; + void RPCTypeCheck(const UniValue& params, const std::list<UniValueType>& typesExpected, bool fAllowNull) @@ -131,18 +133,18 @@ CPubKey HexToPubKey(const std::string& hex_in) } // Retrieves a public key for an address from the given FillableSigningProvider -CPubKey AddrToPubKey(FillableSigningProvider* const keystore, const std::string& addr_in) +CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string& addr_in) { CTxDestination dest = DecodeDestination(addr_in); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address: " + addr_in); } - CKeyID key = GetKeyForDestination(*keystore, dest); + CKeyID key = GetKeyForDestination(keystore, dest); if (key.IsNull()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("%s does not refer to a key", addr_in)); } CPubKey vchPubKey; - if (!keystore->GetPubKey(key, vchPubKey)) { + if (!keystore.GetPubKey(key, vchPubKey)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("no full public key for address %s", addr_in)); } if (!vchPubKey.IsFullyValid()) { diff --git a/src/rpc/util.h b/src/rpc/util.h index 221638aa9e..065a992a88 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -22,6 +22,12 @@ #include <boost/variant.hpp> +/** + * String used to describe UNIX epoch time in documentation, factored out to a + * constant for consistency. + */ +extern const std::string UNIX_EPOCH_TIME; + class FillableSigningProvider; class CPubKey; class CScript; @@ -69,7 +75,7 @@ extern std::string HelpExampleCli(const std::string& methodname, const std::stri extern std::string HelpExampleRpc(const std::string& methodname, const std::string& args); CPubKey HexToPubKey(const std::string& hex_in); -CPubKey AddrToPubKey(FillableSigningProvider* const keystore, const std::string& addr_in); +CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string& addr_in); CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out); UniValue DescribeAddress(const CTxDestination& dest); diff --git a/src/test/base32_tests.cpp b/src/test/base32_tests.cpp index bd6ece935b..690368b177 100644 --- a/src/test/base32_tests.cpp +++ b/src/test/base32_tests.cpp @@ -20,6 +20,17 @@ BOOST_AUTO_TEST_CASE(base32_testvectors) std::string strDec = DecodeBase32(vstrOut[i]); BOOST_CHECK_EQUAL(strDec, vstrIn[i]); } + + // Decoding strings with embedded NUL characters should fail + bool failure; + (void)DecodeBase32(std::string("invalid", 7), &failure); + BOOST_CHECK_EQUAL(failure, true); + (void)DecodeBase32(std::string("AWSX3VPP", 8), &failure); + BOOST_CHECK_EQUAL(failure, false); + (void)DecodeBase32(std::string("AWSX3VPP\0invalid", 16), &failure); + BOOST_CHECK_EQUAL(failure, true); + (void)DecodeBase32(std::string("AWSX3VPPinvalid", 15), &failure); + BOOST_CHECK_EQUAL(failure, true); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index 52301f799a..57559fa687 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -7,6 +7,7 @@ #include <base58.h> #include <test/util/setup_common.h> #include <util/strencodings.h> +#include <util/vector.h> #include <univalue.h> @@ -53,17 +54,45 @@ BOOST_AUTO_TEST_CASE(base58_DecodeBase58) } std::vector<unsigned char> expected = ParseHex(test[0].get_str()); std::string base58string = test[1].get_str(); - BOOST_CHECK_MESSAGE(DecodeBase58(base58string, result), strTest); + BOOST_CHECK_MESSAGE(DecodeBase58(base58string, result, 256), strTest); BOOST_CHECK_MESSAGE(result.size() == expected.size() && std::equal(result.begin(), result.end(), expected.begin()), strTest); } - BOOST_CHECK(!DecodeBase58("invalid", result)); + BOOST_CHECK(!DecodeBase58("invalid", result, 100)); + BOOST_CHECK(!DecodeBase58(std::string("invalid"), result, 100)); + BOOST_CHECK(!DecodeBase58(std::string("\0invalid", 8), result, 100)); + + BOOST_CHECK(DecodeBase58(std::string("good", 4), result, 100)); + BOOST_CHECK(!DecodeBase58(std::string("bad0IOl", 7), result, 100)); + BOOST_CHECK(!DecodeBase58(std::string("goodbad0IOl", 11), result, 100)); + BOOST_CHECK(!DecodeBase58(std::string("good\0bad0IOl", 12), result, 100)); // check that DecodeBase58 skips whitespace, but still fails with unexpected non-whitespace at the end. - BOOST_CHECK(!DecodeBase58(" \t\n\v\f\r skip \r\f\v\n\t a", result)); - BOOST_CHECK( DecodeBase58(" \t\n\v\f\r skip \r\f\v\n\t ", result)); + BOOST_CHECK(!DecodeBase58(" \t\n\v\f\r skip \r\f\v\n\t a", result, 3)); + BOOST_CHECK( DecodeBase58(" \t\n\v\f\r skip \r\f\v\n\t ", result, 3)); std::vector<unsigned char> expected = ParseHex("971a55"); BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); + + BOOST_CHECK(DecodeBase58Check(std::string("3vQB7B6MrGQZaxCuFg4oh", 21), result, 100)); + BOOST_CHECK(!DecodeBase58Check(std::string("3vQB7B6MrGQZaxCuFg4oi", 21), result, 100)); + BOOST_CHECK(!DecodeBase58Check(std::string("3vQB7B6MrGQZaxCuFg4oh0IOl", 25), result, 100)); + BOOST_CHECK(!DecodeBase58Check(std::string("3vQB7B6MrGQZaxCuFg4oh\00IOl", 26), result, 100)); +} + +BOOST_AUTO_TEST_CASE(base58_random_encode_decode) +{ + for (int n = 0; n < 1000; ++n) { + unsigned int len = 1 + InsecureRandBits(8); + unsigned int zeroes = InsecureRandBool() ? InsecureRandRange(len + 1) : 0; + auto data = Cat(std::vector<unsigned char>(zeroes, '\000'), g_insecure_rand_ctx.randbytes(len - zeroes)); + auto encoded = EncodeBase58Check(data); + std::vector<unsigned char> decoded; + auto ok_too_small = DecodeBase58Check(encoded, decoded, InsecureRandRange(len)); + BOOST_CHECK(!ok_too_small); + auto ok = DecodeBase58Check(encoded, decoded, len + InsecureRandRange(257 - len)); + BOOST_CHECK(ok); + BOOST_CHECK(data == decoded); + } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/base64_tests.cpp b/src/test/base64_tests.cpp index a5fed55504..94df4d1955 100644 --- a/src/test/base64_tests.cpp +++ b/src/test/base64_tests.cpp @@ -20,6 +20,17 @@ BOOST_AUTO_TEST_CASE(base64_testvectors) std::string strDec = DecodeBase64(strEnc); BOOST_CHECK_EQUAL(strDec, vstrIn[i]); } + + // Decoding strings with embedded NUL characters should fail + bool failure; + (void)DecodeBase64(std::string("invalid", 7), &failure); + BOOST_CHECK_EQUAL(failure, true); + (void)DecodeBase64(std::string("nQB/pZw=", 8), &failure); + BOOST_CHECK_EQUAL(failure, false); + (void)DecodeBase64(std::string("nQB/pZw=\0invalid", 16), &failure); + BOOST_CHECK_EQUAL(failure, true); + (void)DecodeBase64(std::string("nQB/pZw=invalid", 15), &failure); + BOOST_CHECK_EQUAL(failure, true); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index 6745bb9015..482fe3772c 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_tests.cpp @@ -17,6 +17,7 @@ #include <condition_variable> #include <unordered_set> +#include <utility> BOOST_FIXTURE_TEST_SUITE(checkqueue_tests, TestingSetup) diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 591a317d17..2deb0c5bfc 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -186,14 +186,15 @@ static void TestHKDF_SHA256_32(const std::string &ikm_hex, const std::string &sa BOOST_CHECK(HexStr(out, out + 32) == okm_check_hex); } -static std::string LongTestString() { +static std::string LongTestString() +{ std::string ret; - for (int i=0; i<200000; i++) { - ret += (unsigned char)(i); - ret += (unsigned char)(i >> 4); - ret += (unsigned char)(i >> 8); - ret += (unsigned char)(i >> 12); - ret += (unsigned char)(i >> 16); + for (int i = 0; i < 200000; i++) { + ret += (char)(i); + ret += (char)(i >> 4); + ret += (char)(i >> 8); + ret += (char)(i >> 12); + ret += (char)(i >> 16); } return ret; } diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp index 57d5b2bb5c..b647c0f70b 100644 --- a/src/test/dbwrapper_tests.cpp +++ b/src/test/dbwrapper_tests.cpp @@ -397,6 +397,18 @@ BOOST_AUTO_TEST_CASE(iterator_string_ordering) } } +BOOST_AUTO_TEST_CASE(unicodepath) +{ + // Attempt to create a database with a utf8 character in the path. + // On Windows this test will fail if the directory is created using + // the ANSI CreateDirectoryA call and the code page isn't UTF8. + // It will succeed if the created with CreateDirectoryW. + fs::path ph = GetDataDir() / "test_runner_₿_🏃_20191128_104644"; + CDBWrapper dbw(ph, (1 << 20)); + + fs::path lockPath = ph / "LOCK"; + BOOST_CHECK(boost::filesystem::exists(lockPath)); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/fuzz/block.cpp b/src/test/fuzz/block.cpp new file mode 100644 index 0000000000..431248de4a --- /dev/null +++ b/src/test/fuzz/block.cpp @@ -0,0 +1,63 @@ +// 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 <chainparams.h> +#include <consensus/merkle.h> +#include <consensus/validation.h> +#include <core_io.h> +#include <core_memusage.h> +#include <pubkey.h> +#include <primitives/block.h> +#include <streams.h> +#include <test/fuzz/fuzz.h> +#include <validation.h> +#include <version.h> + +#include <cassert> +#include <string> + +void initialize() +{ + const static auto verify_handle = MakeUnique<ECCVerifyHandle>(); + SelectParams(CBaseChainParams::REGTEST); +} + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); + CBlock block; + try { + int nVersion; + ds >> nVersion; + ds.SetVersion(nVersion); + ds >> block; + } catch (const std::ios_base::failure&) { + return; + } + const Consensus::Params& consensus_params = Params().GetConsensus(); + BlockValidationState validation_state_pow_and_merkle; + const bool valid_incl_pow_and_merkle = CheckBlock(block, validation_state_pow_and_merkle, consensus_params, /* fCheckPOW= */ true, /* fCheckMerkleRoot= */ true); + BlockValidationState validation_state_pow; + const bool valid_incl_pow = CheckBlock(block, validation_state_pow, consensus_params, /* fCheckPOW= */ true, /* fCheckMerkleRoot= */ false); + BlockValidationState validation_state_merkle; + const bool valid_incl_merkle = CheckBlock(block, validation_state_merkle, consensus_params, /* fCheckPOW= */ false, /* fCheckMerkleRoot= */ true); + BlockValidationState validation_state_none; + const bool valid_incl_none = CheckBlock(block, validation_state_none, consensus_params, /* fCheckPOW= */ false, /* fCheckMerkleRoot= */ false); + if (valid_incl_pow_and_merkle) { + assert(valid_incl_pow && valid_incl_merkle && valid_incl_none); + } else if (valid_incl_merkle || valid_incl_pow) { + assert(valid_incl_none); + } + (void)block.GetHash(); + (void)block.ToString(); + (void)BlockMerkleRoot(block); + if (!block.vtx.empty()) { + // TODO: Avoid array index out of bounds error in BlockWitnessMerkleRoot + // when block.vtx.empty(). + (void)BlockWitnessMerkleRoot(block); + } + (void)GetBlockWeight(block); + (void)GetWitnessCommitmentIndex(block); + (void)RecursiveDynamicUsage(block); +} diff --git a/src/test/fuzz/descriptor_parse.cpp b/src/test/fuzz/descriptor_parse.cpp index c4c25854fd..47d5038c26 100644 --- a/src/test/fuzz/descriptor_parse.cpp +++ b/src/test/fuzz/descriptor_parse.cpp @@ -3,11 +3,14 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chainparams.h> +#include <pubkey.h> #include <script/descriptor.h> #include <test/fuzz/fuzz.h> +#include <util/memory.h> void initialize() { + static const auto verify_handle = MakeUnique<ECCVerifyHandle>(); SelectParams(CBaseChainParams::REGTEST); } diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index bcd8691359..bd05283b78 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.cpp @@ -5,18 +5,25 @@ #include <addrdb.h> #include <addrman.h> #include <blockencodings.h> +#include <blockfilter.h> #include <chain.h> #include <coins.h> #include <compressor.h> #include <consensus/merkle.h> +#include <key.h> +#include <merkleblock.h> #include <net.h> #include <primitives/block.h> #include <protocol.h> +#include <psbt.h> #include <pubkey.h> +#include <script/keyorigin.h> #include <streams.h> #include <undo.h> #include <version.h> +#include <exception> +#include <stdexcept> #include <stdint.h> #include <unistd.h> @@ -30,137 +37,186 @@ void initialize() static const auto verify_handle = MakeUnique<ECCVerifyHandle>(); } -void test_one_input(const std::vector<uint8_t>& buffer) +namespace { + +struct invalid_fuzzing_input_exception : public std::exception { +}; + +template <typename T> +CDataStream Serialize(const T& obj) +{ + CDataStream ds(SER_NETWORK, INIT_PROTO_VERSION); + ds << obj; + return ds; +} + +template <typename T> +T Deserialize(CDataStream ds) +{ + T obj; + ds >> obj; + return obj; +} + +template <typename T> +void DeserializeFromFuzzingInput(const std::vector<uint8_t>& buffer, T& obj) { CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); try { - int nVersion; - ds >> nVersion; - ds.SetVersion(nVersion); - } catch (const std::ios_base::failure& e) { - return; + int version; + ds >> version; + ds.SetVersion(version); + } catch (const std::ios_base::failure&) { + throw invalid_fuzzing_input_exception(); + } + try { + ds >> obj; + } catch (const std::ios_base::failure&) { + throw invalid_fuzzing_input_exception(); } + assert(buffer.empty() || !Serialize(obj).empty()); +} + +template <typename T> +void AssertEqualAfterSerializeDeserialize(const T& obj) +{ + assert(Deserialize<T>(Serialize(obj)) == obj); +} -#if BLOCK_DESERIALIZE - try - { - CBlock block; - ds >> block; - } catch (const std::ios_base::failure& e) {return;} +} // namespace + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + try { +#if BLOCK_FILTER_DESERIALIZE + BlockFilter block_filter; + DeserializeFromFuzzingInput(buffer, block_filter); +#elif ADDR_INFO_DESERIALIZE + CAddrInfo addr_info; + DeserializeFromFuzzingInput(buffer, addr_info); +#elif BLOCK_FILE_INFO_DESERIALIZE + CBlockFileInfo block_file_info; + DeserializeFromFuzzingInput(buffer, block_file_info); +#elif BLOCK_HEADER_AND_SHORT_TXIDS_DESERIALIZE + CBlockHeaderAndShortTxIDs block_header_and_short_txids; + DeserializeFromFuzzingInput(buffer, block_header_and_short_txids); +#elif FEE_RATE_DESERIALIZE + CFeeRate fee_rate; + DeserializeFromFuzzingInput(buffer, fee_rate); + AssertEqualAfterSerializeDeserialize(fee_rate); +#elif MERKLE_BLOCK_DESERIALIZE + CMerkleBlock merkle_block; + DeserializeFromFuzzingInput(buffer, merkle_block); +#elif OUT_POINT_DESERIALIZE + COutPoint out_point; + DeserializeFromFuzzingInput(buffer, out_point); + AssertEqualAfterSerializeDeserialize(out_point); +#elif PARTIAL_MERKLE_TREE_DESERIALIZE + CPartialMerkleTree partial_merkle_tree; + DeserializeFromFuzzingInput(buffer, partial_merkle_tree); +#elif PUB_KEY_DESERIALIZE + CPubKey pub_key; + DeserializeFromFuzzingInput(buffer, pub_key); + // TODO: The following equivalence should hold for CPubKey? Fix. + // AssertEqualAfterSerializeDeserialize(pub_key); +#elif SCRIPT_DESERIALIZE + CScript script; + DeserializeFromFuzzingInput(buffer, script); +#elif SUB_NET_DESERIALIZE + CSubNet sub_net; + DeserializeFromFuzzingInput(buffer, sub_net); + AssertEqualAfterSerializeDeserialize(sub_net); +#elif TX_IN_DESERIALIZE + CTxIn tx_in; + DeserializeFromFuzzingInput(buffer, tx_in); + AssertEqualAfterSerializeDeserialize(tx_in); +#elif FLAT_FILE_POS_DESERIALIZE + FlatFilePos flat_file_pos; + DeserializeFromFuzzingInput(buffer, flat_file_pos); + AssertEqualAfterSerializeDeserialize(flat_file_pos); +#elif KEY_ORIGIN_INFO_DESERIALIZE + KeyOriginInfo key_origin_info; + DeserializeFromFuzzingInput(buffer, key_origin_info); + AssertEqualAfterSerializeDeserialize(key_origin_info); +#elif PARTIALLY_SIGNED_TRANSACTION_DESERIALIZE + PartiallySignedTransaction partially_signed_transaction; + DeserializeFromFuzzingInput(buffer, partially_signed_transaction); +#elif PREFILLED_TRANSACTION_DESERIALIZE + PrefilledTransaction prefilled_transaction; + DeserializeFromFuzzingInput(buffer, prefilled_transaction); +#elif PSBT_INPUT_DESERIALIZE + PSBTInput psbt_input; + DeserializeFromFuzzingInput(buffer, psbt_input); +#elif PSBT_OUTPUT_DESERIALIZE + PSBTOutput psbt_output; + DeserializeFromFuzzingInput(buffer, psbt_output); +#elif BLOCK_DESERIALIZE + CBlock block; + DeserializeFromFuzzingInput(buffer, block); #elif BLOCKLOCATOR_DESERIALIZE - try - { - CBlockLocator bl; - ds >> bl; - } catch (const std::ios_base::failure& e) {return;} + CBlockLocator bl; + DeserializeFromFuzzingInput(buffer, bl); #elif BLOCKMERKLEROOT - try - { - CBlock block; - ds >> block; - bool mutated; - BlockMerkleRoot(block, &mutated); - } catch (const std::ios_base::failure& e) {return;} + CBlock block; + DeserializeFromFuzzingInput(buffer, block); + bool mutated; + BlockMerkleRoot(block, &mutated); #elif ADDRMAN_DESERIALIZE - try - { - CAddrMan am; - ds >> am; - } catch (const std::ios_base::failure& e) {return;} + CAddrMan am; + DeserializeFromFuzzingInput(buffer, am); #elif BLOCKHEADER_DESERIALIZE - try - { - CBlockHeader bh; - ds >> bh; - } catch (const std::ios_base::failure& e) {return;} + CBlockHeader bh; + DeserializeFromFuzzingInput(buffer, bh); #elif BANENTRY_DESERIALIZE - try - { - CBanEntry be; - ds >> be; - } catch (const std::ios_base::failure& e) {return;} + CBanEntry be; + DeserializeFromFuzzingInput(buffer, be); #elif TXUNDO_DESERIALIZE - try - { - CTxUndo tu; - ds >> tu; - } catch (const std::ios_base::failure& e) {return;} + CTxUndo tu; + DeserializeFromFuzzingInput(buffer, tu); #elif BLOCKUNDO_DESERIALIZE - try - { - CBlockUndo bu; - ds >> bu; - } catch (const std::ios_base::failure& e) {return;} + CBlockUndo bu; + DeserializeFromFuzzingInput(buffer, bu); #elif COINS_DESERIALIZE - try - { - Coin coin; - ds >> coin; - } catch (const std::ios_base::failure& e) {return;} + Coin coin; + DeserializeFromFuzzingInput(buffer, coin); #elif NETADDR_DESERIALIZE - try - { - CNetAddr na; - ds >> na; - } catch (const std::ios_base::failure& e) {return;} + CNetAddr na; + DeserializeFromFuzzingInput(buffer, na); + AssertEqualAfterSerializeDeserialize(na); #elif SERVICE_DESERIALIZE - try - { - CService s; - ds >> s; - } catch (const std::ios_base::failure& e) {return;} + CService s; + DeserializeFromFuzzingInput(buffer, s); + AssertEqualAfterSerializeDeserialize(s); #elif MESSAGEHEADER_DESERIALIZE - CMessageHeader::MessageStartChars pchMessageStart = {0x00, 0x00, 0x00, 0x00}; - try - { - CMessageHeader mh(pchMessageStart); - ds >> mh; - if (!mh.IsValid(pchMessageStart)) {return;} - } catch (const std::ios_base::failure& e) {return;} + const CMessageHeader::MessageStartChars pchMessageStart = {0x00, 0x00, 0x00, 0x00}; + CMessageHeader mh(pchMessageStart); + DeserializeFromFuzzingInput(buffer, mh); + (void)mh.IsValid(pchMessageStart); #elif ADDRESS_DESERIALIZE - try - { - CAddress a; - ds >> a; - } catch (const std::ios_base::failure& e) {return;} + CAddress a; + DeserializeFromFuzzingInput(buffer, a); #elif INV_DESERIALIZE - try - { - CInv i; - ds >> i; - } catch (const std::ios_base::failure& e) {return;} + CInv i; + DeserializeFromFuzzingInput(buffer, i); #elif BLOOMFILTER_DESERIALIZE - try - { - CBloomFilter bf; - ds >> bf; - } catch (const std::ios_base::failure& e) {return;} + CBloomFilter bf; + DeserializeFromFuzzingInput(buffer, bf); #elif DISKBLOCKINDEX_DESERIALIZE - try - { - CDiskBlockIndex dbi; - ds >> dbi; - } catch (const std::ios_base::failure& e) {return;} + CDiskBlockIndex dbi; + DeserializeFromFuzzingInput(buffer, dbi); #elif TXOUTCOMPRESSOR_DESERIALIZE - CTxOut to; - CTxOutCompressor toc(to); - try - { - ds >> toc; - } catch (const std::ios_base::failure& e) {return;} + CTxOut to; + CTxOutCompressor toc(to); + DeserializeFromFuzzingInput(buffer, toc); #elif BLOCKTRANSACTIONS_DESERIALIZE - try - { - BlockTransactions bt; - ds >> bt; - } catch (const std::ios_base::failure& e) {return;} + BlockTransactions bt; + DeserializeFromFuzzingInput(buffer, bt); #elif BLOCKTRANSACTIONSREQUEST_DESERIALIZE - try - { - BlockTransactionsRequest btr; - ds >> btr; - } catch (const std::ios_base::failure& e) {return;} + BlockTransactionsRequest btr; + DeserializeFromFuzzingInput(buffer, btr); #else #error Need at least one fuzz target to compile #endif + } catch (const invalid_fuzzing_input_exception&) { + } } diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp new file mode 100644 index 0000000000..723938bcdb --- /dev/null +++ b/src/test/fuzz/integer.cpp @@ -0,0 +1,127 @@ +// 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 <arith_uint256.h> +#include <compressor.h> +#include <consensus/merkle.h> +#include <core_io.h> +#include <crypto/common.h> +#include <crypto/siphash.h> +#include <key_io.h> +#include <memusage.h> +#include <netbase.h> +#include <policy/settings.h> +#include <pow.h> +#include <pubkey.h> +#include <rpc/util.h> +#include <script/signingprovider.h> +#include <script/standard.h> +#include <serialize.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <uint256.h> +#include <util/strencodings.h> +#include <util/system.h> +#include <util/time.h> + +#include <cassert> +#include <limits> +#include <vector> + +void initialize() +{ + SelectParams(CBaseChainParams::REGTEST); +} + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + if (buffer.size() < sizeof(uint256) + sizeof(uint160)) { + return; + } + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const uint256 u256(fuzzed_data_provider.ConsumeBytes<unsigned char>(sizeof(uint256))); + const uint160 u160(fuzzed_data_provider.ConsumeBytes<unsigned char>(sizeof(uint160))); + const uint64_t u64 = fuzzed_data_provider.ConsumeIntegral<uint64_t>(); + const int64_t i64 = fuzzed_data_provider.ConsumeIntegral<int64_t>(); + const uint32_t u32 = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); + const int32_t i32 = fuzzed_data_provider.ConsumeIntegral<int32_t>(); + const uint16_t u16 = fuzzed_data_provider.ConsumeIntegral<uint16_t>(); + const int16_t i16 = fuzzed_data_provider.ConsumeIntegral<int16_t>(); + const uint8_t u8 = fuzzed_data_provider.ConsumeIntegral<uint8_t>(); + const int8_t i8 = fuzzed_data_provider.ConsumeIntegral<int8_t>(); + // We cannot assume a specific value of std::is_signed<char>::value: + // ConsumeIntegral<char>() instead of casting from {u,}int8_t. + const char ch = fuzzed_data_provider.ConsumeIntegral<char>(); + + const Consensus::Params& consensus_params = Params().GetConsensus(); + (void)CheckProofOfWork(u256, u32, consensus_params); + (void)CompressAmount(u64); + static const uint256 u256_min(uint256S("0000000000000000000000000000000000000000000000000000000000000000")); + static const uint256 u256_max(uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + const std::vector<uint256> v256{u256, u256_min, u256_max}; + (void)ComputeMerkleRoot(v256); + (void)CountBits(u64); + (void)DecompressAmount(u64); + (void)FormatISO8601Date(i64); + (void)FormatISO8601DateTime(i64); + (void)GetSizeOfCompactSize(u64); + (void)GetSpecialScriptSize(u32); + // (void)GetVirtualTransactionSize(i64, i64); // function defined only for a subset of int64_t inputs + // (void)GetVirtualTransactionSize(i64, i64, u32); // function defined only for a subset of int64_t/uint32_t inputs + (void)HexDigit(ch); + (void)i64tostr(i64); + (void)IsDigit(ch); + (void)IsSpace(ch); + (void)IsSwitchChar(ch); + (void)itostr(i32); + (void)memusage::DynamicUsage(ch); + (void)memusage::DynamicUsage(i16); + (void)memusage::DynamicUsage(i32); + (void)memusage::DynamicUsage(i64); + (void)memusage::DynamicUsage(i8); + (void)memusage::DynamicUsage(u16); + (void)memusage::DynamicUsage(u32); + (void)memusage::DynamicUsage(u64); + (void)memusage::DynamicUsage(u8); + const unsigned char uch = static_cast<unsigned char>(u8); + (void)memusage::DynamicUsage(uch); + (void)MillisToTimeval(i64); + const double d = ser_uint64_to_double(u64); + assert(ser_double_to_uint64(d) == u64); + const float f = ser_uint32_to_float(u32); + assert(ser_float_to_uint32(f) == u32); + (void)SighashToStr(uch); + (void)SipHashUint256(u64, u64, u256); + (void)SipHashUint256Extra(u64, u64, u256, u32); + (void)ToLower(ch); + + const arith_uint256 au256 = UintToArith256(u256); + assert(ArithToUint256(au256) == u256); + assert(uint256S(au256.GetHex()) == u256); + (void)au256.bits(); + (void)au256.GetCompact(/* fNegative= */ false); + (void)au256.GetCompact(/* fNegative= */ true); + (void)au256.getdouble(); + (void)au256.GetHex(); + (void)au256.GetLow64(); + (void)au256.size(); + (void)au256.ToString(); + + const CKeyID key_id{u160}; + const CScriptID script_id{u160}; + // CTxDestination = CNoDestination ∪ PKHash ∪ ScriptHash ∪ WitnessV0ScriptHash ∪ WitnessV0KeyHash ∪ WitnessUnknown + const PKHash pk_hash{u160}; + const ScriptHash script_hash{u160}; + const WitnessV0KeyHash witness_v0_key_hash{u160}; + const WitnessV0ScriptHash witness_v0_script_hash{u256}; + const std::vector<CTxDestination> destinations{pk_hash, script_hash, witness_v0_key_hash, witness_v0_script_hash}; + const SigningProvider store; + for (const CTxDestination& destination : destinations) { + (void)DescribeAddress(destination); + (void)EncodeDestination(destination); + (void)GetKeyForDestination(store, destination); + (void)GetScriptForDestination(destination); + (void)IsValidDestination(destination); + } +} diff --git a/src/test/fuzz/parse_hd_keypath.cpp b/src/test/fuzz/parse_hd_keypath.cpp new file mode 100644 index 0000000000..9a23f4b2d4 --- /dev/null +++ b/src/test/fuzz/parse_hd_keypath.cpp @@ -0,0 +1,13 @@ +// Copyright (c) 2009-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <test/fuzz/fuzz.h> +#include <util/bip32.h> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + const std::string keypath_str(buffer.begin(), buffer.end()); + std::vector<uint32_t> keypath; + (void)ParseHDKeypath(keypath_str, keypath); +} diff --git a/src/test/fuzz/parse_numbers.cpp b/src/test/fuzz/parse_numbers.cpp new file mode 100644 index 0000000000..59f89dc9fb --- /dev/null +++ b/src/test/fuzz/parse_numbers.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2009-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <test/fuzz/fuzz.h> +#include <util/moneystr.h> +#include <util/strencodings.h> + +#include <string> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + const std::string random_string(buffer.begin(), buffer.end()); + + CAmount amount; + (void)ParseMoney(random_string, amount); + + double d; + (void)ParseDouble(random_string, &d); + + int32_t i32; + (void)ParseInt32(random_string, &i32); + (void)atoi(random_string); + + uint32_t u32; + (void)ParseUInt32(random_string, &u32); + + int64_t i64; + (void)atoi64(random_string); + (void)ParseFixedPoint(random_string, 3, &i64); + (void)ParseInt64(random_string, &i64); + + uint64_t u64; + (void)ParseUInt64(random_string, &u64); +} diff --git a/src/test/fuzz/parse_script.cpp b/src/test/fuzz/parse_script.cpp new file mode 100644 index 0000000000..21ac1aecf3 --- /dev/null +++ b/src/test/fuzz/parse_script.cpp @@ -0,0 +1,16 @@ +// Copyright (c) 2009-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <core_io.h> +#include <script/script.h> +#include <test/fuzz/fuzz.h> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + const std::string script_string(buffer.begin(), buffer.end()); + try { + (void)ParseScript(script_string); + } catch (const std::runtime_error&) { + } +} diff --git a/src/test/fuzz/parse_univalue.cpp b/src/test/fuzz/parse_univalue.cpp new file mode 100644 index 0000000000..3ad112dbad --- /dev/null +++ b/src/test/fuzz/parse_univalue.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2009-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <chainparams.h> +#include <core_io.h> +#include <rpc/client.h> +#include <rpc/util.h> +#include <test/fuzz/fuzz.h> +#include <util/memory.h> + +#include <limits> +#include <string> + +void initialize() +{ + static const auto verify_handle = MakeUnique<ECCVerifyHandle>(); + SelectParams(CBaseChainParams::REGTEST); +} + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + const std::string random_string(buffer.begin(), buffer.end()); + bool valid = true; + const UniValue univalue = [&] { + try { + return ParseNonRFCJSONValue(random_string); + } catch (const std::runtime_error&) { + valid = false; + return NullUniValue; + } + }(); + if (!valid) { + return; + } + try { + (void)ParseHashO(univalue, "A"); + (void)ParseHashO(univalue, random_string); + } catch (const UniValue&) { + } catch (const std::runtime_error&) { + } + try { + (void)ParseHashV(univalue, "A"); + (void)ParseHashV(univalue, random_string); + } catch (const UniValue&) { + } catch (const std::runtime_error&) { + } + try { + (void)ParseHexO(univalue, "A"); + (void)ParseHexO(univalue, random_string); + } catch (const UniValue&) { + } catch (const std::runtime_error&) { + } + try { + (void)ParseHexUV(univalue, "A"); + (void)ParseHexUV(univalue, random_string); + } catch (const UniValue&) { + } catch (const std::runtime_error&) { + } + try { + (void)ParseHexV(univalue, "A"); + (void)ParseHexV(univalue, random_string); + } catch (const UniValue&) { + } catch (const std::runtime_error&) { + } + try { + (void)ParseSighashString(univalue); + } catch (const std::runtime_error&) { + } + try { + (void)AmountFromValue(univalue); + } catch (const UniValue&) { + } catch (const std::runtime_error&) { + } + try { + FlatSigningProvider provider; + (void)EvalDescriptorStringOrObject(univalue, provider); + } catch (const UniValue&) { + } catch (const std::runtime_error&) { + } + try { + (void)ParseConfirmTarget(univalue, std::numeric_limits<unsigned int>::max()); + } catch (const UniValue&) { + } catch (const std::runtime_error&) { + } + try { + (void)ParseDescriptorRange(univalue); + } catch (const UniValue&) { + } catch (const std::runtime_error&) { + } +} diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp index 76b230ef3c..fefafda36b 100644 --- a/src/test/fuzz/transaction.cpp +++ b/src/test/fuzz/transaction.cpp @@ -26,19 +26,31 @@ void test_one_input(const std::vector<uint8_t>& buffer) int nVersion; ds >> nVersion; ds.SetVersion(nVersion); - } catch (const std::ios_base::failure& e) { + } catch (const std::ios_base::failure&) { return; } - bool valid = true; + bool valid_tx = true; const CTransaction tx = [&] { try { return CTransaction(deserialize, ds); - } catch (const std::ios_base::failure& e) { - valid = false; + } catch (const std::ios_base::failure&) { + valid_tx = false; return CTransaction(); } }(); - if (!valid) { + bool valid_mutable_tx = true; + CDataStream ds_mtx(buffer, SER_NETWORK, INIT_PROTO_VERSION); + CMutableTransaction mutable_tx; + try { + int nVersion; + ds_mtx >> nVersion; + ds_mtx.SetVersion(nVersion); + ds_mtx >> mutable_tx; + } catch (const std::ios_base::failure&) { + valid_mutable_tx = false; + } + assert(valid_tx == valid_mutable_tx); + if (!valid_tx) { return; } diff --git a/src/test/fuzz/tx_in.cpp b/src/test/fuzz/tx_in.cpp new file mode 100644 index 0000000000..8e116537d1 --- /dev/null +++ b/src/test/fuzz/tx_in.cpp @@ -0,0 +1,33 @@ +// 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 <consensus/validation.h> +#include <core_memusage.h> +#include <policy/policy.h> +#include <primitives/transaction.h> +#include <streams.h> +#include <test/fuzz/fuzz.h> +#include <version.h> + +#include <cassert> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); + CTxIn tx_in; + try { + int version; + ds >> version; + ds.SetVersion(version); + ds >> tx_in; + } catch (const std::ios_base::failure&) { + return; + } + + (void)GetTransactionInputWeight(tx_in); + (void)GetVirtualTransactionInputSize(tx_in); + (void)RecursiveDynamicUsage(tx_in); + + (void)tx_in.ToString(); +} diff --git a/src/test/fuzz/tx_out.cpp b/src/test/fuzz/tx_out.cpp new file mode 100644 index 0000000000..aa1338d5ba --- /dev/null +++ b/src/test/fuzz/tx_out.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <consensus/validation.h> +#include <core_memusage.h> +#include <policy/policy.h> +#include <primitives/transaction.h> +#include <streams.h> +#include <test/fuzz/fuzz.h> +#include <version.h> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); + CTxOut tx_out; + try { + int version; + ds >> version; + ds.SetVersion(version); + ds >> tx_out; + } catch (const std::ios_base::failure&) { + return; + } + + const CFeeRate dust_relay_fee{DUST_RELAY_TX_FEE}; + (void)GetDustThreshold(tx_out, dust_relay_fee); + (void)IsDust(tx_out, dust_relay_fee); + (void)RecursiveDynamicUsage(tx_out); + + (void)tx_out.ToString(); + (void)tx_out.IsNull(); + tx_out.SetNull(); + assert(tx_out.IsNull()); +} diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index eb0050a4a3..0939803953 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -713,6 +713,29 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vout[0].nValue = nDustThreshold; BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + // Disallowed nVersion + t.nVersion = -1; + reason.clear(); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "version"); + + t.nVersion = 0; + reason.clear(); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "version"); + + t.nVersion = 3; + reason.clear(); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "version"); + + // Allowed nVersion + t.nVersion = 1; + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + + t.nVersion = 2; + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + // Check dust with odd relay fee to verify rounding: // nDustThreshold = 182 * 3702 / 1000 dustRelayFee = CFeeRate(3702); @@ -797,6 +820,17 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); BOOST_CHECK_EQUAL(reason, "scriptsig-size"); + + // Check bare multisig (standard if policy flag fIsBareMultisigStd is set) + fIsBareMultisigStd = true; + t.vout[0].scriptPubKey = GetScriptForMultisig(1, {key.GetPubKey()}); // simple 1-of-1 + t.vin[0].scriptSig = CScript() << std::vector<unsigned char>(65, 0); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + + fIsBareMultisigStd = false; + reason.clear(); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "bare-multisig"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index b9fcd97a8f..a5cbae89b4 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -1069,6 +1069,11 @@ BOOST_AUTO_TEST_CASE(util_ParseMoney) // Parsing negative amounts must fail BOOST_CHECK(!ParseMoney("-1", ret)); + + // Parsing strings with embedded NUL characters should fail + BOOST_CHECK(!ParseMoney(std::string("\0-1", 3), ret)); + BOOST_CHECK(!ParseMoney(std::string("\01", 2), ret)); + BOOST_CHECK(!ParseMoney(std::string("1\0", 2), ret)); } BOOST_AUTO_TEST_CASE(util_IsHex) diff --git a/src/tinyformat.h b/src/tinyformat.h index 182f518a0b..be63f2d5d8 100644 --- a/src/tinyformat.h +++ b/src/tinyformat.h @@ -33,6 +33,7 @@ // // * Type safety and extensibility for user defined types. // * C99 printf() compatibility, to the extent possible using std::ostream +// * POSIX extension for positional arguments // * Simplicity and minimalism. A single header file to include and distribute // with your projects. // * Augment rather than replace the standard stream formatting mechanism @@ -42,7 +43,7 @@ // Main interface example usage // ---------------------------- // -// To print a date to std::cout: +// To print a date to std::cout for American usage: // // std::string weekday = "Wednesday"; // const char* month = "July"; @@ -52,6 +53,14 @@ // // tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min); // +// POSIX extension for positional arguments is available. +// The ability to rearrange formatting arguments is an important feature +// for localization because the word order may vary in different languages. +// +// Previous example for German usage. Arguments are reordered: +// +// tfm::printf("%1$s, %3$d. %2$s, %4$d:%5$.2d\n", weekday, month, day, hour, min); +// // The strange types here emphasize the type safety of the interface; it is // possible to print a std::string using the "%s" conversion, and a // size_t using the "%d" conversion. A similar result could be achieved @@ -133,12 +142,17 @@ namespace tfm = tinyformat; //------------------------------------------------------------------------------ // Implementation details. #include <algorithm> -#include <cassert> #include <iostream> #include <sstream> -#include <stdexcept> +#include <stdexcept> // Added for Bitcoin Core + +#ifndef TINYFORMAT_ASSERT +# include <cassert> +# define TINYFORMAT_ASSERT(cond) assert(cond) +#endif #ifndef TINYFORMAT_ERROR +# include <cassert> # define TINYFORMAT_ERROR(reason) assert(0 && reason) #endif @@ -149,13 +163,13 @@ namespace tfm = tinyformat; #endif #if defined(__GLIBCXX__) && __GLIBCXX__ < 20080201 -// std::showpos is broken on old libstdc++ as provided with OSX. See +// std::showpos is broken on old libstdc++ as provided with macOS. See // http://gcc.gnu.org/ml/libstdc++/2007-11/msg00075.html # define TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND #endif #ifdef __APPLE__ -// Workaround OSX linker warning: Xcode uses different default symbol +// Workaround macOS linker warning: Xcode uses different default symbol // visibilities for static libs vs executables (see issue #25) # define TINYFORMAT_HIDDEN __attribute__((visibility("hidden"))) #else @@ -164,6 +178,7 @@ namespace tfm = tinyformat; namespace tinyformat { +// Added for Bitcoin Core class format_error: public std::runtime_error { public: @@ -218,7 +233,7 @@ template<int n> struct is_wchar<wchar_t[n]> {}; template<typename T, typename fmtT, bool convertible = is_convertible<T, fmtT>::value> struct formatValueAsType { - static void invoke(std::ostream& /*out*/, const T& /*value*/) { assert(0); } + static void invoke(std::ostream& /*out*/, const T& /*value*/) { TINYFORMAT_ASSERT(0); } }; // Specialized version for types that can actually be converted to fmtT, as // indicated by the "convertible" template parameter. @@ -240,8 +255,7 @@ struct formatZeroIntegerWorkaround<T,true> { static bool invoke(std::ostream& out, const T& value) { - if (static_cast<int>(value) == 0 && out.flags() & std::ios::showpos) - { + if (static_cast<int>(value) == 0 && out.flags() & std::ios::showpos) { out << "+0"; return true; } @@ -282,7 +296,7 @@ inline void formatTruncated(std::ostream& out, const T& value, int ntrunc) inline void formatTruncated(std::ostream& out, type* value, int ntrunc) \ { \ std::streamsize len = 0; \ - while(len < ntrunc && value[len] != 0) \ + while (len < ntrunc && value[len] != 0) \ ++len; \ out.write(value, len); \ } @@ -328,15 +342,14 @@ inline void formatValue(std::ostream& out, const char* /*fmtBegin*/, // could otherwise lead to a crash when printing a dangling (const char*). const bool canConvertToChar = detail::is_convertible<T,char>::value; const bool canConvertToVoidPtr = detail::is_convertible<T, const void*>::value; - if(canConvertToChar && *(fmtEnd-1) == 'c') + if (canConvertToChar && *(fmtEnd-1) == 'c') detail::formatValueAsType<T, char>::invoke(out, value); - else if(canConvertToVoidPtr && *(fmtEnd-1) == 'p') + else if (canConvertToVoidPtr && *(fmtEnd-1) == 'p') detail::formatValueAsType<T, const void*>::invoke(out, value); #ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND - else if(detail::formatZeroIntegerWorkaround<T>::invoke(out, value)) /**/; + else if (detail::formatZeroIntegerWorkaround<T>::invoke(out, value)) /**/; #endif - else if(ntrunc >= 0) - { + else if (ntrunc >= 0) { // Take care not to overread C strings in truncating conversions like // "%.4s" where at most 4 characters may be read. detail::formatTruncated(out, value, ntrunc); @@ -351,8 +364,7 @@ inline void formatValue(std::ostream& out, const char* /*fmtBegin*/, inline void formatValue(std::ostream& out, const char* /*fmtBegin*/, \ const char* fmtEnd, int /**/, charType value) \ { \ - switch(*(fmtEnd-1)) \ - { \ + switch (*(fmtEnd-1)) { \ case 'u': case 'd': case 'i': case 'o': case 'X': case 'x': \ out << static_cast<int>(value); break; \ default: \ @@ -490,19 +502,19 @@ namespace detail { // Type-opaque holder for an argument to format(), with associated actions on // the type held as explicit function pointers. This allows FormatArg's for -// each argument to be allocated as a homogenous array inside FormatList +// each argument to be allocated as a homogeneous array inside FormatList // whereas a naive implementation based on inheritance does not. class FormatArg { public: FormatArg() - : m_value(nullptr), - m_formatImpl(nullptr), - m_toIntImpl(nullptr) - { } + : m_value(NULL), + m_formatImpl(NULL), + m_toIntImpl(NULL) + { } template<typename T> - explicit FormatArg(const T& value) + FormatArg(const T& value) : m_value(static_cast<const void*>(&value)), m_formatImpl(&formatImpl<T>), m_toIntImpl(&toIntImpl<T>) @@ -511,15 +523,15 @@ class FormatArg void format(std::ostream& out, const char* fmtBegin, const char* fmtEnd, int ntrunc) const { - assert(m_value); - assert(m_formatImpl); + TINYFORMAT_ASSERT(m_value); + TINYFORMAT_ASSERT(m_formatImpl); m_formatImpl(out, fmtBegin, fmtEnd, ntrunc, m_value); } int toInt() const { - assert(m_value); - assert(m_toIntImpl); + TINYFORMAT_ASSERT(m_value); + TINYFORMAT_ASSERT(m_toIntImpl); return m_toIntImpl(m_value); } @@ -549,36 +561,68 @@ class FormatArg inline int parseIntAndAdvance(const char*& c) { int i = 0; - for(;*c >= '0' && *c <= '9'; ++c) + for (;*c >= '0' && *c <= '9'; ++c) i = 10*i + (*c - '0'); return i; } -// Print literal part of format string and return next format spec -// position. +// Parse width or precision `n` from format string pointer `c`, and advance it +// to the next character. If an indirection is requested with `*`, the argument +// is read from `args[argIndex]` and `argIndex` is incremented (or read +// from `args[n]` in positional mode). Returns true if one or more +// characters were read. +inline bool parseWidthOrPrecision(int& n, const char*& c, bool positionalMode, + const detail::FormatArg* args, + int& argIndex, int numArgs) +{ + if (*c >= '0' && *c <= '9') { + n = parseIntAndAdvance(c); + } + else if (*c == '*') { + ++c; + n = 0; + if (positionalMode) { + int pos = parseIntAndAdvance(c) - 1; + if (*c != '$') + TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one"); + if (pos >= 0 && pos < numArgs) + n = args[pos].toInt(); + else + TINYFORMAT_ERROR("tinyformat: Positional argument out of range"); + ++c; + } + else { + if (argIndex < numArgs) + n = args[argIndex++].toInt(); + else + TINYFORMAT_ERROR("tinyformat: Not enough arguments to read variable width or precision"); + } + } + else { + return false; + } + return true; +} + +// Print literal part of format string and return next format spec position. // -// Skips over any occurrences of '%%', printing a literal '%' to the -// output. The position of the first % character of the next -// nontrivial format spec is returned, or the end of string. +// Skips over any occurrences of '%%', printing a literal '%' to the output. +// The position of the first % character of the next nontrivial format spec is +// returned, or the end of string. inline const char* printFormatStringLiteral(std::ostream& out, const char* fmt) { const char* c = fmt; - for(;; ++c) - { - switch(*c) - { - case '\0': - out.write(fmt, c - fmt); + for (;; ++c) { + if (*c == '\0') { + out.write(fmt, c - fmt); + return c; + } + else if (*c == '%') { + out.write(fmt, c - fmt); + if (*(c+1) != '%') return c; - case '%': - out.write(fmt, c - fmt); - if(*(c+1) != '%') - return c; - // for "%%", tack trailing % onto next literal section. - fmt = ++c; - break; - default: - break; + // for "%%", tack trailing % onto next literal section. + fmt = ++c; } } } @@ -587,23 +631,43 @@ inline const char* printFormatStringLiteral(std::ostream& out, const char* fmt) // Parse a format string and set the stream state accordingly. // // The format mini-language recognized here is meant to be the one from C99, -// with the form "%[flags][width][.precision][length]type". +// with the form "%[flags][width][.precision][length]type" with POSIX +// positional arguments extension. +// +// POSIX positional arguments extension: +// Conversions can be applied to the nth argument after the format in +// the argument list, rather than to the next unused argument. In this case, +// the conversion specifier character % (see below) is replaced by the sequence +// "%n$", where n is a decimal integer in the range [1,{NL_ARGMAX}], +// giving the position of the argument in the argument list. This feature +// provides for the definition of format strings that select arguments +// in an order appropriate to specific languages. +// +// The format can contain either numbered argument conversion specifications +// (that is, "%n$" and "*m$"), or unnumbered argument conversion specifications +// (that is, % and * ), but not both. The only exception to this is that %% +// can be mixed with the "%n$" form. The results of mixing numbered and +// unnumbered argument specifications in a format string are undefined. +// When numbered argument specifications are used, specifying the Nth argument +// requires that all the leading arguments, from the first to the (N-1)th, +// are specified in the format string. +// +// In format strings containing the "%n$" form of conversion specification, +// numbered arguments in the argument list can be referenced from the format +// string as many times as required. // // Formatting options which can't be natively represented using the ostream // state are returned in spacePadPositive (for space padded positive numbers) // and ntrunc (for truncating conversions). argIndex is incremented if // necessary to pull out variable width and precision. The function returns a // pointer to the character after the end of the current format spec. -inline const char* streamStateFromFormat(std::ostream& out, bool& spacePadPositive, +inline const char* streamStateFromFormat(std::ostream& out, bool& positionalMode, + bool& spacePadPositive, int& ntrunc, const char* fmtStart, - const detail::FormatArg* formatters, - int& argIndex, int numFormatters) + const detail::FormatArg* args, + int& argIndex, int numArgs) { - if(*fmtStart != '%') - { - TINYFORMAT_ERROR("tinyformat: Not enough conversion specifiers in format string"); - return fmtStart; - } + TINYFORMAT_ASSERT(*fmtStart == '%'); // Reset stream state to defaults. out.width(0); out.precision(6); @@ -616,100 +680,113 @@ inline const char* streamStateFromFormat(std::ostream& out, bool& spacePadPositi bool widthSet = false; int widthExtra = 0; const char* c = fmtStart + 1; - // 1) Parse flags - for(;; ++c) - { - switch(*c) - { - case '#': - out.setf(std::ios::showpoint | std::ios::showbase); - continue; - case '0': - // overridden by left alignment ('-' flag) - if(!(out.flags() & std::ios::left)) - { - // Use internal padding so that numeric values are - // formatted correctly, eg -00010 rather than 000-10 - out.fill('0'); - out.setf(std::ios::internal, std::ios::adjustfield); - } - continue; - case '-': - out.fill(' '); - out.setf(std::ios::left, std::ios::adjustfield); - continue; - case ' ': - // overridden by show positive sign, '+' flag. - if(!(out.flags() & std::ios::showpos)) - spacePadPositive = true; - continue; - case '+': - out.setf(std::ios::showpos); - spacePadPositive = false; - widthExtra = 1; - continue; - default: - break; + + // 1) Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag. + if (*c >= '0' && *c <= '9') { + const char tmpc = *c; + int value = parseIntAndAdvance(c); + if (*c == '$') { + // value is an argument index + if (value > 0 && value <= numArgs) + argIndex = value - 1; + else + TINYFORMAT_ERROR("tinyformat: Positional argument out of range"); + ++c; + positionalMode = true; + } + else if (positionalMode) { + TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one"); + } + else { + if (tmpc == '0') { + // Use internal padding so that numeric values are + // formatted correctly, eg -00010 rather than 000-10 + out.fill('0'); + out.setf(std::ios::internal, std::ios::adjustfield); + } + if (value != 0) { + // Nonzero value means that we parsed width. + widthSet = true; + out.width(value); + } } - break; } - // 2) Parse width - if(*c >= '0' && *c <= '9') - { - widthSet = true; - out.width(parseIntAndAdvance(c)); + else if (positionalMode) { + TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one"); } - if(*c == '*') - { - widthSet = true; + // 2) Parse flags and width if we did not do it in previous step. + if (!widthSet) { + // Parse flags + for (;; ++c) { + switch (*c) { + case '#': + out.setf(std::ios::showpoint | std::ios::showbase); + continue; + case '0': + // overridden by left alignment ('-' flag) + if (!(out.flags() & std::ios::left)) { + // Use internal padding so that numeric values are + // formatted correctly, eg -00010 rather than 000-10 + out.fill('0'); + out.setf(std::ios::internal, std::ios::adjustfield); + } + continue; + case '-': + out.fill(' '); + out.setf(std::ios::left, std::ios::adjustfield); + continue; + case ' ': + // overridden by show positive sign, '+' flag. + if (!(out.flags() & std::ios::showpos)) + spacePadPositive = true; + continue; + case '+': + out.setf(std::ios::showpos); + spacePadPositive = false; + widthExtra = 1; + continue; + default: + break; + } + break; + } + // Parse width int width = 0; - if(argIndex < numFormatters) - width = formatters[argIndex++].toInt(); - else - TINYFORMAT_ERROR("tinyformat: Not enough arguments to read variable width"); - if(width < 0) - { - // negative widths correspond to '-' flag set - out.fill(' '); - out.setf(std::ios::left, std::ios::adjustfield); - width = -width; + widthSet = parseWidthOrPrecision(width, c, positionalMode, + args, argIndex, numArgs); + if (widthSet) { + if (width < 0) { + // negative widths correspond to '-' flag set + out.fill(' '); + out.setf(std::ios::left, std::ios::adjustfield); + width = -width; + } + out.width(width); } - out.width(width); - ++c; } // 3) Parse precision - if(*c == '.') - { + if (*c == '.') { ++c; int precision = 0; - if(*c == '*') - { - ++c; - if(argIndex < numFormatters) - precision = formatters[argIndex++].toInt(); - else - TINYFORMAT_ERROR("tinyformat: Not enough arguments to read variable precision"); - } - else - { - if(*c >= '0' && *c <= '9') - precision = parseIntAndAdvance(c); - else if(*c == '-') // negative precisions ignored, treated as zero. - parseIntAndAdvance(++c); - } - out.precision(precision); - precisionSet = true; + parseWidthOrPrecision(precision, c, positionalMode, + args, argIndex, numArgs); + // Presence of `.` indicates precision set, unless the inferred value + // was negative in which case the default is used. + precisionSet = precision >= 0; + if (precisionSet) + out.precision(precision); } // 4) Ignore any C99 length modifier - while(*c == 'l' || *c == 'h' || *c == 'L' || - *c == 'j' || *c == 'z' || *c == 't') + while (*c == 'l' || *c == 'h' || *c == 'L' || + *c == 'j' || *c == 'z' || *c == 't') { ++c; + } // 5) We're up to the conversion specifier character. // Set stream flags based on conversion specifier (thanks to the // boost::format class for forging the way here). bool intConversion = false; - switch(*c) - { + switch (*c) { case 'u': case 'd': case 'i': out.setf(std::ios::dec, std::ios::basefield); intConversion = true; @@ -738,6 +815,18 @@ inline const char* streamStateFromFormat(std::ostream& out, bool& spacePadPositi case 'f': out.setf(std::ios::fixed, std::ios::floatfield); break; + case 'A': + out.setf(std::ios::uppercase); + // Falls through + case 'a': +# ifdef _MSC_VER + // Workaround https://developercommunity.visualstudio.com/content/problem/520472/hexfloat-stream-output-does-not-ignore-precision-a.html + // by always setting maximum precision on MSVC to avoid precision + // loss for doubles. + out.precision(13); +# endif + out.setf(std::ios::fixed | std::ios::scientific, std::ios::floatfield); + break; case 'G': out.setf(std::ios::uppercase); // Falls through @@ -746,17 +835,13 @@ inline const char* streamStateFromFormat(std::ostream& out, bool& spacePadPositi // As in boost::format, let stream decide float format. out.flags(out.flags() & ~std::ios::floatfield); break; - case 'a': case 'A': - TINYFORMAT_ERROR("tinyformat: the %a and %A conversion specs " - "are not supported"); - break; case 'c': // Handled as special case inside formatValue() break; case 's': - if(precisionSet) + if (precisionSet) ntrunc = static_cast<int>(out.precision()); - // Make %s print booleans as "true" and "false" + // Make %s print Booleans as "true" and "false" out.setf(std::ios::boolalpha); break; case 'n': @@ -770,8 +855,7 @@ inline const char* streamStateFromFormat(std::ostream& out, bool& spacePadPositi default: break; } - if(intConversion && precisionSet && !widthSet) - { + if (intConversion && precisionSet && !widthSet) { // "precision" for integers gives the minimum number of digits (to be // padded with zeros on the left). This isn't really supported by the // iostreams, but we can approximately simulate it with the width if @@ -786,8 +870,8 @@ inline const char* streamStateFromFormat(std::ostream& out, bool& spacePadPositi //------------------------------------------------------------------------------ inline void formatImpl(std::ostream& out, const char* fmt, - const detail::FormatArg* formatters, - int numFormatters) + const detail::FormatArg* args, + int numArgs) { // Saved stream state std::streamsize origWidth = out.width(); @@ -795,26 +879,34 @@ inline void formatImpl(std::ostream& out, const char* fmt, std::ios::fmtflags origFlags = out.flags(); char origFill = out.fill(); - for (int argIndex = 0; argIndex < numFormatters; ++argIndex) - { - // Parse the format string + // "Positional mode" means all format specs should be of the form "%n$..." + // with `n` an integer. We detect this in `streamStateFromFormat`. + bool positionalMode = false; + int argIndex = 0; + while (true) { fmt = printFormatStringLiteral(out, fmt); + if (*fmt == '\0') { + if (!positionalMode && argIndex < numArgs) { + TINYFORMAT_ERROR("tinyformat: Not enough conversion specifiers in format string"); + } + break; + } bool spacePadPositive = false; int ntrunc = -1; - const char* fmtEnd = streamStateFromFormat(out, spacePadPositive, ntrunc, fmt, - formatters, argIndex, numFormatters); - if (argIndex >= numFormatters) - { - // Check args remain after reading any variable width/precision - TINYFORMAT_ERROR("tinyformat: Not enough format arguments"); + const char* fmtEnd = streamStateFromFormat(out, positionalMode, spacePadPositive, ntrunc, fmt, + args, argIndex, numArgs); + // NB: argIndex may be incremented by reading variable width/precision + // in `streamStateFromFormat`, so do the bounds check here. + if (argIndex >= numArgs) { + TINYFORMAT_ERROR("tinyformat: Too many conversion specifiers in format string"); return; } - const FormatArg& arg = formatters[argIndex]; + const FormatArg& arg = args[argIndex]; // Format the arg into the stream. - if(!spacePadPositive) + if (!spacePadPositive) { arg.format(out, fmt, fmtEnd, ntrunc); - else - { + } + else { // The following is a special case with no direct correspondence // between stream formatting and the printf() behaviour. Simulate // it crudely by formatting into a temporary string stream and @@ -824,18 +916,17 @@ inline void formatImpl(std::ostream& out, const char* fmt, tmpStream.setf(std::ios::showpos); arg.format(tmpStream, fmt, fmtEnd, ntrunc); std::string result = tmpStream.str(); // allocates... yuck. - for(size_t i = 0, iend = result.size(); i < iend; ++i) - if(result[i] == '+') result[i] = ' '; + for (size_t i = 0, iend = result.size(); i < iend; ++i) { + if (result[i] == '+') + result[i] = ' '; + } out << result; } + if (!positionalMode) + ++argIndex; fmt = fmtEnd; } - // Print remaining part of format string. - fmt = printFormatStringLiteral(out, fmt); - if(*fmt != '\0') - TINYFORMAT_ERROR("tinyformat: Too many conversion specifiers in format string"); - // Restore stream state out.width(origWidth); out.precision(origPrecision); @@ -855,14 +946,14 @@ inline void formatImpl(std::ostream& out, const char* fmt, class FormatList { public: - FormatList(detail::FormatArg* formatters, int N) - : m_formatters(formatters), m_N(N) { } + FormatList(detail::FormatArg* args, int N) + : m_args(args), m_N(N) { } friend void vformat(std::ostream& out, const char* fmt, const FormatList& list); private: - const detail::FormatArg* m_formatters; + const detail::FormatArg* m_args; int m_N; }; @@ -879,29 +970,33 @@ class FormatListN : public FormatList public: #ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES template<typename... Args> - explicit FormatListN(const Args&... args) + FormatListN(const Args&... args) : FormatList(&m_formatterStore[0], N), m_formatterStore { FormatArg(args)... } { static_assert(sizeof...(args) == N, "Number of args must be N"); } #else // C++98 version void init(int) {} -# define TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR(n) \ - \ - template<TINYFORMAT_ARGTYPES(n)> \ - explicit FormatListN(TINYFORMAT_VARARGS(n)) \ - : FormatList(&m_formatterStore[0], n) \ - { assert(n == N); init(0, TINYFORMAT_PASSARGS(n)); } \ - \ - template<TINYFORMAT_ARGTYPES(n)> \ - void init(int i, TINYFORMAT_VARARGS(n)) \ - { \ - m_formatterStore[i] = FormatArg(v1); \ - init(i+1 TINYFORMAT_PASSARGS_TAIL(n)); \ +# define TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR(n) \ + \ + template<TINYFORMAT_ARGTYPES(n)> \ + FormatListN(TINYFORMAT_VARARGS(n)) \ + : FormatList(&m_formatterStore[0], n) \ + { TINYFORMAT_ASSERT(n == N); init(0, TINYFORMAT_PASSARGS(n)); } \ + \ + template<TINYFORMAT_ARGTYPES(n)> \ + void init(int i, TINYFORMAT_VARARGS(n)) \ + { \ + m_formatterStore[i] = FormatArg(v1); \ + init(i+1 TINYFORMAT_PASSARGS_TAIL(n)); \ } TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR) # undef TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR #endif + FormatListN(const FormatListN& other) + : FormatList(&m_formatterStore[0], N) + { std::copy(&other.m_formatterStore[0], &other.m_formatterStore[N], + &m_formatterStore[0]); } private: FormatArg m_formatterStore[N]; @@ -956,7 +1051,7 @@ TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_MAKEFORMATLIST) /// list of format arguments is held in a single function argument. inline void vformat(std::ostream& out, const char* fmt, FormatListRef list) { - detail::formatImpl(out, fmt, list.m_formatters, list.m_N); + detail::formatImpl(out, fmt, list.m_args, list.m_N); } @@ -993,6 +1088,7 @@ void printfln(const char* fmt, const Args&... args) std::cout << '\n'; } + #else // C++98 version inline void format(std::ostream& out, const char* fmt) @@ -1063,6 +1159,7 @@ std::string format(const std::string &fmt, const Args&... args) } // namespace tinyformat +// Added for Bitcoin Core: /** Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for details) */ #define strprintf tfm::format diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 08f935c24f..3c967a04c0 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -17,6 +17,7 @@ #include <util/system.h> #include <util/moneystr.h> #include <util/time.h> +#include <validationinterface.h> CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee, int64_t _nTime, unsigned int _entryHeight, @@ -403,7 +404,12 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) { - NotifyEntryRemoved(it->GetSharedTx(), reason); + CTransactionRef ptx = it->GetSharedTx(); + NotifyEntryRemoved(ptx, reason); + if (reason != MemPoolRemovalReason::BLOCK && reason != MemPoolRemovalReason::CONFLICT) { + GetMainSignals().TransactionRemovedFromMempool(ptx); + } + const uint256 hash = it->GetTx().GetHash(); for (const CTxIn& txin : it->GetTx().vin) mapNextTx.erase(txin.prevout); diff --git a/src/util/moneystr.cpp b/src/util/moneystr.cpp index ba5a12e58c..3e75a2e3e9 100644 --- a/src/util/moneystr.cpp +++ b/src/util/moneystr.cpp @@ -7,6 +7,7 @@ #include <tinyformat.h> #include <util/strencodings.h> +#include <util/string.h> std::string FormatMoney(const CAmount& n) { @@ -32,6 +33,9 @@ std::string FormatMoney(const CAmount& n) bool ParseMoney(const std::string& str, CAmount& nRet) { + if (!ValidAsCString(str)) { + return false; + } return ParseMoney(str.c_str(), nRet); } diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 46042f5634..31719cd975 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/strencodings.h> +#include <util/string.h> #include <tinyformat.h> @@ -190,6 +191,12 @@ std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) std::string DecodeBase64(const std::string& str, bool* pf_invalid) { + if (!ValidAsCString(str)) { + if (pf_invalid) { + *pf_invalid = true; + } + return {}; + } std::vector<unsigned char> vchRet = DecodeBase64(str.c_str(), pf_invalid); return std::string((const char*)vchRet.data(), vchRet.size()); } @@ -259,6 +266,12 @@ std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) std::string DecodeBase32(const std::string& str, bool* pf_invalid) { + if (!ValidAsCString(str)) { + if (pf_invalid) { + *pf_invalid = true; + } + return {}; + } std::vector<unsigned char> vchRet = DecodeBase32(str.c_str(), pf_invalid); return std::string((const char*)vchRet.data(), vchRet.size()); } @@ -269,7 +282,7 @@ NODISCARD static bool ParsePrechecks(const std::string& str) return false; if (str.size() >= 1 && (IsSpace(str[0]) || IsSpace(str[str.size()-1]))) // No padding allowed return false; - if (str.size() != strlen(str.c_str())) // No embedded NUL characters allowed + if (!ValidAsCString(str)) // No embedded NUL characters allowed return false; return true; } diff --git a/src/util/string.h b/src/util/string.h index 76a83a4949..c6fa08e5b3 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -5,6 +5,9 @@ #ifndef BITCOIN_UTIL_STRING_H #define BITCOIN_UTIL_STRING_H +#include <attributes.h> + +#include <cstring> #include <string> #include <vector> @@ -31,4 +34,12 @@ inline std::string Join(const std::vector<std::string>& list, const std::string& return Join(list, separator, [](const std::string& i) { return i; }); } +/** + * Check if a string does not contain any embedded NUL (\0) characters + */ +NODISCARD inline bool ValidAsCString(const std::string& str) noexcept +{ + return str.size() == strlen(str.c_str()); +} + #endif // BITCOIN_UTIL_STRENCODINGS_H diff --git a/src/util/system.cpp b/src/util/system.cpp index 2a2ae6fdf5..563ff6a54b 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1126,17 +1126,13 @@ fs::path AbsPathForConfigVal(const fs::path& path, bool net_specific) return fs::absolute(path, GetDataDir(net_specific)); } -int ScheduleBatchPriority() +void ScheduleBatchPriority() { #ifdef SCHED_BATCH const static sched_param param{}; - if (int ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m)) { + if (pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m) != 0) { LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(errno)); - return ret; } - return 0; -#else - return 1; #endif } diff --git a/src/util/system.h b/src/util/system.h index e0b6371dc9..82903b5187 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -367,10 +367,8 @@ std::string CopyrightHolders(const std::string& strPrefix); * On platforms that support it, tell the kernel the calling thread is * CPU-intensive and non-interactive. See SCHED_BATCH in sched(7) for details. * - * @return The return value of sched_setschedule(), or 1 on systems without - * sched_setschedule(). */ -int ScheduleBatchPriority(); +void ScheduleBatchPriority(); namespace util { diff --git a/src/validation.h b/src/validation.h index d87113c41f..54f97e7213 100644 --- a/src/validation.h +++ b/src/validation.h @@ -23,7 +23,6 @@ #include <versionbits.h> #include <serialize.h> -#include <algorithm> #include <atomic> #include <map> #include <memory> diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 663308bae9..6c0f4d5edd 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -7,9 +7,9 @@ #include <primitives/block.h> #include <scheduler.h> -#include <txmempool.h> #include <future> +#include <unordered_map> #include <utility> #include <boost/signals2/signal.hpp> @@ -46,11 +46,6 @@ struct MainSignalsInstance { static CMainSignals g_signals; -// This map has to a separate global instead of a member of MainSignalsInstance, -// because RegisterWithMempoolSignals is currently called before RegisterBackgroundSignalScheduler, -// so MainSignalsInstance hasn't been created yet. -static std::unordered_map<CTxMemPool*, boost::signals2::scoped_connection> g_connNotifyEntryRemoved; - void CMainSignals::RegisterBackgroundSignalScheduler(CScheduler& scheduler) { assert(!m_internals); m_internals.reset(new MainSignalsInstance(&scheduler)); @@ -71,17 +66,6 @@ size_t CMainSignals::CallbacksPending() { return m_internals->m_schedulerClient.CallbacksPending(); } -void CMainSignals::RegisterWithMempoolSignals(CTxMemPool& pool) { - g_connNotifyEntryRemoved.emplace(std::piecewise_construct, - std::forward_as_tuple(&pool), - std::forward_as_tuple(pool.NotifyEntryRemoved.connect(std::bind(&CMainSignals::MempoolEntryRemoved, this, std::placeholders::_1, std::placeholders::_2))) - ); -} - -void CMainSignals::UnregisterWithMempoolSignals(CTxMemPool& pool) { - g_connNotifyEntryRemoved.erase(&pool); -} - CMainSignals& GetMainSignals() { return g_signals; @@ -126,13 +110,6 @@ void SyncWithValidationInterfaceQueue() { promise.get_future().wait(); } -void CMainSignals::MempoolEntryRemoved(CTransactionRef ptx, MemPoolRemovalReason reason) { - if (reason != MemPoolRemovalReason::BLOCK && reason != MemPoolRemovalReason::CONFLICT) { - m_internals->m_schedulerClient.AddToProcessQueue([ptx, this] { - m_internals->TransactionRemovedFromMempool(ptx); - }); - } -} void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { // Dependencies exist that require UpdatedBlockTip events to be delivered in the order in which @@ -150,6 +127,12 @@ void CMainSignals::TransactionAddedToMempool(const CTransactionRef &ptx) { }); } +void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef &ptx) { + m_internals->m_schedulerClient.AddToProcessQueue([ptx, this] { + m_internals->TransactionRemovedFromMempool(ptx); + }); +} + void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>>& pvtxConflicted) { m_internals->m_schedulerClient.AddToProcessQueue([pblock, pindex, pvtxConflicted, this] { m_internals->BlockConnected(pblock, pindex, *pvtxConflicted); diff --git a/src/validationinterface.h b/src/validationinterface.h index 6a8059a6a0..63aad8661f 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -21,8 +21,6 @@ class CConnman; class CValidationInterface; class uint256; class CScheduler; -class CTxMemPool; -enum class MemPoolRemovalReason; // These functions dispatch to one or all registered wallets @@ -158,8 +156,6 @@ private: friend void ::UnregisterAllValidationInterfaces(); friend void ::CallFunctionInValidationInterfaceQueue(std::function<void ()> func); - void MempoolEntryRemoved(CTransactionRef tx, MemPoolRemovalReason reason); - public: /** Register a CScheduler to give callbacks which should run in the background (may only be called once) */ void RegisterBackgroundSignalScheduler(CScheduler& scheduler); @@ -170,13 +166,10 @@ public: size_t CallbacksPending(); - /** Register with mempool to call TransactionRemovedFromMempool callbacks */ - void RegisterWithMempoolSignals(CTxMemPool& pool); - /** Unregister with mempool */ - void UnregisterWithMempoolSignals(CTxMemPool& pool); void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload); void TransactionAddedToMempool(const CTransactionRef &); + void TransactionRemovedFromMempool(const CTransactionRef &); void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &); void BlockDisconnected(const std::shared_ptr<const CBlock> &, const CBlockIndex* pindex); void ChainStateFlushed(const CBlockLocator &); diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index fca4b75c45..2893d0ab3d 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -6,14 +6,18 @@ #define BITCOIN_WALLET_COINCONTROL_H #include <optional.h> +#include <outputtype.h> #include <policy/feerate.h> #include <policy/fees.h> #include <primitives/transaction.h> -#include <wallet/wallet.h> +#include <script/standard.h> const int DEFAULT_MIN_DEPTH = 0; const int DEFAULT_MAX_DEPTH = 9999999; +//! Default for -avoidpartialspends +static constexpr bool DEFAULT_AVOIDPARTIALSPENDS = false; + /** Coin Control Features. */ class CCoinControl { diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 8f0b495ac4..36588eb7d1 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -108,12 +108,11 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt return feebumper::Result::OK; } -static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, CCoinControl& coin_control, CAmount& old_fee) +static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CAmount old_fee, CCoinControl& coin_control) { // Get the fee rate of the original transaction. This is calculated from // the tx fee/vsize, so it may have been rounded down. Add 1 satoshi to the // result. - old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); CFeeRate feerate(old_fee, txSize); feerate += CFeeRate(1); @@ -309,6 +308,8 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo } } + old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); + if (coin_control.m_feerate) { // The user provided a feeRate argument. // We calculate this here to avoid compiler warning on the cs_wallet lock @@ -319,7 +320,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo } } else { // The user did not provide a feeRate argument - new_coin_control.m_feerate = EstimateFeeRate(wallet, wtx, new_coin_control, old_fee); + new_coin_control.m_feerate = EstimateFeeRate(wallet, wtx, old_fee, new_coin_control); } // Fill in required inputs we are double-spending(all of them) diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index a8b3df1f2e..dd0d2ffbd7 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -8,9 +8,11 @@ #include <net.h> #include <node/context.h> #include <outputtype.h> +#include <ui_interface.h> #include <util/moneystr.h> #include <util/system.h> #include <util/translation.h> +#include <wallet/coincontrol.h> #include <wallet/wallet.h> #include <walletinitinterface.h> @@ -58,7 +60,7 @@ void WalletInit::AddWalletOptions() const gArgs.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); gArgs.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); - gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); #if HAVE_SYSTEM gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); #endif diff --git a/src/wallet/psbtwallet.cpp b/src/wallet/psbtwallet.cpp index aa13cacca4..96c1ad8d3f 100644 --- a/src/wallet/psbtwallet.cpp +++ b/src/wallet/psbtwallet.cpp @@ -39,12 +39,35 @@ TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& ps return TransactionError::SIGHASH_MISMATCH; } - complete &= SignPSBTInput(HidingSigningProvider(pwallet->GetSigningProvider(), !sign, !bip32derivs), psbtx, i, sighash_type); + // Get the scriptPubKey to know which SigningProvider to use + CScript script; + if (!input.witness_utxo.IsNull()) { + script = input.witness_utxo.scriptPubKey; + } else if (input.non_witness_utxo) { + script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey; + } else { + // There's no UTXO so we can just skip this now + complete = false; + continue; + } + SignatureData sigdata; + input.FillSignatureData(sigdata); + const SigningProvider* provider = pwallet->GetSigningProvider(script, sigdata); + if (!provider) { + complete = false; + continue; + } + + complete &= SignPSBTInput(HidingSigningProvider(provider, !sign, !bip32derivs), psbtx, i, sighash_type); } // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { - UpdatePSBTOutput(HidingSigningProvider(pwallet->GetSigningProvider(), true, !bip32derivs), psbtx, i); + const CTxOut& out = psbtx.tx->vout.at(i); + const SigningProvider* provider = pwallet->GetSigningProvider(out.scriptPubKey); + if (provider) { + UpdatePSBTOutput(HidingSigningProvider(provider, true, !bip32derivs), psbtx, i); + } } return TransactionError::OK; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index d95481500d..633ac1b16d 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1286,7 +1286,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) {"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"} }, - {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n" + {"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" diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 00742aed7a..63cbe02b17 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -560,7 +560,11 @@ static UniValue signmessage(const JSONRPCRequest& request) throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } - const SigningProvider* provider = pwallet->GetSigningProvider(); + CScript script_pub_key = GetScriptForDestination(*pkhash); + const SigningProvider* provider = pwallet->GetSigningProvider(script_pub_key); + if (!provider) { + throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); + } CKey key; CKeyID keyID(*pkhash); @@ -947,7 +951,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) } RPCHelpMan{"addmultisigaddress", - "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" + "\nAdd an nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" "Each key is a Bitcoin address or hex-encoded public key.\n" "This functionality is only intended for use with non-watchonly addresses.\n" "See `importaddress` for watchonly p2sh address support.\n" @@ -994,7 +998,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) { pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str())); } else { - pubkeys.push_back(AddrToPubKey(&spk_man, keys_or_addrs[i].get_str())); + pubkeys.push_back(AddrToPubKey(spk_man, keys_or_addrs[i].get_str())); } } @@ -1370,14 +1374,14 @@ static const std::string TransactionDescriptionString() " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n" " \"blockheight\": n, (numeric) The block height containing the transaction.\n" " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n" - " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" + " \"blocktime\": xxx, (numeric) The block time expressed in " + UNIX_EPOCH_TIME + ".\n" " \"txid\": \"transactionid\", (string) The transaction id.\n" " \"walletconflicts\": [ (array) Conflicting transaction ids.\n" " \"txid\", (string) The transaction id.\n" " ...\n" " ],\n" - " \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n" - " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT).\n" + " \"time\": xxx, (numeric) The transaction time expressed in " + UNIX_EPOCH_TIME + ".\n" + " \"timereceived\": xxx, (numeric) The time received expressed in " + UNIX_EPOCH_TIME + ".\n" " \"comment\": \"...\", (string) If a comment is associated with the transaction, only present if not empty.\n" " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" " may be unknown for unconfirmed transactions not in the mempool\n"; @@ -2424,10 +2428,10 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) " \"unconfirmed_balance\": xxx, (numeric) DEPRECATED. Identical to getbalances().mine.untrusted_pending\n" " \"immature_balance\": xxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.immature\n" " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" - " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n" + " \"keypoololdest\": xxxxxx, (numeric) the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool\n" " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n" " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n" - " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" + " \"unlocked_until\": ttt, (numeric) the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n" " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n" @@ -2940,34 +2944,36 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("label", i->second.name); } - const SigningProvider* provider = pwallet->GetSigningProvider(); - if (scriptPubKey.IsPayToScriptHash()) { - const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address)); - CScript redeemScript; - if (provider->GetCScript(hash, redeemScript)) { - entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); - // Now check if the redeemScript is actually a P2WSH script - CTxDestination witness_destination; - if (redeemScript.IsPayToWitnessScriptHash()) { - bool extracted = ExtractDestination(redeemScript, witness_destination); - CHECK_NONFATAL(extracted); - // Also return the witness script - const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination); - CScriptID id; - CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); - CScript witnessScript; - if (provider->GetCScript(id, witnessScript)) { - entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + const SigningProvider* provider = pwallet->GetSigningProvider(scriptPubKey); + if (provider) { + if (scriptPubKey.IsPayToScriptHash()) { + const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address)); + CScript redeemScript; + if (provider->GetCScript(hash, redeemScript)) { + entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); + // Now check if the redeemScript is actually a P2WSH script + CTxDestination witness_destination; + if (redeemScript.IsPayToWitnessScriptHash()) { + bool extracted = ExtractDestination(redeemScript, witness_destination); + CHECK_NONFATAL(extracted); + // Also return the witness script + const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination); + CScriptID id; + CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); + CScript witnessScript; + if (provider->GetCScript(id, witnessScript)) { + entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } } } - } - } else if (scriptPubKey.IsPayToWitnessScriptHash()) { - const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address); - CScriptID id; - CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); - CScript witnessScript; - if (provider->GetCScript(id, witnessScript)) { - entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } else if (scriptPubKey.IsPayToWitnessScriptHash()) { + const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address); + CScriptID id; + CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); + CScript witnessScript; + if (provider->GetCScript(id, witnessScript)) { + entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } } } } @@ -2978,8 +2984,11 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); if (out.fSolvable) { - auto descriptor = InferDescriptor(scriptPubKey, *pwallet->GetLegacyScriptPubKeyMan()); - entry.pushKV("desc", descriptor->ToString()); + const SigningProvider* provider = pwallet->GetSigningProvider(scriptPubKey); + if (provider) { + auto descriptor = InferDescriptor(scriptPubKey, *provider); + entry.pushKV("desc", descriptor->ToString()); + } } if (avoid_reuse) entry.pushKV("reused", reused); entry.pushKV("safe", out.fSafe); @@ -3288,7 +3297,23 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) // Parse the prevtxs array ParsePrevouts(request.params[1], nullptr, coins); - return SignTransaction(mtx, &*pwallet->GetLegacyScriptPubKeyMan(), coins, request.params[2]); + std::set<const SigningProvider*> providers; + for (const std::pair<COutPoint, Coin> coin_pair : coins) { + const SigningProvider* provider = pwallet->GetSigningProvider(coin_pair.second.out.scriptPubKey); + if (provider) { + providers.insert(std::move(provider)); + } + } + if (providers.size() == 0) { + // When there are no available providers, use DUMMY_SIGNING_PROVIDER so we can check if the tx is complete + providers.insert(&DUMMY_SIGNING_PROVIDER); + } + + UniValue result(UniValue::VOBJ); + for (const SigningProvider* provider : providers) { + SignTransaction(mtx, provider, coins, request.params[2], result); + } + return result; } static UniValue bumpfee(const JSONRPCRequest& request) @@ -3653,9 +3678,10 @@ static UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& de { UniValue ret(UniValue::VOBJ); UniValue detail = DescribeAddress(dest); + CScript script = GetScriptForDestination(dest); const SigningProvider* provider = nullptr; if (pwallet) { - provider = pwallet->GetSigningProvider(); + provider = pwallet->GetSigningProvider(script); } ret.pushKVs(detail); ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(provider), dest)); @@ -3683,52 +3709,57 @@ UniValue getaddressinfo(const JSONRPCRequest& request) } RPCHelpMan{"getaddressinfo", - "\nReturn information about the given bitcoin address. Some information requires the address\n" - "to be in the wallet.\n", + "\nReturn information about the given bitcoin address.\n" + "Some of the information will only be present if the address is in the active wallet.\n", { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to get the information of."}, + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for which to get information."}, }, RPCResult{ "{\n" - " \"address\" : \"address\", (string) The bitcoin address validated\n" - " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n" - " \"ismine\" : true|false, (boolean) If the address is yours or not\n" - " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" - " \"solvable\" : true|false, (boolean) Whether we know how to spend coins sent to this address, ignoring the possible lack of private keys\n" - " \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable)\n" - " \"isscript\" : true|false, (boolean) If the key is a script\n" - " \"ischange\" : true|false, (boolean) If the address was used for change output\n" - " \"iswitness\" : true|false, (boolean) If the address is a witness address\n" - " \"witness_version\" : version (numeric, optional) The version number of the witness program\n" - " \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program\n" - " \"script\" : \"type\" (string, optional) The output script type. Only if \"isscript\" is true and the redeemscript is known. Possible types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash, witness_v0_scripthash, witness_unknown\n" - " \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address\n" - " \"pubkeys\" (string, optional) Array of pubkeys associated with the known redeemscript (only if \"script\" is \"multisig\")\n" + " \"address\" : \"address\", (string) The bitcoin address validated.\n" + " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address.\n" + " \"ismine\" : true|false, (boolean) If the address is yours.\n" + " \"iswatchonly\" : true|false, (boolean) If the address is watchonly.\n" + " \"solvable\" : true|false, (boolean) If we know how to spend coins sent to this address, ignoring the possible lack of private keys.\n" + " \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable).\n" + " \"isscript\" : true|false, (boolean) If the key is a script.\n" + " \"ischange\" : true|false, (boolean) If the address was used for change output.\n" + " \"iswitness\" : true|false, (boolean) If the address is a witness address.\n" + " \"witness_version\" : version (numeric, optional) The version number of the witness program.\n" + " \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program.\n" + " \"script\" : \"type\" (string, optional) The output script type. Only if isscript is true and the redeemscript is known. Possible\n" + " types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash,\n" + " witness_v0_scripthash, witness_unknown.\n" + " \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address.\n" + " \"pubkeys\" (array, optional) Array of pubkeys associated with the known redeemscript (only if script is multisig).\n" " [\n" - " \"pubkey\"\n" + " \"pubkey\" (string)\n" " ,...\n" " ]\n" - " \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output (only if \"script\" is \"multisig\")\n" - " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key, for single-key addresses (possibly embedded in P2SH or P2WSH)\n" - " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. It includes all getaddressinfo output fields for the embedded address, excluding metadata (\"timestamp\", \"hdkeypath\", \"hdseedid\") and relation to the wallet (\"ismine\", \"iswatchonly\").\n" - " \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed\n" - " \"label\" : \"label\" (string) The label associated with the address, \"\" is the default label\n" - " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n" - " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" - " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed\n" - " \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingperint of the master key.\n" - " \"labels\" (object) Array of labels associated with the address.\n" + " \"sigsrequired\" : xxxxx (numeric, optional) The number of signatures required to spend multisig output (only if script is multisig).\n" + " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH).\n" + " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. Includes all\n" + " getaddressinfo output fields for the embedded address, excluding metadata (timestamp, hdkeypath,\n" + " hdseedid) and relation to the wallet (ismine, iswatchonly).\n" + " \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed.\n" + " \"label\" : \"label\" (string) The label associated with the address. Defaults to \"\". Equivalent to the name field in the labels array.\n" + " \"timestamp\" : timestamp, (number, optional) The creation time of the key, if available, expressed in " + UNIX_EPOCH_TIME + ".\n" + " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath, if the key is HD and available.\n" + " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed.\n" + " \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingerprint of the master key.\n" + " \"labels\" (object) An array of labels associated with the address. Currently limited to one label but returned\n" + " as an array to keep the API stable if multiple labels are enabled in the future.\n" " [\n" " { (json object of label data)\n" - " \"name\": \"labelname\" (string) The label\n" - " \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n" + " \"name\": \"label name\" (string) The label name. Defaults to \"\". Equivalent to the label field above.\n" + " \"purpose\": \"purpose\" (string) The purpose of the associated address (send or receive).\n" " },...\n" " ]\n" "}\n" }, RPCExamples{ - HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") - + HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleCli("getaddressinfo", "\"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl\"") + + HelpExampleRpc("getaddressinfo", "\"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl\"") }, }.Check(request); @@ -3747,24 +3778,40 @@ UniValue getaddressinfo(const JSONRPCRequest& request) CScript scriptPubKey = GetScriptForDestination(dest); ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); - const SigningProvider* provider = pwallet->GetSigningProvider(); + + const SigningProvider* provider = pwallet->GetSigningProvider(scriptPubKey); isminetype mine = pwallet->IsMine(dest); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); - bool solvable = IsSolvable(*provider, scriptPubKey); + + bool solvable = provider && IsSolvable(*provider, scriptPubKey); ret.pushKV("solvable", solvable); + if (solvable) { ret.pushKV("desc", InferDescriptor(scriptPubKey, *provider)->ToString()); } + ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); + + // Return DescribeWalletAddress fields. + // Always returned: isscript, ischange, iswitness. + // Optional: witness_version, witness_program, script, hex, pubkeys (array), + // sigsrequired, pubkey, embedded, iscompressed. UniValue detail = DescribeWalletAddress(pwallet, dest); ret.pushKVs(detail); + + // Return label field if existing. Currently only one label can be + // associated with an address, so the label should be equivalent to the + // value of the name key/value pair in the labels hash array below. if (pwallet->mapAddressBook.count(dest)) { ret.pushKV("label", pwallet->mapAddressBook[dest].name); } + ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); - ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(); + // Fetch KeyMetadata, if present, for the timestamp, hdkeypath, hdseedid, + // and hdmasterfingerprint fields. + ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey); if (spk_man) { if (const CKeyMetadata* meta = spk_man->GetMetadata(dest)) { ret.pushKV("timestamp", meta->nCreateTime); @@ -3776,9 +3823,11 @@ UniValue getaddressinfo(const JSONRPCRequest& request) } } - // Currently only one label can be associated with an address, return an array - // so the API remains stable if we allow multiple labels to be associated with - // an address. + // Return a labels array containing a hash of key/value pairs for the label + // name and address purpose. The name value is equivalent to the label field + // above. Currently only one label can be associated with an address, but we + // return an array so the API remains stable if we allow multiple labels to + // be associated with an address in the future. UniValue labels(UniValue::VARR); std::map<CTxDestination, CAddressBookData>::iterator mi = pwallet->mapAddressBook.find(dest); if (mi != pwallet->mapAddressBook.end()) { diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 3eaaf3786c..b4315014cb 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -18,7 +18,7 @@ bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestinat // Generate a new key that is added to wallet CPubKey new_key; - if (!GetKeyFromPool(new_key)) { + if (!GetKeyFromPool(new_key, type)) { error = "Error: Keypool ran out, please call keypoolrefill first"; return false; } @@ -202,12 +202,11 @@ isminetype LegacyScriptPubKeyMan::IsMine(const CScript& script) const assert(false); } -bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) +bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys) { { LOCK(cs_KeyStore); - if (!SetCrypted()) - return false; + assert(mapKeys.empty()); bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys bool keyFail = false; @@ -217,7 +216,7 @@ bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) const CPubKey &vchPubKey = (*mi).second.first; const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; CKey key; - if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) + if (!DecryptKey(master_key, vchCryptedSecret, vchPubKey, key)) { keyFail = true; break; @@ -233,53 +232,55 @@ bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) } if (keyFail || (!keyPass && !accept_no_keys)) return false; - vMasterKey = vMasterKeyIn; fDecryptionThoroughlyChecked = true; } - NotifyStatusChanged(this); return true; } -bool LegacyScriptPubKeyMan::EncryptKeys(CKeyingMaterial& vMasterKeyIn) +bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) { + AssertLockHeld(cs_wallet); LOCK(cs_KeyStore); - if (!mapCryptedKeys.empty() || IsCrypted()) + encrypted_batch = batch; + if (!mapCryptedKeys.empty()) { + encrypted_batch = nullptr; return false; + } - fUseCrypto = true; - for (const KeyMap::value_type& mKey : mapKeys) + KeyMap keys_to_encrypt; + keys_to_encrypt.swap(mapKeys); // Clear mapKeys so AddCryptedKeyInner will succeed. + for (const KeyMap::value_type& mKey : keys_to_encrypt) { const CKey &key = mKey.second; CPubKey vchPubKey = key.GetPubKey(); CKeyingMaterial vchSecret(key.begin(), key.end()); std::vector<unsigned char> vchCryptedSecret; - if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) + if (!EncryptSecret(master_key, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) { + encrypted_batch = nullptr; return false; - if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) + } + if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) { + encrypted_batch = nullptr; return false; + } } - mapKeys.clear(); + encrypted_batch = nullptr; return true; } -bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) +bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) { + if (!CanGetAddresses(internal)) { + return false; + } + if (!ReserveKeyFromKeyPool(index, keypool, internal)) { return false; } + address = GetDestinationForKey(keypool.vchPubKey, type); return true; } -void LegacyScriptPubKeyMan::KeepDestination(int64_t index) -{ - KeepKey(index); -} - -void LegacyScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) -{ - ReturnKey(index, internal, pubkey); -} - void LegacyScriptPubKeyMan::MarkUnusedAddresses(const CScript& script) { AssertLockHeld(cs_wallet); @@ -460,7 +461,7 @@ size_t LegacyScriptPubKeyMan::KeypoolCountExternalKeys() unsigned int LegacyScriptPubKeyMan::GetKeyPoolSize() const { AssertLockHeld(cs_wallet); - return setInternalKeyPool.size() + setExternalKeyPool.size(); + return setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(); } int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const @@ -548,7 +549,7 @@ bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& s RemoveWatchOnly(script); } - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { return batch.WriteKey(pubkey, secret.GetPrivKey(), mapKeyMetadata[pubkey.GetID()]); @@ -589,7 +590,7 @@ void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID& script_id, const bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) { LOCK(cs_KeyStore); - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { return FillableSigningProvider::AddKeyPubKey(key, pubkey); } @@ -599,7 +600,7 @@ bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pu std::vector<unsigned char> vchCryptedSecret; CKeyingMaterial vchSecret(key.begin(), key.end()); - if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { + if (!EncryptSecret(m_storage.GetEncryptionKey(), vchSecret, pubkey.GetHash(), vchCryptedSecret)) { return false; } @@ -617,9 +618,7 @@ bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std:: bool LegacyScriptPubKeyMan::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) { LOCK(cs_KeyStore); - if (!SetCrypted()) { - return false; - } + assert(mapKeys.empty()); mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); ImplicitlyLearnRelatedKeyScripts(vchPubKey); @@ -746,7 +745,7 @@ void LegacyScriptPubKeyMan::SetHDChain(const CHDChain& chain, bool memonly) bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const { LOCK(cs_KeyStore); - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { return FillableSigningProvider::HaveKey(address); } return mapCryptedKeys.count(address) > 0; @@ -755,7 +754,7 @@ bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const { LOCK(cs_KeyStore); - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { return FillableSigningProvider::GetKey(address, keyOut); } @@ -764,7 +763,7 @@ bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const { const CPubKey &vchPubKey = (*mi).second.first; const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; - return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); + return DecryptKey(m_storage.GetEncryptionKey(), vchCryptedSecret, vchPubKey, keyOut); } return false; } @@ -802,7 +801,7 @@ bool LegacyScriptPubKeyMan::GetWatchPubKey(const CKeyID &address, CPubKey &pubke bool LegacyScriptPubKeyMan::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const { LOCK(cs_KeyStore); - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) { return GetWatchPubKey(address, vchPubKeyOut); } @@ -1092,15 +1091,20 @@ void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const m_pool_key_to_index[pubkey.GetID()] = index; } -void LegacyScriptPubKeyMan::KeepKey(int64_t nIndex) +void LegacyScriptPubKeyMan::KeepDestination(int64_t nIndex, const OutputType& type) { // Remove from key pool WalletBatch batch(m_storage.GetDatabase()); batch.ErasePool(nIndex); + CPubKey pubkey; + bool have_pk = GetPubKey(m_index_to_reserved_key.at(nIndex), pubkey); + assert(have_pk); + LearnRelatedScripts(pubkey, type); + m_index_to_reserved_key.erase(nIndex); WalletLogPrintf("keypool keep %d\n", nIndex); } -void LegacyScriptPubKeyMan::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) +void LegacyScriptPubKeyMan::ReturnDestination(int64_t nIndex, bool fInternal, const CTxDestination&) { // Return to key pool { @@ -1112,13 +1116,15 @@ void LegacyScriptPubKeyMan::ReturnKey(int64_t nIndex, bool fInternal, const CPub } else { setExternalKeyPool.insert(nIndex); } - m_pool_key_to_index[pubkey.GetID()] = nIndex; + CKeyID& pubkey_id = m_index_to_reserved_key.at(nIndex); + m_pool_key_to_index[pubkey_id] = nIndex; + m_index_to_reserved_key.erase(nIndex); NotifyCanGetAddressesChanged(); } WalletLogPrintf("keypool return %d\n", nIndex); } -bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, bool internal) +bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType type, bool internal) { if (!CanGetAddresses(internal)) { return false; @@ -1134,7 +1140,7 @@ bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, bool internal) result = GenerateNewKey(batch, internal); return true; } - KeepKey(nIndex); + KeepDestination(nIndex, type); result = keypool.vchPubKey; } return true; @@ -1179,6 +1185,8 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key throw std::runtime_error(std::string(__func__) + ": keypool entry invalid"); } + assert(m_index_to_reserved_key.count(nIndex) == 0); + m_index_to_reserved_key[nIndex] = keypool.vchPubKey.GetID(); m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); WalletLogPrintf("keypool reserve %d\n", nIndex); } @@ -1379,7 +1387,7 @@ bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::set<CScript>& script_ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const { LOCK(cs_KeyStore); - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { return FillableSigningProvider::GetKeys(); } std::set<CKeyID> set_address; @@ -1393,13 +1401,8 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const LegacyScriptPubKeyMan::LegacyScriptPubKeyMan(CWallet& wallet) : ScriptPubKeyMan(wallet), m_wallet(wallet), - cs_wallet(wallet.cs_wallet), - vMasterKey(wallet.vMasterKey), - fUseCrypto(wallet.fUseCrypto), - fDecryptionThoroughlyChecked(wallet.fDecryptionThoroughlyChecked) {} + cs_wallet(wallet.cs_wallet) {} -bool LegacyScriptPubKeyMan::SetCrypted() { return m_wallet.SetCrypted(); } -bool LegacyScriptPubKeyMan::IsCrypted() const { return m_wallet.IsCrypted(); } void LegacyScriptPubKeyMan::NotifyWatchonlyChanged(bool fHaveWatchOnly) const { return m_wallet.NotifyWatchonlyChanged(fHaveWatchOnly); } void LegacyScriptPubKeyMan::NotifyCanGetAddressesChanged() const { return m_wallet.NotifyCanGetAddressesChanged(); } template<typename... Params> void LegacyScriptPubKeyMan::WalletLogPrintf(const std::string& fmt, const Params&... parameters) const { return m_wallet.WalletLogPrintf(fmt, parameters...); } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 4f17156792..aa5eac3a85 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -31,6 +31,8 @@ public: virtual void UnsetBlankWalletFlag(WalletBatch&) = 0; virtual bool CanSupportFeature(enum WalletFeature) const = 0; virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr, bool = false) = 0; + virtual const CKeyingMaterial& GetEncryptionKey() const = 0; + virtual bool HasEncryptionKeys() const = 0; virtual bool IsLocked() const = 0; }; @@ -150,9 +152,13 @@ public: virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { return false; } virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; } - virtual bool GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) { return false; } - virtual void KeepDestination(int64_t index) {} - virtual void ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) {} + //! 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) { return false; } + virtual void KeepDestination(int64_t index, const OutputType& type) {} + virtual void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) {} virtual bool TopUp(unsigned int size = 0) { return false; } @@ -193,6 +199,9 @@ public: class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider { private: + //! keeps track of whether Unlock has run a thorough check before + bool fDecryptionThoroughlyChecked = false; + using WatchOnlySet = std::set<CScript>; using WatchKeyMap = std::map<CKeyID, CPubKey>; @@ -246,9 +255,11 @@ private: std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_wallet); int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0; std::map<CKeyID, int64_t> m_pool_key_to_index; + // Tracks keypool indexes to CKeyIDs of keys that have been taken out of the keypool but may be returned to it + std::map<int64_t, CKeyID> m_index_to_reserved_key; //! Fetches a key from the keypool - bool GetKeyFromPool(CPubKey &key, bool internal = false); + bool GetKeyFromPool(CPubKey &key, const OutputType type, bool internal = false); /** * Reserves a key from the keypool and sets nIndex to its index @@ -266,19 +277,16 @@ private: */ bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); - void KeepKey(int64_t nIndex); - void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); - public: bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) override; isminetype IsMine(const CScript& script) const override; - //! will encrypt previously unencrypted keys - bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + 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, int64_t& index, CKeyPool& keypool) override; - void KeepDestination(int64_t index) override; - void ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) override; + bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, 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; bool TopUp(unsigned int size = 0) override; @@ -404,16 +412,11 @@ public: friend class CWallet; friend class ReserveDestination; LegacyScriptPubKeyMan(CWallet& wallet); - bool SetCrypted(); - bool IsCrypted() const; void NotifyWatchonlyChanged(bool fHaveWatchOnly) const; void NotifyCanGetAddressesChanged() const; template<typename... Params> void WalletLogPrintf(const std::string& fmt, const Params&... parameters) const; CWallet& m_wallet; CCriticalSection& cs_wallet; - CKeyingMaterial& vMasterKey GUARDED_BY(cs_KeyStore); - std::atomic<bool>& fUseCrypto; - bool& fDecryptionThoroughlyChecked; }; #endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index bc068f1499..7e9f88f6b7 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -55,7 +55,7 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set) set.emplace(MakeTransactionRef(tx), nInput); } -static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0) +static void add_coin(CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) { balance += nValue; static int nextLockTime = 0; @@ -63,21 +63,31 @@ static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = fa tx.nLockTime = nextLockTime++; // so all transactions get different hashes tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; + if (spendable) { + CTxDestination dest; + std::string error; + assert(wallet.GetNewDestination(OutputType::BECH32, "", dest, error)); + tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest); + } if (fIsFromMe) { // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(), // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe() tx.vin.resize(1); } - std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx))); + std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&wallet, MakeTransactionRef(std::move(tx))); if (fIsFromMe) { wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1); } COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); vCoins.push_back(output); - testWallet.AddToWallet(*wtx.get()); + wallet.AddToWallet(*wtx.get()); wtxn.emplace_back(std::move(wtx)); } +static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) +{ + add_coin(testWallet, nValue, nAge, fIsFromMe, nInput, spendable); +} static void empty_wallet(void) { @@ -252,17 +262,33 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) vCoins.at(0).nInputBytes = 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(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); - // Make sure that we aren't using BnB when there are preset inputs + // Test fees subtracted from output: + empty_wallet(); + add_coin(1 * CENT); + vCoins.at(0).nInputBytes = 40; + BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); + coin_selection_params_bnb.m_subtract_fee_outputs = true; + BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); + BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); + + // Make sure that can use BnB when there are preset inputs empty_wallet(); - add_coin(5 * CENT); - add_coin(3 * CENT); - add_coin(2 * CENT); - CCoinControl coin_control; - coin_control.fAllowOtherInputs = true; - coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i)); - BOOST_CHECK(testWallet.SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used)); - BOOST_CHECK(!bnb_used); - BOOST_CHECK(!coin_selection_params_bnb.use_bnb); + { + std::unique_ptr<CWallet> wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()); + bool firstRun; + wallet->LoadWallet(firstRun); + LOCK(wallet->cs_wallet); + add_coin(*wallet, 5 * CENT, 6 * 24, false, 0, true); + add_coin(*wallet, 3 * CENT, 6 * 24, false, 0, true); + add_coin(*wallet, 2 * CENT, 6 * 24, false, 0, true); + CCoinControl coin_control; + coin_control.fAllowOtherInputs = true; + coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i)); + coin_selection_params_bnb.effective_fee = CFeeRate(0); + BOOST_CHECK(wallet->SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used)); + BOOST_CHECK(bnb_used); + BOOST_CHECK(coin_selection_params_bnb.use_bnb); + } } BOOST_AUTO_TEST_CASE(knapsack_solver_test) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ff182c847f..41a816312a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -532,8 +532,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) { LOCK(cs_wallet); mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; - assert(!encrypted_batch); - encrypted_batch = new WalletBatch(*database); + WalletBatch* encrypted_batch = new WalletBatch(*database); if (!encrypted_batch->TxnBegin()) { delete encrypted_batch; encrypted_batch = nullptr; @@ -542,7 +541,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey); if (auto spk_man = m_spk_man.get()) { - if (!spk_man->EncryptKeys(_vMasterKey)) { + if (!spk_man->Encrypt(_vMasterKey, encrypted_batch)) { encrypted_batch->TxnAbort(); delete encrypted_batch; encrypted_batch = nullptr; @@ -822,7 +821,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) void CWallet::LoadToWallet(CWalletTx& wtxIn) { - // If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken. + // If wallet doesn't have a chain (e.g bitcoin-wallet), lock can't be taken. auto locked_chain = LockChain(); if (locked_chain) { Optional<int> block_height = locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock); @@ -1363,7 +1362,11 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig const CScript& scriptPubKey = txout.scriptPubKey; SignatureData sigdata; - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(scriptPubKey); + if (!provider) { + // We don't know about this scriptpbuKey; + return false; + } if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { return false; @@ -1444,11 +1447,9 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) { std::vector<CTxOut> txouts; - // Look up the inputs. We should have already checked that this transaction - // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our - // wallet, with a valid index into the vout array, and the ability to sign. for (const CTxIn& input : tx.vin) { const auto mi = wallet->mapWallet.find(input.prevout.hash); + // Can not estimate size without knowing the input details if (mi == wallet->mapWallet.end()) { return -1; } @@ -1463,8 +1464,6 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall { CMutableTransaction txNew(tx); if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { - // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) - // implies that we can sign for every input. return -1; } return GetVirtualTransactionSize(CTransaction(txNew)); @@ -2125,7 +2124,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< continue; } - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(wtx.tx->vout[i].scriptPubKey); bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); @@ -2160,7 +2159,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch for (const COutput& coin : availableCoins) { CTxDestination address; - if (coin.fSpendable && + if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) && ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { result[address].emplace_back(std::move(coin)); } @@ -2168,12 +2167,16 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch std::vector<COutPoint> lockedCoins; ListLockedCoins(lockedCoins); + // Include watch-only for wallets without private keys + const bool include_watch_only = IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; for (const COutPoint& output : lockedCoins) { auto it = mapWallet.find(output.hash); if (it != mapWallet.end()) { int depth = it->second.GetDepthInMainChain(); if (depth >= 0 && output.n < it->second.tx->vout.size() && - IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { + IsMine(it->second.tx->vout[output.n]) == is_mine_filter + ) { CTxDestination address; if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { result[address].emplace_back( @@ -2234,7 +2237,11 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil if (effective_value > 0) { group.fee += coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes); group.long_term_fee += coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes); - group.effective_value += effective_value; + if (coin_selection_params.m_subtract_fee_outputs) { + group.effective_value += coin.txout.nValue; + } else { + group.effective_value += effective_value; + } ++it; } else { it = group.Discard(coin); @@ -2260,13 +2267,14 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const { std::vector<COutput> vCoins(vAvailableCoins); + CAmount value_to_select = nTargetValue; + + // Default to bnb was not used. If we use it, we set it later + bnb_used = false; // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) { - // We didn't use BnB here, so set it to false. - bnb_used = false; - for (const COutput& out : vCoins) { if (!out.fSpendable) @@ -2285,22 +2293,30 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm coin_control.ListSelected(vPresetInputs); for (const COutPoint& outpoint : vPresetInputs) { - // For now, don't use BnB if preset inputs are selected. TODO: Enable this later - bnb_used = false; - coin_selection_params.use_bnb = false; - std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash); if (it != mapWallet.end()) { const CWalletTx& wtx = it->second; // Clearly invalid input, fail - if (wtx.tx->vout.size() <= outpoint.n) + if (wtx.tx->vout.size() <= outpoint.n) { return false; + } // Just to calculate the marginal byte size - nValueFromPresetInputs += wtx.tx->vout[outpoint.n].nValue; - setPresetCoins.insert(CInputCoin(wtx.tx, outpoint.n)); - } else + CInputCoin coin(wtx.tx, outpoint.n, wtx.GetSpendSize(outpoint.n, false)); + nValueFromPresetInputs += coin.txout.nValue; + if (coin.m_input_bytes <= 0) { + return false; // Not solvable, can't estimate size for fee + } + coin.effective_value = coin.txout.nValue - coin_selection_params.effective_fee.GetFee(coin.m_input_bytes); + if (coin_selection_params.use_bnb) { + value_to_select -= coin.effective_value; + } else { + value_to_select -= coin.txout.nValue; + } + setPresetCoins.insert(coin); + } else { return false; // TODO: Allow non-wallet inputs + } } // remove preset inputs from vCoins @@ -2329,14 +2345,14 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count); bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); - bool res = nTargetValue <= nValueFromPresetInputs || - SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || - SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); + bool res = value_to_select <= 0 || + SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || + SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || + (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset util::insert(setCoinsRet, setPresetCoins); @@ -2362,8 +2378,9 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue; SignatureData sigdata; - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(scriptPubKey); if (!provider) { + // We don't know about this scriptpbuKey; return false; } @@ -2519,7 +2536,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; - ReserveDestination reservedest(this); + const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); + ReserveDestination reservedest(this, change_type); int nChangePosRequest = nChangePosInOut; unsigned int nSubtractFeeFromAmount = 0; for (const auto& recipient : vecSend) @@ -2578,8 +2596,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std return false; } CTxDestination dest; - const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); - bool ret = reservedest.GetReservedDestination(change_type, dest, true); + bool ret = reservedest.GetReservedDestination(dest, true); if (!ret) { strFailReason = "Keypool ran out, please call keypoolrefill first"; @@ -2602,7 +2619,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // BnB selector is the only selector used when this is true. // That should only happen on the first pass through the loop. - coin_selection_params.use_bnb = nSubtractFeeFromAmount == 0; // If we are doing subtract fee from recipient, then don't use BnB + coin_selection_params.use_bnb = true; + coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values // Start with no fee and loop until there is enough fee while (true) { @@ -2616,7 +2634,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std nValueToSelect += nFeeRet; // vouts to the payees - coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size) + if (!coin_selection_params.m_subtract_fee_outputs) { + coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size) + } for (const auto& recipient : vecSend) { CTxOut txout(recipient.nAmount, recipient.scriptPubKey); @@ -2633,7 +2653,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } } // Include the fee cost for outputs. Note this is only used for BnB right now - coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); + if (!coin_selection_params.m_subtract_fee_outputs) { + coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); + } if (IsDust(txout, chain().relayDustFee())) { @@ -2652,7 +2674,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } // Choose coins to use - bool bnb_used; + bool bnb_used = false; if (pick_new_inputs) { nValueIn = 0; setCoins.clear(); @@ -2825,7 +2847,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std const CScript& scriptPubKey = coin.txout.scriptPubKey; SignatureData sigdata; - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(scriptPubKey); if (!provider || !ProduceSignature(*provider, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) { strFailReason = _("Signing transaction failed").translated; @@ -2921,7 +2943,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { // Even if we don't use this lock in this function, we want to preserve // lock order in LoadToWallet if query of chain state is needed to know - // tx status. If lock can't be taken (e.g wallet-tool), tx confirmation + // tx status. If lock can't be taken (e.g bitcoin-wallet), tx confirmation // status may be not reliable. auto locked_chain = LockChain(); LOCK(cs_wallet); @@ -3098,8 +3120,8 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des m_spk_man->TopUp(); - ReserveDestination reservedest(this); - if (!reservedest.GetReservedDestination(type, dest, true)) { + ReserveDestination reservedest(this, type); + if (!reservedest.GetReservedDestination(dest, true)) { error = "Error: Keypool ran out, please call keypoolrefill first"; return false; } @@ -3265,49 +3287,41 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co return result; } -bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestination& dest, bool internal) +bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal) { m_spk_man = pwallet->GetLegacyScriptPubKeyMan(); if (!m_spk_man) { return false; } - if (!pwallet->CanGetAddresses(internal)) { - return false; - } if (nIndex == -1) { CKeyPool keypool; - if (!m_spk_man->GetReservedDestination(type, internal, nIndex, keypool)) { + if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) { return false; } - vchPubKey = keypool.vchPubKey; fInternal = keypool.fInternal; } - assert(vchPubKey.IsValid()); - m_spk_man->LearnRelatedScripts(vchPubKey, type); - address = GetDestinationForKey(vchPubKey, type); dest = address; return true; } void ReserveDestination::KeepDestination() { - if (nIndex != -1) - m_spk_man->KeepDestination(nIndex); + if (nIndex != -1) { + m_spk_man->KeepDestination(nIndex, type); + } nIndex = -1; - vchPubKey = CPubKey(); address = CNoDestination(); } void ReserveDestination::ReturnDestination() { if (nIndex != -1) { - m_spk_man->ReturnDestination(nIndex, fInternal, vchPubKey); + m_spk_man->ReturnDestination(nIndex, fInternal, address); } nIndex = -1; - vchPubKey = CPubKey(); address = CNoDestination(); } @@ -3988,15 +4002,9 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu return groups; } -bool CWallet::SetCrypted() +bool CWallet::IsCrypted() const { - LOCK(cs_KeyStore); - if (fUseCrypto) - return true; - if (!mapKeys.empty()) - return false; - fUseCrypto = true; - return true; + return HasEncryptionKeys(); } bool CWallet::IsLocked() const @@ -4010,7 +4018,7 @@ bool CWallet::IsLocked() const bool CWallet::Lock() { - if (!SetCrypted()) + if (!IsCrypted()) return false; { @@ -4022,12 +4030,32 @@ bool CWallet::Lock() return true; } -ScriptPubKeyMan* CWallet::GetScriptPubKeyMan() const +bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) +{ + { + LOCK(cs_KeyStore); + if (m_spk_man) { + if (!m_spk_man->CheckDecryptionKey(vMasterKeyIn, accept_no_keys)) { + return false; + } + } + vMasterKey = vMasterKeyIn; + } + NotifyStatusChanged(this); + return true; +} + +ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const CScript& script) const +{ + return m_spk_man.get(); +} + +const SigningProvider* CWallet::GetSigningProvider(const CScript& script) const { return m_spk_man.get(); } -const SigningProvider* CWallet::GetSigningProvider() const +const SigningProvider* CWallet::GetSigningProvider(const CScript& script, SignatureData& sigdata) const { return m_spk_man.get(); } @@ -4036,3 +4064,13 @@ LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const { return m_spk_man.get(); } + +const CKeyingMaterial& CWallet::GetEncryptionKey() const +{ + return vMasterKey; +} + +bool CWallet::HasEncryptionKeys() const +{ + return !mapMasterKeys.empty(); +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a6b9c0131a..c4511601de 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -71,8 +71,6 @@ static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000; static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true; //! Default for -walletrejectlongchains static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false; -//! Default for -avoidpartialspends -static const bool DEFAULT_AVOIDPARTIALSPENDS = false; //! -txconfirmtarget default static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6; //! -walletrbf default @@ -140,12 +138,12 @@ class ReserveDestination { protected: //! The wallet to reserve from - CWallet* pwallet; - LegacyScriptPubKeyMan* m_spk_man{nullptr}; + CWallet* const pwallet; + //! The ScriptPubKeyMan to reserve from. Based on type when GetReservedDestination is called + ScriptPubKeyMan* m_spk_man{nullptr}; + OutputType const type; //! The index of the address's key in the keypool int64_t nIndex{-1}; - //! The public key for the address - CPubKey vchPubKey; //! The destination CTxDestination address; //! Whether this is from the internal (change output) keypool @@ -153,10 +151,9 @@ protected: public: //! Construct a ReserveDestination object. This does NOT reserve an address yet - explicit ReserveDestination(CWallet* pwalletIn) - { - pwallet = pwalletIn; - } + explicit ReserveDestination(CWallet* pwallet, OutputType type) + : pwallet(pwallet) + , type(type) { } ReserveDestination(const ReserveDestination&) = delete; ReserveDestination& operator=(const ReserveDestination&) = delete; @@ -168,7 +165,7 @@ public: } //! Reserve an address - bool GetReservedDestination(const OutputType type, CTxDestination& pubkey, bool internal); + bool GetReservedDestination(CTxDestination& pubkey, bool internal); //! Return reserved address void ReturnDestination(); //! Keep the address. Do not return it's key to the keypool when this object goes out of scope @@ -584,6 +581,8 @@ struct CoinSelectionParams size_t change_spend_size = 0; CFeeRate effective_fee = CFeeRate(0); size_t tx_noinputs_size = 0; + //! Indicate that we are subtracting the fee from outputs + bool m_subtract_fee_outputs = false; CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size) : use_bnb(use_bnb), change_output_size(change_output_size), change_spend_size(change_spend_size), effective_fee(effective_fee), tx_noinputs_size(tx_noinputs_size) {} CoinSelectionParams() {} @@ -598,14 +597,7 @@ class CWallet final : public WalletStorage, private interfaces::Chain::Notificat private: CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore); - //! if fUseCrypto is true, mapKeys must be empty - //! if fUseCrypto is false, vMasterKey must be empty - std::atomic<bool> fUseCrypto; - - //! keeps track of whether Unlock has run a thorough check before - bool fDecryptionThoroughlyChecked; - bool SetCrypted(); bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys = false); std::atomic<bool> fAbortRescan{false}; @@ -735,9 +727,7 @@ public: /** Construct wallet with specified name and database implementation. */ CWallet(interfaces::Chain* chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) - : fUseCrypto(false), - fDecryptionThoroughlyChecked(false), - m_chain(chain), + : m_chain(chain), m_location(location), database(std::move(database)) { @@ -747,11 +737,9 @@ public: { // Should not have slots connected at this point. assert(NotifyUnload.empty()); - delete encrypted_batch; - encrypted_batch = nullptr; } - bool IsCrypted() const { return fUseCrypto; } + bool IsCrypted() const; bool IsLocked() const override; bool Lock(); @@ -1128,10 +1116,18 @@ public: LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...); }; - ScriptPubKeyMan* GetScriptPubKeyMan() const; - const SigningProvider* GetSigningProvider() const; + //! Get the ScriptPubKeyMan for a script + ScriptPubKeyMan* GetScriptPubKeyMan(const CScript& script) const; + + //! Get the SigningProvider for a script + const SigningProvider* GetSigningProvider(const CScript& script) const; + const SigningProvider* GetSigningProvider(const CScript& script, SignatureData& sigdata) const; + LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const; + const CKeyingMaterial& GetEncryptionKey() const override; + bool HasEncryptionKeys() const override; + // Temporary LegacyScriptPubKeyMan accessors and aliases. friend class LegacyScriptPubKeyMan; std::unique_ptr<LegacyScriptPubKeyMan> m_spk_man = MakeUnique<LegacyScriptPubKeyMan>(*this); @@ -1141,8 +1137,6 @@ public: LegacyScriptPubKeyMan::CryptedKeyMap& mapCryptedKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapCryptedKeys; LegacyScriptPubKeyMan::WatchOnlySet& setWatchOnly GUARDED_BY(cs_KeyStore) = m_spk_man->setWatchOnly; LegacyScriptPubKeyMan::WatchKeyMap& mapWatchKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapWatchKeys; - WalletBatch*& encrypted_batch GUARDED_BY(cs_wallet) = m_spk_man->encrypted_batch; - using CryptedKeyMap = LegacyScriptPubKeyMan::CryptedKeyMap; /** Get last block processed height */ int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) |