diff --git a/src/Makefile.am b/src/Makefile.am index 008b866e0..9323e49d7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,570 +1,574 @@ # Copyright (c) 2013-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. DIST_SUBDIRS = secp256k1 univalue AM_LDFLAGS = $(PTHREAD_CFLAGS) $(LIBTOOL_LDFLAGS) $(HARDENED_LDFLAGS) AM_CXXFLAGS = $(HARDENED_CXXFLAGS) $(ERROR_CXXFLAGS) AM_CPPFLAGS = $(HARDENED_CPPFLAGS) EXTRA_LIBRARIES = if EMBEDDED_UNIVALUE LIBUNIVALUE = univalue/libunivalue.la $(LIBUNIVALUE): $(wildcard univalue/lib/*) $(wildcard univalue/include/*) $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) else LIBUNIVALUE = $(UNIVALUE_LIBS) endif BITCOIN_CONFIG_INCLUDES=-I$(builddir)/config BITCOIN_INCLUDES=-I$(builddir) -I$(builddir)/obj $(BDB_CPPFLAGS) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) $(CRYPTO_CFLAGS) $(SSL_CFLAGS) BITCOIN_INCLUDES += -I$(srcdir)/secp256k1/include BITCOIN_INCLUDES += $(UNIVALUE_CFLAGS) BITCOIN_SEEDER_INCLUDES = -I$(srcdir)/seeder BITCOIN_SEEDER_INCLUDES += $(BITCOIN_INCLUDES) LIBBITCOIN_SERVER=libbitcoin_server.a LIBBITCOIN_COMMON=libbitcoin_common.a LIBBITCOIN_CONSENSUS=libbitcoin_consensus.a LIBBITCOIN_CLI=libbitcoin_cli.a LIBBITCOIN_UTIL=libbitcoin_util.a LIBBITCOIN_CRYPTO=crypto/libbitcoin_crypto.a LIBBITCOINQT=qt/libbitcoinqt.a LIBSECP256K1=secp256k1/libsecp256k1.la if ENABLE_ZMQ LIBBITCOIN_ZMQ=libbitcoin_zmq.a endif if BUILD_BITCOIN_LIBS LIBBITCOINCONSENSUS=libbitcoinconsensus.la endif if BUILD_BITCOIN_SEEDER LIBBITCOIN_SEEDER=libbitcoin_seeder.a endif if ENABLE_WALLET LIBBITCOIN_WALLET=libbitcoin_wallet.a endif $(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*) $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) # Make is not made aware of per-object dependencies to avoid limiting building parallelization # But to build the less dependent modules first, we manually select their order here: EXTRA_LIBRARIES += \ $(LIBBITCOIN_CRYPTO) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_CLI) \ $(LIBBITCOIN_SEEDER) \ $(LIBBITCOIN_WALLET) \ $(LIBBITCOIN_ZMQ) lib_LTLIBRARIES = $(LIBBITCOINCONSENSUS) bin_PROGRAMS = noinst_PROGRAMS = TESTS = BENCHMARKS = if BUILD_BITCOIND bin_PROGRAMS += bitcoind endif if BUILD_BITCOIN_SEEDER bin_PROGRAMS += bitcoin-seeder endif if BUILD_BITCOIN_UTILS bin_PROGRAMS += bitcoin-cli bitcoin-tx endif .PHONY: FORCE check-symbols check-security # bitcoin core # BITCOIN_CORE_H = \ addrdb.h \ addrman.h \ base58.h \ bloom.h \ blockencodings.h \ cashaddr.h \ cashaddrenc.h \ chain.h \ chainparams.h \ chainparamsbase.h \ chainparamsseeds.h \ checkpoints.h \ checkqueue.h \ clientversion.h \ coins.h \ compat.h \ compat/byteswap.h \ compat/endian.h \ compat/sanity.h \ compressor.h \ config.h \ consensus/consensus.h \ core_io.h \ core_memusage.h \ cuckoocache.h \ + dstencode.h \ globals.h \ httprpc.h \ httpserver.h \ indirectmap.h \ init.h \ key.h \ keystore.h \ dbwrapper.h \ limitedmap.h \ memusage.h \ merkleblock.h \ miner.h \ net.h \ net_processing.h \ netaddress.h \ netbase.h \ netmessagemaker.h \ noui.h \ policy/fees.h \ policy/policy.h \ pow.h \ protocol.h \ random.h \ reverselock.h \ rpc/blockchain.h \ rpc/client.h \ rpc/misc.h \ rpc/protocol.h \ rpc/server.h \ rpc/register.h \ scheduler.h \ script/scriptcache.h \ script/sigcache.h \ script/sign.h \ script/standard.h \ script/ismine.h \ streams.h \ support/allocators/secure.h \ support/allocators/zeroafterfree.h \ support/cleanse.h \ support/events.h \ support/lockedpool.h \ sync.h \ threadsafety.h \ threadinterrupt.h \ timedata.h \ torcontrol.h \ txdb.h \ txmempool.h \ ui_interface.h \ undo.h \ util.h \ utilmoneystr.h \ utiltime.h \ validation.h \ validationinterface.h \ versionbits.h \ wallet/coincontrol.h \ wallet/crypter.h \ wallet/db.h \ wallet/finaltx.h \ wallet/rpcdump.h \ wallet/rpcwallet.h \ wallet/wallet.h \ wallet/walletdb.h \ warnings.h \ zmq/zmqabstractnotifier.h \ zmq/zmqconfig.h\ zmq/zmqnotificationinterface.h \ zmq/zmqpublishnotifier.h obj/build.h: FORCE @$(MKDIR_P) $(builddir)/obj @$(top_srcdir)/share/genbuild.sh $(abs_top_builddir)/src/obj/build.h \ $(abs_top_srcdir) libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h # server: shared between bitcoind and bitcoin-qt libbitcoin_server_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ addrman.cpp \ addrdb.cpp \ bloom.cpp \ blockencodings.cpp \ chain.cpp \ checkpoints.cpp \ config.cpp \ globals.cpp \ httprpc.cpp \ httpserver.cpp \ init.cpp \ dbwrapper.cpp \ merkleblock.cpp \ miner.cpp \ net.cpp \ net_processing.cpp \ noui.cpp \ policy/fees.cpp \ policy/policy.cpp \ pow.cpp \ rest.cpp \ rpc/abc.cpp \ rpc/blockchain.cpp \ rpc/mining.cpp \ rpc/misc.cpp \ rpc/net.cpp \ rpc/rawtransaction.cpp \ rpc/server.cpp \ script/scriptcache.cpp \ script/sigcache.cpp \ script/ismine.cpp \ timedata.cpp \ torcontrol.cpp \ txdb.cpp \ txmempool.cpp \ ui_interface.cpp \ validation.cpp \ validationinterface.cpp \ versionbits.cpp \ $(BITCOIN_CORE_H) if ENABLE_ZMQ libbitcoin_zmq_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(ZMQ_CFLAGS) libbitcoin_zmq_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_zmq_a_SOURCES = \ zmq/zmqabstractnotifier.cpp \ zmq/zmqnotificationinterface.cpp \ zmq/zmqpublishnotifier.cpp endif # wallet: shared between bitcoind and bitcoin-qt, but only linked # when wallet enabled libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ wallet/crypter.cpp \ wallet/db.cpp \ wallet/finaltx.cpp \ wallet/rpcdump.cpp \ wallet/rpcwallet.cpp \ wallet/wallet.cpp \ wallet/walletdb.cpp \ $(BITCOIN_CORE_H) # crypto primitives library crypto_libbitcoin_crypto_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_CONFIG_INCLUDES) crypto_libbitcoin_crypto_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) crypto_libbitcoin_crypto_a_SOURCES = \ crypto/aes.cpp \ crypto/aes.h \ crypto/chacha20.h \ crypto/chacha20.cpp \ crypto/common.h \ crypto/hmac_sha256.cpp \ crypto/hmac_sha256.h \ crypto/hmac_sha512.cpp \ crypto/hmac_sha512.h \ crypto/ripemd160.cpp \ crypto/ripemd160.h \ crypto/sha1.cpp \ crypto/sha1.h \ crypto/sha256.cpp \ crypto/sha256.h \ crypto/sha512.cpp \ crypto/sha512.h # consensus: shared between all executables that validate any consensus rules. libbitcoin_consensus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_consensus_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_consensus_a_SOURCES = \ amount.h \ arith_uint256.cpp \ arith_uint256.h \ consensus/merkle.cpp \ consensus/merkle.h \ consensus/params.h \ consensus/validation.h \ hash.cpp \ hash.h \ prevector.h \ primitives/block.cpp \ primitives/block.h \ primitives/transaction.cpp \ primitives/transaction.h \ pubkey.cpp \ pubkey.h \ script/bitcoinconsensus.cpp \ script/interpreter.cpp \ script/interpreter.h \ script/script.cpp \ script/script.h \ script/script_error.cpp \ script/script_error.h \ serialize.h \ tinyformat.h \ uint256.cpp \ uint256.h \ utilstrencodings.cpp \ utilstrencodings.h \ version.h # common: shared between bitcoind, and bitcoin-qt and non-server tools libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_common_a_SOURCES = \ amount.cpp \ base58.cpp \ cashaddr.cpp \ cashaddrenc.cpp \ chainparams.cpp \ + config.cpp \ coins.cpp \ compressor.cpp \ + dstencode.cpp \ + globals.cpp \ core_read.cpp \ core_write.cpp \ key.cpp \ keystore.cpp \ netaddress.cpp \ netbase.cpp \ protocol.cpp \ scheduler.cpp \ script/sign.cpp \ script/standard.cpp \ warnings.cpp \ $(BITCOIN_CORE_H) # util: shared between all executables. # This library *must* be included to make sure that the glibc # backward-compatibility objects and their sanity checks are linked. libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_util_a_SOURCES = \ support/lockedpool.cpp \ chainparamsbase.cpp \ clientversion.cpp \ compat/glibc_sanity.cpp \ compat/glibcxx_sanity.cpp \ compat/strnlen.cpp \ random.cpp \ rpc/protocol.cpp \ support/cleanse.cpp \ sync.cpp \ threadinterrupt.cpp \ util.cpp \ utilmoneystr.cpp \ utilstrencodings.cpp \ utiltime.cpp \ $(BITCOIN_CORE_H) if GLIBC_BACK_COMPAT libbitcoin_util_a_SOURCES += compat/glibc_compat.cpp endif # cli: shared between bitcoin-cli and bitcoin-qt libbitcoin_cli_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_cli_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_cli_a_SOURCES = \ rpc/client.cpp \ $(BITCOIN_CORE_H) # seeder library libbitcoin_seeder_a_CPPFLAGS = $(AM_CPPFLAGS) $(PIE_FLAGS) $(BITCOIN_SEEDER_INCLUDES) libbitcoin_seeder_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_seeder_a_SOURCES = \ seeder/bitcoin.cpp \ seeder/bitcoin.h \ seeder/compat.h \ seeder/db.cpp \ seeder/db.h \ seeder/dns.cpp \ seeder/dns.h \ seeder/netbase.cpp \ seeder/netbase.h \ seeder/protocol.cpp \ seeder/protocol.h \ seeder/strlcpy.h \ seeder/util.cpp \ seeder/util.h nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h # # bitcoind binary # bitcoind_SOURCES = bitcoind.cpp bitcoind_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) bitcoind_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) bitcoind_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) if TARGET_WINDOWS bitcoind_SOURCES += bitcoind-res.rc endif bitcoind_LDADD = \ $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_COMMON) \ $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_WALLET) \ $(LIBBITCOIN_ZMQ) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ $(LIBLEVELDB) \ $(LIBLEVELDB_SSE42) \ $(LIBMEMENV) \ $(LIBSECP256K1) bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) # bitcoin-cli binary # bitcoin_cli_SOURCES = bitcoin-cli.cpp bitcoin_cli_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS) bitcoin_cli_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) bitcoin_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) if TARGET_WINDOWS bitcoin_cli_SOURCES += bitcoin-cli-res.rc endif bitcoin_cli_LDADD = \ $(LIBBITCOIN_CLI) \ $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CRYPTO) bitcoin_cli_LDADD += $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_LIBS) # # bitcoin-seeder binary # bitcoin_seeder_SOURCES = seeder/main.cpp bitcoin_seeder_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_SEEDER_INCLUDES) bitcoin_seeder_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) bitcoin_seeder_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) bitcoin_seeder_LDADD = \ $(LIBBITCOIN_SEEDER) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CRYPTO) bitcoin_seeder_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) # # bitcoin-tx binary # bitcoin_tx_SOURCES = bitcoin-tx.cpp bitcoin_tx_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) bitcoin_tx_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) bitcoin_tx_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) if TARGET_WINDOWS bitcoin_tx_SOURCES += bitcoin-tx-res.rc endif bitcoin_tx_LDADD = \ $(LIBUNIVALUE) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ $(LIBSECP256K1) bitcoin_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS) # # bitcoinconsensus library # if BUILD_BITCOIN_LIBS include_HEADERS = script/bitcoinconsensus.h libbitcoinconsensus_la_SOURCES = $(crypto_libbitcoin_crypto_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) if GLIBC_BACK_COMPAT libbitcoinconsensus_la_SOURCES += compat/glibc_compat.cpp endif libbitcoinconsensus_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined $(RELDFLAGS) libbitcoinconsensus_la_LIBADD = $(LIBSECP256K1) libbitcoinconsensus_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(builddir)/obj -I$(srcdir)/secp256k1/include -DBUILD_BITCOIN_INTERNAL libbitcoinconsensus_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) endif # CTAES_DIST = crypto/ctaes/bench.c CTAES_DIST += crypto/ctaes/ctaes.c CTAES_DIST += crypto/ctaes/ctaes.h CTAES_DIST += crypto/ctaes/README.md CTAES_DIST += crypto/ctaes/test.c CLEANFILES = $(EXTRA_LIBRARIES) CLEANFILES += *.gcda *.gcno CLEANFILES += compat/*.gcda compat/*.gcno CLEANFILES += consensus/*.gcda consensus/*.gcno CLEANFILES += crypto/*.gcda crypto/*.gcno CLEANFILES += policy/*.gcda policy/*.gcno CLEANFILES += primitives/*.gcda primitives/*.gcno CLEANFILES += script/*.gcda script/*.gcno CLEANFILES += support/*.gcda support/*.gcno CLEANFILES += univalue/*.gcda univalue/*.gcno CLEANFILES += wallet/*.gcda wallet/*.gcno CLEANFILES += wallet/test/*.gcda wallet/test/*.gcno CLEANFILES += zmq/*.gcda zmq/*.gcno DISTCLEANFILES = obj/build.h EXTRA_DIST = $(CTAES_DIST) clean-local: -$(MAKE) -C secp256k1 clean -$(MAKE) -C univalue clean -rm -f leveldb/*/*.gcda leveldb/*/*.gcno leveldb/helpers/memenv/*.gcda leveldb/helpers/memenv/*.gcno -rm -rf test/__pycache__ .rc.o: @test -f $(WINDRES) ## FIXME: How to get the appropriate modulename_CPPFLAGS in here? $(AM_V_GEN) $(WINDRES) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(CPPFLAGS) -DWINDRES_PREPROC -i $< -o $@ .mm.o: $(AM_V_CXX) $(OBJCXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CXXFLAGS) $(QT_INCLUDES) $(AM_CXXFLAGS) $(PIE_FLAGS) $(CXXFLAGS) -c -o $@ $< check-symbols: $(bin_PROGRAMS) if GLIBC_BACK_COMPAT @echo "Checking glibc back compat..." $(AM_V_at) READELF=$(READELF) CPPFILT=$(CPPFILT) $(top_srcdir)/contrib/devtools/symbol-check.py < $(bin_PROGRAMS) endif check-security: $(bin_PROGRAMS) if HARDEN @echo "Checking binary security..." $(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) $(top_srcdir)/contrib/devtools/security-check.py < $(bin_PROGRAMS) endif %.pb.cc %.pb.h: %.proto @test -f $(PROTOC) $(AM_V_GEN) $(PROTOC) --cpp_out=$(@D) --proto_path=$( "$@.new" && mv -f "$@.new" "$@" @echo "Generated $@" diff --git a/src/base58.cpp b/src/base58.cpp index 899bbd29d..2b956200c 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -1,309 +1,303 @@ // Copyright (c) 2014-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" #include "hash.h" #include "script/script.h" #include "uint256.h" #include #include #include #include #include #include #include /** All alphanumeric characters except for "0", "I", "O", and "l" */ static const char *pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; bool DecodeBase58(const char *psz, std::vector &vch) { // Skip leading spaces. while (*psz && isspace(*psz)) { psz++; } // Skip and count leading '1's. int zeroes = 0; int length = 0; while (*psz == '1') { zeroes++; psz++; } // Allocate enough space in big-endian base256 representation. // log(58) / log(256), rounded up. int size = strlen(psz) * 733 / 1000 + 1; std::vector b256(size); // Process the characters. while (*psz && !isspace(*psz)) { // Decode base58 character const char *ch = strchr(pszBase58, *psz); if (ch == nullptr) { return false; } // Apply "b256 = b256 * 58 + ch". int carry = ch - pszBase58; int i = 0; for (std::vector::reverse_iterator it = b256.rbegin(); (carry != 0 || i < length) && (it != b256.rend()); ++it, ++i) { carry += 58 * (*it); *it = carry % 256; carry /= 256; } assert(carry == 0); length = i; psz++; } // Skip trailing spaces. while (isspace(*psz)) { psz++; } if (*psz != 0) { return false; } // Skip leading zeroes in b256. std::vector::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); while (it != b256.end()) { vch.push_back(*(it++)); } return true; } std::string EncodeBase58(const uint8_t *pbegin, const uint8_t *pend) { // Skip & count leading zeroes. int zeroes = 0; int length = 0; while (pbegin != pend && *pbegin == 0) { pbegin++; zeroes++; } // Allocate enough space in big-endian base58 representation. // log(256) / log(58), rounded up. int size = (pend - pbegin) * 138 / 100 + 1; std::vector b58(size); // Process the bytes. while (pbegin != pend) { int carry = *pbegin; int i = 0; // Apply "b58 = b58 * 256 + ch". for (std::vector::reverse_iterator it = b58.rbegin(); (carry != 0 || i < length) && (it != b58.rend()); it++, i++) { carry += 256 * (*it); *it = carry % 58; carry /= 58; } assert(carry == 0); length = i; pbegin++; } // Skip leading zeroes in base58 result. std::vector::iterator it = b58.begin() + (size - length); while (it != b58.end() && *it == 0) { it++; } // Translate the result into a string. std::string str; str.reserve(zeroes + (b58.end() - it)); str.assign(zeroes, '1'); while (it != b58.end()) { str += pszBase58[*(it++)]; } return str; } std::string EncodeBase58(const std::vector &vch) { return EncodeBase58(&vch[0], &vch[0] + vch.size()); } bool DecodeBase58(const std::string &str, std::vector &vchRet) { return DecodeBase58(str.c_str(), vchRet); } std::string EncodeBase58Check(const std::vector &vchIn) { // add 4-byte hash check to the end std::vector vch(vchIn); uint256 hash = Hash(vch.begin(), vch.end()); vch.insert(vch.end(), (uint8_t *)&hash, (uint8_t *)&hash + 4); return EncodeBase58(vch); } bool DecodeBase58Check(const char *psz, std::vector &vchRet) { if (!DecodeBase58(psz, vchRet) || (vchRet.size() < 4)) { vchRet.clear(); return false; } // re-calculate the checksum, insure it matches the included 4-byte checksum uint256 hash = Hash(vchRet.begin(), vchRet.end() - 4); if (memcmp(&hash, &vchRet.end()[-4], 4) != 0) { vchRet.clear(); return false; } vchRet.resize(vchRet.size() - 4); return true; } bool DecodeBase58Check(const std::string &str, std::vector &vchRet) { return DecodeBase58Check(str.c_str(), vchRet); } CBase58Data::CBase58Data() { vchVersion.clear(); vchData.clear(); } void CBase58Data::SetData(const std::vector &vchVersionIn, const void *pdata, size_t nSize) { vchVersion = vchVersionIn; vchData.resize(nSize); if (!vchData.empty()) { memcpy(&vchData[0], pdata, nSize); } } void CBase58Data::SetData(const std::vector &vchVersionIn, const uint8_t *pbegin, const uint8_t *pend) { SetData(vchVersionIn, (void *)pbegin, pend - pbegin); } bool CBase58Data::SetString(const char *psz, unsigned int nVersionBytes) { std::vector vchTemp; bool rc58 = DecodeBase58Check(psz, vchTemp); if ((!rc58) || (vchTemp.size() < nVersionBytes)) { vchData.clear(); vchVersion.clear(); return false; } vchVersion.assign(vchTemp.begin(), vchTemp.begin() + nVersionBytes); vchData.resize(vchTemp.size() - nVersionBytes); if (!vchData.empty()) { memcpy(&vchData[0], &vchTemp[nVersionBytes], vchData.size()); } memory_cleanse(&vchTemp[0], vchTemp.size()); return true; } bool CBase58Data::SetString(const std::string &str) { return SetString(str.c_str()); } std::string CBase58Data::ToString() const { std::vector vch = vchVersion; vch.insert(vch.end(), vchData.begin(), vchData.end()); return EncodeBase58Check(vch); } int CBase58Data::CompareTo(const CBase58Data &b58) const { if (vchVersion < b58.vchVersion) return -1; if (vchVersion > b58.vchVersion) return 1; if (vchData < b58.vchData) return -1; if (vchData > b58.vchData) return 1; return 0; } namespace { class DestinationEncoder : public boost::static_visitor { private: const CChainParams &m_params; public: DestinationEncoder(const CChainParams ¶ms) : m_params(params) {} std::string operator()(const CKeyID &id) const { std::vector data = m_params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); data.insert(data.end(), id.begin(), id.end()); return EncodeBase58Check(data); } std::string operator()(const CScriptID &id) const { std::vector data = m_params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); data.insert(data.end(), id.begin(), id.end()); return EncodeBase58Check(data); } std::string operator()(const CNoDestination &no) const { return ""; } }; CTxDestination DecodeDestination(const std::string &str, const CChainParams ¶ms) { std::vector data; uint160 hash; if (!DecodeBase58Check(str, data)) { return CNoDestination(); } // Base58Check decoding const std::vector &pubkey_prefix = params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); if (data.size() == 20 + pubkey_prefix.size() && std::equal(pubkey_prefix.begin(), pubkey_prefix.end(), data.begin())) { memcpy(hash.begin(), &data[pubkey_prefix.size()], 20); return CKeyID(hash); } const std::vector &script_prefix = params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); if (data.size() == 20 + script_prefix.size() && std::equal(script_prefix.begin(), script_prefix.end(), data.begin())) { memcpy(hash.begin(), &data[script_prefix.size()], 20); return CScriptID(hash); } return CNoDestination(); } +} // namespace } // namespace void CBitcoinSecret::SetKey(const CKey &vchSecret) { assert(vchSecret.IsValid()); SetData(Params().Base58Prefix(CChainParams::SECRET_KEY), vchSecret.begin(), vchSecret.size()); if (vchSecret.IsCompressed()) vchData.push_back(1); } CKey CBitcoinSecret::GetKey() { CKey ret; assert(vchData.size() >= 32); ret.Set(vchData.begin(), vchData.begin() + 32, vchData.size() > 32 && vchData[32] == 1); return ret; } bool CBitcoinSecret::IsValid() const { bool fExpectedFormat = vchData.size() == 32 || (vchData.size() == 33 && vchData[32] == 1); bool fCorrectVersion = vchVersion == Params().Base58Prefix(CChainParams::SECRET_KEY); return fExpectedFormat && fCorrectVersion; } bool CBitcoinSecret::SetString(const char *pszSecret) { return CBase58Data::SetString(pszSecret) && IsValid(); } bool CBitcoinSecret::SetString(const std::string &strSecret) { return SetString(strSecret.c_str()); } -std::string EncodeDestination(const CTxDestination &dest) { - return boost::apply_visitor(DestinationEncoder(Params()), dest); -} - -CTxDestination DecodeDestination(const std::string &str) { - return DecodeDestination(str, Params()); -} - -bool IsValidDestinationString(const std::string &str, - const CChainParams ¶ms) { - return IsValidDestination(DecodeDestination(str, params)); +std::string EncodeLegacyAddr(const CTxDestination &dest, + const CChainParams ¶ms) { + return boost::apply_visitor(DestinationEncoder(params), dest); } -bool IsValidDestinationString(const std::string &str) { - return IsValidDestination(DecodeDestination(str, Params())); +CTxDestination DecodeLegacyAddr(const std::string &str, + const CChainParams ¶ms) { + return DecodeDestination(str, params); } diff --git a/src/base58.h b/src/base58.h index 24dea0d21..3f29b80a8 100644 --- a/src/base58.h +++ b/src/base58.h @@ -1,164 +1,161 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2015 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. /** * Why base-58 instead of standard base-64 encoding? * - Don't want 0OIl characters that look the same in some fonts and * could be used to create visually identical looking data. * - A string with non-alphanumeric characters is not as easily accepted as * input. * - E-mail usually won't line-break if there's no punctuation to break at. * - Double-clicking selects the whole string as one word if it's all * alphanumeric. */ #ifndef BITCOIN_BASE58_H #define BITCOIN_BASE58_H #include "chainparams.h" #include "key.h" #include "pubkey.h" #include "script/standard.h" #include "support/allocators/zeroafterfree.h" #include #include /** * Encode a byte sequence as a base58-encoded string. * pbegin and pend cannot be nullptr, unless both are. */ std::string EncodeBase58(const uint8_t *pbegin, const uint8_t *pend); /** * Encode a byte vector as a base58-encoded string */ std::string EncodeBase58(const std::vector &vch); /** * Decode a base58-encoded string (psz) into a byte vector (vchRet). * return true if decoding is successful. * psz cannot be nullptr. */ bool DecodeBase58(const char *psz, std::vector &vchRet); /** * Decode a base58-encoded string (str) into a byte vector (vchRet). * return true if decoding is successful. */ bool DecodeBase58(const std::string &str, std::vector &vchRet); /** * Encode a byte vector into a base58-encoded string, including checksum */ std::string EncodeBase58Check(const std::vector &vchIn); /** * Decode a base58-encoded string (psz) that includes a checksum into a byte * vector (vchRet), return true if decoding is successful */ inline bool DecodeBase58Check(const char *psz, std::vector &vchRet); /** * Decode a base58-encoded string (str) that includes a checksum into a byte * vector (vchRet), return true if decoding is successful */ inline bool DecodeBase58Check(const std::string &str, std::vector &vchRet); /** * Base class for all base58-encoded data */ class CBase58Data { protected: //! the version byte(s) std::vector vchVersion; //! the actually encoded data typedef std::vector> vector_uchar; vector_uchar vchData; CBase58Data(); void SetData(const std::vector &vchVersionIn, const void *pdata, size_t nSize); void SetData(const std::vector &vchVersionIn, const uint8_t *pbegin, const uint8_t *pend); public: bool SetString(const char *psz, unsigned int nVersionBytes = 1); bool SetString(const std::string &str); std::string ToString() const; int CompareTo(const CBase58Data &b58) const; bool operator==(const CBase58Data &b58) const { return CompareTo(b58) == 0; } bool operator<=(const CBase58Data &b58) const { return CompareTo(b58) <= 0; } bool operator>=(const CBase58Data &b58) const { return CompareTo(b58) >= 0; } bool operator<(const CBase58Data &b58) const { return CompareTo(b58) < 0; } bool operator>(const CBase58Data &b58) const { return CompareTo(b58) > 0; } }; /** * A base58-encoded secret key */ class CBitcoinSecret : public CBase58Data { public: void SetKey(const CKey &vchSecret); CKey GetKey(); bool IsValid() const; bool SetString(const char *pszSecret); bool SetString(const std::string &strSecret); CBitcoinSecret(const CKey &vchSecret) { SetKey(vchSecret); } CBitcoinSecret() {} }; template class CBitcoinExtKeyBase : public CBase58Data { public: void SetKey(const K &key) { uint8_t vch[Size]; key.Encode(vch); SetData(Params().Base58Prefix(Type), vch, vch + Size); } K GetKey() { K ret; if (vchData.size() == Size) { // If base58 encoded data does not hold an ext key, return a // !IsValid() key ret.Decode(&vchData[0]); } return ret; } CBitcoinExtKeyBase(const K &key) { SetKey(key); } CBitcoinExtKeyBase(const std::string &strBase58c) { SetString(strBase58c.c_str(), Params().Base58Prefix(Type).size()); } CBitcoinExtKeyBase() {} }; typedef CBitcoinExtKeyBase CBitcoinExtKey; typedef CBitcoinExtKeyBase CBitcoinExtPubKey; -std::string EncodeDestination(const CTxDestination &dest); -CTxDestination DecodeDestination(const std::string &str); -bool IsValidDestinationString(const std::string &str); -bool IsValidDestinationString(const std::string &str, - const CChainParams ¶ms); +std::string EncodeLegacyAddr(const CTxDestination &dest, const CChainParams &); +CTxDestination DecodeLegacyAddr(const std::string &str, const CChainParams &); #endif // BITCOIN_BASE58_H diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 34bd866f5..12094039e 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -1,902 +1,904 @@ // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "base58.h" +#include "chainparams.h" #include "clientversion.h" #include "coins.h" #include "consensus/consensus.h" #include "core_io.h" +#include "dstencode.h" #include "keystore.h" #include "policy/policy.h" #include "primitives/transaction.h" #include "script/script.h" #include "script/sign.h" #include "univalue.h" #include "util.h" #include "utilmoneystr.h" #include "utilstrencodings.h" #include #include static bool fCreateBlank; static std::map registers; static const int CONTINUE_EXECUTION = -1; // // This function returns either one of EXIT_ codes when it's expected to stop // the process or CONTINUE_EXECUTION when it's expected to continue further. // static int AppInitRawTx(int argc, char *argv[]) { // // Parameters // ParseParameters(argc, argv); // Check for -testnet or -regtest parameter (Params() calls are only valid // after this clause) try { SelectParams(ChainNameFromCommandLine()); } catch (const std::exception &e) { fprintf(stderr, "Error: %s\n", e.what()); return EXIT_FAILURE; } fCreateBlank = GetBoolArg("-create", false); if (argc < 2 || IsArgSet("-?") || IsArgSet("-h") || IsArgSet("-help")) { // First part of help message is specific to this utility std::string strUsage = strprintf(_("%s bitcoin-tx utility version"), _(PACKAGE_NAME)) + " " + FormatFullVersion() + "\n\n" + _("Usage:") + "\n" + " bitcoin-tx [options] [commands] " + _("Update hex-encoded bitcoin transaction") + "\n" + " bitcoin-tx [options] -create [commands] " + _("Create hex-encoded bitcoin transaction") + "\n" + "\n"; fprintf(stdout, "%s", strUsage.c_str()); strUsage = HelpMessageGroup(_("Options:")); strUsage += HelpMessageOpt("-?", _("This help message")); strUsage += HelpMessageOpt("-create", _("Create new, empty TX.")); strUsage += HelpMessageOpt("-json", _("Select JSON output")); strUsage += HelpMessageOpt("-txid", _("Output only the hex-encoded transaction " "id of the resultant transaction.")); AppendParamsHelpMessages(strUsage); fprintf(stdout, "%s", strUsage.c_str()); strUsage = HelpMessageGroup(_("Commands:")); strUsage += HelpMessageOpt("delin=N", _("Delete input N from TX")); strUsage += HelpMessageOpt("delout=N", _("Delete output N from TX")); strUsage += HelpMessageOpt("in=TXID:VOUT(:SEQUENCE_NUMBER)", _("Add input to TX")); strUsage += HelpMessageOpt("locktime=N", _("Set TX lock time to N")); strUsage += HelpMessageOpt("nversion=N", _("Set TX version to N")); strUsage += HelpMessageOpt("outaddr=VALUE:ADDRESS", _("Add address-based output to TX")); strUsage += HelpMessageOpt("outpubkey=VALUE:PUBKEY[:FLAGS]", _("Add pay-to-pubkey output to TX") + ". " + _("Optionally add the \"S\" flag to wrap the " "output in a pay-to-script-hash.")); strUsage += HelpMessageOpt("outdata=[VALUE:]DATA", _("Add data-based output to TX")); strUsage += HelpMessageOpt("outscript=VALUE:SCRIPT[:FLAGS]", _("Add raw script output to TX") + ". " + _("Optionally add the \"S\" flag to wrap the " "output in a pay-to-script-hash.")); strUsage += HelpMessageOpt( "outmultisig=VALUE:REQUIRED:PUBKEYS:PUBKEY1:PUBKEY2:....[:FLAGS]", _("Add Pay To n-of-m Multi-sig output to TX. n = REQUIRED, m = " "PUBKEYS") + ". " + _("Optionally add the \"S\" flag to wrap the output in " "a pay-to-script-hash.")); strUsage += HelpMessageOpt( "sign=SIGHASH-FLAGS", _("Add zero or more signatures to transaction") + ". " + _("This command requires JSON registers:") + _("prevtxs=JSON object") + ", " + _("privatekeys=JSON object") + ". " + _("See signrawtransaction docs for format of sighash " "flags, JSON objects.")); fprintf(stdout, "%s", strUsage.c_str()); strUsage = HelpMessageGroup(_("Register Commands:")); strUsage += HelpMessageOpt("load=NAME:FILENAME", _("Load JSON file FILENAME into register NAME")); strUsage += HelpMessageOpt("set=NAME:JSON-STRING", _("Set register NAME to given JSON-STRING")); fprintf(stdout, "%s", strUsage.c_str()); if (argc < 2) { fprintf(stderr, "Error: too few parameters\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } return CONTINUE_EXECUTION; } static void RegisterSetJson(const std::string &key, const std::string &rawJson) { UniValue val; if (!val.read(rawJson)) { std::string strErr = "Cannot parse JSON for key " + key; throw std::runtime_error(strErr); } registers[key] = val; } static void RegisterSet(const std::string &strInput) { // separate NAME:VALUE in string size_t pos = strInput.find(':'); if ((pos == std::string::npos) || (pos == 0) || (pos == (strInput.size() - 1))) { throw std::runtime_error("Register input requires NAME:VALUE"); } std::string key = strInput.substr(0, pos); std::string valStr = strInput.substr(pos + 1, std::string::npos); RegisterSetJson(key, valStr); } static void RegisterLoad(const std::string &strInput) { // separate NAME:FILENAME in string size_t pos = strInput.find(':'); if ((pos == std::string::npos) || (pos == 0) || (pos == (strInput.size() - 1))) { throw std::runtime_error("Register load requires NAME:FILENAME"); } std::string key = strInput.substr(0, pos); std::string filename = strInput.substr(pos + 1, std::string::npos); FILE *f = fopen(filename.c_str(), "r"); if (!f) { std::string strErr = "Cannot open file " + filename; throw std::runtime_error(strErr); } // load file chunks into one big buffer std::string valStr; while ((!feof(f)) && (!ferror(f))) { char buf[4096]; int bread = fread(buf, 1, sizeof(buf), f); if (bread <= 0) { break; } valStr.insert(valStr.size(), buf, bread); } int error = ferror(f); fclose(f); if (error) { std::string strErr = "Error reading file " + filename; throw std::runtime_error(strErr); } // evaluate as JSON buffer register RegisterSetJson(key, valStr); } static Amount ExtractAndValidateValue(const std::string &strValue) { Amount value; if (!ParseMoney(strValue, value)) { throw std::runtime_error("invalid TX output value"); } return value; } static void MutateTxVersion(CMutableTransaction &tx, const std::string &cmdVal) { int64_t newVersion = atoi64(cmdVal); if (newVersion < 1 || newVersion > CTransaction::MAX_STANDARD_VERSION) { throw std::runtime_error("Invalid TX version requested"); } tx.nVersion = int(newVersion); } static void MutateTxLocktime(CMutableTransaction &tx, const std::string &cmdVal) { int64_t newLocktime = atoi64(cmdVal); if (newLocktime < 0LL || newLocktime > 0xffffffffLL) { throw std::runtime_error("Invalid TX locktime requested"); } tx.nLockTime = (unsigned int)newLocktime; } static void MutateTxAddInput(CMutableTransaction &tx, const std::string &strInput) { std::vector vStrInputParts; boost::split(vStrInputParts, strInput, boost::is_any_of(":")); // separate TXID:VOUT in string if (vStrInputParts.size() < 2) { throw std::runtime_error("TX input missing separator"); } // extract and validate TXID std::string strTxid = vStrInputParts[0]; if ((strTxid.size() != 64) || !IsHex(strTxid)) { throw std::runtime_error("invalid TX input txid"); } uint256 txid(uint256S(strTxid)); static const unsigned int minTxOutSz = 9; static const unsigned int maxVout = MAX_TX_SIZE / minTxOutSz; // extract and validate vout std::string strVout = vStrInputParts[1]; int vout = atoi(strVout); if ((vout < 0) || (vout > (int)maxVout)) { throw std::runtime_error("invalid TX input vout"); } // extract the optional sequence number uint32_t nSequenceIn = std::numeric_limits::max(); if (vStrInputParts.size() > 2) { nSequenceIn = std::stoul(vStrInputParts[2]); } // append to transaction input list CTxIn txin(txid, vout, CScript(), nSequenceIn); tx.vin.push_back(txin); } static void MutateTxAddOutAddr(CMutableTransaction &tx, const std::string &strInput) { // Separate into VALUE:ADDRESS std::vector vStrInputParts; boost::split(vStrInputParts, strInput, boost::is_any_of(":")); if (vStrInputParts.size() != 2) { throw std::runtime_error("TX output missing or too many separators"); } // Extract and validate VALUE Amount value = ExtractAndValidateValue(vStrInputParts[0]); // extract and validate ADDRESS std::string strAddr = vStrInputParts[1]; CTxDestination destination = DecodeDestination(strAddr); if (!IsValidDestination(destination)) { throw std::runtime_error("invalid TX output address"); } CScript scriptPubKey = GetScriptForDestination(destination); // construct TxOut, append to transaction output list CTxOut txout(value, scriptPubKey); tx.vout.push_back(txout); } static void MutateTxAddOutPubKey(CMutableTransaction &tx, const std::string &strInput) { // Separate into VALUE:PUBKEY[:FLAGS] std::vector vStrInputParts; boost::split(vStrInputParts, strInput, boost::is_any_of(":")); if (vStrInputParts.size() < 2 || vStrInputParts.size() > 3) { throw std::runtime_error("TX output missing or too many separators"); } // Extract and validate VALUE Amount value = ExtractAndValidateValue(vStrInputParts[0]); // Extract and validate PUBKEY CPubKey pubkey(ParseHex(vStrInputParts[1])); if (!pubkey.IsFullyValid()) { throw std::runtime_error("invalid TX output pubkey"); } CScript scriptPubKey = GetScriptForRawPubKey(pubkey); // Extract and validate FLAGS bool bScriptHash = false; if (vStrInputParts.size() == 3) { std::string flags = vStrInputParts[2]; bScriptHash = (flags.find("S") != std::string::npos); } if (bScriptHash) { // Get the ID for the script, and then construct a P2SH destination for // it. scriptPubKey = GetScriptForDestination(CScriptID(scriptPubKey)); } // construct TxOut, append to transaction output list CTxOut txout(value, scriptPubKey); tx.vout.push_back(txout); } static void MutateTxAddOutMultiSig(CMutableTransaction &tx, const std::string &strInput) { // Separate into VALUE:REQUIRED:NUMKEYS:PUBKEY1:PUBKEY2:....[:FLAGS] std::vector vStrInputParts; boost::split(vStrInputParts, strInput, boost::is_any_of(":")); // Check that there are enough parameters if (vStrInputParts.size() < 3) { throw std::runtime_error("Not enough multisig parameters"); } // Extract and validate VALUE Amount value = ExtractAndValidateValue(vStrInputParts[0]); // Extract REQUIRED uint32_t required = stoul(vStrInputParts[1]); // Extract NUMKEYS uint32_t numkeys = stoul(vStrInputParts[2]); // Validate there are the correct number of pubkeys if (vStrInputParts.size() < numkeys + 3) { throw std::runtime_error("incorrect number of multisig pubkeys"); } if (required < 1 || required > 20 || numkeys < 1 || numkeys > 20 || numkeys < required) { throw std::runtime_error("multisig parameter mismatch. Required " + std::to_string(required) + " of " + std::to_string(numkeys) + "signatures."); } // extract and validate PUBKEYs std::vector pubkeys; for (int pos = 1; pos <= int(numkeys); pos++) { CPubKey pubkey(ParseHex(vStrInputParts[pos + 2])); if (!pubkey.IsFullyValid()) { throw std::runtime_error("invalid TX output pubkey"); } pubkeys.push_back(pubkey); } // Extract FLAGS bool bScriptHash = false; if (vStrInputParts.size() == numkeys + 4) { std::string flags = vStrInputParts.back(); bScriptHash = (flags.find("S") != std::string::npos); } else if (vStrInputParts.size() > numkeys + 4) { // Validate that there were no more parameters passed throw std::runtime_error("Too many parameters"); } CScript scriptPubKey = GetScriptForMultisig(required, pubkeys); if (bScriptHash) { // Get the ID for the script, and then construct a P2SH destination for // it. scriptPubKey = GetScriptForDestination(CScriptID(scriptPubKey)); } // construct TxOut, append to transaction output list CTxOut txout(value, scriptPubKey); tx.vout.push_back(txout); } static void MutateTxAddOutData(CMutableTransaction &tx, const std::string &strInput) { Amount value = 0; // separate [VALUE:]DATA in string size_t pos = strInput.find(':'); if (pos == 0) { throw std::runtime_error("TX output value not specified"); } if (pos != std::string::npos) { // Extract and validate VALUE value = ExtractAndValidateValue(strInput.substr(0, pos)); } // extract and validate DATA std::string strData = strInput.substr(pos + 1, std::string::npos); if (!IsHex(strData)) { throw std::runtime_error("invalid TX output data"); } std::vector data = ParseHex(strData); CTxOut txout(value, CScript() << OP_RETURN << data); tx.vout.push_back(txout); } static void MutateTxAddOutScript(CMutableTransaction &tx, const std::string &strInput) { // separate VALUE:SCRIPT[:FLAGS] std::vector vStrInputParts; boost::split(vStrInputParts, strInput, boost::is_any_of(":")); if (vStrInputParts.size() < 2) throw std::runtime_error("TX output missing separator"); // Extract and validate VALUE Amount value = ExtractAndValidateValue(vStrInputParts[0]); // extract and validate script std::string strScript = vStrInputParts[1]; CScript scriptPubKey = ParseScript(strScript); // Extract FLAGS bool bScriptHash = false; if (vStrInputParts.size() == 3) { std::string flags = vStrInputParts.back(); bScriptHash = (flags.find("S") != std::string::npos); } if (bScriptHash) { scriptPubKey = GetScriptForDestination(CScriptID(scriptPubKey)); } // construct TxOut, append to transaction output list CTxOut txout(value, scriptPubKey); tx.vout.push_back(txout); } static void MutateTxDelInput(CMutableTransaction &tx, const std::string &strInIdx) { // parse requested deletion index int inIdx = atoi(strInIdx); if (inIdx < 0 || inIdx >= (int)tx.vin.size()) { std::string strErr = "Invalid TX input index '" + strInIdx + "'"; throw std::runtime_error(strErr.c_str()); } // delete input from transaction tx.vin.erase(tx.vin.begin() + inIdx); } static void MutateTxDelOutput(CMutableTransaction &tx, const std::string &strOutIdx) { // parse requested deletion index int outIdx = atoi(strOutIdx); if (outIdx < 0 || outIdx >= (int)tx.vout.size()) { std::string strErr = "Invalid TX output index '" + strOutIdx + "'"; throw std::runtime_error(strErr.c_str()); } // delete output from transaction tx.vout.erase(tx.vout.begin() + outIdx); } static const unsigned int N_SIGHASH_OPTS = 12; static const struct { const char *flagStr; int flags; } sighashOptions[N_SIGHASH_OPTS] = { {"ALL", SIGHASH_ALL}, {"NONE", SIGHASH_NONE}, {"SINGLE", SIGHASH_SINGLE}, {"ALL|ANYONECANPAY", SIGHASH_ALL | SIGHASH_ANYONECANPAY}, {"NONE|ANYONECANPAY", SIGHASH_NONE | SIGHASH_ANYONECANPAY}, {"SINGLE|ANYONECANPAY", SIGHASH_SINGLE | SIGHASH_ANYONECANPAY}, {"ALL|FORKID", SIGHASH_ALL | SIGHASH_FORKID}, {"NONE|FORKID", SIGHASH_NONE | SIGHASH_FORKID}, {"SINGLE|FORKID", SIGHASH_SINGLE | SIGHASH_FORKID}, {"ALL|FORKID|ANYONECANPAY", SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_ANYONECANPAY}, {"NONE|FORKID|ANYONECANPAY", SIGHASH_NONE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY}, {"SINGLE|FORKID|ANYONECANPAY", SIGHASH_SINGLE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY}, }; static bool findSighashFlags(int &flags, const std::string &flagStr) { flags = 0; for (unsigned int i = 0; i < N_SIGHASH_OPTS; i++) { if (flagStr == sighashOptions[i].flagStr) { flags = sighashOptions[i].flags; return true; } } return false; } uint256 ParseHashUO(std::map &o, std::string strKey) { if (!o.count(strKey)) { return uint256(); } return ParseHashUV(o[strKey], strKey); } std::vector ParseHexUO(std::map &o, std::string strKey) { if (!o.count(strKey)) { std::vector emptyVec; return emptyVec; } return ParseHexUV(o[strKey], strKey); } static Amount AmountFromValue(const UniValue &value) { if (!value.isNum() && !value.isStr()) { throw std::runtime_error("Amount is not a number or string"); } int64_t n; if (!ParseFixedPoint(value.getValStr(), 8, &n)) { throw std::runtime_error("Invalid amount"); } Amount amount = Amount(n); if (!MoneyRange(amount)) { throw std::runtime_error("Amount out of range"); } return amount; } static void MutateTxSign(CMutableTransaction &tx, const std::string &flagStr) { int nHashType = SIGHASH_ALL | SIGHASH_FORKID; if ((flagStr.size() > 0) && !findSighashFlags(nHashType, flagStr)) { throw std::runtime_error("unknown sighash flag/sign option"); } std::vector txVariants; txVariants.push_back(tx); // mergedTx will end up with all the signatures; it starts as a clone of the // raw tx: CMutableTransaction mergedTx(txVariants[0]); bool fComplete = true; CCoinsView viewDummy; CCoinsViewCache view(&viewDummy); if (!registers.count("privatekeys")) { throw std::runtime_error("privatekeys register variable must be set."); } CBasicKeyStore tempKeystore; UniValue keysObj = registers["privatekeys"]; for (unsigned int kidx = 0; kidx < keysObj.size(); kidx++) { if (!keysObj[kidx].isStr()) { throw std::runtime_error("privatekey not a std::string"); } CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(keysObj[kidx].getValStr()); if (!fGood) { throw std::runtime_error("privatekey not valid"); } CKey key = vchSecret.GetKey(); tempKeystore.AddKey(key); } // Add previous txouts given in the RPC call: if (!registers.count("prevtxs")) { throw std::runtime_error("prevtxs register variable must be set."); } UniValue prevtxsObj = registers["prevtxs"]; for (unsigned int previdx = 0; previdx < prevtxsObj.size(); previdx++) { UniValue prevOut = prevtxsObj[previdx]; if (!prevOut.isObject()) { throw std::runtime_error("expected prevtxs internal object"); } std::map types = { {"txid", UniValue::VSTR}, {"vout", UniValue::VNUM}, {"scriptPubKey", UniValue::VSTR}}; if (!prevOut.checkObject(types)) { throw std::runtime_error("prevtxs internal object typecheck fail"); } uint256 txid = ParseHashUV(prevOut["txid"], "txid"); int nOut = atoi(prevOut["vout"].getValStr()); if (nOut < 0) { throw std::runtime_error("vout must be positive"); } COutPoint out(txid, nOut); std::vector pkData( ParseHexUV(prevOut["scriptPubKey"], "scriptPubKey")); CScript scriptPubKey(pkData.begin(), pkData.end()); { const Coin &coin = view.AccessCoin(out); if (!coin.IsSpent() && coin.GetTxOut().scriptPubKey != scriptPubKey) { std::string err("Previous output scriptPubKey mismatch:\n"); err = err + ScriptToAsmStr(coin.GetTxOut().scriptPubKey) + "\nvs:\n" + ScriptToAsmStr(scriptPubKey); throw std::runtime_error(err); } CTxOut txout; txout.scriptPubKey = scriptPubKey; txout.nValue = 0; if (prevOut.exists("amount")) { txout.nValue = AmountFromValue(prevOut["amount"]); } view.AddCoin(out, Coin(txout, 1, false), true); } // If redeemScript given and private keys given, add redeemScript to the // tempKeystore so it can be signed: if (scriptPubKey.IsPayToScriptHash() && prevOut.exists("redeemScript")) { UniValue v = prevOut["redeemScript"]; std::vector rsData(ParseHexUV(v, "redeemScript")); CScript redeemScript(rsData.begin(), rsData.end()); tempKeystore.AddCScript(redeemScript); } } const CKeyStore &keystore = tempKeystore; bool fHashSingle = ((nHashType & ~(SIGHASH_ANYONECANPAY | SIGHASH_FORKID)) == SIGHASH_SINGLE); // Sign what we can: for (size_t i = 0; i < mergedTx.vin.size(); i++) { CTxIn &txin = mergedTx.vin[i]; const Coin &coin = view.AccessCoin(txin.prevout); if (coin.IsSpent()) { fComplete = false; continue; } const CScript &prevPubKey = coin.GetTxOut().scriptPubKey; const Amount amount = coin.GetTxOut().nValue; SignatureData sigdata; // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mergedTx.vout.size())) { ProduceSignature( MutableTransactionSignatureCreator( &keystore, &mergedTx, i, amount.GetSatoshis(), nHashType), prevPubKey, sigdata); } // ... and merge in other signatures: for (const CTransaction &txv : txVariants) { sigdata = CombineSignatures(prevPubKey, MutableTransactionSignatureChecker( &mergedTx, i, amount.GetSatoshis()), sigdata, DataFromTransaction(txv, i)); } UpdateTransaction(mergedTx, i, sigdata); if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker( &mergedTx, i, amount.GetSatoshis()))) { fComplete = false; } } if (fComplete) { // do nothing... for now // perhaps store this for later optional JSON output } tx = mergedTx; } class Secp256k1Init { ECCVerifyHandle globalVerifyHandle; public: Secp256k1Init() { ECC_Start(); } ~Secp256k1Init() { ECC_Stop(); } }; static void MutateTx(CMutableTransaction &tx, const std::string &command, const std::string &commandVal) { std::unique_ptr ecc; if (command == "nversion") { MutateTxVersion(tx, commandVal); } else if (command == "locktime") { MutateTxLocktime(tx, commandVal); } else if (command == "delin") { MutateTxDelInput(tx, commandVal); } else if (command == "in") { MutateTxAddInput(tx, commandVal); } else if (command == "delout") { MutateTxDelOutput(tx, commandVal); } else if (command == "outaddr") { MutateTxAddOutAddr(tx, commandVal); } else if (command == "outpubkey") { MutateTxAddOutPubKey(tx, commandVal); } else if (command == "outmultisig") { MutateTxAddOutMultiSig(tx, commandVal); } else if (command == "outscript") { MutateTxAddOutScript(tx, commandVal); } else if (command == "outdata") { MutateTxAddOutData(tx, commandVal); } else if (command == "sign") { if (!ecc) { ecc.reset(new Secp256k1Init()); } MutateTxSign(tx, commandVal); } else if (command == "load") { RegisterLoad(commandVal); } else if (command == "set") { RegisterSet(commandVal); } else { throw std::runtime_error("unknown command"); } } static void OutputTxJSON(const CTransaction &tx) { UniValue entry(UniValue::VOBJ); TxToUniv(tx, uint256(), entry); std::string jsonOutput = entry.write(4); fprintf(stdout, "%s\n", jsonOutput.c_str()); } static void OutputTxHash(const CTransaction &tx) { // the hex-encoded transaction id. std::string strHexHash = tx.GetId().GetHex(); fprintf(stdout, "%s\n", strHexHash.c_str()); } static void OutputTxHex(const CTransaction &tx) { std::string strHex = EncodeHexTx(tx); fprintf(stdout, "%s\n", strHex.c_str()); } static void OutputTx(const CTransaction &tx) { if (GetBoolArg("-json", false)) { OutputTxJSON(tx); } else if (GetBoolArg("-txid", false)) { OutputTxHash(tx); } else { OutputTxHex(tx); } } static std::string readStdin() { char buf[4096]; std::string ret; while (!feof(stdin)) { size_t bread = fread(buf, 1, sizeof(buf), stdin); ret.append(buf, bread); if (bread < sizeof(buf)) { break; } } if (ferror(stdin)) { throw std::runtime_error("error reading stdin"); } boost::algorithm::trim_right(ret); return ret; } static int CommandLineRawTx(int argc, char *argv[]) { std::string strPrint; int nRet = 0; try { // Skip switches; Permit common stdin convention "-" while (argc > 1 && IsSwitchChar(argv[1][0]) && (argv[1][1] != 0)) { argc--; argv++; } CMutableTransaction tx; int startArg; if (!fCreateBlank) { // require at least one param if (argc < 2) { throw std::runtime_error("too few parameters"); } // param: hex-encoded bitcoin transaction std::string strHexTx(argv[1]); // "-" implies standard input if (strHexTx == "-") { strHexTx = readStdin(); } if (!DecodeHexTx(tx, strHexTx)) { throw std::runtime_error("invalid transaction encoding"); } startArg = 2; } else { startArg = 1; } for (int i = startArg; i < argc; i++) { std::string arg = argv[i]; std::string key, value; size_t eqpos = arg.find('='); if (eqpos == std::string::npos) { key = arg; } else { key = arg.substr(0, eqpos); value = arg.substr(eqpos + 1); } MutateTx(tx, key, value); } OutputTx(tx); } catch (const boost::thread_interrupted &) { throw; } catch (const std::exception &e) { strPrint = std::string("error: ") + e.what(); nRet = EXIT_FAILURE; } catch (...) { PrintExceptionContinue(nullptr, "CommandLineRawTx()"); throw; } if (strPrint != "") { fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); } return nRet; } int main(int argc, char *argv[]) { SetupEnvironment(); try { int ret = AppInitRawTx(argc, argv); if (ret != CONTINUE_EXECUTION) return ret; } catch (const std::exception &e) { PrintExceptionContinue(&e, "AppInitRawTx()"); return EXIT_FAILURE; } catch (...) { PrintExceptionContinue(nullptr, "AppInitRawTx()"); return EXIT_FAILURE; } int ret = EXIT_FAILURE; try { ret = CommandLineRawTx(argc, argv); } catch (const std::exception &e) { PrintExceptionContinue(&e, "CommandLineRawTx()"); } catch (...) { PrintExceptionContinue(nullptr, "CommandLineRawTx()"); } return ret; } diff --git a/src/config.cpp b/src/config.cpp index 2298fb2cf..e05cf417b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,46 +1,55 @@ // Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "config.h" #include "chainparams.h" #include "consensus/consensus.h" #include "globals.h" +GlobalConfig::GlobalConfig() : useCashAddr(false) {} + bool GlobalConfig::SetMaxBlockSize(uint64_t maxBlockSize) { // Do not allow maxBlockSize to be set below historic 1MB limit // It cannot be equal either because of the "must be big" UAHF rule. if (maxBlockSize <= LEGACY_MAX_BLOCK_SIZE) { return false; } nMaxBlockSize = maxBlockSize; return true; } uint64_t GlobalConfig::GetMaxBlockSize() const { return nMaxBlockSize; } bool GlobalConfig::SetBlockPriorityPercentage(int64_t blockPriorityPercentage) { // blockPriorityPercentage has to belong to [0..100] if ((blockPriorityPercentage < 0) || (blockPriorityPercentage > 100)) { return false; } nBlockPriorityPercentage = blockPriorityPercentage; return true; } uint8_t GlobalConfig::GetBlockPriorityPercentage() const { return nBlockPriorityPercentage; } const CChainParams &GlobalConfig::GetChainParams() const { return Params(); } static GlobalConfig gConfig; const Config &GetConfig() { return gConfig; } + +void GlobalConfig::SetCashAddrEncoding(bool c) { + useCashAddr = c; +} +bool GlobalConfig::UseCashAddrEncoding() const { + return useCashAddr; +} diff --git a/src/config.h b/src/config.h index 09358692d..a668a5c52 100644 --- a/src/config.h +++ b/src/config.h @@ -1,36 +1,44 @@ // Copyright (c) 2017 Amaury SÉCHET // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_CONFIG_H #define BITCOIN_CONFIG_H #include #include class CChainParams; class Config : public boost::noncopyable { public: virtual bool SetMaxBlockSize(uint64_t maxBlockSize) = 0; virtual uint64_t GetMaxBlockSize() const = 0; virtual bool SetBlockPriorityPercentage(int64_t blockPriorityPercentage) = 0; virtual uint8_t GetBlockPriorityPercentage() const = 0; virtual const CChainParams &GetChainParams() const = 0; + virtual void SetCashAddrEncoding(bool) = 0; + virtual bool UseCashAddrEncoding() const = 0; }; class GlobalConfig final : public Config { public: + GlobalConfig(); bool SetMaxBlockSize(uint64_t maxBlockSize); uint64_t GetMaxBlockSize() const; bool SetBlockPriorityPercentage(int64_t blockPriorityPercentage); uint8_t GetBlockPriorityPercentage() const; const CChainParams &GetChainParams() const; + void SetCashAddrEncoding(bool) override; + bool UseCashAddrEncoding() const override; + +private: + bool useCashAddr; }; // Temporary woraround. const Config &GetConfig(); #endif diff --git a/src/core_write.cpp b/src/core_write.cpp index c18d23f4b..82d080643 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -1,240 +1,240 @@ // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "core_io.h" -#include "base58.h" +#include "dstencode.h" #include "primitives/transaction.h" #include "script/script.h" #include "script/standard.h" #include "serialize.h" #include "streams.h" #include "util.h" #include "utilmoneystr.h" #include "utilstrencodings.h" #include std::string FormatScript(const CScript &script) { std::string ret; CScript::const_iterator it = script.begin(); opcodetype op; while (it != script.end()) { CScript::const_iterator it2 = it; std::vector vch; if (script.GetOp2(it, op, &vch)) { if (op == OP_0) { ret += "0 "; continue; } if ((op >= OP_1 && op <= OP_16) || op == OP_1NEGATE) { ret += strprintf("%i ", op - OP_1NEGATE - 1); continue; } if (op >= OP_NOP && op <= OP_NOP10) { std::string str(GetOpName(op)); if (str.substr(0, 3) == std::string("OP_")) { ret += str.substr(3, std::string::npos) + " "; continue; } } if (vch.size() > 0) { ret += strprintf("0x%x 0x%x ", HexStr(it2, it - vch.size()), HexStr(it - vch.size(), it)); } else { ret += strprintf("0x%x ", HexStr(it2, it)); } continue; } ret += strprintf("0x%x ", HexStr(it2, script.end())); break; } return ret.substr(0, ret.size() - 1); } const std::map mapSigHashTypes = { {SIGHASH_ALL, "ALL"}, {SIGHASH_ALL | SIGHASH_ANYONECANPAY, "ALL|ANYONECANPAY"}, {SIGHASH_ALL | SIGHASH_FORKID, "ALL|FORKID"}, {SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_ANYONECANPAY, "ALL|FORKID|ANYONECANPAY"}, {SIGHASH_NONE, "NONE"}, {SIGHASH_NONE | SIGHASH_ANYONECANPAY, "NONE|ANYONECANPAY"}, {SIGHASH_NONE | SIGHASH_FORKID, "NONE|FORKID"}, {SIGHASH_NONE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY, "NONE|FORKID|ANYONECANPAY"}, {SIGHASH_SINGLE, "SINGLE"}, {SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, "SINGLE|ANYONECANPAY"}, {SIGHASH_SINGLE | SIGHASH_FORKID, "SINGLE|FORKID"}, {SIGHASH_SINGLE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY, "SINGLE|FORKID|ANYONECANPAY"}, }; /** * Create the assembly string representation of a CScript object. * @param[in] script CScript object to convert into the asm string * representation. * @param[in] fAttemptSighashDecode Whether to attempt to decode sighash * types on data within the script that matches the format of a signature. Only * pass true for scripts you believe could contain signatures. For example, pass * false, or omit the this argument (defaults to false), for scriptPubKeys. */ std::string ScriptToAsmStr(const CScript &script, const bool fAttemptSighashDecode) { std::string str; opcodetype opcode; std::vector vch; CScript::const_iterator pc = script.begin(); while (pc < script.end()) { if (!str.empty()) { str += " "; } if (!script.GetOp(pc, opcode, vch)) { str += "[error]"; return str; } if (0 <= opcode && opcode <= OP_PUSHDATA4) { if (vch.size() <= static_cast::size_type>(4)) { str += strprintf("%d", CScriptNum(vch, false).getint()); } else { // the IsUnspendable check makes sure not to try to decode // OP_RETURN data that may match the format of a signature if (fAttemptSighashDecode && !script.IsUnspendable()) { std::string strSigHashDecode; // goal: only attempt to decode a defined sighash type from // data that looks like a signature within a scriptSig. This // won't decode correctly formatted public keys in Pubkey or // Multisig scripts due to the restrictions on the pubkey // formats (see IsCompressedOrUncompressedPubKey) being // incongruous with the checks in CheckSignatureEncoding. uint32_t flags = SCRIPT_VERIFY_STRICTENC; if (vch.back() & SIGHASH_FORKID) { // If the transaction is using SIGHASH_FORKID, we need // to set the apropriate flag. // TODO: Remove after the Hard Fork. flags |= SCRIPT_ENABLE_SIGHASH_FORKID; } if (CheckSignatureEncoding(vch, flags, nullptr)) { const uint8_t chSigHashType = vch.back(); if (mapSigHashTypes.count(chSigHashType)) { strSigHashDecode = "[" + mapSigHashTypes.find(chSigHashType)->second + "]"; // remove the sighash type byte. it will be replaced // by the decode. vch.pop_back(); } } str += HexStr(vch) + strSigHashDecode; } else { str += HexStr(vch); } } } else { str += GetOpName(opcode); } } return str; } std::string EncodeHexTx(const CTransaction &tx, const int serialFlags) { CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | serialFlags); ssTx << tx; return HexStr(ssTx.begin(), ssTx.end()); } void ScriptPubKeyToUniv(const CScript &scriptPubKey, UniValue &out, bool fIncludeHex) { txnouttype type; std::vector addresses; int nRequired; out.pushKV("asm", ScriptToAsmStr(scriptPubKey)); if (fIncludeHex) { out.pushKV("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end())); } if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { out.pushKV("type", GetTxnOutputType(type)); return; } out.pushKV("reqSigs", nRequired); out.pushKV("type", GetTxnOutputType(type)); UniValue a(UniValue::VARR); for (const CTxDestination &addr : addresses) { a.push_back(EncodeDestination(addr)); } out.pushKV("addresses", a); } void TxToUniv(const CTransaction &tx, const uint256 &hashBlock, UniValue &entry) { entry.pushKV("txid", tx.GetId().GetHex()); entry.pushKV("hash", tx.GetHash().GetHex()); entry.pushKV("version", tx.nVersion); entry.pushKV("locktime", (int64_t)tx.nLockTime); UniValue vin(UniValue::VARR); for (unsigned int i = 0; i < tx.vin.size(); i++) { const CTxIn &txin = tx.vin[i]; UniValue in(UniValue::VOBJ); if (tx.IsCoinBase()) { in.pushKV("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); } else { in.pushKV("txid", txin.prevout.hash.GetHex()); in.pushKV("vout", (int64_t)txin.prevout.n); UniValue o(UniValue::VOBJ); o.pushKV("asm", ScriptToAsmStr(txin.scriptSig, true)); o.pushKV("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); in.pushKV("scriptSig", o); } in.pushKV("sequence", (int64_t)txin.nSequence); vin.push_back(in); } entry.pushKV("vin", vin); UniValue vout(UniValue::VARR); for (unsigned int i = 0; i < tx.vout.size(); i++) { const CTxOut &txout = tx.vout[i]; UniValue out(UniValue::VOBJ); UniValue outValue(UniValue::VNUM, FormatMoney(txout.nValue.GetSatoshis())); out.pushKV("value", outValue); out.pushKV("n", (int64_t)i); UniValue o(UniValue::VOBJ); ScriptPubKeyToUniv(txout.scriptPubKey, o, true); out.pushKV("scriptPubKey", o); vout.push_back(out); } entry.pushKV("vout", vout); if (!hashBlock.IsNull()) { entry.pushKV("blockhash", hashBlock.GetHex()); } // the hex-encoded transaction. used the name "hex" to be consistent with // the verbose output of "getrawtransaction". entry.pushKV("hex", EncodeHexTx(tx)); } diff --git a/src/dstencode.cpp b/src/dstencode.cpp new file mode 100644 index 000000000..ee3a4b061 --- /dev/null +++ b/src/dstencode.cpp @@ -0,0 +1,41 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "dstencode.h" +#include "base58.h" +#include "cashaddrenc.h" +#include "chainparams.h" +#include "config.h" +#include "script/standard.h" + +std::string EncodeDestination(const CTxDestination &dst, + const CChainParams ¶ms, const Config &cfg) { + return cfg.UseCashAddrEncoding() ? EncodeCashAddr(dst, params) + : EncodeLegacyAddr(dst, params); +} + +CTxDestination DecodeDestination(const std::string &addr, + const CChainParams ¶ms) { + CTxDestination dst = DecodeCashAddr(addr, params); + if (IsValidDestination(dst)) { + return dst; + } + return DecodeLegacyAddr(addr, params); +} + +bool IsValidDestinationString(const std::string &addr, + const CChainParams ¶ms) { + return IsValidDestination(DecodeDestination(addr, params)); +} + +std::string EncodeDestination(const CTxDestination &dst) { + return EncodeDestination(dst, Params(), GetConfig()); +} + +CTxDestination DecodeDestination(const std::string &addr) { + return DecodeDestination(addr, Params()); +} + +bool IsValidDestinationString(const std::string &addr) { + return IsValidDestinationString(addr, Params()); +} diff --git a/src/dstencode.h b/src/dstencode.h new file mode 100644 index 000000000..89412801c --- /dev/null +++ b/src/dstencode.h @@ -0,0 +1,26 @@ +#ifndef BITCOIN_DSTENCODE_H +#define BITCOIN_DSTENCODE_H + +// key.h and pubkey.h are not used here, but gcc doesn't want to instantiate +// CTxDestination if types are unknown +#include "key.h" +#include "pubkey.h" +#include "script/standard.h" +#include + +class Config; +class CChainParams; + +std::string EncodeDestination(const CTxDestination &, const CChainParams &, + const Config &); +CTxDestination DecodeDestination(const std::string &addr, const CChainParams &); +bool IsValidDestinationString(const std::string &addr, + const CChainParams ¶ms); + +// Temporary workaround. Don't rely on global state, pass all parameters in new +// code. +std::string EncodeDestination(const CTxDestination &); +CTxDestination DecodeDestination(const std::string &addr); +bool IsValidDestinationString(const std::string &addr); + +#endif // BITCOIN_DSTENCODE_H diff --git a/src/init.cpp b/src/init.cpp index e1b4ae180..feadc0942 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1,2190 +1,2191 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "init.h" #include "addrman.h" #include "amount.h" #include "chain.h" #include "chainparams.h" #include "checkpoints.h" #include "compat/sanity.h" #include "config.h" #include "consensus/validation.h" #include "httprpc.h" #include "httpserver.h" #include "key.h" #include "miner.h" #include "net.h" #include "net_processing.h" #include "netbase.h" #include "policy/policy.h" #include "rpc/register.h" #include "rpc/server.h" #include "scheduler.h" #include "script/scriptcache.h" #include "script/sigcache.h" #include "script/standard.h" #include "timedata.h" #include "torcontrol.h" #include "txdb.h" #include "txmempool.h" #include "ui_interface.h" #include "util.h" #include "utilmoneystr.h" #include "validation.h" #include "validationinterface.h" #ifdef ENABLE_WALLET #include "wallet/rpcdump.h" #include "wallet/wallet.h" #endif #include "warnings.h" #include #include #include #ifndef WIN32 #include #endif #include #include #include #include #include #include #include #include #include #if ENABLE_ZMQ #include "zmq/zmqnotificationinterface.h" #endif bool fFeeEstimatesInitialized = false; static const bool DEFAULT_PROXYRANDOMIZE = true; static const bool DEFAULT_REST_ENABLE = false; static const bool DEFAULT_DISABLE_SAFEMODE = false; static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false; std::unique_ptr g_connman; std::unique_ptr peerLogic; #if ENABLE_ZMQ static CZMQNotificationInterface *pzmqNotificationInterface = nullptr; #endif #ifdef WIN32 // Win32 LevelDB doesn't use filedescriptors, and the ones used for accessing // block files don't count towards the fd_set size limit anyway. #define MIN_CORE_FILEDESCRIPTORS 0 #else #define MIN_CORE_FILEDESCRIPTORS 150 #endif /** Used to pass flags to the Bind() function */ enum BindFlags { BF_NONE = 0, BF_EXPLICIT = (1U << 0), BF_REPORT_ERROR = (1U << 1), BF_WHITELIST = (1U << 2), }; static const char *FEE_ESTIMATES_FILENAME = "fee_estimates.dat"; ////////////////////////////////////////////////////////////////////////////// // // Shutdown // // // Thread management and startup/shutdown: // // The network-processing threads are all part of a thread group created by // AppInit() or the Qt main() function. // // A clean exit happens when StartShutdown() or the SIGTERM signal handler sets // fRequestShutdown, which triggers the DetectShutdownThread(), which interrupts // the main thread group. DetectShutdownThread() then exits, which causes // AppInit() to continue (it .joins the shutdown thread). Shutdown() is then // called to clean up database connections, and stop other threads that should // only be stopped after the main network-processing threads have exited. // // Note that if running -daemon the parent process returns from AppInit2 before // adding any threads to the threadGroup, so .join_all() returns immediately and // the parent exits from main(). // // Shutdown for Qt is very similar, only it uses a QTimer to detect // fRequestShutdown getting set, and then does the normal Qt shutdown thing. // std::atomic fRequestShutdown(false); std::atomic fDumpMempoolLater(false); void StartShutdown() { fRequestShutdown = true; } bool ShutdownRequested() { return fRequestShutdown; } /** * This is a minimally invasive approach to shutdown on LevelDB read errors from * the chainstate, while keeping user interface out of the common library, which * is shared between bitcoind, and bitcoin-qt and non-server tools. */ class CCoinsViewErrorCatcher : public CCoinsViewBacked { public: CCoinsViewErrorCatcher(CCoinsView *view) : CCoinsViewBacked(view) {} bool GetCoin(const COutPoint &outpoint, Coin &coin) const { try { return CCoinsViewBacked::GetCoin(outpoint, coin); } catch (const std::runtime_error &e) { uiInterface.ThreadSafeMessageBox( _("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR); LogPrintf("Error reading from database: %s\n", e.what()); // Starting the shutdown sequence and returning false to the caller // would be interpreted as 'entry not found' (as opposed to unable // to read data), and could lead to invalid interpretation. Just // exit immediately, as we can't continue anyway, and all writes // should be atomic. abort(); } } // Writes do not need similar protection, as failure to write is handled by // the caller. }; static CCoinsViewDB *pcoinsdbview = nullptr; static CCoinsViewErrorCatcher *pcoinscatcher = nullptr; static std::unique_ptr globalVerifyHandle; void Interrupt(boost::thread_group &threadGroup) { InterruptHTTPServer(); InterruptHTTPRPC(); InterruptRPC(); InterruptREST(); InterruptTorControl(); if (g_connman) g_connman->Interrupt(); threadGroup.interrupt_all(); } void Shutdown() { LogPrintf("%s: In progress...\n", __func__); static CCriticalSection cs_Shutdown; TRY_LOCK(cs_Shutdown, lockShutdown); if (!lockShutdown) return; /// Note: Shutdown() must be able to handle cases in which AppInit2() failed /// part of the way, for example if the data directory was found to be /// locked. Be sure that anything that writes files or flushes caches only /// does this if the respective module was initialized. RenameThread("bitcoin-shutoff"); mempool.AddTransactionsUpdated(1); StopHTTPRPC(); StopREST(); StopRPC(); StopHTTPServer(); #ifdef ENABLE_WALLET if (pwalletMain) pwalletMain->Flush(false); #endif MapPort(false); UnregisterValidationInterface(peerLogic.get()); peerLogic.reset(); g_connman.reset(); StopTorControl(); UnregisterNodeSignals(GetNodeSignals()); if (fDumpMempoolLater) DumpMempool(); if (fFeeEstimatesInitialized) { boost::filesystem::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME; CAutoFile est_fileout(fopen(est_path.string().c_str(), "wb"), SER_DISK, CLIENT_VERSION); if (!est_fileout.IsNull()) mempool.WriteFeeEstimates(est_fileout); else LogPrintf("%s: Failed to write fee estimates to %s\n", __func__, est_path.string()); fFeeEstimatesInitialized = false; } { LOCK(cs_main); if (pcoinsTip != nullptr) { FlushStateToDisk(); } delete pcoinsTip; pcoinsTip = nullptr; delete pcoinscatcher; pcoinscatcher = nullptr; delete pcoinsdbview; pcoinsdbview = nullptr; delete pblocktree; pblocktree = nullptr; } #ifdef ENABLE_WALLET if (pwalletMain) pwalletMain->Flush(true); #endif #if ENABLE_ZMQ if (pzmqNotificationInterface) { UnregisterValidationInterface(pzmqNotificationInterface); delete pzmqNotificationInterface; pzmqNotificationInterface = nullptr; } #endif #ifndef WIN32 try { boost::filesystem::remove(GetPidFile()); } catch (const boost::filesystem::filesystem_error &e) { LogPrintf("%s: Unable to remove pidfile: %s\n", __func__, e.what()); } #endif UnregisterAllValidationInterfaces(); #ifdef ENABLE_WALLET delete pwalletMain; pwalletMain = nullptr; #endif globalVerifyHandle.reset(); ECC_Stop(); LogPrintf("%s: done\n", __func__); } /** * Signal handlers are very limited in what they are allowed to do, so: */ void HandleSIGTERM(int) { fRequestShutdown = true; } void HandleSIGHUP(int) { fReopenDebugLog = true; } static bool Bind(CConnman &connman, const CService &addr, unsigned int flags) { if (!(flags & BF_EXPLICIT) && IsLimited(addr)) return false; std::string strError; if (!connman.BindListenPort(addr, strError, (flags & BF_WHITELIST) != 0)) { if (flags & BF_REPORT_ERROR) return InitError(strError); return false; } return true; } void OnRPCStarted() { uiInterface.NotifyBlockTip.connect(&RPCNotifyBlockChange); } void OnRPCStopped() { uiInterface.NotifyBlockTip.disconnect(&RPCNotifyBlockChange); RPCNotifyBlockChange(false, nullptr); cvBlockChange.notify_all(); LogPrint("rpc", "RPC stopped.\n"); } void OnRPCPreCommand(const CRPCCommand &cmd) { // Observe safe mode. std::string strWarning = GetWarnings("rpc"); if (strWarning != "" && !GetBoolArg("-disablesafemode", DEFAULT_DISABLE_SAFEMODE) && !cmd.okSafeMode) throw JSONRPCError(RPC_FORBIDDEN_BY_SAFE_MODE, std::string("Safe mode: ") + strWarning); } std::string HelpMessage(HelpMessageMode mode) { const bool showDebug = GetBoolArg("-help-debug", false); // When adding new options to the categories, please keep and ensure // alphabetical ordering. Do not translate _(...) -help-debug options, Many // technical terms, and only a very small audience, so is unnecessary stress // to translators. std::string strUsage = HelpMessageGroup(_("Options:")); strUsage += HelpMessageOpt("-?", _("Print this help message and exit")); strUsage += HelpMessageOpt("-version", _("Print version and exit")); strUsage += HelpMessageOpt( "-alertnotify=", _("Execute command when a relevant alert is received or we see a " "really long fork (%s in cmd is replaced by message)")); strUsage += HelpMessageOpt("-blocknotify=", _("Execute command when the best block changes " "(%s in cmd is replaced by block hash)")); if (showDebug) strUsage += HelpMessageOpt( "-blocksonly", strprintf( _("Whether to operate in a blocks only mode (default: %d)"), DEFAULT_BLOCKSONLY)); strUsage += HelpMessageOpt( "-assumevalid=", strprintf(_("If this block is in the chain assume that it and its " "ancestors are valid and potentially skip their script " "verification (0 to verify all, default: %s, testnet: %s)"), Params(CBaseChainParams::MAIN) .GetConsensus() .defaultAssumeValid.GetHex(), Params(CBaseChainParams::TESTNET) .GetConsensus() .defaultAssumeValid.GetHex())); strUsage += HelpMessageOpt( "-conf=", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME)); if (mode == HMM_BITCOIND) { #if HAVE_DECL_DAEMON strUsage += HelpMessageOpt( "-daemon", _("Run in the background as a daemon and accept commands")); #endif } strUsage += HelpMessageOpt("-datadir=", _("Specify data directory")); strUsage += HelpMessageOpt( "-dbcache=", strprintf( _("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache)); if (showDebug) strUsage += HelpMessageOpt( "-feefilter", strprintf("Tell other nodes to filter invs to us by " "our mempool min fee (default: %d)", DEFAULT_FEEFILTER)); strUsage += HelpMessageOpt( "-loadblock=", _("Imports blocks from external blk000??.dat file on startup")); strUsage += HelpMessageOpt( "-maxorphantx=", strprintf(_("Keep at most unconnectable " "transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS)); strUsage += HelpMessageOpt("-maxmempool=", strprintf(_("Keep the transaction memory pool " "below megabytes (default: %u)"), DEFAULT_MAX_MEMPOOL_SIZE)); strUsage += HelpMessageOpt("-mempoolexpiry=", strprintf(_("Do not keep transactions in the mempool " "longer than hours (default: %u)"), DEFAULT_MEMPOOL_EXPIRY)); strUsage += HelpMessageOpt( "-blockreconstructionextratxn=", strprintf(_("Extra transactions to keep in memory for compact block " "reconstructions (default: %u)"), DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN)); strUsage += HelpMessageOpt( "-par=", strprintf(_("Set the number of script verification threads (%u to %d, " "0 = auto, <0 = leave that many cores free, default: %d)"), -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS)); #ifndef WIN32 strUsage += HelpMessageOpt( "-pid=", strprintf(_("Specify pid file (default: %s)"), BITCOIN_PID_FILENAME)); #endif strUsage += HelpMessageOpt( "-prune=", strprintf( _("Reduce storage requirements by enabling pruning (deleting) of " "old blocks. This allows the pruneblockchain RPC to be called to " "delete specific blocks, and enables automatic pruning of old " "blocks if a target size in MiB is provided. This mode is " "incompatible with -txindex and -rescan. " "Warning: Reverting this setting requires re-downloading the " "entire blockchain. " "(default: 0 = disable pruning blocks, 1 = allow manual pruning " "via RPC, >%u = automatically prune block files to stay under " "the specified target size in MiB)"), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024)); strUsage += HelpMessageOpt( "-reindex-chainstate", _("Rebuild chain state from the currently indexed blocks")); strUsage += HelpMessageOpt("-reindex", _("Rebuild chain state and block index from " "the blk*.dat files on disk")); #ifndef WIN32 strUsage += HelpMessageOpt( "-sysperms", _("Create new files with system default permissions, instead of umask " "077 (only effective with disabled wallet functionality)")); #endif strUsage += HelpMessageOpt( "-txindex", strprintf(_("Maintain a full transaction index, used by " "the getrawtransaction rpc call (default: %d)"), DEFAULT_TXINDEX)); strUsage += HelpMessageGroup(_("Connection options:")); strUsage += HelpMessageOpt( "-addnode=", _("Add a node to connect to and attempt to keep the connection open")); strUsage += HelpMessageOpt( "-banscore=", strprintf( _("Threshold for disconnecting misbehaving peers (default: %u)"), DEFAULT_BANSCORE_THRESHOLD)); strUsage += HelpMessageOpt( "-bantime=", strprintf(_("Number of seconds to keep misbehaving " "peers from reconnecting (default: %u)"), DEFAULT_MISBEHAVING_BANTIME)); strUsage += HelpMessageOpt("-bind=", _("Bind to given address and always listen on " "it. Use [host]:port notation for IPv6")); strUsage += HelpMessageOpt("-connect=", _("Connect only to the specified node(s); -noconnect or " "-connect=0 alone to disable automatic connections")); strUsage += HelpMessageOpt("-discover", _("Discover own IP addresses (default: 1 when " "listening and no -externalip or -proxy)")); strUsage += HelpMessageOpt( "-dns", _("Allow DNS lookups for -addnode, -seednode and -connect") + " " + strprintf(_("(default: %d)"), DEFAULT_NAME_LOOKUP)); strUsage += HelpMessageOpt( "-dnsseed", _("Query for peer addresses via DNS lookup, if low on " "addresses (default: 1 unless -connect/-noconnect)")); strUsage += HelpMessageOpt("-externalip=", _("Specify your own public address")); strUsage += HelpMessageOpt( "-forcednsseed", strprintf( _("Always query for peer addresses via DNS lookup (default: %d)"), DEFAULT_FORCEDNSSEED)); strUsage += HelpMessageOpt("-listen", _("Accept connections from outside (default: " "1 if no -proxy or -connect/-noconnect)")); strUsage += HelpMessageOpt( "-listenonion", strprintf(_("Automatically create Tor hidden service (default: %d)"), DEFAULT_LISTEN_ONION)); strUsage += HelpMessageOpt( "-maxconnections=", strprintf(_("Maintain at most connections to peers (default: %u)"), DEFAULT_MAX_PEER_CONNECTIONS)); strUsage += HelpMessageOpt("-maxreceivebuffer=", strprintf(_("Maximum per-connection receive buffer, " "*1000 bytes (default: %u)"), DEFAULT_MAXRECEIVEBUFFER)); strUsage += HelpMessageOpt( "-maxsendbuffer=", strprintf(_("Maximum per-connection send buffer, " "*1000 bytes (default: %u)"), DEFAULT_MAXSENDBUFFER)); strUsage += HelpMessageOpt( "-maxtimeadjustment", strprintf(_("Maximum allowed median peer time offset adjustment. Local " "perspective of time may be influenced by peers forward or " "backward by this amount. (default: %u seconds)"), DEFAULT_MAX_TIME_ADJUSTMENT)); strUsage += HelpMessageOpt("-onion=", strprintf(_("Use separate SOCKS5 proxy to reach peers " "via Tor hidden services (default: %s)"), "-proxy")); strUsage += HelpMessageOpt( "-onlynet=", _("Only connect to nodes in network (ipv4, ipv6 or onion)")); strUsage += HelpMessageOpt("-permitbaremultisig", strprintf(_("Relay non-P2SH multisig (default: %d)"), DEFAULT_PERMIT_BAREMULTISIG)); strUsage += HelpMessageOpt( "-peerbloomfilters", strprintf(_("Support filtering of blocks and transaction with bloom " "filters (default: %d)"), DEFAULT_PEERBLOOMFILTERS)); strUsage += HelpMessageOpt( "-port=", strprintf( _("Listen for connections on (default: %u or testnet: %u)"), Params(CBaseChainParams::MAIN).GetDefaultPort(), Params(CBaseChainParams::TESTNET).GetDefaultPort())); strUsage += HelpMessageOpt("-proxy=", _("Connect through SOCKS5 proxy")); strUsage += HelpMessageOpt( "-proxyrandomize", strprintf(_("Randomize credentials for every proxy connection. This " "enables Tor stream isolation (default: %d)"), DEFAULT_PROXYRANDOMIZE)); strUsage += HelpMessageOpt( "-seednode=", _("Connect to a node to retrieve peer addresses, and disconnect")); strUsage += HelpMessageOpt( "-timeout=", strprintf(_("Specify connection timeout in " "milliseconds (minimum: 1, default: %d)"), DEFAULT_CONNECT_TIMEOUT)); strUsage += HelpMessageOpt("-torcontrol=:", strprintf(_("Tor control port to use if onion " "listening enabled (default: %s)"), DEFAULT_TOR_CONTROL)); strUsage += HelpMessageOpt("-torpassword=", _("Tor control port password (default: empty)")); #ifdef USE_UPNP #if USE_UPNP strUsage += HelpMessageOpt("-upnp", _("Use UPnP to map the listening port " "(default: 1 when listening and no -proxy)")); #else strUsage += HelpMessageOpt( "-upnp", strprintf(_("Use UPnP to map the listening port (default: %u)"), 0)); #endif #endif strUsage += HelpMessageOpt("-whitebind=", _("Bind to given address and whitelist peers connecting " "to it. Use [host]:port notation for IPv6")); strUsage += HelpMessageOpt( "-whitelist=", _("Whitelist peers connecting from the given IP address (e.g. 1.2.3.4) " "or CIDR notated network (e.g. 1.2.3.0/24). Can be specified " "multiple times.") + " " + _("Whitelisted peers cannot be DoS banned and their " "transactions are always relayed, even if they are already " "in the mempool, useful e.g. for a gateway")); strUsage += HelpMessageOpt( "-whitelistrelay", strprintf(_("Accept relayed transactions received from whitelisted " "peers even when not relaying transactions (default: %d)"), DEFAULT_WHITELISTRELAY)); strUsage += HelpMessageOpt( "-whitelistforcerelay", strprintf(_("Force relay of transactions from whitelisted peers even " "if they violate local relay policy (default: %d)"), DEFAULT_WHITELISTFORCERELAY)); strUsage += HelpMessageOpt( "-maxuploadtarget=", strprintf(_("Tries to keep outbound traffic under the given target (in " "MiB per 24h), 0 = no limit (default: %d)"), DEFAULT_MAX_UPLOAD_TARGET)); #ifdef ENABLE_WALLET strUsage += CWallet::GetWalletHelpString(showDebug); #endif #if ENABLE_ZMQ strUsage += HelpMessageGroup(_("ZeroMQ notification options:")); strUsage += HelpMessageOpt("-zmqpubhashblock=
", _("Enable publish hash block in
")); strUsage += HelpMessageOpt("-zmqpubhashtx=
", _("Enable publish hash transaction in
")); strUsage += HelpMessageOpt("-zmqpubrawblock=
", _("Enable publish raw block in
")); strUsage += HelpMessageOpt("-zmqpubrawtx=
", _("Enable publish raw transaction in
")); #endif strUsage += HelpMessageGroup(_("Debugging/Testing options:")); strUsage += HelpMessageOpt("-uacomment=", _("Append comment to the user agent string")); if (showDebug) { strUsage += HelpMessageOpt( "-checkblocks=", strprintf( _("How many blocks to check at startup (default: %u, 0 = all)"), DEFAULT_CHECKBLOCKS)); strUsage += HelpMessageOpt("-checklevel=", strprintf(_("How thorough the block verification of " "-checkblocks is (0-4, default: %u)"), DEFAULT_CHECKLEVEL)); strUsage += HelpMessageOpt( "-checkblockindex", strprintf( "Do a full consistency check for mapBlockIndex, " "setBlockIndexCandidates, chainActive and mapBlocksUnlinked " "occasionally. Also sets -checkmempool (default: %d)", Params(CBaseChainParams::MAIN).DefaultConsistencyChecks())); strUsage += HelpMessageOpt( "-checkmempool=", strprintf( "Run checks every transactions (default: %d)", Params(CBaseChainParams::MAIN).DefaultConsistencyChecks())); strUsage += HelpMessageOpt( "-checkpoints", strprintf("Disable expensive verification for " "known chain history (default: %d)", DEFAULT_CHECKPOINTS_ENABLED)); strUsage += HelpMessageOpt( "-disablesafemode", strprintf("Disable safemode, override a real " "safe mode event (default: %d)", DEFAULT_DISABLE_SAFEMODE)); strUsage += HelpMessageOpt( "-testsafemode", strprintf("Force safe mode (default: %d)", DEFAULT_TESTSAFEMODE)); strUsage += HelpMessageOpt("-dropmessagestest=", "Randomly drop 1 of every network messages"); strUsage += HelpMessageOpt("-fuzzmessagestest=", "Randomly fuzz 1 of every network messages"); strUsage += HelpMessageOpt( "-stopafterblockimport", strprintf( "Stop running after importing blocks from disk (default: %d)", DEFAULT_STOPAFTERBLOCKIMPORT)); strUsage += HelpMessageOpt( "-limitancestorcount=", strprintf("Do not accept transactions if number of in-mempool " "ancestors is or more (default: %u)", DEFAULT_ANCESTOR_LIMIT)); strUsage += HelpMessageOpt("-limitancestorsize=", strprintf("Do not accept transactions whose size " "with all in-mempool ancestors exceeds " " kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT)); strUsage += HelpMessageOpt( "-limitdescendantcount=", strprintf("Do not accept transactions if any ancestor would have " " or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT)); strUsage += HelpMessageOpt( "-limitdescendantsize=", strprintf("Do not accept transactions if any ancestor would have " "more than kilobytes of in-mempool descendants " "(default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT)); strUsage += HelpMessageOpt("-bip9params=deployment:start:end", "Use given start/end times for specified " "BIP9 deployment (regtest-only)"); } std::string debugCategories = "addrman, alert, bench, cmpctblock, coindb, db, http, libevent, lock, " "mempool, mempoolrej, net, proxy, prune, rand, reindex, rpc, " "selectcoins, tor, zmq"; // Don't translate these and qt below if (mode == HMM_BITCOIN_QT) debugCategories += ", qt"; strUsage += HelpMessageOpt( "-debug=", strprintf(_("Output debugging information (default: %u, supplying " " is optional)"), 0) + ". " + _("If is not supplied or if = 1, " "output all debugging information.") + _(" can be:") + " " + debugCategories + "."); if (showDebug) strUsage += HelpMessageOpt( "-nodebug", "Turn off debugging messages, same as -debug=0"); strUsage += HelpMessageOpt( "-help-debug", _("Show all debugging options (usage: --help -help-debug)")); strUsage += HelpMessageOpt( "-logips", strprintf(_("Include IP addresses in debug output (default: %d)"), DEFAULT_LOGIPS)); strUsage += HelpMessageOpt( "-logtimestamps", strprintf(_("Prepend debug output with timestamp (default: %d)"), DEFAULT_LOGTIMESTAMPS)); if (showDebug) { strUsage += HelpMessageOpt( "-logtimemicros", strprintf( "Add microsecond precision to debug timestamps (default: %d)", DEFAULT_LOGTIMEMICROS)); strUsage += HelpMessageOpt( "-mocktime=", "Replace actual time with seconds since epoch (default: 0)"); strUsage += HelpMessageOpt( "-limitfreerelay=", strprintf("Continuously rate-limit free transactions to *1000 " "bytes per minute (default: %u)", DEFAULT_LIMITFREERELAY)); strUsage += HelpMessageOpt("-relaypriority", strprintf("Require high priority for relaying free " "or low-fee transactions (default: %d)", DEFAULT_RELAYPRIORITY)); strUsage += HelpMessageOpt( "-maxsigcachesize=", strprintf("Limit size of signature cache to MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE)); strUsage += HelpMessageOpt( "-maxscriptcachesize=", strprintf("Limit size of script cache to MiB (default: %u)", DEFAULT_MAX_SCRIPT_CACHE_SIZE)); strUsage += HelpMessageOpt( "-maxtipage=", strprintf("Maximum tip age in seconds to consider node in initial " "block download (default: %u)", DEFAULT_MAX_TIP_AGE)); } strUsage += HelpMessageOpt( "-minrelaytxfee=", strprintf( _("Fees (in %s/kB) smaller than this are considered zero fee for " "relaying, mining and transaction creation (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE))); strUsage += HelpMessageOpt( "-maxtxfee=", strprintf(_("Maximum total fees (in %s) to use in a single wallet " "transaction or raw transaction; setting this too low may " "abort large transactions (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE))); strUsage += HelpMessageOpt( "-printtoconsole", _("Send trace/debug info to console instead of debug.log file")); if (showDebug) { strUsage += HelpMessageOpt( "-printpriority", strprintf("Log transaction priority and fee per " "kB when mining blocks (default: %d)", DEFAULT_PRINTPRIORITY)); } strUsage += HelpMessageOpt("-shrinkdebugfile", _("Shrink debug.log file on client startup " "(default: 1 when no -debug)")); AppendParamsHelpMessages(strUsage, showDebug); strUsage += HelpMessageGroup(_("Node relay options:")); if (showDebug) { strUsage += HelpMessageOpt( "-acceptnonstdtxn", strprintf( "Relay and mine \"non-standard\" transactions (%sdefault: %d)", "testnet/regtest only; ", !Params(CBaseChainParams::TESTNET).RequireStandard())); strUsage += HelpMessageOpt("-excessiveblocksize=", strprintf(_("Do not accept blocks larger than this " "limit, in bytes (default: %d)"), LEGACY_MAX_BLOCK_SIZE)); strUsage += HelpMessageOpt( "-incrementalrelayfee=", strprintf( "Fee rate (in %s/kB) used to define cost of relay, used for " "mempool limiting and BIP 125 replacement. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE))); strUsage += HelpMessageOpt( "-dustrelayfee=", strprintf("Fee rate (in %s/kB) used to defined dust, the value of " "an output such that it will cost about 1/3 of its value " "in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE))); } strUsage += HelpMessageOpt("-bytespersigop", strprintf(_("Equivalent bytes per sigop in transactions " "for relay and mining (default: %u)"), DEFAULT_BYTES_PER_SIGOP)); strUsage += HelpMessageOpt( "-datacarrier", strprintf(_("Relay and mine data carrier transactions (default: %d)"), DEFAULT_ACCEPT_DATACARRIER)); strUsage += HelpMessageOpt( "-datacarriersize", strprintf(_("Maximum size of data in data carrier transactions we " "relay and mine (default: %u)"), MAX_OP_RETURN_RELAY)); strUsage += HelpMessageGroup(_("Block creation options:")); strUsage += HelpMessageOpt( "-blockmaxsize=", strprintf(_("Set maximum block size in bytes (default: %d)"), DEFAULT_MAX_GENERATED_BLOCK_SIZE)); strUsage += HelpMessageOpt( "-blockprioritypercentage=", strprintf(_("Set maximum percentage of a block reserved to " "high-priority/low-fee transactions (default: %d)"), DEFAULT_BLOCK_PRIORITY_PERCENTAGE)); strUsage += HelpMessageOpt( "-blockmintxfee=", strprintf(_("Set lowest fee rate (in %s/kB) for transactions to be " "included in block creation. (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE))); if (showDebug) strUsage += HelpMessageOpt("-blockversion=", "Override block version to test forking scenarios"); strUsage += HelpMessageGroup(_("RPC server options:")); strUsage += HelpMessageOpt("-server", _("Accept command line and JSON-RPC commands")); strUsage += HelpMessageOpt( "-rest", strprintf(_("Accept public REST requests (default: %d)"), DEFAULT_REST_ENABLE)); strUsage += HelpMessageOpt( "-rpcbind=", _("Bind to given address to listen for JSON-RPC connections. Use " "[host]:port notation for IPv6. This option can be specified " "multiple times (default: bind to all interfaces)")); strUsage += HelpMessageOpt("-rpccookiefile=", _("Location of the auth cookie (default: data dir)")); strUsage += HelpMessageOpt("-rpcuser=", _("Username for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcpassword=", _("Password for JSON-RPC connections")); strUsage += HelpMessageOpt( "-rpcauth=", _("Username and hashed password for JSON-RPC connections. The field " " comes in the format: :$. A canonical " "python script is included in share/rpcuser. The client then " "connects normally using the " "rpcuser=/rpcpassword= pair of arguments. This " "option can be specified multiple times")); strUsage += HelpMessageOpt( "-rpcport=", strprintf(_("Listen for JSON-RPC connections on (default: %u or " "testnet: %u)"), BaseParams(CBaseChainParams::MAIN).RPCPort(), BaseParams(CBaseChainParams::TESTNET).RPCPort())); strUsage += HelpMessageOpt( "-rpcallowip=", _("Allow JSON-RPC connections from specified source. Valid for " "are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. " "1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This " "option can be specified multiple times")); strUsage += HelpMessageOpt( "-rpcthreads=", strprintf( _("Set the number of threads to service RPC calls (default: %d)"), DEFAULT_HTTP_THREADS)); if (showDebug) { strUsage += HelpMessageOpt( "-rpcworkqueue=", strprintf("Set the depth of the work queue to " "service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE)); strUsage += HelpMessageOpt( "-rpcservertimeout=", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT)); } return strUsage; } std::string LicenseInfo() { const std::string URL_SOURCE_CODE = ""; const std::string URL_WEBSITE = ""; return CopyrightHolders( strprintf(_("Copyright (C) %i-%i"), 2009, COPYRIGHT_YEAR) + " ") + "\n" + "\n" + strprintf(_("Please contribute if you find %s useful. " "Visit %s for further information about the software."), PACKAGE_NAME, URL_WEBSITE) + "\n" + strprintf(_("The source code is available from %s."), URL_SOURCE_CODE) + "\n" + "\n" + _("This is experimental software.") + "\n" + strprintf(_("Distributed under the MIT software license, see the " "accompanying file %s or %s"), "COPYING", "") + "\n" + "\n" + strprintf(_("This product includes software developed by the " "OpenSSL Project for use in the OpenSSL Toolkit %s and " "cryptographic software written by Eric Young and UPnP " "software written by Thomas Bernard."), "") + "\n"; } static void BlockNotifyCallback(bool initialSync, const CBlockIndex *pBlockIndex) { if (initialSync || !pBlockIndex) return; std::string strCmd = GetArg("-blocknotify", ""); boost::replace_all(strCmd, "%s", pBlockIndex->GetBlockHash().GetHex()); boost::thread t(runCommand, strCmd); // thread runs free } static bool fHaveGenesis = false; static boost::mutex cs_GenesisWait; static CConditionVariable condvar_GenesisWait; static void BlockNotifyGenesisWait(bool, const CBlockIndex *pBlockIndex) { if (pBlockIndex != nullptr) { { boost::unique_lock lock_GenesisWait(cs_GenesisWait); fHaveGenesis = true; } condvar_GenesisWait.notify_all(); } } struct CImportingNow { CImportingNow() { assert(fImporting == false); fImporting = true; } ~CImportingNow() { assert(fImporting == true); fImporting = false; } }; // If we're using -prune with -reindex, then delete block files that will be // ignored by the reindex. Since reindexing works by starting at block file 0 // and looping until a blockfile is missing, do the same here to delete any // later block files after a gap. Also delete all rev files since they'll be // rewritten by the reindex anyway. This ensures that vinfoBlockFile is in sync // with what's actually on disk by the time we start downloading, so that // pruning works correctly. void CleanupBlockRevFiles() { std::map mapBlockFiles; // Glob all blk?????.dat and rev?????.dat files from the blocks directory. // Remove the rev files immediately and insert the blk file paths into an // ordered map keyed by block file index. LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for " "-reindex with -prune\n"); boost::filesystem::path blocksdir = GetDataDir() / "blocks"; for (boost::filesystem::directory_iterator it(blocksdir); it != boost::filesystem::directory_iterator(); it++) { if (is_regular_file(*it) && it->path().filename().string().length() == 12 && it->path().filename().string().substr(8, 4) == ".dat") { if (it->path().filename().string().substr(0, 3) == "blk") mapBlockFiles[it->path().filename().string().substr(3, 5)] = it->path(); else if (it->path().filename().string().substr(0, 3) == "rev") remove(it->path()); } } // Remove all block files that aren't part of a contiguous set starting at // zero by walking the ordered map (keys are block file indices) by keeping // a separate counter. Once we hit a gap (or if 0 doesn't exist) start // removing block files. int nContigCounter = 0; for (const std::pair &item : mapBlockFiles) { if (atoi(item.first) == nContigCounter) { nContigCounter++; continue; } remove(item.second); } } void ThreadImport(const Config &config, std::vector vImportFiles) { RenameThread("bitcoin-loadblk"); { CImportingNow imp; // -reindex if (fReindex) { int nFile = 0; while (true) { CDiskBlockPos pos(nFile, 0); if (!boost::filesystem::exists(GetBlockPosFilename(pos, "blk"))) break; // No block files left to reindex FILE *file = OpenBlockFile(pos, true); if (!file) break; // This error is logged in OpenBlockFile LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile); LoadExternalBlockFile(config, file, &pos); nFile++; } pblocktree->WriteReindexing(false); fReindex = false; LogPrintf("Reindexing finished\n"); // To avoid ending up in a situation without genesis block, re-try // initializing (no-op if reindexing worked): InitBlockIndex(config); } // hardcoded $DATADIR/bootstrap.dat boost::filesystem::path pathBootstrap = GetDataDir() / "bootstrap.dat"; if (boost::filesystem::exists(pathBootstrap)) { FILE *file = fopen(pathBootstrap.string().c_str(), "rb"); if (file) { boost::filesystem::path pathBootstrapOld = GetDataDir() / "bootstrap.dat.old"; LogPrintf("Importing bootstrap.dat...\n"); LoadExternalBlockFile(config, file); RenameOver(pathBootstrap, pathBootstrapOld); } else { LogPrintf("Warning: Could not open bootstrap file %s\n", pathBootstrap.string()); } } // -loadblock= for (const boost::filesystem::path &path : vImportFiles) { FILE *file = fopen(path.string().c_str(), "rb"); if (file) { LogPrintf("Importing blocks file %s...\n", path.string()); LoadExternalBlockFile(config, file); } else { LogPrintf("Warning: Could not open blocks file %s\n", path.string()); } } // scan for better chains in the block chain database, that are not yet // connected in the active best chain CValidationState state; if (!ActivateBestChain(config, state)) { LogPrintf("Failed to connect best block"); StartShutdown(); } if (GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) { LogPrintf("Stopping after block import\n"); StartShutdown(); } } // End scope of CImportingNow LoadMempool(config); fDumpMempoolLater = !fRequestShutdown; } /** Sanity checks * Ensure that Bitcoin is running in a usable environment with all * necessary library support. */ bool InitSanityCheck(void) { if (!ECC_InitSanityCheck()) { InitError( "Elliptic curve cryptography sanity check failure. Aborting."); return false; } if (!glibc_sanity_test() || !glibcxx_sanity_test()) { return false; } if (!Random_SanityCheck()) { InitError("OS cryptographic RNG sanity check failure. Aborting."); return false; } return true; } static bool AppInitServers(Config &config, boost::thread_group &threadGroup) { RPCServer::OnStarted(&OnRPCStarted); RPCServer::OnStopped(&OnRPCStopped); RPCServer::OnPreCommand(&OnRPCPreCommand); if (!InitHTTPServer(config)) return false; if (!StartRPC()) return false; if (!StartHTTPRPC()) return false; if (GetBoolArg("-rest", DEFAULT_REST_ENABLE) && !StartREST()) return false; if (!StartHTTPServer()) return false; return true; } // Parameter interaction based on rules void InitParameterInteraction() { // when specifying an explicit binding address, you want to listen on it // even when -connect or -proxy is specified. if (IsArgSet("-bind")) { if (SoftSetBoolArg("-listen", true)) LogPrintf( "%s: parameter interaction: -bind set -> setting -listen=1\n", __func__); } if (IsArgSet("-whitebind")) { if (SoftSetBoolArg("-listen", true)) LogPrintf("%s: parameter interaction: -whitebind set -> setting " "-listen=1\n", __func__); } if (mapMultiArgs.count("-connect") && mapMultiArgs.at("-connect").size() > 0) { // when only connecting to trusted nodes, do not seed via DNS, or listen // by default. if (SoftSetBoolArg("-dnsseed", false)) LogPrintf("%s: parameter interaction: -connect set -> setting " "-dnsseed=0\n", __func__); if (SoftSetBoolArg("-listen", false)) LogPrintf("%s: parameter interaction: -connect set -> setting " "-listen=0\n", __func__); } if (IsArgSet("-proxy")) { // to protect privacy, do not listen by default if a default proxy // server is specified. if (SoftSetBoolArg("-listen", false)) LogPrintf( "%s: parameter interaction: -proxy set -> setting -listen=0\n", __func__); // to protect privacy, do not use UPNP when a proxy is set. The user may // still specify -listen=1 to listen locally, so don't rely on this // happening through -listen below. if (SoftSetBoolArg("-upnp", false)) LogPrintf( "%s: parameter interaction: -proxy set -> setting -upnp=0\n", __func__); // to protect privacy, do not discover addresses by default if (SoftSetBoolArg("-discover", false)) LogPrintf("%s: parameter interaction: -proxy set -> setting " "-discover=0\n", __func__); } if (!GetBoolArg("-listen", DEFAULT_LISTEN)) { // do not map ports or try to retrieve public IP when not listening // (pointless) if (SoftSetBoolArg("-upnp", false)) LogPrintf( "%s: parameter interaction: -listen=0 -> setting -upnp=0\n", __func__); if (SoftSetBoolArg("-discover", false)) LogPrintf( "%s: parameter interaction: -listen=0 -> setting -discover=0\n", __func__); if (SoftSetBoolArg("-listenonion", false)) LogPrintf("%s: parameter interaction: -listen=0 -> setting " "-listenonion=0\n", __func__); } if (IsArgSet("-externalip")) { // if an explicit public IP is specified, do not try to find others if (SoftSetBoolArg("-discover", false)) LogPrintf("%s: parameter interaction: -externalip set -> setting " "-discover=0\n", __func__); } // disable whitelistrelay in blocksonly mode if (GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)) { if (SoftSetBoolArg("-whitelistrelay", false)) LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting " "-whitelistrelay=0\n", __func__); } // Forcing relay from whitelisted hosts implies we will accept relays from // them in the first place. if (GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) { if (SoftSetBoolArg("-whitelistrelay", true)) LogPrintf("%s: parameter interaction: -whitelistforcerelay=1 -> " "setting -whitelistrelay=1\n", __func__); } } static std::string ResolveErrMsg(const char *const optname, const std::string &strBind) { return strprintf(_("Cannot resolve -%s address: '%s'"), optname, strBind); } void InitLogging() { fPrintToConsole = GetBoolArg("-printtoconsole", false); fLogTimestamps = GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS); fLogTimeMicros = GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS); fLogIPs = GetBoolArg("-logips", DEFAULT_LOGIPS); LogPrintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); LogPrintf("%s version %s\n", CLIENT_NAME, FormatFullVersion()); } namespace { // Variables internal to initialization process only ServiceFlags nRelevantServices = NODE_NETWORK; int nMaxConnections; int nUserMaxConnections; int nFD; ServiceFlags nLocalServices = NODE_NETWORK; } // namespace [[noreturn]] static void new_handler_terminate() { // Rather than throwing std::bad-alloc if allocation fails, terminate // immediately to (try to) avoid chain corruption. Since LogPrintf may // itself allocate memory, set the handler directly to terminate first. std::set_new_handler(std::terminate); LogPrintf("Error: Out of memory. Terminating.\n"); // The log was successful, terminate now. std::terminate(); }; bool AppInitBasicSetup() { // Step 1: setup #ifdef _MSC_VER // Turn off Microsoft heap dump noise _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, 0)); #endif #if _MSC_VER >= 1400 // Disable confusing "helpful" text message on abort, Ctrl-C _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); #endif #ifdef WIN32 // Enable Data Execution Prevention (DEP) // Minimum supported OS versions: WinXP SP3, WinVista >= SP1, Win Server 2008 // A failure is non-critical and needs no further attention! #ifndef PROCESS_DEP_ENABLE // We define this here, because GCCs winbase.h limits this to _WIN32_WINNT >= // 0x0601 (Windows 7), which is not correct. Can be removed, when GCCs winbase.h // is fixed! #define PROCESS_DEP_ENABLE 0x00000001 #endif typedef BOOL(WINAPI * PSETPROCDEPPOL)(DWORD); PSETPROCDEPPOL setProcDEPPol = (PSETPROCDEPPOL)GetProcAddress( GetModuleHandleA("Kernel32.dll"), "SetProcessDEPPolicy"); if (setProcDEPPol != nullptr) setProcDEPPol(PROCESS_DEP_ENABLE); #endif if (!SetupNetworking()) return InitError("Initializing networking failed"); #ifndef WIN32 if (!GetBoolArg("-sysperms", false)) { umask(077); } // Clean shutdown on SIGTERM struct sigaction sa; sa.sa_handler = HandleSIGTERM; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGTERM, &sa, nullptr); sigaction(SIGINT, &sa, nullptr); // Reopen debug.log on SIGHUP struct sigaction sa_hup; sa_hup.sa_handler = HandleSIGHUP; sigemptyset(&sa_hup.sa_mask); sa_hup.sa_flags = 0; sigaction(SIGHUP, &sa_hup, nullptr); // Ignore SIGPIPE, otherwise it will bring the daemon down if the client // closes unexpectedly signal(SIGPIPE, SIG_IGN); #endif std::set_new_handler(new_handler_terminate); return true; } bool AppInitParameterInteraction(Config &config) { const CChainParams &chainparams = Params(); // Step 2: parameter interactions // also see: InitParameterInteraction() // if using block pruning, then disallow txindex if (GetArg("-prune", 0)) { if (GetBoolArg("-txindex", DEFAULT_TXINDEX)) return InitError(_("Prune mode is incompatible with -txindex.")); } // if space reserved for high priority transactions is misconfigured // stop program execution and warn the user with a proper error message const int64_t blkprio = GetArg("-blockprioritypercentage", DEFAULT_BLOCK_PRIORITY_PERCENTAGE); if (!config.SetBlockPriorityPercentage(blkprio)) { return InitError(_("Block priority percentage has to belong to the " "[0..100] interval.")); } // Make sure enough file descriptors are available int nBind = std::max( (mapMultiArgs.count("-bind") ? mapMultiArgs.at("-bind").size() : 0) + (mapMultiArgs.count("-whitebind") ? mapMultiArgs.at("-whitebind").size() : 0), size_t(1)); nUserMaxConnections = GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS); nMaxConnections = std::max(nUserMaxConnections, 0); // Trim requested connection counts, to fit into system limitations nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS)), 0); nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS + MAX_ADDNODE_CONNECTIONS); if (nFD < MIN_CORE_FILEDESCRIPTORS) return InitError(_("Not enough file descriptors available.")); nMaxConnections = std::min(nFD - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS, nMaxConnections); if (nMaxConnections < nUserMaxConnections) InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, " "because of system limitations."), nUserMaxConnections, nMaxConnections)); // Step 3: parameter-to-internal-flags fDebug = mapMultiArgs.count("-debug"); // Special-case: if -debug=0/-nodebug is set, turn off debugging messages if (fDebug) { const std::vector &categories = mapMultiArgs.at("-debug"); if (GetBoolArg("-nodebug", false) || find(categories.begin(), categories.end(), std::string("0")) != categories.end()) fDebug = false; } // Check for -debugnet if (GetBoolArg("-debugnet", false)) InitWarning( _("Unsupported argument -debugnet ignored, use -debug=net.")); // Check for -socks - as this is a privacy risk to continue, exit here if (IsArgSet("-socks")) return InitError( _("Unsupported argument -socks found. Setting SOCKS version isn't " "possible anymore, only SOCKS5 proxies are supported.")); // Check for -tor - as this is a privacy risk to continue, exit here if (GetBoolArg("-tor", false)) return InitError(_("Unsupported argument -tor found, use -onion.")); if (GetBoolArg("-benchmark", false)) InitWarning( _("Unsupported argument -benchmark ignored, use -debug=bench.")); if (GetBoolArg("-whitelistalwaysrelay", false)) InitWarning(_("Unsupported argument -whitelistalwaysrelay ignored, use " "-whitelistrelay and/or -whitelistforcerelay.")); if (IsArgSet("-blockminsize")) InitWarning("Unsupported argument -blockminsize ignored."); // Checkmempool and checkblockindex default to true in regtest mode int ratio = std::min( std::max(GetArg("-checkmempool", chainparams.DefaultConsistencyChecks() ? 1 : 0), 0), 1000000); if (ratio != 0) { mempool.setSanityCheck(1.0 / ratio); } fCheckBlockIndex = GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks()); fCheckpointsEnabled = GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED); hashAssumeValid = uint256S( GetArg("-assumevalid", chainparams.GetConsensus().defaultAssumeValid.GetHex())); if (!hashAssumeValid.IsNull()) LogPrintf("Assuming ancestors of block %s have valid signatures.\n", hashAssumeValid.GetHex()); else LogPrintf("Validating signatures for all blocks.\n"); // mempool limits int64_t nMempoolSizeMax = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; int64_t nMempoolSizeMin = GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000 * 40; if (nMempoolSizeMax < 0 || nMempoolSizeMax < nMempoolSizeMin) return InitError(strprintf(_("-maxmempool must be at least %d MB"), std::ceil(nMempoolSizeMin / 1000000.0))); // Incremental relay fee sets the minimimum feerate increase necessary for // BIP 125 replacement in the mempool and the amount the mempool min fee // increases above the feerate of txs evicted due to mempool limiting. if (IsArgSet("-incrementalrelayfee")) { Amount n = 0; if (!ParseMoney(GetArg("-incrementalrelayfee", ""), n)) return InitError(AmountErrMsg("incrementalrelayfee", GetArg("-incrementalrelayfee", ""))); incrementalRelayFee = CFeeRate(n); } // -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency nScriptCheckThreads = GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS); if (nScriptCheckThreads <= 0) nScriptCheckThreads += GetNumCores(); if (nScriptCheckThreads <= 1) nScriptCheckThreads = 0; else if (nScriptCheckThreads > MAX_SCRIPTCHECK_THREADS) nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS; // Configure excessive block size. const uint64_t nProposedExcessiveBlockSize = GetArg("-excessiveblocksize", DEFAULT_MAX_BLOCK_SIZE); if (!config.SetMaxBlockSize(nProposedExcessiveBlockSize)) { return InitError( _("Excessive block size must be > 1,000,000 bytes (1MB)")); } // Check blockmaxsize does not exceed maximum accepted block size. const uint64_t nProposedMaxGeneratedBlockSize = GetArg("-blockmaxsize", DEFAULT_MAX_GENERATED_BLOCK_SIZE); if (nProposedMaxGeneratedBlockSize > config.GetMaxBlockSize()) { auto msg = _("Max generated block size (blockmaxsize) cannot exceed " "the excessive block size (excessiveblocksize)"); return InitError(msg); } // block pruning; get the amount of disk space (in MiB) to allot for block & // undo files int64_t nPruneArg = GetArg("-prune", 0); if (nPruneArg < 0) { return InitError( _("Prune cannot be configured with a negative value.")); } nPruneTarget = (uint64_t)nPruneArg * 1024 * 1024; if (nPruneArg == 1) { // manual pruning: -prune=1 LogPrintf("Block pruning enabled. Use RPC call " "pruneblockchain(height) to manually prune block and undo " "files.\n"); nPruneTarget = std::numeric_limits::max(); fPruneMode = true; } else if (nPruneTarget) { if (nPruneTarget < MIN_DISK_SPACE_FOR_BLOCK_FILES) { return InitError( strprintf(_("Prune configured below the minimum of %d MiB. " "Please use a higher number."), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024)); } LogPrintf("Prune configured to target %uMiB on disk for block and undo " "files.\n", nPruneTarget / 1024 / 1024); fPruneMode = true; } RegisterAllRPCCommands(tableRPC); #ifdef ENABLE_WALLET RegisterWalletRPCCommands(tableRPC); RegisterDumpRPCCommands(tableRPC); #endif nConnectTimeout = GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT); if (nConnectTimeout <= 0) nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; // Fee-per-kilobyte amount considered the same as "free". If you are mining, // be careful setting this: if you set it to zero then a transaction spammer // can cheaply fill blocks using 1-satoshi-fee transactions. It should be // set above the real cost to you of processing a transaction. if (IsArgSet("-minrelaytxfee")) { Amount n = 0; if (!ParseMoney(GetArg("-minrelaytxfee", ""), n) || 0 == n) return InitError( AmountErrMsg("minrelaytxfee", GetArg("-minrelaytxfee", ""))); // High fee check is done afterward in CWallet::ParameterInteraction() ::minRelayTxFee = CFeeRate(n); } else if (incrementalRelayFee > ::minRelayTxFee) { // Allow only setting incrementalRelayFee to control both ::minRelayTxFee = incrementalRelayFee; LogPrintf( "Increasing minrelaytxfee to %s to match incrementalrelayfee\n", ::minRelayTxFee.ToString()); } // Sanity check argument for min fee for including tx in block // TODO: Harmonize which arguments need sanity checking and where that // happens. if (IsArgSet("-blockmintxfee")) { Amount n = 0; if (!ParseMoney(GetArg("-blockmintxfee", ""), n)) return InitError( AmountErrMsg("blockmintxfee", GetArg("-blockmintxfee", ""))); } // Feerate used to define dust. Shouldn't be changed lightly as old // implementations may inadvertently create non-standard transactions. if (IsArgSet("-dustrelayfee")) { Amount n = 0; if (!ParseMoney(GetArg("-dustrelayfee", ""), n) || 0 == n) return InitError( AmountErrMsg("dustrelayfee", GetArg("-dustrelayfee", ""))); dustRelayFee = CFeeRate(n); } fRequireStandard = !GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard()); if (chainparams.RequireStandard() && !fRequireStandard) return InitError( strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString())); nBytesPerSigOp = GetArg("-bytespersigop", nBytesPerSigOp); #ifdef ENABLE_WALLET if (!CWallet::ParameterInteraction()) return false; #endif fIsBareMultisigStd = GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG); fAcceptDatacarrier = GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER); nMaxDatacarrierBytes = GetArg("-datacarriersize", nMaxDatacarrierBytes); // Option to startup with mocktime set (used for regression testing): SetMockTime(GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op if (GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS)) nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM); // Signal Bitcoin Cash support. // TODO: remove some time after the hardfork when no longer needed // to differentiate the network nodes. nLocalServices = ServiceFlags(nLocalServices | NODE_BITCOIN_CASH); // Preferentially keep peers which service NODE_BITCOIN_CASH nRelevantServices = ServiceFlags(nRelevantServices | NODE_BITCOIN_CASH); nMaxTipAge = GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE); if (mapMultiArgs.count("-bip9params")) { // Allow overriding BIP9 parameters for testing if (!chainparams.MineBlocksOnDemand()) { return InitError( "BIP9 parameters may only be overridden on regtest."); } const std::vector &deployments = mapMultiArgs.at("-bip9params"); for (auto i : deployments) { std::vector vDeploymentParams; boost::split(vDeploymentParams, i, boost::is_any_of(":")); if (vDeploymentParams.size() != 3) { return InitError("BIP9 parameters malformed, expecting " "deployment:start:end"); } int64_t nStartTime, nTimeout; if (!ParseInt64(vDeploymentParams[1], &nStartTime)) { return InitError( strprintf("Invalid nStartTime (%s)", vDeploymentParams[1])); } if (!ParseInt64(vDeploymentParams[2], &nTimeout)) { return InitError( strprintf("Invalid nTimeout (%s)", vDeploymentParams[2])); } bool found = false; for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { if (vDeploymentParams[0].compare( VersionBitsDeploymentInfo[j].name) == 0) { UpdateRegtestBIP9Parameters(Consensus::DeploymentPos(j), nStartTime, nTimeout); found = true; LogPrintf("Setting BIP9 activation parameters for %s to " "start=%ld, timeout=%ld\n", vDeploymentParams[0], nStartTime, nTimeout); break; } } if (!found) { return InitError( strprintf("Invalid deployment (%s)", vDeploymentParams[0])); } } } + return true; } static bool LockDataDirectory(bool probeOnly) { std::string strDataDir = GetDataDir().string(); // Make sure only a single Bitcoin process is using the data directory. boost::filesystem::path pathLockFile = GetDataDir() / ".lock"; // empty lock file; created if it doesn't exist. FILE *file = fopen(pathLockFile.string().c_str(), "a"); if (file) fclose(file); try { static boost::interprocess::file_lock lock( pathLockFile.string().c_str()); if (!lock.try_lock()) { return InitError( strprintf(_("Cannot obtain a lock on data directory %s. %s is " "probably already running."), strDataDir, _(PACKAGE_NAME))); } if (probeOnly) { lock.unlock(); } } catch (const boost::interprocess::interprocess_exception &e) { return InitError(strprintf(_("Cannot obtain a lock on data directory " "%s. %s is probably already running.") + " %s.", strDataDir, _(PACKAGE_NAME), e.what())); } return true; } bool AppInitSanityChecks() { // Step 4: sanity checks // Initialize elliptic curve code ECC_Start(); globalVerifyHandle.reset(new ECCVerifyHandle()); // Sanity check if (!InitSanityCheck()) return InitError(strprintf( _("Initialization sanity check failed. %s is shutting down."), _(PACKAGE_NAME))); // Probe the data directory lock to give an early error message, if possible return LockDataDirectory(true); } bool AppInitMain(Config &config, boost::thread_group &threadGroup, CScheduler &scheduler) { const CChainParams &chainparams = Params(); // Step 4a: application initialization // After daemonization get the data directory lock again and hold on to it // until exit. This creates a slight window for a race condition to happen, // however this condition is harmless: it will at most make us exit without // printing a message to console. if (!LockDataDirectory(false)) { // Detailed error printed inside LockDataDirectory return false; } #ifndef WIN32 CreatePidFile(GetPidFile(), getpid()); #endif if (GetBoolArg("-shrinkdebugfile", !fDebug)) { // Do this first since it both loads a bunch of debug.log into memory, // and because this needs to happen before any other debug.log printing. ShrinkDebugFile(); } if (fPrintToDebugLog) OpenDebugLog(); if (!fLogTimestamps) LogPrintf("Startup time: %s\n", DateTimeStrFormat("%Y-%m-%d %H:%M:%S", GetTime())); LogPrintf("Default data directory %s\n", GetDefaultDataDir().string()); LogPrintf("Using data directory %s\n", GetDataDir().string()); LogPrintf("Using config file %s\n", GetConfigFile(GetArg("-conf", BITCOIN_CONF_FILENAME)).string()); LogPrintf("Using at most %i automatic connections (%i file descriptors " "available)\n", nMaxConnections, nFD); InitSignatureCache(); InitScriptExecutionCache(); LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads); if (nScriptCheckThreads) { for (int i = 0; i < nScriptCheckThreads - 1; i++) { threadGroup.create_thread(&ThreadScriptCheck); } } // Start the lightweight task scheduler thread CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler); threadGroup.create_thread(boost::bind(&TraceThread, "scheduler", serviceLoop)); /* Start the RPC server already. It will be started in "warmup" mode * and not really process calls already (but it will signify connections * that the server is there and will be ready later). Warmup mode will * be disabled when initialisation is finished. */ if (GetBoolArg("-server", false)) { uiInterface.InitMessage.connect(SetRPCWarmupStatus); if (!AppInitServers(config, threadGroup)) return InitError( _("Unable to start HTTP server. See debug log for details.")); } int64_t nStart; // Step 5: verify wallet database integrity #ifdef ENABLE_WALLET if (!CWallet::Verify()) return false; #endif // Step 6: network initialization // Note that we absolutely cannot open any actual connections // until the very end ("start node") as the UTXO/block state // is not yet setup and may end up being set up twice if we // need to reindex later. assert(!g_connman); g_connman = std::unique_ptr( new CConnman(config, GetRand(std::numeric_limits::max()), GetRand(std::numeric_limits::max()))); CConnman &connman = *g_connman; peerLogic.reset(new PeerLogicValidation(&connman)); RegisterValidationInterface(peerLogic.get()); RegisterNodeSignals(GetNodeSignals()); if (mapMultiArgs.count("-onlynet")) { std::set nets; for (const std::string &snet : mapMultiArgs.at("-onlynet")) { enum Network net = ParseNetwork(snet); if (net == NET_UNROUTABLE) return InitError(strprintf( _("Unknown network specified in -onlynet: '%s'"), snet)); nets.insert(net); } for (int n = 0; n < NET_MAX; n++) { enum Network net = (enum Network)n; if (!nets.count(net)) SetLimited(net); } } if (mapMultiArgs.count("-whitelist")) { for (const std::string &net : mapMultiArgs.at("-whitelist")) { CSubNet subnet; LookupSubNet(net.c_str(), subnet); if (!subnet.IsValid()) return InitError(strprintf( _("Invalid netmask specified in -whitelist: '%s'"), net)); connman.AddWhitelistedRange(subnet); } } bool proxyRandomize = GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE); // -proxy sets a proxy for all outgoing network traffic // -noproxy (or -proxy=0) as well as the empty string can be used to not set // a proxy, this is the default std::string proxyArg = GetArg("-proxy", ""); SetLimited(NET_TOR); if (proxyArg != "" && proxyArg != "0") { CService resolved(LookupNumeric(proxyArg.c_str(), 9050)); proxyType addrProxy = proxyType(resolved, proxyRandomize); if (!addrProxy.IsValid()) return InitError( strprintf(_("Invalid -proxy address: '%s'"), proxyArg)); SetProxy(NET_IPV4, addrProxy); SetProxy(NET_IPV6, addrProxy); SetProxy(NET_TOR, addrProxy); SetNameProxy(addrProxy); SetLimited(NET_TOR, false); // by default, -proxy sets onion as // reachable, unless -noonion later } // -onion can be used to set only a proxy for .onion, or override normal // proxy for .onion addresses. // -noonion (or -onion=0) disables connecting to .onion entirely. An empty // string is used to not override the onion proxy (in which case it defaults // to -proxy set above, or none) std::string onionArg = GetArg("-onion", ""); if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 SetLimited(NET_TOR); // set onions as unreachable } else { CService resolved(LookupNumeric(onionArg.c_str(), 9050)); proxyType addrOnion = proxyType(resolved, proxyRandomize); if (!addrOnion.IsValid()) return InitError( strprintf(_("Invalid -onion address: '%s'"), onionArg)); SetProxy(NET_TOR, addrOnion); SetLimited(NET_TOR, false); } } // see Step 2: parameter interactions for more information about these fListen = GetBoolArg("-listen", DEFAULT_LISTEN); fDiscover = GetBoolArg("-discover", true); fNameLookup = GetBoolArg("-dns", DEFAULT_NAME_LOOKUP); fRelayTxes = !GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY); if (fListen) { bool fBound = false; if (mapMultiArgs.count("-bind")) { for (const std::string &strBind : mapMultiArgs.at("-bind")) { CService addrBind; if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) return InitError(ResolveErrMsg("bind", strBind)); fBound |= Bind(connman, addrBind, (BF_EXPLICIT | BF_REPORT_ERROR)); } } if (mapMultiArgs.count("-whitebind")) { for (const std::string &strBind : mapMultiArgs.at("-whitebind")) { CService addrBind; if (!Lookup(strBind.c_str(), addrBind, 0, false)) return InitError(ResolveErrMsg("whitebind", strBind)); if (addrBind.GetPort() == 0) return InitError(strprintf( _("Need to specify a port with -whitebind: '%s'"), strBind)); fBound |= Bind(connman, addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST)); } } if (!mapMultiArgs.count("-bind") && !mapMultiArgs.count("-whitebind")) { struct in_addr inaddr_any; inaddr_any.s_addr = INADDR_ANY; fBound |= Bind(connman, CService(in6addr_any, GetListenPort()), BF_NONE); fBound |= Bind(connman, CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE); } if (!fBound) return InitError(_("Failed to listen on any port. Use -listen=0 if " "you want this.")); } if (mapMultiArgs.count("-externalip")) { for (const std::string &strAddr : mapMultiArgs.at("-externalip")) { CService addrLocal; if (Lookup(strAddr.c_str(), addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid()) AddLocal(addrLocal, LOCAL_MANUAL); else return InitError(ResolveErrMsg("externalip", strAddr)); } } if (mapMultiArgs.count("-seednode")) { for (const std::string &strDest : mapMultiArgs.at("-seednode")) { connman.AddOneShot(strDest); } } #if ENABLE_ZMQ pzmqNotificationInterface = CZMQNotificationInterface::Create(); if (pzmqNotificationInterface) { RegisterValidationInterface(pzmqNotificationInterface); } #endif // unlimited unless -maxuploadtarget is set uint64_t nMaxOutboundLimit = 0; uint64_t nMaxOutboundTimeframe = MAX_UPLOAD_TIMEFRAME; if (IsArgSet("-maxuploadtarget")) { nMaxOutboundLimit = GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET) * 1024 * 1024; } // Step 7: load block chain fReindex = GetBoolArg("-reindex", false); bool fReindexChainState = GetBoolArg("-reindex-chainstate", false); // Upgrading to 0.8; hard-link the old blknnnn.dat files into /blocks/ boost::filesystem::path blocksDir = GetDataDir() / "blocks"; if (!boost::filesystem::exists(blocksDir)) { boost::filesystem::create_directories(blocksDir); bool linked = false; for (unsigned int i = 1; i < 10000; i++) { boost::filesystem::path source = GetDataDir() / strprintf("blk%04u.dat", i); if (!boost::filesystem::exists(source)) break; boost::filesystem::path dest = blocksDir / strprintf("blk%05u.dat", i - 1); try { boost::filesystem::create_hard_link(source, dest); LogPrintf("Hardlinked %s -> %s\n", source.string(), dest.string()); linked = true; } catch (const boost::filesystem::filesystem_error &e) { // Note: hardlink creation failing is not a disaster, it just // means blocks will get re-downloaded from peers. LogPrintf("Error hardlinking blk%04u.dat: %s\n", i, e.what()); break; } } if (linked) { fReindex = true; } } // cache size calculations int64_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20); // total cache cannot be less than nMinDbCache nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be greater than nMaxDbcache nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); int64_t nBlockTreeDBCache = nTotalCache / 8; nBlockTreeDBCache = std::min(nBlockTreeDBCache, (GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxBlockDBAndTxIndexCache : nMaxBlockDBCache) << 20); nTotalCache -= nBlockTreeDBCache; // use 25%-50% of the remainder for disk cache int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // cap total coins db cache nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); nTotalCache -= nCoinDBCache; // the rest goes to in-memory cache nCoinCacheUsage = nTotalCache; int64_t nMempoolSizeMax = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; LogPrintf("Cache configuration:\n"); LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1fMiB for in-memory UTXO set (plus up to %.1fMiB of " "unused mempool space)\n", nCoinCacheUsage * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024)); bool fLoaded = false; while (!fLoaded) { bool fReset = fReindex; std::string strLoadError; uiInterface.InitMessage(_("Loading block index...")); nStart = GetTimeMillis(); do { try { UnloadBlockIndex(); delete pcoinsTip; delete pcoinsdbview; delete pcoinscatcher; delete pblocktree; pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex); pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex || fReindexChainState); pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); pcoinsTip = new CCoinsViewCache(pcoinscatcher); if (fReindex) { pblocktree->WriteReindexing(true); // If we're reindexing in prune mode, wipe away unusable // block files and all undo data files if (fPruneMode) { CleanupBlockRevFiles(); } } else if (!pcoinsdbview->Upgrade()) { strLoadError = _("Error upgrading chainstate database"); break; } if (!LoadBlockIndex(chainparams)) { strLoadError = _("Error loading block database"); break; } // If the loaded chain has a wrong genesis, bail out immediately // (we're likely using a testnet datadir, or the other way // around). if (!mapBlockIndex.empty() && mapBlockIndex.count( chainparams.GetConsensus().hashGenesisBlock) == 0) return InitError(_("Incorrect or no genesis block found. " "Wrong datadir for network?")); // Initialize the block index (no-op if non-empty database was // already loaded) if (!InitBlockIndex(config)) { strLoadError = _("Error initializing block database"); break; } // Check for changed -txindex state if (fTxIndex != GetBoolArg("-txindex", DEFAULT_TXINDEX)) { strLoadError = _("You need to rebuild the database using " "-reindex-chainstate to change -txindex"); break; } // Check for changed -prune state. What we are concerned about // is a user who has pruned blocks in the past, but is now // trying to run unpruned. if (fHavePruned && !fPruneMode) { strLoadError = _("You need to rebuild the database using -reindex to " "go back to unpruned mode. This will redownload the " "entire blockchain"); break; } if (!fReindex && chainActive.Tip() != nullptr) { uiInterface.InitMessage(_("Rewinding blocks...")); if (!RewindBlockIndex(config, chainparams)) { strLoadError = _("Unable to rewind the database to a " "pre-fork state. You will need to " "redownload the blockchain"); break; } } uiInterface.InitMessage(_("Verifying blocks...")); if (fHavePruned && GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) { LogPrintf("Prune: pruned datadir may not have more than %d " "blocks; only checking available blocks", MIN_BLOCKS_TO_KEEP); } { LOCK(cs_main); CBlockIndex *tip = chainActive.Tip(); RPCNotifyBlockChange(true, tip); if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) { strLoadError = _("The block database contains a block which " "appears to be from the future. " "This may be due to your computer's date and " "time being set incorrectly. " "Only rebuild the block database if you are sure " "that your computer's date and time are correct"); break; } } if (!CVerifyDB().VerifyDB( config, chainparams, pcoinsdbview, GetArg("-checklevel", DEFAULT_CHECKLEVEL), GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) { strLoadError = _("Corrupted block database detected"); break; } } catch (const std::exception &e) { if (fDebug) LogPrintf("%s\n", e.what()); strLoadError = _("Error opening block database"); break; } fLoaded = true; } while (false); if (!fLoaded) { // first suggest a reindex if (!fReset) { bool fRet = uiInterface.ThreadSafeQuestion( strLoadError + ".\n\n" + _("Do you want to rebuild the block database now?"), strLoadError + ".\nPlease restart with -reindex or " "-reindex-chainstate to recover.", "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT); if (fRet) { fReindex = true; fRequestShutdown = false; } else { LogPrintf("Aborted block database rebuild. Exiting.\n"); return false; } } else { return InitError(strLoadError); } } } // As LoadBlockIndex can take several minutes, it's possible the user // requested to kill the GUI during the last operation. If so, exit. // As the program has not fully started yet, Shutdown() is possibly // overkill. if (fRequestShutdown) { LogPrintf("Shutdown requested. Exiting.\n"); return false; } LogPrintf(" block index %15dms\n", GetTimeMillis() - nStart); boost::filesystem::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME; CAutoFile est_filein(fopen(est_path.string().c_str(), "rb"), SER_DISK, CLIENT_VERSION); // Allowed to fail as this file IS missing on first startup. if (!est_filein.IsNull()) mempool.ReadFeeEstimates(est_filein); fFeeEstimatesInitialized = true; // Step 8: load wallet #ifdef ENABLE_WALLET if (!CWallet::InitLoadWallet()) return false; #else LogPrintf("No wallet support compiled in!\n"); #endif // Step 9: data directory maintenance // if pruning, unset the service bit and perform the initial blockstore // prune after any wallet rescanning has taken place. if (fPruneMode) { LogPrintf("Unsetting NODE_NETWORK on prune mode\n"); nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK); if (!fReindex) { uiInterface.InitMessage(_("Pruning blockstore...")); PruneAndFlush(); } } // Step 10: import blocks if (!CheckDiskSpace()) return false; // Either install a handler to notify us when genesis activates, or set // fHaveGenesis directly. // No locking, as this happens before any background thread is started. if (chainActive.Tip() == nullptr) { uiInterface.NotifyBlockTip.connect(BlockNotifyGenesisWait); } else { fHaveGenesis = true; } if (IsArgSet("-blocknotify")) uiInterface.NotifyBlockTip.connect(BlockNotifyCallback); std::vector vImportFiles; if (mapMultiArgs.count("-loadblock")) { for (const std::string &strFile : mapMultiArgs.at("-loadblock")) { vImportFiles.push_back(strFile); } } threadGroup.create_thread( boost::bind(&ThreadImport, std::ref(config), vImportFiles)); // Wait for genesis block to be processed { boost::unique_lock lock(cs_GenesisWait); while (!fHaveGenesis) { condvar_GenesisWait.wait(lock); } uiInterface.NotifyBlockTip.disconnect(BlockNotifyGenesisWait); } // Step 11: start node //// debug print LogPrintf("mapBlockIndex.size() = %u\n", mapBlockIndex.size()); LogPrintf("nBestHeight = %d\n", chainActive.Height()); if (GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) StartTorControl(threadGroup, scheduler); Discover(threadGroup); // Map ports with UPnP MapPort(GetBoolArg("-upnp", DEFAULT_UPNP)); std::string strNodeError; CConnman::Options connOptions; connOptions.nLocalServices = nLocalServices; connOptions.nRelevantServices = nRelevantServices; connOptions.nMaxConnections = nMaxConnections; connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections); connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chainActive.Height(); connOptions.uiInterface = &uiInterface; connOptions.nSendBufferMaxSize = 1000 * GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); connOptions.nReceiveFloodSize = 1000 * GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); connOptions.nMaxOutboundTimeframe = nMaxOutboundTimeframe; connOptions.nMaxOutboundLimit = nMaxOutboundLimit; if (!connman.Start(scheduler, strNodeError, connOptions)) return InitError(strNodeError); // Step 12: finished SetRPCWarmupFinished(); uiInterface.InitMessage(_("Done loading")); #ifdef ENABLE_WALLET if (pwalletMain) pwalletMain->postInitProcess(threadGroup); #endif return !fRequestShutdown; } diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index d6479b89c..ef760e450 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -1,416 +1,416 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "addresstablemodel.h" #include "guiutil.h" #include "walletmodel.h" -#include "base58.h" +#include "dstencode.h" #include "wallet/wallet.h" #include #include const QString AddressTableModel::Send = "S"; const QString AddressTableModel::Receive = "R"; struct AddressTableEntry { enum Type { Sending, Receiving, /* QSortFilterProxyModel will filter these out */ Hidden }; Type type; QString label; QString address; AddressTableEntry() {} AddressTableEntry(Type _type, const QString &_label, const QString &_address) : type(_type), label(_label), address(_address) {} }; struct AddressTableEntryLessThan { bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const { return a.address < b.address; } bool operator()(const AddressTableEntry &a, const QString &b) const { return a.address < b; } bool operator()(const QString &a, const AddressTableEntry &b) const { return a < b.address; } }; /* Determine address type from address purpose */ static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine) { AddressTableEntry::Type addressType = AddressTableEntry::Hidden; // "refund" addresses aren't shown, and change addresses aren't in // mapAddressBook at all. if (strPurpose == "send") addressType = AddressTableEntry::Sending; else if (strPurpose == "receive") addressType = AddressTableEntry::Receiving; else if (strPurpose == "unknown" || strPurpose == "") { // if purpose not set, guess addressType = (isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending); } return addressType; } // Private implementation class AddressTablePriv { public: CWallet *wallet; QList cachedAddressTable; AddressTableModel *parent; AddressTablePriv(CWallet *_wallet, AddressTableModel *_parent) : wallet(_wallet), parent(_parent) {} void refreshAddressTable() { cachedAddressTable.clear(); { LOCK(wallet->cs_wallet); for (const std::pair &item : wallet->mapAddressBook) { const CTxDestination &address = item.first; bool fMine = IsMine(*wallet, address); AddressTableEntry::Type addressType = translateTransactionType( QString::fromStdString(item.second.purpose), fMine); const std::string &strName = item.second.name; cachedAddressTable.append(AddressTableEntry( addressType, QString::fromStdString(strName), QString::fromStdString(EncodeDestination(address)))); } } // qLowerBound() and qUpperBound() require our cachedAddressTable list // to be sorted in asc order. Even though the map is already sorted this // re-sorting step is needed because the originating map is sorted by // binary address, not by base58() address. qSort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan()); } void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status) { // Find address / label in model QList::iterator lower = qLowerBound(cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); QList::iterator upper = qUpperBound(cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); int lowerIndex = (lower - cachedAddressTable.begin()); int upperIndex = (upper - cachedAddressTable.begin()); bool inModel = (lower != upper); AddressTableEntry::Type newEntryType = translateTransactionType(purpose, isMine); switch (status) { case CT_NEW: if (inModel) { qWarning() << "AddressTablePriv::updateEntry: Warning: Got " "CT_NEW, but entry is already in model"; break; } parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex); cachedAddressTable.insert( lowerIndex, AddressTableEntry(newEntryType, label, address)); parent->endInsertRows(); break; case CT_UPDATED: if (!inModel) { qWarning() << "AddressTablePriv::updateEntry: Warning: Got " "CT_UPDATED, but entry is not in model"; break; } lower->type = newEntryType; lower->label = label; parent->emitDataChanged(lowerIndex); break; case CT_DELETED: if (!inModel) { qWarning() << "AddressTablePriv::updateEntry: Warning: Got " "CT_DELETED, but entry is not in model"; break; } parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex - 1); cachedAddressTable.erase(lower, upper); parent->endRemoveRows(); break; } } int size() { return cachedAddressTable.size(); } AddressTableEntry *index(int idx) { if (idx >= 0 && idx < cachedAddressTable.size()) { return &cachedAddressTable[idx]; } else { return 0; } } }; AddressTableModel::AddressTableModel(CWallet *_wallet, WalletModel *parent) : QAbstractTableModel(parent), walletModel(parent), wallet(_wallet), priv(0) { columns << tr("Label") << tr("Address"); priv = new AddressTablePriv(wallet, this); priv->refreshAddressTable(); } AddressTableModel::~AddressTableModel() { delete priv; } int AddressTableModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return priv->size(); } int AddressTableModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return columns.length(); } QVariant AddressTableModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); AddressTableEntry *rec = static_cast(index.internalPointer()); if (role == Qt::DisplayRole || role == Qt::EditRole) { switch (index.column()) { case Label: if (rec->label.isEmpty() && role == Qt::DisplayRole) { return tr("(no label)"); } else { return rec->label; } case Address: return rec->address; } } else if (role == Qt::FontRole) { QFont font; if (index.column() == Address) { font = GUIUtil::fixedPitchFont(); } return font; } else if (role == TypeRole) { switch (rec->type) { case AddressTableEntry::Sending: return Send; case AddressTableEntry::Receiving: return Receive; default: break; } } return QVariant(); } bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; AddressTableEntry *rec = static_cast(index.internalPointer()); std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive"); editStatus = OK; if (role == Qt::EditRole) { LOCK(wallet->cs_wallet); /* For SetAddressBook / DelAddressBook */ CTxDestination curAddress = DecodeDestination(rec->address.toStdString()); if (index.column() == Label) { // Do nothing, if old label == new label if (rec->label == value.toString()) { editStatus = NO_CHANGES; return false; } wallet->SetAddressBook(curAddress, value.toString().toStdString(), strPurpose); } else if (index.column() == Address) { CTxDestination newAddress = DecodeDestination(value.toString().toStdString()); // Refuse to set invalid address, set error status and return false if (boost::get(&newAddress)) { editStatus = INVALID_ADDRESS; return false; } // Do nothing, if old address == new address else if (newAddress == curAddress) { editStatus = NO_CHANGES; return false; } // Check for duplicate addresses to prevent accidental deletion of // addresses, if you try to paste an existing address over another // address (with a different label) else if (wallet->mapAddressBook.count(newAddress)) { editStatus = DUPLICATE_ADDRESS; return false; } // Double-check that we're not overwriting a receiving address else if (rec->type == AddressTableEntry::Sending) { // Remove old entry wallet->DelAddressBook(curAddress); // Add new entry with new address wallet->SetAddressBook(newAddress, rec->label.toStdString(), strPurpose); } } return true; } return false; } QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole && section < columns.size()) { return columns[section]; } } return QVariant(); } Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const { if (!index.isValid()) return 0; AddressTableEntry *rec = static_cast(index.internalPointer()); Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; // Can edit address and label for sending addresses, and only label for // receiving addresses. if (rec->type == AddressTableEntry::Sending || (rec->type == AddressTableEntry::Receiving && index.column() == Label)) { retval |= Qt::ItemIsEditable; } return retval; } QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(parent); AddressTableEntry *data = priv->index(row); if (data) { return createIndex(row, column, priv->index(row)); } else { return QModelIndex(); } } void AddressTableModel::updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status) { // Update address book model from Bitcoin core priv->updateEntry(address, label, isMine, purpose, status); } QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address) { std::string strLabel = label.toStdString(); std::string strAddress = address.toStdString(); editStatus = OK; if (type == Send) { if (!walletModel->validateAddress(address)) { editStatus = INVALID_ADDRESS; return QString(); } // Check for duplicate addresses { LOCK(wallet->cs_wallet); if (wallet->mapAddressBook.count(DecodeDestination(strAddress))) { editStatus = DUPLICATE_ADDRESS; return QString(); } } } else if (type == Receive) { // Generate a new address to associate with given label CPubKey newKey; if (!wallet->GetKeyFromPool(newKey)) { WalletModel::UnlockContext ctx(walletModel->requestUnlock()); if (!ctx.isValid()) { // Unlock wallet failed or was cancelled editStatus = WALLET_UNLOCK_FAILURE; return QString(); } if (!wallet->GetKeyFromPool(newKey)) { editStatus = KEY_GENERATION_FAILURE; return QString(); } } strAddress = EncodeDestination(newKey.GetID()); } else { return QString(); } // Add entry { LOCK(wallet->cs_wallet); wallet->SetAddressBook(DecodeDestination(strAddress), strLabel, (type == Send ? "send" : "receive")); } return QString::fromStdString(strAddress); } bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); AddressTableEntry *rec = priv->index(row); if (count != 1 || !rec || rec->type == AddressTableEntry::Receiving) { // Can only remove one row at a time, and cannot remove rows not in // model. // Also refuse to remove receiving addresses. return false; } { LOCK(wallet->cs_wallet); wallet->DelAddressBook(DecodeDestination(rec->address.toStdString())); } return true; } /* Look up label for address in address book, if not found return empty string. */ QString AddressTableModel::labelForAddress(const QString &address) const { { LOCK(wallet->cs_wallet); CTxDestination destination = DecodeDestination(address.toStdString()); std::map::iterator mi = wallet->mapAddressBook.find(destination); if (mi != wallet->mapAddressBook.end()) { return QString::fromStdString(mi->second.name); } } return QString(); } int AddressTableModel::lookupAddress(const QString &address) const { QModelIndexList lst = match(index(0, Address, QModelIndex()), Qt::EditRole, address, 1, Qt::MatchExactly); if (lst.isEmpty()) { return -1; } else { return lst.at(0).row(); } } void AddressTableModel::emitDataChanged(int idx) { Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length() - 1, QModelIndex())); } diff --git a/src/qt/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp index c6562f527..080d1d52e 100644 --- a/src/qt/bitcoinaddressvalidator.cpp +++ b/src/qt/bitcoinaddressvalidator.cpp @@ -1,84 +1,84 @@ // Copyright (c) 2011-2014 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 "bitcoinaddressvalidator.h" -#include "base58.h" +#include "dstencode.h" /* Base58 characters are: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" This is: - All numbers except for '0' - All upper-case letters except for 'I' and 'O' - All lower-case letters except for 'l' */ BitcoinAddressEntryValidator::BitcoinAddressEntryValidator(QObject *parent) : QValidator(parent) {} QValidator::State BitcoinAddressEntryValidator::validate(QString &input, int &pos) const { Q_UNUSED(pos); // Empty address is "intermediate" input if (input.isEmpty()) return QValidator::Intermediate; // Correction for (int idx = 0; idx < input.size();) { bool removeChar = false; QChar ch = input.at(idx); // Corrections made are very conservative on purpose, to avoid // users unexpectedly getting away with typos that would normally // be detected, and thus sending to the wrong address. switch (ch.unicode()) { // Qt categorizes these as "Other_Format" not "Separator_Space" case 0x200B: // ZERO WIDTH SPACE case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE removeChar = true; break; default: break; } // Remove whitespace if (ch.isSpace()) removeChar = true; // To next character if (removeChar) input.remove(idx, 1); else ++idx; } // Validation QValidator::State state = QValidator::Acceptable; for (int idx = 0; idx < input.size(); ++idx) { int ch = input.at(idx).unicode(); if (((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) && ch != 'l' && ch != 'I' && ch != '0' && ch != 'O') { // Alphanumeric and not a 'forbidden' character } else { state = QValidator::Invalid; } } return state; } BitcoinAddressCheckValidator::BitcoinAddressCheckValidator(QObject *parent) : QValidator(parent) {} QValidator::State BitcoinAddressCheckValidator::validate(QString &input, int &pos) const { Q_UNUSED(pos); // Validate the passed Bitcoin address if (IsValidDestinationString(input.toStdString())) { return QValidator::Acceptable; } return QValidator::Invalid; } diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index f7628791c..69eadc441 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -1,844 +1,845 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "coincontroldialog.h" #include "ui_coincontroldialog.h" #include "addresstablemodel.h" #include "bitcoinunits.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" #include "txmempool.h" #include "walletmodel.h" +#include "dstencode.h" #include "init.h" #include "policy/policy.h" #include "validation.h" // For mempool #include "wallet/coincontrol.h" #include "wallet/wallet.h" #include #include #include #include #include #include #include #include #include #include QList CoinControlDialog::payAmounts; CCoinControl *CoinControlDialog::coinControl = new CCoinControl(); bool CoinControlDialog::fSubtractFeeFromAmount = false; bool CCoinControlWidgetItem::operator<(const QTreeWidgetItem &other) const { int column = treeWidget()->sortColumn(); if (column == CoinControlDialog::COLUMN_AMOUNT || column == CoinControlDialog::COLUMN_DATE || column == CoinControlDialog::COLUMN_CONFIRMATIONS) return data(column, Qt::UserRole).toLongLong() < other.data(column, Qt::UserRole).toLongLong(); return QTreeWidgetItem::operator<(other); } CoinControlDialog::CoinControlDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::CoinControlDialog), model(0), platformStyle(_platformStyle) { ui->setupUi(this); // context menu actions QAction *copyAddressAction = new QAction(tr("Copy address"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); // we need to enable/disable this copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this unlockAction = new QAction(tr("Unlock unspent"), this); // context menu contextMenu = new QMenu(this); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(copyAmountAction); contextMenu->addAction(copyTransactionHashAction); contextMenu->addSeparator(); contextMenu->addAction(lockAction); contextMenu->addAction(unlockAction); // context menu signals connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); connect(copyTransactionHashAction, SIGNAL(triggered()), this, SLOT(copyTransactionHash())); connect(lockAction, SIGNAL(triggered()), this, SLOT(lockCoin())); connect(unlockAction, SIGNAL(triggered()), this, SLOT(unlockCoin())); // clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(clipboardQuantity())); connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(clipboardAmount())); connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(clipboardFee())); connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(clipboardAfterFee())); connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(clipboardBytes())); connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(clipboardLowOutput())); connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(clipboardChange())); ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); ui->labelCoinControlAmount->addAction(clipboardAmountAction); ui->labelCoinControlFee->addAction(clipboardFeeAction); ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); ui->labelCoinControlBytes->addAction(clipboardBytesAction); ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); ui->labelCoinControlChange->addAction(clipboardChangeAction); // toggle tree/list mode connect(ui->radioTreeMode, SIGNAL(toggled(bool)), this, SLOT(radioTreeMode(bool))); connect(ui->radioListMode, SIGNAL(toggled(bool)), this, SLOT(radioListMode(bool))); // click on checkbox connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(viewItemChanged(QTreeWidgetItem *, int))); // click on header #if QT_VERSION < 0x050000 ui->treeWidget->header()->setClickable(true); #else ui->treeWidget->header()->setSectionsClickable(true); #endif connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this, SLOT(headerSectionClicked(int))); // ok button connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonBoxClicked(QAbstractButton *))); // (un)select all connect(ui->pushButtonSelectAll, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked())); // change coin control first column label due Qt4 bug. // see https://github.com/bitcoin/bitcoin/issues/5716 ui->treeWidget->headerItem()->setText(COLUMN_CHECKBOX, QString()); ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 110); ui->treeWidget->setColumnWidth(COLUMN_LABEL, 190); ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 320); ui->treeWidget->setColumnWidth(COLUMN_DATE, 130); ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110); // store transaction hash in this column, but don't show it ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store vout index in this column, but don't show it ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // default view is sorted by amount desc sortView(COLUMN_AMOUNT, Qt::DescendingOrder); // restore list mode and sortorder as a convenience feature QSettings settings; if (settings.contains("nCoinControlMode") && !settings.value("nCoinControlMode").toBool()) ui->radioTreeMode->click(); if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder")) sortView( settings.value("nCoinControlSortColumn").toInt(), ((Qt::SortOrder)settings.value("nCoinControlSortOrder").toInt())); } CoinControlDialog::~CoinControlDialog() { QSettings settings; settings.setValue("nCoinControlMode", ui->radioListMode->isChecked()); settings.setValue("nCoinControlSortColumn", sortColumn); settings.setValue("nCoinControlSortOrder", (int)sortOrder); delete ui; } void CoinControlDialog::setModel(WalletModel *_model) { this->model = _model; if (_model && _model->getOptionsModel() && _model->getAddressTableModel()) { updateView(); updateLabelLocked(); CoinControlDialog::updateLabels(_model, this); } } // ok button void CoinControlDialog::buttonBoxClicked(QAbstractButton *button) { if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { // closes the dialog done(QDialog::Accepted); } } // (un)select all void CoinControlDialog::buttonSelectAllClicked() { Qt::CheckState state = Qt::Checked; for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked) { state = Qt::Unchecked; break; } } ui->treeWidget->setEnabled(false); for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state) ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state); ui->treeWidget->setEnabled(true); if (state == Qt::Unchecked) { // just to be sure coinControl->UnSelectAll(); } CoinControlDialog::updateLabels(model, this); } // context menu void CoinControlDialog::showMenu(const QPoint &point) { QTreeWidgetItem *item = ui->treeWidget->itemAt(point); if (item) { contextMenuItem = item; // disable some items (like Copy Transaction ID, lock, unlock) for tree // roots in context menu if (item->text(COLUMN_TXHASH).length() == 64) { // transaction hash is 64 characters (this means its a child node, // so its not a parent node in tree mode) copyTransactionHashAction->setEnabled(true); if (model->isLockedCoin( uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())) { lockAction->setEnabled(false); unlockAction->setEnabled(true); } else { lockAction->setEnabled(true); unlockAction->setEnabled(false); } } else { // this means click on parent node in tree mode -> disable all copyTransactionHashAction->setEnabled(false); lockAction->setEnabled(false); unlockAction->setEnabled(false); } // show context menu contextMenu->exec(QCursor::pos()); } } // context menu action: copy amount void CoinControlDialog::copyAmount() { GUIUtil::setClipboard( BitcoinUnits::removeSpaces(contextMenuItem->text(COLUMN_AMOUNT))); } // context menu action: copy label void CoinControlDialog::copyLabel() { if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent()) GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_LABEL)); else GUIUtil::setClipboard(contextMenuItem->text(COLUMN_LABEL)); } // context menu action: copy address void CoinControlDialog::copyAddress() { if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent()) GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_ADDRESS)); else GUIUtil::setClipboard(contextMenuItem->text(COLUMN_ADDRESS)); } // context menu action: copy transaction id void CoinControlDialog::copyTransactionHash() { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_TXHASH)); } // context menu action: lock coin void CoinControlDialog::lockCoin() { if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); COutPoint outpt( uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); model->lockCoin(outpt); contextMenuItem->setDisabled(true); contextMenuItem->setIcon( COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); updateLabelLocked(); } // context menu action: unlock coin void CoinControlDialog::unlockCoin() { COutPoint outpt( uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); model->unlockCoin(outpt); contextMenuItem->setDisabled(false); contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); updateLabelLocked(); } // copy label "Quantity" to clipboard void CoinControlDialog::clipboardQuantity() { GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); } // copy label "Amount" to clipboard void CoinControlDialog::clipboardAmount() { GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left( ui->labelCoinControlAmount->text().indexOf(" "))); } // copy label "Fee" to clipboard void CoinControlDialog::clipboardFee() { GUIUtil::setClipboard( ui->labelCoinControlFee->text() .left(ui->labelCoinControlFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // copy label "After fee" to clipboard void CoinControlDialog::clipboardAfterFee() { GUIUtil::setClipboard( ui->labelCoinControlAfterFee->text() .left(ui->labelCoinControlAfterFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // copy label "Bytes" to clipboard void CoinControlDialog::clipboardBytes() { GUIUtil::setClipboard( ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); } // copy label "Dust" to clipboard void CoinControlDialog::clipboardLowOutput() { GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); } // copy label "Change" to clipboard void CoinControlDialog::clipboardChange() { GUIUtil::setClipboard( ui->labelCoinControlChange->text() .left(ui->labelCoinControlChange->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // treeview: sort void CoinControlDialog::sortView(int column, Qt::SortOrder order) { sortColumn = column; sortOrder = order; ui->treeWidget->sortItems(column, order); ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } // treeview: clicked on header void CoinControlDialog::headerSectionClicked(int logicalIndex) { // click on most left column -> do nothing if (logicalIndex == COLUMN_CHECKBOX) { ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } else { if (sortColumn == logicalIndex) sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); else { sortColumn = logicalIndex; // if label or address then default => asc, else default => desc sortOrder = ((sortColumn == COLUMN_LABEL || sortColumn == COLUMN_ADDRESS) ? Qt::AscendingOrder : Qt::DescendingOrder); } sortView(sortColumn, sortOrder); } } // toggle tree mode void CoinControlDialog::radioTreeMode(bool checked) { if (checked && model) updateView(); } // toggle list mode void CoinControlDialog::radioListMode(bool checked) { if (checked && model) updateView(); } // checkbox clicked by user void CoinControlDialog::viewItemChanged(QTreeWidgetItem *item, int column) { // transaction hash is 64 characters (this means its a child node, so its // not a parent node in tree mode) if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) { COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) { coinControl->UnSelect(outpt); } else if (item->isDisabled()) { // locked (this happens if "check all" through parent node) item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); } else { coinControl->Select(outpt); } // selection changed -> update labels if (ui->treeWidget->isEnabled()) { // do not update on every click for (un)select all CoinControlDialog::updateLabels(model, this); } } // TODO: Remove this temporary qt5 fix after Qt5.3 and Qt5.4 are no longer used. // Fixed in Qt5.5 and above: https://bugreports.qt.io/browse/QTBUG-43473 #if QT_VERSION >= 0x050000 else if (column == COLUMN_CHECKBOX && item->childCount() > 0) { if (item->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked && item->child(0)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) item->setCheckState(COLUMN_CHECKBOX, Qt::Checked); } #endif } // shows count of locked unspent outputs void CoinControlDialog::updateLabelLocked() { std::vector vOutpts; model->listLockedCoins(vOutpts); if (vOutpts.size() > 0) { ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); ui->labelLocked->setVisible(true); } else ui->labelLocked->setVisible(false); } void CoinControlDialog::updateLabels(WalletModel *model, QDialog *dialog) { if (!model) return; // nPayAmount CAmount nPayAmount = 0; bool fDust = false; CMutableTransaction txDummy; for (const CAmount &amount : CoinControlDialog::payAmounts) { nPayAmount += amount; if (amount > 0) { CTxOut txout(amount, (CScript)std::vector(24, 0)); txDummy.vout.push_back(txout); if (txout.IsDust(dustRelayFee)) fDust = true; } } CAmount nAmount = 0; CAmount nPayFee = 0; CAmount nAfterFee = 0; CAmount nChange = 0; unsigned int nBytes = 0; unsigned int nBytesInputs = 0; double dPriority = 0; double dPriorityInputs = 0; unsigned int nQuantity = 0; int nQuantityUncompressed = 0; bool fAllowFree = false; std::vector vCoinControl; std::vector vOutputs; coinControl->ListSelected(vCoinControl); model->getOutputs(vCoinControl, vOutputs); for (const COutput &out : vOutputs) { // unselect already spent, very unlikely scenario, this could happen // when selected are spent elsewhere, like rpc or another computer uint256 txhash = out.tx->GetId(); COutPoint outpt(txhash, out.i); if (model->isSpent(outpt)) { coinControl->UnSelect(outpt); continue; } // Quantity nQuantity++; // Amount nAmount += out.tx->tx->vout[out.i].nValue.GetSatoshis(); // Priority dPriorityInputs += (double)out.tx->tx->vout[out.i].nValue.GetSatoshis() * (out.nDepth + 1); // Bytes CTxDestination address; if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address)) { CPubKey pubkey; CKeyID *keyid = boost::get(&address); if (keyid && model->getPubKey(*keyid, pubkey)) { nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); if (!pubkey.IsCompressed()) nQuantityUncompressed++; } else { // in all error cases, simply assume 148 here nBytesInputs += 148; } } else nBytesInputs += 148; } // calculation if (nQuantity > 0) { // Bytes // always assume +1 output for change here nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // in the subtract fee from amount case, we can tell if zero change // already and subtract the bytes, so that fee calculation afterwards is // accurate if (CoinControlDialog::fSubtractFeeFromAmount) if (nAmount - nPayAmount == 0) nBytes -= 34; // Fee nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool) .GetSatoshis(); if (nPayFee > 0 && coinControl->nMinimumTotalFee > nPayFee) nPayFee = coinControl->nMinimumTotalFee.GetSatoshis(); // Allow free? (require at least hard-coded threshold and default to // that if no estimate) double mempoolEstimatePriority = mempool.estimateSmartPriority(nTxConfirmTarget); // 29 = 180 - 151 (uncompressed public keys are over the limit. max 151 // bytes of the input are ignored for priority) dPriority = dPriorityInputs / (nBytes - nBytesInputs + (nQuantityUncompressed * 29)); double dPriorityNeeded = std::max(mempoolEstimatePriority, AllowFreeThreshold()); fAllowFree = (dPriority >= dPriorityNeeded); if (fSendFreeTransactions) if (fAllowFree && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) nPayFee = 0; if (nPayAmount > 0) { nChange = nAmount - nPayAmount; if (!CoinControlDialog::fSubtractFeeFromAmount) nChange -= nPayFee; // Never create dust outputs; if we would, just add the dust to the // fee. if (nChange > 0 && nChange < MIN_CHANGE) { CTxOut txout(nChange, (CScript)std::vector(24, 0)); if (txout.IsDust(dustRelayFee)) { // dust-change will be raised until no dust if (CoinControlDialog::fSubtractFeeFromAmount) { nChange = txout.GetDustThreshold(dustRelayFee).GetSatoshis(); } else { nPayFee += nChange; nChange = 0; } } } if (nChange == 0 && !CoinControlDialog::fSubtractFeeFromAmount) { nBytes -= 34; } } // after fee nAfterFee = std::max(nAmount - nPayFee, 0); } // actually update labels int nDisplayUnit = BitcoinUnits::BCC; if (model && model->getOptionsModel()) { nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); } QLabel *l1 = dialog->findChild("labelCoinControlQuantity"); QLabel *l2 = dialog->findChild("labelCoinControlAmount"); QLabel *l3 = dialog->findChild("labelCoinControlFee"); QLabel *l4 = dialog->findChild("labelCoinControlAfterFee"); QLabel *l5 = dialog->findChild("labelCoinControlBytes"); QLabel *l7 = dialog->findChild("labelCoinControlLowOutput"); QLabel *l8 = dialog->findChild("labelCoinControlChange"); // enable/disable "dust" and "change" dialog->findChild("labelCoinControlLowOutputText") ->setEnabled(nPayAmount > 0); dialog->findChild("labelCoinControlLowOutput") ->setEnabled(nPayAmount > 0); dialog->findChild("labelCoinControlChangeText") ->setEnabled(nPayAmount > 0); dialog->findChild("labelCoinControlChange") ->setEnabled(nPayAmount > 0); // stats // Quantity l1->setText(QString::number(nQuantity)); // Amount l2->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAmount)); // Fee l3->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nPayFee)); // After Fee l4->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // Bytes l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes)); // Dust l7->setText(fDust ? tr("yes") : tr("no")); // Change l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange)); if (nPayFee > 0 && (coinControl->nMinimumTotalFee < nPayFee)) { l3->setText(ASYMP_UTF8 + l3->text()); l4->setText(ASYMP_UTF8 + l4->text()); if (nChange > 0 && !CoinControlDialog::fSubtractFeeFromAmount) { l8->setText(ASYMP_UTF8 + l8->text()); } } // turn label red when dust l7->setStyleSheet((fDust) ? "color:red;" : ""); // tool tips QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller " "than the current dust threshold."); // how many satoshis the estimated fee can vary per byte we guess wrong double dFeeVary; if (payTxFee.GetFeePerK() > Amount(0)) { dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) .GetSatoshis() / 1000; } else { dFeeVary = (double)std::max( CWallet::GetRequiredFee(1000), mempool.estimateSmartFee(nTxConfirmTarget).GetFeePerK()) .GetSatoshis() / 1000; } QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); l3->setToolTip(toolTip4); l4->setToolTip(toolTip4); l7->setToolTip(toolTipDust); l8->setToolTip(toolTip4); dialog->findChild("labelCoinControlFeeText") ->setToolTip(l3->toolTip()); dialog->findChild("labelCoinControlAfterFeeText") ->setToolTip(l4->toolTip()); dialog->findChild("labelCoinControlBytesText") ->setToolTip(l5->toolTip()); dialog->findChild("labelCoinControlLowOutputText") ->setToolTip(l7->toolTip()); dialog->findChild("labelCoinControlChangeText") ->setToolTip(l8->toolTip()); // Insufficient funds QLabel *label = dialog->findChild("labelCoinControlInsuffFunds"); if (label) { label->setVisible(nChange < 0); } } void CoinControlDialog::updateView() { if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) { return; } bool treeMode = ui->radioTreeMode->isChecked(); ui->treeWidget->clear(); // performance, otherwise updateLabels would be called for every checked // checkbox ui->treeWidget->setEnabled(false); ui->treeWidget->setAlternatingRowColors(!treeMode); QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); std::map> mapCoins; model->listCoins(mapCoins); for (const std::pair> &coins : mapCoins) { CCoinControlWidgetItem *itemWalletAddress = new CCoinControlWidgetItem(); itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); QString sWalletAddress = coins.first; QString sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); if (sWalletLabel.isEmpty()) { sWalletLabel = tr("(no label)"); } if (treeMode) { // wallet address ui->treeWidget->addTopLevelItem(itemWalletAddress); itemWalletAddress->setFlags(flgTristate); itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // label itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); // address itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); } CAmount nSum = 0; int nChildren = 0; for (const COutput &out : coins.second) { nSum += out.tx->tx->vout[out.i].nValue.GetSatoshis(); nChildren++; CCoinControlWidgetItem *itemOutput; if (treeMode) { itemOutput = new CCoinControlWidgetItem(itemWalletAddress); } else { itemOutput = new CCoinControlWidgetItem(ui->treeWidget); } itemOutput->setFlags(flgCheckbox); itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // address CTxDestination outputAddress; QString sAddress = ""; if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, outputAddress)) { sAddress = QString::fromStdString(EncodeDestination(outputAddress)); // if listMode or change => show bitcoin address. In tree mode, // address is not shown again for direct wallet address outputs if (!treeMode || (!(sAddress == sWalletAddress))) itemOutput->setText(COLUMN_ADDRESS, sAddress); } // label if (!(sAddress == sWalletAddress)) { // change tooltip from where the change comes from itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)") .arg(sWalletLabel) .arg(sWalletAddress)); itemOutput->setText(COLUMN_LABEL, tr("(change)")); } else if (!treeMode) { QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress); if (sLabel.isEmpty()) { sLabel = tr("(no label)"); } itemOutput->setText(COLUMN_LABEL, sLabel); } // amount itemOutput->setText( COLUMN_AMOUNT, BitcoinUnits::format( nDisplayUnit, out.tx->tx->vout[out.i].nValue.GetSatoshis())); // padding so that sorting works correctly itemOutput->setData( COLUMN_AMOUNT, Qt::UserRole, QVariant( (qlonglong)out.tx->tx->vout[out.i].nValue.GetSatoshis())); // date itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime())); itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong)out.tx->GetTxTime())); // confirmations itemOutput->setText(COLUMN_CONFIRMATIONS, QString::number(out.nDepth)); itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong)out.nDepth)); // transaction hash uint256 txhash = out.tx->GetId(); itemOutput->setText(COLUMN_TXHASH, QString::fromStdString(txhash.GetHex())); // vout index itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i)); // disable locked coins if (model->isLockedCoin(txhash, out.i)) { COutPoint outpt(txhash, out.i); // just to be sure coinControl->UnSelect(outpt); itemOutput->setDisabled(true); itemOutput->setIcon( COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); } // set checkbox if (coinControl->IsSelected(COutPoint(txhash, out.i))) { itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); } } // amount if (treeMode) { itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); itemWalletAddress->setText( COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); itemWalletAddress->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong)nSum)); } } // expand all partially selected if (treeMode) { for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) ui->treeWidget->topLevelItem(i)->setExpanded(true); } // sort view sortView(sortColumn, sortOrder); ui->treeWidget->setEnabled(true); } diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index ab29369ba..fe5484a8a 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1,990 +1,991 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "guiutil.h" #include "bitcoinaddressvalidator.h" #include "bitcoinunits.h" #include "qvalidatedlineedit.h" #include "walletmodel.h" +#include "dstencode.h" #include "init.h" #include "policy/policy.h" #include "primitives/transaction.h" #include "protocol.h" #include "script/script.h" #include "script/standard.h" #include "util.h" #ifdef WIN32 #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0501 #ifdef _WIN32_IE #undef _WIN32_IE #endif #define _WIN32_IE 0x0501 #define WIN32_LEAN_AND_MEAN 1 #ifndef NOMINMAX #define NOMINMAX #endif #include "shellapi.h" #include "shlobj.h" #include "shlwapi.h" #endif #include #include #if BOOST_FILESYSTEM_VERSION >= 3 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for Qt::mightBeRichText #include #if QT_VERSION < 0x050000 #include #else #include #endif #if QT_VERSION >= 0x50200 #include #endif #if BOOST_FILESYSTEM_VERSION >= 3 static boost::filesystem::detail::utf8_codecvt_facet utf8; #endif #if defined(Q_OS_MAC) // These Mac includes must be done in the global namespace #include #include extern double NSAppKitVersionNumber; #if !defined(NSAppKitVersionNumber10_8) #define NSAppKitVersionNumber10_8 1187 #endif #if !defined(NSAppKitVersionNumber10_9) #define NSAppKitVersionNumber10_9 1265 #endif #endif namespace GUIUtil { const QString URI_SCHEME("bitcoincash"); QString dateTimeStr(const QDateTime &date) { return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm"); } QString dateTimeStr(qint64 nTime) { return dateTimeStr(QDateTime::fromTime_t((qint32)nTime)); } QFont fixedPitchFont() { #if QT_VERSION >= 0x50200 return QFontDatabase::systemFont(QFontDatabase::FixedFont); #else QFont font("Monospace"); #if QT_VERSION >= 0x040800 font.setStyleHint(QFont::Monospace); #else font.setStyleHint(QFont::TypeWriter); #endif return font; #endif } // Just some dummy data to generate an convincing random-looking (but // consistent) address static const uint8_t dummydata[] = { 0xeb, 0x15, 0x23, 0x1d, 0xfc, 0xeb, 0x60, 0x92, 0x58, 0x86, 0xb6, 0x7d, 0x06, 0x52, 0x99, 0x92, 0x59, 0x15, 0xae, 0xb1, 0x72, 0xc0, 0x66, 0x47}; // Generate a dummy address with invalid CRC, starting with the network prefix. static std::string DummyAddress(const CChainParams ¶ms) { std::vector sourcedata = params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); sourcedata.insert(sourcedata.end(), dummydata, dummydata + sizeof(dummydata)); for (int i = 0; i < 256; ++i) { // Try every trailing byte std::string s = EncodeBase58(sourcedata.data(), sourcedata.data() + sourcedata.size()); if (!IsValidDestinationString(s)) { return s; } sourcedata[sourcedata.size() - 1] += 1; } return ""; } void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) { parent->setFocusProxy(widget); widget->setFont(fixedPitchFont()); #if QT_VERSION >= 0x040700 // We don't want translators to use own addresses in translations // and this is the only place, where this address is supplied. widget->setPlaceholderText( QObject::tr("Enter a Bitcoin address (e.g. %1)") .arg(QString::fromStdString(DummyAddress(Params())))); #endif widget->setValidator(new BitcoinAddressEntryValidator(parent)); widget->setCheckValidator(new BitcoinAddressCheckValidator(parent)); } void setupAmountWidget(QLineEdit *widget, QWidget *parent) { QDoubleValidator *amountValidator = new QDoubleValidator(parent); amountValidator->setDecimals(8); amountValidator->setBottom(0.0); widget->setValidator(amountValidator); widget->setAlignment(Qt::AlignRight | Qt::AlignVCenter); } bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) { // return if URI is not valid or is no bitcoincash: URI if (!uri.isValid() || uri.scheme() != URI_SCHEME) return false; SendCoinsRecipient rv; rv.address = uri.path(); // Trim any following forward slash which may have been added by the OS if (rv.address.endsWith("/")) { rv.address.truncate(rv.address.length() - 1); } rv.amount = 0; #if QT_VERSION < 0x050000 QList> items = uri.queryItems(); #else QUrlQuery uriQuery(uri); QList> items = uriQuery.queryItems(); #endif for (QList>::iterator i = items.begin(); i != items.end(); i++) { bool fShouldReturnFalse = false; if (i->first.startsWith("req-")) { i->first.remove(0, 4); fShouldReturnFalse = true; } if (i->first == "label") { rv.label = i->second; fShouldReturnFalse = false; } if (i->first == "message") { rv.message = i->second; fShouldReturnFalse = false; } else if (i->first == "amount") { if (!i->second.isEmpty()) { if (!BitcoinUnits::parse(BitcoinUnits::BCC, i->second, &rv.amount)) { return false; } } fShouldReturnFalse = false; } if (fShouldReturnFalse) { return false; } } if (out) { *out = rv; } return true; } bool parseBitcoinURI(QString uri, SendCoinsRecipient *out) { // Convert bitcoincash:// to bitcoincash: // // Cannot handle this later, because bitcoincash:// // will cause Qt to see the part after // as host, // which will lower-case it (and thus invalidate the address). if (uri.startsWith(URI_SCHEME + "://", Qt::CaseInsensitive)) { uri.replace(0, URI_SCHEME.length() + 3, URI_SCHEME + ":"); } QUrl uriInstance(uri); return parseBitcoinURI(uriInstance, out); } QString formatBitcoinURI(const SendCoinsRecipient &info) { QString ret = (URI_SCHEME + ":%1").arg(info.address); int paramCount = 0; if (info.amount) { ret += QString("?amount=%1") .arg(BitcoinUnits::format(BitcoinUnits::BCC, info.amount, false, BitcoinUnits::separatorNever)); paramCount++; } if (!info.label.isEmpty()) { QString lbl(QUrl::toPercentEncoding(info.label)); ret += QString("%1label=%2").arg(paramCount == 0 ? "?" : "&").arg(lbl); paramCount++; } if (!info.message.isEmpty()) { QString msg(QUrl::toPercentEncoding(info.message)); ret += QString("%1message=%2").arg(paramCount == 0 ? "?" : "&").arg(msg); paramCount++; } return ret; } bool isDust(const QString &address, const CAmount &amount) { CTxDestination dest = DecodeDestination(address.toStdString()); CScript script = GetScriptForDestination(dest); CTxOut txOut(amount, script); return txOut.IsDust(dustRelayFee); } QString HtmlEscape(const QString &str, bool fMultiLine) { #if QT_VERSION < 0x050000 QString escaped = Qt::escape(str); #else QString escaped = str.toHtmlEscaped(); #endif if (fMultiLine) { escaped = escaped.replace("\n", "
\n"); } return escaped; } QString HtmlEscape(const std::string &str, bool fMultiLine) { return HtmlEscape(QString::fromStdString(str), fMultiLine); } void copyEntryData(QAbstractItemView *view, int column, int role) { if (!view || !view->selectionModel()) return; QModelIndexList selection = view->selectionModel()->selectedRows(column); if (!selection.isEmpty()) { // Copy first item setClipboard(selection.at(0).data(role).toString()); } } QList getEntryData(QAbstractItemView *view, int column) { if (!view || !view->selectionModel()) return QList(); return view->selectionModel()->selectedRows(column); } QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) { QString selectedFilter; QString myDir; // Default to user documents location if (dir.isEmpty()) { #if QT_VERSION < 0x050000 myDir = QDesktopServices::storageLocation( QDesktopServices::DocumentsLocation); #else myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); #endif } else { myDir = dir; } /* Directly convert path to native OS path separators */ QString result = QDir::toNativeSeparators(QFileDialog::getSaveFileName( parent, caption, myDir, filter, &selectedFilter)); /* Extract first suffix from filter pattern "Description (*.foo)" or * "Description (*.foo *.bar ...) */ QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); QString selectedSuffix; if (filter_re.exactMatch(selectedFilter)) { selectedSuffix = filter_re.cap(1); } /* Add suffix if needed */ QFileInfo info(result); if (!result.isEmpty()) { if (info.suffix().isEmpty() && !selectedSuffix.isEmpty()) { /* No suffix specified, add selected suffix */ if (!result.endsWith(".")) result.append("."); result.append(selectedSuffix); } } /* Return selected suffix if asked to */ if (selectedSuffixOut) { *selectedSuffixOut = selectedSuffix; } return result; } QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) { QString selectedFilter; QString myDir; if (dir.isEmpty()) // Default to user documents location { #if QT_VERSION < 0x050000 myDir = QDesktopServices::storageLocation( QDesktopServices::DocumentsLocation); #else myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); #endif } else { myDir = dir; } /* Directly convert path to native OS path separators */ QString result = QDir::toNativeSeparators(QFileDialog::getOpenFileName( parent, caption, myDir, filter, &selectedFilter)); if (selectedSuffixOut) { /* Extract first suffix from filter pattern "Description (*.foo)" or * "Description (*.foo *.bar ...) */ QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); QString selectedSuffix; if (filter_re.exactMatch(selectedFilter)) { selectedSuffix = filter_re.cap(1); } *selectedSuffixOut = selectedSuffix; } return result; } Qt::ConnectionType blockingGUIThreadConnection() { if (QThread::currentThread() != qApp->thread()) { return Qt::BlockingQueuedConnection; } else { return Qt::DirectConnection; } } bool checkPoint(const QPoint &p, const QWidget *w) { QWidget *atW = QApplication::widgetAt(w->mapToGlobal(p)); if (!atW) return false; return atW->topLevelWidget() == w; } bool isObscured(QWidget *w) { return !(checkPoint(QPoint(0, 0), w) && checkPoint(QPoint(w->width() - 1, 0), w) && checkPoint(QPoint(0, w->height() - 1), w) && checkPoint(QPoint(w->width() - 1, w->height() - 1), w) && checkPoint(QPoint(w->width() / 2, w->height() / 2), w)); } void openDebugLogfile() { boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; /* Open debug.log with the associated application */ if (boost::filesystem::exists(pathDebug)) QDesktopServices::openUrl( QUrl::fromLocalFile(boostPathToQString(pathDebug))); } void SubstituteFonts(const QString &language) { #if defined(Q_OS_MAC) // Background: // OSX's default font changed in 10.9 and Qt is unable to find it with its // usual fallback methods when building against the 10.7 sdk or lower. // The 10.8 SDK added a function to let it find the correct fallback font. // If this fallback is not properly loaded, some characters may fail to // render correctly. // // The same thing happened with 10.10. .Helvetica Neue DeskInterface is now // default. // // Solution: If building with the 10.7 SDK or lower and the user's platform // is 10.9 or higher at runtime, substitute the correct font. This needs to // happen before the QApplication is created. #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_8) { if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) /* On a 10.9 - 10.9.x system */ QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande"); else { /* 10.10 or later system */ if (language == "zh_CN" || language == "zh_TW" || language == "zh_HK") // traditional or simplified Chinese QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Heiti SC"); else if (language == "ja") // Japanesee QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Songti SC"); else QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Lucida Grande"); } } #endif #endif } ToolTipToRichTextFilter::ToolTipToRichTextFilter(int _size_threshold, QObject *parent) : QObject(parent), size_threshold(_size_threshold) {} bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt) { if (evt->type() == QEvent::ToolTipChange) { QWidget *widget = static_cast(obj); QString tooltip = widget->toolTip(); if (tooltip.size() > size_threshold && !tooltip.startsWith(" to make sure Qt detects this as rich text // Escape the current message as HTML and replace \n by
tooltip = "" + HtmlEscape(tooltip, true) + ""; widget->setToolTip(tooltip); return true; } } return QObject::eventFilter(obj, evt); } void TableViewLastColumnResizingFixer::connectViewHeadersSignals() { connect(tableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(on_sectionResized(int, int, int))); connect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged())); } // We need to disconnect these while handling the resize events, otherwise we // can enter infinite loops. void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals() { disconnect(tableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(on_sectionResized(int, int, int))); disconnect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged())); } // Setup the resize mode, handles compatibility for Qt5 and below as the method // signatures changed. // Refactored here for readability. void TableViewLastColumnResizingFixer::setViewHeaderResizeMode( int logicalIndex, QHeaderView::ResizeMode resizeMode) { #if QT_VERSION < 0x050000 tableView->horizontalHeader()->setResizeMode(logicalIndex, resizeMode); #else tableView->horizontalHeader()->setSectionResizeMode(logicalIndex, resizeMode); #endif } void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex, int width) { tableView->setColumnWidth(nColumnIndex, width); tableView->horizontalHeader()->resizeSection(nColumnIndex, width); } int TableViewLastColumnResizingFixer::getColumnsWidth() { int nColumnsWidthSum = 0; for (int i = 0; i < columnCount; i++) { nColumnsWidthSum += tableView->horizontalHeader()->sectionSize(i); } return nColumnsWidthSum; } int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column) { int nResult = lastColumnMinimumWidth; int nTableWidth = tableView->horizontalHeader()->width(); if (nTableWidth > 0) { int nOtherColsWidth = getColumnsWidth() - tableView->horizontalHeader()->sectionSize(column); nResult = std::max(nResult, nTableWidth - nOtherColsWidth); } return nResult; } // Make sure we don't make the columns wider than the table's viewport width. void TableViewLastColumnResizingFixer::adjustTableColumnsWidth() { disconnectViewHeadersSignals(); resizeColumn(lastColumnIndex, getAvailableWidthForColumn(lastColumnIndex)); connectViewHeadersSignals(); int nTableWidth = tableView->horizontalHeader()->width(); int nColsWidth = getColumnsWidth(); if (nColsWidth > nTableWidth) { resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); } } // Make column use all the space available, useful during window resizing. void TableViewLastColumnResizingFixer::stretchColumnWidth(int column) { disconnectViewHeadersSignals(); resizeColumn(column, getAvailableWidthForColumn(column)); connectViewHeadersSignals(); } // When a section is resized this is a slot-proxy for ajustAmountColumnWidth(). void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex, int oldSize, int newSize) { adjustTableColumnsWidth(); int remainingWidth = getAvailableWidthForColumn(logicalIndex); if (newSize > remainingWidth) { resizeColumn(logicalIndex, remainingWidth); } } // When the table's geometry is ready, we manually perform the stretch of the // "Message" column, // as the "Stretch" resize mode does not allow for interactive resizing. void TableViewLastColumnResizingFixer::on_geometriesChanged() { if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) != 0) { disconnectViewHeadersSignals(); resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); connectViewHeadersSignals(); } } /** * Initializes all internal variables and prepares the * the resize modes of the last 2 columns of the table and */ TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer( QTableView *table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent) : QObject(parent), tableView(table), lastColumnMinimumWidth(lastColMinimumWidth), allColumnsMinimumWidth(allColsMinimumWidth) { columnCount = tableView->horizontalHeader()->count(); lastColumnIndex = columnCount - 1; secondToLastColumnIndex = columnCount - 2; tableView->horizontalHeader()->setMinimumSectionSize( allColumnsMinimumWidth); setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive); setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive); } #ifdef WIN32 static boost::filesystem::path StartupShortcutPath() { std::string chain = ChainNameFromCommandLine(); if (chain == CBaseChainParams::MAIN) return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk"; // Remove this special case when CBaseChainParams::TESTNET = "testnet4" if (chain == CBaseChainParams::TESTNET) return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin (testnet).lnk"; return GetSpecialFolderPath(CSIDL_STARTUP) / strprintf("Bitcoin (%s).lnk", chain); } bool GetStartOnSystemStartup() { // check for Bitcoin*.lnk return boost::filesystem::exists(StartupShortcutPath()); } bool SetStartOnSystemStartup(bool fAutoStart) { // If the shortcut exists already, remove it for updating boost::filesystem::remove(StartupShortcutPath()); if (fAutoStart) { CoInitialize(nullptr); // Get a pointer to the IShellLink interface. IShellLink *psl = nullptr; HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&psl)); if (SUCCEEDED(hres)) { // Get the current executable path TCHAR pszExePath[MAX_PATH]; GetModuleFileName(nullptr, pszExePath, sizeof(pszExePath)); // Start client minimized QString strArgs = "-min"; // Set -testnet /-regtest options strArgs += QString::fromStdString(strprintf( " -testnet=%d -regtest=%d", GetBoolArg("-testnet", false), GetBoolArg("-regtest", false))); #ifdef UNICODE boost::scoped_array args(new TCHAR[strArgs.length() + 1]); // Convert the QString to TCHAR* strArgs.toWCharArray(args.get()); // Add missing '\0'-termination to string args[strArgs.length()] = '\0'; #endif // Set the path to the shortcut target psl->SetPath(pszExePath); PathRemoveFileSpec(pszExePath); psl->SetWorkingDirectory(pszExePath); psl->SetShowCmd(SW_SHOWMINNOACTIVE); #ifndef UNICODE psl->SetArguments(strArgs.toStdString().c_str()); #else psl->SetArguments(args.get()); #endif // Query IShellLink for the IPersistFile interface for // saving the shortcut in persistent storage. IPersistFile *ppf = nullptr; hres = psl->QueryInterface(IID_IPersistFile, reinterpret_cast(&ppf)); if (SUCCEEDED(hres)) { WCHAR pwsz[MAX_PATH]; // Ensure that the string is ANSI. MultiByteToWideChar(CP_ACP, 0, StartupShortcutPath().string().c_str(), -1, pwsz, MAX_PATH); // Save the link by calling IPersistFile::Save. hres = ppf->Save(pwsz, TRUE); ppf->Release(); psl->Release(); CoUninitialize(); return true; } psl->Release(); } CoUninitialize(); return false; } return true; } #elif defined(Q_OS_LINUX) // Follow the Desktop Application Autostart Spec: // http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html static boost::filesystem::path GetAutostartDir() { namespace fs = boost::filesystem; char *pszConfigHome = getenv("XDG_CONFIG_HOME"); if (pszConfigHome) return fs::path(pszConfigHome) / "autostart"; char *pszHome = getenv("HOME"); if (pszHome) return fs::path(pszHome) / ".config" / "autostart"; return fs::path(); } static boost::filesystem::path GetAutostartFilePath() { std::string chain = ChainNameFromCommandLine(); if (chain == CBaseChainParams::MAIN) return GetAutostartDir() / "bitcoin.desktop"; return GetAutostartDir() / strprintf("bitcoin-%s.lnk", chain); } bool GetStartOnSystemStartup() { boost::filesystem::ifstream optionFile(GetAutostartFilePath()); if (!optionFile.good()) return false; // Scan through file for "Hidden=true": std::string line; while (!optionFile.eof()) { getline(optionFile, line); if (line.find("Hidden") != std::string::npos && line.find("true") != std::string::npos) return false; } optionFile.close(); return true; } bool SetStartOnSystemStartup(bool fAutoStart) { if (!fAutoStart) boost::filesystem::remove(GetAutostartFilePath()); else { char pszExePath[MAX_PATH + 1]; memset(pszExePath, 0, sizeof(pszExePath)); if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath) - 1) == -1) return false; boost::filesystem::create_directories(GetAutostartDir()); boost::filesystem::ofstream optionFile( GetAutostartFilePath(), std::ios_base::out | std::ios_base::trunc); if (!optionFile.good()) return false; std::string chain = ChainNameFromCommandLine(); // Write a bitcoin.desktop file to the autostart directory: optionFile << "[Desktop Entry]\n"; optionFile << "Type=Application\n"; if (chain == CBaseChainParams::MAIN) optionFile << "Name=Bitcoin\n"; else optionFile << strprintf("Name=Bitcoin (%s)\n", chain); optionFile << "Exec=" << pszExePath << strprintf(" -min -testnet=%d -regtest=%d\n", GetBoolArg("-testnet", false), GetBoolArg("-regtest", false)); optionFile << "Terminal=false\n"; optionFile << "Hidden=false\n"; optionFile.close(); } return true; } #elif defined(Q_OS_MAC) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // based on: // https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m // NB: caller must release returned ref if it's not NULL LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl); LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl) { LSSharedFileListItemRef foundItem = nullptr; // loop through the list of startup items and try to find the bitcoin app CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(list, nullptr); for (int i = 0; !foundItem && 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 && CFEqual(currentItemURL, findUrl)) { // found CFRetain(foundItem = item); } if (currentItemURL) { CFRelease(currentItemURL); } } CFRelease(listSnapshot); return foundItem; } bool GetStartOnSystemStartup() { CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); LSSharedFileListRef loginItems = LSSharedFileListCreate( nullptr, kLSSharedFileListSessionLoginItems, nullptr); LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); // findStartupItemInList retains the item it returned, need to release if (foundItem) { CFRelease(foundItem); } CFRelease(loginItems); CFRelease(bitcoinAppUrl); return foundItem; } bool SetStartOnSystemStartup(bool fAutoStart) { CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); LSSharedFileListRef loginItems = LSSharedFileListCreate( nullptr, kLSSharedFileListSessionLoginItems, nullptr); LSSharedFileListItemRef foundItem = findStartupItemInList(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); } // findStartupItemInList retains the item it returned, need to release if (foundItem) { CFRelease(foundItem); } CFRelease(loginItems); CFRelease(bitcoinAppUrl); return true; } #pragma GCC diagnostic pop #else bool GetStartOnSystemStartup() { return false; } bool SetStartOnSystemStartup(bool fAutoStart) { return false; } #endif void saveWindowGeometry(const QString &strSetting, QWidget *parent) { QSettings settings; settings.setValue(strSetting + "Pos", parent->pos()); settings.setValue(strSetting + "Size", parent->size()); } void restoreWindowGeometry(const QString &strSetting, const QSize &defaultSize, QWidget *parent) { QSettings settings; QPoint pos = settings.value(strSetting + "Pos").toPoint(); QSize size = settings.value(strSetting + "Size", defaultSize).toSize(); if (!pos.x() && !pos.y()) { QRect screen = QApplication::desktop()->screenGeometry(); pos.setX((screen.width() - size.width()) / 2); pos.setY((screen.height() - size.height()) / 2); } parent->resize(size); parent->move(pos); } void setClipboard(const QString &str) { QApplication::clipboard()->setText(str, QClipboard::Clipboard); QApplication::clipboard()->setText(str, QClipboard::Selection); } #if BOOST_FILESYSTEM_VERSION >= 3 boost::filesystem::path qstringToBoostPath(const QString &path) { return boost::filesystem::path(path.toStdString(), utf8); } QString boostPathToQString(const boost::filesystem::path &path) { return QString::fromStdString(path.string(utf8)); } #else #warning Conversion between boost path and QString can use invalid character encoding with boost_filesystem v2 and older boost::filesystem::path qstringToBoostPath(const QString &path) { return boost::filesystem::path(path.toStdString()); } QString boostPathToQString(const boost::filesystem::path &path) { return QString::fromStdString(path.string()); } #endif QString formatDurationStr(int secs) { QStringList strList; int days = secs / 86400; int hours = (secs % 86400) / 3600; int mins = (secs % 3600) / 60; int seconds = secs % 60; if (days) strList.append(QString(QObject::tr("%1 d")).arg(days)); if (hours) strList.append(QString(QObject::tr("%1 h")).arg(hours)); if (mins) strList.append(QString(QObject::tr("%1 m")).arg(mins)); if (seconds || (!days && !hours && !mins)) strList.append(QString(QObject::tr("%1 s")).arg(seconds)); return strList.join(" "); } 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; 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_XTHIN: strList.append("XTHIN"); break; case NODE_BITCOIN_CASH: strList.append("CASH"); break; default: strList.append(QString("%1[%2]").arg("UNKNOWN").arg(check)); } } } if (strList.size()) return strList.join(" & "); else return QObject::tr("None"); } QString formatPingTime(double dPingTime) { return (dPingTime == std::numeric_limits::max() / 1e6 || dPingTime == 0) ? QObject::tr("N/A") : QString(QObject::tr("%1 ms")) .arg(QString::number((int)(dPingTime * 1000), 10)); } QString formatTimeOffset(int64_t nTimeOffset) { return QString(QObject::tr("%1 s")) .arg(QString::number((int)nTimeOffset, 10)); } QString formatNiceTimeOffset(qint64 secs) { // Represent time from last generated block in human readable text QString timeBehindText; const int HOUR_IN_SECONDS = 60 * 60; const int DAY_IN_SECONDS = 24 * 60 * 60; const int WEEK_IN_SECONDS = 7 * 24 * 60 * 60; // Average length of year in Gregorian calendar const int YEAR_IN_SECONDS = 31556952; if (secs < 60) { timeBehindText = QObject::tr("%n second(s)", "", secs); } else if (secs < 2 * HOUR_IN_SECONDS) { timeBehindText = QObject::tr("%n minute(s)", "", secs / 60); } else if (secs < 2 * DAY_IN_SECONDS) { timeBehindText = QObject::tr("%n hour(s)", "", secs / HOUR_IN_SECONDS); } else if (secs < 2 * WEEK_IN_SECONDS) { timeBehindText = QObject::tr("%n day(s)", "", secs / DAY_IN_SECONDS); } else if (secs < YEAR_IN_SECONDS) { timeBehindText = QObject::tr("%n week(s)", "", secs / WEEK_IN_SECONDS); } else { qint64 years = secs / YEAR_IN_SECONDS; qint64 remainder = secs % YEAR_IN_SECONDS; timeBehindText = QObject::tr("%1 and %2") .arg(QObject::tr("%n year(s)", "", years)) .arg(QObject::tr("%n week(s)", "", remainder / WEEK_IN_SECONDS)); } return timeBehindText; } void ClickableLabel::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT clicked(event->pos()); } void ClickableProgressBar::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT clicked(event->pos()); } } // namespace GUIUtil diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 4c67a9b45..b742a773e 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -1,818 +1,818 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "paymentserver.h" #include "bitcoinunits.h" #include "guiutil.h" #include "optionsmodel.h" -#include "base58.h" #include "chainparams.h" +#include "dstencode.h" #include "policy/policy.h" #include "ui_interface.h" #include "util.h" #include "wallet/wallet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION < 0x050000 #include #else #include #endif const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds // BIP70 payment protocol messages const char *BIP70_MESSAGE_PAYMENTACK = "PaymentACK"; const char *BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; // BIP71 payment protocol media types const char *BIP71_MIMETYPE_PAYMENT = "application/bitcoincash-payment"; const char *BIP71_MIMETYPE_PAYMENTACK = "application/bitcoincash-paymentack"; const char *BIP71_MIMETYPE_PAYMENTREQUEST = "application/bitcoincash-paymentrequest"; struct X509StoreDeleter { void operator()(X509_STORE *b) { X509_STORE_free(b); } }; struct X509Deleter { void operator()(X509 *b) { X509_free(b); } }; namespace // Anon namespace { std::unique_ptr certStore; } // // Create a name that is unique for: // testnet / non-testnet // data directory // static QString ipcServerName() { QString name("BitcoinQt"); // Append a simple hash of the datadir // Note that GetDataDir(true) returns a different path for -testnet versus // main net QString ddir(GUIUtil::boostPathToQString(GetDataDir(true))); name.append(QString::number(qHash(ddir))); return name; } // // We store payment URIs and requests received before the main GUI window is up // and ready to ask the user to send payment. // static QList savedPaymentRequests; static void ReportInvalidCertificate(const QSslCertificate &cert) { #if QT_VERSION < 0x050000 qDebug() << QString("%1: Payment server found an invalid certificate: ") .arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName); #else qDebug() << QString("%1: Payment server found an invalid certificate: ") .arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName); #endif } // // Load OpenSSL's list of root certificate authorities // void PaymentServer::LoadRootCAs(X509_STORE *_store) { // Unit tests mostly use this, to pass in fake root CAs: if (_store) { certStore.reset(_store); return; } // Normal execution, use either -rootcertificates or system certs: certStore.reset(X509_STORE_new()); // Note: use "-system-" default here so that users can pass // -rootcertificates="" and get 'I don't like X.509 certificates, don't // trust anybody' behavior: QString certFile = QString::fromStdString(GetArg("-rootcertificates", "-system-")); // Empty store if (certFile.isEmpty()) { qDebug() << QString("PaymentServer::%1: Payment request authentication " "via X.509 certificates disabled.") .arg(__func__); return; } QList certList; if (certFile != "-system-") { qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root " "certificate.") .arg(__func__) .arg(certFile); certList = QSslCertificate::fromPath(certFile); // Use those certificates when fetching payment requests, too: QSslSocket::setDefaultCaCertificates(certList); } else certList = QSslSocket::systemCaCertificates(); int nRootCerts = 0; const QDateTime currentTime = QDateTime::currentDateTime(); for (const QSslCertificate &cert : certList) { // Don't log nullptr certificates if (cert.isNull()) continue; // Not yet active/valid, or expired certificate if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) { ReportInvalidCertificate(cert); continue; } #if QT_VERSION >= 0x050000 // Blacklisted certificate if (cert.isBlacklisted()) { ReportInvalidCertificate(cert); continue; } #endif QByteArray certData = cert.toDer(); const uint8_t *data = (const uint8_t *)certData.data(); std::unique_ptr x509( d2i_X509(0, &data, certData.size())); if (x509 && X509_STORE_add_cert(certStore.get(), x509.get())) { // Note: X509_STORE increases the reference count to the X509 // object, we still have to release our reference to it. ++nRootCerts; } else { ReportInvalidCertificate(cert); continue; } } qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts << " root certificates"; // Project for another day: // Fetch certificate revocation lists, and add them to certStore. // Issues to consider: // performance (start a thread to fetch in background?) // privacy (fetch through tor/proxy so IP address isn't revealed) // would it be easier to just use a compiled-in blacklist? // or use Qt's blacklist? // "certificate stapling" with server-side caching is more efficient } // // Sending to the server is done synchronously, at startup. // If the server isn't already running, startup continues, and the items in // savedPaymentRequest will be handled when uiReady() is called. // // Warning: ipcSendCommandLine() is called early in init, so don't use "Q_EMIT // message()", but "QMessageBox::"! // void PaymentServer::ipcParseCommandLine(int argc, char *argv[]) { for (int i = 1; i < argc; i++) { QString arg(argv[i]); if (arg.startsWith("-")) continue; // If the bitcoincash: URI contains a payment request, we are not able // to detect the network as that would require fetching and parsing the // payment request. That means clicking such an URI which contains a // testnet payment request will start a mainnet instance and throw a // "wrong network" error. if (arg.startsWith(GUIUtil::URI_SCHEME + ":", Qt::CaseInsensitive)) // bitcoincash: URI { savedPaymentRequests.append(arg); SendCoinsRecipient r; if (GUIUtil::parseBitcoinURI(arg, &r) && !r.address.isEmpty()) { if (IsValidDestinationString(r.address.toStdString(), Params(CBaseChainParams::MAIN))) { SelectParams(CBaseChainParams::MAIN); } else if (IsValidDestinationString( r.address.toStdString(), Params(CBaseChainParams::TESTNET))) { SelectParams(CBaseChainParams::TESTNET); } } } else if (QFile::exists(arg)) { // Filename savedPaymentRequests.append(arg); PaymentRequestPlus request; if (readPaymentRequestFromFile(arg, request)) { if (request.getDetails().network() == "main") { SelectParams(CBaseChainParams::MAIN); } else if (request.getDetails().network() == "test") { SelectParams(CBaseChainParams::TESTNET); } } } else { // Printing to debug.log is about the best we can do here, the GUI // hasn't started yet so we can't pop up a message box. qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " "file does not exist: " << arg; } } } // // Sending to the server is done synchronously, at startup. // If the server isn't already running, startup continues, and the items in // savedPaymentRequest will be handled when uiReady() is called. // bool PaymentServer::ipcSendCommandLine() { bool fResult = false; for (const QString &r : savedPaymentRequests) { QLocalSocket *socket = new QLocalSocket(); socket->connectToServer(ipcServerName(), QIODevice::WriteOnly); if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT)) { delete socket; socket = nullptr; return false; } QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out << r; out.device()->seek(0); socket->write(block); socket->flush(); socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT); socket->disconnectFromServer(); delete socket; socket = nullptr; fResult = true; } return fResult; } PaymentServer::PaymentServer(QObject *parent, bool startLocalServer) : QObject(parent), saveURIs(true), uriServer(0), netManager(0), optionsModel(0) { // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; // Install global event filter to catch QFileOpenEvents // on Mac: sent when you click bitcoincash: links // other OSes: helpful when dealing with payment request files if (parent) parent->installEventFilter(this); QString name = ipcServerName(); // Clean up old socket leftover from a crash: QLocalServer::removeServer(name); if (startLocalServer) { uriServer = new QLocalServer(this); if (!uriServer->listen(name)) { // constructor is called early in init, so don't use "Q_EMIT // message()" here QMessageBox::critical(0, tr("Payment request error"), tr("Cannot start %1: click-to-pay handler") .arg(GUIUtil::URI_SCHEME)); } else { connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection())); connect(this, SIGNAL(receivedPaymentACK(QString)), this, SLOT(handlePaymentACK(QString))); } } } PaymentServer::~PaymentServer() { google::protobuf::ShutdownProtobufLibrary(); } // // OSX-specific way of handling bitcoincash: URIs and PaymentRequest mime types. // Also used by paymentservertests.cpp and when opening a payment request file // via "Open URI..." menu entry. // bool PaymentServer::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::FileOpen) { QFileOpenEvent *fileEvent = static_cast(event); if (!fileEvent->file().isEmpty()) handleURIOrFile(fileEvent->file()); else if (!fileEvent->url().isEmpty()) handleURIOrFile(fileEvent->url().toString()); return true; } return QObject::eventFilter(object, event); } void PaymentServer::initNetManager() { if (!optionsModel) return; if (netManager != nullptr) delete netManager; // netManager is used to fetch paymentrequests given in bitcoincash: URIs netManager = new QNetworkAccessManager(this); QNetworkProxy proxy; // Query active SOCKS5 proxy if (optionsModel->getProxySettings(proxy)) { netManager->setProxy(proxy); qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port(); } else qDebug() << "PaymentServer::initNetManager: No active proxy server found."; connect(netManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(netRequestFinished(QNetworkReply *))); connect(netManager, SIGNAL(sslErrors(QNetworkReply *, const QList &)), this, SLOT(reportSslErrors(QNetworkReply *, const QList &))); } void PaymentServer::uiReady() { initNetManager(); saveURIs = false; for (const QString &s : savedPaymentRequests) { handleURIOrFile(s); } savedPaymentRequests.clear(); } void PaymentServer::handleURIOrFile(const QString &s) { if (saveURIs) { savedPaymentRequests.append(s); return; } // bitcoincash: URI if (s.startsWith(GUIUtil::URI_SCHEME + ":", Qt::CaseInsensitive)) { #if QT_VERSION < 0x050000 QUrl uri(s); #else QUrlQuery uri((QUrl(s))); #endif if (uri.hasQueryItem("r")) { // payment request URI QByteArray temp; temp.append(uri.queryItemValue("r")); QString decoded = QUrl::fromPercentEncoding(temp); QUrl fetchUrl(decoded, QUrl::StrictMode); if (fetchUrl.isValid()) { qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" << fetchUrl << ")"; fetchRequest(fetchUrl); } else { qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " << fetchUrl; Q_EMIT message(tr("URI handling"), tr("Payment request fetch URL is invalid: %1") .arg(fetchUrl.toString()), CClientUIInterface::ICON_WARNING); } return; } else { // normal URI SendCoinsRecipient recipient; if (GUIUtil::parseBitcoinURI(s, &recipient)) { if (!IsValidDestinationString( recipient.address.toStdString())) { Q_EMIT message( tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address), CClientUIInterface::MSG_ERROR); } else Q_EMIT receivedPaymentRequest(recipient); } else Q_EMIT message( tr("URI handling"), tr("URI cannot be parsed! This can be caused by an invalid " "Bitcoin address or malformed URI parameters."), CClientUIInterface::ICON_WARNING); return; } } // payment request file if (QFile::exists(s)) { PaymentRequestPlus request; SendCoinsRecipient recipient; if (!readPaymentRequestFromFile(s, request)) { Q_EMIT message(tr("Payment request file handling"), tr("Payment request file cannot be read! This can " "be caused by an invalid payment request file."), CClientUIInterface::ICON_WARNING); } else if (processPaymentRequest(request, recipient)) Q_EMIT receivedPaymentRequest(recipient); return; } } void PaymentServer::handleURIConnection() { QLocalSocket *clientConnection = uriServer->nextPendingConnection(); while (clientConnection->bytesAvailable() < (int)sizeof(quint32)) clientConnection->waitForReadyRead(); connect(clientConnection, SIGNAL(disconnected()), clientConnection, SLOT(deleteLater())); QDataStream in(clientConnection); in.setVersion(QDataStream::Qt_4_0); if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) { return; } QString msg; in >> msg; handleURIOrFile(msg); } // // Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine() // so don't use "Q_EMIT message()", but "QMessageBox::"! // bool PaymentServer::readPaymentRequestFromFile(const QString &filename, PaymentRequestPlus &request) { QFile f(filename); if (!f.open(QIODevice::ReadOnly)) { qWarning() << QString("PaymentServer::%1: Failed to open %2") .arg(__func__) .arg(filename); return false; } // BIP70 DoS protection if (!verifySize(f.size())) { return false; } QByteArray data = f.readAll(); return request.parse(data); } bool PaymentServer::processPaymentRequest(const PaymentRequestPlus &request, SendCoinsRecipient &recipient) { if (!optionsModel) return false; if (request.IsInitialized()) { // Payment request network matches client network? if (!verifyNetwork(request.getDetails())) { Q_EMIT message( tr("Payment request rejected"), tr("Payment request network doesn't match client network."), CClientUIInterface::MSG_ERROR); return false; } // Make sure any payment requests involved are still valid. // This is re-checked just before sending coins in // WalletModel::sendCoins(). if (verifyExpired(request.getDetails())) { Q_EMIT message(tr("Payment request rejected"), tr("Payment request expired."), CClientUIInterface::MSG_ERROR); return false; } } else { Q_EMIT message(tr("Payment request error"), tr("Payment request is not initialized."), CClientUIInterface::MSG_ERROR); return false; } recipient.paymentRequest = request; recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo()); request.getMerchant(certStore.get(), recipient.authenticatedMerchant); QList> sendingTos = request.getPayTo(); QStringList addresses; for (const std::pair &sendingTo : sendingTos) { // Extract and check destination addresses CTxDestination dest; if (ExtractDestination(sendingTo.first, dest)) { // Append destination address addresses.append(QString::fromStdString(EncodeDestination(dest))); } else if (!recipient.authenticatedMerchant.isEmpty()) { // Unauthenticated payment requests to custom bitcoin addresses are // not supported // (there is no good way to tell the user where they are paying in a // way they'd // have a chance of understanding). Q_EMIT message(tr("Payment request rejected"), tr("Unverified payment requests to custom payment " "scripts are unsupported."), CClientUIInterface::MSG_ERROR); return false; } // Bitcoin amounts are stored as (optional) uint64 in the protobuf // messages (see paymentrequest.proto), but CAmount is defined as // int64_t. Because of that we need to verify that amounts are in a // valid range and no overflow has happened. if (!verifyAmount(sendingTo.second)) { Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR); return false; } // Extract and check amounts CTxOut txOut(sendingTo.second, sendingTo.first); if (txOut.IsDust(dustRelayFee)) { Q_EMIT message( tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered " "dust).") .arg(BitcoinUnits::formatWithUnit( optionsModel->getDisplayUnit(), sendingTo.second)), CClientUIInterface::MSG_ERROR); return false; } recipient.amount += sendingTo.second; // Also verify that the final amount is still in a valid range after // adding additional amounts. if (!verifyAmount(recipient.amount)) { Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR); return false; } } // Store addresses and format them to fit nicely into the GUI recipient.address = addresses.join("
"); if (!recipient.authenticatedMerchant.isEmpty()) { qDebug() << "PaymentServer::processPaymentRequest: Secure payment " "request from " << recipient.authenticatedMerchant; } else { qDebug() << "PaymentServer::processPaymentRequest: Insecure payment " "request to " << addresses.join(", "); } return true; } void PaymentServer::fetchRequest(const QUrl &url) { QNetworkRequest netRequest; netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTREQUEST); netRequest.setUrl(url); netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST); netManager->get(netRequest); } void PaymentServer::fetchPaymentACK(CWallet *wallet, SendCoinsRecipient recipient, QByteArray transaction) { const payments::PaymentDetails &details = recipient.paymentRequest.getDetails(); if (!details.has_payment_url()) return; QNetworkRequest netRequest; netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK); netRequest.setUrl(QString::fromStdString(details.payment_url())); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BIP71_MIMETYPE_PAYMENT); netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK); payments::Payment payment; payment.set_merchant_data(details.merchant_data()); payment.add_transactions(transaction.data(), transaction.size()); // Create a new refund address, or re-use: QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant); std::string strAccount = account.toStdString(); std::set refundAddresses = wallet->GetAccountAddresses(strAccount); if (!refundAddresses.empty()) { CScript s = GetScriptForDestination(*refundAddresses.begin()); payments::Output *refund_to = payment.add_refund_to(); refund_to->set_script(&s[0], s.size()); } else { CPubKey newKey; if (wallet->GetKeyFromPool(newKey)) { CKeyID keyID = newKey.GetID(); wallet->SetAddressBook(keyID, strAccount, "refund"); CScript s = GetScriptForDestination(keyID); payments::Output *refund_to = payment.add_refund_to(); refund_to->set_script(&s[0], s.size()); } else { // This should never happen, because sending coins should have just // unlocked the wallet and refilled the keypool. qWarning() << "PaymentServer::fetchPaymentACK: Error getting " "refund key, refund_to not set"; } } int length = payment.ByteSize(); netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length); QByteArray serData(length, '\0'); if (payment.SerializeToArray(serData.data(), length)) { netManager->post(netRequest, serData); } else { // This should never happen, either. qWarning() << "PaymentServer::fetchPaymentACK: Error serializing " "payment message"; } } void PaymentServer::netRequestFinished(QNetworkReply *reply) { reply->deleteLater(); // BIP70 DoS protection if (!verifySize(reply->size())) { Q_EMIT message( tr("Payment request rejected"), tr("Payment request %1 is too large (%2 bytes, allowed %3 bytes).") .arg(reply->request().url().toString()) .arg(reply->size()) .arg(BIP70_MAX_PAYMENTREQUEST_SIZE), CClientUIInterface::MSG_ERROR); return; } if (reply->error() != QNetworkReply::NoError) { QString msg = tr("Error communicating with %1: %2") .arg(reply->request().url().toString()) .arg(reply->errorString()); qWarning() << "PaymentServer::netRequestFinished: " << msg; Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR); return; } QByteArray data = reply->readAll(); QString requestType = reply->request().attribute(QNetworkRequest::User).toString(); if (requestType == BIP70_MESSAGE_PAYMENTREQUEST) { PaymentRequestPlus request; SendCoinsRecipient recipient; if (!request.parse(data)) { qWarning() << "PaymentServer::netRequestFinished: Error parsing " "payment request"; Q_EMIT message(tr("Payment request error"), tr("Payment request cannot be parsed!"), CClientUIInterface::MSG_ERROR); } else if (processPaymentRequest(request, recipient)) Q_EMIT receivedPaymentRequest(recipient); return; } else if (requestType == BIP70_MESSAGE_PAYMENTACK) { payments::PaymentACK paymentACK; if (!paymentACK.ParseFromArray(data.data(), data.size())) { QString msg = tr("Bad response from server %1") .arg(reply->request().url().toString()); qWarning() << "PaymentServer::netRequestFinished: " << msg; Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR); } else { Q_EMIT receivedPaymentACK(GUIUtil::HtmlEscape(paymentACK.memo())); } } } void PaymentServer::reportSslErrors(QNetworkReply *reply, const QList &errs) { Q_UNUSED(reply); QString errString; for (const QSslError &err : errs) { qWarning() << "PaymentServer::reportSslErrors: " << err; errString += err.errorString() + "\n"; } Q_EMIT message(tr("Network request error"), errString, CClientUIInterface::MSG_ERROR); } void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) { this->optionsModel = _optionsModel; } void PaymentServer::handlePaymentACK(const QString &paymentACKMsg) { // currently we don't further process or store the paymentACK message Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg, CClientUIInterface::ICON_INFORMATION | CClientUIInterface::MODAL); } bool PaymentServer::verifyNetwork( const payments::PaymentDetails &requestDetails) { bool fVerified = requestDetails.network() == Params().NetworkIDString(); if (!fVerified) { qWarning() << QString("PaymentServer::%1: Payment request network " "\"%2\" doesn't match client network \"%3\".") .arg(__func__) .arg(QString::fromStdString(requestDetails.network())) .arg(QString::fromStdString( Params().NetworkIDString())); } return fVerified; } bool PaymentServer::verifyExpired( const payments::PaymentDetails &requestDetails) { bool fVerified = (requestDetails.has_expires() && (int64_t)requestDetails.expires() < GetTime()); if (fVerified) { const QString requestExpires = QString::fromStdString(DateTimeStrFormat( "%Y-%m-%d %H:%M:%S", (int64_t)requestDetails.expires())); qWarning() << QString( "PaymentServer::%1: Payment request expired \"%2\".") .arg(__func__) .arg(requestExpires); } return fVerified; } bool PaymentServer::verifySize(qint64 requestSize) { bool fVerified = (requestSize <= BIP70_MAX_PAYMENTREQUEST_SIZE); if (!fVerified) { qWarning() << QString("PaymentServer::%1: Payment request too large " "(%2 bytes, allowed %3 bytes).") .arg(__func__) .arg(requestSize) .arg(BIP70_MAX_PAYMENTREQUEST_SIZE); } return fVerified; } bool PaymentServer::verifyAmount(const CAmount &requestAmount) { bool fVerified = MoneyRange(requestAmount); if (!fVerified) { qWarning() << QString("PaymentServer::%1: Payment request amount out " "of allowed range (%2, allowed 0 - %3).") .arg(__func__) .arg(requestAmount) .arg(MAX_MONEY.GetSatoshis()); } return fVerified; } X509_STORE *PaymentServer::getCertStore() { return certStore.get(); } diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index a9bcb51ea..a0e9dbd92 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -1,971 +1,971 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "sendcoinsdialog.h" #include "ui_sendcoinsdialog.h" #include "addresstablemodel.h" #include "bitcoinunits.h" #include "clientmodel.h" #include "coincontroldialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" #include "sendcoinsentry.h" #include "walletmodel.h" -#include "base58.h" #include "chainparams.h" +#include "dstencode.h" #include "txmempool.h" #include "ui_interface.h" #include "validation.h" // mempool and minRelayTxFee #include "wallet/coincontrol.h" #include "wallet/wallet.h" #include #include #include #include #include #define SEND_CONFIRM_DELAY 3 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::SendCoinsDialog), clientModel(0), model(0), fNewRecipientAllowed(true), fFeeMinimized(true), platformStyle(_platformStyle) { ui->setupUi(this); if (!_platformStyle->getImagesOnButtons()) { ui->addButton->setIcon(QIcon()); ui->clearButton->setIcon(QIcon()); ui->sendButton->setIcon(QIcon()); } else { ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add")); ui->clearButton->setIcon( _platformStyle->SingleColorIcon(":/icons/remove")); ui->sendButton->setIcon( _platformStyle->SingleColorIcon(":/icons/send")); } GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this); addEntry(); connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry())); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); // Coin Control connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked())); connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); // Coin Control: clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity())); connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount())); connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee())); connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee())); connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes())); connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput())); connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange())); ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); ui->labelCoinControlAmount->addAction(clipboardAmountAction); ui->labelCoinControlFee->addAction(clipboardFeeAction); ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); ui->labelCoinControlBytes->addAction(clipboardBytesAction); ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); ui->labelCoinControlChange->addAction(clipboardChangeAction); // init transaction fee section QSettings settings; if (!settings.contains("fFeeSectionMinimized")) settings.setValue("fFeeSectionMinimized", true); // compatibility if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) { // custom settings.setValue("nFeeRadio", 1); } if (!settings.contains("nFeeRadio")) { // recommended settings.setValue("nFeeRadio", 0); } // compatibility if (!settings.contains("nCustomFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) { // total at least settings.setValue("nCustomFeeRadio", 1); } if (!settings.contains("nCustomFeeRadio")) { // per kilobyte settings.setValue("nCustomFeeRadio", 0); } if (!settings.contains("nSmartFeeSliderPosition")) settings.setValue("nSmartFeeSliderPosition", 0); if (!settings.contains("nTransactionFee")) settings.setValue("nTransactionFee", (qint64)DEFAULT_TRANSACTION_FEE.GetSatoshis()); if (!settings.contains("fPayOnlyMinFee")) settings.setValue("fPayOnlyMinFee", false); ui->groupFee->setId(ui->radioSmartFee, 0); ui->groupFee->setId(ui->radioCustomFee, 1); ui->groupFee ->button( (int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt()))) ->setChecked(true); ui->groupCustomFee->setId(ui->radioCustomPerKilobyte, 0); ui->groupCustomFee->setId(ui->radioCustomAtLeast, 1); ui->groupCustomFee ->button((int)std::max( 0, std::min(1, settings.value("nCustomFeeRadio").toInt()))) ->setChecked(true); ui->customFee->setValue(settings.value("nTransactionFee").toLongLong()); ui->checkBoxMinimumFee->setChecked( settings.value("fPayOnlyMinFee").toBool()); minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool()); } void SendCoinsDialog::setClientModel(ClientModel *_clientModel) { this->clientModel = _clientModel; if (_clientModel) { connect(_clientModel, SIGNAL(numBlocksChanged(int, QDateTime, double, bool)), this, SLOT(updateSmartFeeLabel())); } } void SendCoinsDialog::setModel(WalletModel *_model) { this->model = _model; if (_model && _model->getOptionsModel()) { for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast( ui->entries->itemAt(i)->widget()); if (entry) { entry->setModel(_model); } } setBalance(_model->getBalance(), _model->getUnconfirmedBalance(), _model->getImmatureBalance(), _model->getWatchBalance(), _model->getWatchUnconfirmedBalance(), _model->getWatchImmatureBalance()); connect(_model, SIGNAL(balanceChanged(CAmount, CAmount, CAmount, CAmount, CAmount, CAmount)), this, SLOT(setBalance(CAmount, CAmount, CAmount, CAmount, CAmount, CAmount))); connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); updateDisplayUnit(); // Coin Control connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels())); connect(_model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool))); ui->frameCoinControl->setVisible( _model->getOptionsModel()->getCoinControlFeatures()); coinControlUpdateLabels(); // fee section connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateSmartFeeLabel())); connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateGlobalFeeVariables())); connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(coinControlUpdateLabels())); connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls())); connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables())); connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables())); connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(updateGlobalFeeVariables())); connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); ui->customFee->setSingleStep( CWallet::GetRequiredFee(1000).GetSatoshis()); updateFeeSectionControls(); updateMinFeeLabel(); updateSmartFeeLabel(); updateGlobalFeeVariables(); // set the smartfee-sliders default value (wallets default conf.target // or last stored value) QSettings settings; if (settings.value("nSmartFeeSliderPosition").toInt() == 0) ui->sliderSmartFee->setValue(ui->sliderSmartFee->maximum() - model->getDefaultConfirmTarget() + 2); else ui->sliderSmartFee->setValue( settings.value("nSmartFeeSliderPosition").toInt()); } } SendCoinsDialog::~SendCoinsDialog() { QSettings settings; settings.setValue("fFeeSectionMinimized", fFeeMinimized); settings.setValue("nFeeRadio", ui->groupFee->checkedId()); settings.setValue("nCustomFeeRadio", ui->groupCustomFee->checkedId()); settings.setValue("nSmartFeeSliderPosition", ui->sliderSmartFee->value()); settings.setValue("nTransactionFee", (qint64)ui->customFee->value()); settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked()); delete ui; } void SendCoinsDialog::on_sendButton_clicked() { if (!model || !model->getOptionsModel()) return; QList recipients; bool valid = true; for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); if (entry) { if (entry->validate()) { recipients.append(entry->getValue()); } else { valid = false; } } } if (!valid || recipients.isEmpty()) { return; } fNewRecipientAllowed = false; WalletModel::UnlockContext ctx(model->requestUnlock()); if (!ctx.isValid()) { // Unlock wallet was cancelled fNewRecipientAllowed = true; return; } // prepare transaction for getting txFee earlier WalletModelTransaction currentTransaction(recipients); WalletModel::SendCoinsReturn prepareStatus; // Always use a CCoinControl instance, use the CoinControlDialog instance if // CoinControl has been enabled CCoinControl ctrl; if (model->getOptionsModel()->getCoinControlFeatures()) ctrl = *CoinControlDialog::coinControl; if (ui->radioSmartFee->isChecked()) ctrl.nConfirmTarget = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2; else ctrl.nConfirmTarget = 0; prepareStatus = model->prepareTransaction(currentTransaction, &ctrl); // process prepareStatus and on error generate message shown to user processSendCoinsReturn( prepareStatus, BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); if (prepareStatus.status != WalletModel::OK) { fNewRecipientAllowed = true; return; } CAmount txFee = currentTransaction.getTransactionFee(); // Format confirmation message QStringList formatted; for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients()) { // generate bold amount string QString amount = "" + BitcoinUnits::formatHtmlWithUnit( model->getOptionsModel()->getDisplayUnit(), rcp.amount); amount.append(""); // generate monospace address string QString address = "" + rcp.address; address.append(""); QString recipientElement; // normal payment if (!rcp.paymentRequest.IsInitialized()) { if (rcp.label.length() > 0) { // label with address recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)); recipientElement.append(QString(" (%1)").arg(address)); } else { // just address recipientElement = tr("%1 to %2").arg(amount, address); } } else if (!rcp.authenticatedMerchant.isEmpty()) { // authenticated payment request recipientElement = tr("%1 to %2") .arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant)); } else { // unauthenticated payment request recipientElement = tr("%1 to %2").arg(amount, address); } formatted.append(recipientElement); } QString questionString = tr("Are you sure you want to send?"); questionString.append("

%1"); if (txFee > 0) { // append fee string if a fee is required questionString.append("
"); questionString.append(BitcoinUnits::formatHtmlWithUnit( model->getOptionsModel()->getDisplayUnit(), txFee)); questionString.append(" "); questionString.append(tr("added as transaction fee")); // append transaction size questionString.append( " (" + QString::number( (double)currentTransaction.getTransactionSize() / 1000) + " kB)"); } // add total amount in all subdivision units questionString.append("
"); CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee; QStringList alternativeUnits; for (BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { if (u != model->getOptionsModel()->getDisplayUnit()) alternativeUnits.append( BitcoinUnits::formatHtmlWithUnit(u, totalAmount)); } questionString.append( tr("Total Amount %1") .arg(BitcoinUnits::formatHtmlWithUnit( model->getOptionsModel()->getDisplayUnit(), totalAmount))); questionString.append( QString("
(=%2)
") .arg(alternativeUnits.join(" " + tr("or") + "
"))); SendConfirmationDialog confirmationDialog( tr("Confirm send coins"), questionString.arg(formatted.join("
")), SEND_CONFIRM_DELAY, this); confirmationDialog.exec(); QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result(); if (retval != QMessageBox::Yes) { fNewRecipientAllowed = true; return; } // 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) { accept(); CoinControlDialog::coinControl->UnSelectAll(); coinControlUpdateLabels(); } fNewRecipientAllowed = true; } void SendCoinsDialog::clear() { // Remove entries until only one left while (ui->entries->count()) { ui->entries->takeAt(0)->widget()->deleteLater(); } addEntry(); updateTabsAndLabels(); } void SendCoinsDialog::reject() { clear(); } void SendCoinsDialog::accept() { clear(); } SendCoinsEntry *SendCoinsDialog::addEntry() { SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this); entry->setModel(model); ui->entries->addWidget(entry); connect(entry, SIGNAL(removeEntry(SendCoinsEntry *)), this, SLOT(removeEntry(SendCoinsEntry *))); connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels())); connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels())); // Focus the field, so that entry can start immediately entry->clear(); entry->setFocus(); ui->scrollAreaWidgetContents->resize( ui->scrollAreaWidgetContents->sizeHint()); qApp->processEvents(); QScrollBar *bar = ui->scrollArea->verticalScrollBar(); if (bar) bar->setSliderPosition(bar->maximum()); updateTabsAndLabels(); return entry; } void SendCoinsDialog::updateTabsAndLabels() { setupTabChain(0); coinControlUpdateLabels(); } void SendCoinsDialog::removeEntry(SendCoinsEntry *entry) { entry->hide(); // If the last entry is about to be removed add an empty one if (ui->entries->count() == 1) addEntry(); entry->deleteLater(); updateTabsAndLabels(); } QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) { for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); if (entry) { prev = entry->setupTabChain(prev); } } QWidget::setTabOrder(prev, ui->sendButton); QWidget::setTabOrder(ui->sendButton, ui->clearButton); QWidget::setTabOrder(ui->clearButton, ui->addButton); return ui->addButton; } void SendCoinsDialog::setAddress(const QString &address) { SendCoinsEntry *entry = 0; // Replace the first entry if it is still unused if (ui->entries->count() == 1) { SendCoinsEntry *first = qobject_cast(ui->entries->itemAt(0)->widget()); if (first->isClear()) { entry = first; } } if (!entry) { entry = addEntry(); } entry->setAddress(address); } void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) { if (!fNewRecipientAllowed) return; SendCoinsEntry *entry = 0; // Replace the first entry if it is still unused if (ui->entries->count() == 1) { SendCoinsEntry *first = qobject_cast(ui->entries->itemAt(0)->widget()); if (first->isClear()) { entry = first; } } if (!entry) { entry = addEntry(); } entry->setValue(rv); updateTabsAndLabels(); } bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv) { // Just paste the entry, all pre-checks are done in paymentserver.cpp. pasteEntry(rv); return true; } void SendCoinsDialog::setBalance(const CAmount &balance, const CAmount &unconfirmedBalance, const CAmount &immatureBalance, const CAmount &watchBalance, const CAmount &watchUnconfirmedBalance, const CAmount &watchImmatureBalance) { Q_UNUSED(unconfirmedBalance); Q_UNUSED(immatureBalance); Q_UNUSED(watchBalance); Q_UNUSED(watchUnconfirmedBalance); Q_UNUSED(watchImmatureBalance); if (model && model->getOptionsModel()) { ui->labelBalance->setText(BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), balance)); } } void SendCoinsDialog::updateDisplayUnit() { setBalance(model->getBalance(), 0, 0, 0, 0, 0); ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); updateMinFeeLabel(); updateSmartFeeLabel(); } void SendCoinsDialog::processSendCoinsReturn( const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg) { QPair msgParams; // Default to a warning message, override if error message is needed msgParams.second = CClientUIInterface::MSG_WARNING; // This comment is specific to SendCoinsDialog usage of // WalletModel::SendCoinsReturn. // WalletModel::TransactionCommitFailed is used only in // WalletModel::sendCoins() all others are used only in // WalletModel::prepareTransaction() switch (sendCoinsReturn.status) { case WalletModel::InvalidAddress: msgParams.first = tr("The recipient address is not valid. Please recheck."); break; case WalletModel::InvalidAmount: msgParams.first = tr("The amount to pay must be larger than 0."); break; case WalletModel::AmountExceedsBalance: msgParams.first = tr("The amount exceeds your balance."); break; case WalletModel::AmountWithFeeExceedsBalance: msgParams.first = tr("The total exceeds your balance when the %1 " "transaction fee is included.") .arg(msgArg); break; case WalletModel::DuplicateAddress: msgParams.first = tr("Duplicate address found: addresses should " "only be used once each."); break; case WalletModel::TransactionCreationFailed: msgParams.first = tr("Transaction creation failed!"); msgParams.second = CClientUIInterface::MSG_ERROR; break; case WalletModel::TransactionCommitFailed: msgParams.first = tr("The transaction was rejected with the following reason: %1") .arg(sendCoinsReturn.reasonCommitFailed); msgParams.second = CClientUIInterface::MSG_ERROR; break; case WalletModel::AbsurdFee: msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.") .arg(BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), maxTxFee.GetSatoshis())); break; case WalletModel::PaymentRequestExpired: msgParams.first = tr("Payment request expired."); msgParams.second = CClientUIInterface::MSG_ERROR; break; // included to prevent a compiler warning. case WalletModel::OK: default: return; } Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second); } void SendCoinsDialog::minimizeFeeSection(bool fMinimize) { ui->labelFeeMinimized->setVisible(fMinimize); ui->buttonChooseFee->setVisible(fMinimize); ui->buttonMinimizeFee->setVisible(!fMinimize); ui->frameFeeSelection->setVisible(!fMinimize); ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0); fFeeMinimized = fMinimize; } void SendCoinsDialog::on_buttonChooseFee_clicked() { minimizeFeeSection(false); } void SendCoinsDialog::on_buttonMinimizeFee_clicked() { updateFeeMinimizedLabel(); minimizeFeeSection(true); } void SendCoinsDialog::setMinimumFee() { ui->radioCustomPerKilobyte->setChecked(true); ui->customFee->setValue(CWallet::GetRequiredFee(1000).GetSatoshis()); } void SendCoinsDialog::updateFeeSectionControls() { ui->sliderSmartFee->setEnabled(ui->radioSmartFee->isChecked()); ui->labelSmartFee->setEnabled(ui->radioSmartFee->isChecked()); ui->labelSmartFee2->setEnabled(ui->radioSmartFee->isChecked()); ui->labelSmartFee3->setEnabled(ui->radioSmartFee->isChecked()); ui->labelFeeEstimation->setEnabled(ui->radioSmartFee->isChecked()); ui->labelSmartFeeNormal->setEnabled(ui->radioSmartFee->isChecked()); ui->labelSmartFeeFast->setEnabled(ui->radioSmartFee->isChecked()); ui->confirmationTargetLabel->setEnabled(ui->radioSmartFee->isChecked()); ui->checkBoxMinimumFee->setEnabled(ui->radioCustomFee->isChecked()); ui->labelMinFeeWarning->setEnabled(ui->radioCustomFee->isChecked()); ui->radioCustomPerKilobyte->setEnabled( ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); ui->radioCustomAtLeast->setEnabled( ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked() && CoinControlDialog::coinControl->HasSelected()); ui->customFee->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); } void SendCoinsDialog::updateGlobalFeeVariables() { if (ui->radioSmartFee->isChecked()) { int nConfirmTarget = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2; payTxFee = CFeeRate(0); // set nMinimumTotalFee to 0 to not accidentally pay a custom fee CoinControlDialog::coinControl->nMinimumTotalFee = 0; // show the estimated required time for confirmation ui->confirmationTargetLabel->setText( GUIUtil::formatDurationStr( nConfirmTarget * Params().GetConsensus().nPowTargetSpacing) + " / " + tr("%n block(s)", "", nConfirmTarget)); } else { payTxFee = CFeeRate(ui->customFee->value()); // if user has selected to set a minimum absolute fee, pass the value to // coincontrol // set nMinimumTotalFee to 0 in case of user has selected that the fee // is per KB CoinControlDialog::coinControl->nMinimumTotalFee = ui->radioCustomAtLeast->isChecked() ? ui->customFee->value() : 0; } } void SendCoinsDialog::updateFeeMinimizedLabel() { if (!model || !model->getOptionsModel()) return; if (ui->radioSmartFee->isChecked()) ui->labelFeeMinimized->setText(ui->labelSmartFee->text()); else { ui->labelFeeMinimized->setText( BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + ((ui->radioCustomPerKilobyte->isChecked()) ? "/kB" : "")); } } void SendCoinsDialog::updateMinFeeLabel() { if (model && model->getOptionsModel()) ui->checkBoxMinimumFee->setText( tr("Pay only the required fee of %1") .arg(BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), CWallet::GetRequiredFee(1000).GetSatoshis()) + "/kB")); } void SendCoinsDialog::updateSmartFeeLabel() { if (!model || !model->getOptionsModel()) return; int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2; int estimateFoundAtBlocks = nBlocksToConfirm; CFeeRate feeRate = mempool.estimateSmartFee(nBlocksToConfirm, &estimateFoundAtBlocks); // not enough data => minfee if (feeRate <= CFeeRate(0)) { ui->labelSmartFee->setText( BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), std::max(CWallet::fallbackFee.GetFeePerK(), CWallet::GetRequiredFee(1000)) .GetSatoshis()) + "/kB"); // (Smart fee not initialized yet. This usually takes a few blocks...) ui->labelSmartFee2->show(); ui->labelFeeEstimation->setText(""); } else { ui->labelSmartFee->setText( BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), std::max(feeRate.GetFeePerK(), CWallet::GetRequiredFee(1000)) .GetSatoshis()) + "/kB"); ui->labelSmartFee2->hide(); ui->labelFeeEstimation->setText( tr("Estimated to begin confirmation within %n block(s).", "", estimateFoundAtBlocks)); } updateFeeMinimizedLabel(); } // Coin Control: copy label "Quantity" to clipboard void SendCoinsDialog::coinControlClipboardQuantity() { GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); } // Coin Control: copy label "Amount" to clipboard void SendCoinsDialog::coinControlClipboardAmount() { GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left( ui->labelCoinControlAmount->text().indexOf(" "))); } // Coin Control: copy label "Fee" to clipboard void SendCoinsDialog::coinControlClipboardFee() { GUIUtil::setClipboard( ui->labelCoinControlFee->text() .left(ui->labelCoinControlFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // Coin Control: copy label "After fee" to clipboard void SendCoinsDialog::coinControlClipboardAfterFee() { GUIUtil::setClipboard( ui->labelCoinControlAfterFee->text() .left(ui->labelCoinControlAfterFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // Coin Control: copy label "Bytes" to clipboard void SendCoinsDialog::coinControlClipboardBytes() { GUIUtil::setClipboard( ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); } // Coin Control: copy label "Dust" to clipboard void SendCoinsDialog::coinControlClipboardLowOutput() { GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); } // Coin Control: copy label "Change" to clipboard void SendCoinsDialog::coinControlClipboardChange() { GUIUtil::setClipboard( ui->labelCoinControlChange->text() .left(ui->labelCoinControlChange->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // Coin Control: settings menu - coin control enabled/disabled by user void SendCoinsDialog::coinControlFeatureChanged(bool checked) { ui->frameCoinControl->setVisible(checked); // coin control features disabled if (!checked && model) CoinControlDialog::coinControl->SetNull(); // make sure we set back the confirmation target updateGlobalFeeVariables(); coinControlUpdateLabels(); } // Coin Control: button inputs -> show actual coin control dialog void SendCoinsDialog::coinControlButtonClicked() { CoinControlDialog dlg(platformStyle); dlg.setModel(model); dlg.exec(); coinControlUpdateLabels(); } // Coin Control: checkbox custom change address void SendCoinsDialog::coinControlChangeChecked(int state) { if (state == Qt::Unchecked) { CoinControlDialog::coinControl->destChange = CNoDestination(); ui->labelCoinControlChangeLabel->clear(); } else { // use this to re-validate an already entered address coinControlChangeEdited(ui->lineEditCoinControlChange->text()); } ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked)); } // Coin Control: custom change address changed void SendCoinsDialog::coinControlChangeEdited(const QString &text) { if (model && model->getAddressTableModel()) { // Default to no change address until verified CoinControlDialog::coinControl->destChange = CNoDestination(); ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); const CTxDestination dest = DecodeDestination(text.toStdString()); if (text.isEmpty()) { // Nothing entered ui->labelCoinControlChangeLabel->setText(""); } else if (!IsValidDestination(dest)) { // Invalid address ui->labelCoinControlChangeLabel->setText( tr("Warning: Invalid Bitcoin address")); } else { // Valid address if (!model->IsSpendable(dest)) { ui->labelCoinControlChangeLabel->setText( tr("Warning: Unknown change address")); // confirmation dialog QMessageBox::StandardButton btnRetVal = QMessageBox::question( this, tr("Confirm custom change address"), tr("The address you selected for change is not part of " "this wallet. Any or all funds in your wallet may be " "sent to this address. Are you sure?"), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); if (btnRetVal == QMessageBox::Yes) CoinControlDialog::coinControl->destChange = dest; else { ui->lineEditCoinControlChange->setText(""); ui->labelCoinControlChangeLabel->setStyleSheet( "QLabel{color:black;}"); ui->labelCoinControlChangeLabel->setText(""); } } else { // Known change address ui->labelCoinControlChangeLabel->setStyleSheet( "QLabel{color:black;}"); // Query label QString associatedLabel = model->getAddressTableModel()->labelForAddress(text); if (!associatedLabel.isEmpty()) ui->labelCoinControlChangeLabel->setText(associatedLabel); else ui->labelCoinControlChangeLabel->setText(tr("(no label)")); CoinControlDialog::coinControl->destChange = dest; } } } } // Coin Control: update labels void SendCoinsDialog::coinControlUpdateLabels() { if (!model || !model->getOptionsModel()) return; if (model->getOptionsModel()->getCoinControlFeatures()) { // enable minimum absolute fee UI controls ui->radioCustomAtLeast->setVisible(true); // only enable the feature if inputs are selected ui->radioCustomAtLeast->setEnabled( ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked() && CoinControlDialog::coinControl->HasSelected()); } else { // in case coin control is disabled (=default), hide minimum absolute // fee UI controls ui->radioCustomAtLeast->setVisible(false); return; } // set pay amounts CoinControlDialog::payAmounts.clear(); CoinControlDialog::fSubtractFeeFromAmount = false; for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); if (entry && !entry->isHidden()) { SendCoinsRecipient rcp = entry->getValue(); CoinControlDialog::payAmounts.append(rcp.amount); if (rcp.fSubtractFeeFromAmount) CoinControlDialog::fSubtractFeeFromAmount = true; } } if (CoinControlDialog::coinControl->HasSelected()) { // actual coin control calculation CoinControlDialog::updateLabels(model, this); // show coin control stats ui->labelCoinControlAutomaticallySelected->hide(); ui->widgetCoinControl->show(); } else { // hide coin control stats ui->labelCoinControlAutomaticallySelected->show(); ui->widgetCoinControl->hide(); ui->labelCoinControlInsuffFunds->hide(); } } SendConfirmationDialog::SendConfirmationDialog(const QString &title, const QString &text, int _secDelay, QWidget *parent) : QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), secDelay(_secDelay) { setDefaultButton(QMessageBox::Cancel); yesButton = button(QMessageBox::Yes); updateYesButton(); connect(&countDownTimer, SIGNAL(timeout()), this, SLOT(countDown())); } int SendConfirmationDialog::exec() { updateYesButton(); countDownTimer.start(1000); return QMessageBox::exec(); } void SendConfirmationDialog::countDown() { secDelay--; updateYesButton(); if (secDelay <= 0) { countDownTimer.stop(); } } void SendConfirmationDialog::updateYesButton() { if (secDelay > 0) { yesButton->setEnabled(false); yesButton->setText(tr("Yes") + " (" + QString::number(secDelay) + ")"); } else { yesButton->setEnabled(true); yesButton->setText(tr("Yes")); } } diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 32e18402b..2cf7ab4ee 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -1,280 +1,280 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "signverifymessagedialog.h" #include "ui_signverifymessagedialog.h" #include "addressbookpage.h" #include "guiutil.h" #include "platformstyle.h" #include "walletmodel.h" -#include "base58.h" +#include "dstencode.h" #include "init.h" #include "validation.h" // For strMessageMagic #include "wallet/wallet.h" #include #include #include SignVerifyMessageDialog::SignVerifyMessageDialog( const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::SignVerifyMessageDialog), model(0), platformStyle(_platformStyle) { ui->setupUi(this); ui->addressBookButton_SM->setIcon( platformStyle->SingleColorIcon(":/icons/address-book")); ui->pasteButton_SM->setIcon( platformStyle->SingleColorIcon(":/icons/editpaste")); ui->copySignatureButton_SM->setIcon( platformStyle->SingleColorIcon(":/icons/editcopy")); ui->signMessageButton_SM->setIcon( platformStyle->SingleColorIcon(":/icons/edit")); ui->clearButton_SM->setIcon( platformStyle->SingleColorIcon(":/icons/remove")); ui->addressBookButton_VM->setIcon( platformStyle->SingleColorIcon(":/icons/address-book")); ui->verifyMessageButton_VM->setIcon( platformStyle->SingleColorIcon(":/icons/transaction_0")); ui->clearButton_VM->setIcon( platformStyle->SingleColorIcon(":/icons/remove")); #if QT_VERSION >= 0x040700 ui->signatureOut_SM->setPlaceholderText( tr("Click \"Sign Message\" to generate signature")); #endif GUIUtil::setupAddressWidget(ui->addressIn_SM, this); GUIUtil::setupAddressWidget(ui->addressIn_VM, this); ui->addressIn_SM->installEventFilter(this); ui->messageIn_SM->installEventFilter(this); ui->signatureOut_SM->installEventFilter(this); ui->addressIn_VM->installEventFilter(this); ui->messageIn_VM->installEventFilter(this); ui->signatureIn_VM->installEventFilter(this); ui->signatureOut_SM->setFont(GUIUtil::fixedPitchFont()); ui->signatureIn_VM->setFont(GUIUtil::fixedPitchFont()); } SignVerifyMessageDialog::~SignVerifyMessageDialog() { delete ui; } void SignVerifyMessageDialog::setModel(WalletModel *_model) { this->model = _model; } void SignVerifyMessageDialog::setAddress_SM(const QString &address) { ui->addressIn_SM->setText(address); ui->messageIn_SM->setFocus(); } void SignVerifyMessageDialog::setAddress_VM(const QString &address) { ui->addressIn_VM->setText(address); ui->messageIn_VM->setFocus(); } void SignVerifyMessageDialog::showTab_SM(bool fShow) { ui->tabWidget->setCurrentIndex(0); if (fShow) this->show(); } void SignVerifyMessageDialog::showTab_VM(bool fShow) { ui->tabWidget->setCurrentIndex(1); if (fShow) this->show(); } void SignVerifyMessageDialog::on_addressBookButton_SM_clicked() { if (model && model->getAddressTableModel()) { AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::ReceivingTab, this); dlg.setModel(model->getAddressTableModel()); if (dlg.exec()) { setAddress_SM(dlg.getReturnValue()); } } } void SignVerifyMessageDialog::on_pasteButton_SM_clicked() { setAddress_SM(QApplication::clipboard()->text()); } void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() { if (!model) return; /* Clear old signature to ensure users don't get confused on error with an * old signature displayed */ ui->signatureOut_SM->clear(); CTxDestination destination = DecodeDestination(ui->addressIn_SM->text().toStdString()); if (!IsValidDestination(destination)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText( tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again.")); return; } const CKeyID *keyID = boost::get(&destination); if (!keyID) { ui->addressIn_SM->setValid(false); ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText( tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again.")); return; } WalletModel::UnlockContext ctx(model->requestUnlock()); if (!ctx.isValid()) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText(tr("Wallet unlock was cancelled.")); return; } CKey key; if (!model->getPrivKey(*keyID, key)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText( tr("Private key for the entered address is not available.")); return; } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << ui->messageIn_SM->document()->toPlainText().toStdString(); std::vector vchSig; if (!key.SignCompact(ss.GetHash(), vchSig)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText(QString("") + tr("Message signing failed.") + QString("")); return; } ui->statusLabel_SM->setStyleSheet("QLabel { color: green; }"); ui->statusLabel_SM->setText(QString("") + tr("Message signed.") + QString("")); ui->signatureOut_SM->setText( QString::fromStdString(EncodeBase64(&vchSig[0], vchSig.size()))); } void SignVerifyMessageDialog::on_copySignatureButton_SM_clicked() { GUIUtil::setClipboard(ui->signatureOut_SM->text()); } void SignVerifyMessageDialog::on_clearButton_SM_clicked() { ui->addressIn_SM->clear(); ui->messageIn_SM->clear(); ui->signatureOut_SM->clear(); ui->statusLabel_SM->clear(); ui->addressIn_SM->setFocus(); } void SignVerifyMessageDialog::on_addressBookButton_VM_clicked() { if (model && model->getAddressTableModel()) { AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::SendingTab, this); dlg.setModel(model->getAddressTableModel()); if (dlg.exec()) { setAddress_VM(dlg.getReturnValue()); } } } void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked() { CTxDestination destination = DecodeDestination(ui->addressIn_VM->text().toStdString()); if (!IsValidDestination(destination)) { ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText( tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again.")); return; } if (!boost::get(&destination)) { ui->addressIn_VM->setValid(false); ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText( tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again.")); return; } bool fInvalid = false; std::vector vchSig = DecodeBase64( ui->signatureIn_VM->text().toStdString().c_str(), &fInvalid); if (fInvalid) { ui->signatureIn_VM->setValid(false); ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText( tr("The signature could not be decoded.") + QString(" ") + tr("Please check the signature and try again.")); return; } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << ui->messageIn_VM->document()->toPlainText().toStdString(); CPubKey pubkey; if (!pubkey.RecoverCompact(ss.GetHash(), vchSig)) { ui->signatureIn_VM->setValid(false); ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText( tr("The signature did not match the message digest.") + QString(" ") + tr("Please check the signature and try again.")); return; } if (!(CTxDestination(pubkey.GetID()) == destination)) { ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText(QString("") + tr("Message verification failed.") + QString("")); return; } ui->statusLabel_VM->setStyleSheet("QLabel { color: green; }"); ui->statusLabel_VM->setText(QString("") + tr("Message verified.") + QString("")); } void SignVerifyMessageDialog::on_clearButton_VM_clicked() { ui->addressIn_VM->clear(); ui->signatureIn_VM->clear(); ui->messageIn_VM->clear(); ui->statusLabel_VM->clear(); ui->addressIn_VM->setFocus(); } bool SignVerifyMessageDialog::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::FocusIn) { if (ui->tabWidget->currentIndex() == 0) { /* Clear status message on focus change */ ui->statusLabel_SM->clear(); /* Select generated signature */ if (object == ui->signatureOut_SM) { ui->signatureOut_SM->selectAll(); return true; } } else if (ui->tabWidget->currentIndex() == 1) { /* Clear status message on focus change */ ui->statusLabel_VM->clear(); } } return QDialog::eventFilter(object, event); } diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 49ff41498..1088ac7cd 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -1,389 +1,389 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "transactiondesc.h" #include "bitcoinunits.h" #include "guiutil.h" #include "paymentserver.h" #include "transactionrecord.h" -#include "base58.h" #include "consensus/consensus.h" +#include "dstencode.h" #include "script/script.h" #include "timedata.h" #include "util.h" #include "validation.h" #include "wallet/db.h" #include "wallet/finaltx.h" #include "wallet/wallet.h" #include #include QString TransactionDesc::FormatTxStatus(const CWalletTx &wtx) { AssertLockHeld(cs_main); if (!CheckFinalTx(wtx)) { if (wtx.tx->nLockTime < LOCKTIME_THRESHOLD) return tr("Open for %n more block(s)", "", wtx.tx->nLockTime - chainActive.Height()); else return tr("Open until %1") .arg(GUIUtil::dateTimeStr(wtx.tx->nLockTime)); } else { int nDepth = wtx.GetDepthInMainChain(); if (nDepth < 0) return tr("conflicted with a transaction with %1 confirmations") .arg(-nDepth); else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) return tr("%1/offline").arg(nDepth); else if (nDepth == 0) return tr("0/unconfirmed, %1") .arg((wtx.InMempool() ? tr("in memory pool") : tr("not in memory pool"))) + (wtx.isAbandoned() ? ", " + tr("abandoned") : ""); else if (nDepth < 6) return tr("%1/unconfirmed").arg(nDepth); else return tr("%1 confirmations").arg(nDepth); } } QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionRecord *rec, int unit) { QString strHTML; LOCK2(cs_main, wallet->cs_wallet); strHTML.reserve(4000); strHTML += ""; int64_t nTime = wtx.GetTxTime(); Amount nCredit = wtx.GetCredit(ISMINE_ALL); Amount nDebit = wtx.GetDebit(ISMINE_ALL); CAmount nNet = (nCredit - nDebit).GetSatoshis(); strHTML += "" + tr("Status") + ": " + FormatTxStatus(wtx); int nRequests = wtx.GetRequestCount(); if (nRequests != -1) { if (nRequests == 0) strHTML += tr(", has not been successfully broadcast yet"); else if (nRequests > 0) strHTML += tr(", broadcast through %n node(s)", "", nRequests); } strHTML += "
"; strHTML += "" + tr("Date") + ": " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "
"; // // From // if (wtx.IsCoinBase()) { strHTML += "" + tr("Source") + ": " + tr("Generated") + "
"; } else if (wtx.mapValue.count("from") && !wtx.mapValue["from"].empty()) { // Online transaction strHTML += "" + tr("From") + ": " + GUIUtil::HtmlEscape(wtx.mapValue["from"]) + "
"; } else { // Offline transaction if (nNet > 0) { // Credit CTxDestination address = DecodeDestination(rec->address); if (IsValidDestination(address)) { if (wallet->mapAddressBook.count(address)) { strHTML += "" + tr("From") + ": " + tr("unknown") + "
"; strHTML += "" + tr("To") + ": "; strHTML += GUIUtil::HtmlEscape(rec->address); QString addressOwned = (::IsMine(*wallet, address) == ISMINE_SPENDABLE) ? tr("own address") : tr("watch-only"); if (!wallet->mapAddressBook[address].name.empty()) strHTML += " (" + addressOwned + ", " + tr("label") + ": " + GUIUtil::HtmlEscape( wallet->mapAddressBook[address].name) + ")"; else strHTML += " (" + addressOwned + ")"; strHTML += "
"; } } } } // // To // if (wtx.mapValue.count("to") && !wtx.mapValue["to"].empty()) { // Online transaction std::string strAddress = wtx.mapValue["to"]; strHTML += "" + tr("To") + ": "; CTxDestination dest = DecodeDestination(strAddress); if (wallet->mapAddressBook.count(dest) && !wallet->mapAddressBook[dest].name.empty()) strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[dest].name) + " "; strHTML += GUIUtil::HtmlEscape(strAddress) + "
"; } // // Amount // if (wtx.IsCoinBase() && nCredit == 0) { // // Coinbase // Amount nUnmatured = 0; for (const CTxOut &txout : wtx.tx->vout) { nUnmatured += wallet->GetCredit(txout, ISMINE_ALL); } strHTML += "" + tr("Credit") + ": "; if (wtx.IsInMainChain()) strHTML += BitcoinUnits::formatHtmlWithUnit( unit, nUnmatured.GetSatoshis()) + " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")"; else strHTML += "(" + tr("not accepted") + ")"; strHTML += "
"; } else if (nNet > 0) { // // Credit // strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "
"; } else { isminetype fAllFromMe = ISMINE_SPENDABLE; for (const CTxIn &txin : wtx.tx->vin) { isminetype mine = wallet->IsMine(txin); if (fAllFromMe > mine) fAllFromMe = mine; } isminetype fAllToMe = ISMINE_SPENDABLE; for (const CTxOut &txout : wtx.tx->vout) { isminetype mine = wallet->IsMine(txout); if (fAllToMe > mine) fAllToMe = mine; } if (fAllFromMe) { if (fAllFromMe & ISMINE_WATCH_ONLY) strHTML += "" + tr("From") + ": " + tr("watch-only") + "
"; // // Debit // for (const CTxOut &txout : wtx.tx->vout) { // Ignore change isminetype toSelf = wallet->IsMine(txout); if ((toSelf == ISMINE_SPENDABLE) && (fAllFromMe == ISMINE_SPENDABLE)) continue; if (!wtx.mapValue.count("to") || wtx.mapValue["to"].empty()) { // Offline transaction CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address)) { strHTML += "" + tr("To") + ": "; if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty()) strHTML += GUIUtil::HtmlEscape( wallet->mapAddressBook[address].name) + " "; strHTML += GUIUtil::HtmlEscape(EncodeDestination(address)); if (toSelf == ISMINE_SPENDABLE) strHTML += " (own address)"; else if (toSelf & ISMINE_WATCH_ONLY) strHTML += " (watch-only)"; strHTML += "
"; } } strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, -txout.nValue.GetSatoshis()) + "
"; if (toSelf) strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, txout.nValue.GetSatoshis()) + "
"; } if (fAllToMe) { // Payment to self Amount nChange = wtx.GetChange(); Amount nValue = nCredit - nChange; strHTML += "" + tr("Total debit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, -nValue.GetSatoshis()) + "
"; strHTML += "" + tr("Total credit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, nValue.GetSatoshis()) + "
"; } Amount nTxFee = nDebit - wtx.tx->GetValueOut(); if (nTxFee > 0) strHTML += "" + tr("Transaction fee") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, -nTxFee.GetSatoshis()) + "
"; } else { // // Mixed debit transaction // for (const CTxIn &txin : wtx.tx->vin) { if (wallet->IsMine(txin)) strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, -wallet->GetDebit(txin, ISMINE_ALL).GetSatoshis()) + "
"; } for (const CTxOut &txout : wtx.tx->vout) { if (wallet->IsMine(txout)) strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, wallet->GetCredit(txout, ISMINE_ALL) .GetSatoshis()) + "
"; } } } strHTML += "" + tr("Net amount") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "
"; // // Message // if (wtx.mapValue.count("message") && !wtx.mapValue["message"].empty()) strHTML += "
" + tr("Message") + ":
" + GUIUtil::HtmlEscape(wtx.mapValue["message"], true) + "
"; if (wtx.mapValue.count("comment") && !wtx.mapValue["comment"].empty()) strHTML += "
" + tr("Comment") + ":
" + GUIUtil::HtmlEscape(wtx.mapValue["comment"], true) + "
"; strHTML += "" + tr("Transaction ID") + ": " + rec->getTxID() + "
"; strHTML += "" + tr("Transaction total size") + ": " + QString::number(wtx.tx->GetTotalSize()) + " bytes
"; strHTML += "" + tr("Output index") + ": " + QString::number(rec->getOutputIndex()) + "
"; // Message from normal bitcoincash:URI (bitcoincash:123...?message=example) for (const std::pair &r : wtx.vOrderForm) { if (r.first == "Message") strHTML += "
" + tr("Message") + ":
" + GUIUtil::HtmlEscape(r.second, true) + "
"; } // // PaymentRequest info: // for (const std::pair &r : wtx.vOrderForm) { if (r.first == "PaymentRequest") { PaymentRequestPlus req; req.parse( QByteArray::fromRawData(r.second.data(), r.second.size())); QString merchant; if (req.getMerchant(PaymentServer::getCertStore(), merchant)) strHTML += "" + tr("Merchant") + ": " + GUIUtil::HtmlEscape(merchant) + "
"; } } if (wtx.IsCoinBase()) { quint32 numBlocksToMaturity = COINBASE_MATURITY + 1; strHTML += "
" + tr("Generated coins must mature %1 blocks before they can be " "spent. When you generated this block, it was broadcast to the " "network to be added to the block chain. If it fails to get " "into the chain, its state will change to \"not accepted\" and " "it won't be spendable. This may occasionally happen if another " "node generates a block within a few seconds of yours.") .arg(QString::number(numBlocksToMaturity)) + "
"; } // // Debug view // if (fDebug) { strHTML += "

" + tr("Debug information") + "

"; for (const CTxIn &txin : wtx.tx->vin) { if (wallet->IsMine(txin)) strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, -wallet->GetDebit(txin, ISMINE_ALL).GetSatoshis()) + "
"; } for (const CTxOut &txout : wtx.tx->vout) { if (wallet->IsMine(txout)) strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, wallet->GetCredit(txout, ISMINE_ALL).GetSatoshis()) + "
"; } strHTML += "
" + tr("Transaction") + ":
"; strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true); strHTML += "
" + tr("Inputs") + ":"; strHTML += "
    "; for (const CTxIn &txin : wtx.tx->vin) { COutPoint prevout = txin.prevout; Coin prev; if (pcoinsTip->GetCoin(prevout, prev)) { strHTML += "
  • "; const CTxOut &vout = prev.GetTxOut(); CTxDestination address; if (ExtractDestination(vout.scriptPubKey, address)) { if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty()) { strHTML += GUIUtil::HtmlEscape( wallet->mapAddressBook[address].name) + " "; } strHTML += QString::fromStdString(EncodeDestination(address)); } strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit( unit, vout.nValue.GetSatoshis()); strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "
  • "; strHTML = strHTML + " IsWatchOnly=" + (wallet->IsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + ""; } } strHTML += "
"; } strHTML += "
"; return strHTML; } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index fe652ed41..2f04d7697 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -1,227 +1,227 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "transactionrecord.h" -#include "base58.h" #include "consensus/consensus.h" +#include "dstencode.h" #include "timedata.h" #include "validation.h" #include "wallet/finaltx.h" #include "wallet/wallet.h" #include /** * Return positive answer if transaction should be shown in list. */ bool TransactionRecord::showTransaction(const CWalletTx &wtx) { if (wtx.IsCoinBase()) { // Ensures we show generated coins / mined transactions at depth 1 if (!wtx.IsInMainChain()) { return false; } } return true; } /** * Decompose CWallet transaction to model transaction records. */ QList TransactionRecord::decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx) { QList parts; int64_t nTime = wtx.GetTxTime(); Amount nCredit = wtx.GetCredit(ISMINE_ALL); Amount nDebit = wtx.GetDebit(ISMINE_ALL); CAmount nNet = (nCredit - nDebit).GetSatoshis(); uint256 hash = wtx.GetId(); std::map mapValue = wtx.mapValue; if (nNet > 0 || wtx.IsCoinBase()) { // // Credit // for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { const CTxOut &txout = wtx.tx->vout[i]; isminetype mine = wallet->IsMine(txout); if (mine) { TransactionRecord sub(hash, nTime); CTxDestination address; sub.idx = i; // vout index sub.credit = txout.nValue.GetSatoshis(); sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY; if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address)) { // Received by Bitcoin Address sub.type = TransactionRecord::RecvWithAddress; sub.address = EncodeDestination(address); } else { // Received by IP connection (deprecated features), or a // multisignature or other non-simple transaction sub.type = TransactionRecord::RecvFromOther; sub.address = mapValue["from"]; } if (wtx.IsCoinBase()) { // Generated sub.type = TransactionRecord::Generated; } parts.append(sub); } } } else { bool involvesWatchAddress = false; isminetype fAllFromMe = ISMINE_SPENDABLE; for (const CTxIn &txin : wtx.tx->vin) { isminetype mine = wallet->IsMine(txin); if (mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true; if (fAllFromMe > mine) fAllFromMe = mine; } isminetype fAllToMe = ISMINE_SPENDABLE; for (const CTxOut &txout : wtx.tx->vout) { isminetype mine = wallet->IsMine(txout); if (mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true; if (fAllToMe > mine) fAllToMe = mine; } if (fAllFromMe && fAllToMe) { // Payment to self CAmount nChange = wtx.GetChange().GetSatoshis(); parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", -(nDebit - nChange).GetSatoshis(), (nCredit - nChange).GetSatoshis())); // maybe pass to TransactionRecord as constructor argument parts.last().involvesWatchAddress = involvesWatchAddress; } else if (fAllFromMe) { // // Debit // Amount nTxFee = nDebit - wtx.tx->GetValueOut(); for (unsigned int nOut = 0; nOut < wtx.tx->vout.size(); nOut++) { const CTxOut &txout = wtx.tx->vout[nOut]; TransactionRecord sub(hash, nTime); sub.idx = nOut; sub.involvesWatchAddress = involvesWatchAddress; if (wallet->IsMine(txout)) { // Ignore parts sent to self, as this is usually the change // from a transaction sent back to our own address. continue; } CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address)) { // Sent to Bitcoin Address sub.type = TransactionRecord::SendToAddress; sub.address = EncodeDestination(address); } else { // Sent to IP, or other non-address transaction like OP_EVAL sub.type = TransactionRecord::SendToOther; sub.address = mapValue["to"]; } CAmount nValue = txout.nValue.GetSatoshis(); /* Add fee to first output */ if (nTxFee > 0) { nValue += nTxFee.GetSatoshis(); nTxFee = 0; } sub.debit = -nValue; parts.append(sub); } } else { // // Mixed debit transaction, can't break down payees // parts.append(TransactionRecord( hash, nTime, TransactionRecord::Other, "", nNet, 0)); parts.last().involvesWatchAddress = involvesWatchAddress; } } return parts; } void TransactionRecord::updateStatus(const CWalletTx &wtx) { AssertLockHeld(cs_main); // Determine transaction status // Find the block the tx is in CBlockIndex *pindex = nullptr; BlockMap::iterator mi = mapBlockIndex.find(wtx.hashBlock); if (mi != mapBlockIndex.end()) pindex = (*mi).second; // Sort order, unrecorded transactions sort to the top status.sortKey = strprintf("%010d-%01d-%010u-%03d", (pindex ? pindex->nHeight : std::numeric_limits::max()), (wtx.IsCoinBase() ? 1 : 0), wtx.nTimeReceived, idx); status.countsForBalance = wtx.IsTrusted() && !(wtx.GetBlocksToMaturity() > 0); status.depth = wtx.GetDepthInMainChain(); status.cur_num_blocks = chainActive.Height(); if (!CheckFinalTx(wtx)) { if (wtx.tx->nLockTime < LOCKTIME_THRESHOLD) { status.status = TransactionStatus::OpenUntilBlock; status.open_for = wtx.tx->nLockTime - chainActive.Height(); } else { status.status = TransactionStatus::OpenUntilDate; status.open_for = wtx.tx->nLockTime; } } // For generated transactions, determine maturity else if (type == TransactionRecord::Generated) { if (wtx.GetBlocksToMaturity() > 0) { status.status = TransactionStatus::Immature; if (wtx.IsInMainChain()) { status.matures_in = wtx.GetBlocksToMaturity(); // Check if the block was requested by anyone if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) status.status = TransactionStatus::MaturesWarning; } else { status.status = TransactionStatus::NotAccepted; } } else { status.status = TransactionStatus::Confirmed; } } else { if (status.depth < 0) { status.status = TransactionStatus::Conflicted; } else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) { status.status = TransactionStatus::Offline; } else if (status.depth == 0) { status.status = TransactionStatus::Unconfirmed; if (wtx.isAbandoned()) status.status = TransactionStatus::Abandoned; } else if (status.depth < RecommendedNumConfirmations) { status.status = TransactionStatus::Confirming; } else { status.status = TransactionStatus::Confirmed; } } } bool TransactionRecord::statusUpdateNeeded() { AssertLockHeld(cs_main); return status.cur_num_blocks != chainActive.Height(); } QString TransactionRecord::getTxID() const { return QString::fromStdString(hash.ToString()); } int TransactionRecord::getOutputIndex() const { return idx; } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index efdf8e940..061802a4a 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1,681 +1,681 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "walletmodel.h" #include "addresstablemodel.h" #include "consensus/validation.h" #include "guiconstants.h" #include "guiutil.h" #include "paymentserver.h" #include "recentrequeststablemodel.h" #include "transactiontablemodel.h" -#include "base58.h" +#include "dstencode.h" #include "keystore.h" #include "net.h" // for g_connman #include "sync.h" #include "ui_interface.h" #include "util.h" // for GetBoolArg #include "validation.h" #include "wallet/wallet.h" #include "wallet/walletdb.h" // for BackupWallet #include #include #include #include WalletModel::WalletModel(const PlatformStyle *platformStyle, CWallet *_wallet, OptionsModel *_optionsModel, QObject *parent) : QObject(parent), wallet(_wallet), optionsModel(_optionsModel), addressTableModel(0), transactionTableModel(0), recentRequestsTableModel(0), cachedBalance(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0), cachedEncryptionStatus(Unencrypted), cachedNumBlocks(0) { fHaveWatchOnly = wallet->HaveWatchOnly(); fForceCheckBalanceChanged = false; addressTableModel = new AddressTableModel(wallet, this); transactionTableModel = new TransactionTableModel(platformStyle, wallet, this); recentRequestsTableModel = new RecentRequestsTableModel(wallet, this); // This timer will be fired repeatedly to update the balance pollTimer = new QTimer(this); connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollBalanceChanged())); pollTimer->start(MODEL_UPDATE_DELAY); subscribeToCoreSignals(); } WalletModel::~WalletModel() { unsubscribeFromCoreSignals(); } CAmount WalletModel::getBalance(const CCoinControl *coinControl) const { if (coinControl) { Amount nBalance = 0; std::vector vCoins; wallet->AvailableCoins(vCoins, true, coinControl); for (const COutput &out : vCoins) { if (out.fSpendable) nBalance += out.tx->tx->vout[out.i].nValue; } return nBalance.GetSatoshis(); } return wallet->GetBalance().GetSatoshis(); } CAmount WalletModel::getUnconfirmedBalance() const { return wallet->GetUnconfirmedBalance().GetSatoshis(); } CAmount WalletModel::getImmatureBalance() const { return wallet->GetImmatureBalance().GetSatoshis(); } bool WalletModel::haveWatchOnly() const { return fHaveWatchOnly; } CAmount WalletModel::getWatchBalance() const { return wallet->GetWatchOnlyBalance().GetSatoshis(); } CAmount WalletModel::getWatchUnconfirmedBalance() const { return wallet->GetUnconfirmedWatchOnlyBalance().GetSatoshis(); } CAmount WalletModel::getWatchImmatureBalance() const { return wallet->GetImmatureWatchOnlyBalance().GetSatoshis(); } void WalletModel::updateStatus() { EncryptionStatus newEncryptionStatus = getEncryptionStatus(); if (cachedEncryptionStatus != newEncryptionStatus) Q_EMIT encryptionStatusChanged(newEncryptionStatus); } void WalletModel::pollBalanceChanged() { // Get required locks upfront. This avoids the GUI from getting stuck on // periodical polls if the core is holding the locks for a longer time - for // example, during a wallet rescan. TRY_LOCK(cs_main, lockMain); if (!lockMain) return; TRY_LOCK(wallet->cs_wallet, lockWallet); if (!lockWallet) return; if (fForceCheckBalanceChanged || chainActive.Height() != cachedNumBlocks) { fForceCheckBalanceChanged = false; // Balance and number of transactions might have changed cachedNumBlocks = chainActive.Height(); checkBalanceChanged(); if (transactionTableModel) transactionTableModel->updateConfirmations(); } } void WalletModel::checkBalanceChanged() { CAmount newBalance = getBalance(); CAmount newUnconfirmedBalance = getUnconfirmedBalance(); CAmount newImmatureBalance = getImmatureBalance(); CAmount newWatchOnlyBalance = 0; CAmount newWatchUnconfBalance = 0; CAmount newWatchImmatureBalance = 0; if (haveWatchOnly()) { newWatchOnlyBalance = getWatchBalance(); newWatchUnconfBalance = getWatchUnconfirmedBalance(); newWatchImmatureBalance = getWatchImmatureBalance(); } if (cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance || cachedWatchOnlyBalance != newWatchOnlyBalance || cachedWatchUnconfBalance != newWatchUnconfBalance || cachedWatchImmatureBalance != newWatchImmatureBalance) { cachedBalance = newBalance; cachedUnconfirmedBalance = newUnconfirmedBalance; cachedImmatureBalance = newImmatureBalance; cachedWatchOnlyBalance = newWatchOnlyBalance; cachedWatchUnconfBalance = newWatchUnconfBalance; cachedWatchImmatureBalance = newWatchImmatureBalance; Q_EMIT balanceChanged(newBalance, newUnconfirmedBalance, newImmatureBalance, newWatchOnlyBalance, newWatchUnconfBalance, newWatchImmatureBalance); } } void WalletModel::updateTransaction() { // Balance and number of transactions might have changed fForceCheckBalanceChanged = true; } void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status) { if (addressTableModel) addressTableModel->updateEntry(address, label, isMine, purpose, status); } void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) { fHaveWatchOnly = fHaveWatchonly; Q_EMIT notifyWatchonlyChanged(fHaveWatchonly); } bool WalletModel::validateAddress(const QString &address) { return IsValidDestinationString(address.toStdString()); } WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl) { CAmount total = 0; bool fSubtractFeeFromAmount = false; QList recipients = transaction.getRecipients(); std::vector vecSend; if (recipients.empty()) { return OK; } // Used to detect duplicates QSet setAddress; int nAddresses = 0; // Pre-check input data for validity for (const SendCoinsRecipient &rcp : recipients) { if (rcp.fSubtractFeeFromAmount) fSubtractFeeFromAmount = true; // PaymentRequest... if (rcp.paymentRequest.IsInitialized()) { CAmount subtotal = 0; const payments::PaymentDetails &details = rcp.paymentRequest.getDetails(); for (int i = 0; i < details.outputs_size(); i++) { const payments::Output &out = details.outputs(i); if (out.amount() <= 0) continue; subtotal += out.amount(); const uint8_t *scriptStr = (const uint8_t *)out.script().data(); CScript scriptPubKey(scriptStr, scriptStr + out.script().size()); CAmount nAmount = out.amount(); CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount}; vecSend.push_back(recipient); } if (subtotal <= 0) { return InvalidAmount; } total += subtotal; } else { // User-entered bitcoin address / amount: if (!validateAddress(rcp.address)) { return InvalidAddress; } if (rcp.amount <= 0) { return InvalidAmount; } setAddress.insert(rcp.address); ++nAddresses; CScript scriptPubKey = GetScriptForDestination( DecodeDestination(rcp.address.toStdString())); CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount}; vecSend.push_back(recipient); total += rcp.amount; } } if (setAddress.size() != nAddresses) { return DuplicateAddress; } CAmount nBalance = getBalance(coinControl); if (total > nBalance) { return AmountExceedsBalance; } { LOCK2(cs_main, wallet->cs_wallet); transaction.newPossibleKeyChange(wallet); Amount nFeeRequired = 0; int nChangePosRet = -1; std::string strFailReason; CWalletTx *newTx = transaction.getTransaction(); CReserveKey *keyChange = transaction.getPossibleKeyChange(); bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl); transaction.setTransactionFee(nFeeRequired.GetSatoshis()); if (fSubtractFeeFromAmount && fCreated) transaction.reassignAmounts(nChangePosRet); if (!fCreated) { if (!fSubtractFeeFromAmount && (total + nFeeRequired.GetSatoshis()) > nBalance) { return SendCoinsReturn(AmountWithFeeExceedsBalance); } Q_EMIT message(tr("Send Coins"), QString::fromStdString(strFailReason), CClientUIInterface::MSG_ERROR); return TransactionCreationFailed; } // reject absurdly high fee. (This can never happen because the wallet // caps the fee at maxTxFee. This merely serves as a belt-and-suspenders // check) if (nFeeRequired > Amount(maxTxFee)) return AbsurdFee; } return SendCoinsReturn(OK); } WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &transaction) { /* store serialized transaction */ QByteArray transaction_array; { LOCK2(cs_main, wallet->cs_wallet); CWalletTx *newTx = transaction.getTransaction(); for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { if (rcp.paymentRequest.IsInitialized()) { // Make sure any payment requests involved are still valid. if (PaymentServer::verifyExpired( rcp.paymentRequest.getDetails())) { return PaymentRequestExpired; } // Store PaymentRequests in wtx.vOrderForm in wallet. std::string key("PaymentRequest"); std::string value; rcp.paymentRequest.SerializeToString(&value); newTx->vOrderForm.push_back(make_pair(key, value)); } else if (!rcp.message.isEmpty()) { // Message from normal bitcoincash:URI // (bitcoincash:123...?message=example) newTx->vOrderForm.push_back( make_pair("Message", rcp.message.toStdString())); } } CReserveKey *keyChange = transaction.getPossibleKeyChange(); CValidationState state; if (!wallet->CommitTransaction(*newTx, *keyChange, g_connman.get(), state)) return SendCoinsReturn( TransactionCommitFailed, QString::fromStdString(state.GetRejectReason())); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << *newTx->tx; transaction_array.append(&(ssTx[0]), ssTx.size()); } // Add addresses / update labels that we've sent to to the address book, and // emit coinsSent signal for each recipient for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { // Don't touch the address book when we have a payment request if (!rcp.paymentRequest.IsInitialized()) { std::string strAddress = rcp.address.toStdString(); CTxDestination dest = DecodeDestination(strAddress); std::string strLabel = rcp.label.toStdString(); { LOCK(wallet->cs_wallet); std::map::iterator mi = wallet->mapAddressBook.find(dest); // Check if we have a new address or an updated label if (mi == wallet->mapAddressBook.end()) { wallet->SetAddressBook(dest, strLabel, "send"); } else if (mi->second.name != strLabel) { // "" means don't change purpose wallet->SetAddressBook(dest, strLabel, ""); } } } Q_EMIT coinsSent(wallet, rcp, transaction_array); } // update balance immediately, otherwise there could be a short noticeable // delay until pollBalanceChanged hits checkBalanceChanged(); return SendCoinsReturn(OK); } OptionsModel *WalletModel::getOptionsModel() { return optionsModel; } AddressTableModel *WalletModel::getAddressTableModel() { return addressTableModel; } TransactionTableModel *WalletModel::getTransactionTableModel() { return transactionTableModel; } RecentRequestsTableModel *WalletModel::getRecentRequestsTableModel() { return recentRequestsTableModel; } WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const { if (!wallet->IsCrypted()) { return Unencrypted; } else if (wallet->IsLocked()) { return Locked; } else { return Unlocked; } } bool WalletModel::setWalletEncrypted(bool encrypted, const SecureString &passphrase) { if (encrypted) { // Encrypt return wallet->EncryptWallet(passphrase); } else { // Decrypt -- TODO; not supported yet return false; } } bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase) { if (locked) { // Lock return wallet->Lock(); } else { // Unlock return wallet->Unlock(passPhrase); } } bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass) { bool retval; { LOCK(wallet->cs_wallet); // Make sure wallet is locked before attempting pass change wallet->Lock(); retval = wallet->ChangeWalletPassphrase(oldPass, newPass); } return retval; } bool WalletModel::backupWallet(const QString &filename) { return wallet->BackupWallet(filename.toLocal8Bit().data()); } // Handlers for core signals static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStore *wallet) { qDebug() << "NotifyKeyStoreStatusChanged"; QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); } static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, const std::string &purpose, ChangeType status) { QString strAddress = QString::fromStdString(EncodeDestination(address)); QString strLabel = QString::fromStdString(label); QString strPurpose = QString::fromStdString(purpose); qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, Q_ARG(QString, strAddress), Q_ARG(QString, strLabel), Q_ARG(bool, isMine), Q_ARG(QString, strPurpose), Q_ARG(int, status)); } static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status) { Q_UNUSED(wallet); Q_UNUSED(hash); Q_UNUSED(status); QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection); } static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress) { // emits signal "showProgress" QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(title)), Q_ARG(int, nProgress)); } static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly) { QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection, Q_ARG(bool, fHaveWatchonly)); } void WalletModel::subscribeToCoreSignals() { // Connect signals to wallet wallet->NotifyStatusChanged.connect( boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); wallet->NotifyAddressBookChanged.connect( boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5, _6)); wallet->NotifyTransactionChanged.connect( boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); wallet->NotifyWatchonlyChanged.connect( boost::bind(NotifyWatchonlyChanged, this, _1)); } void WalletModel::unsubscribeFromCoreSignals() { // Disconnect signals from wallet wallet->NotifyStatusChanged.disconnect( boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); wallet->NotifyAddressBookChanged.disconnect( boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5, _6)); wallet->NotifyTransactionChanged.disconnect( boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); wallet->NotifyWatchonlyChanged.disconnect( boost::bind(NotifyWatchonlyChanged, this, _1)); } // WalletModel::UnlockContext implementation WalletModel::UnlockContext WalletModel::requestUnlock() { bool was_locked = getEncryptionStatus() == Locked; if (was_locked) { // Request UI to unlock wallet Q_EMIT requireUnlock(); } // If wallet is still locked, unlock was failed or cancelled, mark context // as invalid bool valid = getEncryptionStatus() != Locked; return UnlockContext(this, valid, was_locked); } WalletModel::UnlockContext::UnlockContext(WalletModel *_wallet, bool _valid, bool _relock) : wallet(_wallet), valid(_valid), relock(_relock) {} WalletModel::UnlockContext::~UnlockContext() { if (valid && relock) { wallet->setWalletLocked(true); } } void WalletModel::UnlockContext::CopyFrom(const UnlockContext &rhs) { // Transfer context; old object no longer relocks wallet *this = rhs; rhs.relock = false; } bool WalletModel::getPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const { return wallet->GetPubKey(address, vchPubKeyOut); } bool WalletModel::IsSpendable(const CTxDestination &dest) const { return IsMine(*wallet, dest) & ISMINE_SPENDABLE; } bool WalletModel::getPrivKey(const CKeyID &address, CKey &vchPrivKeyOut) const { return wallet->GetKey(address, vchPrivKeyOut); } // returns a list of COutputs from COutPoints void WalletModel::getOutputs(const std::vector &vOutpoints, std::vector &vOutputs) { LOCK2(cs_main, wallet->cs_wallet); for (const COutPoint &outpoint : vOutpoints) { if (!wallet->mapWallet.count(outpoint.hash)) continue; int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain(); if (nDepth < 0) continue; COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true, true); vOutputs.push_back(out); } } bool WalletModel::isSpent(const COutPoint &outpoint) const { LOCK2(cs_main, wallet->cs_wallet); return wallet->IsSpent(outpoint.hash, outpoint.n); } // AvailableCoins + LockedCoins grouped by wallet address (put change in one // group with wallet address) void WalletModel::listCoins( std::map> &mapCoins) const { std::vector vCoins; wallet->AvailableCoins(vCoins); // ListLockedCoins, mapWallet LOCK2(cs_main, wallet->cs_wallet); std::vector vLockedCoins; wallet->ListLockedCoins(vLockedCoins); // add locked coins for (const COutPoint &outpoint : vLockedCoins) { if (!wallet->mapWallet.count(outpoint.hash)) continue; int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain(); if (nDepth < 0) continue; COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true, true); if (outpoint.n < out.tx->tx->vout.size() && wallet->IsMine(out.tx->tx->vout[outpoint.n]) == ISMINE_SPENDABLE) vCoins.push_back(out); } for (const COutput &out : vCoins) { COutput cout = out; while (wallet->IsChange(cout.tx->tx->vout[cout.i]) && cout.tx->tx->vin.size() > 0 && wallet->IsMine(cout.tx->tx->vin[0])) { if (!wallet->mapWallet.count(cout.tx->tx->vin[0].prevout.hash)) break; cout = COutput(&wallet->mapWallet[cout.tx->tx->vin[0].prevout.hash], cout.tx->tx->vin[0].prevout.n, 0, true, true); } CTxDestination address; if (!out.fSpendable || !ExtractDestination(cout.tx->tx->vout[cout.i].scriptPubKey, address)) continue; mapCoins[QString::fromStdString(EncodeDestination(address))].push_back( out); } } bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const { LOCK2(cs_main, wallet->cs_wallet); return wallet->IsLockedCoin(hash, n); } void WalletModel::lockCoin(COutPoint &output) { LOCK2(cs_main, wallet->cs_wallet); wallet->LockCoin(output); } void WalletModel::unlockCoin(COutPoint &output) { LOCK2(cs_main, wallet->cs_wallet); wallet->UnlockCoin(output); } void WalletModel::listLockedCoins(std::vector &vOutpts) { LOCK2(cs_main, wallet->cs_wallet); wallet->ListLockedCoins(vOutpts); } void WalletModel::loadReceiveRequests( std::vector &vReceiveRequests) { LOCK(wallet->cs_wallet); for (const std::pair &item : wallet->mapAddressBook) { for (const std::pair &item2 : item.second.destdata) if (item2.first.size() > 2 && item2.first.substr(0, 2) == "rr") // receive request vReceiveRequests.push_back(item2.second); } } bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest) { CTxDestination dest = DecodeDestination(sAddress); std::stringstream ss; ss << nId; // "rr" prefix = "receive request" in destdata std::string key = "rr" + ss.str(); LOCK(wallet->cs_wallet); if (sRequest.empty()) return wallet->EraseDestData(dest, key); else return wallet->AddDestData(dest, key, sRequest); } bool WalletModel::transactionCanBeAbandoned(uint256 hash) const { LOCK2(cs_main, wallet->cs_wallet); const CWalletTx *wtx = wallet->GetWalletTx(hash); if (!wtx || wtx->isAbandoned() || wtx->GetDepthInMainChain() > 0 || wtx->InMempool()) return false; return true; } bool WalletModel::abandonTransaction(uint256 hash) const { LOCK2(cs_main, wallet->cs_wallet); return wallet->AbandonTransaction(hash); } bool WalletModel::isWalletEnabled() { return !GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET); } bool WalletModel::hdEnabled() const { return wallet->IsHDEnabled(); } int WalletModel::getDefaultConfirmTarget() const { return nTxConfirmTarget; } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 99d73195e..08f9cf580 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1,1097 +1,1097 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "amount.h" -#include "base58.h" #include "chain.h" #include "chainparams.h" #include "config.h" #include "consensus/consensus.h" #include "consensus/params.h" #include "consensus/validation.h" #include "core_io.h" +#include "dstencode.h" #include "init.h" #include "miner.h" #include "net.h" #include "policy/policy.h" #include "pow.h" #include "rpc/blockchain.h" #include "rpc/server.h" #include "txmempool.h" #include "util.h" #include "utilstrencodings.h" #include "validation.h" #include "validationinterface.h" #include #include #include /** * Return average network hashes per second based on the last 'lookup' blocks, * or from the last difficulty change if 'lookup' is nonpositive. If 'height' is * nonnegative, compute the estimate at the time when a given block was found. */ static UniValue GetNetworkHashPS(int lookup, int height) { CBlockIndex *pb = chainActive.Tip(); if (height >= 0 && height < chainActive.Height()) { pb = chainActive[height]; } if (pb == nullptr || !pb->nHeight) { return 0; } // If lookup is -1, then use blocks since last difficulty change. if (lookup <= 0) { lookup = pb->nHeight % Params().GetConsensus().DifficultyAdjustmentInterval() + 1; } // If lookup is larger than chain, then set it to chain length. if (lookup > pb->nHeight) { lookup = pb->nHeight; } CBlockIndex *pb0 = pb; int64_t minTime = pb0->GetBlockTime(); int64_t maxTime = minTime; for (int i = 0; i < lookup; i++) { pb0 = pb0->pprev; int64_t time = pb0->GetBlockTime(); minTime = std::min(time, minTime); maxTime = std::max(time, maxTime); } // In case there's a situation where minTime == maxTime, we don't want a // divide by zero exception. if (minTime == maxTime) { return 0; } arith_uint256 workDiff = pb->nChainWork - pb0->nChainWork; int64_t timeDiff = maxTime - minTime; return workDiff.getdouble() / timeDiff; } static UniValue getnetworkhashps(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 2) { throw std::runtime_error( "getnetworkhashps ( nblocks height )\n" "\nReturns the estimated network hashes per second based on the " "last n blocks.\n" "Pass in [blocks] to override # of blocks, -1 specifies since last " "difficulty change.\n" "Pass in [height] to estimate the network speed at the time when a " "certain block was found.\n" "\nArguments:\n" "1. nblocks (numeric, optional, default=120) The number of " "blocks, or -1 for blocks since last difficulty change.\n" "2. height (numeric, optional, default=-1) To estimate at the " "time of the given height.\n" "\nResult:\n" "x (numeric) Hashes per second estimated\n" "\nExamples:\n" + HelpExampleCli("getnetworkhashps", "") + HelpExampleRpc("getnetworkhashps", "")); } LOCK(cs_main); return GetNetworkHashPS( request.params.size() > 0 ? request.params[0].get_int() : 120, request.params.size() > 1 ? request.params[1].get_int() : -1); } static UniValue generateBlocks(const Config &config, std::shared_ptr coinbaseScript, int nGenerate, uint64_t nMaxTries, bool keepScript) { static const int nInnerLoopCount = 0x100000; int nHeightStart = 0; int nHeightEnd = 0; int nHeight = 0; { // Don't keep cs_main locked. LOCK(cs_main); nHeightStart = chainActive.Height(); nHeight = nHeightStart; nHeightEnd = nHeightStart + nGenerate; } unsigned int nExtraNonce = 0; UniValue blockHashes(UniValue::VARR); while (nHeight < nHeightEnd) { std::unique_ptr pblocktemplate( BlockAssembler(config, Params()) .CreateNewBlock(coinbaseScript->reserveScript)); if (!pblocktemplate.get()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); } CBlock *pblock = &pblocktemplate->block; { LOCK(cs_main); IncrementExtraNonce(config, pblock, chainActive.Tip(), nExtraNonce); } while (nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) { ++pblock->nNonce; --nMaxTries; } if (nMaxTries == 0) { break; } if (pblock->nNonce == nInnerLoopCount) { continue; } std::shared_ptr shared_pblock = std::make_shared(*pblock); if (!ProcessNewBlock(config, shared_pblock, true, nullptr)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); } ++nHeight; blockHashes.push_back(pblock->GetHash().GetHex()); // Mark script as important because it was used at least for one // coinbase output if the script came from the wallet. if (keepScript) { coinbaseScript->KeepScript(); } } return blockHashes; } static UniValue generate(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "generate nblocks ( maxtries )\n" "\nMine up to nblocks blocks immediately (before the RPC call " "returns)\n" "\nArguments:\n" "1. nblocks (numeric, required) How many blocks are generated " "immediately.\n" "2. maxtries (numeric, optional) How many iterations to try " "(default = 1000000).\n" "\nResult:\n" "[ blockhashes ] (array) hashes of blocks generated\n" "\nExamples:\n" "\nGenerate 11 blocks\n" + HelpExampleCli("generate", "11")); } int nGenerate = request.params[0].get_int(); uint64_t nMaxTries = 1000000; if (request.params.size() > 1) { nMaxTries = request.params[1].get_int(); } std::shared_ptr coinbaseScript; GetMainSignals().ScriptForMining(coinbaseScript); // If the keypool is exhausted, no script is returned at all. Catch this. if (!coinbaseScript) { throw JSONRPCError( RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } // Throw an error if no script was provided. if (coinbaseScript->reserveScript.empty()) { throw JSONRPCError( RPC_INTERNAL_ERROR, "No coinbase script available (mining requires a wallet)"); } return generateBlocks(config, coinbaseScript, nGenerate, nMaxTries, true); } static UniValue generatetoaddress(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { throw std::runtime_error( "generatetoaddress nblocks address (maxtries)\n" "\nMine blocks immediately to a specified address (before the RPC " "call returns)\n" "\nArguments:\n" "1. nblocks (numeric, required) How many blocks are generated " "immediately.\n" "2. address (string, required) The address to send the newly " "generated bitcoin to.\n" "3. maxtries (numeric, optional) How many iterations to try " "(default = 1000000).\n" "\nResult:\n" "[ blockhashes ] (array) hashes of blocks generated\n" "\nExamples:\n" "\nGenerate 11 blocks to myaddress\n" + HelpExampleCli("generatetoaddress", "11 \"myaddress\"")); } int nGenerate = request.params[0].get_int(); uint64_t nMaxTries = 1000000; if (request.params.size() > 2) { nMaxTries = request.params[2].get_int(); } CTxDestination destination = DecodeDestination(request.params[1].get_str()); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid address"); } std::shared_ptr coinbaseScript(new CReserveScript()); coinbaseScript->reserveScript = GetScriptForDestination(destination); return generateBlocks(config, coinbaseScript, nGenerate, nMaxTries, false); } static UniValue getmininginfo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( "getmininginfo\n" "\nReturns a json object containing mining-related information." "\nResult:\n" "{\n" " \"blocks\": nnn, (numeric) The current block\n" " \"currentblocksize\": nnn, (numeric) The last block size\n" " \"currentblocktx\": nnn, (numeric) The last block " "transaction\n" " \"difficulty\": xxx.xxxxx (numeric) The current difficulty\n" " \"errors\": \"...\" (string) Current errors\n" " \"networkhashps\": nnn, (numeric) The network hashes per " "second\n" " \"pooledtx\": n (numeric) The size of the mempool\n" " \"chain\": \"xxxx\", (string) current network name as " "defined in BIP70 (main, test, regtest)\n" "}\n" "\nExamples:\n" + HelpExampleCli("getmininginfo", "") + HelpExampleRpc("getmininginfo", "")); } LOCK(cs_main); UniValue obj(UniValue::VOBJ); obj.push_back(Pair("blocks", int(chainActive.Height()))); obj.push_back(Pair("currentblocksize", uint64_t(nLastBlockSize))); obj.push_back(Pair("currentblocktx", uint64_t(nLastBlockTx))); obj.push_back(Pair("difficulty", double(GetDifficulty(chainActive.Tip())))); obj.push_back(Pair("blockprioritypercentage", uint8_t(GetArg("-blockprioritypercentage", DEFAULT_BLOCK_PRIORITY_PERCENTAGE)))); obj.push_back(Pair("errors", GetWarnings("statusbar"))); obj.push_back(Pair("networkhashps", getnetworkhashps(config, request))); obj.push_back(Pair("pooledtx", uint64_t(mempool.size()))); obj.push_back(Pair("chain", Params().NetworkIDString())); return obj; } // NOTE: Unlike wallet RPC (which use BCC values), mining RPCs follow GBT (BIP // 22) in using satoshi amounts static UniValue prioritisetransaction(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 3) { throw std::runtime_error( "prioritisetransaction \n" "Accepts the transaction into mined blocks at a higher (or lower) " "priority\n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction id.\n" "2. priority_delta (numeric, required) The priority to add or " "subtract.\n" " The transaction selection algorithm considers " "the tx as it would have a higher priority.\n" " (priority of a transaction is calculated: " "coinage * value_in_satoshis / txsize) \n" "3. fee_delta (numeric, required) The fee value (in satoshis) " "to add (or subtract, if negative).\n" " The fee is not actually paid, only the " "algorithm for selecting transactions into a block\n" " considers the transaction as it would have paid " "a higher (or lower) fee.\n" "\nResult:\n" "true (boolean) Returns true\n" "\nExamples:\n" + HelpExampleCli("prioritisetransaction", "\"txid\" 0.0 10000") + HelpExampleRpc("prioritisetransaction", "\"txid\", 0.0, 10000")); } LOCK(cs_main); uint256 hash = ParseHashStr(request.params[0].get_str(), "txid"); Amount nAmount = request.params[2].get_int64(); mempool.PrioritiseTransaction(hash, request.params[0].get_str(), request.params[1].get_real(), nAmount); return true; } // NOTE: Assumes a conclusive result; if result is inconclusive, it must be // handled by caller static UniValue BIP22ValidationResult(const Config &config, const CValidationState &state) { if (state.IsValid()) { return NullUniValue; } std::string strRejectReason = state.GetRejectReason(); if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, strRejectReason); } if (state.IsInvalid()) { if (strRejectReason.empty()) { return "rejected"; } return strRejectReason; } // Should be impossible. return "valid?"; } std::string gbt_vb_name(const Consensus::DeploymentPos pos) { const struct BIP9DeploymentInfo &vbinfo = VersionBitsDeploymentInfo[pos]; std::string s = vbinfo.name; if (!vbinfo.gbt_force) { s.insert(s.begin(), '!'); } return s; } static UniValue getblocktemplate(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( "getblocktemplate ( TemplateRequest )\n" "\nIf the request parameters include a 'mode' key, that is used to " "explicitly select between the default 'template' request or a " "'proposal'.\n" "It returns data needed to construct a block to work on.\n" "For full specification, see BIPs 22, 23, 9, and 145:\n" " " "https://github.com/bitcoin/bips/blob/master/bip-0022.mediawiki\n" " " "https://github.com/bitcoin/bips/blob/master/bip-0023.mediawiki\n" " " "https://github.com/bitcoin/bips/blob/master/" "bip-0009.mediawiki#getblocktemplate_changes\n" " " "https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki\n" "\nArguments:\n" "1. template_request (json object, optional) A json object " "in the following spec\n" " {\n" " \"mode\":\"template\" (string, optional) This must be " "set to \"template\", \"proposal\" (see BIP 23), or omitted\n" " \"capabilities\":[ (array, optional) A list of " "strings\n" " \"support\" (string) client side supported " "feature, 'longpoll', 'coinbasetxn', 'coinbasevalue', 'proposal', " "'serverlist', 'workid'\n" " ,...\n" " ],\n" " \"rules\":[ (array, optional) A list of " "strings\n" " \"support\" (string) client side supported " "softfork deployment\n" " ,...\n" " ]\n" " }\n" "\n" "\nResult:\n" "{\n" " \"version\" : n, (numeric) The preferred " "block version\n" " \"rules\" : [ \"rulename\", ... ], (array of strings) " "specific block rules that are to be enforced\n" " \"vbavailable\" : { (json object) set of " "pending, supported versionbit (BIP 9) softfork deployments\n" " \"rulename\" : bitnumber (numeric) identifies the " "bit number as indicating acceptance and readiness for the named " "softfork rule\n" " ,...\n" " },\n" " \"vbrequired\" : n, (numeric) bit mask of " "versionbits the server requires set in submissions\n" " \"previousblockhash\" : \"xxxx\", (string) The hash of " "current highest block\n" " \"transactions\" : [ (array) contents of " "non-coinbase transactions that should be included in the next " "block\n" " {\n" " \"data\" : \"xxxx\", (string) transaction " "data encoded in hexadecimal (byte-for-byte)\n" " \"txid\" : \"xxxx\", (string) transaction id " "encoded in little-endian hexadecimal\n" " \"hash\" : \"xxxx\", (string) hash encoded " "in little-endian hexadecimal (including witness data)\n" " \"depends\" : [ (array) array of numbers " "\n" " n (numeric) transactions " "before this one (by 1-based index in 'transactions' list) that " "must be present in the final block if this one is\n" " ,...\n" " ],\n" " \"fee\": n, (numeric) difference in " "value between transaction inputs and outputs (in Satoshis); for " "coinbase transactions, this is a negative Number of the total " "collected block fees (ie, not including the block subsidy); if " "key is not present, fee is unknown and clients MUST NOT assume " "there isn't one\n" " \"sigops\" : n, (numeric) total SigOps " "cost, as counted for purposes of block limits; if key is not " "present, sigop cost is unknown and clients MUST NOT assume it is " "zero\n" " \"required\" : true|false (boolean) if provided and " "true, this transaction must be in the final block\n" " }\n" " ,...\n" " ],\n" " \"coinbaseaux\" : { (json object) data that " "should be included in the coinbase's scriptSig content\n" " \"flags\" : \"xx\" (string) key name is to " "be ignored, and value included in scriptSig\n" " },\n" " \"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" " \"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" " ],\n" " \"noncerange\" : \"00000000ffffffff\",(string) A range of valid " "nonces\n" " \"sigoplimit\" : n, (numeric) limit of sigops " "in blocks\n" " \"sizelimit\" : n, (numeric) limit of block " "size\n" " \"curtime\" : ttt, (numeric) current timestamp " "in seconds since epoch (Jan 1 1970 GMT)\n" " \"bits\" : \"xxxxxxxx\", (string) compressed " "target of next block\n" " \"height\" : n (numeric) The height of the " "next block\n" "}\n" "\nExamples:\n" + HelpExampleCli("getblocktemplate", "") + HelpExampleRpc("getblocktemplate", "")); } LOCK(cs_main); std::string strMode = "template"; UniValue lpval = NullUniValue; std::set setClientRules; int64_t nMaxVersionPreVB = -1; if (request.params.size() > 0) { const UniValue &oparam = request.params[0].get_obj(); const UniValue &modeval = find_value(oparam, "mode"); if (modeval.isStr()) { strMode = modeval.get_str(); } else if (modeval.isNull()) { /* Do nothing */ } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); } lpval = find_value(oparam, "longpollid"); if (strMode == "proposal") { const UniValue &dataval = find_value(oparam, "data"); if (!dataval.isStr()) { throw JSONRPCError(RPC_TYPE_ERROR, "Missing data String key for proposal"); } CBlock block; if (!DecodeHexBlk(block, dataval.get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); } uint256 hash = block.GetHash(); BlockMap::iterator mi = mapBlockIndex.find(hash); if (mi != mapBlockIndex.end()) { CBlockIndex *pindex = mi->second; if (pindex->IsValid(BLOCK_VALID_SCRIPTS)) { return "duplicate"; } if (pindex->nStatus & BLOCK_FAILED_MASK) { return "duplicate-invalid"; } return "duplicate-inconclusive"; } CBlockIndex *const pindexPrev = chainActive.Tip(); // TestBlockValidity only supports blocks built on the current Tip if (block.hashPrevBlock != pindexPrev->GetBlockHash()) { return "inconclusive-not-best-prevblk"; } CValidationState state; TestBlockValidity(config, state, Params(), block, pindexPrev, false, true); return BIP22ValidationResult(config, state); } const UniValue &aClientRules = find_value(oparam, "rules"); if (aClientRules.isArray()) { for (size_t i = 0; i < aClientRules.size(); ++i) { const UniValue &v = aClientRules[i]; setClientRules.insert(v.get_str()); } } else { // NOTE: It is important that this NOT be read if versionbits is // supported const UniValue &uvMaxVersion = find_value(oparam, "maxversion"); if (uvMaxVersion.isNum()) { nMaxVersionPreVB = uvMaxVersion.get_int64(); } } } if (strMode != "template") { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); } if (!g_connman) { throw JSONRPCError( RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } if (g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) { throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Bitcoin is not connected!"); } if (IsInitialBlockDownload()) { throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Bitcoin is downloading blocks..."); } static unsigned int nTransactionsUpdatedLast; if (!lpval.isNull()) { // Wait to respond until either the best block changes, OR a minute has // passed and there are more transactions uint256 hashWatchedChain; boost::system_time checktxtime; unsigned int nTransactionsUpdatedLastLP; if (lpval.isStr()) { // Format: std::string lpstr = lpval.get_str(); hashWatchedChain.SetHex(lpstr.substr(0, 64)); nTransactionsUpdatedLastLP = atoi64(lpstr.substr(64)); } else { // NOTE: Spec does not specify behaviour for non-string longpollid, // but this makes testing easier hashWatchedChain = chainActive.Tip()->GetBlockHash(); nTransactionsUpdatedLastLP = nTransactionsUpdatedLast; } // Release the wallet and main lock while waiting LEAVE_CRITICAL_SECTION(cs_main); { checktxtime = boost::get_system_time() + boost::posix_time::minutes(1); boost::unique_lock lock(csBestBlock); while (chainActive.Tip()->GetBlockHash() == hashWatchedChain && IsRPCRunning()) { if (!cvBlockChange.timed_wait(lock, checktxtime)) { // Timeout: Check transactions for update if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP) { break; } checktxtime += boost::posix_time::seconds(10); } } } ENTER_CRITICAL_SECTION(cs_main); if (!IsRPCRunning()) { throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down"); } // TODO: Maybe recheck connections/IBD and (if something wrong) send an // expires-immediately template to stop miners? } // Update block static CBlockIndex *pindexPrev; static int64_t nStart; static std::unique_ptr pblocktemplate; if (pindexPrev != chainActive.Tip() || (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5)) { // Clear pindexPrev so future calls make a new block, despite any // failures from here on pindexPrev = nullptr; // Store the pindexBest used before CreateNewBlock, to avoid races nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); CBlockIndex *pindexPrevNew = chainActive.Tip(); nStart = GetTime(); // Create new block CScript scriptDummy = CScript() << OP_TRUE; pblocktemplate = BlockAssembler(config, Params()).CreateNewBlock(scriptDummy); if (!pblocktemplate) { throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); } // Need to update only after we know CreateNewBlock succeeded pindexPrev = pindexPrevNew; } CBlock *pblock = &pblocktemplate->block; // pointer for convenience const Consensus::Params &consensusParams = Params().GetConsensus(); // Update nTime UpdateTime(pblock, consensusParams, pindexPrev); pblock->nNonce = 0; UniValue aCaps(UniValue::VARR); aCaps.push_back("proposal"); UniValue transactions(UniValue::VARR); std::map setTxIndex; int i = 0; for (const auto &it : pblock->vtx) { const CTransaction &tx = *it; uint256 txId = tx.GetId(); setTxIndex[txId] = i++; if (tx.IsCoinBase()) { continue; } UniValue entry(UniValue::VOBJ); entry.push_back(Pair("data", EncodeHexTx(tx))); entry.push_back(Pair("txid", txId.GetHex())); entry.push_back(Pair("hash", tx.GetHash().GetHex())); UniValue deps(UniValue::VARR); for (const CTxIn &in : tx.vin) { if (setTxIndex.count(in.prevout.hash)) deps.push_back(setTxIndex[in.prevout.hash]); } entry.push_back(Pair("depends", deps)); int index_in_template = i - 1; entry.push_back(Pair( "fee", pblocktemplate->vTxFees[index_in_template].GetSatoshis())); int64_t nTxSigOps = pblocktemplate->vTxSigOpsCount[index_in_template]; entry.push_back(Pair("sigops", nTxSigOps)); transactions.push_back(entry); } UniValue aux(UniValue::VOBJ); aux.push_back( Pair("flags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end()))); arith_uint256 hashTarget = arith_uint256().SetCompact(pblock->nBits); UniValue aMutable(UniValue::VARR); aMutable.push_back("time"); aMutable.push_back("transactions"); aMutable.push_back("prevblock"); UniValue result(UniValue::VOBJ); result.push_back(Pair("capabilities", aCaps)); UniValue aRules(UniValue::VARR); UniValue vbavailable(UniValue::VOBJ); for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { Consensus::DeploymentPos pos = Consensus::DeploymentPos(j); ThresholdState state = VersionBitsState(pindexPrev, consensusParams, pos, versionbitscache); switch (state) { case THRESHOLD_DEFINED: case THRESHOLD_FAILED: // Not exposed to GBT at all break; case THRESHOLD_LOCKED_IN: // Ensure bit is set in block version pblock->nVersion |= VersionBitsMask(consensusParams, pos); // FALLTHROUGH to get vbavailable set... case THRESHOLD_STARTED: { const struct BIP9DeploymentInfo &vbinfo = VersionBitsDeploymentInfo[pos]; vbavailable.push_back(Pair( gbt_vb_name(pos), consensusParams.vDeployments[pos].bit)); if (setClientRules.find(vbinfo.name) == setClientRules.end()) { if (!vbinfo.gbt_force) { // If the client doesn't support this, don't indicate it // in the [default] version pblock->nVersion &= ~VersionBitsMask(consensusParams, pos); } } break; } case THRESHOLD_ACTIVE: { // Add to rules only const struct BIP9DeploymentInfo &vbinfo = VersionBitsDeploymentInfo[pos]; aRules.push_back(gbt_vb_name(pos)); if (setClientRules.find(vbinfo.name) == setClientRules.end()) { // Not supported by the client; make sure it's safe to // proceed if (!vbinfo.gbt_force) { // If we do anything other than throw an exception here, // be sure version/force isn't sent to old clients throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("Support for '%s' rule requires explicit " "client support", vbinfo.name)); } } break; } } } result.push_back(Pair("version", pblock->nVersion)); result.push_back(Pair("rules", aRules)); result.push_back(Pair("vbavailable", vbavailable)); result.push_back(Pair("vbrequired", int(0))); if (nMaxVersionPreVB >= 2) { // If VB is supported by the client, nMaxVersionPreVB is -1, so we won't // get here. Because BIP 34 changed how the generation transaction is // serialized, we can only use version/force back to v2 blocks. This is // safe to do [otherwise-]unconditionally only because we are throwing // an exception above if a non-force deployment gets activated. Note // that this can probably also be removed entirely after the first BIP9 // non-force deployment (ie, probably segwit) gets activated. aMutable.push_back("version/force"); } result.push_back(Pair("previousblockhash", pblock->hashPrevBlock.GetHex())); result.push_back(Pair("transactions", transactions)); result.push_back(Pair("coinbaseaux", aux)); result.push_back( Pair("coinbasevalue", (int64_t)pblock->vtx[0]->vout[0].nValue.GetSatoshis())); result.push_back( Pair("longpollid", chainActive.Tip()->GetBlockHash().GetHex() + i64tostr(nTransactionsUpdatedLast))); result.push_back(Pair("target", hashTarget.GetHex())); result.push_back( Pair("mintime", (int64_t)pindexPrev->GetMedianTimePast() + 1)); result.push_back(Pair("mutable", aMutable)); result.push_back(Pair("noncerange", "00000000ffffffff")); // FIXME: Allow for mining block greater than 1M. result.push_back( Pair("sigoplimit", GetMaxBlockSigOpsCount(DEFAULT_MAX_BLOCK_SIZE))); result.push_back(Pair("sizelimit", DEFAULT_MAX_BLOCK_SIZE)); result.push_back(Pair("curtime", pblock->GetBlockTime())); result.push_back(Pair("bits", strprintf("%08x", pblock->nBits))); result.push_back(Pair("height", (int64_t)(pindexPrev->nHeight + 1))); return result; } class submitblock_StateCatcher : public CValidationInterface { public: uint256 hash; bool found; CValidationState state; submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), found(false), state() {} protected: virtual void BlockChecked(const CBlock &block, const CValidationState &stateIn) { if (block.GetHash() != hash) { return; } found = true; state = stateIn; } }; static UniValue submitblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "submitblock \"hexdata\" ( \"jsonparametersobject\" )\n" "\nAttempts to submit new block to network.\n" "The 'jsonparametersobject' parameter is currently ignored.\n" "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n" "\nArguments\n" "1. \"hexdata\" (string, required) the hex-encoded block " "data to submit\n" "2. \"parameters\" (string, optional) object of optional " "parameters\n" " {\n" " \"workid\" : \"id\" (string, optional) if the server " "provided a workid, it MUST be included with submissions\n" " }\n" "\nResult:\n" "\nExamples:\n" + HelpExampleCli("submitblock", "\"mydata\"") + HelpExampleRpc("submitblock", "\"mydata\"")); } std::shared_ptr blockptr = std::make_shared(); CBlock &block = *blockptr; if (!DecodeHexBlk(block, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); } if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block does not start with a coinbase"); } uint256 hash = block.GetHash(); bool fBlockPresent = false; { LOCK(cs_main); BlockMap::iterator mi = mapBlockIndex.find(hash); if (mi != mapBlockIndex.end()) { CBlockIndex *pindex = mi->second; if (pindex->IsValid(BLOCK_VALID_SCRIPTS)) { return "duplicate"; } if (pindex->nStatus & BLOCK_FAILED_MASK) { return "duplicate-invalid"; } // Otherwise, we might only have the header - process the block // before returning fBlockPresent = true; } } submitblock_StateCatcher sc(block.GetHash()); RegisterValidationInterface(&sc); bool fAccepted = ProcessNewBlock(config, blockptr, true, nullptr); UnregisterValidationInterface(&sc); if (fBlockPresent) { if (fAccepted && !sc.found) { return "duplicate-inconclusive"; } return "duplicate"; } if (!sc.found) { return "inconclusive"; } return BIP22ValidationResult(config, sc.state); } static UniValue estimatefee(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "estimatefee nblocks\n" "\nEstimates the approximate fee per kilobyte needed for a " "transaction to begin\n" "confirmation within nblocks blocks.\n" "\nArguments:\n" "1. nblocks (numeric, required)\n" "\nResult:\n" "n (numeric) estimated fee-per-kilobyte\n" "\n" "A negative value is returned if not enough transactions and " "blocks\n" "have been observed to make an estimate.\n" "-1 is always returned for nblocks == 1 as it is impossible to " "calculate\n" "a fee that is high enough to get reliably included in the next " "block.\n" "\nExample:\n" + HelpExampleCli("estimatefee", "6")); } RPCTypeCheck(request.params, {UniValue::VNUM}); int nBlocks = request.params[0].get_int(); if (nBlocks < 1) { nBlocks = 1; } CFeeRate feeRate = mempool.estimateFee(nBlocks); if (feeRate == CFeeRate(0)) { return -1.0; } return ValueFromAmount(feeRate.GetFeePerK()); } static UniValue estimatepriority(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "estimatepriority nblocks\n" "\nDEPRECATED. Estimates the approximate priority " "a zero-fee transaction needs to begin\n" "confirmation within nblocks blocks.\n" "\nArguments:\n" "1. nblocks (numeric, required)\n" "\nResult:\n" "n (numeric) estimated priority\n" "\n" "A negative value is returned if not enough " "transactions and blocks\n" "have been observed to make an estimate.\n" "\nExample:\n" + HelpExampleCli("estimatepriority", "6")); } RPCTypeCheck(request.params, {UniValue::VNUM}); int nBlocks = request.params[0].get_int(); if (nBlocks < 1) { nBlocks = 1; } return mempool.estimatePriority(nBlocks); } static UniValue estimatesmartfee(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "estimatesmartfee nblocks\n" "\nWARNING: This interface is unstable and may disappear or " "change!\n" "\nEstimates the approximate fee per kilobyte needed for a " "transaction to begin\n" "confirmation within nblocks blocks if possible and return the " "number of blocks\n" "for which the estimate is valid.\n" "\nArguments:\n" "1. nblocks (numeric)\n" "\nResult:\n" "{\n" " \"feerate\" : x.x, (numeric) estimate fee-per-kilobyte (in " "BCC)\n" " \"blocks\" : n (numeric) block number where estimate " "was found\n" "}\n" "\n" "A negative value is returned if not enough transactions and " "blocks\n" "have been observed to make an estimate for any number of blocks.\n" "However it will not return a value below the mempool reject fee.\n" "\nExample:\n" + HelpExampleCli("estimatesmartfee", "6")); } RPCTypeCheck(request.params, {UniValue::VNUM}); int nBlocks = request.params[0].get_int(); UniValue result(UniValue::VOBJ); int answerFound; CFeeRate feeRate = mempool.estimateSmartFee(nBlocks, &answerFound); result.push_back(Pair( "feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK()))); result.push_back(Pair("blocks", answerFound)); return result; } static UniValue estimatesmartpriority(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "estimatesmartpriority nblocks\n" "\nDEPRECATED. WARNING: This interface is unstable and may " "disappear or change!\n" "\nEstimates the approximate priority a zero-fee transaction needs " "to begin\n" "confirmation within nblocks blocks if possible and return the " "number of blocks\n" "for which the estimate is valid.\n" "\nArguments:\n" "1. nblocks (numeric, required)\n" "\nResult:\n" "{\n" " \"priority\" : x.x, (numeric) estimated priority\n" " \"blocks\" : n (numeric) block number where estimate " "was found\n" "}\n" "\n" "A negative value is returned if not enough transactions and " "blocks\n" "have been observed to make an estimate for any number of blocks.\n" "However if the mempool reject fee is set it will return 1e9 * " "MAX_MONEY.\n" "\nExample:\n" + HelpExampleCli("estimatesmartpriority", "6")); } RPCTypeCheck(request.params, {UniValue::VNUM}); int nBlocks = request.params[0].get_int(); UniValue result(UniValue::VOBJ); int answerFound; double priority = mempool.estimateSmartPriority(nBlocks, &answerFound); result.push_back(Pair("priority", priority)); result.push_back(Pair("blocks", answerFound)); return result; } // clang-format off static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // ---------- ------------------------ ---------------------- ---------- {"mining", "getnetworkhashps", getnetworkhashps, true, {"nblocks", "height"}}, {"mining", "getmininginfo", getmininginfo, true, {}}, {"mining", "prioritisetransaction", prioritisetransaction, true, {"txid", "priority_delta", "fee_delta"}}, {"mining", "getblocktemplate", getblocktemplate, true, {"template_request"}}, {"mining", "submitblock", submitblock, true, {"hexdata", "parameters"}}, {"generating", "generate", generate, true, {"nblocks", "maxtries"}}, {"generating", "generatetoaddress", generatetoaddress, true, {"nblocks", "address", "maxtries"}}, {"util", "estimatefee", estimatefee, true, {"nblocks"}}, {"util", "estimatepriority", estimatepriority, true, {"nblocks"}}, {"util", "estimatesmartfee", estimatesmartfee, true, {"nblocks"}}, {"util", "estimatesmartpriority", estimatesmartpriority, true, {"nblocks"}}, }; // clang-format on void RegisterMiningRPCCommands(CRPCTable &t) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) t.appendCommand(commands[vcidx].name, &commands[vcidx]); } diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 1e54ea5e2..e1dd9015c 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1,606 +1,606 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "rpc/misc.h" - #include "base58.h" #include "clientversion.h" #include "config.h" +#include "dstencode.h" #include "init.h" #include "net.h" #include "netbase.h" #include "rpc/blockchain.h" #include "rpc/server.h" #include "timedata.h" #include "util.h" #include "utilstrencodings.h" #include "validation.h" #ifdef ENABLE_WALLET #include "wallet/wallet.h" #include "wallet/walletdb.h" #endif #include #include /** * @note Do not add or change anything in the information returned by this * method. `getinfo` exists for backwards-compatibility only. It combines * information from wildly different sources in the program, which is a mess, * and is thus planned to be deprecated eventually. * * Based on the source of the information, new information should be added to: * - `getblockchaininfo`, * - `getnetworkinfo` or * - `getwalletinfo` * * Or alternatively, create a specific query method for the information. **/ static UniValue getinfo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( "getinfo\n" "\nDEPRECATED. Returns an object containing various state info.\n" "\nResult:\n" "{\n" " \"version\": xxxxx, (numeric) the server version\n" " \"protocolversion\": xxxxx, (numeric) the protocol version\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n" " \"balance\": xxxxxxx, (numeric) the total bitcoin " "balance of the wallet\n" " \"blocks\": xxxxxx, (numeric) the current number of " "blocks processed in the server\n" " \"timeoffset\": xxxxx, (numeric) the time offset\n" " \"connections\": xxxxx, (numeric) the number of " "connections\n" " \"proxy\": \"host:port\", (string, optional) the proxy used " "by the server\n" " \"difficulty\": xxxxxx, (numeric) the current difficulty\n" " \"testnet\": true|false, (boolean) if the server is using " "testnet or not\n" " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds " "since Unix epoch) of the oldest pre-generated key in the key " "pool\n" " \"keypoolsize\": xxxx, (numeric) how many new keys are " "pre-generated\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" " \"paytxfee\": x.xxxx, (numeric) the transaction fee set " "in " + CURRENCY_UNIT + "/kB\n" " \"relayfee\": x.xxxx, (numeric) minimum " "relay fee for non-free transactions in " + CURRENCY_UNIT + "/kB\n" " \"errors\": \"...\" (string) any error messages\n" "}\n" "\nExamples:\n" + HelpExampleCli("getinfo", "") + HelpExampleRpc("getinfo", "")); #ifdef ENABLE_WALLET LOCK2(cs_main, pwalletMain ? &pwalletMain->cs_wallet : nullptr); #else LOCK(cs_main); #endif proxyType proxy; GetProxy(NET_IPV4, proxy); UniValue obj(UniValue::VOBJ); obj.push_back(Pair("version", CLIENT_VERSION)); obj.push_back(Pair("protocolversion", PROTOCOL_VERSION)); #ifdef ENABLE_WALLET if (pwalletMain) { obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); obj.push_back( Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); } #endif obj.push_back(Pair("blocks", (int)chainActive.Height())); obj.push_back(Pair("timeoffset", GetTimeOffset())); if (g_connman) obj.push_back(Pair("connections", (int)g_connman->GetNodeCount( CConnman::CONNECTIONS_ALL))); obj.push_back(Pair("proxy", (proxy.IsValid() ? proxy.proxy.ToStringIPPort() : std::string()))); obj.push_back(Pair("difficulty", double(GetDifficulty(chainActive.Tip())))); obj.push_back(Pair("testnet", Params().NetworkIDString() == CBaseChainParams::TESTNET)); #ifdef ENABLE_WALLET if (pwalletMain) { obj.push_back( Pair("keypoololdest", pwalletMain->GetOldestKeyPoolTime())); obj.push_back(Pair("keypoolsize", (int)pwalletMain->GetKeyPoolSize())); } if (pwalletMain && pwalletMain->IsCrypted()) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); #endif obj.push_back( Pair("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()))); obj.push_back(Pair("errors", GetWarnings("statusbar"))); return obj; } #ifdef ENABLE_WALLET class DescribeAddressVisitor : public boost::static_visitor { public: UniValue operator()(const CNoDestination &dest) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const CKeyID &keyID) const { UniValue obj(UniValue::VOBJ); CPubKey vchPubKey; obj.push_back(Pair("isscript", false)); if (pwalletMain && pwalletMain->GetPubKey(keyID, vchPubKey)) { obj.push_back(Pair("pubkey", HexStr(vchPubKey))); obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed())); } return obj; } UniValue operator()(const CScriptID &scriptID) const { UniValue obj(UniValue::VOBJ); CScript subscript; obj.push_back(Pair("isscript", true)); if (pwalletMain && pwalletMain->GetCScript(scriptID, subscript)) { std::vector addresses; txnouttype whichType; int nRequired; ExtractDestinations(subscript, whichType, addresses, nRequired); obj.push_back(Pair("script", GetTxnOutputType(whichType))); obj.push_back( Pair("hex", HexStr(subscript.begin(), subscript.end()))); UniValue a(UniValue::VARR); for (const CTxDestination &addr : addresses) { a.push_back(EncodeDestination(addr)); } obj.push_back(Pair("addresses", a)); if (whichType == TX_MULTISIG) obj.push_back(Pair("sigsrequired", nRequired)); } return obj; } }; #endif static UniValue validateaddress(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "validateaddress \"address\"\n" "\nReturn information about the given bitcoin address.\n" "\nArguments:\n" "1. \"address\" (string, required) The bitcoin address to " "validate\n" "\nResult:\n" "{\n" " \"isvalid\" : true|false, (boolean) If the address is " "valid or not. If not, this is the only property returned.\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" " \"isscript\" : true|false, (boolean) If the key is a " "script\n" " \"pubkey\" : \"publickeyhex\", (string) The hex value of the " "raw public key\n" " \"iscompressed\" : true|false, (boolean) If the address is " "compressed\n" " \"account\" : \"account\" (string) DEPRECATED. The " "account associated with the address, \"\" is the default account\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" " \"hdmasterkeyid\" : \"\" (string, optional) The " "Hash160 of the HD master pubkey\n" "}\n" "\nExamples:\n" + HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")); #ifdef ENABLE_WALLET LOCK2(cs_main, pwalletMain ? &pwalletMain->cs_wallet : nullptr); #else LOCK(cs_main); #endif CTxDestination dest = DecodeDestination(request.params[0].get_str()); bool isValid = IsValidDestination(dest); UniValue ret(UniValue::VOBJ); ret.push_back(Pair("isvalid", isValid)); if (isValid) { std::string currentAddress = EncodeDestination(dest); ret.push_back(Pair("address", currentAddress)); CScript scriptPubKey = GetScriptForDestination(dest); ret.push_back(Pair("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); #ifdef ENABLE_WALLET isminetype mine = pwalletMain ? IsMine(*pwalletMain, dest) : ISMINE_NO; ret.push_back(Pair("ismine", (mine & ISMINE_SPENDABLE) ? true : false)); ret.push_back( Pair("iswatchonly", (mine & ISMINE_WATCH_ONLY) ? true : false)); UniValue detail = boost::apply_visitor(DescribeAddressVisitor(), dest); ret.pushKVs(detail); if (pwalletMain && pwalletMain->mapAddressBook.count(dest)) { ret.push_back( Pair("account", pwalletMain->mapAddressBook[dest].name)); } if (pwalletMain) { const auto &meta = pwalletMain->mapKeyMetadata; const CKeyID *keyID = boost::get(&dest); auto it = keyID ? meta.find(*keyID) : meta.end(); if (it == meta.end()) { it = meta.find(CScriptID(scriptPubKey)); } if (it != meta.end()) { ret.push_back(Pair("timestamp", it->second.nCreateTime)); if (!it->second.hdKeypath.empty()) { ret.push_back(Pair("hdkeypath", it->second.hdKeypath)); ret.push_back(Pair("hdmasterkeyid", it->second.hdMasterKeyID.GetHex())); } } } #endif } return ret; } /** * Used by addmultisigaddress / createmultisig: */ CScript createmultisig_redeemScript(const UniValue ¶ms) { int nRequired = params[0].get_int(); const UniValue &keys = params[1].get_array(); // Gather public keys if (nRequired < 1) throw std::runtime_error( "a multisignature address must require at least one key to redeem"); if ((int)keys.size() < nRequired) throw std::runtime_error( strprintf("not enough keys supplied " "(got %u keys, but need at least %d to redeem)", keys.size(), nRequired)); if (keys.size() > 16) throw std::runtime_error( "Number of addresses involved in the " "multisignature address creation > 16\nReduce the " "number"); std::vector pubkeys; pubkeys.resize(keys.size()); for (unsigned int i = 0; i < keys.size(); i++) { const std::string &ks = keys[i].get_str(); #ifdef ENABLE_WALLET // Case 1: Bitcoin address and we have full public key: CTxDestination dest = DecodeDestination(ks); if (pwalletMain && IsValidDestination(dest)) { const CKeyID *keyID = boost::get(&dest); if (!keyID) { throw std::runtime_error( strprintf("%s does not refer to a key", ks)); } CPubKey vchPubKey; if (!pwalletMain->GetPubKey(*keyID, vchPubKey)) { throw std::runtime_error( strprintf("no full public key for address %s", ks)); } if (!vchPubKey.IsFullyValid()) throw std::runtime_error(" Invalid public key: " + ks); pubkeys[i] = vchPubKey; } // Case 2: hex public key else #endif if (IsHex(ks)) { CPubKey vchPubKey(ParseHex(ks)); if (!vchPubKey.IsFullyValid()) throw std::runtime_error(" Invalid public key: " + ks); pubkeys[i] = vchPubKey; } else { throw std::runtime_error(" Invalid public key: " + ks); } } CScript result = GetScriptForMultisig(nRequired, pubkeys); if (result.size() > MAX_SCRIPT_ELEMENT_SIZE) throw std::runtime_error( strprintf("redeemScript exceeds size limit: %d > %d", result.size(), MAX_SCRIPT_ELEMENT_SIZE)); return result; } static UniValue createmultisig(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 2) { std::string msg = "createmultisig nrequired [\"key\",...]\n" "\nCreates a multi-signature address with n signature of m keys " "required.\n" "It returns a json object with the address and redeemScript.\n" "\nArguments:\n" "1. nrequired (numeric, required) The number of required " "signatures out of the n keys or addresses.\n" "2. \"keys\" (string, required) A json array of keys which " "are bitcoin addresses or hex-encoded public keys\n" " [\n" " \"key\" (string) bitcoin address or hex-encoded public " "key\n" " ,...\n" " ]\n" "\nResult:\n" "{\n" " \"address\":\"multisigaddress\", (string) The value of the new " "multisig address.\n" " \"redeemScript\":\"script\" (string) The string value of " "the hex-encoded redemption script.\n" "}\n" "\nExamples:\n" "\nCreate a multisig address from 2 addresses\n" + HelpExampleCli("createmultisig", "2 " "\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\"," "\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + "\nAs a json rpc call\n" + HelpExampleRpc("createmultisig", "2, " "\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\"," "\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\""); throw std::runtime_error(msg); } // Construct using pay-to-script-hash: CScript inner = createmultisig_redeemScript(request.params); CScriptID innerID(inner); UniValue result(UniValue::VOBJ); result.push_back(Pair("address", EncodeDestination(innerID))); result.push_back(Pair("redeemScript", HexStr(inner.begin(), inner.end()))); return result; } static UniValue verifymessage(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 3) throw std::runtime_error( "verifymessage \"address\" \"signature\" \"message\"\n" "\nVerify a signed message\n" "\nArguments:\n" "1. \"address\" (string, required) The bitcoin address to " "use for the signature.\n" "2. \"signature\" (string, required) The signature provided " "by the signer in base 64 encoding (see signmessage).\n" "3. \"message\" (string, required) The message that was " "signed.\n" "\nResult:\n" "true|false (boolean) If the signature is verified or not.\n" "\nExamples:\n" "\nUnlock the wallet for 30 seconds\n" + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + "\nCreate the signature\n" + HelpExampleCli( "signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4" "XX\" \"signature\" \"my " "message\"") + "\nAs json rpc\n" + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4" "XX\", \"signature\", \"my " "message\"")); LOCK(cs_main); std::string strAddress = request.params[0].get_str(); std::string strSign = request.params[1].get_str(); std::string strMessage = request.params[2].get_str(); CTxDestination destination = DecodeDestination(strAddress); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); } const CKeyID *keyID = boost::get(&destination); if (!keyID) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } bool fInvalid = false; std::vector vchSig = DecodeBase64(strSign.c_str(), &fInvalid); if (fInvalid) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding"); CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; CPubKey pubkey; if (!pubkey.RecoverCompact(ss.GetHash(), vchSig)) return false; return (pubkey.GetID() == *keyID); } static UniValue signmessagewithprivkey(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 2) throw std::runtime_error( "signmessagewithprivkey \"privkey\" \"message\"\n" "\nSign a message with the private key of an address\n" "\nArguments:\n" "1. \"privkey\" (string, required) The private key to sign " "the message with.\n" "2. \"message\" (string, required) The message to create a " "signature of.\n" "\nResult:\n" "\"signature\" (string) The signature of the message " "encoded in base 64\n" "\nExamples:\n" "\nCreate the signature\n" + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4" "XX\" \"signature\" \"my " "message\"") + "\nAs json rpc\n" + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"")); std::string strPrivkey = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(strPrivkey); if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); CKey key = vchSecret.GetKey(); if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; std::vector vchSig; if (!key.SignCompact(ss.GetHash(), vchSig)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); return EncodeBase64(&vchSig[0], vchSig.size()); } static UniValue setmocktime(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "setmocktime timestamp\n" "\nSet the local time to given timestamp (-regtest only)\n" "\nArguments:\n" "1. timestamp (integer, required) Unix seconds-since-epoch " "timestamp\n" " Pass 0 to go back to using the system time."); if (!Params().MineBlocksOnDemand()) throw std::runtime_error( "setmocktime for regression testing (-regtest mode) only"); // For now, don't change mocktime if we're in the middle of validation, as // this could have an effect on mempool time-based eviction, as well as // IsCurrentForFeeEstimation() and IsInitialBlockDownload(). // TODO: figure out the right way to synchronize around mocktime, and // ensure all callsites of GetTime() are accessing this safely. LOCK(cs_main); RPCTypeCheck(request.params, {UniValue::VNUM}); SetMockTime(request.params[0].get_int64()); return NullUniValue; } static UniValue RPCLockedMemoryInfo() { LockedPool::Stats stats = LockedPoolManager::Instance().stats(); UniValue obj(UniValue::VOBJ); obj.push_back(Pair("used", uint64_t(stats.used))); obj.push_back(Pair("free", uint64_t(stats.free))); obj.push_back(Pair("total", uint64_t(stats.total))); obj.push_back(Pair("locked", uint64_t(stats.locked))); obj.push_back(Pair("chunks_used", uint64_t(stats.chunks_used))); obj.push_back(Pair("chunks_free", uint64_t(stats.chunks_free))); return obj; } static UniValue getmemoryinfo(const Config &config, const JSONRPCRequest &request) { /* Please, avoid using the word "pool" here in the RPC interface or help, * as users will undoubtedly confuse it with the other "memory pool" */ if (request.fHelp || request.params.size() != 0) throw std::runtime_error( "getmemoryinfo\n" "Returns an object containing information about memory usage.\n" "\nResult:\n" "{\n" " \"locked\": { (json object) Information about " "locked memory manager\n" " \"used\": xxxxx, (numeric) Number of bytes used\n" " \"free\": xxxxx, (numeric) Number of bytes available " "in current arenas\n" " \"total\": xxxxxxx, (numeric) Total number of bytes " "managed\n" " \"locked\": xxxxxx, (numeric) Amount of bytes that " "succeeded locking. If this number is smaller than total, locking " "pages failed at some point and key data could be swapped to " "disk.\n" " \"chunks_used\": xxxxx, (numeric) Number allocated chunks\n" " \"chunks_free\": xxxxx, (numeric) Number unused chunks\n" " }\n" "}\n" "\nExamples:\n" + HelpExampleCli("getmemoryinfo", "") + HelpExampleRpc("getmemoryinfo", "")); UniValue obj(UniValue::VOBJ); obj.push_back(Pair("locked", RPCLockedMemoryInfo())); return obj; } static UniValue echo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp) throw std::runtime_error( "echo|echojson \"message\" ...\n" "\nSimply echo back the input arguments. This command is for " "testing.\n" "\nThe difference between echo and echojson is that echojson has " "argument conversion enabled in the client-side table in" "bitcoin-cli and the GUI. There is no server-side difference."); return request.params; } // clang-format off static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // ------------------- ------------------------ ---------------------- ---------- { "control", "getinfo", getinfo, true, {} }, /* uses wallet if enabled */ { "control", "getmemoryinfo", getmemoryinfo, true, {} }, { "util", "validateaddress", validateaddress, true, {"address"} }, /* uses wallet if enabled */ { "util", "createmultisig", createmultisig, true, {"nrequired","keys"} }, { "util", "verifymessage", verifymessage, true, {"address","signature","message"} }, { "util", "signmessagewithprivkey", signmessagewithprivkey, true, {"privkey","message"} }, /* Not shown in help */ { "hidden", "setmocktime", setmocktime, true, {"timestamp"}}, { "hidden", "echo", echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, { "hidden", "echojson", echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, }; // clang-format on void RegisterMiscRPCCommands(CRPCTable &t) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) t.appendCommand(commands[vcidx].name, &commands[vcidx]); } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index f0eba193b..87c384824 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1,1184 +1,1185 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" #include "chain.h" #include "coins.h" #include "config.h" #include "consensus/validation.h" #include "core_io.h" +#include "dstencode.h" #include "init.h" #include "keystore.h" #include "merkleblock.h" #include "net.h" #include "policy/policy.h" #include "primitives/transaction.h" #include "rpc/server.h" #include "script/script.h" #include "script/script_error.h" #include "script/sign.h" #include "script/standard.h" #include "txmempool.h" #include "uint256.h" #include "utilstrencodings.h" #include "validation.h" #ifdef ENABLE_WALLET #include "wallet/wallet.h" #endif #include #include void ScriptPubKeyToJSON(const CScript &scriptPubKey, UniValue &out, bool fIncludeHex) { txnouttype type; std::vector addresses; int nRequired; out.push_back(Pair("asm", ScriptToAsmStr(scriptPubKey))); if (fIncludeHex) { out.push_back( Pair("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); } if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { out.push_back(Pair("type", GetTxnOutputType(type))); return; } out.push_back(Pair("reqSigs", nRequired)); out.push_back(Pair("type", GetTxnOutputType(type))); UniValue a(UniValue::VARR); for (const CTxDestination &addr : addresses) { a.push_back(EncodeDestination(addr)); } out.push_back(Pair("addresses", a)); } void TxToJSON(const CTransaction &tx, const uint256 hashBlock, UniValue &entry) { entry.push_back(Pair("txid", tx.GetId().GetHex())); entry.push_back(Pair("hash", tx.GetHash().GetHex())); entry.push_back(Pair( "size", (int)::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION))); entry.push_back(Pair("version", tx.nVersion)); entry.push_back(Pair("locktime", (int64_t)tx.nLockTime)); UniValue vin(UniValue::VARR); for (unsigned int i = 0; i < tx.vin.size(); i++) { const CTxIn &txin = tx.vin[i]; UniValue in(UniValue::VOBJ); if (tx.IsCoinBase()) { in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); } else { in.push_back(Pair("txid", txin.prevout.hash.GetHex())); in.push_back(Pair("vout", (int64_t)txin.prevout.n)); UniValue o(UniValue::VOBJ); o.push_back(Pair("asm", ScriptToAsmStr(txin.scriptSig, true))); o.push_back(Pair( "hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); in.push_back(Pair("scriptSig", o)); } in.push_back(Pair("sequence", (int64_t)txin.nSequence)); vin.push_back(in); } entry.push_back(Pair("vin", vin)); UniValue vout(UniValue::VARR); for (unsigned int i = 0; i < tx.vout.size(); i++) { const CTxOut &txout = tx.vout[i]; UniValue out(UniValue::VOBJ); out.push_back(Pair("value", ValueFromAmount(txout.nValue))); out.push_back(Pair("n", (int64_t)i)); UniValue o(UniValue::VOBJ); ScriptPubKeyToJSON(txout.scriptPubKey, o, true); out.push_back(Pair("scriptPubKey", o)); vout.push_back(out); } entry.push_back(Pair("vout", vout)); if (!hashBlock.IsNull()) { entry.push_back(Pair("blockhash", hashBlock.GetHex())); BlockMap::iterator mi = mapBlockIndex.find(hashBlock); if (mi != mapBlockIndex.end() && (*mi).second) { CBlockIndex *pindex = (*mi).second; if (chainActive.Contains(pindex)) { entry.push_back(Pair("confirmations", 1 + chainActive.Height() - pindex->nHeight)); entry.push_back(Pair("time", pindex->GetBlockTime())); entry.push_back(Pair("blocktime", pindex->GetBlockTime())); } else { entry.push_back(Pair("confirmations", 0)); } } } } static UniValue getrawtransaction(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "getrawtransaction \"txid\" ( verbose )\n" "\nNOTE: By default this function only works for mempool " "transactions. If the -txindex option is\n" "enabled, it also works for blockchain transactions.\n" "DEPRECATED: for now, it also works for transactions with unspent " "outputs.\n" "\nReturn the raw transaction data.\n" "\nIf verbose is 'true', returns an Object with information about " "'txid'.\n" "If verbose is 'false' or omitted, returns a string that is " "serialized, hex-encoded data for 'txid'.\n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction id\n" "2. verbose (bool, optional, default=false) If false, return " "a string, otherwise return a json object\n" "\nResult (if verbose is not set or set to false):\n" "\"data\" (string) The serialized, hex-encoded data for " "'txid'\n" "\nResult (if verbose is set to true):\n" "{\n" " \"hex\" : \"data\", (string) The serialized, hex-encoded " "data for 'txid'\n" " \"txid\" : \"id\", (string) The transaction id (same as " "provided)\n" " \"hash\" : \"id\", (string) The transaction hash " "(differs from txid for witness transactions)\n" " \"size\" : n, (numeric) The serialized transaction " "size\n" " \"version\" : n, (numeric) The version\n" " \"locktime\" : ttt, (numeric) The lock time\n" " \"vin\" : [ (array of json objects)\n" " {\n" " \"txid\": \"id\", (string) The transaction id\n" " \"vout\": n, (numeric) \n" " \"scriptSig\": { (json object) The script\n" " \"asm\": \"asm\", (string) asm\n" " \"hex\": \"hex\" (string) hex\n" " },\n" " \"sequence\": n (numeric) The script sequence number\n" " }\n" " ,...\n" " ],\n" " \"vout\" : [ (array of json objects)\n" " {\n" " \"value\" : x.xxx, (numeric) The value in " + CURRENCY_UNIT + "\n" " \"n\" : n, (numeric) index\n" " \"scriptPubKey\" : { (json object)\n" " \"asm\" : \"asm\", (string) the asm\n" " \"hex\" : \"hex\", (string) the hex\n" " \"reqSigs\" : n, (numeric) The required sigs\n" " \"type\" : \"pubkeyhash\", (string) The type, eg " "'pubkeyhash'\n" " \"addresses\" : [ (json array of string)\n" " \"address\" (string) bitcoin address\n" " ,...\n" " ]\n" " }\n" " }\n" " ,...\n" " ],\n" " \"blockhash\" : \"hash\", (string) the block hash\n" " \"confirmations\" : n, (numeric) The confirmations\n" " \"time\" : ttt, (numeric) The transaction time in " "seconds since epoch (Jan 1 1970 GMT)\n" " \"blocktime\" : ttt (numeric) The block time in seconds " "since epoch (Jan 1 1970 GMT)\n" "}\n" "\nExamples:\n" + HelpExampleCli("getrawtransaction", "\"mytxid\"") + HelpExampleCli("getrawtransaction", "\"mytxid\" true") + HelpExampleRpc("getrawtransaction", "\"mytxid\", true")); } LOCK(cs_main); uint256 hash = ParseHashV(request.params[0], "parameter 1"); // Accept either a bool (true) or a num (>=1) to indicate verbose output. bool fVerbose = false; if (request.params.size() > 1) { if (request.params[1].isNum()) { if (request.params[1].get_int() != 0) { fVerbose = true; } } else if (request.params[1].isBool()) { if (request.params[1].isTrue()) { fVerbose = true; } } else { throw JSONRPCError( RPC_TYPE_ERROR, "Invalid type provided. Verbose parameter must be a boolean."); } } CTransactionRef tx; uint256 hashBlock; if (!GetTransaction(config, hash, tx, hashBlock, true)) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, std::string(fTxIndex ? "No such mempool or blockchain transaction" : "No such mempool transaction. Use -txindex " "to enable blockchain transaction queries") + ". Use gettransaction for wallet transactions."); } std::string strHex = EncodeHexTx(*tx, RPCSerializationFlags()); if (!fVerbose) { return strHex; } UniValue result(UniValue::VOBJ); result.push_back(Pair("hex", strHex)); TxToJSON(*tx, hashBlock, result); return result; } static UniValue gettxoutproof(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2)) { throw std::runtime_error( "gettxoutproof [\"txid\",...] ( blockhash )\n" "\nReturns a hex-encoded proof that \"txid\" was included in a " "block.\n" "\nNOTE: By default this function only works sometimes. This is " "when there is an\n" "unspent output in the utxo for this transaction. To make it " "always work,\n" "you need to maintain a transaction index, using the -txindex " "command line option or\n" "specify the block in which the transaction is included manually " "(by blockhash).\n" "\nArguments:\n" "1. \"txids\" (string) A json array of txids to filter\n" " [\n" " \"txid\" (string) A transaction hash\n" " ,...\n" " ]\n" "2. \"blockhash\" (string, optional) If specified, looks for " "txid in the block with this hash\n" "\nResult:\n" "\"data\" (string) A string that is a serialized, " "hex-encoded data for the proof.\n"); } std::set setTxids; uint256 oneTxid; UniValue txids = request.params[0].get_array(); for (unsigned int idx = 0; idx < txids.size(); idx++) { const UniValue &txid = txids[idx]; if (txid.get_str().length() != 64 || !IsHex(txid.get_str())) { throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid txid ") + txid.get_str()); } uint256 hash(uint256S(txid.get_str())); if (setTxids.count(hash)) { throw JSONRPCError( RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txid.get_str()); } setTxids.insert(hash); oneTxid = hash; } LOCK(cs_main); CBlockIndex *pblockindex = nullptr; uint256 hashBlock; if (request.params.size() > 1) { hashBlock = uint256S(request.params[1].get_str()); if (!mapBlockIndex.count(hashBlock)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); pblockindex = mapBlockIndex[hashBlock]; } else { const Coin &coin = AccessByTxid(*pcoinsTip, oneTxid); if (!coin.IsSpent() && coin.GetHeight() > 0 && int64_t(coin.GetHeight()) <= chainActive.Height()) { pblockindex = chainActive[coin.GetHeight()]; } } if (pblockindex == nullptr) { CTransactionRef tx; if (!GetTransaction(config, oneTxid, tx, hashBlock, false) || hashBlock.IsNull()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block"); } if (!mapBlockIndex.count(hashBlock)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt"); } pblockindex = mapBlockIndex[hashBlock]; } CBlock block; if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); } unsigned int ntxFound = 0; for (const auto &tx : block.vtx) { if (setTxids.count(tx->GetId())) { ntxFound++; } } if (ntxFound != setTxids.size()) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, "(Not all) transactions not found in specified block"); } CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION); CMerkleBlock mb(block, setTxids); ssMB << mb; std::string strHex = HexStr(ssMB.begin(), ssMB.end()); return strHex; } static UniValue verifytxoutproof(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "verifytxoutproof \"proof\"\n" "\nVerifies that a proof points to a transaction in a block, " "returning the transaction it commits to\n" "and throwing an RPC error if the block is not in our best chain\n" "\nArguments:\n" "1. \"proof\" (string, required) The hex-encoded proof " "generated by gettxoutproof\n" "\nResult:\n" "[\"txid\"] (array, strings) The txid(s) which the proof " "commits to, or empty array if the proof is invalid\n"); } CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION); CMerkleBlock merkleBlock; ssMB >> merkleBlock; UniValue res(UniValue::VARR); std::vector vMatch; std::vector vIndex; if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) { return res; } LOCK(cs_main); if (!mapBlockIndex.count(merkleBlock.header.GetHash()) || !chainActive.Contains(mapBlockIndex[merkleBlock.header.GetHash()])) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); } for (const uint256 &hash : vMatch) { res.push_back(hash.GetHex()); } return res; } static UniValue createrawtransaction(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { throw std::runtime_error( "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] " "{\"address\":amount,\"data\":\"hex\",...} ( locktime )\n" "\nCreate a transaction spending the given inputs and creating new " "outputs.\n" "Outputs can be addresses or data.\n" "Returns hex-encoded raw transaction.\n" "Note that the transaction's inputs are not signed, and\n" "it is not stored in the wallet or transmitted to the network.\n" "\nArguments:\n" "1. \"inputs\" (array, required) A json array of " "json objects\n" " [\n" " {\n" " \"txid\":\"id\", (string, required) The transaction " "id\n" " \"vout\":n, (numeric, required) The output " "number\n" " \"sequence\":n (numeric, optional) The sequence " "number\n" " } \n" " ,...\n" " ]\n" "2. \"outputs\" (object, required) a json object " "with outputs\n" " {\n" " \"address\": x.xxx, (numeric or string, required) The " "key is the bitcoin address, the numeric value (can be string) is " "the " + CURRENCY_UNIT + " amount\n" " \"data\": \"hex\" (string, required) The key is " "\"data\", the value is hex encoded data\n" " ,...\n" " }\n" "3. locktime (numeric, optional, default=0) Raw " "locktime. Non-0 value also locktime-activates inputs\n" "\nResult:\n" "\"transaction\" (string) hex string of the " "transaction\n" "\nExamples:\n" + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" " "\"{\\\"address\\\":0.01}\"") + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" " "\"{\\\"data\\\":\\\"00010203\\\"}\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", " "\"{\\\"address\\\":0.01}\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", " "\"{\\\"data\\\":\\\"00010203\\\"}\"")); } RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VOBJ, UniValue::VNUM}, true); if (request.params[0].isNull() || request.params[1].isNull()) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); } UniValue inputs = request.params[0].get_array(); UniValue sendTo = request.params[1].get_obj(); CMutableTransaction rawTx; if (request.params.size() > 2 && !request.params[2].isNull()) { int64_t nLockTime = request.params[2].get_int64(); if (nLockTime < 0 || nLockTime > std::numeric_limits::max()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); } rawTx.nLockTime = nLockTime; } for (size_t idx = 0; idx < inputs.size(); idx++) { const UniValue &input = inputs[idx]; const UniValue &o = input.get_obj(); uint256 txid = ParseHashO(o, "txid"); const UniValue &vout_v = find_value(o, "vout"); if (!vout_v.isNum()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key"); } int nOutput = vout_v.get_int(); if (nOutput < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); } uint32_t nSequence = (rawTx.nLockTime ? std::numeric_limits::max() - 1 : std::numeric_limits::max()); // Set the sequence number if passed in the parameters object. const UniValue &sequenceObj = find_value(o, "sequence"); if (sequenceObj.isNum()) { int64_t seqNr64 = sequenceObj.get_int64(); if (seqNr64 < 0 || seqNr64 > std::numeric_limits::max()) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range"); } nSequence = uint32_t(seqNr64); } CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence); rawTx.vin.push_back(in); } std::set destinations; std::vector addrList = sendTo.getKeys(); for (const std::string &name_ : addrList) { if (name_ == "data") { std::vector data = ParseHexV(sendTo[name_].getValStr(), "Data"); CTxOut out(0, CScript() << OP_RETURN << data); rawTx.vout.push_back(out); } else { CTxDestination destination = DecodeDestination(name_); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_); } if (!destinations.insert(destination).second) { throw JSONRPCError( RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_); } CScript scriptPubKey = GetScriptForDestination(destination); Amount nAmount = AmountFromValue(sendTo[name_]); CTxOut out(nAmount, scriptPubKey); rawTx.vout.push_back(out); } } return EncodeHexTx(rawTx); } static UniValue decoderawtransaction(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "decoderawtransaction \"hexstring\"\n" "\nReturn a JSON object representing the serialized, hex-encoded " "transaction.\n" "\nArguments:\n" "1. \"hexstring\" (string, required) The transaction hex " "string\n" "\nResult:\n" "{\n" " \"txid\" : \"id\", (string) The transaction id\n" " \"hash\" : \"id\", (string) The transaction hash " "(differs from txid for witness transactions)\n" " \"size\" : n, (numeric) The transaction size\n" " \"version\" : n, (numeric) The version\n" " \"locktime\" : ttt, (numeric) The lock time\n" " \"vin\" : [ (array of json objects)\n" " {\n" " \"txid\": \"id\", (string) The transaction id\n" " \"vout\": n, (numeric) The output number\n" " \"scriptSig\": { (json object) The script\n" " \"asm\": \"asm\", (string) asm\n" " \"hex\": \"hex\" (string) hex\n" " },\n" " \"sequence\": n (numeric) The script sequence number\n" " }\n" " ,...\n" " ],\n" " \"vout\" : [ (array of json objects)\n" " {\n" " \"value\" : x.xxx, (numeric) The value in " + CURRENCY_UNIT + "\n" " \"n\" : n, (numeric) index\n" " \"scriptPubKey\" : { (json object)\n" " \"asm\" : \"asm\", (string) the asm\n" " \"hex\" : \"hex\", (string) the hex\n" " \"reqSigs\" : n, (numeric) The required sigs\n" " \"type\" : \"pubkeyhash\", (string) The type, eg " "'pubkeyhash'\n" " \"addresses\" : [ (json array of string)\n" " \"12tvKAXCxZjSmdNbao16dKXC8tRWfcF5oc\" (string) " "bitcoin address\n" " ,...\n" " ]\n" " }\n" " }\n" " ,...\n" " ],\n" "}\n" "\nExamples:\n" + HelpExampleCli("decoderawtransaction", "\"hexstring\"") + HelpExampleRpc("decoderawtransaction", "\"hexstring\"")); } LOCK(cs_main); RPCTypeCheck(request.params, {UniValue::VSTR}); CMutableTransaction mtx; if (!DecodeHexTx(mtx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } UniValue result(UniValue::VOBJ); TxToJSON(CTransaction(std::move(mtx)), uint256(), result); return result; } static UniValue decodescript(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "decodescript \"hexstring\"\n" "\nDecode a hex-encoded script.\n" "\nArguments:\n" "1. \"hexstring\" (string) the hex encoded script\n" "\nResult:\n" "{\n" " \"asm\":\"asm\", (string) Script public key\n" " \"hex\":\"hex\", (string) hex encoded public key\n" " \"type\":\"type\", (string) The output type\n" " \"reqSigs\": n, (numeric) The required signatures\n" " \"addresses\": [ (json array of string)\n" " \"address\" (string) bitcoin address\n" " ,...\n" " ],\n" " \"p2sh\",\"address\" (string) address of P2SH script wrapping " "this redeem script (not returned if the script is already a " "P2SH).\n" "}\n" "\nExamples:\n" + HelpExampleCli("decodescript", "\"hexstring\"") + HelpExampleRpc("decodescript", "\"hexstring\"")); } RPCTypeCheck(request.params, {UniValue::VSTR}); UniValue r(UniValue::VOBJ); CScript script; if (request.params[0].get_str().size() > 0) { std::vector scriptData( ParseHexV(request.params[0], "argument")); script = CScript(scriptData.begin(), scriptData.end()); } else { // Empty scripts are valid. } ScriptPubKeyToJSON(script, r, false); UniValue type; type = find_value(r, "type"); if (type.isStr() && type.get_str() != "scripthash") { // P2SH cannot be wrapped in a P2SH. If this script is already a P2SH, // don't return the address for a P2SH of the P2SH. r.push_back(Pair("p2sh", EncodeDestination(CScriptID(script)))); } return r; } /** * Pushes a JSON object for script verification or signing errors to vErrorsRet. */ static void TxInErrorToJSON(const CTxIn &txin, UniValue &vErrorsRet, const std::string &strMessage) { UniValue entry(UniValue::VOBJ); entry.push_back(Pair("txid", txin.prevout.hash.ToString())); entry.push_back(Pair("vout", (uint64_t)txin.prevout.n)); entry.push_back(Pair("scriptSig", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); entry.push_back(Pair("sequence", (uint64_t)txin.nSequence)); entry.push_back(Pair("error", strMessage)); vErrorsRet.push_back(entry); } static UniValue signrawtransaction(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) { throw std::runtime_error( "signrawtransaction \"hexstring\" ( " "[{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\"," "\"redeemScript\":\"hex\"},...] [\"privatekey1\",...] sighashtype " ")\n" "\nSign inputs for raw transaction (serialized, hex-encoded).\n" "The second optional argument (may be null) is an array of " "previous transaction outputs that\n" "this transaction depends on but may not yet be in the block " "chain.\n" "The third optional argument (may be null) is an array of " "base58-encoded private\n" "keys that, if given, will be the only keys used to sign the " "transaction.\n" #ifdef ENABLE_WALLET + HelpRequiringPassphrase() + "\n" #endif "\nArguments:\n" "1. \"hexstring\" (string, required) The transaction hex " "string\n" "2. \"prevtxs\" (string, optional) An json array of previous " "dependent transaction outputs\n" " [ (json array of json objects, or 'null' if " "none provided)\n" " {\n" " \"txid\":\"id\", (string, required) The " "transaction id\n" " \"vout\":n, (numeric, required) The " "output number\n" " \"scriptPubKey\": \"hex\", (string, required) script " "key\n" " \"redeemScript\": \"hex\", (string, required for P2SH " "or P2WSH) redeem script\n" " \"amount\": value (numeric, required) The " "amount spent\n" " }\n" " ,...\n" " ]\n" "3. \"privkeys\" (string, optional) A json array of " "base58-encoded private keys for signing\n" " [ (json array of strings, or 'null' if none " "provided)\n" " \"privatekey\" (string) private key in base58-encoding\n" " ,...\n" " ]\n" "4. \"sighashtype\" (string, optional, default=ALL) The " "signature hash type. Must be one of\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" " \"ALL|ANYONECANPAY\"\n" " \"NONE|ANYONECANPAY\"\n" " \"SINGLE|ANYONECANPAY\"\n" " \"ALL|FORKID\"\n" " \"NONE|FORKID\"\n" " \"SINGLE|FORKID\"\n" " \"ALL|FORKID|ANYONECANPAY\"\n" " \"NONE|FORKID|ANYONECANPAY\"\n" " \"SINGLE|FORKID|ANYONECANPAY\"\n" "\nResult:\n" "{\n" " \"hex\" : \"value\", (string) The hex-encoded raw " "transaction with signature(s)\n" " \"complete\" : true|false, (boolean) If the transaction has a " "complete set of signatures\n" " \"errors\" : [ (json array of objects) Script " "verification errors (if there are any)\n" " {\n" " \"txid\" : \"hash\", (string) The hash of the " "referenced, previous transaction\n" " \"vout\" : n, (numeric) The index of the " "output to spent and used as input\n" " \"scriptSig\" : \"hex\", (string) The hex-encoded " "signature script\n" " \"sequence\" : n, (numeric) Script sequence " "number\n" " \"error\" : \"text\" (string) Verification or " "signing error related to the input\n" " }\n" " ,...\n" " ]\n" "}\n" "\nExamples:\n" + HelpExampleCli("signrawtransaction", "\"myhex\"") + HelpExampleRpc("signrawtransaction", "\"myhex\"")); } #ifdef ENABLE_WALLET LOCK2(cs_main, pwalletMain ? &pwalletMain->cs_wallet : nullptr); #else LOCK(cs_main); #endif RPCTypeCheck( request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); std::vector txData(ParseHexV(request.params[0], "argument 1")); CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); std::vector txVariants; while (!ssData.empty()) { try { CMutableTransaction tx; ssData >> tx; txVariants.push_back(tx); } catch (const std::exception &) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } } if (txVariants.empty()) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transaction"); } // mergedTx will end up with all the signatures; it starts as a clone of the // rawtx: CMutableTransaction mergedTx(txVariants[0]); // Fetch previous transactions (inputs): CCoinsView viewDummy; CCoinsViewCache view(&viewDummy); { LOCK(mempool.cs); CCoinsViewCache &viewChain = *pcoinsTip; CCoinsViewMemPool viewMempool(&viewChain, mempool); // Temporarily switch cache backend to db+mempool view. view.SetBackend(viewMempool); for (const CTxIn &txin : mergedTx.vin) { // Load entries from viewChain into view; can fail. view.AccessCoin(txin.prevout); } // Switch back to avoid locking mempool for too long. view.SetBackend(viewDummy); } bool fGivenKeys = false; CBasicKeyStore tempKeystore; if (request.params.size() > 2 && !request.params[2].isNull()) { fGivenKeys = true; UniValue keys = request.params[2].get_array(); for (size_t idx = 0; idx < keys.size(); idx++) { UniValue k = keys[idx]; CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(k.get_str()); if (!fGood) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); } CKey key = vchSecret.GetKey(); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); } tempKeystore.AddKey(key); } #ifdef ENABLE_WALLET } else if (pwalletMain) { EnsureWalletIsUnlocked(); #endif } // Add previous txouts given in the RPC call: if (request.params.size() > 1 && !request.params[1].isNull()) { UniValue prevTxs = request.params[1].get_array(); for (size_t idx = 0; idx < prevTxs.size(); idx++) { const UniValue &p = prevTxs[idx]; if (!p.isObject()) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "expected object with " "{\"txid'\",\"vout\",\"scriptPubKey\"}"); } UniValue prevOut = p.get_obj(); RPCTypeCheckObj(prevOut, { {"txid", UniValueType(UniValue::VSTR)}, {"vout", UniValueType(UniValue::VNUM)}, {"scriptPubKey", UniValueType(UniValue::VSTR)}, // "amount" is also required but check is done // below due to UniValue::VNUM erroneously // not accepting quoted numerics // (which are valid JSON) }); uint256 txid = ParseHashO(prevOut, "txid"); int nOut = find_value(prevOut, "vout").get_int(); if (nOut < 0) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout must be positive"); } COutPoint out(txid, nOut); std::vector pkData(ParseHexO(prevOut, "scriptPubKey")); CScript scriptPubKey(pkData.begin(), pkData.end()); { const Coin &coin = view.AccessCoin(out); if (!coin.IsSpent() && coin.GetTxOut().scriptPubKey != scriptPubKey) { std::string err("Previous output scriptPubKey mismatch:\n"); err = err + ScriptToAsmStr(coin.GetTxOut().scriptPubKey) + "\nvs:\n" + ScriptToAsmStr(scriptPubKey); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err); } CTxOut txout; txout.scriptPubKey = scriptPubKey; txout.nValue = 0; if (prevOut.exists("amount")) { txout.nValue = AmountFromValue(find_value(prevOut, "amount")); } else { // amount param is required in replay-protected txs. // Note that we must check for its presence here rather // than use RPCTypeCheckObj() above, since UniValue::VNUM // parser incorrectly parses numerics with quotes, eg // "3.12" as a string when JSON allows it to also parse // as numeric. And we have to accept numerics with quotes // because our own dogfood (our rpc results) always // produces decimal numbers that are quoted // eg getbalance returns "3.14152" rather than 3.14152 throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing amount"); } view.AddCoin(out, Coin(txout, 1, false), true); } // If redeemScript given and not using the local wallet (private // keys given), add redeemScript to the tempKeystore so it can be // signed: if (fGivenKeys && scriptPubKey.IsPayToScriptHash()) { RPCTypeCheckObj( prevOut, { {"txid", UniValueType(UniValue::VSTR)}, {"vout", UniValueType(UniValue::VNUM)}, {"scriptPubKey", UniValueType(UniValue::VSTR)}, {"redeemScript", UniValueType(UniValue::VSTR)}, }); UniValue v = find_value(prevOut, "redeemScript"); if (!v.isNull()) { std::vector rsData(ParseHexV(v, "redeemScript")); CScript redeemScript(rsData.begin(), rsData.end()); tempKeystore.AddCScript(redeemScript); } } } } #ifdef ENABLE_WALLET const CKeyStore &keystore = ((fGivenKeys || !pwalletMain) ? tempKeystore : *pwalletMain); #else const CKeyStore &keystore = tempKeystore; #endif int nHashType = SIGHASH_ALL | SIGHASH_FORKID; if (request.params.size() > 3 && !request.params[3].isNull()) { static std::map mapSigHashValues = { {"ALL", SIGHASH_ALL}, {"ALL|ANYONECANPAY", SIGHASH_ALL | SIGHASH_ANYONECANPAY}, {"ALL|FORKID", SIGHASH_ALL | SIGHASH_FORKID}, {"ALL|FORKID|ANYONECANPAY", SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_ANYONECANPAY}, {"NONE", SIGHASH_NONE}, {"NONE|ANYONECANPAY", SIGHASH_NONE | SIGHASH_ANYONECANPAY}, {"NONE|FORKID", SIGHASH_NONE | SIGHASH_FORKID}, {"NONE|FORKID|ANYONECANPAY", SIGHASH_NONE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY}, {"SINGLE", SIGHASH_SINGLE}, {"SINGLE|ANYONECANPAY", SIGHASH_SINGLE | SIGHASH_ANYONECANPAY}, {"SINGLE|FORKID", SIGHASH_SINGLE | SIGHASH_FORKID}, {"SINGLE|FORKID|ANYONECANPAY", SIGHASH_SINGLE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY}, }; std::string strHashType = request.params[3].get_str(); if (!mapSigHashValues.count(strHashType)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid sighash param"); } nHashType = mapSigHashValues[strHashType]; if ((nHashType & SIGHASH_FORKID) == 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Signature must use SIGHASH_FORKID"); } } bool fHashSingle = ((nHashType & ~(SIGHASH_ANYONECANPAY | SIGHASH_FORKID)) == SIGHASH_SINGLE); // Script verification errors. UniValue vErrors(UniValue::VARR); // Use CTransaction for the constant parts of the transaction to avoid // rehashing. const CTransaction txConst(mergedTx); // Sign what we can: for (size_t i = 0; i < mergedTx.vin.size(); i++) { CTxIn &txin = mergedTx.vin[i]; const Coin &coin = view.AccessCoin(txin.prevout); if (coin.IsSpent()) { TxInErrorToJSON(txin, vErrors, "Input not found or already spent"); continue; } const CScript &prevPubKey = coin.GetTxOut().scriptPubKey; const Amount amount = coin.GetTxOut().nValue; SignatureData sigdata; // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mergedTx.vout.size())) { ProduceSignature(MutableTransactionSignatureCreator( &keystore, &mergedTx, i, amount, nHashType), prevPubKey, sigdata); } // ... and merge in other signatures: for (const CMutableTransaction &txv : txVariants) { if (txv.vin.size() > i) { sigdata = CombineSignatures( prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i)); } } UpdateTransaction(mergedTx, i, sigdata); ScriptError serror = SCRIPT_ERR_OK; if (!VerifyScript( txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) { TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror)); } } bool fComplete = vErrors.empty(); UniValue result(UniValue::VOBJ); result.push_back(Pair("hex", EncodeHexTx(mergedTx))); result.push_back(Pair("complete", fComplete)); if (!vErrors.empty()) { result.push_back(Pair("errors", vErrors)); } return result; } static UniValue sendrawtransaction(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "sendrawtransaction \"hexstring\" ( allowhighfees )\n" "\nSubmits raw transaction (serialized, hex-encoded) to local node " "and network.\n" "\nAlso see createrawtransaction and signrawtransaction calls.\n" "\nArguments:\n" "1. \"hexstring\" (string, required) The hex string of the raw " "transaction)\n" "2. allowhighfees (boolean, optional, default=false) Allow high " "fees\n" "\nResult:\n" "\"hex\" (string) The transaction hash in hex\n" "\nExamples:\n" "\nCreate a transaction\n" + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : " "\\\"mytxid\\\",\\\"vout\\\":0}]\" " "\"{\\\"myaddress\\\":0.01}\"") + "Sign the transaction, and get back the hex\n" + HelpExampleCli("signrawtransaction", "\"myhex\"") + "\nSend the transaction (signed hex)\n" + HelpExampleCli("sendrawtransaction", "\"signedhex\"") + "\nAs a json rpc call\n" + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")); } LOCK(cs_main); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); // parse hex string from parameter CMutableTransaction mtx; if (!DecodeHexTx(mtx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } CTransactionRef tx(MakeTransactionRef(std::move(mtx))); const uint256 &txid = tx->GetId(); bool fLimitFree = false; Amount nMaxRawTxFee = maxTxFee; if (request.params.size() > 1 && request.params[1].get_bool()) { nMaxRawTxFee = 0; } CCoinsViewCache &view = *pcoinsTip; bool fHaveChain = false; for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) { const Coin &existingCoin = view.AccessCoin(COutPoint(txid, o)); fHaveChain = !existingCoin.IsSpent(); } bool fHaveMempool = mempool.exists(txid); if (!fHaveMempool && !fHaveChain) { // Push to local node and sync with wallets. CValidationState state; bool fMissingInputs; if (!AcceptToMemoryPool(config, mempool, state, std::move(tx), fLimitFree, &fMissingInputs, nullptr, false, nMaxRawTxFee)) { if (state.IsInvalid()) { throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason())); } else { if (fMissingInputs) { throw JSONRPCError(RPC_TRANSACTION_ERROR, "Missing inputs"); } throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason()); } } } else if (fHaveChain) { throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain"); } if (!g_connman) { throw JSONRPCError( RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } CInv inv(MSG_TX, txid); g_connman->ForEachNode([&inv](CNode *pnode) { pnode->PushInventory(inv); }); return txid.GetHex(); } // clang-format off static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // ------------------- ------------------------ ---------------------- ---------- { "rawtransactions", "getrawtransaction", getrawtransaction, true, {"txid","verbose"} }, { "rawtransactions", "createrawtransaction", createrawtransaction, true, {"inputs","outputs","locktime"} }, { "rawtransactions", "decoderawtransaction", decoderawtransaction, true, {"hexstring"} }, { "rawtransactions", "decodescript", decodescript, true, {"hexstring"} }, { "rawtransactions", "sendrawtransaction", sendrawtransaction, false, {"hexstring","allowhighfees"} }, { "rawtransactions", "signrawtransaction", signrawtransaction, false, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ { "blockchain", "gettxoutproof", gettxoutproof, true, {"txids", "blockhash"} }, { "blockchain", "verifytxoutproof", verifytxoutproof, true, {"proof"} }, }; // clang-format on void RegisterRawTransactionRPCCommands(CRPCTable &t) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { t.appendCommand(commands[vcidx].name, &commands[vcidx]); } } diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index 533e57491..e2b59921c 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -1,278 +1,278 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" #include "data/base58_encode_decode.json.h" #include "data/base58_keys_invalid.json.h" #include "data/base58_keys_valid.json.h" #include "key.h" #include "script/script.h" #include "test/test_bitcoin.h" #include "uint256.h" #include "util.h" #include "utilstrencodings.h" #include #include extern UniValue read_json(const std::string &jsondata); BOOST_FIXTURE_TEST_SUITE(base58_tests, BasicTestingSetup) // Goal: test low-level base58 encoding functionality BOOST_AUTO_TEST_CASE(base58_EncodeBase58) { UniValue tests = read_json(std::string(json_tests::base58_encode_decode, json_tests::base58_encode_decode + sizeof(json_tests::base58_encode_decode))); for (unsigned int idx = 0; idx < tests.size(); idx++) { UniValue test = tests[idx]; std::string strTest = test.write(); // Allow for extra stuff (useful for comments) if (test.size() < 2) { BOOST_ERROR("Bad test: " << strTest); continue; } std::vector sourcedata = ParseHex(test[0].get_str()); std::string base58string = test[1].get_str(); BOOST_CHECK_MESSAGE( EncodeBase58(sourcedata.data(), sourcedata.data() + sourcedata.size()) == base58string, strTest); } } // Goal: test low-level base58 decoding functionality BOOST_AUTO_TEST_CASE(base58_DecodeBase58) { UniValue tests = read_json(std::string(json_tests::base58_encode_decode, json_tests::base58_encode_decode + sizeof(json_tests::base58_encode_decode))); std::vector result; for (unsigned int idx = 0; idx < tests.size(); idx++) { UniValue test = tests[idx]; std::string strTest = test.write(); // Allow for extra stuff (useful for comments) if (test.size() < 2) { BOOST_ERROR("Bad test: " << strTest); continue; } std::vector expected = ParseHex(test[0].get_str()); std::string base58string = test[1].get_str(); BOOST_CHECK_MESSAGE(DecodeBase58(base58string, result), strTest); BOOST_CHECK_MESSAGE( result.size() == expected.size() && std::equal(result.begin(), result.end(), expected.begin()), strTest); } BOOST_CHECK(!DecodeBase58("invalid", result)); // 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)); std::vector expected = ParseHex("971a55"); BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); } // Visitor to check address type class TestAddrTypeVisitor : public boost::static_visitor { private: std::string exp_addrType; public: TestAddrTypeVisitor(const std::string &_exp_addrType) : exp_addrType(_exp_addrType) {} bool operator()(const CKeyID &id) const { return (exp_addrType == "pubkey"); } bool operator()(const CScriptID &id) const { return (exp_addrType == "script"); } bool operator()(const CNoDestination &no) const { return (exp_addrType == "none"); } }; // Visitor to check address payload class TestPayloadVisitor : public boost::static_visitor { private: std::vector exp_payload; public: TestPayloadVisitor(std::vector &_exp_payload) : exp_payload(_exp_payload) {} bool operator()(const CKeyID &id) const { uint160 exp_key(exp_payload); return exp_key == id; } bool operator()(const CScriptID &id) const { uint160 exp_key(exp_payload); return exp_key == id; } bool operator()(const CNoDestination &no) const { return exp_payload.size() == 0; } }; // Goal: check that parsed keys match test payload BOOST_AUTO_TEST_CASE(base58_keys_valid_parse) { UniValue tests = read_json(std::string( json_tests::base58_keys_valid, json_tests::base58_keys_valid + sizeof(json_tests::base58_keys_valid))); CBitcoinSecret secret; CTxDestination destination; SelectParams(CBaseChainParams::MAIN); for (unsigned int idx = 0; idx < tests.size(); idx++) { UniValue test = tests[idx]; std::string strTest = test.write(); // Allow for extra stuff (useful for comments) if (test.size() < 3) { BOOST_ERROR("Bad test: " << strTest); continue; } std::string exp_base58string = test[0].get_str(); std::vector exp_payload = ParseHex(test[1].get_str()); const UniValue &metadata = test[2].get_obj(); bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); bool isTestnet = find_value(metadata, "isTestnet").get_bool(); if (isTestnet) SelectParams(CBaseChainParams::TESTNET); else SelectParams(CBaseChainParams::MAIN); if (isPrivkey) { bool isCompressed = find_value(metadata, "isCompressed").get_bool(); // Must be valid private key BOOST_CHECK_MESSAGE(secret.SetString(exp_base58string), "!SetString:" + strTest); BOOST_CHECK_MESSAGE(secret.IsValid(), "!IsValid:" + strTest); CKey privkey = secret.GetKey(); BOOST_CHECK_MESSAGE(privkey.IsCompressed() == isCompressed, "compressed mismatch:" + strTest); BOOST_CHECK_MESSAGE(privkey.size() == exp_payload.size() && std::equal(privkey.begin(), privkey.end(), exp_payload.begin()), "key mismatch:" + strTest); // Private key must be invalid public key - destination = DecodeDestination(exp_base58string); + destination = DecodeLegacyAddr(exp_base58string, Params()); BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid privkey as pubkey:" + strTest); } else { // "script" or "pubkey" std::string exp_addrType = find_value(metadata, "addrType").get_str(); // Must be valid public key - destination = DecodeDestination(exp_base58string); + destination = DecodeLegacyAddr(exp_base58string, Params()); BOOST_CHECK_MESSAGE(IsValidDestination(destination), "!IsValid:" + strTest); BOOST_CHECK_MESSAGE((boost::get(&destination) != nullptr) == (exp_addrType == "script"), "isScript mismatch" + strTest); BOOST_CHECK_MESSAGE( boost::apply_visitor(TestAddrTypeVisitor(exp_addrType), destination), "addrType mismatch" + strTest); // Public key must be invalid private key secret.SetString(exp_base58string); BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid pubkey as privkey:" + strTest); } } } // Goal: check that generated keys match test vectors BOOST_AUTO_TEST_CASE(base58_keys_valid_gen) { UniValue tests = read_json(std::string( json_tests::base58_keys_valid, json_tests::base58_keys_valid + sizeof(json_tests::base58_keys_valid))); for (unsigned int idx = 0; idx < tests.size(); idx++) { UniValue test = tests[idx]; std::string strTest = test.write(); // Allow for extra stuff (useful for comments) if (test.size() < 3) { BOOST_ERROR("Bad test: " << strTest); continue; } std::string exp_base58string = test[0].get_str(); std::vector exp_payload = ParseHex(test[1].get_str()); const UniValue &metadata = test[2].get_obj(); bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); bool isTestnet = find_value(metadata, "isTestnet").get_bool(); if (isTestnet) SelectParams(CBaseChainParams::TESTNET); else SelectParams(CBaseChainParams::MAIN); if (isPrivkey) { bool isCompressed = find_value(metadata, "isCompressed").get_bool(); CKey key; key.Set(exp_payload.begin(), exp_payload.end(), isCompressed); assert(key.IsValid()); CBitcoinSecret secret; secret.SetKey(key); BOOST_CHECK_MESSAGE(secret.ToString() == exp_base58string, "result mismatch: " + strTest); } else { std::string exp_addrType = find_value(metadata, "addrType").get_str(); CTxDestination dest; if (exp_addrType == "pubkey") { dest = CKeyID(uint160(exp_payload)); } else if (exp_addrType == "script") { dest = CScriptID(uint160(exp_payload)); } else if (exp_addrType == "none") { dest = CNoDestination(); } else { BOOST_ERROR("Bad addrtype: " << strTest); continue; } - std::string address = EncodeDestination(dest); + std::string address = EncodeLegacyAddr(dest, Params()); BOOST_CHECK_MESSAGE(address == exp_base58string, "mismatch: " + strTest); } } SelectParams(CBaseChainParams::MAIN); } // Goal: check that base58 parsing code is robust against a variety of corrupted // data BOOST_AUTO_TEST_CASE(base58_keys_invalid) { // Negative testcases UniValue tests = read_json(std::string(json_tests::base58_keys_invalid, json_tests::base58_keys_invalid + sizeof(json_tests::base58_keys_invalid))); CBitcoinSecret secret; CTxDestination destination; for (unsigned int idx = 0; idx < tests.size(); idx++) { UniValue test = tests[idx]; std::string strTest = test.write(); // Allow for extra stuff (useful for comments) if (test.size() < 1) { BOOST_ERROR("Bad test: " << strTest); continue; } std::string exp_base58string = test[0].get_str(); // must be invalid as public and as private key - destination = DecodeDestination(exp_base58string); + destination = DecodeLegacyAddr(exp_base58string, Params()); BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid pubkey:" + strTest); secret.SetString(exp_base58string); BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid privkey:" + strTest); } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/dstencode_tests.cpp b/src/test/dstencode_tests.cpp new file mode 100644 index 000000000..e1df89b4c --- /dev/null +++ b/src/test/dstencode_tests.cpp @@ -0,0 +1,78 @@ +// Copyright (c) 2017 The Bitcoin 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 "config.h" +#include "dstencode.h" +#include "test/test_bitcoin.h" + +#include + +namespace { + +class DummyCfg : public Config { +public: + DummyCfg() : useCashAddr(false) {} + virtual bool SetMaxBlockSize(uint64_t maxBlockSize) { return false; } + virtual uint64_t GetMaxBlockSize() const { return 0; } + virtual bool SetBlockPriorityPercentage(int64_t blockPriorityPercentage) { + return false; + } + virtual uint8_t GetBlockPriorityPercentage() const { return 0; } + virtual const CChainParams &GetChainParams() const { + return Params(CBaseChainParams::MAIN); + } + virtual void SetCashAddrEncoding(bool b) { useCashAddr = b; } + virtual bool UseCashAddrEncoding() const { return useCashAddr; } + +private: + bool useCashAddr; +}; + +} // anon ns + +BOOST_FIXTURE_TEST_SUITE(dstencode_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(test_addresses) { + std::vector hash = {118, 160, 64, 83, 189, 160, 168, + 139, 218, 81, 119, 184, 106, 21, + 195, 178, 159, 85, 152, 115}; + + const CTxDestination dstKey = CKeyID(uint160(hash)); + const CTxDestination dstScript = CScriptID(uint160(hash)); + + std::string cashaddr_pubkey = + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"; + std::string cashaddr_script = + "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq"; + std::string base58_pubkey = "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"; + std::string base58_script = "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"; + + const CChainParams ¶ms = Params(CBaseChainParams::MAIN); + DummyCfg cfg; + + // Check encoding + cfg.SetCashAddrEncoding(true); + BOOST_CHECK_EQUAL(cashaddr_pubkey, EncodeDestination(dstKey, params, cfg)); + BOOST_CHECK_EQUAL(cashaddr_script, + EncodeDestination(dstScript, params, cfg)); + cfg.SetCashAddrEncoding(false); + BOOST_CHECK_EQUAL(base58_pubkey, EncodeDestination(dstKey, params, cfg)); + BOOST_CHECK_EQUAL(base58_script, EncodeDestination(dstScript, params, cfg)); + + // Check decoding + BOOST_CHECK(dstKey == DecodeDestination(cashaddr_pubkey, params)); + BOOST_CHECK(dstScript == DecodeDestination(cashaddr_script, params)); + BOOST_CHECK(dstKey == DecodeDestination(base58_pubkey, params)); + BOOST_CHECK(dstScript == DecodeDestination(base58_script, params)); + + // Validation + BOOST_CHECK(IsValidDestinationString(cashaddr_pubkey, params)); + BOOST_CHECK(IsValidDestinationString(cashaddr_script, params)); + BOOST_CHECK(IsValidDestinationString(base58_pubkey, params)); + BOOST_CHECK(IsValidDestinationString(base58_script, params)); + BOOST_CHECK(!IsValidDestinationString("notvalid", params)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 3c4a28907..c53b5b904 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -1,205 +1,206 @@ // Copyright (c) 2012-2015 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 "key.h" #include "base58.h" +#include "dstencode.h" #include "script/script.h" #include "test/test_bitcoin.h" #include "uint256.h" #include "util.h" #include "utilstrencodings.h" #include #include #include static const std::string strSecret1 = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"; static const std::string strSecret2 = "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"; static const std::string strSecret1C = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"; static const std::string strSecret2C = "L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"; static const std::string addr1 = "1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ"; static const std::string addr2 = "1F5y5E5FMc5YzdJtB9hLaUe43GDxEKXENJ"; static const std::string addr1C = "1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs"; static const std::string addr2C = "1CRj2HyM1CXWzHAXLQtiGLyggNT9WQqsDs"; static const std::string strAddressBad = "1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF"; #ifdef KEY_TESTS_DUMPINFO void dumpKeyInfo(uint256 privkey) { CKey key; key.resize(32); memcpy(&secret[0], &privkey, 32); std::vector sec; sec.resize(32); memcpy(&sec[0], &secret[0], 32); printf(" * secret (hex): %s\n", HexStr(sec).c_str()); for (int nCompressed = 0; nCompressed < 2; nCompressed++) { bool fCompressed = nCompressed == 1; printf(" * %s:\n", fCompressed ? "compressed" : "uncompressed"); CBitcoinSecret bsecret; bsecret.SetSecret(secret, fCompressed); printf(" * secret (base58): %s\n", bsecret.ToString().c_str()); CKey key; key.SetSecret(secret, fCompressed); std::vector vchPubKey = key.GetPubKey(); printf(" * pubkey (hex): %s\n", HexStr(vchPubKey).c_str()); printf(" * address (base58): %s\n", CBitcoinAddress(vchPubKey).ToString().c_str()); } } #endif BOOST_FIXTURE_TEST_SUITE(key_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(key_test1) { CBitcoinSecret bsecret1, bsecret2, bsecret1C, bsecret2C, baddress1; BOOST_CHECK(bsecret1.SetString(strSecret1)); BOOST_CHECK(bsecret2.SetString(strSecret2)); BOOST_CHECK(bsecret1C.SetString(strSecret1C)); BOOST_CHECK(bsecret2C.SetString(strSecret2C)); BOOST_CHECK(!baddress1.SetString(strAddressBad)); CKey key1 = bsecret1.GetKey(); BOOST_CHECK(key1.IsCompressed() == false); CKey key2 = bsecret2.GetKey(); BOOST_CHECK(key2.IsCompressed() == false); CKey key1C = bsecret1C.GetKey(); BOOST_CHECK(key1C.IsCompressed() == true); CKey key2C = bsecret2C.GetKey(); BOOST_CHECK(key2C.IsCompressed() == true); CPubKey pubkey1 = key1.GetPubKey(); CPubKey pubkey2 = key2.GetPubKey(); CPubKey pubkey1C = key1C.GetPubKey(); CPubKey pubkey2C = key2C.GetPubKey(); BOOST_CHECK(key1.VerifyPubKey(pubkey1)); BOOST_CHECK(!key1.VerifyPubKey(pubkey1C)); BOOST_CHECK(!key1.VerifyPubKey(pubkey2)); BOOST_CHECK(!key1.VerifyPubKey(pubkey2C)); BOOST_CHECK(!key1C.VerifyPubKey(pubkey1)); BOOST_CHECK(key1C.VerifyPubKey(pubkey1C)); BOOST_CHECK(!key1C.VerifyPubKey(pubkey2)); BOOST_CHECK(!key1C.VerifyPubKey(pubkey2C)); BOOST_CHECK(!key2.VerifyPubKey(pubkey1)); BOOST_CHECK(!key2.VerifyPubKey(pubkey1C)); BOOST_CHECK(key2.VerifyPubKey(pubkey2)); BOOST_CHECK(!key2.VerifyPubKey(pubkey2C)); BOOST_CHECK(!key2C.VerifyPubKey(pubkey1)); BOOST_CHECK(!key2C.VerifyPubKey(pubkey1C)); BOOST_CHECK(!key2C.VerifyPubKey(pubkey2)); BOOST_CHECK(key2C.VerifyPubKey(pubkey2C)); BOOST_CHECK(DecodeDestination(addr1) == CTxDestination(pubkey1.GetID())); BOOST_CHECK(DecodeDestination(addr2) == CTxDestination(pubkey2.GetID())); BOOST_CHECK(DecodeDestination(addr1C) == CTxDestination(pubkey1C.GetID())); BOOST_CHECK(DecodeDestination(addr2C) == CTxDestination(pubkey2C.GetID())); for (int n = 0; n < 16; n++) { std::string strMsg = strprintf("Very secret message %i: 11", n); uint256 hashMsg = Hash(strMsg.begin(), strMsg.end()); // normal signatures std::vector sign1, sign2, sign1C, sign2C; BOOST_CHECK(key1.Sign(hashMsg, sign1)); BOOST_CHECK(key2.Sign(hashMsg, sign2)); BOOST_CHECK(key1C.Sign(hashMsg, sign1C)); BOOST_CHECK(key2C.Sign(hashMsg, sign2C)); BOOST_CHECK(pubkey1.Verify(hashMsg, sign1)); BOOST_CHECK(!pubkey1.Verify(hashMsg, sign2)); BOOST_CHECK(pubkey1.Verify(hashMsg, sign1C)); BOOST_CHECK(!pubkey1.Verify(hashMsg, sign2C)); BOOST_CHECK(!pubkey2.Verify(hashMsg, sign1)); BOOST_CHECK(pubkey2.Verify(hashMsg, sign2)); BOOST_CHECK(!pubkey2.Verify(hashMsg, sign1C)); BOOST_CHECK(pubkey2.Verify(hashMsg, sign2C)); BOOST_CHECK(pubkey1C.Verify(hashMsg, sign1)); BOOST_CHECK(!pubkey1C.Verify(hashMsg, sign2)); BOOST_CHECK(pubkey1C.Verify(hashMsg, sign1C)); BOOST_CHECK(!pubkey1C.Verify(hashMsg, sign2C)); BOOST_CHECK(!pubkey2C.Verify(hashMsg, sign1)); BOOST_CHECK(pubkey2C.Verify(hashMsg, sign2)); BOOST_CHECK(!pubkey2C.Verify(hashMsg, sign1C)); BOOST_CHECK(pubkey2C.Verify(hashMsg, sign2C)); // compact signatures (with key recovery) std::vector csign1, csign2, csign1C, csign2C; BOOST_CHECK(key1.SignCompact(hashMsg, csign1)); BOOST_CHECK(key2.SignCompact(hashMsg, csign2)); BOOST_CHECK(key1C.SignCompact(hashMsg, csign1C)); BOOST_CHECK(key2C.SignCompact(hashMsg, csign2C)); CPubKey rkey1, rkey2, rkey1C, rkey2C; BOOST_CHECK(rkey1.RecoverCompact(hashMsg, csign1)); BOOST_CHECK(rkey2.RecoverCompact(hashMsg, csign2)); BOOST_CHECK(rkey1C.RecoverCompact(hashMsg, csign1C)); BOOST_CHECK(rkey2C.RecoverCompact(hashMsg, csign2C)); BOOST_CHECK(rkey1 == pubkey1); BOOST_CHECK(rkey2 == pubkey2); BOOST_CHECK(rkey1C == pubkey1C); BOOST_CHECK(rkey2C == pubkey2C); } // test deterministic signing std::vector detsig, detsigc; std::string strMsg = "Very deterministic message"; uint256 hashMsg = Hash(strMsg.begin(), strMsg.end()); BOOST_CHECK(key1.Sign(hashMsg, detsig)); BOOST_CHECK(key1C.Sign(hashMsg, detsigc)); BOOST_CHECK(detsig == detsigc); BOOST_CHECK(detsig == ParseHex("304402205dbbddda71772d95ce91cd2d14b592cfbc1dd0aabd6a3" "94b6c2d377bbe59d31d022014ddda21494a4e221f0824f0b8b924" "c43fa43c0ad57dccdaa11f81a6bd4582f6")); BOOST_CHECK(key2.Sign(hashMsg, detsig)); BOOST_CHECK(key2C.Sign(hashMsg, detsigc)); BOOST_CHECK(detsig == detsigc); BOOST_CHECK(detsig == ParseHex("3044022052d8a32079c11e79db95af63bb9600c5b04f21a9ca33d" "c129c2bfa8ac9dc1cd5022061d8ae5e0f6c1a16bde3719c64c2fd" "70e404b6428ab9a69566962e8771b5944d")); BOOST_CHECK(key1.SignCompact(hashMsg, detsig)); BOOST_CHECK(key1C.SignCompact(hashMsg, detsigc)); BOOST_CHECK(detsig == ParseHex("1c5dbbddda71772d95ce91cd2d14b592cfbc1dd0aabd6a394b6c2" "d377bbe59d31d14ddda21494a4e221f0824f0b8b924c43fa43c0a" "d57dccdaa11f81a6bd4582f6")); BOOST_CHECK(detsigc == ParseHex("205dbbddda71772d95ce91cd2d14b592cfbc1dd0aabd6a394b6c2" "d377bbe59d31d14ddda21494a4e221f0824f0b8b924c43fa43c0a" "d57dccdaa11f81a6bd4582f6")); BOOST_CHECK(key2.SignCompact(hashMsg, detsig)); BOOST_CHECK(key2C.SignCompact(hashMsg, detsigc)); BOOST_CHECK(detsig == ParseHex("1c52d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2" "bfa8ac9dc1cd561d8ae5e0f6c1a16bde3719c64c2fd70e404b642" "8ab9a69566962e8771b5944d")); BOOST_CHECK(detsigc == ParseHex("2052d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2" "bfa8ac9dc1cd561d8ae5e0f6c1a16bde3719c64c2fd70e404b642" "8ab9a69566962e8771b5944d")); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 8b7fbc3bf..57bfe07d4 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1,1248 +1,1249 @@ // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" #include "chain.h" #include "core_io.h" +#include "dstencode.h" #include "init.h" #include "merkleblock.h" #include "rpc/server.h" #include "script/script.h" #include "script/standard.h" #include "sync.h" #include "util.h" #include "utiltime.h" #include "validation.h" #include "wallet.h" #include #include #include #include #include void EnsureWalletIsUnlocked(); bool EnsureWalletIsAvailable(bool avoidException); static std::string EncodeDumpTime(int64_t nTime) { return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime); } static int64_t DecodeDumpTime(const std::string &str) { static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); static const std::locale loc( std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ")); std::istringstream iss(str); iss.imbue(loc); boost::posix_time::ptime ptime(boost::date_time::not_a_date_time); iss >> ptime; if (ptime.is_not_a_date_time()) return 0; return (ptime - epoch).total_seconds(); } static std::string EncodeDumpString(const std::string &str) { std::stringstream ret; for (uint8_t c : str) { if (c <= 32 || c >= 128 || c == '%') { ret << '%' << HexStr(&c, &c + 1); } else { ret << c; } } return ret.str(); } std::string DecodeDumpString(const std::string &str) { std::stringstream ret; for (unsigned int pos = 0; pos < str.length(); pos++) { uint8_t c = str[pos]; if (c == '%' && pos + 2 < str.length()) { c = (((str[pos + 1] >> 6) * 9 + ((str[pos + 1] - '0') & 15)) << 4) | ((str[pos + 2] >> 6) * 9 + ((str[pos + 2] - '0') & 15)); pos += 2; } ret << c; } return ret.str(); } UniValue importprivkey(const Config &config, const JSONRPCRequest &request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( "importprivkey \"bitcoinprivkey\" ( \"label\" ) ( rescan )\n" "\nAdds a private key (as returned by dumpprivkey) to your " "wallet.\n" "\nArguments:\n" "1. \"bitcoinprivkey\" (string, required) The private key (see " "dumpprivkey)\n" "2. \"label\" (string, optional, default=\"\") An " "optional label\n" "3. rescan (boolean, optional, default=true) Rescan " "the wallet for transactions\n" "\nNote: This call can take minutes to complete if rescan is " "true.\n" "\nExamples:\n" "\nDump a private key\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + "\nImport the private key with rescan\n" + HelpExampleCli("importprivkey", "\"mykey\"") + "\nImport using a label and without rescan\n" + HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") + "\nImport using default blank label and without rescan\n" + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")); LOCK2(cs_main, pwalletMain->cs_wallet); EnsureWalletIsUnlocked(); std::string strSecret = request.params[0].get_str(); std::string strLabel = ""; if (request.params.size() > 1) strLabel = request.params[1].get_str(); // Whether to perform rescan after import bool fRescan = true; if (request.params.size() > 2) fRescan = request.params[2].get_bool(); if (fRescan && fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(strSecret); if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); CKey key = vchSecret.GetKey(); if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); CKeyID vchAddress = pubkey.GetID(); { pwalletMain->MarkDirty(); pwalletMain->SetAddressBook(vchAddress, strLabel, "receive"); // Don't throw error in case a key is already there if (pwalletMain->HaveKey(vchAddress)) return NullUniValue; pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1; if (!pwalletMain->AddKeyPubKey(key, pubkey)) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); // whenever a key is imported, we need to scan the whole chain pwalletMain->UpdateTimeFirstKey(1); if (fRescan) { pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); } } return NullUniValue; } void ImportAddress(const CTxDestination &dest, const std::string &strLabel); void ImportScript(const CScript &script, const std::string &strLabel, bool isRedeemScript) { if (!isRedeemScript && ::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the " "private key for this address or " "script"); pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script, 0 /* nCreateTime */)) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); if (isRedeemScript) { if (!pwalletMain->HaveCScript(script) && !pwalletMain->AddCScript(script)) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); ImportAddress(CScriptID(script), strLabel); } else { CTxDestination destination; if (ExtractDestination(script, destination)) { pwalletMain->SetAddressBook(destination, strLabel, "receive"); } } } void ImportAddress(const CTxDestination &dest, const std::string &strLabel) { CScript script = GetScriptForDestination(dest); ImportScript(script, strLabel, false); // add to address book or update label if (IsValidDestination(dest)) pwalletMain->SetAddressBook(dest, strLabel, "receive"); } UniValue importaddress(const Config &config, const JSONRPCRequest &request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw std::runtime_error( "importaddress \"address\" ( \"label\" rescan p2sh )\n" "\nAdds a script (in hex) or address that can be watched as if it " "were in your wallet but cannot be used to spend.\n" "\nArguments:\n" "1. \"script\" (string, required) The hex-encoded script " "(or address)\n" "2. \"label\" (string, optional, default=\"\") An " "optional label\n" "3. rescan (boolean, optional, default=true) Rescan " "the wallet for transactions\n" "4. p2sh (boolean, optional, default=false) Add " "the P2SH version of the script as well\n" "\nNote: This call can take minutes to complete if rescan is " "true.\n" "If you have the full public key, you should call importpubkey " "instead of this.\n" "\nNote: If you import a non-standard raw script in hex form, " "outputs sending to it will be treated\n" "as change, and not show up in many RPCs.\n" "\nExamples:\n" "\nImport a script with rescan\n" + HelpExampleCli("importaddress", "\"myscript\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false")); std::string strLabel = ""; if (request.params.size() > 1) strLabel = request.params[1].get_str(); // Whether to perform rescan after import bool fRescan = true; if (request.params.size() > 2) fRescan = request.params[2].get_bool(); if (fRescan && fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); // Whether to import a p2sh version, too bool fP2SH = false; if (request.params.size() > 3) fP2SH = request.params[3].get_bool(); LOCK2(cs_main, pwalletMain->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (IsValidDestination(dest)) { if (fP2SH) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use " "a script instead"); } ImportAddress(dest, strLabel); } else if (IsHex(request.params[0].get_str())) { std::vector data(ParseHex(request.params[0].get_str())); ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH); } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); } if (fRescan) { pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); pwalletMain->ReacceptWalletTransactions(); } return NullUniValue; } UniValue importprunedfunds(const Config &config, const JSONRPCRequest &request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() != 2) throw std::runtime_error( "importprunedfunds\n" "\nImports funds without rescan. Corresponding address or script " "must previously be included in wallet. Aimed towards pruned " "wallets. The end-user is responsible to import additional " "transactions that subsequently spend the imported outputs or " "rescan after the point in the blockchain the transaction is " "included.\n" "\nArguments:\n" "1. \"rawtransaction\" (string, required) A raw transaction in hex " "funding an already-existing address in wallet\n" "2. \"txoutproof\" (string, required) The hex output from " "gettxoutproof that contains the transaction\n"); CMutableTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); uint256 txid = tx.GetId(); CWalletTx wtx(pwalletMain, MakeTransactionRef(std::move(tx))); CDataStream ssMB(ParseHexV(request.params[1], "proof"), SER_NETWORK, PROTOCOL_VERSION); CMerkleBlock merkleBlock; ssMB >> merkleBlock; // Search partial merkle tree in proof for our transaction and index in // valid block std::vector vMatch; std::vector vIndex; unsigned int txnIndex = 0; if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) { LOCK(cs_main); if (!mapBlockIndex.count(merkleBlock.header.GetHash()) || !chainActive.Contains(mapBlockIndex[merkleBlock.header.GetHash()])) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); std::vector::const_iterator it; if ((it = std::find(vMatch.begin(), vMatch.end(), txid)) == vMatch.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof"); } txnIndex = vIndex[it - vMatch.begin()]; } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); } wtx.nIndex = txnIndex; wtx.hashBlock = merkleBlock.header.GetHash(); LOCK2(cs_main, pwalletMain->cs_wallet); if (pwalletMain->IsMine(wtx)) { pwalletMain->AddToWallet(wtx, false); return NullUniValue; } throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction"); } UniValue removeprunedfunds(const Config &config, const JSONRPCRequest &request) { if (!EnsureWalletIsAvailable(request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "removeprunedfunds \"txid\"\n" "\nDeletes the specified transaction from the wallet. Meant for " "use with pruned wallets and as a companion to importprunedfunds. " "This will effect wallet balances.\n" "\nArguments:\n" "1. \"txid\" (string, required) The hex-encoded id of " "the transaction you are deleting\n" "\nExamples:\n" + HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1" "ce581bebf46446a512166eae762873" "4ea0a5\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("removprunedfunds", "\"a8d0c0184dde994a09ec054286f1c" "e581bebf46446a512166eae7628734e" "a0a5\"")); } LOCK2(cs_main, pwalletMain->cs_wallet); uint256 hash; hash.SetHex(request.params[0].get_str()); std::vector vHash; vHash.push_back(hash); std::vector vHashOut; if (pwalletMain->ZapSelectTx(vHash, vHashOut) != DB_LOAD_OK) { throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete the transaction."); } if (vHashOut.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet."); } return NullUniValue; } UniValue importpubkey(const Config &config, const JSONRPCRequest &request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw std::runtime_error( "importpubkey \"pubkey\" ( \"label\" rescan )\n" "\nAdds a public key (in hex) that can be watched as if it were in " "your wallet but cannot be used to spend.\n" "\nArguments:\n" "1. \"pubkey\" (string, required) The hex-encoded public " "key\n" "2. \"label\" (string, optional, default=\"\") An " "optional label\n" "3. rescan (boolean, optional, default=true) Rescan " "the wallet for transactions\n" "\nNote: This call can take minutes to complete if rescan is " "true.\n" "\nExamples:\n" "\nImport a public key with rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")); std::string strLabel = ""; if (request.params.size() > 1) strLabel = request.params[1].get_str(); // Whether to perform rescan after import bool fRescan = true; if (request.params.size() > 2) fRescan = request.params[2].get_bool(); if (fRescan && fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); if (!IsHex(request.params[0].get_str())) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); std::vector data(ParseHex(request.params[0].get_str())); CPubKey pubKey(data.begin(), data.end()); if (!pubKey.IsFullyValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); LOCK2(cs_main, pwalletMain->cs_wallet); ImportAddress(pubKey.GetID(), strLabel); ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false); if (fRescan) { pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); pwalletMain->ReacceptWalletTransactions(); } return NullUniValue; } UniValue importwallet(const Config &config, const JSONRPCRequest &request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "importwallet \"filename\"\n" "\nImports keys from a wallet dump file (see dumpwallet).\n" "\nArguments:\n" "1. \"filename\" (string, required) The wallet file\n" "\nExamples:\n" "\nDump the wallet\n" + HelpExampleCli("dumpwallet", "\"test\"") + "\nImport the wallet\n" + HelpExampleCli("importwallet", "\"test\"") + "\nImport using the json rpc call\n" + HelpExampleRpc("importwallet", "\"test\"")); if (fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode"); LOCK2(cs_main, pwalletMain->cs_wallet); EnsureWalletIsUnlocked(); std::ifstream file; file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate); if (!file.is_open()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); int64_t nTimeBegin = chainActive.Tip()->GetBlockTime(); bool fGood = true; int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); file.seekg(0, file.beg); pwalletMain->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI while (file.good()) { pwalletMain->ShowProgress( "", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100)))); std::string line; std::getline(file, line); if (line.empty() || line[0] == '#') continue; std::vector vstr; boost::split(vstr, line, boost::is_any_of(" ")); if (vstr.size() < 2) continue; CBitcoinSecret vchSecret; if (!vchSecret.SetString(vstr[0])) continue; CKey key = vchSecret.GetKey(); CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); CKeyID keyid = pubkey.GetID(); if (pwalletMain->HaveKey(keyid)) { LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid)); continue; } int64_t nTime = DecodeDumpTime(vstr[1]); std::string strLabel; bool fLabel = true; for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { if (boost::algorithm::starts_with(vstr[nStr], "#")) break; if (vstr[nStr] == "change=1") fLabel = false; if (vstr[nStr] == "reserve=1") fLabel = false; if (boost::algorithm::starts_with(vstr[nStr], "label=")) { strLabel = DecodeDumpString(vstr[nStr].substr(6)); fLabel = true; } } LogPrintf("Importing %s...\n", EncodeDestination(keyid)); if (!pwalletMain->AddKeyPubKey(key, pubkey)) { fGood = false; continue; } pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime; if (fLabel) pwalletMain->SetAddressBook(keyid, strLabel, "receive"); nTimeBegin = std::min(nTimeBegin, nTime); } file.close(); pwalletMain->ShowProgress("", 100); // hide progress dialog in GUI CBlockIndex *pindex = chainActive.Tip(); while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - 7200) pindex = pindex->pprev; pwalletMain->UpdateTimeFirstKey(nTimeBegin); LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->nHeight + 1); pwalletMain->ScanForWalletTransactions(pindex); pwalletMain->MarkDirty(); if (!fGood) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet"); return NullUniValue; } UniValue dumpprivkey(const Config &config, const JSONRPCRequest &request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "dumpprivkey \"address\"\n" "\nReveals the private key corresponding to 'address'.\n" "Then the importprivkey can be used with this output\n" "\nArguments:\n" "1. \"address\" (string, required) The bitcoin address for the " "private key\n" "\nResult:\n" "\"key\" (string) The private key\n" "\nExamples:\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + HelpExampleCli("importprivkey", "\"mykey\"") + HelpExampleRpc("dumpprivkey", "\"myaddress\"")); LOCK2(cs_main, pwalletMain->cs_wallet); EnsureWalletIsUnlocked(); std::string strAddress = request.params[0].get_str(); CTxDestination dest = DecodeDestination(strAddress); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } const CKeyID *keyID = boost::get(&dest); if (!keyID) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); } CKey vchSecret; if (!pwalletMain->GetKey(*keyID, vchSecret)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); } return CBitcoinSecret(vchSecret).ToString(); } UniValue dumpwallet(const Config &config, const JSONRPCRequest &request) { if (!EnsureWalletIsAvailable(request.fHelp)) return NullUniValue; if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "dumpwallet \"filename\"\n" "\nDumps all wallet keys in a human-readable format.\n" "\nArguments:\n" "1. \"filename\" (string, required) The filename\n" "\nExamples:\n" + HelpExampleCli("dumpwallet", "\"test\"") + HelpExampleRpc("dumpwallet", "\"test\"")); LOCK2(cs_main, pwalletMain->cs_wallet); EnsureWalletIsUnlocked(); std::ofstream file; file.open(request.params[0].get_str().c_str()); if (!file.is_open()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); std::map mapKeyBirth; std::set setKeyPool; pwalletMain->GetKeyBirthTimes(mapKeyBirth); pwalletMain->GetAllReserveKeys(setKeyPool); // sort time/key pairs std::vector> vKeyBirth; for (const auto &entry : mapKeyBirth) { if (const CKeyID *keyID = boost::get(&entry.first)) { // set and test vKeyBirth.push_back(std::make_pair(entry.second, *keyID)); } } mapKeyBirth.clear(); std::sort(vKeyBirth.begin(), vKeyBirth.end()); // produce output file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD); file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime())); file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString()); file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime())); file << "\n"; // add the base58check encoded extended master if the wallet uses HD CKeyID masterKeyID = pwalletMain->GetHDChain().masterKeyID; if (!masterKeyID.IsNull()) { CKey key; if (pwalletMain->GetKey(masterKeyID, key)) { CExtKey masterKey; masterKey.SetMaster(key.begin(), key.size()); CBitcoinExtKey b58extkey; b58extkey.SetKey(masterKey); file << "# extended private masterkey: " << b58extkey.ToString() << "\n\n"; } } for (std::vector>::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { const CKeyID &keyid = it->second; std::string strTime = EncodeDumpTime(it->first); std::string strAddr = EncodeDestination(keyid); CKey key; if (pwalletMain->GetKey(keyid, key)) { file << strprintf("%s %s ", CBitcoinSecret(key).ToString(), strTime); if (pwalletMain->mapAddressBook.count(keyid)) { file << strprintf( "label=%s", EncodeDumpString(pwalletMain->mapAddressBook[keyid].name)); } else if (keyid == masterKeyID) { file << "hdmaster=1"; } else if (setKeyPool.count(keyid)) { file << "reserve=1"; } else if (pwalletMain->mapKeyMetadata[keyid].hdKeypath == "m") { file << "inactivehdmaster=1"; } else { file << "change=1"; } file << strprintf( " # addr=%s%s\n", strAddr, (pwalletMain->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath=" + pwalletMain->mapKeyMetadata[keyid].hdKeypath : "")); } } file << "\n"; file << "# End of dump\n"; file.close(); return NullUniValue; } UniValue ProcessImport(const UniValue &data, const int64_t timestamp) { try { bool success = false; // Required fields. const UniValue &scriptPubKey = data["scriptPubKey"]; // Should have script or JSON with "address". if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scriptPubKey"); } // Optional fields. const std::string &strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; const UniValue &pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); const UniValue &keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); const bool &internal = data.exists("internal") ? data["internal"].get_bool() : false; const bool &watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; const std::string &label = data.exists("label") && !internal ? data["label"].get_str() : ""; bool isScript = scriptPubKey.getType() == UniValue::VSTR; bool isP2SH = strRedeemScript.length() > 0; const std::string &output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); // Parse the output. CScript script; CTxDestination dest; if (!isScript) { dest = DecodeDestination(output); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); } script = GetScriptForDestination(dest); } else { if (!IsHex(output)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey"); } std::vector vData(ParseHex(output)); script = CScript(vData.begin(), vData.end()); } // Watchonly and private keys if (watchOnly && keys.size()) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Incompatibility found between watchonly and keys"); } // Internal + Label if (internal && data.exists("label")) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Incompatibility found between internal and label"); } // Not having Internal + Script if (!internal && isScript) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set for hex scriptPubKey"); } // Keys / PubKeys size check. if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address"); } // Invalid P2SH redeemScript if (isP2SH && !IsHex(strRedeemScript)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script"); } // Process. // // P2SH if (isP2SH) { // Import redeem script. std::vector vData(ParseHex(strRedeemScript)); CScript redeemScript = CScript(vData.begin(), vData.end()); // Invalid P2SH address if (!script.IsPayToScriptHash()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script"); } pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(redeemScript) && !pwalletMain->AddWatchOnly(redeemScript, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } if (!pwalletMain->HaveCScript(redeemScript) && !pwalletMain->AddCScript(redeemScript)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); } CTxDestination redeem_dest = CScriptID(redeemScript); CScript redeemDestination = GetScriptForDestination(redeem_dest); if (::IsMine(*pwalletMain, redeemDestination) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private " "key for this address or script"); } pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(redeemDestination) && !pwalletMain->AddWatchOnly(redeemDestination, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } // add to address book or update label if (IsValidDestination(dest)) { pwalletMain->SetAddressBook(dest, label, "receive"); } // Import private keys. if (keys.size()) { for (size_t i = 0; i < keys.size(); i++) { const std::string &privkey = keys[i].get_str(); CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(privkey); if (!fGood) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CKey key = vchSecret.GetKey(); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); } CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); CKeyID vchAddress = pubkey.GetID(); pwalletMain->MarkDirty(); pwalletMain->SetAddressBook(vchAddress, label, "receive"); if (pwalletMain->HaveKey(vchAddress)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key"); } pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = timestamp; if (!pwalletMain->AddKeyPubKey(key, pubkey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } pwalletMain->UpdateTimeFirstKey(timestamp); } } success = true; } else { // Import public keys. if (pubKeys.size() && keys.size() == 0) { const std::string &strPubKey = pubKeys[0].get_str(); if (!IsHex(strPubKey)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); } std::vector vData(ParseHex(strPubKey)); CPubKey pubKey(vData.begin(), vData.end()); if (!pubKey.IsFullyValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); } CTxDestination pubkey_dest = pubKey.GetID(); // Consistency check. if (!isScript && !(pubkey_dest == dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); } // Consistency check. if (isScript) { CTxDestination destination; if (ExtractDestination(script, destination)) { if (!(destination == pubkey_dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); } } } CScript pubKeyScript = GetScriptForDestination(pubkey_dest); if (::IsMine(*pwalletMain, pubKeyScript) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already " "contains the private " "key for this address " "or script"); } pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(pubKeyScript) && !pwalletMain->AddWatchOnly(pubKeyScript, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } // add to address book or update label if (IsValidDestination(pubkey_dest)) { pwalletMain->SetAddressBook(pubkey_dest, label, "receive"); } // TODO Is this necessary? CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey); if (::IsMine(*pwalletMain, scriptRawPubKey) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already " "contains the private " "key for this address " "or script"); } pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(scriptRawPubKey) && !pwalletMain->AddWatchOnly(scriptRawPubKey, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } success = true; } // Import private keys. if (keys.size()) { const std::string &strPrivkey = keys[0].get_str(); // Checks. CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(strPrivkey); if (!fGood) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CKey key = vchSecret.GetKey(); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); } CPubKey pubKey = key.GetPubKey(); assert(key.VerifyPubKey(pubKey)); CTxDestination pubkey_dest = pubKey.GetID(); // Consistency check. if (!isScript && !(pubkey_dest == dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); } // Consistency check. if (isScript) { CTxDestination destination; if (ExtractDestination(script, destination)) { if (!(destination == pubkey_dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); } } } CKeyID vchAddress = pubKey.GetID(); pwalletMain->MarkDirty(); pwalletMain->SetAddressBook(vchAddress, label, "receive"); if (pwalletMain->HaveKey(vchAddress)) { return false; } pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = timestamp; if (!pwalletMain->AddKeyPubKey(key, pubKey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } pwalletMain->UpdateTimeFirstKey(timestamp); success = true; } // Import scriptPubKey only. if (pubKeys.size() == 0 && keys.size() == 0) { if (::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already " "contains the private " "key for this address " "or script"); } pwalletMain->MarkDirty(); if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } if (scriptPubKey.getType() == UniValue::VOBJ) { // add to address book or update label if (IsValidDestination(dest)) { pwalletMain->SetAddressBook(dest, label, "receive"); } } success = true; } } UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(success)); return result; } catch (const UniValue &e) { UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV("error", e); return result; } catch (...) { UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); return result; } } int64_t GetImportTimestamp(const UniValue &data, int64_t now) { if (data.exists("timestamp")) { const UniValue ×tamp = data["timestamp"]; if (timestamp.isNum()) { return timestamp.get_int64(); } else if (timestamp.isStr() && timestamp.get_str() == "now") { return now; } throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp " "value for key. got type %s", uvTypeName(timestamp.type()))); } throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key"); } UniValue importmulti(const Config &config, const JSONRPCRequest &mainRequest) { // clang-format off if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) throw std::runtime_error( "importmulti \"requests\" \"options\"\n\n" "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options).\n\n" "Arguments:\n" "1. requests (array, required) Data to be imported\n" " [ (array of json objects)\n" " {\n" " \"scriptPubKey\": \"