diff --git a/.arclint b/.arclint index d7a858295..3d5f09f0f 100644 --- a/.arclint +++ b/.arclint @@ -1,292 +1,292 @@ { "linters": { "generated": { "type": "generated" }, "clang-format": { "type": "clang-format", - "version": ">=10.0", - "bin": ["clang-format-10", "clang-format"], + "version": ">=11.0", + "bin": ["clang-format-11", "clang-format"], "include": "(^src/.*\\.(h|c|cpp|mm)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "autopep8": { "type": "autopep8", "version": ">=1.3.4", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ], "flags": [ "--aggressive", "--ignore=W503,W504" ] }, "flake8": { "type": "flake8", "version": ">=3.0", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ], "flags": [ "--ignore=E303,E305,E501,E704,W503,W504" ] }, "lint-format-strings": { "type": "lint-format-strings", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)", "(^src/test/fuzz/strprintf.cpp$)" ] }, "check-doc": { "type": "check-doc", "include": "(^src/.*\\.(h|c|cpp)$)" }, "lint-tests": { "type": "lint-tests", "include": "(^src/(seeder/|rpc/|wallet/)?test/.*\\.(cpp)$)" }, "lint-python-format": { "type": "lint-python-format", "include": "(\\.py$)", "exclude": [ "(^test/lint/lint-python-format\\.py$)", "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "phpcs": { "type": "phpcs", "include": "(\\.php$)", "exclude": [ "(^arcanist/__phutil_library_.+\\.php$)" ], "phpcs.standard": "arcanist/phpcs.xml" }, "lint-locale-dependence": { "type": "lint-locale-dependence", "include": "(^src/.*\\.(h|cpp)$)", "exclude": [ "(^src/(crypto/ctaes/|leveldb/|secp256k1/|tinyformat.h|univalue/))", "(^src/bench/nanobench.h$)" ] }, "lint-cheader": { "type": "lint-cheader", "include": "(^src/.*\\.(h|cpp)$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "spelling": { "type": "spelling", "exclude": [ "(^build-aux/m4/)", "(^depends/)", "(^doc/release-notes/)", "(^contrib/gitian-builder/)", "(^src/(qt/locale|secp256k1|univalue|leveldb)/)", "(^test/lint/dictionary/)" ], "spelling.dictionaries": [ "test/lint/dictionary/english.json" ] }, "lint-assert-with-side-effects": { "type": "lint-assert-with-side-effects", "include": "(^src/.*\\.(h|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "lint-include-quotes": { "type": "lint-include-quotes", "include": "(^src/.*\\.(h|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "lint-include-guard": { "type": "lint-include-guard", "include": "(^src/.*\\.h$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)", "(^src/tinyformat.h$)" ] }, "lint-include-source": { "type": "lint-include-source", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "lint-stdint": { "type": "lint-stdint", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)", "(^src/compat/assumptions.h$)" ] }, "lint-source-filename": { "type": "lint-source-filename", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "lint-boost-dependencies": { "type": "lint-boost-dependencies", "include": "(^src/.*\\.(h|cpp)$)" }, "lint-python-encoding": { "type": "lint-python-encoding", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "lint-python-shebang": { "type": "lint-python-shebang", "include": "(\\.py$)", "exclude": [ "(__init__\\.py$)", "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "lint-bash-shebang": { "type": "lint-bash-shebang", "include": "(\\.sh$)", "exclude": [ "(^contrib/gitian-builder/)" ] }, "shellcheck": { "type": "shellcheck", "version": ">=0.7.0", "flags": [ "--external-sources", "--source-path=SCRIPTDIR" ], "include": "(\\.sh$)", "exclude": [ "(^contrib/gitian-builder/)", "(^src/(secp256k1|univalue)/)" ] }, "lint-shell-locale": { "type": "lint-shell-locale", "include": "(\\.sh$)", "exclude": [ "(^contrib/gitian-builder/)", "(^src/(secp256k1|univalue)/)", "(^cmake/utils/log-and-print-on-failure.sh)" ] }, "lint-cpp-void-parameters": { "type": "lint-cpp-void-parameters", "include": "(^src/.*\\.(h|cpp)$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)", "(^src/compat/glibc_compat.cpp$)" ] }, "lint-logs": { "type": "lint-logs", "include": "(^src/.*\\.(h|cpp)$)" }, "lint-qt": { "type": "lint-qt", "include": "(^src/qt/.*\\.(h|cpp)$)", "exclude": [ "(^src/qt/(locale|forms|res)/)" ] }, "lint-doxygen": { "type": "lint-doxygen", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "lint-whitespace": { "type": "lint-whitespace", "include": "(\\.(ac|am|cmake|conf|in|include|json|m4|md|openrc|php|pl|sh|txt|yml)$)", "exclude": [ "(^contrib/gitian-builder/)", "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "yamllint": { "type": "yamllint", "include": "(\\.(yml|yaml)$)", "exclude": "(^src/(secp256k1|univalue|leveldb)/)" }, "lint-check-nonfatal": { "type": "lint-check-nonfatal", "include": [ "(^src/rpc/.*\\.(h|c|cpp)$)", "(^src/wallet/rpc*.*\\.(h|c|cpp)$)" ], "exclude": "(^src/rpc/server.cpp)" }, "lint-markdown": { "type": "lint-markdown", "include": [ "(\\.md$)" ], "exclude": "(^contrib/gitian-builder/)" }, "lint-python-mypy": { "type": "lint-python-mypy", "version": ">=0.780", "include": "(\\.py$)", "exclude": "(^contrib/)", "flags": [ "--ignore-missing-imports" ] }, "lint-python-mutable-default": { "type": "lint-python-mutable-default", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "prettier": { "type": "prettier", "version":">=2.4.1", "include": "(^web/.*\\.(css|html|js|json|jsx|md|scss|ts|tsx)$)", "exclude": "(^web/.*/translations/.*\\.json$)" }, "lint-python-isort": { "type": "lint-python-isort", "version": ">=5.6.4", "include": "(\\.py$)", "exclude": "(^contrib/)" } } } diff --git a/arcanist/linter/ClangFormatLinter.php b/arcanist/linter/ClangFormatLinter.php index 323f6020c..da1a8ef75 100644 --- a/arcanist/linter/ClangFormatLinter.php +++ b/arcanist/linter/ClangFormatLinter.php @@ -1,104 +1,104 @@ <?php /** * Uses the clang format to format C/C++/Obj-C code */ final class ClangFormatLinter extends ArcanistExternalLinter { public function getInfoName() { return 'clang-format'; } public function getInfoURI() { return ''; } public function getInfoDescription() { return pht('Use clang-format for processing specified files.'); } public function getLinterName() { return 'clang-format'; } public function getLinterConfigurationName() { return 'clang-format'; } public function getLinterConfigurationOptions() { $options = array(); return $options + parent::getLinterConfigurationOptions(); } public function getDefaultBinary() { return 'clang-format'; } public function getVersion() { list($stdout) = execx('%C -version', $this->getExecutableCommand()); $matches = array(); $regex = '/^clang-format version (?P<version>\d+\.\d+)\./'; if (preg_match($regex, $stdout, $matches)) { $version = $matches['version']; } else { return false; } /* * FIXME: This is a hack to only allow for clang-format version 10.x. * The .arclint `version` field only allow to filter versions using `=`, * `>`, `<`, `>=` or `<=`. There is no facility to define that the required - * version should be >= 10.0 and < 11.0. + * version should be >= 11.0 and < 12.0. */ - if (substr($version, 0, 2) != '10') { - throw new Exception(pht('Linter %s requires clang-format version 10.x. '. + if (substr($version, 0, 2) != '11') { + throw new Exception(pht('Linter %s requires clang-format version 11.x. '. 'You have version %s.', ClangFormatLinter::class, $version)); } return $version; } public function getInstallInstructions() { return pht('Make sure clang-format is in directory specified by $PATH'); } public function shouldExpectCommandErrors() { return false; } protected function getMandatoryFlags() { return array(); } protected function parseLinterOutput($path, $err, $stdout, $stderr) { $ok = ($err == 0); if (!$ok) { return false; } $root = $this->getProjectRoot(); $path = Filesystem::resolvePath($path, $root); $orig = file_get_contents($path); if ($orig == $stdout) { return array(); } $message = id(new ArcanistLintMessage()) ->setPath($path) ->setLine(1) ->setChar(1) ->setGranularity(ArcanistLinter::GRANULARITY_FILE) ->setCode('CFMT') ->setSeverity(ArcanistLintSeverity::SEVERITY_AUTOFIX) ->setName('Code style violation') ->setDescription("'$path' has code style errors.") ->setOriginalText($orig) ->setReplacementText($stdout); return array($message); } } diff --git a/contrib/teamcity/setup-debian-buster.sh b/contrib/teamcity/setup-debian-buster.sh index e1d933555..467c8e437 100755 --- a/contrib/teamcity/setup-debian-buster.sh +++ b/contrib/teamcity/setup-debian-buster.sh @@ -1,30 +1,30 @@ #!/usr/bin/env bash export LC_ALL=C.UTF-8 set -euxo pipefail TEAMCITY_DIR=$(dirname "$0") # Install all the build dependencies "${TEAMCITY_DIR}"/../utils/install-dependencies.sh # Python library for interacting with teamcity pip3 install teamcity-messages # Install Python dependencies for the build bot # Note: Path should be relative to TEAMCITY_DIR since the base image build # context may be different than the project root. pip3 install -r "${TEAMCITY_DIR}"/../buildbot/requirements.txt # Make sure clang-10 has highest priority -update-alternatives --install /usr/bin/clang clang "$(command -v clang-10)" 100 -update-alternatives --install /usr/bin/clang++ clang++ "$(command -v clang++-10)" 100 -update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer "$(command -v llvm-symbolizer-10)" 100 +update-alternatives --install /usr/bin/clang clang "$(command -v clang-11)" 100 +update-alternatives --install /usr/bin/clang++ clang++ "$(command -v clang++-11)" 100 +update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer "$(command -v llvm-symbolizer-11)" 100 # Set default git config so that any git operations requiring authoring, # rebasing, or cherry-picking of commits just work out of the box. git config --global user.name "abc-bot" git config --global user.email "no-email-abc-bot@bitcoinabc.org" # npm uses ssh to connect to github by default, use https instead git config --global url."https://github.com".insteadOf ssh://git@github.com diff --git a/contrib/utils/install-dependencies.sh b/contrib/utils/install-dependencies.sh index a85c52280..0f7205856 100755 --- a/contrib/utils/install-dependencies.sh +++ b/contrib/utils/install-dependencies.sh @@ -1,139 +1,139 @@ #!/usr/bin/env bash export LC_ALL=C.UTF-8 set -euxo pipefail dpkg --add-architecture i386 PACKAGES=( arcanist automake autotools-dev binutils bsdmainutils build-essential ccache curl default-jdk devscripts doxygen dput flake8 g++-aarch64-linux-gnu g++-arm-linux-gnueabihf gettext-base git golang g++-mingw-w64 gnupg graphviz gperf help2man imagemagick jq lcov less lib32stdc++-8-dev libboost-all-dev libbz2-dev libc6-dev:i386 libcap-dev libdb++-dev libdb-dev libevent-dev libjemalloc-dev libminiupnpc-dev libprotobuf-dev libqrencode-dev libqt5core5a libqt5dbus5 libqt5gui5 librsvg2-bin libsqlite3-dev libssl-dev libtiff-tools libtinfo5 libtool libzmq3-dev lld make ninja-build nsis php-codesniffer pkg-config protobuf-compiler python3 python3-autopep8 python3-pip python3-setuptools python3-yaml python3-zmq qemu-user-static qttools5-dev qttools5-dev-tools software-properties-common tar wget xvfb yamllint wine ) function join_by() { local IFS="$1" shift echo "$*" } apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y $(join_by ' ' "${PACKAGES[@]}") BACKPORTS=( cmake shellcheck ) echo "deb http://deb.debian.org/debian buster-backports main" | tee -a /etc/apt/sources.list apt-get update DEBIAN_FRONTEND=noninteractive apt-get -t buster-backports install -y $(join_by ' ' "${BACKPORTS[@]}") # Install llvm-8 and clang-10 apt-key add "$(dirname "$0")"/llvm.pub add-apt-repository "deb https://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" -add-apt-repository "deb https://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" +add-apt-repository "deb https://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" apt-get update LLVM_PACKAGES=( - clang-10 - clang-format-10 - clang-tidy-10 - clang-tools-10 + clang-11 + clang-format-11 + clang-tidy-11 + clang-tools-11 ) DEBIAN_FRONTEND=noninteractive apt-get install -y $(join_by ' ' "${LLVM_PACKAGES[@]}") # Use the mingw posix variant update-alternatives --set x86_64-w64-mingw32-g++ $(command -v x86_64-w64-mingw32-g++-posix) update-alternatives --set x86_64-w64-mingw32-gcc $(command -v x86_64-w64-mingw32-gcc-posix) # Python library for merging nested structures pip3 install deepmerge # For running Python test suites pip3 install pytest # Up-to-date mypy and isort packages are required python linters pip3 install isort==5.6.4 mypy==0.780 echo "export PATH=\"$(python3 -m site --user-base)/bin:\$PATH\"" >> ~/.bashrc # shellcheck source=/dev/null source ~/.bashrc # Install pandoc. The version from buster is outdated, so get a more recent one # from github. wget https://github.com/jgm/pandoc/releases/download/2.10.1/pandoc-2.10.1-1-amd64.deb echo "4515d6fe2bf8b82765d8dfa1e1b63ccb0ff3332d60389f948672eaa37932e936 pandoc-2.10.1-1-amd64.deb" | sha256sum -c DEBIAN_FRONTEND=noninteractive dpkg -i pandoc-2.10.1-1-amd64.deb # Install npm v7.x and nodejs v15.x curl -sL https://deb.nodesource.com/setup_15.x | bash - apt-get install -y nodejs diff --git a/src/i2p.cpp b/src/i2p.cpp index c37851e11..eadbe54ad 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -1,421 +1,421 @@ // Copyright (c) 2020-2020 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 <i2p.h> #include <chainparams.h> #include <compat.h> #include <compat/endian.h> #include <crypto/sha256.h> #include <fs.h> #include <logging.h> #include <netaddress.h> #include <netbase.h> #include <random.h> #include <tinyformat.h> #include <util/readwritefile.h> #include <util/sock.h> #include <util/spanparsing.h> #include <util/strencodings.h> #include <util/system.h> #include <chrono> #include <memory> #include <stdexcept> #include <string> namespace i2p { /** * Swap Standard Base64 <-> I2P Base64. * Standard Base64 uses `+` and `/` as last two characters of its alphabet. * I2P Base64 uses `-` and `~` respectively. * So it is easy to detect in which one is the input and convert to the other. * @param[in] from Input to convert. * @return converted `from` */ static std::string SwapBase64(const std::string &from) { std::string to; to.resize(from.size()); for (size_t i = 0; i < from.size(); ++i) { switch (from[i]) { case '-': to[i] = '+'; break; case '~': to[i] = '/'; break; case '+': to[i] = '-'; break; case '/': to[i] = '~'; break; default: to[i] = from[i]; break; } } return to; } /** * Decode an I2P-style Base64 string. * @param[in] i2p_b64 I2P-style Base64 string. * @return decoded `i2p_b64` * @throw std::runtime_error if decoding fails */ static Binary DecodeI2PBase64(const std::string &i2p_b64) { const std::string &std_b64 = SwapBase64(i2p_b64); bool invalid; Binary decoded = DecodeBase64(std_b64.c_str(), &invalid); if (invalid) { throw std::runtime_error( strprintf("Cannot decode Base64: \"%s\"", i2p_b64)); } return decoded; } /** * Derive the .b32.i2p address of an I2P destination (binary). * @param[in] dest I2P destination. * @return the address that corresponds to `dest` * @throw std::runtime_error if conversion fails */ static CNetAddr DestBinToAddr(const Binary &dest) { CSHA256 hasher; hasher.Write(dest.data(), dest.size()); uint8_t hash[CSHA256::OUTPUT_SIZE]; hasher.Finalize(hash); CNetAddr addr; const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p"; if (!addr.SetSpecial(addr_str)) { throw std::runtime_error( strprintf("Cannot parse I2P address: \"%s\"", addr_str)); } return addr; } /** * Derive the .b32.i2p address of an I2P destination (I2P-style Base64). * @param[in] dest I2P destination. * @return the address that corresponds to `dest` * @throw std::runtime_error if conversion fails */ static CNetAddr DestB64ToAddr(const std::string &dest) { const Binary &decoded = DecodeI2PBase64(dest); return DestBinToAddr(decoded); } namespace sam { Session::Session(const fs::path &private_key_file, const CService &control_host, CThreadInterrupt *interrupt) : m_private_key_file(private_key_file), m_control_host(control_host), m_interrupt(interrupt), m_control_sock(std::make_unique<Sock>(INVALID_SOCKET)) {} Session::~Session() { LOCK(m_mutex); Disconnect(); } bool Session::Listen(Connection &conn) { try { LOCK(m_mutex); CreateIfNotCreatedAlready(); conn.me = m_my_addr; conn.sock = StreamAccept(); return true; } catch (const std::runtime_error &e) { Log("Error listening: %s", e.what()); CheckControlSock(); } return false; } bool Session::Accept(Connection &conn) { try { while (!*m_interrupt) { Sock::Event occurred; conn.sock->Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred); if ((occurred & Sock::RECV) == 0) { // Timeout, no incoming connections within MAX_WAIT_FOR_IO. continue; } const std::string &peer_dest = conn.sock->RecvUntilTerminator( '\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE); conn.peer = CService(DestB64ToAddr(peer_dest), I2P_SAM31_PORT); return true; } } catch (const std::runtime_error &e) { Log("Error accepting: %s", e.what()); CheckControlSock(); } return false; } bool Session::Connect(const CService &to, Connection &conn, bool &proxy_error) { // Refuse connecting to arbitrary ports. We don't specify any // destination port to the SAM proxy when connecting (SAM 3.1 does not // use ports) and it forces/defaults it to I2P_SAM31_PORT. if (to.GetPort() != I2P_SAM31_PORT) { proxy_error = false; return false; } proxy_error = true; std::string session_id; std::unique_ptr<Sock> sock; conn.peer = to; try { { LOCK(m_mutex); CreateIfNotCreatedAlready(); session_id = m_session_id; conn.me = m_my_addr; sock = Hello(); } const Reply &lookup_reply = SendRequestAndGetReply( *sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP())); const std::string &dest = lookup_reply.Get("VALUE"); const Reply &connect_reply = SendRequestAndGetReply( *sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest), false); const std::string &result = connect_reply.Get("RESULT"); if (result == "OK") { conn.sock = std::move(sock); return true; } if (result == "INVALID_ID") { LOCK(m_mutex); Disconnect(); throw std::runtime_error("Invalid session id"); } if (result == "CANT_REACH_PEER" || result == "TIMEOUT") { proxy_error = false; } throw std::runtime_error(strprintf("\"%s\"", connect_reply.full)); } catch (const std::runtime_error &e) { Log("Error connecting to %s: %s", to.ToString(), e.what()); CheckControlSock(); return false; } } // Private methods std::string Session::Reply::Get(const std::string &key) const { const auto &pos = keys.find(key); if (pos == keys.end() || !pos->second.has_value()) { throw std::runtime_error( strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full)); } return pos->second.value(); } template <typename... Args> - void Session::Log(const std::string &fmt, const Args &... args) const { + void Session::Log(const std::string &fmt, const Args &...args) const { LogPrint(BCLog::I2P, "I2P: %s\n", tfm::format(fmt, args...)); } Session::Reply Session::SendRequestAndGetReply(const Sock &sock, const std::string &request, bool check_result_ok) const { sock.SendComplete(request + "\n", MAX_WAIT_FOR_IO, *m_interrupt); Reply reply; // Don't log the full "SESSION CREATE ..." because it contains our // private key. reply.request = request.substr(0, 14) == "SESSION CREATE" ? "SESSION CREATE ..." : request; // It could take a few minutes for the I2P router to reply as it is // querying the I2P network (when doing name lookup, for example). // Notice: `RecvUntilTerminator()` is checking `m_interrupt` more often, // so we would not be stuck here for long if `m_interrupt` is signaled. static constexpr auto recv_timeout = 3min; reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt, MAX_MSG_SIZE); for (const auto &kv : spanparsing::Split(reply.full, ' ')) { const auto &pos = std::find(kv.begin(), kv.end(), '='); if (pos != kv.end()) { reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()}); } else { reply.keys.emplace(std::string{kv.begin(), kv.end()}, std::nullopt); } } if (check_result_ok && reply.Get("RESULT") != "OK") { throw std::runtime_error(strprintf( "Unexpected reply to \"%s\": \"%s\"", request, reply.full)); } return reply; } std::unique_ptr<Sock> Session::Hello() const { auto sock = CreateSock(m_control_host); if (!sock) { throw std::runtime_error("Cannot create socket"); } if (!ConnectSocketDirectly(m_control_host, *sock, nConnectTimeout, true)) { throw std::runtime_error( strprintf("Cannot connect to %s", m_control_host.ToString())); } SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1"); return sock; } void Session::CheckControlSock() { LOCK(m_mutex); std::string errmsg; if (!m_control_sock->IsConnected(errmsg)) { Log("Control socket error: %s", errmsg); Disconnect(); } } void Session::DestGenerate(const Sock &sock) { // https://geti2p.net/spec/common-structures#key-certificates // "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and // Destinations". Use "7" because i2pd <2.24.0 does not recognize the // textual form. const Reply &reply = SendRequestAndGetReply( sock, "DEST GENERATE SIGNATURE_TYPE=7", false); m_private_key = DecodeI2PBase64(reply.Get("PRIV")); } void Session::GenerateAndSavePrivateKey(const Sock &sock) { DestGenerate(sock); // umask is set to 077 in init.cpp, which is ok (unless -sysperms is // given) if (!WriteBinaryFile( m_private_key_file, std::string(m_private_key.begin(), m_private_key.end()))) { throw std::runtime_error( strprintf("Cannot save I2P private key to %s", fs::quoted(fs::PathToString(m_private_key_file)))); } } Binary Session::MyDestination() const { // From https://geti2p.net/spec/common-structures#destination: // "They are 387 bytes plus the certificate length specified at bytes // 385-386, which may be non-zero" static constexpr size_t DEST_LEN_BASE = 387; static constexpr size_t CERT_LEN_POS = 385; uint16_t cert_len; memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len)); cert_len = be16toh(cert_len); const size_t dest_len = DEST_LEN_BASE + cert_len; return Binary{m_private_key.begin(), m_private_key.begin() + dest_len}; } void Session::CreateIfNotCreatedAlready() { std::string errmsg; if (m_control_sock->IsConnected(errmsg)) { return; } Log("Creating SAM session with %s", m_control_host.ToString()); auto sock = Hello(); const auto &[read_ok, data] = ReadBinaryFile(m_private_key_file); if (read_ok) { m_private_key.assign(data.begin(), data.end()); } else { GenerateAndSavePrivateKey(*sock); } const std::string &session_id = GetRandHash().GetHex().substr( 0, 10); // full is an overkill, too verbose in the logs const std::string &private_key_b64 = SwapBase64(EncodeBase64(m_private_key)); SendRequestAndGetReply( *sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s", session_id, private_key_b64)); m_my_addr = CService(DestBinToAddr(MyDestination()), I2P_SAM31_PORT); m_session_id = session_id; m_control_sock = std::move(sock); LogPrintf("I2P: SAM session created: session id=%s, my address=%s\n", m_session_id, m_my_addr.ToString()); } std::unique_ptr<Sock> Session::StreamAccept() { auto sock = Hello(); const Reply &reply = SendRequestAndGetReply( *sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false); const std::string &result = reply.Get("RESULT"); if (result == "OK") { return sock; } if (result == "INVALID_ID") { // If our session id is invalid, then force session re-creation on // next usage. Disconnect(); } throw std::runtime_error(strprintf("\"%s\"", reply.full)); } void Session::Disconnect() { if (m_control_sock->Get() != INVALID_SOCKET) { if (m_session_id.empty()) { Log("Destroying incomplete session"); } else { Log("Destroying session %s", m_session_id); } } m_control_sock->Reset(); m_session_id.clear(); } } // namespace sam } // namespace i2p diff --git a/src/i2p.h b/src/i2p.h index 5c39f946f..3a05bf8cf 100644 --- a/src/i2p.h +++ b/src/i2p.h @@ -1,283 +1,283 @@ // Copyright (c) 2020-2020 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_I2P_H #define BITCOIN_I2P_H #include <compat.h> #include <fs.h> #include <netaddress.h> #include <sync.h> #include <threadinterrupt.h> #include <util/sock.h> #include <memory> #include <optional> #include <string> #include <unordered_map> #include <vector> namespace i2p { /** * Binary data. */ using Binary = std::vector<uint8_t>; /** * An established connection with another peer. */ struct Connection { /** Connected socket. */ std::unique_ptr<Sock> sock; /** Our I2P address. */ CService me; /** The peer's I2P address. */ CService peer; }; namespace sam { /** * The maximum size of an incoming message from the I2P SAM proxy (in * bytes). Used to avoid a runaway proxy from sending us an "unlimited" * amount of data without a terminator. The longest known message is ~1400 * bytes, so this is high enough not to be triggered during normal * operation, yet low enough to avoid a malicious proxy from filling our * memory. */ static constexpr size_t MAX_MSG_SIZE{65536}; /** * I2P SAM session. */ class Session { public: /** * Construct a session. This will not initiate any IO, the session will * be lazily created later when first used. * @param[in] private_key_file Path to a private key file. If the file * does not exist then the private key will be generated and saved into * the file. * @param[in] control_host Location of the SAM proxy. * @param[in,out] interrupt If this is signaled then all operations are * canceled as soon as possible and executing methods throw an * exception. Notice: only a pointer to the `CThreadInterrupt` object is * saved, so it must not be destroyed earlier than this `Session` * object. */ Session(const fs::path &private_key_file, const CService &control_host, CThreadInterrupt *interrupt); /** * Destroy the session, closing the internally used sockets. The sockets * that have been returned by `Accept()` or `Connect()` will not be * closed, but they will be closed by the SAM proxy because the session * is destroyed. So they will return an error next time we try to read * or write to them. */ ~Session(); /** * Start listening for an incoming connection. * @param[out] conn Upon successful completion the `sock` and `me` * members will be set to the listening socket and address. * @return true on success */ bool Listen(Connection &conn); /** * Wait for and accept a new incoming connection. * @param[in,out] conn The `sock` member is used for waiting and * accepting. Upon successful completion the `peer` member will be set * to the address of the incoming peer. * @return true on success */ bool Accept(Connection &conn); /** * Connect to an I2P peer. * @param[in] to Peer to connect to. * @param[out] conn Established connection. Only set if `true` is * returned. * @param[out] proxy_error If an error occurs due to proxy or general * network failure, then this is set to `true`. If an error occurs due * to unreachable peer (likely peer is down), then it is set to `false`. * Only set if `false` is returned. * @return true on success */ bool Connect(const CService &to, Connection &conn, bool &proxy_error); private: /** * A reply from the SAM proxy. */ struct Reply { /** * Full, unparsed reply. */ std::string full; /** * Request, used for detailed error reporting. */ std::string request; /** * A map of keywords from the parsed reply. * For example, if the reply is "A=X B C=YZ", then the map will be * keys["A"] == "X" * keys["B"] == (empty std::optional) * keys["C"] == "YZ" */ std::unordered_map<std::string, std::optional<std::string>> keys; /** * Get the value of a given key. * For example if the reply is "A=X B" then: * Value("A") -> "X" * Value("B") -> throws * Value("C") -> throws * @param[in] key Key whose value to retrieve * @returns the key's value * @throws std::runtime_error if the key is not present or if it has * no value */ std::string Get(const std::string &key) const; }; /** * Log a message in the `BCLog::I2P` category. * @param[in] fmt printf(3)-like format string. * @param[in] args printf(3)-like arguments that correspond to `fmt`. */ template <typename... Args> - void Log(const std::string &fmt, const Args &... args) const; + void Log(const std::string &fmt, const Args &...args) const; /** * Send request and get a reply from the SAM proxy. * @param[in] sock A socket that is connected to the SAM proxy. * @param[in] request Raw request to send, a newline terminator is * appended to it. * @param[in] check_result_ok If true then after receiving the reply a * check is made whether it contains "RESULT=OK" and an exception is * thrown if it does not. * @throws std::runtime_error if an error occurs */ Reply SendRequestAndGetReply(const Sock &sock, const std::string &request, bool check_result_ok = true) const; /** * Open a new connection to the SAM proxy. * @return a connected socket * @throws std::runtime_error if an error occurs */ std::unique_ptr<Sock> Hello() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex); /** * Check the control socket for errors and possibly disconnect. */ void CheckControlSock(); /** * Generate a new destination with the SAM proxy and set `m_private_key` * to it. * @param[in] sock Socket to use for talking to the SAM proxy. * @throws std::runtime_error if an error occurs */ void DestGenerate(const Sock &sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex); /** * Generate a new destination with the SAM proxy, set `m_private_key` to * it and save it on disk to `m_private_key_file`. * @param[in] sock Socket to use for talking to the SAM proxy. * @throws std::runtime_error if an error occurs */ void GenerateAndSavePrivateKey(const Sock &sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex); /** * Derive own destination from `m_private_key`. * @see https://geti2p.net/spec/common-structures#destination * @return an I2P destination */ Binary MyDestination() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex); /** * Create the session if not already created. Reads the private key file * and connects to the SAM proxy. * @throws std::runtime_error if an error occurs */ void CreateIfNotCreatedAlready() EXCLUSIVE_LOCKS_REQUIRED(m_mutex); /** * Open a new connection to the SAM proxy and issue "STREAM ACCEPT" * request using the existing session id. * @return the idle socket that is waiting for a peer to connect to us * @throws std::runtime_error if an error occurs */ std::unique_ptr<Sock> StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(m_mutex); /** * Destroy the session, closing the internally used sockets. */ void Disconnect() EXCLUSIVE_LOCKS_REQUIRED(m_mutex); /** * The name of the file where this peer's private key is stored (in * binary). */ const fs::path m_private_key_file; /** * The host and port of the SAM control service. */ const CService m_control_host; /** * Cease network activity when this is signaled. */ CThreadInterrupt *const m_interrupt; /** * Mutex protecting the members that can be concurrently accessed. */ mutable Mutex m_mutex; /** * The private key of this peer. * @see The reply to the "DEST GENERATE" command in * https://geti2p.net/en/docs/api/samv3 */ Binary m_private_key GUARDED_BY(m_mutex); /** * SAM control socket. * Used to connect to the I2P SAM service and create a session * ("SESSION CREATE"). With the established session id we later open * other connections to the SAM service to accept incoming I2P * connections and make outgoing ones. * See https://geti2p.net/en/docs/api/samv3 */ std::unique_ptr<Sock> m_control_sock GUARDED_BY(m_mutex); /** * Our .b32.i2p address. * Derived from `m_private_key`. */ CService m_my_addr GUARDED_BY(m_mutex); /** * SAM session id. */ std::string m_session_id GUARDED_BY(m_mutex); }; } // namespace sam } // namespace i2p #endif // BITCOIN_I2P_H diff --git a/src/index/base.cpp b/src/index/base.cpp index 6c5d90a77..b5628428f 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -1,337 +1,337 @@ // Copyright (c) 2017-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <blockdb.h> #include <chain.h> #include <chainparams.h> #include <config.h> #include <index/base.h> #include <node/ui_interface.h> #include <shutdown.h> #include <tinyformat.h> #include <util/system.h> #include <util/translation.h> #include <validation.h> #include <warnings.h> #include <functional> constexpr char DB_BEST_BLOCK = 'B'; constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds constexpr int64_t SYNC_LOCATOR_WRITE_INTERVAL = 30; // seconds template <typename... Args> -static void FatalError(const char *fmt, const Args &... args) { +static void FatalError(const char *fmt, const Args &...args) { std::string strMessage = tfm::format(fmt, args...); SetMiscWarning(Untranslated(strMessage)); LogPrintf("*** %s\n", strMessage); AbortError(_("A fatal internal error occurred, see debug.log for details")); StartShutdown(); } BaseIndex::DB::DB(const fs::path &path, size_t n_cache_size, bool f_memory, bool f_wipe, bool f_obfuscate) : CDBWrapper(path, n_cache_size, f_memory, f_wipe, f_obfuscate) {} bool BaseIndex::DB::ReadBestBlock(CBlockLocator &locator) const { bool success = Read(DB_BEST_BLOCK, locator); if (!success) { locator.SetNull(); } return success; } void BaseIndex::DB::WriteBestBlock(CDBBatch &batch, const CBlockLocator &locator) { batch.Write(DB_BEST_BLOCK, locator); } BaseIndex::~BaseIndex() { Interrupt(); Stop(); } bool BaseIndex::Init() { CBlockLocator locator; if (!GetDB().ReadBestBlock(locator)) { locator.SetNull(); } LOCK(cs_main); if (locator.IsNull()) { m_best_block_index = nullptr; } else { m_best_block_index = FindForkInGlobalIndex(::ChainActive(), locator); } m_synced = m_best_block_index.load() == ::ChainActive().Tip(); return true; } static const CBlockIndex *NextSyncBlock(const CBlockIndex *pindex_prev) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); if (!pindex_prev) { return ::ChainActive().Genesis(); } const CBlockIndex *pindex = ::ChainActive().Next(pindex_prev); if (pindex) { return pindex; } return ::ChainActive().Next(::ChainActive().FindFork(pindex_prev)); } void BaseIndex::ThreadSync() { const CBlockIndex *pindex = m_best_block_index.load(); if (!m_synced) { auto &consensus_params = GetConfig().GetChainParams().GetConsensus(); int64_t last_log_time = 0; int64_t last_locator_write_time = 0; while (true) { if (m_interrupt) { m_best_block_index = pindex; // No need to handle errors in Commit. If it fails, the error // will be already be logged. The best way to recover is to // continue, as index cannot be corrupted by a missed commit to // disk for an advanced index state. Commit(); return; } { LOCK(cs_main); const CBlockIndex *pindex_next = NextSyncBlock(pindex); if (!pindex_next) { m_best_block_index = pindex; m_synced = true; // No need to handle errors in Commit. See rationale above. Commit(); break; } if (pindex_next->pprev != pindex && !Rewind(pindex, pindex_next->pprev)) { FatalError( "%s: Failed to rewind index %s to a previous chain tip", __func__, GetName()); return; } pindex = pindex_next; } int64_t current_time = GetTime(); if (last_log_time + SYNC_LOG_INTERVAL < current_time) { LogPrintf("Syncing %s with block chain from height %d\n", GetName(), pindex->nHeight); last_log_time = current_time; } if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) { m_best_block_index = pindex; last_locator_write_time = current_time; // No need to handle errors in Commit. See rationale above. Commit(); } CBlock block; if (!ReadBlockFromDisk(block, pindex, consensus_params)) { FatalError("%s: Failed to read block %s from disk", __func__, pindex->GetBlockHash().ToString()); return; } if (!WriteBlock(block, pindex)) { FatalError("%s: Failed to write block %s to index database", __func__, pindex->GetBlockHash().ToString()); return; } } } if (pindex) { LogPrintf("%s is enabled at height %d\n", GetName(), pindex->nHeight); } else { LogPrintf("%s is enabled\n", GetName()); } } bool BaseIndex::Commit() { CDBBatch batch(GetDB()); if (!CommitInternal(batch) || !GetDB().WriteBatch(batch)) { return error("%s: Failed to commit latest %s state", __func__, GetName()); } return true; } bool BaseIndex::CommitInternal(CDBBatch &batch) { LOCK(cs_main); GetDB().WriteBestBlock(batch, ::ChainActive().GetLocator(m_best_block_index)); return true; } bool BaseIndex::Rewind(const CBlockIndex *current_tip, const CBlockIndex *new_tip) { assert(current_tip == m_best_block_index); assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip); // In the case of a reorg, ensure persisted block locator is not stale. m_best_block_index = new_tip; if (!Commit()) { // If commit fails, revert the best block index to avoid corruption. m_best_block_index = current_tip; return false; } return true; } void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex *pindex) { if (!m_synced) { return; } const CBlockIndex *best_block_index = m_best_block_index.load(); if (!best_block_index) { if (pindex->nHeight != 0) { FatalError("%s: First block connected is not the genesis block " "(height=%d)", __func__, pindex->nHeight); return; } } else { // Ensure block connects to an ancestor of the current best block. This // should be the case most of the time, but may not be immediately after // the the sync thread catches up and sets m_synced. Consider the case // where there is a reorg and the blocks on the stale branch are in the // ValidationInterface queue backlog even after the sync thread has // caught up to the new chain tip. In this unlikely event, log a warning // and let the queue clear. if (best_block_index->GetAncestor(pindex->nHeight - 1) != pindex->pprev) { LogPrintf("%s: WARNING: Block %s does not connect to an ancestor " "of known best chain (tip=%s); not updating index\n", __func__, pindex->GetBlockHash().ToString(), best_block_index->GetBlockHash().ToString()); return; } if (best_block_index != pindex->pprev && !Rewind(best_block_index, pindex->pprev)) { FatalError("%s: Failed to rewind index %s to a previous chain tip", __func__, GetName()); return; } } if (WriteBlock(*block, pindex)) { m_best_block_index = pindex; } else { FatalError("%s: Failed to write block %s to index", __func__, pindex->GetBlockHash().ToString()); return; } } void BaseIndex::ChainStateFlushed(const CBlockLocator &locator) { if (!m_synced) { return; } const BlockHash &locator_tip_hash = locator.vHave.front(); const CBlockIndex *locator_tip_index; { LOCK(cs_main); locator_tip_index = LookupBlockIndex(locator_tip_hash); } if (!locator_tip_index) { FatalError("%s: First block (hash=%s) in locator was not found", __func__, locator_tip_hash.ToString()); return; } // This checks that ChainStateFlushed callbacks are received after // BlockConnected. The check may fail immediately after the the sync thread // catches up and sets m_synced. Consider the case where there is a reorg // and the blocks on the stale branch are in the ValidationInterface queue // backlog even after the sync thread has caught up to the new chain tip. In // this unlikely event, log a warning and let the queue clear. const CBlockIndex *best_block_index = m_best_block_index.load(); if (best_block_index->GetAncestor(locator_tip_index->nHeight) != locator_tip_index) { LogPrintf("%s: WARNING: Locator contains block (hash=%s) not on known " "best chain (tip=%s); not writing index locator\n", __func__, locator_tip_hash.ToString(), best_block_index->GetBlockHash().ToString()); return; } // No need to handle errors in Commit. If it fails, the error will be // already be logged. The best way to recover is to continue, as index // cannot be corrupted by a missed commit to disk for an advanced index // state. Commit(); } bool BaseIndex::BlockUntilSyncedToCurrentChain() const { AssertLockNotHeld(cs_main); if (!m_synced) { return false; } { // Skip the queue-draining stuff if we know we're caught up with // ::ChainActive().Tip(). LOCK(cs_main); const CBlockIndex *chain_tip = ::ChainActive().Tip(); const CBlockIndex *best_block_index = m_best_block_index.load(); if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) { return true; } } LogPrintf("%s: %s is catching up on block notifications\n", __func__, GetName()); SyncWithValidationInterfaceQueue(); return true; } void BaseIndex::Interrupt() { m_interrupt(); } void BaseIndex::Start() { // Need to register this ValidationInterface before running Init(), so that // callbacks are not missed if Init sets m_synced to true. RegisterValidationInterface(this); if (!Init()) { FatalError("%s: %s failed to initialize", __func__, GetName()); return; } m_thread_sync = std::thread(&TraceThread<std::function<void()>>, GetName(), std::bind(&BaseIndex::ThreadSync, this)); } void BaseIndex::Stop() { UnregisterValidationInterface(this); if (m_thread_sync.joinable()) { m_thread_sync.join(); } } IndexSummary BaseIndex::GetSummary() const { IndexSummary summary{}; summary.name = GetName(); summary.synced = m_synced; summary.best_block_height = m_best_block_index.load()->nHeight; return summary; } diff --git a/src/logging.h b/src/logging.h index 81423c6bc..8dc437bd5 100644 --- a/src/logging.h +++ b/src/logging.h @@ -1,218 +1,218 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_LOGGING_H #define BITCOIN_LOGGING_H #include <fs.h> #include <threadsafety.h> #include <tinyformat.h> #include <util/string.h> #include <atomic> #include <cstdint> #include <list> #include <mutex> #include <string> static const bool DEFAULT_LOGTIMEMICROS = false; static const bool DEFAULT_LOGIPS = false; static const bool DEFAULT_LOGTIMESTAMPS = true; static const bool DEFAULT_LOGTHREADNAMES = false; static const bool DEFAULT_LOGSOURCELOCATIONS = false; extern bool fLogIPs; extern const char *const DEFAULT_DEBUGLOGFILE; struct LogCategory { std::string category; bool active; }; namespace BCLog { enum LogFlags : uint32_t { NONE = 0, NET = (1 << 0), TOR = (1 << 1), MEMPOOL = (1 << 2), HTTP = (1 << 3), BENCH = (1 << 4), ZMQ = (1 << 5), WALLETDB = (1 << 6), RPC = (1 << 7), ESTIMATEFEE = (1 << 8), ADDRMAN = (1 << 9), SELECTCOINS = (1 << 10), REINDEX = (1 << 11), CMPCTBLOCK = (1 << 12), RAND = (1 << 13), PRUNE = (1 << 14), PROXY = (1 << 15), MEMPOOLREJ = (1 << 16), LIBEVENT = (1 << 17), COINDB = (1 << 18), QT = (1 << 19), LEVELDB = (1 << 20), VALIDATION = (1 << 21), AVALANCHE = (1 << 22), I2P = (1 << 23), ALL = ~uint32_t(0), }; class Logger { private: // Can not use Mutex from sync.h because in debug mode it would cause a // deadlock when a potential deadlock was detected mutable StdMutex m_cs; FILE *m_fileout GUARDED_BY(m_cs) = nullptr; std::list<std::string> m_msgs_before_open GUARDED_BY(m_cs); //! Buffer messages before logging can be started. bool m_buffering GUARDED_BY(m_cs) = true; /** * m_started_new_line is a state variable that will suppress printing of the * timestamp when multiple calls are made that don't end in a newline. */ std::atomic_bool m_started_new_line{true}; /** * Log categories bitfield. */ std::atomic<uint32_t> m_categories{0}; std::string LogTimestampStr(const std::string &str); /** Slots that connect to the print signal */ std::list<std::function<void(const std::string &)>> m_print_callbacks GUARDED_BY(m_cs){}; public: bool m_print_to_console = false; bool m_print_to_file = false; bool m_log_timestamps = DEFAULT_LOGTIMESTAMPS; bool m_log_time_micros = DEFAULT_LOGTIMEMICROS; bool m_log_threadnames = DEFAULT_LOGTHREADNAMES; bool m_log_sourcelocations = DEFAULT_LOGSOURCELOCATIONS; fs::path m_file_path; std::atomic<bool> m_reopen_file{false}; ~Logger(); /** Send a string to the log output */ void LogPrintStr(const std::string &str, const std::string &logging_function, const std::string &source_file, const int source_line); /** Returns whether logs will be written to any output */ bool Enabled() const { StdLockGuard scoped_lock(m_cs); return m_buffering || m_print_to_console || m_print_to_file || !m_print_callbacks.empty(); } /** Connect a slot to the print signal and return the connection */ std::list<std::function<void(const std::string &)>>::iterator PushBackCallback(std::function<void(const std::string &)> fun) { StdLockGuard scoped_lock(m_cs); m_print_callbacks.push_back(std::move(fun)); return --m_print_callbacks.end(); } /** Delete a connection */ void DeleteCallback( std::list<std::function<void(const std::string &)>>::iterator it) { StdLockGuard scoped_lock(m_cs); m_print_callbacks.erase(it); } /** Start logging (and flush all buffered messages) */ bool StartLogging(); /** Only for testing */ void DisconnectTestLogger(); void ShrinkDebugFile(); uint32_t GetCategoryMask() const { return m_categories.load(); } void EnableCategory(LogFlags category); bool EnableCategory(const std::string &str); void DisableCategory(LogFlags category); bool DisableCategory(const std::string &str); /** Return true if log accepts specified category */ bool WillLogCategory(LogFlags category) const; /** Returns a vector of the log categories */ std::vector<LogCategory> LogCategoriesList() const; /** Returns a string with the log categories */ std::string LogCategoriesString() const { return Join(LogCategoriesList(), ", ", [&](const LogCategory &i) { return i.category; }); }; /** Default for whether ShrinkDebugFile should be run */ bool DefaultShrinkDebugFile() const; }; } // namespace BCLog BCLog::Logger &LogInstance(); /** Return true if log accepts specified category */ static inline bool LogAcceptCategory(BCLog::LogFlags category) { return LogInstance().WillLogCategory(category); } /** Return true if str parses as a log category and set the flag */ bool GetLogCategory(BCLog::LogFlags &flag, const std::string &str); // Be conservative when using LogPrintf/error or other things which // unconditionally log to debug.log! It should not be the case that an inbound // peer can fill up a user's disk with debug.log entries. template <typename... Args> static inline void LogPrintf_(const std::string &logging_function, const std::string &source_file, - const int source_line, const char *fmt, const Args &... args) { + const int source_line, const char *fmt, const Args &...args) { if (LogInstance().Enabled()) { std::string log_msg; try { log_msg = tfm::format(fmt, args...); } catch (tinyformat::format_error &fmterr) { /** * Original format string will have newline so don't add one here */ log_msg = "Error \"" + std::string(fmterr.what()) + "\" while formatting log message: " + fmt; } LogInstance().LogPrintStr(log_msg, logging_function, source_file, source_line); } } #define LogPrintf(...) LogPrintf_(__func__, __FILE__, __LINE__, __VA_ARGS__) // Use a macro instead of a function for conditional logging to prevent // evaluating arguments when logging for the category is not enabled. #define LogPrint(category, ...) \ do { \ if (LogAcceptCategory((category))) { \ LogPrintf(__VA_ARGS__); \ } \ } while (0) /** * These are aliases used to explicitly state that the message should not end * with a newline character. It allows for detecting the missing newlines that * could make the logs hard to read. */ #define LogPrintfToBeContinued LogPrintf #define LogPrintToBeContinued LogPrint #endif // BITCOIN_LOGGING_H diff --git a/src/netbase.cpp b/src/netbase.cpp index e02502ad1..ecc5715f0 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -1,857 +1,857 @@ // 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. #include <netbase.h> #include <compat.h> #include <sync.h> #include <tinyformat.h> #include <util/sock.h> #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> #include <util/time.h> #include <atomic> #include <chrono> #include <cstdint> #include <functional> #include <memory> #ifndef WIN32 #include <fcntl.h> #else #include <codecvt> #endif #ifdef USE_POLL #include <poll.h> #endif // Settings static Mutex g_proxyinfo_mutex; static proxyType proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex); static proxyType nameProxy GUARDED_BY(g_proxyinfo_mutex); int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; bool fNameLookup = DEFAULT_NAME_LOOKUP; // Need ample time for negotiation for very slow proxies such as Tor // (milliseconds) static const int SOCKS5_RECV_TIMEOUT = 20 * 1000; static std::atomic<bool> interruptSocks5Recv(false); std::vector<CNetAddr> WrappedGetAddrInfo(const std::string &name, bool allow_lookup) { addrinfo ai_hint{}; // We want a TCP port, which is a streaming socket type ai_hint.ai_socktype = SOCK_STREAM; ai_hint.ai_protocol = IPPROTO_TCP; // We don't care which address family (IPv4 or IPv6) is returned ai_hint.ai_family = AF_UNSPEC; // If we allow lookups of hostnames, use the AI_ADDRCONFIG flag to only // return addresses whose family we have an address configured for. // // If we don't allow lookups, then use the AI_NUMERICHOST flag for // getaddrinfo to only decode numerical network addresses and suppress // hostname lookups. ai_hint.ai_flags = allow_lookup ? AI_ADDRCONFIG : AI_NUMERICHOST; addrinfo *ai_res{nullptr}; const int n_err{getaddrinfo(name.c_str(), nullptr, &ai_hint, &ai_res)}; if (n_err != 0) { return {}; } // Traverse the linked list starting with ai_trav. addrinfo *ai_trav{ai_res}; std::vector<CNetAddr> resolved_addresses; while (ai_trav != nullptr) { if (ai_trav->ai_family == AF_INET) { assert(ai_trav->ai_addrlen >= sizeof(sockaddr_in)); resolved_addresses.emplace_back( reinterpret_cast<sockaddr_in *>(ai_trav->ai_addr)->sin_addr); } if (ai_trav->ai_family == AF_INET6) { assert(ai_trav->ai_addrlen >= sizeof(sockaddr_in6)); const sockaddr_in6 *s6{ reinterpret_cast<sockaddr_in6 *>(ai_trav->ai_addr)}; resolved_addresses.emplace_back(s6->sin6_addr, s6->sin6_scope_id); } ai_trav = ai_trav->ai_next; } freeaddrinfo(ai_res); return resolved_addresses; } DNSLookupFn g_dns_lookup{WrappedGetAddrInfo}; enum Network ParseNetwork(const std::string &net_in) { std::string net = ToLower(net_in); if (net == "ipv4") { return NET_IPV4; } if (net == "ipv6") { return NET_IPV6; } if (net == "onion") { return NET_ONION; } if (net == "tor") { LogPrintf("Warning: net name 'tor' is deprecated and will be removed " "in the future. You should use 'onion' instead.\n"); return NET_ONION; } if (net == "i2p") { return NET_I2P; } return NET_UNROUTABLE; } std::string GetNetworkName(enum Network net) { switch (net) { case NET_UNROUTABLE: return "not_publicly_routable"; case NET_IPV4: return "ipv4"; case NET_IPV6: return "ipv6"; case NET_ONION: return "onion"; case NET_I2P: return "i2p"; case NET_CJDNS: return "cjdns"; case NET_INTERNAL: return "internal"; case NET_MAX: assert(false); } // no default case, so the compiler can warn about missing cases assert(false); } std::vector<std::string> GetNetworkNames(bool append_unroutable) { std::vector<std::string> names; for (int n = 0; n < NET_MAX; ++n) { const enum Network network { static_cast<Network>(n) }; if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) { continue; } names.emplace_back(GetNetworkName(network)); } if (append_unroutable) { names.emplace_back(GetNetworkName(NET_UNROUTABLE)); } return names; } static bool LookupIntern(const std::string &name, std::vector<CNetAddr> &vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function) { vIP.clear(); if (!ValidAsCString(name)) { return false; } { CNetAddr addr; // From our perspective, onion addresses are not hostnames but rather // direct encodings of CNetAddr much like IPv4 dotted-decimal notation // or IPv6 colon-separated hextet notation. Since we can't use // getaddrinfo to decode them and it wouldn't make sense to resolve // them, we return a network address representing it instead. See // CNetAddr::SetSpecial(const std::string&) for more details. if (addr.SetSpecial(name)) { vIP.push_back(addr); return true; } } for (const CNetAddr &resolved : dns_lookup_function(name, fAllowLookup)) { if (nMaxSolutions > 0 && vIP.size() >= nMaxSolutions) { break; } // Never allow resolving to an internal address. Consider any such // result invalid. if (!resolved.IsInternal()) { vIP.push_back(resolved); } } return (vIP.size() > 0); } bool LookupHost(const std::string &name, std::vector<CNetAddr> &vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { return false; } std::string strHost = name; if (strHost.empty()) { return false; } if (strHost.front() == '[' && strHost.back() == ']') { strHost = strHost.substr(1, strHost.size() - 2); } return LookupIntern(strHost, vIP, nMaxSolutions, fAllowLookup, dns_lookup_function); } bool LookupHost(const std::string &name, CNetAddr &addr, bool fAllowLookup, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { return false; } std::vector<CNetAddr> vIP; LookupHost(name, vIP, 1, fAllowLookup, dns_lookup_function); if (vIP.empty()) { return false; } addr = vIP.front(); return true; } bool Lookup(const std::string &name, std::vector<CService> &vAddr, uint16_t portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function) { if (name.empty() || !ValidAsCString(name)) { return false; } uint16_t port{portDefault}; std::string hostname; SplitHostPort(name, port, hostname); std::vector<CNetAddr> vIP; bool fRet = LookupIntern(hostname, vIP, nMaxSolutions, fAllowLookup, dns_lookup_function); if (!fRet) { return false; } vAddr.resize(vIP.size()); for (unsigned int i = 0; i < vIP.size(); i++) { vAddr[i] = CService(vIP[i], port); } return true; } bool Lookup(const std::string &name, CService &addr, uint16_t portDefault, bool fAllowLookup, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { return false; } std::vector<CService> vService; bool fRet = Lookup(name, vService, portDefault, fAllowLookup, 1, dns_lookup_function); if (!fRet) { return false; } addr = vService[0]; return true; } CService LookupNumeric(const std::string &name, uint16_t portDefault, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { return {}; } CService addr; // "1.2:345" will fail to resolve the ip, but will still set the port. // If the ip fails to resolve, re-init the result. if (!Lookup(name, addr, portDefault, false, dns_lookup_function)) { addr = CService(); } return addr; } /** SOCKS version */ enum SOCKSVersion : uint8_t { SOCKS4 = 0x04, SOCKS5 = 0x05 }; /** Values defined for METHOD in RFC1928 */ enum SOCKS5Method : uint8_t { NOAUTH = 0x00, //!< No authentication required GSSAPI = 0x01, //!< GSSAPI USER_PASS = 0x02, //!< Username/password NO_ACCEPTABLE = 0xff, //!< No acceptable methods }; /** Values defined for CMD in RFC1928 */ enum SOCKS5Command : uint8_t { CONNECT = 0x01, BIND = 0x02, UDP_ASSOCIATE = 0x03 }; /** Values defined for REP in RFC1928 */ enum SOCKS5Reply : uint8_t { SUCCEEDED = 0x00, //!< Succeeded GENFAILURE = 0x01, //!< General failure NOTALLOWED = 0x02, //!< Connection not allowed by ruleset NETUNREACHABLE = 0x03, //!< Network unreachable HOSTUNREACHABLE = 0x04, //!< Network unreachable CONNREFUSED = 0x05, //!< Connection refused TTLEXPIRED = 0x06, //!< TTL expired CMDUNSUPPORTED = 0x07, //!< Command not supported ATYPEUNSUPPORTED = 0x08, //!< Address type not supported }; /** Values defined for ATYPE in RFC1928 */ enum SOCKS5Atyp : uint8_t { IPV4 = 0x01, DOMAINNAME = 0x03, IPV6 = 0x04, }; /** Status codes that can be returned by InterruptibleRecv */ enum class IntrRecvError { OK, Timeout, Disconnected, NetworkError, Interrupted }; /** * Try to read a specified number of bytes from a socket. Please read the "see * also" section for more detail. * * @param data The buffer where the read bytes should be stored. * @param len The number of bytes to read into the specified buffer. * @param timeout The total timeout in milliseconds for this read. * @param sock The socket (has to be in non-blocking mode) from which to read * bytes. * * @returns An IntrRecvError indicating the resulting status of this read. * IntrRecvError::OK only if all of the specified number of bytes were * read. * * @see This function can be interrupted by calling InterruptSocks5(bool). * Sockets can be made non-blocking with SetSocketNonBlocking(const * SOCKET&, bool). */ static IntrRecvError InterruptibleRecv(uint8_t *data, size_t len, int timeout, const Sock &sock) { int64_t curTime = GetTimeMillis(); int64_t endTime = curTime + timeout; while (len > 0 && curTime < endTime) { // Optimistically try the recv first ssize_t ret = sock.Recv(data, len, 0); if (ret > 0) { len -= ret; data += ret; } else if (ret == 0) { // Unexpected disconnection return IntrRecvError::Disconnected; } else { // Other error or blocking int nErr = WSAGetLastError(); if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) { // Only wait at most MAX_WAIT_FOR_IO at a time, unless // we're approaching the end of the specified total timeout const auto remaining = std::chrono::milliseconds{endTime - curTime}; const auto timeout_ = std::min( remaining, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); if (!sock.Wait(timeout_, Sock::RECV)) { return IntrRecvError::NetworkError; } } else { return IntrRecvError::NetworkError; } } if (interruptSocks5Recv) { return IntrRecvError::Interrupted; } curTime = GetTimeMillis(); } return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout; } /** Credentials for proxy authentication */ struct ProxyCredentials { std::string username; std::string password; }; /** Convert SOCKS5 reply to an error message */ static std::string Socks5ErrorString(uint8_t err) { switch (err) { case SOCKS5Reply::GENFAILURE: return "general failure"; case SOCKS5Reply::NOTALLOWED: return "connection not allowed"; case SOCKS5Reply::NETUNREACHABLE: return "network unreachable"; case SOCKS5Reply::HOSTUNREACHABLE: return "host unreachable"; case SOCKS5Reply::CONNREFUSED: return "connection refused"; case SOCKS5Reply::TTLEXPIRED: return "TTL expired"; case SOCKS5Reply::CMDUNSUPPORTED: return "protocol error"; case SOCKS5Reply::ATYPEUNSUPPORTED: return "address type not supported"; default: return "unknown"; } } /** * Connect to a specified destination service through an already connected * SOCKS5 proxy. * * @param strDest The destination fully-qualified domain name. * @param port The destination port. * @param auth The credentials with which to authenticate with the specified * SOCKS5 proxy. * @param sock The SOCKS5 proxy socket. * * @returns Whether or not the operation succeeded. * * @note The specified SOCKS5 proxy socket must already be connected to the * SOCKS5 proxy. * * @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol * Version 5</a> */ static bool Socks5(const std::string &strDest, uint16_t port, const ProxyCredentials *auth, const Sock &sock) { IntrRecvError recvr; LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest); if (strDest.size() > 255) { return error("Hostname too long"); } // Construct the version identifier/method selection message std::vector<uint8_t> vSocks5Init; // We want the SOCK5 protocol vSocks5Init.push_back(SOCKSVersion::SOCKS5); if (auth) { // 2 method identifiers follow... vSocks5Init.push_back(0x02); vSocks5Init.push_back(SOCKS5Method::NOAUTH); vSocks5Init.push_back(SOCKS5Method::USER_PASS); } else { // 1 method identifier follows... vSocks5Init.push_back(0x01); vSocks5Init.push_back(SOCKS5Method::NOAUTH); } ssize_t ret = sock.Send(vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL); if (ret != (ssize_t)vSocks5Init.size()) { return error("Error sending to proxy"); } uint8_t pchRet1[2]; if ((recvr = InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() " "timeout or other failure\n", strDest, port); return false; } if (pchRet1[0] != SOCKSVersion::SOCKS5) { return error("Proxy failed to initialize"); } if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) { // Perform username/password authentication (as described in RFC1929) std::vector<uint8_t> vAuth; // Current (and only) version of user/pass subnegotiation vAuth.push_back(0x01); if (auth->username.size() > 255 || auth->password.size() > 255) { return error("Proxy username or password too long"); } vAuth.push_back(auth->username.size()); vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end()); vAuth.push_back(auth->password.size()); vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end()); ret = sock.Send(vAuth.data(), vAuth.size(), MSG_NOSIGNAL); if (ret != (ssize_t)vAuth.size()) { return error("Error sending authentication to proxy"); } LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password); uint8_t pchRetA[2]; if ((recvr = InterruptibleRecv(pchRetA, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { return error("Error reading proxy authentication response"); } if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { return error("Proxy authentication unsuccessful"); } } else if (pchRet1[1] == SOCKS5Method::NOAUTH) { // Perform no authentication } else { return error("Proxy requested wrong authentication method %02x", pchRet1[1]); } std::vector<uint8_t> vSocks5; // VER protocol version vSocks5.push_back(SOCKSVersion::SOCKS5); // CMD CONNECT vSocks5.push_back(SOCKS5Command::CONNECT); // RSV Reserved must be 0 vSocks5.push_back(0x00); // ATYP DOMAINNAME vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // Length<=255 is checked at beginning of function vSocks5.push_back(strDest.size()); vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end()); vSocks5.push_back((port >> 8) & 0xFF); vSocks5.push_back((port >> 0) & 0xFF); ret = sock.Send(vSocks5.data(), vSocks5.size(), MSG_NOSIGNAL); if (ret != (ssize_t)vSocks5.size()) { return error("Error sending to proxy"); } uint8_t pchRet2[4]; if ((recvr = InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { if (recvr == IntrRecvError::Timeout) { /** * If a timeout happens here, this effectively means we timed out * while connecting to the remote node. This is very common for Tor, * so do not print an error message. */ return false; } else { return error("Error while reading proxy response"); } } if (pchRet2[0] != SOCKSVersion::SOCKS5) { return error("Proxy failed to accept request"); } if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) { // Failures to connect to a peer that are not proxy errors LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1])); return false; } // Reserved field must be 0 if (pchRet2[2] != 0x00) { return error("Error: malformed proxy response"); } uint8_t pchRet3[256]; switch (pchRet2[3]) { case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, sock); break; case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, sock); break; case SOCKS5Atyp::DOMAINNAME: { recvr = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, sock); if (recvr != IntrRecvError::OK) { return error("Error reading from proxy"); } int nRecv = pchRet3[0]; recvr = InterruptibleRecv(pchRet3, nRecv, SOCKS5_RECV_TIMEOUT, sock); break; } default: return error("Error: malformed proxy response"); } if (recvr != IntrRecvError::OK) { return error("Error reading from proxy"); } if ((recvr = InterruptibleRecv(pchRet3, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { return error("Error reading from proxy"); } LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest); return true; } std::unique_ptr<Sock> CreateSockTCP(const CService &address_family) { // Create a sockaddr from the specified service. struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (!address_family.GetSockAddr((struct sockaddr *)&sockaddr, &len)) { LogPrintf("Cannot create socket for %s: unsupported network\n", address_family.ToString()); return nullptr; } // Create a TCP socket in the address family of the specified service. SOCKET hSocket = socket(((struct sockaddr *)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP); if (hSocket == INVALID_SOCKET) { return nullptr; } // Ensure that waiting for I/O on this socket won't result in undefined // behavior. if (!IsSelectableSocket(hSocket)) { CloseSocket(hSocket); LogPrintf("Cannot create connection: non-selectable socket created (fd " ">= FD_SETSIZE ?)\n"); return nullptr; } #ifdef SO_NOSIGPIPE int set = 1; // Set the no-sigpipe option on the socket for BSD systems, other UNIXes // should use the MSG_NOSIGNAL flag for every send. setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (sockopt_arg_type)&set, sizeof(int)); #endif // Set the no-delay option (disable Nagle's algorithm) on the TCP socket. SetSocketNoDelay(hSocket); // Set the non-blocking option on the socket. if (!SetSocketNonBlocking(hSocket, true)) { CloseSocket(hSocket); LogPrintf("CreateSocket: Setting socket to non-blocking " "failed, error %s\n", NetworkErrorString(WSAGetLastError())); return nullptr; } return std::make_unique<Sock>(hSocket); } std::function<std::unique_ptr<Sock>(const CService &)> CreateSock = CreateSockTCP; template <typename... Args> static void LogConnectFailure(bool manual_connection, const char *fmt, - const Args &... args) { + const Args &...args) { std::string error_message = tfm::format(fmt, args...); if (manual_connection) { LogPrintf("%s\n", error_message); } else { LogPrint(BCLog::NET, "%s\n", error_message); } } bool ConnectSocketDirectly(const CService &addrConnect, const Sock &sock, int nTimeout, bool manual_connection) { // Create a sockaddr from the specified service. struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (sock.Get() == INVALID_SOCKET) { LogPrintf("Cannot connect to %s: invalid socket\n", addrConnect.ToString()); return false; } if (!addrConnect.GetSockAddr((struct sockaddr *)&sockaddr, &len)) { LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToString()); return false; } // Connect to the addrConnect service on the hSocket socket. if (sock.Connect(reinterpret_cast<struct sockaddr *>(&sockaddr), len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); // WSAEINVAL is here because some legacy version of winsock uses it if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) { // Connection didn't actually fail, but is being established // asynchronously. Thus, use async I/O api (select/poll) // synchronously to check for successful connection with a timeout. const Sock::Event requested = Sock::RECV | Sock::SEND; Sock::Event occurred; if (!sock.Wait(std::chrono::milliseconds{nTimeout}, requested, &occurred)) { LogPrintf("wait for connect to %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); return false; } else if (occurred == 0) { LogPrint(BCLog::NET, "connection attempt to %s timed out\n", addrConnect.ToString()); return false; } // Even if the wait was successful, the connect might not // have been successful. The reason for this failure is hidden away // in the SO_ERROR for the socket in modern systems. We read it into // sockerr here. int sockerr; socklen_t sockerr_len = sizeof(sockerr); if (sock.GetSockOpt(SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&sockerr, &sockerr_len) == SOCKET_ERROR) { LogPrintf("getsockopt() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); return false; } if (sockerr != 0) { LogConnectFailure( manual_connection, "connect() to %s failed after wait: %s", addrConnect.ToString(), NetworkErrorString(sockerr)); return false; } } #ifdef WIN32 else if (WSAGetLastError() != WSAEISCONN) #else else #endif { LogConnectFailure(manual_connection, "connect() to %s failed: %s", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); return false; } } return true; } bool SetProxy(enum Network net, const proxyType &addrProxy) { assert(net >= 0 && net < NET_MAX); if (!addrProxy.IsValid()) { return false; } LOCK(g_proxyinfo_mutex); proxyInfo[net] = addrProxy; return true; } bool GetProxy(enum Network net, proxyType &proxyInfoOut) { assert(net >= 0 && net < NET_MAX); LOCK(g_proxyinfo_mutex); if (!proxyInfo[net].IsValid()) { return false; } proxyInfoOut = proxyInfo[net]; return true; } bool SetNameProxy(const proxyType &addrProxy) { if (!addrProxy.IsValid()) { return false; } LOCK(g_proxyinfo_mutex); nameProxy = addrProxy; return true; } bool GetNameProxy(proxyType &nameProxyOut) { LOCK(g_proxyinfo_mutex); if (!nameProxy.IsValid()) { return false; } nameProxyOut = nameProxy; return true; } bool HaveNameProxy() { LOCK(g_proxyinfo_mutex); return nameProxy.IsValid(); } bool IsProxy(const CNetAddr &addr) { LOCK(g_proxyinfo_mutex); for (int i = 0; i < NET_MAX; i++) { if (addr == static_cast<CNetAddr>(proxyInfo[i].proxy)) { return true; } } return false; } bool ConnectThroughProxy(const proxyType &proxy, const std::string &strDest, uint16_t port, const Sock &sock, int nTimeout, bool &outProxyConnectionFailed) { // first connect to proxy server if (!ConnectSocketDirectly(proxy.proxy, sock, nTimeout, true)) { outProxyConnectionFailed = true; return false; } // do socks negotiation if (proxy.randomize_credentials) { ProxyCredentials random_auth; static std::atomic_int counter(0); random_auth.username = random_auth.password = strprintf("%i", counter++); if (!Socks5(strDest, port, &random_auth, sock)) { return false; } } else if (!Socks5(strDest, port, 0, sock)) { return false; } return true; } bool LookupSubNet(const std::string &strSubnet, CSubNet &ret, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(strSubnet)) { return false; } size_t slash = strSubnet.find_last_of('/'); std::vector<CNetAddr> vIP; std::string strAddress = strSubnet.substr(0, slash); // TODO: Use LookupHost(const std::string&, CNetAddr&, bool) instead to just if (LookupHost(strAddress, vIP, 1, false, dns_lookup_function)) { CNetAddr network = vIP[0]; if (slash != strSubnet.npos) { std::string strNetmask = strSubnet.substr(slash + 1); uint8_t n; if (ParseUInt8(strNetmask, &n)) { // If valid number, assume CIDR variable-length subnet masking ret = CSubNet(network, n); return ret.IsValid(); } else { // If not a valid number, try full netmask syntax // Never allow lookup for netmask if (LookupHost(strNetmask, vIP, 1, false, dns_lookup_function)) { ret = CSubNet(network, vIP[0]); return ret.IsValid(); } } } else { ret = CSubNet(network); return ret.IsValid(); } } return false; } bool SetSocketNonBlocking(const SOCKET &hSocket, bool fNonBlocking) { if (fNonBlocking) { #ifdef WIN32 u_long nOne = 1; if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) { #else int fFlags = fcntl(hSocket, F_GETFL, 0); if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == SOCKET_ERROR) { #endif return false; } } else { #ifdef WIN32 u_long nZero = 0; if (ioctlsocket(hSocket, FIONBIO, &nZero) == SOCKET_ERROR) { #else int fFlags = fcntl(hSocket, F_GETFL, 0); if (fcntl(hSocket, F_SETFL, fFlags & ~O_NONBLOCK) == SOCKET_ERROR) { #endif return false; } } return true; } bool SetSocketNoDelay(const SOCKET &hSocket) { int set = 1; int rc = setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (sockopt_arg_type)&set, sizeof(int)); return rc == 0; } void InterruptSocks5(bool interrupt) { interruptSocks5Recv = interrupt; } diff --git a/src/netmessagemaker.h b/src/netmessagemaker.h index b39da87a1..da5c51f94 100644 --- a/src/netmessagemaker.h +++ b/src/netmessagemaker.h @@ -1,37 +1,37 @@ // 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. #ifndef BITCOIN_NETMESSAGEMAKER_H #define BITCOIN_NETMESSAGEMAKER_H #include <serialize.h> struct CSerializedNetMsg; class CVectorWriter; class CNetMsgMaker { public: explicit CNetMsgMaker(int nVersionIn) : nVersion(nVersionIn) {} template <typename... Args> CSerializedNetMsg Make(int nFlags, std::string msg_type, - Args &&... args) const { + Args &&...args) const { CSerializedNetMsg msg; msg.m_type = std::move(msg_type); CVectorWriter{SER_NETWORK, nFlags | nVersion, msg.data, 0, std::forward<Args>(args)...}; return msg; } template <typename... Args> - CSerializedNetMsg Make(std::string msg_type, Args &&... args) const { + CSerializedNetMsg Make(std::string msg_type, Args &&...args) const { return Make(0, std::move(msg_type), std::forward<Args>(args)...); } private: const int nVersion; }; #endif // BITCOIN_NETMESSAGEMAKER_H diff --git a/src/prevector.h b/src/prevector.h index 8dc64a306..82f5c7c55 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -1,615 +1,615 @@ // Copyright (c) 2015-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. #ifndef BITCOIN_PREVECTOR_H #define BITCOIN_PREVECTOR_H #include <algorithm> #include <cassert> #include <cstddef> #include <cstdint> #include <cstdlib> #include <cstring> #include <type_traits> #include <utility> /** * Implements a drop-in replacement for std::vector<T> which stores up to N * elements directly (without heap allocation). The types Size and Diff are used * to store element counts, and can be any unsigned + signed type. * * Storage layout is either: * - Direct allocation: * - Size _size: the number of used elements (between 0 and N) * - T direct[N]: an array of N elements of type T * (only the first _size are initialized). * - Indirect allocation: * - Size _size: the number of used elements plus N + 1 * - Size capacity: the number of allocated elements * - T* indirect: a pointer to an array of capacity elements of type T * (only the first _size are initialized). * * The data type T must be movable by memmove/realloc(). Once we switch to C++, * move constructors can be used instead. */ template <unsigned int N, typename T, typename Size = uint32_t, typename Diff = int32_t> class prevector { public: typedef Size size_type; typedef Diff difference_type; typedef T value_type; typedef value_type &reference; typedef const value_type &const_reference; typedef value_type *pointer; typedef const value_type *const_pointer; class iterator { T *ptr; public: typedef Diff difference_type; typedef T value_type; typedef T *pointer; typedef T &reference; typedef std::random_access_iterator_tag iterator_category; iterator() : ptr(nullptr) {} iterator(T *ptr_) : ptr(ptr_) {} T &operator*() const { return *ptr; } T *operator->() const { return ptr; } T &operator[](size_type pos) { return ptr[pos]; } const T &operator[](size_type pos) const { return ptr[pos]; } iterator &operator++() { ptr++; return *this; } iterator &operator--() { ptr--; return *this; } iterator operator++(int) { iterator copy(*this); ++(*this); return copy; } iterator operator--(int) { iterator copy(*this); --(*this); return copy; } difference_type friend operator-(iterator a, iterator b) { return (&(*a) - &(*b)); } iterator operator+(size_type n) { return iterator(ptr + n); } iterator &operator+=(size_type n) { ptr += n; return *this; } iterator operator-(size_type n) { return iterator(ptr - n); } iterator &operator-=(size_type n) { ptr -= n; return *this; } bool operator==(iterator x) const { return ptr == x.ptr; } bool operator!=(iterator x) const { return ptr != x.ptr; } bool operator>=(iterator x) const { return ptr >= x.ptr; } bool operator<=(iterator x) const { return ptr <= x.ptr; } bool operator>(iterator x) const { return ptr > x.ptr; } bool operator<(iterator x) const { return ptr < x.ptr; } }; class reverse_iterator { T *ptr; public: typedef Diff difference_type; typedef T value_type; typedef T *pointer; typedef T &reference; typedef std::bidirectional_iterator_tag iterator_category; reverse_iterator() : ptr(nullptr) {} reverse_iterator(T *ptr_) : ptr(ptr_) {} T &operator*() { return *ptr; } const T &operator*() const { return *ptr; } T *operator->() { return ptr; } const T *operator->() const { return ptr; } reverse_iterator &operator--() { ptr++; return *this; } reverse_iterator &operator++() { ptr--; return *this; } reverse_iterator operator++(int) { reverse_iterator copy(*this); ++(*this); return copy; } reverse_iterator operator--(int) { reverse_iterator copy(*this); --(*this); return copy; } bool operator==(reverse_iterator x) const { return ptr == x.ptr; } bool operator!=(reverse_iterator x) const { return ptr != x.ptr; } }; class const_iterator { const T *ptr; public: typedef Diff difference_type; typedef const T value_type; typedef const T *pointer; typedef const T &reference; typedef std::random_access_iterator_tag iterator_category; const_iterator() : ptr(nullptr) {} const_iterator(const T *ptr_) : ptr(ptr_) {} const_iterator(iterator x) : ptr(&(*x)) {} const T &operator*() const { return *ptr; } const T *operator->() const { return ptr; } const T &operator[](size_type pos) const { return ptr[pos]; } const_iterator &operator++() { ptr++; return *this; } const_iterator &operator--() { ptr--; return *this; } const_iterator operator++(int) { const_iterator copy(*this); ++(*this); return copy; } const_iterator operator--(int) { const_iterator copy(*this); --(*this); return copy; } difference_type friend operator-(const_iterator a, const_iterator b) { return (&(*a) - &(*b)); } const_iterator operator+(size_type n) { return const_iterator(ptr + n); } const_iterator &operator+=(size_type n) { ptr += n; return *this; } const_iterator operator-(size_type n) { return const_iterator(ptr - n); } const_iterator &operator-=(size_type n) { ptr -= n; return *this; } bool operator==(const_iterator x) const { return ptr == x.ptr; } bool operator!=(const_iterator x) const { return ptr != x.ptr; } bool operator>=(const_iterator x) const { return ptr >= x.ptr; } bool operator<=(const_iterator x) const { return ptr <= x.ptr; } bool operator>(const_iterator x) const { return ptr > x.ptr; } bool operator<(const_iterator x) const { return ptr < x.ptr; } }; class const_reverse_iterator { const T *ptr; public: typedef Diff difference_type; typedef const T value_type; typedef const T *pointer; typedef const T &reference; typedef std::bidirectional_iterator_tag iterator_category; const_reverse_iterator() : ptr(nullptr) {} const_reverse_iterator(const T *ptr_) : ptr(ptr_) {} const_reverse_iterator(reverse_iterator x) : ptr(&(*x)) {} const T &operator*() const { return *ptr; } const T *operator->() const { return ptr; } const_reverse_iterator &operator--() { ptr++; return *this; } const_reverse_iterator &operator++() { ptr--; return *this; } const_reverse_iterator operator++(int) { const_reverse_iterator copy(*this); ++(*this); return copy; } const_reverse_iterator operator--(int) { const_reverse_iterator copy(*this); --(*this); return copy; } bool operator==(const_reverse_iterator x) const { return ptr == x.ptr; } bool operator!=(const_reverse_iterator x) const { return ptr != x.ptr; } }; private: #pragma pack(push, 1) union direct_or_indirect { char direct[sizeof(T) * N]; struct { char *indirect; size_type capacity; } indirect_contents; }; #pragma pack(pop) alignas(char *) direct_or_indirect _union = {}; size_type _size = 0; static_assert(alignof(char *) % alignof(size_type) == 0 && sizeof(char *) % alignof(size_type) == 0, "size_type cannot have more restrictive alignment " "requirement than pointer"); static_assert(alignof(char *) % alignof(T) == 0, "value_type T cannot have more restrictive alignment " "requirement than pointer"); T *direct_ptr(difference_type pos) { return reinterpret_cast<T *>(_union.direct) + pos; } const T *direct_ptr(difference_type pos) const { return reinterpret_cast<const T *>(_union.direct) + pos; } T *indirect_ptr(difference_type pos) { return reinterpret_cast<T *>(_union.indirect_contents.indirect) + pos; } const T *indirect_ptr(difference_type pos) const { return reinterpret_cast<const T *>(_union.indirect_contents.indirect) + pos; } bool is_direct() const { return _size <= N; } void change_capacity(size_type new_capacity) { if (new_capacity <= N) { if (!is_direct()) { T *indirect = indirect_ptr(0); T *src = indirect; T *dst = direct_ptr(0); memcpy(dst, src, size() * sizeof(T)); free(indirect); _size -= N + 1; } } else { if (!is_direct()) { // FIXME: Because malloc/realloc here won't call new_handler if // allocation fails, assert success. These should instead use an // allocator or new/delete so that handlers are called as // necessary, but performance would be slightly degraded by // doing so. _union.indirect_contents.indirect = static_cast<char *>( realloc(_union.indirect_contents.indirect, ((size_t)sizeof(T)) * new_capacity)); assert(_union.indirect_contents.indirect); _union.indirect_contents.capacity = new_capacity; } else { char *new_indirect = static_cast<char *>( malloc(((size_t)sizeof(T)) * new_capacity)); assert(new_indirect); T *src = direct_ptr(0); T *dst = reinterpret_cast<T *>(new_indirect); memcpy(dst, src, size() * sizeof(T)); _union.indirect_contents.indirect = new_indirect; _union.indirect_contents.capacity = new_capacity; _size += N + 1; } } } T *item_ptr(difference_type pos) { return is_direct() ? direct_ptr(pos) : indirect_ptr(pos); } const T *item_ptr(difference_type pos) const { return is_direct() ? direct_ptr(pos) : indirect_ptr(pos); } void fill(T *dst, ptrdiff_t count, const T &value = T{}) { std::fill_n(dst, count, value); } template <typename InputIterator> void fill(T *dst, InputIterator first, InputIterator last) { while (first != last) { new (static_cast<void *>(dst)) T(*first); ++dst; ++first; } } public: void assign(size_type n, const T &val) { clear(); if (capacity() < n) { change_capacity(n); } _size += n; fill(item_ptr(0), n, val); } template <typename InputIterator> void assign(InputIterator first, InputIterator last) { size_type n = last - first; clear(); if (capacity() < n) { change_capacity(n); } _size += n; fill(item_ptr(0), first, last); } prevector() {} explicit prevector(size_type n) { resize(n); } explicit prevector(size_type n, const T &val) { change_capacity(n); _size += n; fill(item_ptr(0), n, val); } template <typename InputIterator> prevector(InputIterator first, InputIterator last) { size_type n = last - first; change_capacity(n); _size += n; fill(item_ptr(0), first, last); } prevector(const prevector<N, T, Size, Diff> &other) { size_type n = other.size(); change_capacity(n); _size += n; fill(item_ptr(0), other.begin(), other.end()); } prevector(prevector<N, T, Size, Diff> &&other) { swap(other); } prevector &operator=(const prevector<N, T, Size, Diff> &other) { if (&other == this) { return *this; } assign(other.begin(), other.end()); return *this; } prevector &operator=(prevector<N, T, Size, Diff> &&other) { swap(other); return *this; } size_type size() const { return is_direct() ? _size : _size - N - 1; } bool empty() const { return size() == 0; } iterator begin() { return iterator(item_ptr(0)); } const_iterator begin() const { return const_iterator(item_ptr(0)); } iterator end() { return iterator(item_ptr(size())); } const_iterator end() const { return const_iterator(item_ptr(size())); } reverse_iterator rbegin() { return reverse_iterator(item_ptr(size() - 1)); } const_reverse_iterator rbegin() const { return const_reverse_iterator(item_ptr(size() - 1)); } reverse_iterator rend() { return reverse_iterator(item_ptr(-1)); } const_reverse_iterator rend() const { return const_reverse_iterator(item_ptr(-1)); } size_t capacity() const { if (is_direct()) { return N; } else { return _union.indirect_contents.capacity; } } T &operator[](size_type pos) { return *item_ptr(pos); } const T &operator[](size_type pos) const { return *item_ptr(pos); } void resize(size_type new_size) { size_type cur_size = size(); if (cur_size == new_size) { return; } if (cur_size > new_size) { erase(item_ptr(new_size), end()); return; } if (new_size > capacity()) { change_capacity(new_size); } ptrdiff_t increase = new_size - cur_size; fill(item_ptr(cur_size), increase); _size += increase; } void reserve(size_type new_capacity) { if (new_capacity > capacity()) { change_capacity(new_capacity); } } void shrink_to_fit() { change_capacity(size()); } void clear() { resize(0); } iterator insert(iterator pos, const T &value) { size_type p = pos - begin(); size_type new_size = size() + 1; if (capacity() < new_size) { change_capacity(new_size + (new_size >> 1)); } T *ptr = item_ptr(p); memmove(ptr + 1, ptr, (size() - p) * sizeof(T)); _size++; new (static_cast<void *>(ptr)) T(value); return iterator(ptr); } void insert(iterator pos, size_type count, const T &value) { size_type p = pos - begin(); size_type new_size = size() + count; if (capacity() < new_size) { change_capacity(new_size + (new_size >> 1)); } T *ptr = item_ptr(p); memmove(ptr + count, ptr, (size() - p) * sizeof(T)); _size += count; fill(item_ptr(p), count, value); } template <typename InputIterator> void insert(iterator pos, InputIterator first, InputIterator last) { size_type p = pos - begin(); difference_type count = last - first; size_type new_size = size() + count; if (capacity() < new_size) { change_capacity(new_size + (new_size >> 1)); } T *ptr = item_ptr(p); memmove(ptr + count, ptr, (size() - p) * sizeof(T)); _size += count; fill(ptr, first, last); } inline void resize_uninitialized(size_type new_size) { // resize_uninitialized changes the size of the prevector but does not // initialize it. If size < new_size, the added elements must be // initialized explicitly. if (capacity() < new_size) { change_capacity(new_size); _size += new_size - size(); return; } if (new_size < size()) { erase(item_ptr(new_size), end()); } else { _size += new_size - size(); } } iterator erase(iterator pos) { return erase(pos, pos + 1); } iterator erase(iterator first, iterator last) { // Erase is not allowed to the change the object's capacity. That means // that when starting with an indirectly allocated prevector with // size and capacity > N, the result may be a still indirectly allocated // prevector with size <= N and capacity > N. A shrink_to_fit() call is // necessary to switch to the (more efficient) directly allocated // representation (with capacity N and size <= N). iterator p = first; char *endp = (char *)&(*end()); if (!std::is_trivially_destructible<T>::value) { while (p != last) { (*p).~T(); _size--; ++p; } } else { _size -= last - p; } memmove(&(*first), &(*last), endp - ((char *)(&(*last)))); return first; } - template <typename... Args> void emplace_back(Args &&... args) { + template <typename... Args> void emplace_back(Args &&...args) { size_type new_size = size() + 1; if (capacity() < new_size) { change_capacity(new_size + (new_size >> 1)); } new (item_ptr(size())) T(std::forward<Args>(args)...); _size++; } void push_back(const T &value) { emplace_back(value); } void pop_back() { erase(end() - 1, end()); } T &front() { return *item_ptr(0); } const T &front() const { return *item_ptr(0); } T &back() { return *item_ptr(size() - 1); } const T &back() const { return *item_ptr(size() - 1); } void swap(prevector<N, T, Size, Diff> &other) { std::swap(_union, other._union); std::swap(_size, other._size); } ~prevector() { if (!std::is_trivially_destructible<T>::value) { clear(); } if (!is_direct()) { free(_union.indirect_contents.indirect); _union.indirect_contents.indirect = nullptr; } } bool operator==(const prevector<N, T, Size, Diff> &other) const { if (other.size() != size()) { return false; } const_iterator b1 = begin(); const_iterator b2 = other.begin(); const_iterator e1 = end(); while (b1 != e1) { if ((*b1) != (*b2)) { return false; } ++b1; ++b2; } return true; } bool operator!=(const prevector<N, T, Size, Diff> &other) const { return !(*this == other); } bool operator<(const prevector<N, T, Size, Diff> &other) const { if (size() < other.size()) { return true; } if (size() > other.size()) { return false; } const_iterator b1 = begin(); const_iterator b2 = other.begin(); const_iterator e1 = end(); while (b1 != e1) { if ((*b1) < (*b2)) { return true; } if ((*b2) < (*b1)) { return false; } ++b1; ++b2; } return false; } size_t allocated_memory() const { if (is_direct()) { return 0; } else { return ((size_t)(sizeof(T))) * _union.indirect_contents.capacity; } } value_type *data() { return item_ptr(0); } const value_type *data() const { return item_ptr(0); } }; #endif // BITCOIN_PREVECTOR_H diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 7d78628d7..9d9c3ec50 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1,1660 +1,1658 @@ // Copyright (c) 2011-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <qt/bitcoingui.h> #include <chain.h> #include <chainparams.h> #include <config.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <node/ui_interface.h> #include <qt/bitcoinunits.h> #include <qt/clientmodel.h> #include <qt/createwalletdialog.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> #ifdef Q_OS_MAC #include <qt/macdockiconhandler.h> #endif #include <qt/modaloverlay.h> #include <qt/networkstyle.h> #include <qt/notificator.h> #include <qt/openuridialog.h> #include <qt/optionsmodel.h> #include <qt/platformstyle.h> #include <qt/rpcconsole.h> #include <qt/utilitydialog.h> #ifdef ENABLE_WALLET #include <qt/walletcontroller.h> #include <qt/walletframe.h> #include <qt/walletmodel.h> #include <qt/walletview.h> #endif // ENABLE_WALLET #include <util/system.h> #include <util/translation.h> #include <validation.h> #include <functional> #include <memory> #include <QAction> #include <QApplication> #include <QComboBox> #include <QDateTime> #include <QDragEnterEvent> #include <QListWidget> #include <QMenu> #include <QMenuBar> #include <QMessageBox> #include <QMimeData> #include <QProgressDialog> #include <QScreen> #include <QSettings> #include <QShortcut> #include <QStackedWidget> #include <QStatusBar> #include <QStyle> #include <QSystemTrayIcon> #include <QTimer> #include <QToolBar> #include <QUrlQuery> #include <QVBoxLayout> #include <QWindow> const std::string BitcoinGUI::DEFAULT_UIPLATFORM = #if defined(Q_OS_MAC) "macosx" #elif defined(Q_OS_WIN) "windows" #else "other" #endif ; BitcoinGUI::BitcoinGUI(interfaces::Node &node, const Config *configIn, const PlatformStyle *_platformStyle, const NetworkStyle *networkStyle, QWidget *parent) : QMainWindow(parent), m_node(node), trayIconMenu{new QMenu()}, config(configIn), platformStyle(_platformStyle), m_network_style(networkStyle) { QSettings settings; if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) { // Restore failed (perhaps missing setting), center the window move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center()); } #ifdef ENABLE_WALLET enableWallet = WalletModel::isWalletEnabled(); #endif // ENABLE_WALLET QApplication::setWindowIcon(m_network_style->getTrayAndWindowIcon()); setWindowIcon(m_network_style->getTrayAndWindowIcon()); updateWindowTitle(); rpcConsole = new RPCConsole(node, _platformStyle, nullptr); helpMessageDialog = new HelpMessageDialog(this, false); #ifdef ENABLE_WALLET if (enableWallet) { /** Create wallet frame and make it the central widget */ walletFrame = new WalletFrame(_platformStyle, this); setCentralWidget(walletFrame); } else #endif // ENABLE_WALLET { /** * When compiled without wallet or -disablewallet is provided, the * central widget is the rpc console. */ setCentralWidget(rpcConsole); Q_EMIT consoleShown(rpcConsole); } modalOverlay = new ModalOverlay(enableWallet, this->centralWidget()); // Accept D&D of URIs setAcceptDrops(true); // Create actions for the toolbar, menu bar and tray/dock icon // Needs walletFrame to be initialized createActions(); // Create application menu bar createMenuBar(); // Create the toolbars createToolBars(); // Create system tray icon and notification if (QSystemTrayIcon::isSystemTrayAvailable()) { createTrayIcon(); } notificator = new Notificator(QApplication::applicationName(), trayIcon, this); // Create status bar statusBar(); // Disable size grip because it looks ugly and nobody needs it statusBar()->setSizeGripEnabled(false); // Status bar notification icons QFrame *frameBlocks = new QFrame(); frameBlocks->setContentsMargins(0, 0, 0, 0); frameBlocks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks); frameBlocksLayout->setContentsMargins(3, 0, 3, 0); frameBlocksLayout->setSpacing(3); unitDisplayControl = new UnitDisplayStatusBarControl(platformStyle); labelWalletEncryptionIcon = new QLabel(); labelWalletHDStatusIcon = new QLabel(); labelProxyIcon = new GUIUtil::ClickableLabel(); connectionsControl = new GUIUtil::ClickableLabel(); labelBlocksIcon = new GUIUtil::ClickableLabel(); if (enableWallet) { frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(unitDisplayControl); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelWalletEncryptionIcon); frameBlocksLayout->addWidget(labelWalletHDStatusIcon); } frameBlocksLayout->addWidget(labelProxyIcon); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(connectionsControl); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelBlocksIcon); frameBlocksLayout->addStretch(); // Progress bar and label for blocks download progressBarLabel = new QLabel(); progressBarLabel->setVisible(false); progressBar = new GUIUtil::ProgressBar(); progressBar->setAlignment(Qt::AlignCenter); progressBar->setVisible(false); // Override style sheet for progress bar for styles that have a segmented // progress bar, as they make the text unreadable (workaround for issue // #1071) // See https://doc.qt.io/qt-5/gallery.html QString curStyle = QApplication::style()->metaObject()->className(); if (curStyle == "QWindowsStyle" || curStyle == "QWindowsXPStyle") { progressBar->setStyleSheet( "QProgressBar { background-color: #e8e8e8; border: 1px solid grey; " "border-radius: 7px; padding: 1px; text-align: center; } " "QProgressBar::chunk { background: QLinearGradient(x1: 0, y1: 0, " "x2: 1, y2: 0, stop: 0 #FF8000, stop: 1 orange); border-radius: " "7px; margin: 0px; }"); } statusBar()->addWidget(progressBarLabel); statusBar()->addWidget(progressBar); statusBar()->addPermanentWidget(frameBlocks); // Install event filter to be able to catch status tip events // (QEvent::StatusTip) this->installEventFilter(this); // Initially wallet actions should be disabled setWalletActionsEnabled(false); // Subscribe to notifications from core subscribeToCoreSignals(); connect(connectionsControl, &GUIUtil::ClickableLabel::clicked, [this] { m_node.setNetworkActive(!m_node.getNetworkActive()); }); connect(labelProxyIcon, &GUIUtil::ClickableLabel::clicked, [this] { openOptionsDialogWithTab(OptionsDialog::TAB_NETWORK); }); connect(labelBlocksIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showModalOverlay); connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay); #ifdef ENABLE_WALLET if (enableWallet) { connect(walletFrame, &WalletFrame::requestedSyncWarningInfo, this, &BitcoinGUI::showModalOverlay); } #endif #ifdef Q_OS_MAC m_app_nap_inhibitor = new CAppNapInhibitor; #endif GUIUtil::handleCloseWindowShortcut(this); } BitcoinGUI::~BitcoinGUI() { // Unsubscribe from notifications from core unsubscribeFromCoreSignals(); QSettings settings; settings.setValue("MainWindowGeometry", saveGeometry()); // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) if (trayIcon) { trayIcon->hide(); } #ifdef Q_OS_MAC delete m_app_nap_inhibitor; delete appMenuBar; MacDockIconHandler::cleanup(); #endif delete rpcConsole; } void BitcoinGUI::createActions() { QActionGroup *tabGroup = new QActionGroup(this); connect(modalOverlay, &ModalOverlay::triggered, tabGroup, &QActionGroup::setEnabled); overviewAction = new QAction(platformStyle->SingleColorIcon(":/icons/overview"), tr("&Overview"), this); overviewAction->setStatusTip(tr("Show general overview of wallet")); overviewAction->setToolTip(overviewAction->statusTip()); overviewAction->setCheckable(true); overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1)); tabGroup->addAction(overviewAction); sendCoinsAction = new QAction( platformStyle->SingleColorIcon(":/icons/send"), tr("&Send"), this); sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address")); sendCoinsAction->setToolTip(sendCoinsAction->statusTip()); sendCoinsAction->setCheckable(true); sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); tabGroup->addAction(sendCoinsAction); sendCoinsMenuAction = new QAction(sendCoinsAction->text(), this); sendCoinsMenuAction->setStatusTip(sendCoinsAction->statusTip()); sendCoinsMenuAction->setToolTip(sendCoinsMenuAction->statusTip()); receiveCoinsAction = new QAction( platformStyle->SingleColorIcon(":/icons/receiving_addresses"), tr("&Receive"), this); receiveCoinsAction->setStatusTip( tr("Request payments (generates QR codes and %1: URIs)") .arg(QString::fromStdString( config->GetChainParams().CashAddrPrefix()))); receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip()); receiveCoinsAction->setCheckable(true); receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); tabGroup->addAction(receiveCoinsAction); receiveCoinsMenuAction = new QAction(receiveCoinsAction->text(), this); receiveCoinsMenuAction->setStatusTip(receiveCoinsAction->statusTip()); receiveCoinsMenuAction->setToolTip(receiveCoinsMenuAction->statusTip()); historyAction = new QAction(platformStyle->SingleColorIcon(":/icons/history"), tr("&Transactions"), this); historyAction->setStatusTip(tr("Browse transaction history")); historyAction->setToolTip(historyAction->statusTip()); historyAction->setCheckable(true); historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); tabGroup->addAction(historyAction); #ifdef ENABLE_WALLET // These showNormalIfMinimized are needed because Send Coins and Receive // Coins can be triggered from the tray menu, and need to show the GUI to be // useful. connect(overviewAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(overviewAction, &QAction::triggered, this, &BitcoinGUI::gotoOverviewPage); connect(sendCoinsAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(sendCoinsAction, &QAction::triggered, [this] { gotoSendCoinsPage(); }); connect(sendCoinsMenuAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(sendCoinsMenuAction, &QAction::triggered, [this] { gotoSendCoinsPage(); }); connect(receiveCoinsAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(receiveCoinsAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); connect(receiveCoinsMenuAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(receiveCoinsMenuAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); connect(historyAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage); #endif // ENABLE_WALLET quitAction = new QAction(tr("E&xit"), this); quitAction->setStatusTip(tr("Quit application")); quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q)); quitAction->setMenuRole(QAction::QuitRole); aboutAction = new QAction(tr("&About %1").arg(PACKAGE_NAME), this); aboutAction->setStatusTip( tr("Show information about %1").arg(PACKAGE_NAME)); aboutAction->setMenuRole(QAction::AboutRole); aboutAction->setEnabled(false); aboutQtAction = new QAction(tr("About &Qt"), this); aboutQtAction->setStatusTip(tr("Show information about Qt")); aboutQtAction->setMenuRole(QAction::AboutQtRole); optionsAction = new QAction(tr("&Options..."), this); optionsAction->setStatusTip( tr("Modify configuration options for %1").arg(PACKAGE_NAME)); optionsAction->setMenuRole(QAction::PreferencesRole); optionsAction->setEnabled(false); toggleHideAction = new QAction(tr("&Show / Hide"), this); toggleHideAction->setStatusTip(tr("Show or hide the main Window")); encryptWalletAction = new QAction(tr("&Encrypt Wallet..."), this); encryptWalletAction->setStatusTip( tr("Encrypt the private keys that belong to your wallet")); encryptWalletAction->setCheckable(true); backupWalletAction = new QAction(tr("&Backup Wallet..."), this); backupWalletAction->setStatusTip(tr("Backup wallet to another location")); changePassphraseAction = new QAction(tr("&Change Passphrase..."), this); changePassphraseAction->setStatusTip( tr("Change the passphrase used for wallet encryption")); signMessageAction = new QAction(tr("Sign &message..."), this); signMessageAction->setStatusTip( tr("Sign messages with your Bitcoin addresses to prove you own them")); verifyMessageAction = new QAction(tr("&Verify message..."), this); verifyMessageAction->setStatusTip( tr("Verify messages to ensure they were signed with specified Bitcoin " "addresses")); m_load_psbt_action = new QAction(tr("Load PSBT..."), this); m_load_psbt_action->setStatusTip( tr("Load Partially Signed Bitcoin Transaction")); openRPCConsoleAction = new QAction(tr("&Debug window"), this); openRPCConsoleAction->setStatusTip( tr("Open node debugging and diagnostic console")); // initially disable the debug window menu item openRPCConsoleAction->setEnabled(false); openRPCConsoleAction->setObjectName("openRPCConsoleAction"); usedSendingAddressesAction = new QAction(tr("&Sending addresses"), this); usedSendingAddressesAction->setStatusTip( tr("Show the list of used sending addresses and labels")); usedReceivingAddressesAction = new QAction(tr("&Receiving addresses"), this); usedReceivingAddressesAction->setStatusTip( tr("Show the list of used receiving addresses and labels")); openAction = new QAction(tr("Open &URI..."), this); openAction->setStatusTip( tr("Open a %1: URI or payment request") .arg(QString::fromStdString( config->GetChainParams().CashAddrPrefix()))); m_open_wallet_action = new QAction(tr("Open Wallet"), this); m_open_wallet_action->setEnabled(false); m_open_wallet_action->setStatusTip(tr("Open a wallet")); m_open_wallet_menu = new QMenu(this); m_close_wallet_action = new QAction(tr("Close Wallet..."), this); m_close_wallet_action->setStatusTip(tr("Close wallet")); m_create_wallet_action = new QAction(tr("Create Wallet..."), this); m_create_wallet_action->setEnabled(false); m_create_wallet_action->setStatusTip(tr("Create a new wallet")); m_close_all_wallets_action = new QAction(tr("Close All Wallets..."), this); m_close_all_wallets_action->setStatusTip(tr("Close all wallets")); showHelpMessageAction = new QAction(tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); showHelpMessageAction->setStatusTip( tr("Show the %1 help message to get a list with possible Bitcoin " "command-line options") .arg(PACKAGE_NAME)); m_mask_values_action = new QAction(tr("&Mask values"), this); m_mask_values_action->setShortcut( QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_M)); m_mask_values_action->setStatusTip( tr("Mask the values in the Overview tab")); m_mask_values_action->setCheckable(true); connect(quitAction, &QAction::triggered, qApp, QApplication::quit); connect(aboutAction, &QAction::triggered, this, &BitcoinGUI::aboutClicked); connect(aboutQtAction, &QAction::triggered, qApp, QApplication::aboutQt); connect(optionsAction, &QAction::triggered, this, &BitcoinGUI::optionsClicked); connect(toggleHideAction, &QAction::triggered, this, &BitcoinGUI::toggleHidden); connect(showHelpMessageAction, &QAction::triggered, this, &BitcoinGUI::showHelpMessageClicked); connect(openRPCConsoleAction, &QAction::triggered, this, &BitcoinGUI::showDebugWindow); // prevents an open debug window from becoming stuck/unusable on client // shutdown connect(quitAction, &QAction::triggered, rpcConsole, &QWidget::hide); #ifdef ENABLE_WALLET if (walletFrame) { connect(encryptWalletAction, &QAction::triggered, walletFrame, &WalletFrame::encryptWallet); connect(backupWalletAction, &QAction::triggered, walletFrame, &WalletFrame::backupWallet); connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase); connect(signMessageAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(signMessageAction, &QAction::triggered, [this] { gotoSignMessageTab(); }); connect(verifyMessageAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(verifyMessageAction, &QAction::triggered, [this] { gotoVerifyMessageTab(); }); connect(m_load_psbt_action, &QAction::triggered, [this] { gotoLoadPSBT(); }); connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses); connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedReceivingAddresses); connect(openAction, &QAction::triggered, this, &BitcoinGUI::openClicked); connect(m_open_wallet_menu, &QMenu::aboutToShow, [this] { m_open_wallet_menu->clear(); for (const std::pair<const std::string, bool> &i : m_wallet_controller->listWalletDir()) { const std::string &path = i.first; QString name = path.empty() ? QString("[" + tr("default wallet") + "]") : QString::fromStdString(path); // Menu items remove single &. Single & are shown when && is in // the string, but only the first occurrence. So replace only // the first & with && name.replace(name.indexOf(QChar('&')), 1, QString("&&")); QAction *action = m_open_wallet_menu->addAction(name); if (i.second) { // This wallet is already loaded action->setEnabled(false); continue; } connect(action, &QAction::triggered, [this, path] { auto activity = new OpenWalletActivity(m_wallet_controller, this); connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet); connect(activity, &OpenWalletActivity::finished, activity, &QObject::deleteLater); activity->open(path); }); } if (m_open_wallet_menu->isEmpty()) { QAction *action = m_open_wallet_menu->addAction(tr("No wallets available")); action->setEnabled(false); } }); connect(m_close_wallet_action, &QAction::triggered, [this] { m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this); }); connect(m_create_wallet_action, &QAction::triggered, [this] { auto activity = new CreateWalletActivity(m_wallet_controller, this); connect(activity, &CreateWalletActivity::created, this, &BitcoinGUI::setCurrentWallet); connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater); activity->create(); }); connect(m_close_all_wallets_action, &QAction::triggered, [this] { m_wallet_controller->closeAllWallets(this); }); connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy); } #endif // ENABLE_WALLET connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindowActivateConsole); connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindow); } void BitcoinGUI::createMenuBar() { #ifdef Q_OS_MAC // Create a decoupled menu bar on Mac which stays even if the window is // closed appMenuBar = new QMenuBar(); #else // Get the main window's menu bar on other platforms appMenuBar = menuBar(); #endif // Configure the menus QMenu *file = appMenuBar->addMenu(tr("&File")); if (walletFrame) { file->addAction(m_create_wallet_action); file->addAction(m_open_wallet_action); file->addAction(m_close_wallet_action); file->addAction(m_close_all_wallets_action); file->addSeparator(); file->addAction(openAction); file->addAction(backupWalletAction); file->addAction(signMessageAction); file->addAction(verifyMessageAction); file->addAction(m_load_psbt_action); file->addSeparator(); } file->addAction(quitAction); QMenu *settings = appMenuBar->addMenu(tr("&Settings")); if (walletFrame) { settings->addAction(encryptWalletAction); settings->addAction(changePassphraseAction); settings->addSeparator(); settings->addAction(m_mask_values_action); settings->addSeparator(); } settings->addAction(optionsAction); QMenu *window_menu = appMenuBar->addMenu(tr("&Window")); QAction *minimize_action = window_menu->addAction(tr("Minimize")); minimize_action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); connect(minimize_action, &QAction::triggered, [] { QApplication::activeWindow()->showMinimized(); }); connect(qApp, &QApplication::focusWindowChanged, [minimize_action](QWindow *window) { minimize_action->setEnabled( window != nullptr && (window->flags() & Qt::Dialog) != Qt::Dialog && window->windowState() != Qt::WindowMinimized); }); #ifdef Q_OS_MAC QAction *zoom_action = window_menu->addAction(tr("Zoom")); connect(zoom_action, &QAction::triggered, [] { QWindow *window = qApp->focusWindow(); if (window->windowState() != Qt::WindowMaximized) { window->showMaximized(); } else { window->showNormal(); } }); connect(qApp, &QApplication::focusWindowChanged, [zoom_action](QWindow *window) { zoom_action->setEnabled(window != nullptr); }); #endif if (walletFrame) { #ifdef Q_OS_MAC window_menu->addSeparator(); QAction *main_window_action = window_menu->addAction(tr("Main Window")); connect(main_window_action, &QAction::triggered, [this] { GUIUtil::bringToFront(this); }); #endif window_menu->addSeparator(); window_menu->addAction(usedSendingAddressesAction); window_menu->addAction(usedReceivingAddressesAction); } window_menu->addSeparator(); for (RPCConsole::TabTypes tab_type : rpcConsole->tabs()) { QAction *tab_action = window_menu->addAction(rpcConsole->tabTitle(tab_type)); tab_action->setShortcut(rpcConsole->tabShortcut(tab_type)); connect(tab_action, &QAction::triggered, [this, tab_type] { rpcConsole->setTabFocus(tab_type); showDebugWindow(); }); } QMenu *help = appMenuBar->addMenu(tr("&Help")); help->addAction(showHelpMessageAction); help->addSeparator(); help->addAction(aboutAction); help->addAction(aboutQtAction); } void BitcoinGUI::createToolBars() { if (walletFrame) { QToolBar *toolbar = addToolBar(tr("Tabs toolbar")); appToolBar = toolbar; toolbar->setContextMenuPolicy(Qt::PreventContextMenu); toolbar->setMovable(false); toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolbar->addAction(overviewAction); toolbar->addAction(sendCoinsAction); toolbar->addAction(receiveCoinsAction); toolbar->addAction(historyAction); overviewAction->setChecked(true); #ifdef ENABLE_WALLET QWidget *spacer = new QWidget(); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); toolbar->addWidget(spacer); m_wallet_selector = new QComboBox(); m_wallet_selector->setSizeAdjustPolicy(QComboBox::AdjustToContents); connect(m_wallet_selector, static_cast<void (QComboBox::*)(int)>( &QComboBox::currentIndexChanged), this, &BitcoinGUI::setCurrentWalletBySelectorIndex); m_wallet_selector_label = new QLabel(); m_wallet_selector_label->setText(tr("Wallet:") + " "); m_wallet_selector_label->setBuddy(m_wallet_selector); m_wallet_selector_label_action = appToolBar->addWidget(m_wallet_selector_label); m_wallet_selector_action = appToolBar->addWidget(m_wallet_selector); m_wallet_selector_label_action->setVisible(false); m_wallet_selector_action->setVisible(false); #endif } } void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndHeaderTipInfo *tip_info) { this->clientModel = _clientModel; if (_clientModel) { // Create system tray menu (or setup the dock menu) that late to prevent // users from calling actions, while the client has not yet fully loaded createTrayIconMenu(); // Keep up to date with client updateNetworkState(); connect(_clientModel, &ClientModel::numConnectionsChanged, this, &BitcoinGUI::setNumConnections); connect(_clientModel, &ClientModel::networkActiveChanged, this, &BitcoinGUI::setNetworkActive); modalOverlay->setKnownBestHeight( tip_info->header_height, QDateTime::fromTime_t(tip_info->header_time)); setNumBlocks(tip_info->block_height, QDateTime::fromTime_t(tip_info->block_time), tip_info->verification_progress, false, SynchronizationState::INIT_DOWNLOAD); connect(_clientModel, &ClientModel::numBlocksChanged, this, &BitcoinGUI::setNumBlocks); // Receive and report messages from client model connect(_clientModel, &ClientModel::message, [this](const QString &title, const QString &message, unsigned int style) { this->message(title, message, style); }); // Show progress dialog connect(_clientModel, &ClientModel::showProgress, this, &BitcoinGUI::showProgress); rpcConsole->setClientModel(_clientModel, tip_info->block_height, tip_info->block_time, tip_info->verification_progress); updateProxyIcon(); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->setClientModel(_clientModel); } #endif // ENABLE_WALLET unitDisplayControl->setOptionsModel(_clientModel->getOptionsModel()); OptionsModel *optionsModel = _clientModel->getOptionsModel(); if (optionsModel && trayIcon) { // be aware of the tray icon disable state change reported by the // OptionsModel object. connect(optionsModel, &OptionsModel::hideTrayIconChanged, this, &BitcoinGUI::setTrayIconVisible); // initialize the disable state of the tray icon with the current // value in the model. setTrayIconVisible(optionsModel->getHideTrayIcon()); } } else { // Disable possibility to show main window via action toggleHideAction->setEnabled(false); if (trayIconMenu) { // Disable context menu on tray icon trayIconMenu->clear(); } // Propagate cleared model to child objects rpcConsole->setClientModel(nullptr); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->setClientModel(nullptr); } #endif // ENABLE_WALLET unitDisplayControl->setOptionsModel(nullptr); } } #ifdef ENABLE_WALLET void BitcoinGUI::setWalletController(WalletController *wallet_controller) { assert(!m_wallet_controller); assert(wallet_controller); m_wallet_controller = wallet_controller; m_create_wallet_action->setEnabled(true); m_open_wallet_action->setEnabled(true); m_open_wallet_action->setMenu(m_open_wallet_menu); connect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet); connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet); for (WalletModel *wallet_model : m_wallet_controller->getOpenWallets()) { addWallet(wallet_model); } } WalletController *BitcoinGUI::getWalletController() { return m_wallet_controller; } void BitcoinGUI::addWallet(WalletModel *walletModel) { if (!walletFrame) { return; } if (!walletFrame->addWallet(walletModel)) { return; } rpcConsole->addWallet(walletModel); if (m_wallet_selector->count() == 0) { setWalletActionsEnabled(true); } else if (m_wallet_selector->count() == 1) { m_wallet_selector_label_action->setVisible(true); m_wallet_selector_action->setVisible(true); } const QString display_name = walletModel->getDisplayName(); m_wallet_selector->addItem(display_name, QVariant::fromValue(walletModel)); } void BitcoinGUI::removeWallet(WalletModel *walletModel) { if (!walletFrame) { return; } labelWalletHDStatusIcon->hide(); labelWalletEncryptionIcon->hide(); int index = m_wallet_selector->findData(QVariant::fromValue(walletModel)); m_wallet_selector->removeItem(index); if (m_wallet_selector->count() == 0) { setWalletActionsEnabled(false); overviewAction->setChecked(true); } else if (m_wallet_selector->count() == 1) { m_wallet_selector_label_action->setVisible(false); m_wallet_selector_action->setVisible(false); } rpcConsole->removeWallet(walletModel); walletFrame->removeWallet(walletModel); updateWindowTitle(); } void BitcoinGUI::setCurrentWallet(WalletModel *wallet_model) { if (!walletFrame) { return; } walletFrame->setCurrentWallet(wallet_model); for (int index = 0; index < m_wallet_selector->count(); ++index) { if (m_wallet_selector->itemData(index).value<WalletModel *>() == wallet_model) { m_wallet_selector->setCurrentIndex(index); break; } } updateWindowTitle(); } void BitcoinGUI::setCurrentWalletBySelectorIndex(int index) { WalletModel *wallet_model = m_wallet_selector->itemData(index).value<WalletModel *>(); if (wallet_model) { setCurrentWallet(wallet_model); } } void BitcoinGUI::removeAllWallets() { if (!walletFrame) { return; } setWalletActionsEnabled(false); walletFrame->removeAllWallets(); } #endif // ENABLE_WALLET void BitcoinGUI::setWalletActionsEnabled(bool enabled) { overviewAction->setEnabled(enabled); sendCoinsAction->setEnabled(enabled); sendCoinsMenuAction->setEnabled(enabled); receiveCoinsAction->setEnabled(enabled); receiveCoinsMenuAction->setEnabled(enabled); historyAction->setEnabled(enabled); encryptWalletAction->setEnabled(enabled); backupWalletAction->setEnabled(enabled); changePassphraseAction->setEnabled(enabled); signMessageAction->setEnabled(enabled); verifyMessageAction->setEnabled(enabled); usedSendingAddressesAction->setEnabled(enabled); usedReceivingAddressesAction->setEnabled(enabled); openAction->setEnabled(enabled); m_close_wallet_action->setEnabled(enabled); m_close_all_wallets_action->setEnabled(enabled); } void BitcoinGUI::createTrayIcon() { assert(QSystemTrayIcon::isSystemTrayAvailable()); #ifndef Q_OS_MAC if (QSystemTrayIcon::isSystemTrayAvailable()) { trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this); QString toolTip = tr("%1 client").arg(PACKAGE_NAME) + " " + m_network_style->getTitleAddText(); trayIcon->setToolTip(toolTip); } #endif } void BitcoinGUI::createTrayIconMenu() { #ifndef Q_OS_MAC // Return if trayIcon is unset (only on non-macOSes) if (!trayIcon) { return; } trayIcon->setContextMenu(trayIconMenu.get()); connect(trayIcon, &QSystemTrayIcon::activated, this, &BitcoinGUI::trayIconActivated); #else // Note: On macOS, the Dock icon is used to provide the tray's // functionality. MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance(); connect(dockIconHandler, &MacDockIconHandler::dockIconClicked, this, &BitcoinGUI::macosDockIconActivated); trayIconMenu->setAsDockMenu(); #endif // Configuration of the tray icon (or Dock icon) menu #ifndef Q_OS_MAC // Note: On macOS, the Dock icon's menu already has Show / Hide action. trayIconMenu->addAction(toggleHideAction); trayIconMenu->addSeparator(); #endif if (enableWallet) { trayIconMenu->addAction(sendCoinsMenuAction); trayIconMenu->addAction(receiveCoinsMenuAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(signMessageAction); trayIconMenu->addAction(verifyMessageAction); trayIconMenu->addSeparator(); } trayIconMenu->addAction(optionsAction); trayIconMenu->addAction(openRPCConsoleAction); #ifndef Q_OS_MAC // This is built-in on macOS trayIconMenu->addSeparator(); trayIconMenu->addAction(quitAction); #endif } #ifndef Q_OS_MAC void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::Trigger) { // Click on system tray icon triggers show/hide of the main window toggleHidden(); } } #else void BitcoinGUI::macosDockIconActivated() { show(); activateWindow(); } #endif void BitcoinGUI::optionsClicked() { openOptionsDialogWithTab(OptionsDialog::TAB_MAIN); } void BitcoinGUI::aboutClicked() { if (!clientModel) { return; } HelpMessageDialog dlg(this, true); dlg.exec(); } void BitcoinGUI::showDebugWindow() { GUIUtil::bringToFront(rpcConsole); Q_EMIT consoleShown(rpcConsole); } void BitcoinGUI::showDebugWindowActivateConsole() { rpcConsole->setTabFocus(RPCConsole::TabTypes::CONSOLE); showDebugWindow(); } void BitcoinGUI::showHelpMessageClicked() { helpMessageDialog->show(); } #ifdef ENABLE_WALLET void BitcoinGUI::openClicked() { OpenURIDialog dlg(config->GetChainParams(), this); if (dlg.exec()) { Q_EMIT receivedURI(dlg.getURI()); } } void BitcoinGUI::gotoOverviewPage() { overviewAction->setChecked(true); if (walletFrame) { walletFrame->gotoOverviewPage(); } } void BitcoinGUI::gotoHistoryPage() { historyAction->setChecked(true); if (walletFrame) { walletFrame->gotoHistoryPage(); } } void BitcoinGUI::gotoReceiveCoinsPage() { receiveCoinsAction->setChecked(true); if (walletFrame) { walletFrame->gotoReceiveCoinsPage(); } } void BitcoinGUI::gotoSendCoinsPage(QString addr) { sendCoinsAction->setChecked(true); if (walletFrame) { walletFrame->gotoSendCoinsPage(addr); } } void BitcoinGUI::gotoSignMessageTab(QString addr) { if (walletFrame) { walletFrame->gotoSignMessageTab(addr); } } void BitcoinGUI::gotoVerifyMessageTab(QString addr) { if (walletFrame) { walletFrame->gotoVerifyMessageTab(addr); } } void BitcoinGUI::gotoLoadPSBT() { if (walletFrame) { walletFrame->gotoLoadPSBT(); } } #endif // ENABLE_WALLET void BitcoinGUI::updateNetworkState() { int count = clientModel->getNumConnections(); QString icon; switch (count) { case 0: icon = ":/icons/connect_0"; break; case 1: case 2: case 3: icon = ":/icons/connect_1"; break; case 4: case 5: case 6: icon = ":/icons/connect_2"; break; case 7: case 8: case 9: icon = ":/icons/connect_3"; break; default: icon = ":/icons/connect_4"; break; } QString tooltip; if (m_node.getNetworkActive()) { tooltip = tr("%n active connection(s) to Bitcoin network", "", count) + QString(".<br>") + tr("Click to disable network activity."); } else { tooltip = tr("Network activity disabled.") + QString("<br>") + tr("Click to enable network activity again."); icon = ":/icons/network_disabled"; } // Don't word-wrap this (fixed-width) tooltip tooltip = QString("<nobr>") + tooltip + QString("</nobr>"); connectionsControl->setToolTip(tooltip); connectionsControl->setPixmap(platformStyle->SingleColorIcon(icon).pixmap( STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); } void BitcoinGUI::setNumConnections(int count) { updateNetworkState(); } void BitcoinGUI::setNetworkActive(bool networkActive) { updateNetworkState(); } void BitcoinGUI::updateHeadersSyncProgressLabel() { int64_t headersTipTime = clientModel->getHeaderTipTime(); int headersTipHeight = clientModel->getHeaderTipHeight(); int estHeadersLeft = (GetTime() - headersTipTime) / config->GetChainParams().GetConsensus().nPowTargetSpacing; if (estHeadersLeft > HEADER_HEIGHT_DELTA_SYNC) { progressBarLabel->setText( tr("Syncing Headers (%1%)...") .arg(QString::number(100.0 / (headersTipHeight + estHeadersLeft) * headersTipHeight, 'f', 1))); } } void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab) { if (!clientModel || !clientModel->getOptionsModel()) { return; } OptionsDialog dlg(this, enableWallet); dlg.setCurrentTab(tab); dlg.setModel(clientModel->getOptionsModel()); dlg.exec(); } void BitcoinGUI::setNumBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state) { // Disabling macOS App Nap on initial sync, disk and reindex operations. #ifdef Q_OS_MAC if (sync_state == SynchronizationState::POST_INIT) { m_app_nap_inhibitor->enableAppNap(); } else { m_app_nap_inhibitor->disableAppNap(); } #endif if (modalOverlay) { if (header) { modalOverlay->setKnownBestHeight(count, blockDate); } else { modalOverlay->tipUpdate(count, blockDate, nVerificationProgress); } } if (!clientModel) { return; } // Prevent orphan statusbar messages (e.g. hover Quit in main menu, wait // until chain-sync starts -> garbled text) statusBar()->clearMessage(); // Acquire current block source enum BlockSource blockSource = clientModel->getBlockSource(); switch (blockSource) { case BlockSource::NETWORK: if (header) { updateHeadersSyncProgressLabel(); return; } progressBarLabel->setText(tr("Synchronizing with network...")); updateHeadersSyncProgressLabel(); break; case BlockSource::DISK: if (header) { progressBarLabel->setText(tr("Indexing blocks on disk...")); } else { progressBarLabel->setText(tr("Processing blocks on disk...")); } break; case BlockSource::REINDEX: progressBarLabel->setText(tr("Reindexing blocks on disk...")); break; case BlockSource::NONE: if (header) { return; } progressBarLabel->setText(tr("Connecting to peers...")); break; } QString tooltip; QDateTime currentDate = QDateTime::currentDateTime(); qint64 secs = blockDate.secsTo(currentDate); tooltip = tr("Processed %n block(s) of transaction history.", "", count); // Set icon state: spinning if catching up, tick otherwise if (secs < MAX_BLOCK_TIME_GAP) { tooltip = tr("Up to date") + QString(".<br>") + tooltip; labelBlocksIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/synced") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->showOutOfSyncWarning(false); modalOverlay->showHide(true, true); } #endif // ENABLE_WALLET progressBarLabel->setVisible(false); progressBar->setVisible(false); } else { QString timeBehindText = GUIUtil::formatNiceTimeOffset(secs); progressBarLabel->setVisible(true); progressBar->setFormat(tr("%1 behind").arg(timeBehindText)); progressBar->setMaximum(1000000000); progressBar->setValue(nVerificationProgress * 1000000000.0 + 0.5); progressBar->setVisible(true); tooltip = tr("Catching up...") + QString("<br>") + tooltip; if (count != prevBlocks) { labelBlocksIcon->setPixmap( platformStyle ->SingleColorIcon(QString(":/animation/spinner-%1") .arg(spinnerFrame, 3, 10, QChar('0'))) .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES; } prevBlocks = count; #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->showOutOfSyncWarning(true); modalOverlay->showHide(); } #endif // ENABLE_WALLET tooltip += QString("<br>"); tooltip += tr("Last received block was generated %1 ago.").arg(timeBehindText); tooltip += QString("<br>"); tooltip += tr("Transactions after this will not yet be visible."); } // Don't word-wrap this (fixed-width) tooltip tooltip = QString("<nobr>") + tooltip + QString("</nobr>"); labelBlocksIcon->setToolTip(tooltip); progressBarLabel->setToolTip(tooltip); progressBar->setToolTip(tooltip); } void BitcoinGUI::message(const QString &title, QString message, unsigned int style, bool *ret, const QString &detailed_message) { // Default title. On macOS, the window title is ignored (as required by the // macOS Guidelines). QString strTitle{PACKAGE_NAME}; // Default to information icon int nMBoxIcon = QMessageBox::Information; int nNotifyIcon = Notificator::Information; QString msgType; if (!title.isEmpty()) { msgType = title; } else { switch (style) { case CClientUIInterface::MSG_ERROR: msgType = tr("Error"); message = tr("Error: %1").arg(message); break; case CClientUIInterface::MSG_WARNING: msgType = tr("Warning"); message = tr("Warning: %1").arg(message); break; case CClientUIInterface::MSG_INFORMATION: msgType = tr("Information"); // No need to prepend the prefix here. break; default: break; } } if (!msgType.isEmpty()) { strTitle += " - " + msgType; } if (style & CClientUIInterface::ICON_ERROR) { nMBoxIcon = QMessageBox::Critical; nNotifyIcon = Notificator::Critical; } else if (style & CClientUIInterface::ICON_WARNING) { nMBoxIcon = QMessageBox::Warning; nNotifyIcon = Notificator::Warning; } if (style & CClientUIInterface::MODAL) { // Check for buttons, use OK as default, if none was supplied QMessageBox::StandardButton buttons; if (!(buttons = (QMessageBox::StandardButton)( style & CClientUIInterface::BTN_MASK))) { buttons = QMessageBox::Ok; } showNormalIfMinimized(); QMessageBox mBox(static_cast<QMessageBox::Icon>(nMBoxIcon), strTitle, message, buttons, this); mBox.setTextFormat(Qt::PlainText); mBox.setDetailedText(detailed_message); int r = mBox.exec(); if (ret != nullptr) { *ret = r == QMessageBox::Ok; } } else { notificator->notify(static_cast<Notificator::Class>(nNotifyIcon), strTitle, message); } } void BitcoinGUI::changeEvent(QEvent *e) { QMainWindow::changeEvent(e); #ifndef Q_OS_MAC // Ignored on Mac if (e->type() == QEvent::WindowStateChange) { if (clientModel && clientModel->getOptionsModel() && clientModel->getOptionsModel()->getMinimizeToTray()) { QWindowStateChangeEvent *wsevt = static_cast<QWindowStateChangeEvent *>(e); if (!(wsevt->oldState() & Qt::WindowMinimized) && isMinimized()) { QTimer::singleShot(0, this, &BitcoinGUI::hide); e->ignore(); } else if ((wsevt->oldState() & Qt::WindowMinimized) && !isMinimized()) { QTimer::singleShot(0, this, &BitcoinGUI::show); e->ignore(); } } } #endif } void BitcoinGUI::closeEvent(QCloseEvent *event) { #ifndef Q_OS_MAC // Ignored on Mac if (clientModel && clientModel->getOptionsModel()) { if (!clientModel->getOptionsModel()->getMinimizeOnClose()) { // close rpcConsole in case it was open to make some space for the // shutdown window rpcConsole->close(); QApplication::quit(); } else { QMainWindow::showMinimized(); event->ignore(); } } #else QMainWindow::closeEvent(event); #endif } void BitcoinGUI::showEvent(QShowEvent *event) { // enable the debug window when the main window shows up openRPCConsoleAction->setEnabled(true); aboutAction->setEnabled(true); optionsAction->setEnabled(true); } #ifdef ENABLE_WALLET void BitcoinGUI::incomingTransaction(const QString &date, int unit, const Amount amount, const QString &type, const QString &address, const QString &label, const QString &walletName) { // On new transaction, make an info balloon QString msg = tr("Date: %1\n").arg(date) + tr("Amount: %1\n") .arg(BitcoinUnits::formatWithUnit(unit, amount, true)); if (m_node.walletClient().getWallets().size() > 1 && !walletName.isEmpty()) { msg += tr("Wallet: %1\n").arg(walletName); } msg += tr("Type: %1\n").arg(type); if (!label.isEmpty()) { msg += tr("Label: %1\n").arg(label); } else if (!address.isEmpty()) { msg += tr("Address: %1\n").arg(address); } message(amount < Amount::zero() ? tr("Sent transaction") : tr("Incoming transaction"), msg, CClientUIInterface::MSG_INFORMATION); } #endif // ENABLE_WALLET void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event) { // Accept only URIs if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } } void BitcoinGUI::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls()) { for (const QUrl &uri : event->mimeData()->urls()) { Q_EMIT receivedURI(uri.toString()); } } event->acceptProposedAction(); } bool BitcoinGUI::eventFilter(QObject *object, QEvent *event) { // Catch status tip events if (event->type() == QEvent::StatusTip) { // Prevent adding text from setStatusTip(), if we currently use the // status bar for displaying other stuff if (progressBarLabel->isVisible() || progressBar->isVisible()) { return true; } } return QMainWindow::eventFilter(object, event); } #ifdef ENABLE_WALLET bool BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient &recipient) { // URI has to be valid if (walletFrame && walletFrame->handlePaymentRequest(recipient)) { showNormalIfMinimized(); gotoSendCoinsPage(); return true; } return false; } void BitcoinGUI::setHDStatus(bool privkeyDisabled, int hdEnabled) { labelWalletHDStatusIcon->setPixmap( platformStyle - ->SingleColorIcon(privkeyDisabled - ? ":/icons/eye" - : hdEnabled ? ":/icons/hd_enabled" + ->SingleColorIcon(privkeyDisabled ? ":/icons/eye" + : hdEnabled ? ":/icons/hd_enabled" : ":/icons/hd_disabled") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletHDStatusIcon->setToolTip( - privkeyDisabled - ? tr("Private key <b>disabled</b>") - : hdEnabled ? tr("HD key generation is <b>enabled</b>") + privkeyDisabled ? tr("Private key <b>disabled</b>") + : hdEnabled ? tr("HD key generation is <b>enabled</b>") : tr("HD key generation is <b>disabled</b>")); labelWalletHDStatusIcon->show(); // eventually disable the QLabel to set its opacity to 50% labelWalletHDStatusIcon->setEnabled(hdEnabled); } void BitcoinGUI::setEncryptionStatus(int status) { switch (status) { case WalletModel::Unencrypted: labelWalletEncryptionIcon->hide(); encryptWalletAction->setChecked(false); changePassphraseAction->setEnabled(false); encryptWalletAction->setEnabled(true); break; case WalletModel::Unlocked: labelWalletEncryptionIcon->show(); labelWalletEncryptionIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/lock_open") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletEncryptionIcon->setToolTip( tr("Wallet is <b>encrypted</b> and currently <b>unlocked</b>")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled(false); break; case WalletModel::Locked: labelWalletEncryptionIcon->show(); labelWalletEncryptionIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/lock_closed") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletEncryptionIcon->setToolTip( tr("Wallet is <b>encrypted</b> and currently <b>locked</b>")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled(false); break; } } void BitcoinGUI::updateWalletStatus() { if (!walletFrame) { return; } WalletView *const walletView = walletFrame->currentWalletView(); if (!walletView) { return; } WalletModel *const walletModel = walletView->getWalletModel(); setEncryptionStatus(walletModel->getEncryptionStatus()); setHDStatus(walletModel->wallet().privateKeysDisabled(), walletModel->wallet().hdEnabled()); } #endif // ENABLE_WALLET void BitcoinGUI::updateProxyIcon() { std::string ip_port; bool proxy_enabled = clientModel->getProxyInfo(ip_port); if (proxy_enabled) { if (!labelProxyIcon->hasPixmap()) { QString ip_port_q = QString::fromStdString(ip_port); labelProxyIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/proxy") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelProxyIcon->setToolTip( tr("Proxy is <b>enabled</b>: %1").arg(ip_port_q)); } else { labelProxyIcon->show(); } } else { labelProxyIcon->hide(); } } void BitcoinGUI::updateWindowTitle() { QString window_title = PACKAGE_NAME; #ifdef ENABLE_WALLET if (walletFrame) { WalletModel *const wallet_model = walletFrame->currentWalletModel(); if (wallet_model && !wallet_model->getWalletName().isEmpty()) { window_title += " - " + wallet_model->getDisplayName(); } } #endif if (!m_network_style->getTitleAddText().isEmpty()) { window_title += " - " + m_network_style->getTitleAddText(); } setWindowTitle(window_title); } void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden) { if (!clientModel) { return; } if (!isHidden() && !isMinimized() && !GUIUtil::isObscured(this) && fToggleHidden) { hide(); } else { GUIUtil::bringToFront(this); } } void BitcoinGUI::toggleHidden() { showNormalIfMinimized(true); } void BitcoinGUI::detectShutdown() { if (m_node.shutdownRequested()) { if (rpcConsole) { rpcConsole->hide(); } qApp->quit(); } } void BitcoinGUI::showProgress(const QString &title, int nProgress) { if (nProgress == 0) { progressDialog = new QProgressDialog(title, QString(), 0, 100); GUIUtil::PolishProgressDialog(progressDialog); progressDialog->setWindowModality(Qt::ApplicationModal); progressDialog->setMinimumDuration(0); progressDialog->setAutoClose(false); progressDialog->setValue(0); } else if (nProgress == 100) { if (progressDialog) { progressDialog->close(); progressDialog->deleteLater(); progressDialog = nullptr; } } else if (progressDialog) { progressDialog->setValue(nProgress); } } void BitcoinGUI::setTrayIconVisible(bool fHideTrayIcon) { if (trayIcon) { trayIcon->setVisible(!fHideTrayIcon); } } void BitcoinGUI::showModalOverlay() { if (modalOverlay && (progressBar->isVisible() || modalOverlay->isLayerVisible())) { modalOverlay->toggleVisibility(); } } static bool ThreadSafeMessageBox(BitcoinGUI *gui, const bilingual_str &message, const std::string &caption, unsigned int style) { bool modal = (style & CClientUIInterface::MODAL); // The SECURE flag has no effect in the Qt GUI. // bool secure = (style & CClientUIInterface::SECURE); style &= ~CClientUIInterface::SECURE; bool ret = false; // This is original message, in English, for googling and referencing. QString detailed_message; if (message.original != message.translated) { detailed_message = BitcoinGUI::tr("Original message:") + "\n" + QString::fromStdString(message.original); } // In case of modal message, use blocking connection to wait for user to // click a button bool invoked = QMetaObject::invokeMethod( gui, "message", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(caption)), Q_ARG(QString, QString::fromStdString(message.translated)), Q_ARG(unsigned int, style), Q_ARG(bool *, &ret), Q_ARG(QString, detailed_message)); assert(invoked); return ret; } void BitcoinGUI::subscribeToCoreSignals() { // Connect signals to client m_handler_message_box = m_node.handleMessageBox( std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); m_handler_question = m_node.handleQuestion( std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_3, std::placeholders::_4)); } void BitcoinGUI::unsubscribeFromCoreSignals() { // Disconnect signals from client m_handler_message_box->disconnect(); m_handler_question->disconnect(); } bool BitcoinGUI::isPrivacyModeActivated() const { assert(m_mask_values_action); return m_mask_values_action->isChecked(); } UnitDisplayStatusBarControl::UnitDisplayStatusBarControl( const PlatformStyle *platformStyle) : optionsModel(nullptr), menu(nullptr) { createContextMenu(); setToolTip(tr("Unit to show amounts in. Click to select another unit.")); QList<BitcoinUnits::Unit> units = BitcoinUnits::availableUnits(); int max_width = 0; const QFontMetrics fm(font()); for (const BitcoinUnits::Unit unit : units) { max_width = qMax(max_width, GUIUtil::TextWidth(fm, BitcoinUnits::longName(unit))); } setMinimumSize(max_width, 0); setAlignment(Qt::AlignRight | Qt::AlignVCenter); setStyleSheet(QString("QLabel { color : %1 }") .arg(platformStyle->SingleColor().name())); } /** So that it responds to button clicks */ void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event) { onDisplayUnitsClicked(event->pos()); } /** Creates context menu, its actions, and wires up all the relevant signals for * mouse events. */ void UnitDisplayStatusBarControl::createContextMenu() { menu = new QMenu(this); for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { QAction *menuAction = new QAction(QString(BitcoinUnits::longName(u)), this); menuAction->setData(QVariant(u)); menu->addAction(menuAction); } connect(menu, &QMenu::triggered, this, &UnitDisplayStatusBarControl::onMenuSelection); } /** Lets the control know about the Options Model (and its signals) */ void UnitDisplayStatusBarControl::setOptionsModel(OptionsModel *_optionsModel) { if (_optionsModel) { this->optionsModel = _optionsModel; // be aware of a display unit change reported by the OptionsModel // object. connect(_optionsModel, &OptionsModel::displayUnitChanged, this, &UnitDisplayStatusBarControl::updateDisplayUnit); // initialize the display units label with the current value in the // model. updateDisplayUnit(_optionsModel->getDisplayUnit()); } } /** When Display Units are changed on OptionsModel it will refresh the display * text of the control on the status bar */ void UnitDisplayStatusBarControl::updateDisplayUnit(int newUnits) { setText(BitcoinUnits::longName(newUnits)); } /** Shows context menu with Display Unit options by the mouse coordinates */ void UnitDisplayStatusBarControl::onDisplayUnitsClicked(const QPoint &point) { QPoint globalPos = mapToGlobal(point); menu->exec(globalPos); } /** Tells underlying optionsModel to update its current display unit. */ void UnitDisplayStatusBarControl::onMenuSelection(QAction *action) { if (action) { optionsModel->setDisplayUnit(action->data()); } } diff --git a/src/rcu.h b/src/rcu.h index cd375aa8e..5a67fcae4 100644 --- a/src/rcu.h +++ b/src/rcu.h @@ -1,234 +1,234 @@ // Copyright (c) 2018-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_RCU_H #define BITCOIN_RCU_H #include <atomic> #include <cassert> #include <cstdint> #include <functional> #include <map> #include <ostream> #include <type_traits> #include <utility> class RCUInfos; class RCUReadLock; class RCUInfos { std::atomic<uint64_t> state; std::atomic<RCUInfos *> next; std::map<uint64_t, std::function<void()>> cleanups; // The largest revision possible means unlocked. static const uint64_t UNLOCKED = -uint64_t(1); RCUInfos(); ~RCUInfos(); void readLock() { assert(!isLocked()); state.store(revision.load()); } void readFree() { assert(isLocked()); state.store(UNLOCKED); } bool isLocked() const { return state.load() != UNLOCKED; } void registerCleanup(const std::function<void()> &f) { cleanups.emplace(++revision, f); } void synchronize(); void runCleanups(); uint64_t hasSyncedTo(uint64_t cutoff = UNLOCKED); friend class RCULock; friend struct RCUTest; static std::atomic<uint64_t> revision; static thread_local RCUInfos infos; }; class RCULock { RCUInfos *infos; explicit RCULock(RCUInfos *infosIn) : infos(infosIn) { infos->readLock(); } friend class RCUInfos; public: RCULock() : RCULock(&RCUInfos::infos) {} ~RCULock() { infos->readFree(); } RCULock(const RCULock &) = delete; RCULock &operator=(const RCULock &) = delete; static bool isLocked() { return RCUInfos::infos.isLocked(); } static void registerCleanup(const std::function<void()> &f) { RCUInfos::infos.registerCleanup(f); } static void synchronize() { RCUInfos::infos.synchronize(); } }; template <typename T> class RCUPtr { T *ptr; // Private construction, so factories have to be used. explicit RCUPtr(T *ptrIn) : ptr(ptrIn) {} public: RCUPtr() : ptr(nullptr) {} ~RCUPtr() { if (ptr != nullptr) { ptr->release(); } } /** * Acquire ownership of some pointer. */ static RCUPtr acquire(T *&ptrIn) { RCUPtr ret(ptrIn); ptrIn = nullptr; return ret; } /** * Construct a new object that is owned by the pointer. */ - template <typename... Args> static RCUPtr make(Args &&... args) { + template <typename... Args> static RCUPtr make(Args &&...args) { return RCUPtr(new T(std::forward<Args>(args)...)); } /** * Construct a new RCUPtr without transferring owership. */ static RCUPtr copy(T *ptr) { if (ptr != nullptr) { ptr->acquire(); } return RCUPtr::acquire(ptr); } /** * Copy semantic. */ RCUPtr(const RCUPtr &src) : ptr(src.ptr) { if (ptr != nullptr) { ptr->acquire(); } } RCUPtr &operator=(const RCUPtr &rhs) { RCUPtr tmp(rhs); std::swap(ptr, tmp.ptr); return *this; } /** * Move semantic. */ RCUPtr(RCUPtr &&src) : RCUPtr() { std::swap(ptr, src.ptr); } RCUPtr &operator=(RCUPtr &&rhs) { std::swap(ptr, rhs.ptr); return *this; } /** * Get allows to access the undelying pointer. RCUPtr keeps ownership. */ T *get() { return ptr; } const T *get() const { return ptr; } /** * Release transfers ownership of the pointer from RCUPtr to the caller. */ T *release() { T *oldPtr = ptr; ptr = nullptr; return oldPtr; } /** * Operator overloading for convenience. */ T *operator->() { return ptr; } const T *operator->() const { return ptr; } T &operator*() { return *ptr; } const T &operator*() const { return *ptr; } explicit operator bool() const { return ptr != nullptr; } /** * Equality checks. */ friend bool operator==(const RCUPtr &lhs, const T *rhs) { return lhs.get() == rhs; } friend bool operator==(const RCUPtr &lhs, const RCUPtr &rhs) { return lhs == rhs.get(); } friend bool operator!=(const RCUPtr &lhs, const T *rhs) { return !(lhs == rhs); } friend bool operator!=(const RCUPtr &lhs, const RCUPtr &rhs) { return !(lhs == rhs); } /** * ostream support. */ friend std::ostream &operator<<(std::ostream &stream, const RCUPtr &rhs) { return stream << rhs.ptr; } }; #define IMPLEMENT_RCU_REFCOUNT(T) \ private: \ mutable std::atomic<T> refcount{0}; \ \ void acquire() const { refcount++; } \ \ bool tryDecrement() const { \ T count = refcount.load(); \ while (count > 0) { \ if (refcount.compare_exchange_weak(count, count - 1)) { \ return true; \ } \ } \ \ return false; \ } \ \ void release() const { \ if (tryDecrement()) { \ return; \ } \ \ RCULock::registerCleanup([this] { \ if (tryDecrement()) { \ return; \ } \ \ delete this; \ }); \ } \ \ static_assert(std::is_integral<T>::value, "T must be an integral type."); \ static_assert(std::is_unsigned<T>::value, "T must be unsigned."); \ \ template <typename> friend class ::RCUPtr #endif // BITCOIN_RCU_H diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index f3e2a3990..dbb9715c9 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1,3105 +1,3105 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <rpc/blockchain.h> #include <amount.h> #include <blockdb.h> #include <blockfilter.h> #include <chain.h> #include <chainparams.h> #include <checkpoints.h> #include <coins.h> #include <config.h> #include <consensus/validation.h> #include <core_io.h> #include <hash.h> #include <index/blockfilterindex.h> #include <network.h> #include <node/coinstats.h> #include <node/context.h> #include <node/utxo_snapshot.h> #include <policy/policy.h> #include <primitives/transaction.h> #include <rpc/server.h> #include <rpc/util.h> #include <script/descriptor.h> #include <streams.h> #include <txdb.h> #include <txmempool.h> #include <undo.h> #include <util/ref.h> #include <util/strencodings.h> #include <util/system.h> #include <util/translation.h> #include <validation.h> #include <validationinterface.h> #include <versionbitsinfo.h> // For VersionBitsDeploymentInfo #include <warnings.h> #include <condition_variable> #include <cstdint> #include <memory> #include <mutex> struct CUpdatedBlock { BlockHash hash; int height; }; static Mutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); NodeContext &EnsureNodeContext(const util::Ref &context) { if (!context.Has<NodeContext>()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found"); } return context.Get<NodeContext>(); } CTxMemPool &EnsureMemPool(const util::Ref &context) { const NodeContext &node = EnsureNodeContext(context); if (!node.mempool) { throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED, "Mempool disabled or instance not found"); } return *node.mempool; } ChainstateManager &EnsureChainman(const util::Ref &context) { const NodeContext &node = EnsureNodeContext(context); if (!node.chainman) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found"); } return *node.chainman; } /** * Calculate the difficulty for a given block index. */ double GetDifficulty(const CBlockIndex *blockindex) { CHECK_NONFATAL(blockindex); int nShift = (blockindex->nBits >> 24) & 0xff; double dDiff = double(0x0000ffff) / double(blockindex->nBits & 0x00ffffff); while (nShift < 29) { dDiff *= 256.0; nShift++; } while (nShift > 29) { dDiff /= 256.0; nShift--; } return dDiff; } static int ComputeNextBlockAndDepth(const CBlockIndex *tip, const CBlockIndex *blockindex, const CBlockIndex *&next) { next = tip->GetAncestor(blockindex->nHeight + 1); if (next && next->pprev == blockindex) { return tip->nHeight - blockindex->nHeight + 1; } next = nullptr; return blockindex == tip ? 1 : -1; } UniValue blockheaderToJSON(const CBlockIndex *tip, const CBlockIndex *blockindex) { // Serialize passed information without accessing chain state of the active // chain! // For performance reasons AssertLockNotHeld(cs_main); UniValue result(UniValue::VOBJ); result.pushKV("hash", blockindex->GetBlockHash().GetHex()); const CBlockIndex *pnext; int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext); result.pushKV("confirmations", confirmations); result.pushKV("height", blockindex->nHeight); result.pushKV("version", blockindex->nVersion); result.pushKV("versionHex", strprintf("%08x", blockindex->nVersion)); result.pushKV("merkleroot", blockindex->hashMerkleRoot.GetHex()); result.pushKV("time", int64_t(blockindex->nTime)); result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast())); result.pushKV("nonce", uint64_t(blockindex->nNonce)); result.pushKV("bits", strprintf("%08x", blockindex->nBits)); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex->nChainWork.GetHex()); result.pushKV("nTx", uint64_t(blockindex->nTx)); if (blockindex->pprev) { result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); } if (pnext) { result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex()); } return result; } UniValue blockToJSON(const CBlock &block, const CBlockIndex *tip, const CBlockIndex *blockindex, bool txDetails) { // Serialize passed information without accessing chain state of the active // chain! // For performance reasons AssertLockNotHeld(cs_main); UniValue result(UniValue::VOBJ); result.pushKV("hash", blockindex->GetBlockHash().GetHex()); const CBlockIndex *pnext; int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext); result.pushKV("confirmations", confirmations); result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION)); result.pushKV("height", blockindex->nHeight); result.pushKV("version", block.nVersion); result.pushKV("versionHex", strprintf("%08x", block.nVersion)); result.pushKV("merkleroot", block.hashMerkleRoot.GetHex()); UniValue txs(UniValue::VARR); for (const auto &tx : block.vtx) { if (txDetails) { UniValue objTx(UniValue::VOBJ); TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags()); txs.push_back(objTx); } else { txs.push_back(tx->GetId().GetHex()); } } result.pushKV("tx", txs); result.pushKV("time", block.GetBlockTime()); result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast())); result.pushKV("nonce", uint64_t(block.nNonce)); result.pushKV("bits", strprintf("%08x", block.nBits)); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex->nChainWork.GetHex()); result.pushKV("nTx", uint64_t(blockindex->nTx)); if (blockindex->pprev) { result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); } if (pnext) { result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex()); } return result; } static RPCHelpMan getblockcount() { return RPCHelpMan{ "getblockcount", "Returns the height of the most-work fully-validated chain.\n" "The genesis block has height 0.\n", {}, RPCResult{RPCResult::Type::NUM, "", "The current block count"}, RPCExamples{HelpExampleCli("getblockcount", "") + HelpExampleRpc("getblockcount", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); return ::ChainActive().Height(); }, }; } static RPCHelpMan getbestblockhash() { return RPCHelpMan{ "getbestblockhash", "Returns the hash of the best (tip) block in the " "most-work fully-validated chain.\n", {}, RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"}, RPCExamples{HelpExampleCli("getbestblockhash", "") + HelpExampleRpc("getbestblockhash", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); return ::ChainActive().Tip()->GetBlockHash().GetHex(); }, }; } RPCHelpMan getfinalizedblockhash() { return RPCHelpMan{ "getfinalizedblockhash", "Returns the hash of the currently finalized block\n", {}, RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"}, RPCExamples{HelpExampleCli("getfinalizedblockhash", "") + HelpExampleRpc("getfinalizedblockhash", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); const CBlockIndex *blockIndexFinalized = ::ChainstateActive().GetFinalizedBlock(); if (blockIndexFinalized) { return blockIndexFinalized->GetBlockHash().GetHex(); } return UniValue(UniValue::VSTR); }, }; } void RPCNotifyBlockChange(const CBlockIndex *pindex) { if (pindex) { LOCK(cs_blockchange); latestblock.hash = pindex->GetBlockHash(); latestblock.height = pindex->nHeight; } cond_blockchange.notify_all(); } static RPCHelpMan waitfornewblock() { return RPCHelpMan{ "waitfornewblock", "Waits for a specific new block and returns useful info about it.\n" "\nReturns the current block on timeout or exit.\n", { {"timeout", RPCArg::Type::NUM, /* default */ "0", "Time in milliseconds to wait for a response. 0 indicates no " "timeout."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "The blockhash"}, {RPCResult::Type::NUM, "height", "Block height"}, }}, RPCExamples{HelpExampleCli("waitfornewblock", "1000") + HelpExampleRpc("waitfornewblock", "1000")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { int timeout = 0; if (!request.params[0].isNull()) { timeout = request.params[0].get_int(); } CUpdatedBlock block; { WAIT_LOCK(cs_blockchange, lock); block = latestblock; if (timeout) { cond_blockchange.wait_for( lock, std::chrono::milliseconds(timeout), [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); }); } else { cond_blockchange.wait( lock, [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); }); } block = latestblock; } UniValue ret(UniValue::VOBJ); ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); return ret; }, }; } static RPCHelpMan waitforblock() { return RPCHelpMan{ "waitforblock", "Waits for a specific new block and returns useful info about it.\n" "\nReturns the current block on timeout or exit.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Block hash to wait for."}, {"timeout", RPCArg::Type::NUM, /* default */ "0", "Time in milliseconds to wait for a response. 0 indicates no " "timeout."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "The blockhash"}, {RPCResult::Type::NUM, "height", "Block height"}, }}, RPCExamples{HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9" "ed7b4a8c619eb02596f8862\" 1000") + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9" "ed7b4a8c619eb02596f8862\", 1000")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { int timeout = 0; BlockHash hash(ParseHashV(request.params[0], "blockhash")); if (!request.params[1].isNull()) { timeout = request.params[1].get_int(); } CUpdatedBlock block; { WAIT_LOCK(cs_blockchange, lock); if (timeout) { cond_blockchange.wait_for( lock, std::chrono::milliseconds(timeout), [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { return latestblock.hash == hash || !IsRPCRunning(); }); } else { cond_blockchange.wait( lock, [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { return latestblock.hash == hash || !IsRPCRunning(); }); } block = latestblock; } UniValue ret(UniValue::VOBJ); ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); return ret; }, }; } static RPCHelpMan waitforblockheight() { return RPCHelpMan{ "waitforblockheight", "Waits for (at least) block height and returns the height and " "hash\nof the current tip.\n" "\nReturns the current block on timeout or exit.\n", { {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "Block height to wait for."}, {"timeout", RPCArg::Type::NUM, /* default */ "0", "Time in milliseconds to wait for a response. 0 indicates no " "timeout."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "The blockhash"}, {RPCResult::Type::NUM, "height", "Block height"}, }}, RPCExamples{HelpExampleCli("waitforblockheight", "100 1000") + HelpExampleRpc("waitforblockheight", "100, 1000")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { int timeout = 0; int height = request.params[0].get_int(); if (!request.params[1].isNull()) { timeout = request.params[1].get_int(); } CUpdatedBlock block; { WAIT_LOCK(cs_blockchange, lock); if (timeout) { cond_blockchange.wait_for( lock, std::chrono::milliseconds(timeout), [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { return latestblock.height >= height || !IsRPCRunning(); }); } else { cond_blockchange.wait( lock, [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { return latestblock.height >= height || !IsRPCRunning(); }); } block = latestblock; } UniValue ret(UniValue::VOBJ); ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); return ret; }, }; } static RPCHelpMan syncwithvalidationinterfacequeue() { return RPCHelpMan{ "syncwithvalidationinterfacequeue", "Waits for the validation interface queue to catch up on everything " "that was there when we entered this function.\n", {}, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("syncwithvalidationinterfacequeue", "") + HelpExampleRpc("syncwithvalidationinterfacequeue", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { SyncWithValidationInterfaceQueue(); return NullUniValue; }, }; } static RPCHelpMan getdifficulty() { return RPCHelpMan{ "getdifficulty", "Returns the proof-of-work difficulty as a multiple of the minimum " "difficulty.\n", {}, RPCResult{RPCResult::Type::NUM, "", "the proof-of-work difficulty as a multiple of the minimum " "difficulty."}, RPCExamples{HelpExampleCli("getdifficulty", "") + HelpExampleRpc("getdifficulty", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); return GetDifficulty(::ChainActive().Tip()); }, }; } static std::vector<RPCResult> MempoolEntryDescription() { const auto &ticker = Currency::get().ticker; return { RPCResult{RPCResult::Type::NUM, "size", "transaction size."}, RPCResult{RPCResult::Type::STR_AMOUNT, "fee", "transaction fee in " + ticker + " (DEPRECATED)"}, RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", "transaction fee with fee deltas used for mining priority " "(DEPRECATED)"}, RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan " "1970 GMT"}, RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"}, RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including " "this one)"}, RPCResult{RPCResult::Type::NUM, "descendantsize", "transaction size of in-mempool descendants " "(including this one)"}, RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", "modified fees (see above) of in-mempool descendants " "(including this one) (DEPRECATED)"}, RPCResult{ RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"}, RPCResult{ RPCResult::Type::NUM, "ancestorsize", "transaction size of in-mempool ancestors (including this one)"}, RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", "modified fees (see above) of in-mempool ancestors " "(including this one) (DEPRECATED)"}, RPCResult{RPCResult::Type::OBJ, "fees", "", { RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + ticker}, RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for " "mining priority in " + ticker}, RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "modified fees (see above) of in-mempool " "ancestors (including this one) in " + ticker}, RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "modified fees (see above) of in-mempool " "descendants (including this one) in " + ticker}, }}, RPCResult{ RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction", {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}}, RPCResult{ RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction", {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}}, RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial " "broadcast not yet acknowledged by any peers)"}, }; } static void entryToJSON(const CTxMemPool &pool, UniValue &info, const CTxMemPoolEntry &e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) { AssertLockHeld(pool.cs); UniValue fees(UniValue::VOBJ); fees.pushKV("base", e.GetFee()); fees.pushKV("modified", e.GetModifiedFee()); fees.pushKV("ancestor", e.GetModFeesWithAncestors()); fees.pushKV("descendant", e.GetModFeesWithDescendants()); info.pushKV("fees", fees); info.pushKV("size", (int)e.GetTxSize()); info.pushKV("fee", e.GetFee()); info.pushKV("modifiedfee", e.GetModifiedFee()); info.pushKV("time", count_seconds(e.GetTime())); info.pushKV("height", (int)e.GetHeight()); info.pushKV("descendantcount", e.GetCountWithDescendants()); info.pushKV("descendantsize", e.GetSizeWithDescendants()); info.pushKV("descendantfees", e.GetModFeesWithDescendants() / SATOSHI); info.pushKV("ancestorcount", e.GetCountWithAncestors()); info.pushKV("ancestorsize", e.GetSizeWithAncestors()); info.pushKV("ancestorfees", e.GetModFeesWithAncestors() / SATOSHI); const CTransaction &tx = e.GetTx(); std::set<std::string> setDepends; for (const CTxIn &txin : tx.vin) { if (pool.exists(txin.prevout.GetTxId())) { setDepends.insert(txin.prevout.GetTxId().ToString()); } } UniValue depends(UniValue::VARR); for (const std::string &dep : setDepends) { depends.push_back(dep); } info.pushKV("depends", depends); UniValue spent(UniValue::VARR); const CTxMemPool::txiter &it = pool.mapTx.find(tx.GetId()); const CTxMemPoolEntry::Children &children = it->GetMemPoolChildrenConst(); for (const CTxMemPoolEntry &child : children) { spent.push_back(child.GetTx().GetId().ToString()); } info.pushKV("spentby", spent); info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetId())); } UniValue MempoolToJSON(const CTxMemPool &pool, bool verbose, bool include_mempool_sequence) { if (verbose) { if (include_mempool_sequence) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values."); } LOCK(pool.cs); UniValue o(UniValue::VOBJ); for (const CTxMemPoolEntry &e : pool.mapTx) { const uint256 &txid = e.GetTx().GetId(); UniValue info(UniValue::VOBJ); entryToJSON(pool, info, e); // Mempool has unique entries so there is no advantage in using // UniValue::pushKV, which checks if the key already exists in O(N). // UniValue::__pushKV is used instead which currently is O(1). o.__pushKV(txid.ToString(), info); } return o; } else { uint64_t mempool_sequence; std::vector<uint256> vtxids; { LOCK(pool.cs); pool.queryHashes(vtxids); mempool_sequence = pool.GetSequence(); } UniValue a(UniValue::VARR); for (const uint256 &txid : vtxids) { a.push_back(txid.ToString()); } if (!include_mempool_sequence) { return a; } else { UniValue o(UniValue::VOBJ); o.pushKV("txids", a); o.pushKV("mempool_sequence", mempool_sequence); return o; } } } static RPCHelpMan getrawmempool() { return RPCHelpMan{ "getrawmempool", "Returns all transaction ids in memory pool as a json array of " "string transaction ids.\n" "\nHint: use getmempoolentry to fetch a specific transaction from the " "mempool.\n", { {"verbose", RPCArg::Type::BOOL, /* default */ "false", "True for a json object, false for array of transaction ids"}, {"mempool_sequence", RPCArg::Type::BOOL, /* default */ "false", "If verbose=false, returns a json object with transaction list " "and mempool sequence number attached."}, }, { RPCResult{"for verbose = false", RPCResult::Type::ARR, "", "", { {RPCResult::Type::STR_HEX, "", "The transaction id"}, }}, RPCResult{"for verbose = true", RPCResult::Type::OBJ_DYN, "", "", { {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, }}, RPCResult{ "for verbose = false and mempool_sequence = true", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::ARR, "txids", "", { {RPCResult::Type::STR_HEX, "", "The transaction id"}, }}, {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."}, }}, }, RPCExamples{HelpExampleCli("getrawmempool", "true") + HelpExampleRpc("getrawmempool", "true")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { bool fVerbose = false; if (!request.params[0].isNull()) { fVerbose = request.params[0].get_bool(); } bool include_mempool_sequence = false; if (!request.params[1].isNull()) { include_mempool_sequence = request.params[1].get_bool(); } return MempoolToJSON(EnsureMemPool(request.context), fVerbose, include_mempool_sequence); }, }; } static RPCHelpMan getmempoolancestors() { return RPCHelpMan{ "getmempoolancestors", "If txid is in the mempool, returns all in-mempool ancestors.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, {"verbose", RPCArg::Type::BOOL, /* default */ "false", "True for a json object, false for array of transaction ids"}, }, { RPCResult{ "for verbose = false", RPCResult::Type::ARR, "", "", {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}}, RPCResult{"for verbose = true", RPCResult::Type::OBJ_DYN, "", "", { {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, }}, }, RPCExamples{HelpExampleCli("getmempoolancestors", "\"mytxid\"") + HelpExampleRpc("getmempoolancestors", "\"mytxid\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { bool fVerbose = false; if (!request.params[1].isNull()) { fVerbose = request.params[1].get_bool(); } TxId txid(ParseHashV(request.params[0], "parameter 1")); const CTxMemPool &mempool = EnsureMemPool(request.context); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(txid); if (it == mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } CTxMemPool::setEntries setAncestors; uint64_t noLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false); if (!fVerbose) { UniValue o(UniValue::VARR); for (CTxMemPool::txiter ancestorIt : setAncestors) { o.push_back(ancestorIt->GetTx().GetId().ToString()); } return o; } else { UniValue o(UniValue::VOBJ); for (CTxMemPool::txiter ancestorIt : setAncestors) { const CTxMemPoolEntry &e = *ancestorIt; const TxId &_txid = e.GetTx().GetId(); UniValue info(UniValue::VOBJ); entryToJSON(mempool, info, e); o.pushKV(_txid.ToString(), info); } return o; } }, }; } static RPCHelpMan getmempooldescendants() { return RPCHelpMan{ "getmempooldescendants", "If txid is in the mempool, returns all in-mempool descendants.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, {"verbose", RPCArg::Type::BOOL, /* default */ "false", "True for a json object, false for array of transaction ids"}, }, { RPCResult{"for verbose = false", RPCResult::Type::ARR, "", "", {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant " "transaction"}}}, RPCResult{"for verbose = true", RPCResult::Type::OBJ_DYN, "", "", { {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, }}, }, RPCExamples{HelpExampleCli("getmempooldescendants", "\"mytxid\"") + HelpExampleRpc("getmempooldescendants", "\"mytxid\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { bool fVerbose = false; if (!request.params[1].isNull()) { fVerbose = request.params[1].get_bool(); } TxId txid(ParseHashV(request.params[0], "parameter 1")); const CTxMemPool &mempool = EnsureMemPool(request.context); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(txid); if (it == mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } CTxMemPool::setEntries setDescendants; mempool.CalculateDescendants(it, setDescendants); // CTxMemPool::CalculateDescendants will include the given tx setDescendants.erase(it); if (!fVerbose) { UniValue o(UniValue::VARR); for (CTxMemPool::txiter descendantIt : setDescendants) { o.push_back(descendantIt->GetTx().GetId().ToString()); } return o; } else { UniValue o(UniValue::VOBJ); for (CTxMemPool::txiter descendantIt : setDescendants) { const CTxMemPoolEntry &e = *descendantIt; const TxId &_txid = e.GetTx().GetId(); UniValue info(UniValue::VOBJ); entryToJSON(mempool, info, e); o.pushKV(_txid.ToString(), info); } return o; } }, }; } static RPCHelpMan getmempoolentry() { return RPCHelpMan{ "getmempoolentry", "Returns mempool data for given transaction\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, }, RPCResult{RPCResult::Type::OBJ, "", "", MempoolEntryDescription()}, RPCExamples{HelpExampleCli("getmempoolentry", "\"mytxid\"") + HelpExampleRpc("getmempoolentry", "\"mytxid\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { TxId txid(ParseHashV(request.params[0], "parameter 1")); const CTxMemPool &mempool = EnsureMemPool(request.context); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(txid); if (it == mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } const CTxMemPoolEntry &e = *it; UniValue info(UniValue::VOBJ); entryToJSON(mempool, info, e); return info; }, }; } static RPCHelpMan getblockhash() { return RPCHelpMan{ "getblockhash", "Returns hash of block in best-block-chain at height provided.\n", { {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The height index"}, }, RPCResult{RPCResult::Type::STR_HEX, "", "The block hash"}, RPCExamples{HelpExampleCli("getblockhash", "1000") + HelpExampleRpc("getblockhash", "1000")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); int nHeight = request.params[0].get_int(); if (nHeight < 0 || nHeight > ::ChainActive().Height()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); } CBlockIndex *pblockindex = ::ChainActive()[nHeight]; return pblockindex->GetBlockHash().GetHex(); }, }; } static RPCHelpMan getblockheader() { return RPCHelpMan{ "getblockheader", "If verbose is false, returns a string that is serialized, hex-encoded " "data for blockheader 'hash'.\n" "If verbose is true, returns an Object with information about " "blockheader <hash>.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"}, {"verbose", RPCArg::Type::BOOL, /* default */ "true", "true for a json object, false for the hex-encoded data"}, }, { RPCResult{ "for verbose = true", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"}, {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not " "on the main chain"}, {RPCResult::Type::NUM, "height", "The block height or index"}, {RPCResult::Type::NUM, "version", "The block version"}, {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"}, {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"}, {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM, "nonce", "The nonce"}, {RPCResult::Type::STR_HEX, "bits", "The bits"}, {RPCResult::Type::NUM, "difficulty", "The difficulty"}, {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the " "current chain"}, {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"}, {RPCResult::Type::STR_HEX, "previousblockhash", "The hash of the previous block"}, {RPCResult::Type::STR_HEX, "nextblockhash", "The hash of the next block"}, }}, RPCResult{"for verbose=false", RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block " "'hash'"}, }, RPCExamples{HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214a" "dbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214a" "dbda81d7e2a3dd146f6ed09\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { BlockHash hash(ParseHashV(request.params[0], "hash")); bool fVerbose = true; if (!request.params[1].isNull()) { fVerbose = request.params[1].get_bool(); } const CBlockIndex *pblockindex; const CBlockIndex *tip; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); tip = ::ChainActive().Tip(); } if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } if (!fVerbose) { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); ssBlock << pblockindex->GetBlockHeader(); std::string strHex = HexStr(ssBlock); return strHex; } return blockheaderToJSON(tip, pblockindex); }, }; } static CBlock GetBlockChecked(const Config &config, const CBlockIndex *pblockindex) { CBlock block; if (IsBlockPruned(pblockindex)) { throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); } if (!ReadBlockFromDisk(block, pblockindex, config.GetChainParams().GetConsensus())) { // Block not found on disk. This could be because we have the block // header in our index but not yet have the block or did not accept the // block. throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); } return block; } static CBlockUndo GetUndoChecked(const CBlockIndex *pblockindex) { CBlockUndo blockUndo; if (IsBlockPruned(pblockindex)) { throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available (pruned data)"); } if (!UndoReadFromDisk(blockUndo, pblockindex)) { throw JSONRPCError(RPC_MISC_ERROR, "Can't read undo data from disk"); } return blockUndo; } static RPCHelpMan getblock() { return RPCHelpMan{ "getblock", "If verbosity is 0 or false, returns a string that is serialized, " "hex-encoded data for block 'hash'.\n" "If verbosity is 1 or true, returns an Object with information about " "block <hash>.\n" "If verbosity is 2, returns an Object with information about block " "<hash> and information about each transaction.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"}, {"verbosity|verbose", RPCArg::Type::NUM, /* default */ "1", "0 for hex-encoded data, 1 for a json object, and 2 for json " "object with transaction data"}, }, { RPCResult{"for verbosity = 0", RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block " "'hash'"}, RPCResult{ "for verbosity = 1", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"}, {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not " "on the main chain"}, {RPCResult::Type::NUM, "size", "The block size"}, {RPCResult::Type::NUM, "height", "The block height or index"}, {RPCResult::Type::NUM, "version", "The block version"}, {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"}, {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"}, {RPCResult::Type::ARR, "tx", "The transaction ids", {{RPCResult::Type::STR_HEX, "", "The transaction id"}}}, {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM, "nonce", "The nonce"}, {RPCResult::Type::STR_HEX, "bits", "The bits"}, {RPCResult::Type::NUM, "difficulty", "The difficulty"}, {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the chain " "up to this block (in hex)"}, {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"}, {RPCResult::Type::STR_HEX, "previousblockhash", "The hash of the previous block"}, {RPCResult::Type::STR_HEX, "nextblockhash", "The hash of the next block"}, }}, RPCResult{"for verbosity = 2", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"}, {RPCResult::Type::ARR, "tx", "", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::ELISION, "", "The transactions in the format of the " "getrawtransaction RPC. Different from " "verbosity = 1 \"tx\" result"}, }}, }}, }}, }, RPCExamples{ HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d" "214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d" "214adbda81d7e2a3dd146f6ed09\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { BlockHash hash(ParseHashV(request.params[0], "blockhash")); int verbosity = 1; if (!request.params[1].isNull()) { if (request.params[1].isNum()) { verbosity = request.params[1].get_int(); } else { verbosity = request.params[1].get_bool() ? 1 : 0; } } CBlock block; const CBlockIndex *pblockindex; const CBlockIndex *tip; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); tip = ::ChainActive().Tip(); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } block = GetBlockChecked(config, pblockindex); } if (verbosity <= 0) { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ssBlock << block; std::string strHex = HexStr(ssBlock); return strHex; } return blockToJSON(block, tip, pblockindex, verbosity >= 2); }, }; } static RPCHelpMan pruneblockchain() { return RPCHelpMan{ "pruneblockchain", "", { {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block height to prune up to. May be set to a discrete " "height, or to a " + UNIX_EPOCH_TIME + "\n" " to prune blocks whose block time is at " "least 2 hours older than the provided timestamp."}, }, RPCResult{RPCResult::Type::NUM, "", "Height of the last block pruned"}, RPCExamples{HelpExampleCli("pruneblockchain", "1000") + HelpExampleRpc("pruneblockchain", "1000")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { if (!fPruneMode) { throw JSONRPCError( RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode."); } LOCK(cs_main); int heightParam = request.params[0].get_int(); if (heightParam < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height."); } // Height value more than a billion is too high to be a block // height, and too low to be a block time (corresponds to timestamp // from Sep 2001). if (heightParam > 1000000000) { // Add a 2 hour buffer to include blocks which might have had // old timestamps CBlockIndex *pindex = ::ChainActive().FindEarliestAtLeast( heightParam - TIMESTAMP_WINDOW, 0); if (!pindex) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find block with at least the " "specified timestamp."); } heightParam = pindex->nHeight; } unsigned int height = (unsigned int)heightParam; unsigned int chainHeight = (unsigned int)::ChainActive().Height(); if (chainHeight < config.GetChainParams().PruneAfterHeight()) { throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning."); } else if (height > chainHeight) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height."); } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) { LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. " "Retaining the minimum number of blocks.\n"); height = chainHeight - MIN_BLOCKS_TO_KEEP; } PruneBlockFilesManual(height); const CBlockIndex *block = ::ChainActive().Tip(); CHECK_NONFATAL(block); while (block->pprev && (block->pprev->nStatus.hasData())) { block = block->pprev; } return uint64_t(block->nHeight); }, }; } static RPCHelpMan gettxoutsetinfo() { return RPCHelpMan{ "gettxoutsetinfo", "Returns statistics about the unspent transaction output set.\n" "Note this call may take some time.\n", { {"hash_type", RPCArg::Type::STR, /* default */ "hash_serialized", "Which UTXO set hash should be calculated. Options: " "'hash_serialized' (the legacy algorithm), 'none'."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "height", "The current block height (index)"}, {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"}, {RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs"}, {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"}, {RPCResult::Type::NUM, "bogosize", "A meaningless metric for UTXO set size"}, {RPCResult::Type::STR_HEX, "hash_serialized", "The serialized hash (only present if 'hash_serialized' " "hash_type is chosen)"}, {RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk"}, {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount"}, }}, RPCExamples{HelpExampleCli("gettxoutsetinfo", "") + HelpExampleRpc("gettxoutsetinfo", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { UniValue ret(UniValue::VOBJ); CCoinsStats stats; ::ChainstateActive().ForceFlushStateToDisk(); const CoinStatsHashType hash_type = ParseHashType( request.params[0], CoinStatsHashType::HASH_SERIALIZED); CCoinsView *coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB()); NodeContext &node = EnsureNodeContext(request.context); if (GetUTXOStats(coins_view, stats, hash_type, node.rpc_interruption_point)) { ret.pushKV("height", int64_t(stats.nHeight)); ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("transactions", int64_t(stats.nTransactions)); ret.pushKV("txouts", int64_t(stats.nTransactionOutputs)); ret.pushKV("bogosize", int64_t(stats.nBogoSize)); if (hash_type == CoinStatsHashType::HASH_SERIALIZED) { ret.pushKV("hash_serialized", stats.hashSerialized.GetHex()); } ret.pushKV("disk_size", stats.nDiskSize); ret.pushKV("total_amount", stats.nTotalAmount); } else { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } return ret; }, }; } RPCHelpMan gettxout() { return RPCHelpMan{ "gettxout", "Returns details about an unspent transaction output.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"}, {"include_mempool", RPCArg::Type::BOOL, /* default */ "true", "Whether to include the mempool. Note that an unspent output that " "is spent in the mempool won't appear."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"}, {RPCResult::Type::NUM, "confirmations", "The number of confirmations"}, {RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + Currency::get().ticker}, {RPCResult::Type::OBJ, "scriptPubKey", "", { {RPCResult::Type::STR_HEX, "asm", ""}, {RPCResult::Type::STR_HEX, "hex", ""}, {RPCResult::Type::NUM, "reqSigs", "Number of required signatures"}, {RPCResult::Type::STR_HEX, "type", "The type, eg pubkeyhash"}, {RPCResult::Type::ARR, "addresses", "array of bitcoin addresses", {{RPCResult::Type::STR, "address", "bitcoin address"}}}, }}, {RPCResult::Type::BOOL, "coinbase", "Coinbase or not"}, }}, RPCExamples{"\nGet unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nView the details\n" + HelpExampleCli("gettxout", "\"txid\" 1") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("gettxout", "\"txid\", 1")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); UniValue ret(UniValue::VOBJ); TxId txid(ParseHashV(request.params[0], "txid")); int n = request.params[1].get_int(); COutPoint out(txid, n); bool fMempool = true; if (!request.params[2].isNull()) { fMempool = request.params[2].get_bool(); } Coin coin; CCoinsViewCache *coins_view = &::ChainstateActive().CoinsTip(); if (fMempool) { const CTxMemPool &mempool = EnsureMemPool(request.context); LOCK(mempool.cs); CCoinsViewMemPool view(coins_view, mempool); if (!view.GetCoin(out, coin) || mempool.isSpent(out)) { return NullUniValue; } } else { if (!coins_view->GetCoin(out, coin)) { return NullUniValue; } } const CBlockIndex *pindex = LookupBlockIndex(coins_view->GetBestBlock()); ret.pushKV("bestblock", pindex->GetBlockHash().GetHex()); if (coin.GetHeight() == MEMPOOL_HEIGHT) { ret.pushKV("confirmations", 0); } else { ret.pushKV("confirmations", int64_t(pindex->nHeight - coin.GetHeight() + 1)); } ret.pushKV("value", coin.GetTxOut().nValue); UniValue o(UniValue::VOBJ); ScriptPubKeyToUniv(coin.GetTxOut().scriptPubKey, o, true); ret.pushKV("scriptPubKey", o); ret.pushKV("coinbase", coin.IsCoinBase()); return ret; }, }; } static RPCHelpMan verifychain() { return RPCHelpMan{ "verifychain", "Verifies blockchain database.\n", { {"checklevel", RPCArg::Type::NUM, /* default */ strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL), strprintf("How thorough the block verification is:\n - %s", Join(CHECKLEVEL_DOC, "\n- "))}, {"nblocks", RPCArg::Type::NUM, /* default */ strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS), "The number of blocks to check."}, }, RPCResult{RPCResult::Type::BOOL, "", "Verified or not"}, RPCExamples{HelpExampleCli("verifychain", "") + HelpExampleRpc("verifychain", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const int check_level(request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].get_int()); const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].get_int()}; LOCK(cs_main); return CVerifyDB().VerifyDB(config, &::ChainstateActive().CoinsTip(), check_level, check_depth); }, }; } static void BIP9SoftForkDescPushBack(UniValue &softforks, const Consensus::Params &consensusParams, Consensus::DeploymentPos id) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { // For BIP9 deployments. // Deployments (e.g. testdummy) with timeout value before Jan 1, 2009 are // hidden. A timeout value of 0 guarantees a softfork will never be // activated. This is used when merging logic to implement a proposed // softfork without a specified deployment schedule. if (consensusParams.vDeployments[id].nTimeout <= 1230768000) { return; } UniValue bip9(UniValue::VOBJ); const ThresholdState thresholdState = VersionBitsTipState(consensusParams, id); switch (thresholdState) { case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break; case ThresholdState::STARTED: bip9.pushKV("status", "started"); break; case ThresholdState::LOCKED_IN: bip9.pushKV("status", "locked_in"); break; case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break; case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break; } if (ThresholdState::STARTED == thresholdState) { bip9.pushKV("bit", consensusParams.vDeployments[id].bit); } bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); int64_t since_height = VersionBitsTipStateSinceHeight(consensusParams, id); bip9.pushKV("since", since_height); if (ThresholdState::STARTED == thresholdState) { UniValue statsUV(UniValue::VOBJ); BIP9Stats statsStruct = VersionBitsTipStatistics(consensusParams, id); statsUV.pushKV("period", statsStruct.period); statsUV.pushKV("threshold", statsStruct.threshold); statsUV.pushKV("elapsed", statsStruct.elapsed); statsUV.pushKV("count", statsStruct.count); statsUV.pushKV("possible", statsStruct.possible); bip9.pushKV("statistics", statsUV); } UniValue rv(UniValue::VOBJ); rv.pushKV("type", "bip9"); rv.pushKV("bip9", bip9); if (ThresholdState::ACTIVE == thresholdState) { rv.pushKV("height", since_height); } rv.pushKV("active", ThresholdState::ACTIVE == thresholdState); softforks.pushKV(VersionBitsDeploymentInfo[id].name, rv); } RPCHelpMan getblockchaininfo() { return RPCHelpMan{ "getblockchaininfo", "Returns an object containing various state info regarding blockchain " "processing.\n", {}, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "chain", "current network name (main, test, regtest)"}, {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The " "genesis block has height 0"}, {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"}, {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"}, {RPCResult::Type::NUM, "difficulty", "the current difficulty"}, {RPCResult::Type::NUM, "mediantime", "median time for the current best block"}, {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"}, {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in " "Initial Block Download mode"}, {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"}, {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"}, {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"}, {RPCResult::Type::NUM, "pruneheight", "lowest-height complete block stored (only present if pruning " "is enabled)"}, {RPCResult::Type::BOOL, "automatic_pruning", "whether automatic pruning is enabled (only present if " "pruning is enabled)"}, {RPCResult::Type::NUM, "prune_target_size", "the target size used by pruning (only present if automatic " "pruning is enabled)"}, {RPCResult::Type::OBJ_DYN, "softforks", "status of softforks", { {RPCResult::Type::OBJ, "xxxx", "name of the softfork", { {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""}, {RPCResult::Type::OBJ, "bip9", "status of bip9 softforks (only for \"bip9\" type)", { {RPCResult::Type::STR, "status", "one of \"defined\", \"started\", " "\"locked_in\", \"active\", \"failed\""}, {RPCResult::Type::NUM, "bit", "the bit (0-28) in the block version field " "used to signal this softfork (only for " "\"started\" status)"}, {RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at " "which the bit gains its meaning"}, {RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the " "deployment is considered failed if not yet " "locked in"}, {RPCResult::Type::NUM, "since", "height of the first block to which the status " "applies"}, {RPCResult::Type::OBJ, "statistics", "numeric statistics about BIP9 signalling for " "a softfork", { {RPCResult::Type::NUM, "period", "the length in blocks of the BIP9 " "signalling period"}, {RPCResult::Type::NUM, "threshold", "the number of blocks with the version " "bit set required to activate the " "feature"}, {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the " "beginning of the current period"}, {RPCResult::Type::NUM, "count", "the number of blocks with the version " "bit set in the current period"}, {RPCResult::Type::BOOL, "possible", "returns false if there are not enough " "blocks left in this period to pass " "activation threshold"}, }}, }}, {RPCResult::Type::NUM, "height", "height of the first block which the rules are or " "will be enforced (only for \"buried\" type, or " "\"bip9\" type with \"active\" status)"}, {RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and " "the next block"}, }}, }}, {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, }}, RPCExamples{HelpExampleCli("getblockchaininfo", "") + HelpExampleRpc("getblockchaininfo", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); const CChainParams &chainparams = config.GetChainParams(); const CBlockIndex *tip = ::ChainActive().Tip(); UniValue obj(UniValue::VOBJ); obj.pushKV("chain", chainparams.NetworkIDString()); obj.pushKV("blocks", int(::ChainActive().Height())); obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1); obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex()); obj.pushKV("difficulty", double(GetDifficulty(tip))); obj.pushKV("mediantime", int64_t(tip->GetMedianTimePast())); obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip)); obj.pushKV("initialblockdownload", ::ChainstateActive().IsInitialBlockDownload()); obj.pushKV("chainwork", tip->nChainWork.GetHex()); obj.pushKV("size_on_disk", CalculateCurrentUsage()); obj.pushKV("pruned", fPruneMode); if (fPruneMode) { const CBlockIndex *block = tip; CHECK_NONFATAL(block); while (block->pprev && (block->pprev->nStatus.hasData())) { block = block->pprev; } obj.pushKV("pruneheight", block->nHeight); // if 0, execution bypasses the whole if block. bool automatic_pruning = (gArgs.GetArg("-prune", 0) != 1); obj.pushKV("automatic_pruning", automatic_pruning); if (automatic_pruning) { obj.pushKV("prune_target_size", nPruneTarget); } } UniValue softforks(UniValue::VOBJ); for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) { BIP9SoftForkDescPushBack(softforks, chainparams.GetConsensus(), Consensus::DeploymentPos(i)); } obj.pushKV("softforks", softforks); obj.pushKV("warnings", GetWarnings(false).original); return obj; }, }; } /** Comparison function for sorting the getchaintips heads. */ struct CompareBlocksByHeight { bool operator()(const CBlockIndex *a, const CBlockIndex *b) const { // Make sure that unequal blocks with the same height do not compare // equal. Use the pointers themselves to make a distinction. if (a->nHeight != b->nHeight) { return (a->nHeight > b->nHeight); } return a < b; } }; static RPCHelpMan getchaintips() { return RPCHelpMan{ "getchaintips", "Return information about all known tips in the block tree, including " "the main chain as well as orphaned branches.\n", {}, RPCResult{ RPCResult::Type::ARR, "", "", {{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "height", "height of the chain tip"}, {RPCResult::Type::STR_HEX, "hash", "block hash of the tip"}, {RPCResult::Type::NUM, "branchlen", "zero for main chain, otherwise length of branch connecting " "the tip to the main chain"}, {RPCResult::Type::STR, "status", "status of the chain, \"active\" for the main chain\n" "Possible values for status:\n" "1. \"invalid\" This branch contains at " "least one invalid block\n" "2. \"parked\" This branch contains at " "least one parked block\n" "3. \"headers-only\" Not all blocks for this " "branch are available, but the headers are valid\n" "4. \"valid-headers\" All blocks are available for " "this branch, but they were never fully validated\n" "5. \"valid-fork\" This branch is not part of " "the active chain, but is fully validated\n" "6. \"active\" This is the tip of the " "active main chain, which is certainly valid"}, }}}}, RPCExamples{HelpExampleCli("getchaintips", "") + HelpExampleRpc("getchaintips", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { ChainstateManager &chainman = EnsureChainman(request.context); LOCK(cs_main); /** * Idea: The set of chain tips is the active chain tip, plus orphan * blocks which do not have another orphan building off of them. * Algorithm: * - Make one pass through BlockIndex(), picking out the orphan * blocks, and also storing a set of the orphan block's pprev * pointers. * - Iterate through the orphan blocks. If the block isn't pointed * to by another orphan, it is a chain tip. * - Add the active chain tip */ std::set<const CBlockIndex *, CompareBlocksByHeight> setTips; std::set<const CBlockIndex *> setOrphans; std::set<const CBlockIndex *> setPrevs; for (const std::pair<const BlockHash, CBlockIndex *> &item : chainman.BlockIndex()) { if (!chainman.ActiveChain().Contains(item.second)) { setOrphans.insert(item.second); setPrevs.insert(item.second->pprev); } } for (std::set<const CBlockIndex *>::iterator it = setOrphans.begin(); it != setOrphans.end(); ++it) { if (setPrevs.erase(*it) == 0) { setTips.insert(*it); } } // Always report the currently active tip. setTips.insert(chainman.ActiveChain().Tip()); /* Construct the output array. */ UniValue res(UniValue::VARR); for (const CBlockIndex *block : setTips) { UniValue obj(UniValue::VOBJ); obj.pushKV("height", block->nHeight); obj.pushKV("hash", block->phashBlock->GetHex()); const int branchLen = block->nHeight - chainman.ActiveChain().FindFork(block)->nHeight; obj.pushKV("branchlen", branchLen); std::string status; if (chainman.ActiveChain().Contains(block)) { // This block is part of the currently active chain. status = "active"; } else if (block->nStatus.isInvalid()) { // This block or one of its ancestors is invalid. status = "invalid"; } else if (block->nStatus.isOnParkedChain()) { // This block or one of its ancestors is parked. status = "parked"; } else if (!block->HaveTxsDownloaded()) { // This block cannot be connected because full block data // for it or one of its parents is missing. status = "headers-only"; } else if (block->IsValid(BlockValidity::SCRIPTS)) { // This block is fully validated, but no longer part of the // active chain. It was probably the active block once, but // was reorganized. status = "valid-fork"; } else if (block->IsValid(BlockValidity::TREE)) { // The headers for this block are valid, but it has not been // validated. It was probably never part of the most-work // chain. status = "valid-headers"; } else { // No clue. status = "unknown"; } obj.pushKV("status", status); res.push_back(obj); } return res; }, }; } UniValue MempoolInfoToJSON(const CTxMemPool &pool) { // Make sure this call is atomic in the pool. LOCK(pool.cs); UniValue ret(UniValue::VOBJ); ret.pushKV("loaded", pool.IsLoaded()); ret.pushKV("size", (int64_t)pool.size()); ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize()); ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage()); size_t maxmempool = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; ret.pushKV("maxmempool", (int64_t)maxmempool); ret.pushKV( "mempoolminfee", std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK()); ret.pushKV("minrelaytxfee", ::minRelayTxFee.GetFeePerK()); ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); return ret; } static RPCHelpMan getmempoolinfo() { return RPCHelpMan{ "getmempoolinfo", "Returns details on the active state of the TX memory pool.\n", {}, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"}, {RPCResult::Type::NUM, "size", "Current tx count"}, {RPCResult::Type::NUM, "bytes", "Sum of all transaction sizes"}, {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"}, {RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"}, {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + Currency::get().ticker + "/kB for tx to be accepted. Is the maximum of " "minrelaytxfee and minimum mempool fee"}, {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"}, {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial " "broadcast yet"}, }}, RPCExamples{HelpExampleCli("getmempoolinfo", "") + HelpExampleRpc("getmempoolinfo", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { return MempoolInfoToJSON(EnsureMemPool(request.context)); }, }; } static RPCHelpMan preciousblock() { return RPCHelpMan{ "preciousblock", "Treats a block as if it were received before others with the same " "work.\n" "\nA later preciousblock call can override the effect of an earlier " "one.\n" "\nThe effects of preciousblock are not retained across restarts.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as precious"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("preciousblock", "\"blockhash\"") + HelpExampleRpc("preciousblock", "\"blockhash\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { BlockHash hash(ParseHashV(request.params[0], "blockhash")); CBlockIndex *pblockindex; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } BlockValidationState state; PreciousBlock(config, state, pblockindex); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); } return NullUniValue; }, }; } RPCHelpMan finalizeblock() { return RPCHelpMan{ "finalizeblock", "Treats a block as final. It cannot be reorged. Any chain\n" "that does not contain this block is invalid. Used on a less\n" "work chain, it can effectively PUT YOU OUT OF CONSENSUS.\n" "USE WITH CAUTION!\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as invalid"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("finalizeblock", "\"blockhash\"") + HelpExampleRpc("finalizeblock", "\"blockhash\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { std::string strHash = request.params[0].get_str(); BlockHash hash(uint256S(strHash)); BlockValidationState state; CBlockIndex *pblockindex = nullptr; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } // end of locked cs_main scope ::ChainstateActive().FinalizeBlock(config, state, pblockindex); if (state.IsValid()) { ActivateBestChain(config, state); } if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } return NullUniValue; }, }; } static RPCHelpMan invalidateblock() { return RPCHelpMan{ "invalidateblock", "Permanently marks a block as invalid, as if it violated a consensus " "rule.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as invalid"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("invalidateblock", "\"blockhash\"") + HelpExampleRpc("invalidateblock", "\"blockhash\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const BlockHash hash(ParseHashV(request.params[0], "blockhash")); BlockValidationState state; CBlockIndex *pblockindex; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } ::ChainstateActive().InvalidateBlock(config, state, pblockindex); if (state.IsValid()) { ActivateBestChain(config, state); } if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } return NullUniValue; }, }; } RPCHelpMan parkblock() { return RPCHelpMan{ "parkblock", "Marks a block as parked.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to park"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("parkblock", "\"blockhash\"") + HelpExampleRpc("parkblock", "\"blockhash\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const std::string strHash = request.params[0].get_str(); const BlockHash hash(uint256S(strHash)); BlockValidationState state; CBlockIndex *pblockindex = nullptr; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } ::ChainstateActive().ParkBlock(config, state, pblockindex); if (state.IsValid()) { ActivateBestChain(config, state); } if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); } return NullUniValue; }, }; } static RPCHelpMan reconsiderblock() { return RPCHelpMan{ "reconsiderblock", "Removes invalidity status of a block, its ancestors and its" "descendants, reconsider them for activation.\n" "This can be used to undo the effects of invalidateblock.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to reconsider"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("reconsiderblock", "\"blockhash\"") + HelpExampleRpc("reconsiderblock", "\"blockhash\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const BlockHash hash(ParseHashV(request.params[0], "blockhash")); { LOCK(cs_main); CBlockIndex *pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } ResetBlockFailureFlags(pblockindex); } BlockValidationState state; ActivateBestChain(config, state); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } return NullUniValue; }, }; } RPCHelpMan unparkblock() { return RPCHelpMan{ "unparkblock", "Removes parked status of a block and its descendants, reconsider " "them for activation.\n" "This can be used to undo the effects of parkblock.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to unpark"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("unparkblock", "\"blockhash\"") + HelpExampleRpc("unparkblock", "\"blockhash\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const std::string strHash = request.params[0].get_str(); const BlockHash hash(uint256S(strHash)); { LOCK(cs_main); CBlockIndex *pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } UnparkBlockAndChildren(pblockindex); } BlockValidationState state; ActivateBestChain(config, state); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); } return NullUniValue; }, }; } static RPCHelpMan getchaintxstats() { return RPCHelpMan{ "getchaintxstats", "Compute statistics about the total number and rate of transactions " "in the chain.\n", { {"nblocks", RPCArg::Type::NUM, /* default */ "one month", "Size of the window in number of blocks"}, {"blockhash", RPCArg::Type::STR_HEX, /* default */ "chain tip", "The hash of the block that ends the window."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM_TIME, "time", "The timestamp for the final block in the window, " "expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM, "txcount", "The total number of transactions in the chain up to " "that point"}, {RPCResult::Type::STR_HEX, "window_final_block_hash", "The hash of the final block in the window"}, {RPCResult::Type::NUM, "window_final_block_height", "The height of the final block in the window."}, {RPCResult::Type::NUM, "window_block_count", "Size of the window in number of blocks"}, {RPCResult::Type::NUM, "window_tx_count", "The number of transactions in the window. Only " "returned if \"window_block_count\" is > 0"}, {RPCResult::Type::NUM, "window_interval", "The elapsed time in the window in seconds. Only " "returned if \"window_block_count\" is > 0"}, {RPCResult::Type::NUM, "txrate", "The average rate of transactions per second in the " "window. Only returned if \"window_interval\" is > 0"}, }}, RPCExamples{HelpExampleCli("getchaintxstats", "") + HelpExampleRpc("getchaintxstats", "2016")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const CBlockIndex *pindex; // By default: 1 month int blockcount = 30 * 24 * 60 * 60 / config.GetChainParams().GetConsensus().nPowTargetSpacing; if (request.params[1].isNull()) { LOCK(cs_main); pindex = ::ChainActive().Tip(); } else { BlockHash hash(ParseHashV(request.params[1], "blockhash")); LOCK(cs_main); pindex = LookupBlockIndex(hash); if (!pindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } if (!::ChainActive().Contains(pindex)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain"); } } CHECK_NONFATAL(pindex != nullptr); if (request.params[0].isNull()) { blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1)); } else { blockcount = request.params[0].get_int(); if (blockcount < 0 || (blockcount > 0 && blockcount >= pindex->nHeight)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block count: " "should be between 0 and " "the block's height - 1"); } } const CBlockIndex *pindexPast = pindex->GetAncestor(pindex->nHeight - blockcount); int nTimeDiff = pindex->GetMedianTimePast() - pindexPast->GetMedianTimePast(); int nTxDiff = pindex->GetChainTxCount() - pindexPast->GetChainTxCount(); UniValue ret(UniValue::VOBJ); ret.pushKV("time", pindex->GetBlockTime()); ret.pushKV("txcount", pindex->GetChainTxCount()); ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex()); ret.pushKV("window_final_block_height", pindex->nHeight); ret.pushKV("window_block_count", blockcount); if (blockcount > 0) { ret.pushKV("window_tx_count", nTxDiff); ret.pushKV("window_interval", nTimeDiff); if (nTimeDiff > 0) { ret.pushKV("txrate", double(nTxDiff) / nTimeDiff); } } return ret; }, }; } template <typename T> static T CalculateTruncatedMedian(std::vector<T> &scores) { size_t size = scores.size(); if (size == 0) { return T(); } std::sort(scores.begin(), scores.end()); if (size % 2 == 0) { return (scores[size / 2 - 1] + scores[size / 2]) / 2; } else { return scores[size / 2]; } } template <typename T> static inline bool SetHasKeys(const std::set<T> &set) { return false; } template <typename T, typename Tk, typename... Args> static inline bool SetHasKeys(const std::set<T> &set, const Tk &key, - const Args &... args) { + const Args &...args) { return (set.count(key) != 0) || SetHasKeys(set, args...); } // outpoint (needed for the utxo index) + nHeight + fCoinBase static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool); static RPCHelpMan getblockstats() { const auto &ticker = Currency::get().ticker; return RPCHelpMan{ "getblockstats", "Compute per block statistics for a given window. All amounts are " "in " + ticker + ".\n" "It won't work for some heights with pruning.\n", { {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", "", {"", "string or numeric"}}, {"stats", RPCArg::Type::ARR, /* default */ "all values", "Values to plot (see result below)", { {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, {"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, }, "stats"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "avgfee", "Average fee in the block"}, {RPCResult::Type::NUM, "avgfeerate", "Average feerate (in satoshis per virtual byte)"}, {RPCResult::Type::NUM, "avgtxsize", "Average transaction size"}, {RPCResult::Type::STR_HEX, "blockhash", "The block hash (to check for potential reorgs)"}, {RPCResult::Type::NUM, "height", "The height of the block"}, {RPCResult::Type::NUM, "ins", "The number of inputs (excluding coinbase)"}, {RPCResult::Type::NUM, "maxfee", "Maximum fee in the block"}, {RPCResult::Type::NUM, "maxfeerate", "Maximum feerate (in satoshis per virtual byte)"}, {RPCResult::Type::NUM, "maxtxsize", "Maximum transaction size"}, {RPCResult::Type::NUM, "medianfee", "Truncated median fee in the block"}, {RPCResult::Type::NUM, "medianfeerate", "Truncated median feerate (in " + ticker + " per byte)"}, {RPCResult::Type::NUM, "mediantime", "The block median time past"}, {RPCResult::Type::NUM, "mediantxsize", "Truncated median transaction size"}, {RPCResult::Type::NUM, "minfee", "Minimum fee in the block"}, {RPCResult::Type::NUM, "minfeerate", "Minimum feerate (in satoshis per virtual byte)"}, {RPCResult::Type::NUM, "mintxsize", "Minimum transaction size"}, {RPCResult::Type::NUM, "outs", "The number of outputs"}, {RPCResult::Type::NUM, "subsidy", "The block subsidy"}, {RPCResult::Type::NUM, "time", "The block time"}, {RPCResult::Type::NUM, "total_out", "Total amount in all outputs (excluding coinbase and thus " "reward [ie subsidy + totalfee])"}, {RPCResult::Type::NUM, "total_size", "Total size of all non-coinbase transactions"}, {RPCResult::Type::NUM, "totalfee", "The fee total"}, {RPCResult::Type::NUM, "txs", "The number of transactions (including coinbase)"}, {RPCResult::Type::NUM, "utxo_increase", "The increase/decrease in the number of unspent outputs"}, {RPCResult::Type::NUM, "utxo_size_inc", "The increase/decrease in size for the utxo index (not " "discounting op_return and similar)"}, }}, RPCExamples{ HelpExampleCli( "getblockstats", R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") + HelpExampleCli("getblockstats", R"(1000 '["minfeerate","avgfeerate"]')") + HelpExampleRpc( "getblockstats", R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") + HelpExampleRpc("getblockstats", R"(1000, ["minfeerate","avgfeerate"])")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); CBlockIndex *pindex; if (request.params[0].isNum()) { const int height = request.params[0].get_int(); const int current_tip = ::ChainActive().Height(); if (height < 0) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height)); } if (height > current_tip) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip)); } pindex = ::ChainActive()[height]; } else { const BlockHash hash( ParseHashV(request.params[0], "hash_or_height")); pindex = LookupBlockIndex(hash); if (!pindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } if (!::ChainActive().Contains(pindex)) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Block is not in chain %s", Params().NetworkIDString())); } } CHECK_NONFATAL(pindex != nullptr); std::set<std::string> stats; if (!request.params[1].isNull()) { const UniValue stats_univalue = request.params[1].get_array(); for (unsigned int i = 0; i < stats_univalue.size(); i++) { const std::string stat = stats_univalue[i].get_str(); stats.insert(stat); } } const CBlock block = GetBlockChecked(config, pindex); const CBlockUndo blockUndo = GetUndoChecked(pindex); // Calculate everything if nothing selected (default) const bool do_all = stats.size() == 0; const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0; const bool do_medianfee = do_all || stats.count("medianfee") != 0; const bool do_medianfeerate = do_all || stats.count("medianfeerate") != 0; const bool loop_inputs = do_all || do_medianfee || do_medianfeerate || SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate"); const bool loop_outputs = do_all || loop_inputs || stats.count("total_out"); const bool do_calculate_size = do_mediantxsize || loop_inputs || SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize"); const int64_t blockMaxSize = config.GetMaxBlockSize(); Amount maxfee = Amount::zero(); Amount maxfeerate = Amount::zero(); Amount minfee = MAX_MONEY; Amount minfeerate = MAX_MONEY; Amount total_out = Amount::zero(); Amount totalfee = Amount::zero(); int64_t inputs = 0; int64_t maxtxsize = 0; int64_t mintxsize = blockMaxSize; int64_t outputs = 0; int64_t total_size = 0; int64_t utxo_size_inc = 0; std::vector<Amount> fee_array; std::vector<Amount> feerate_array; std::vector<int64_t> txsize_array; for (size_t i = 0; i < block.vtx.size(); ++i) { const auto &tx = block.vtx.at(i); outputs += tx->vout.size(); Amount tx_total_out = Amount::zero(); if (loop_outputs) { for (const CTxOut &out : tx->vout) { tx_total_out += out.nValue; utxo_size_inc += GetSerializeSize(out, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD; } } if (tx->IsCoinBase()) { continue; } // Don't count coinbase's fake input inputs += tx->vin.size(); // Don't count coinbase reward total_out += tx_total_out; int64_t tx_size = 0; if (do_calculate_size) { tx_size = tx->GetTotalSize(); if (do_mediantxsize) { txsize_array.push_back(tx_size); } maxtxsize = std::max(maxtxsize, tx_size); mintxsize = std::min(mintxsize, tx_size); total_size += tx_size; } if (loop_inputs) { Amount tx_total_in = Amount::zero(); const auto &txundo = blockUndo.vtxundo.at(i - 1); for (const Coin &coin : txundo.vprevout) { const CTxOut &prevoutput = coin.GetTxOut(); tx_total_in += prevoutput.nValue; utxo_size_inc -= GetSerializeSize(prevoutput, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD; } Amount txfee = tx_total_in - tx_total_out; CHECK_NONFATAL(MoneyRange(txfee)); if (do_medianfee) { fee_array.push_back(txfee); } maxfee = std::max(maxfee, txfee); minfee = std::min(minfee, txfee); totalfee += txfee; Amount feerate = txfee / tx_size; if (do_medianfeerate) { feerate_array.push_back(feerate); } maxfeerate = std::max(maxfeerate, feerate); minfeerate = std::min(minfeerate, feerate); } } UniValue ret_all(UniValue::VOBJ); ret_all.pushKV("avgfee", block.vtx.size() > 1 ? (totalfee / int((block.vtx.size() - 1))) : Amount::zero()); ret_all.pushKV("avgfeerate", total_size > 0 ? (totalfee / total_size) : Amount::zero()); ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0); ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex()); ret_all.pushKV("height", (int64_t)pindex->nHeight); ret_all.pushKV("ins", inputs); ret_all.pushKV("maxfee", maxfee); ret_all.pushKV("maxfeerate", maxfeerate); ret_all.pushKV("maxtxsize", maxtxsize); ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array)); ret_all.pushKV("medianfeerate", CalculateTruncatedMedian(feerate_array)); ret_all.pushKV("mediantime", pindex->GetMedianTimePast()); ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array)); ret_all.pushKV("minfee", minfee == MAX_MONEY ? Amount::zero() : minfee); ret_all.pushKV("minfeerate", minfeerate == MAX_MONEY ? Amount::zero() : minfeerate); ret_all.pushKV("mintxsize", mintxsize == blockMaxSize ? 0 : mintxsize); ret_all.pushKV("outs", outputs); ret_all.pushKV("subsidy", GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())); ret_all.pushKV("time", pindex->GetBlockTime()); ret_all.pushKV("total_out", total_out); ret_all.pushKV("total_size", total_size); ret_all.pushKV("totalfee", totalfee); ret_all.pushKV("txs", (int64_t)block.vtx.size()); ret_all.pushKV("utxo_increase", outputs - inputs); ret_all.pushKV("utxo_size_inc", utxo_size_inc); if (do_all) { return ret_all; } UniValue ret(UniValue::VOBJ); for (const std::string &stat : stats) { const UniValue &value = ret_all[stat]; if (value.isNull()) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic %s", stat)); } ret.pushKV(stat, value); } return ret; }, }; } static RPCHelpMan savemempool() { return RPCHelpMan{ "savemempool", "Dumps the mempool to disk. It will fail until the previous dump is " "fully loaded.\n", {}, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("savemempool", "") + HelpExampleRpc("savemempool", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const CTxMemPool &mempool = EnsureMemPool(request.context); if (!mempool.IsLoaded()) { throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); } if (!DumpMempool(mempool)) { throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); } return NullUniValue; }, }; } namespace { //! Search for a given set of pubkey scripts static bool FindScriptPubKey(std::atomic<int> &scan_progress, const std::atomic<bool> &should_abort, int64_t &count, CCoinsViewCursor *cursor, const std::set<CScript> &needles, std::map<COutPoint, Coin> &out_results, std::function<void()> &interruption_point) { scan_progress = 0; count = 0; while (cursor->Valid()) { COutPoint key; Coin coin; if (!cursor->GetKey(key) || !cursor->GetValue(coin)) { return false; } if (++count % 8192 == 0) { interruption_point(); if (should_abort) { // allow to abort the scan via the abort reference return false; } } if (count % 256 == 0) { // update progress reference every 256 item const TxId &txid = key.GetTxId(); uint32_t high = 0x100 * *txid.begin() + *(txid.begin() + 1); scan_progress = int(high * 100.0 / 65536.0 + 0.5); } if (needles.count(coin.GetTxOut().scriptPubKey)) { out_results.emplace(key, coin); } cursor->Next(); } scan_progress = 100; return true; } } // namespace /** RAII object to prevent concurrency issue when scanning the txout set */ static std::atomic<int> g_scan_progress; static std::atomic<bool> g_scan_in_progress; static std::atomic<bool> g_should_abort_scan; class CoinsViewScanReserver { private: bool m_could_reserve; public: explicit CoinsViewScanReserver() : m_could_reserve(false) {} bool reserve() { CHECK_NONFATAL(!m_could_reserve); if (g_scan_in_progress.exchange(true)) { return false; } m_could_reserve = true; return true; } ~CoinsViewScanReserver() { if (m_could_reserve) { g_scan_in_progress = false; } } }; static RPCHelpMan scantxoutset() { const auto &ticker = Currency::get().ticker; return RPCHelpMan{ "scantxoutset", "EXPERIMENTAL warning: this call may be removed or changed in future " "releases.\n" "\nScans the unspent transaction output set for entries that match " "certain output descriptors.\n" "Examples of output descriptors are:\n" " addr(<address>) Outputs whose scriptPubKey " "corresponds to the specified address (does not include P2PK)\n" " raw(<hex script>) Outputs whose scriptPubKey " "equals the specified hex scripts\n" " combo(<pubkey>) P2PK and P2PKH outputs for " "the given pubkey\n" " pkh(<pubkey>) P2PKH outputs for the given " "pubkey\n" " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for " "the given threshold and pubkeys\n" "\nIn the above, <pubkey> either refers to a fixed public key in " "hexadecimal notation, or to an xpub/xprv optionally followed by one\n" "or more path elements separated by \"/\", and optionally ending in " "\"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n" "unhardened or hardened child keys.\n" "In the latter case, a range needs to be specified by below if " "different from 1000.\n" "For more information on output descriptors, see the documentation in " "the doc/descriptors.md file.\n", { {"action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n" " \"start\" for starting a " "scan\n" " \"abort\" for aborting the " "current scan (returns true when abort was successful)\n" " \"status\" for " "progress report (in %) of the current scan"}, {"scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n" " Every scan object is either a " "string descriptor or an object:", { {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"}, { "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata", { {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"}, {"range", RPCArg::Type::RANGE, /* default */ "1000", "The range of HD chain indexes to explore (either " "end or [begin,end])"}, }, }, }, "[scanobjects,...]"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::BOOL, "success", "Whether the scan was completed"}, {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs scanned"}, {RPCResult::Type::NUM, "height", "The current block height (index)"}, {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"}, {RPCResult::Type::ARR, "unspents", "", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "txid", "The transaction id"}, {RPCResult::Type::NUM, "vout", "The vout value"}, {RPCResult::Type::STR_HEX, "scriptPubKey", "The script key"}, {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched " "scriptPubKey"}, {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + ticker + " of the unspent output"}, {RPCResult::Type::NUM, "height", "Height of the unspent transaction output"}, }}, }}, {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + ticker}, }}, RPCExamples{""}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); UniValue result(UniValue::VOBJ); if (request.params[0].get_str() == "status") { CoinsViewScanReserver reserver; if (reserver.reserve()) { // no scan in progress return NullUniValue; } result.pushKV("progress", g_scan_progress.load()); return result; } else if (request.params[0].get_str() == "abort") { CoinsViewScanReserver reserver; if (reserver.reserve()) { // reserve was possible which means no scan was running return false; } // set the abort flag g_should_abort_scan = true; return true; } else if (request.params[0].get_str() == "start") { CoinsViewScanReserver reserver; if (!reserver.reserve()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action " "\"abort\" or \"status\""); } if (request.params.size() < 2) { throw JSONRPCError(RPC_MISC_ERROR, "scanobjects argument is required for " "the start action"); } std::set<CScript> needles; std::map<CScript, std::string> descriptors; Amount total_in = Amount::zero(); // loop through the scan objects for (const UniValue &scanobject : request.params[1].get_array().getValues()) { FlatSigningProvider provider; auto scripts = EvalDescriptorStringOrObject(scanobject, provider); for (const auto &script : scripts) { std::string inferred = InferDescriptor(script, provider)->ToString(); needles.emplace(script); descriptors.emplace(std::move(script), std::move(inferred)); } } // Scan the unspent transaction output set for inputs UniValue unspents(UniValue::VARR); std::vector<CTxOut> input_txos; std::map<COutPoint, Coin> coins; g_should_abort_scan = false; g_scan_progress = 0; int64_t count = 0; std::unique_ptr<CCoinsViewCursor> pcursor; CBlockIndex *tip; { LOCK(cs_main); ::ChainstateActive().ForceFlushStateToDisk(); pcursor = std::unique_ptr<CCoinsViewCursor>( ::ChainstateActive().CoinsDB().Cursor()); CHECK_NONFATAL(pcursor); tip = ::ChainActive().Tip(); CHECK_NONFATAL(tip); } NodeContext &node = EnsureNodeContext(request.context); bool res = FindScriptPubKey( g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins, node.rpc_interruption_point); result.pushKV("success", res); result.pushKV("txouts", count); result.pushKV("height", tip->nHeight); result.pushKV("bestblock", tip->GetBlockHash().GetHex()); for (const auto &it : coins) { const COutPoint &outpoint = it.first; const Coin &coin = it.second; const CTxOut &txo = coin.GetTxOut(); input_txos.push_back(txo); total_in += txo.nValue; UniValue unspent(UniValue::VOBJ); unspent.pushKV("txid", outpoint.GetTxId().GetHex()); unspent.pushKV("vout", int32_t(outpoint.GetN())); unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey)); unspent.pushKV("desc", descriptors[txo.scriptPubKey]); unspent.pushKV("amount", txo.nValue); unspent.pushKV("height", int32_t(coin.GetHeight())); unspents.push_back(unspent); } result.pushKV("unspents", unspents); result.pushKV("total_amount", total_in); } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); } return result; }, }; } static RPCHelpMan getblockfilter() { return RPCHelpMan{ "getblockfilter", "Retrieve a BIP 157 content filter for a particular block.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"}, {"filtertype", RPCArg::Type::STR, /*default*/ "basic", "The type name of the filter"}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "filter", "the hex-encoded filter data"}, {RPCResult::Type::STR_HEX, "header", "the hex-encoded filter header"}, }}, RPCExamples{ HelpExampleCli("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214a" "dbda81d7e2a3dd146f6ed09\" \"basic\"") + HelpExampleRpc("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7" "e2a3dd146f6ed09\", \"basic\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const BlockHash block_hash( ParseHashV(request.params[0], "blockhash")); std::string filtertype_name = "basic"; if (!request.params[1].isNull()) { filtertype_name = request.params[1].get_str(); } BlockFilterType filtertype; if (!BlockFilterTypeByName(filtertype_name, filtertype)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype"); } BlockFilterIndex *index = GetBlockFilterIndex(filtertype); if (!index) { throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name); } const CBlockIndex *block_index; bool block_was_connected; { LOCK(cs_main); block_index = LookupBlockIndex(block_hash); if (!block_index) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } block_was_connected = block_index->IsValid(BlockValidity::SCRIPTS); } bool index_ready = index->BlockUntilSyncedToCurrentChain(); BlockFilter filter; uint256 filter_header; if (!index->LookupFilter(block_index, filter) || !index->LookupFilterHeader(block_index, filter_header)) { int err_code; std::string errmsg = "Filter not found."; if (!block_was_connected) { err_code = RPC_INVALID_ADDRESS_OR_KEY; errmsg += " Block was not connected to active chain."; } else if (!index_ready) { err_code = RPC_MISC_ERROR; errmsg += " Block filters are still in the process of " "being indexed."; } else { err_code = RPC_INTERNAL_ERROR; errmsg += " This error is unexpected and indicates index " "corruption."; } throw JSONRPCError(err_code, errmsg); } UniValue ret(UniValue::VOBJ); ret.pushKV("filter", HexStr(filter.GetEncodedFilter())); ret.pushKV("header", filter_header.GetHex()); return ret; }, }; } /** * Serialize the UTXO set to a file for loading elsewhere. * * @see SnapshotMetadata */ static RPCHelpMan dumptxoutset() { return RPCHelpMan{ "dumptxoutset", "Write the serialized UTXO set to disk.\n", { {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "path to the output file. If relative, will be prefixed by " "datadir."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "coins_written", "the number of coins written in the snapshot"}, {RPCResult::Type::STR_HEX, "base_hash", "the hash of the base of the snapshot"}, {RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"}, {RPCResult::Type::STR, "path", "the absolute path that the snapshot was written to"}, }}, RPCExamples{HelpExampleCli("dumptxoutset", "utxo.dat")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { fs::path path = fs::absolute( fs::u8path(request.params[0].get_str()), GetDataDir()); // Write to a temporary path and then move into `path` on completion // to avoid confusion due to an interruption. fs::path temppath = fs::absolute( fs::u8path(request.params[0].get_str() + ".incomplete"), GetDataDir()); if (fs::exists(path)) { throw JSONRPCError(RPC_INVALID_PARAMETER, path.u8string() + " already exists. If you are sure this " "is what you want, " "move it out of the way first"); } FILE *file{fsbridge::fopen(temppath, "wb")}; CAutoFile afile{file, SER_DISK, CLIENT_VERSION}; std::unique_ptr<CCoinsViewCursor> pcursor; CCoinsStats stats; CBlockIndex *tip; NodeContext &node = EnsureNodeContext(request.context); { // We need to lock cs_main to ensure that the coinsdb isn't // written to between (i) flushing coins cache to disk // (coinsdb), (ii) getting stats based upon the coinsdb, and // (iii) constructing a cursor to the coinsdb for use below this // block. // // Cursors returned by leveldb iterate over snapshots, so the // contents of the pcursor will not be affected by simultaneous // writes during use below this block. // // See discussion here: // https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369 // LOCK(::cs_main); ::ChainstateActive().ForceFlushStateToDisk(); if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } pcursor = std::unique_ptr<CCoinsViewCursor>( ::ChainstateActive().CoinsDB().Cursor()); tip = LookupBlockIndex(stats.hashBlock); CHECK_NONFATAL(tip); } SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count, uint64_t(tip->GetChainTxCount())}; afile << metadata; COutPoint key; Coin coin; unsigned int iter{0}; while (pcursor->Valid()) { if (iter % 5000 == 0) { node.rpc_interruption_point(); } ++iter; if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { afile << key; afile << coin; } pcursor->Next(); } afile.fclose(); fs::rename(temppath, path); UniValue result(UniValue::VOBJ); result.pushKV("coins_written", stats.coins_count); result.pushKV("base_hash", tip->GetBlockHash().ToString()); result.pushKV("base_height", tip->nHeight); result.pushKV("path", path.u8string()); return result; }, }; } void RegisterBlockchainRPCCommands(CRPCTable &t) { // clang-format off static const CRPCCommand commands[] = { // category actor (function) // ------------------ ---------------------- { "blockchain", getbestblockhash, }, { "blockchain", getblock, }, { "blockchain", getblockchaininfo, }, { "blockchain", getblockcount, }, { "blockchain", getblockhash, }, { "blockchain", getblockheader, }, { "blockchain", getblockstats, }, { "blockchain", getchaintips, }, { "blockchain", getchaintxstats, }, { "blockchain", getdifficulty, }, { "blockchain", getmempoolancestors, }, { "blockchain", getmempooldescendants, }, { "blockchain", getmempoolentry, }, { "blockchain", getmempoolinfo, }, { "blockchain", getrawmempool, }, { "blockchain", gettxout, }, { "blockchain", gettxoutsetinfo, }, { "blockchain", pruneblockchain, }, { "blockchain", savemempool, }, { "blockchain", verifychain, }, { "blockchain", preciousblock, }, { "blockchain", scantxoutset, }, { "blockchain", getblockfilter, }, /* Not shown in help */ { "hidden", getfinalizedblockhash, }, { "hidden", finalizeblock, }, { "hidden", invalidateblock, }, { "hidden", parkblock, }, { "hidden", reconsiderblock, }, { "hidden", syncwithvalidationinterfacequeue, }, { "hidden", dumptxoutset, }, { "hidden", unparkblock, }, { "hidden", waitfornewblock, }, { "hidden", waitforblock, }, { "hidden", waitforblockheight, }, }; // clang-format on for (const auto &c : commands) { t.appendCommand(c.name, &c); } } diff --git a/src/script/sign.h b/src/script/sign.h index f2841867e..adde49c0b 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -1,202 +1,202 @@ // 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. #ifndef BITCOIN_SCRIPT_SIGN_H #define BITCOIN_SCRIPT_SIGN_H #include <coins.h> #include <hash.h> #include <pubkey.h> #include <script/interpreter.h> #include <script/keyorigin.h> #include <script/sighashtype.h> #include <streams.h> class CKey; class CKeyID; class CMutableTransaction; class CScript; class CScriptID; class CTransaction; class SigningProvider; /** Interface for signature creators. */ class BaseSignatureCreator { public: virtual ~BaseSignatureCreator() {} virtual const BaseSignatureChecker &Checker() const = 0; /** Create a singular (non-script) signature. */ virtual bool CreateSig(const SigningProvider &provider, std::vector<uint8_t> &vchSig, const CKeyID &keyid, const CScript &scriptCode) const = 0; }; /** A signature creator for transactions. */ class MutableTransactionSignatureCreator : public BaseSignatureCreator { const CMutableTransaction *txTo; unsigned int nIn; Amount amount; SigHashType sigHashType; const MutableTransactionSignatureChecker checker; public: MutableTransactionSignatureCreator( const CMutableTransaction *txToIn, unsigned int nInIn, const Amount &amountIn, SigHashType sigHashTypeIn = SigHashType()); const BaseSignatureChecker &Checker() const override { return checker; } bool CreateSig(const SigningProvider &provider, std::vector<uint8_t> &vchSig, const CKeyID &keyid, const CScript &scriptCode) const override; }; /** A signature creator that just produces 71-byte empty signatures. */ extern const BaseSignatureCreator &DUMMY_SIGNATURE_CREATOR; /** A signature creator that just produces 72-byte empty signatures. */ extern const BaseSignatureCreator &DUMMY_MAXIMUM_SIGNATURE_CREATOR; typedef std::pair<CPubKey, std::vector<uint8_t>> SigPair; // This struct contains information from a transaction input and also contains // signatures for that input. The information contained here can be used to // create a signature and is also filled by ProduceSignature in order to // construct final scriptSigs. struct SignatureData { /// Stores whether the scriptSig are complete. bool complete = false; /// The scriptSig of an input. Contains complete signatures or the /// traditional partial signatures format. CScript scriptSig; /// The redeemScript (if any) for the input. CScript redeem_script; /// BIP 174 style partial signatures for the input. May contain all /// signatures necessary for producing a final scriptSig. std::map<CKeyID, SigPair> signatures; std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> misc_pubkeys; /// KeyIDs of pubkeys which could not be found std::vector<CKeyID> missing_pubkeys; /// KeyIDs of pubkeys for signatures which could not be found std::vector<CKeyID> missing_sigs; /// ScriptID of the missing redeemScript (if any) uint160 missing_redeem_script; SignatureData() {} explicit SignatureData(const CScript &script) : scriptSig(script) {} void MergeSignatureData(SignatureData sigdata); }; // Takes a stream and multiple arguments and serializes them as if first // serialized into a vector and then into the stream. The resulting output into // the stream has the total serialized length of all of the objects followed by // all objects concatenated with each other. template <typename Stream, typename... X> -void SerializeToVector(Stream &s, const X &... args) { +void SerializeToVector(Stream &s, const X &...args) { WriteCompactSize(s, GetSerializeSizeMany(s.GetVersion(), args...)); SerializeMany(s, args...); } // Takes a stream and multiple arguments and unserializes them first as a vector // then each object individually in the order provided in the arguments. template <typename Stream, typename... X> -void UnserializeFromVector(Stream &s, X &... args) { +void UnserializeFromVector(Stream &s, X &...args) { size_t expected_size = ReadCompactSize(s); size_t remaining_before = s.size(); UnserializeMany(s, args...); size_t remaining_after = s.size(); if (remaining_after + expected_size != remaining_before) { throw std::ios_base::failure("Size of value was not the stated size"); } } // Deserialize HD keypaths into a map template <typename Stream> void DeserializeHDKeypaths(Stream &s, const std::vector<uint8_t> &key, std::map<CPubKey, KeyOriginInfo> &hd_keypaths) { // Make sure that the key is the size of pubkey + 1 if (key.size() != CPubKey::SIZE + 1 && key.size() != CPubKey::COMPRESSED_SIZE + 1) { throw std::ios_base::failure( "Size of key was not the expected size for the type BIP32 keypath"); } // Read in the pubkey from key CPubKey pubkey(key.begin() + 1, key.end()); if (!pubkey.IsFullyValid()) { throw std::ios_base::failure("Invalid pubkey"); } if (hd_keypaths.count(pubkey) > 0) { throw std::ios_base::failure( "Duplicate Key, pubkey derivation path already provided"); } // Read in key path uint64_t value_len = ReadCompactSize(s); if (value_len % 4 || value_len == 0) { throw std::ios_base::failure("Invalid length for HD key path"); } KeyOriginInfo keypath; s >> keypath.fingerprint; for (unsigned int i = 4; i < value_len; i += sizeof(uint32_t)) { uint32_t index; s >> index; keypath.path.push_back(index); } // Add to map hd_keypaths.emplace(pubkey, std::move(keypath)); } // Serialize HD keypaths to a stream from a map template <typename Stream> void SerializeHDKeypaths(Stream &s, const std::map<CPubKey, KeyOriginInfo> &hd_keypaths, uint8_t type) { for (auto keypath_pair : hd_keypaths) { if (!keypath_pair.first.IsValid()) { throw std::ios_base::failure("Invalid CPubKey being serialized"); } SerializeToVector(s, type, MakeSpan(keypath_pair.first)); WriteCompactSize(s, (keypath_pair.second.path.size() + 1) * sizeof(uint32_t)); s << keypath_pair.second.fingerprint; for (const auto &path : keypath_pair.second.path) { s << path; } } } /** Produce a script signature using a generic signature creator. */ bool ProduceSignature(const SigningProvider &provider, const BaseSignatureCreator &creator, const CScript &scriptPubKey, SignatureData &sigdata); /** Produce a script signature for a transaction. */ bool SignSignature(const SigningProvider &provider, const CScript &fromPubKey, CMutableTransaction &txTo, unsigned int nIn, const Amount amount, SigHashType sigHashType); bool SignSignature(const SigningProvider &provider, const CTransaction &txFrom, CMutableTransaction &txTo, unsigned int nIn, SigHashType sigHashType); /** Extract signature data from a transaction input, and insert it. */ SignatureData DataFromTransaction(const CMutableTransaction &tx, unsigned int nIn, const CTxOut &txout); void UpdateInput(CTxIn &input, const SignatureData &data); /** * Check whether we know how to sign for an output like this, assuming we have * all private keys. While this function does not need private keys, the passed * keystore is used to look up public keys and redeemscripts by hash. * Solvability is unrelated to whether we consider this output to be ours. */ bool IsSolvable(const SigningProvider &provider, const CScript &script); /** Sign the CMutableTransaction */ bool SignTransaction(CMutableTransaction &mtx, const SigningProvider *provider, const std::map<COutPoint, Coin> &coins, SigHashType sigHashType, std::map<int, std::string> &input_errors); #endif // BITCOIN_SCRIPT_SIGN_H diff --git a/src/seeder/messagewriter.h b/src/seeder/messagewriter.h index 782507137..fbe423188 100644 --- a/src/seeder/messagewriter.h +++ b/src/seeder/messagewriter.h @@ -1,37 +1,37 @@ // Copyright (c) 2020 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_SEEDER_MESSAGEWRITER_H #define BITCOIN_SEEDER_MESSAGEWRITER_H #include <config.h> #include <net.h> #include <netmessagemaker.h> namespace MessageWriter { template <typename... Args> static void WriteMessage(CDataStream &stream, std::string command, - Args &&... args) { + Args &&...args) { CSerializedNetMsg payload = CNetMsgMaker(stream.GetVersion()) .Make(command, std::forward<Args>(args)...); size_t nMessageSize = payload.data.size(); // Serialize header std::vector<uint8_t> serializedHeader; V1TransportSerializer serializer = V1TransportSerializer(); serializer.prepareForTransport(GetConfig(), payload, serializedHeader); // Write message header + payload to outgoing stream stream.write(reinterpret_cast<const char *>(serializedHeader.data()), serializedHeader.size()); if (nMessageSize) { stream.write(reinterpret_cast<const char *>(payload.data.data()), nMessageSize); } } } // namespace MessageWriter #endif // BITCOIN_SEEDER_MESSAGEWRITER_H diff --git a/src/seeder/test/message_writer_tests.cpp b/src/seeder/test/message_writer_tests.cpp index 4bb2b20b7..e7480acd1 100644 --- a/src/seeder/test/message_writer_tests.cpp +++ b/src/seeder/test/message_writer_tests.cpp @@ -1,94 +1,94 @@ // Copyright (c) 2020 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <hash.h> #include <primitives/block.h> #include <protocol.h> #include <seeder/messagewriter.h> #include <streams.h> #include <version.h> #include <boost/test/unit_test.hpp> #include <string> #include <vector> BOOST_AUTO_TEST_SUITE(message_writer_tests) template <typename... Args> static void CheckMessage(CDataStream &expectedMessage, std::string command, - Args &&... args) { + Args &&...args) { CDataStream message(SER_NETWORK, PROTOCOL_VERSION); MessageWriter::WriteMessage(message, command, std::forward<Args>(args)...); BOOST_CHECK_EQUAL(message.size(), expectedMessage.size()); for (size_t i = 0; i < message.size(); i++) { BOOST_CHECK_EQUAL(message[i], expectedMessage[i]); } } BOOST_AUTO_TEST_CASE(simple_header_and_payload_message_writer_test) { SelectParams(CBaseChainParams::MAIN); int64_t now = GetTime(); uint64_t nonce = 0; uint64_t serviceFlags = uint64_t(ServiceFlags(NODE_NETWORK)); CService service; CAddress addrTo(service, ServiceFlags(NODE_NETWORK)); CAddress addrFrom(service, ServiceFlags(NODE_NETWORK)); std::string user_agent = "/Bitcoin ABC:0.0.0(seeder)/"; int start_height = 1; CDataStream versionPayload(SER_NETWORK, PROTOCOL_VERSION); versionPayload << PROTOCOL_VERSION << serviceFlags << now << addrTo << addrFrom << nonce << user_agent << start_height; CMessageHeader versionhdr(Params().NetMagic(), NetMsgType::VERSION, versionPayload.size()); uint256 hash = Hash(versionPayload); memcpy(versionhdr.pchChecksum, hash.begin(), CMessageHeader::CHECKSUM_SIZE); CDataStream expectedVersion(SER_NETWORK, PROTOCOL_VERSION); expectedVersion << versionhdr; expectedVersion += versionPayload; CheckMessage(expectedVersion, NetMsgType::VERSION, PROTOCOL_VERSION, serviceFlags, now, addrTo, addrFrom, nonce, user_agent, start_height); } BOOST_AUTO_TEST_CASE(header_empty_payload_message_writer_test) { SelectParams(CBaseChainParams::MAIN); CMessageHeader verackHeader(Params().NetMagic(), NetMsgType::VERACK, 0); CDataStream expectedVerack(SER_NETWORK, PROTOCOL_VERSION); // This is an empty payload, but is still necessary for the checksum std::vector<uint8_t> payload; uint256 hash = Hash(payload); memcpy(verackHeader.pchChecksum, hash.begin(), CMessageHeader::CHECKSUM_SIZE); expectedVerack << verackHeader; CheckMessage(expectedVerack, NetMsgType::VERACK); } BOOST_AUTO_TEST_CASE(write_getheaders_message_test) { SelectParams(CBaseChainParams::MAIN); CDataStream payload(SER_NETWORK, PROTOCOL_VERSION); BlockHash bhash(uint256S( "0000000099f5509b5f36b1926bcf82b21d936ebeadee811030dfbbb7fae915d7")); std::vector<BlockHash> vlocator(1, bhash); CBlockLocator locatorhash(vlocator); payload << locatorhash << uint256(); uint256 hash = Hash(payload); CMessageHeader msgHeader(Params().NetMagic(), NetMsgType::GETHEADERS, payload.size()); memcpy(msgHeader.pchChecksum, hash.begin(), CMessageHeader::CHECKSUM_SIZE); CDataStream expectedMsg(SER_NETWORK, PROTOCOL_VERSION); expectedMsg << msgHeader; expectedMsg += payload; CheckMessage(expectedMsg, NetMsgType::GETHEADERS, locatorhash, uint256()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/serialize.h b/src/serialize.h index aab557da4..3550b7443 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -1,1193 +1,1193 @@ // 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. #ifndef BITCOIN_SERIALIZE_H #define BITCOIN_SERIALIZE_H #include <compat/endian.h> #include <prevector.h> #include <span.h> #include <algorithm> #include <array> #include <cstdint> #include <cstring> #include <ios> #include <limits> #include <map> #include <memory> #include <set> #include <string> #include <utility> #include <vector> /** * The maximum size of a serialized object in bytes or number of elements * (for eg vectors) when the size is encoded as CompactSize. */ static constexpr uint64_t MAX_SIZE = 0x02000000; /** * Maximum amount of memory (in bytes) to allocate at once when deserializing * vectors. */ static const unsigned int MAX_VECTOR_ALLOCATE = 5000000; /** * Dummy data type to identify deserializing constructors. * * By convention, a constructor of a type T with signature * * template <typename Stream> T::T(deserialize_type, Stream& s) * * is a deserializing constructor, which builds the type by deserializing it * from s. If T contains const fields, this is likely the only way to do so. */ struct deserialize_type {}; constexpr deserialize_type deserialize{}; //! Safely convert odd char pointer types to standard ones. inline char *CharCast(char *c) { return c; } inline char *CharCast(uint8_t *c) { return (char *)c; } inline const char *CharCast(const char *c) { return c; } inline const char *CharCast(const uint8_t *c) { return (const char *)c; } /** * Lowest-level serialization and conversion. * @note Sizes of these types are verified in the tests */ template <typename Stream> inline void ser_writedata8(Stream &s, uint8_t obj) { s.write((char *)&obj, 1); } template <typename Stream> inline void ser_writedata16(Stream &s, uint16_t obj) { obj = htole16(obj); s.write((char *)&obj, 2); } template <typename Stream> inline void ser_writedata16be(Stream &s, uint16_t obj) { obj = htobe16(obj); s.write((char *)&obj, 2); } template <typename Stream> inline void ser_writedata32(Stream &s, uint32_t obj) { obj = htole32(obj); s.write((char *)&obj, 4); } template <typename Stream> inline void ser_writedata32be(Stream &s, uint32_t obj) { obj = htobe32(obj); s.write((char *)&obj, 4); } template <typename Stream> inline void ser_writedata64(Stream &s, uint64_t obj) { obj = htole64(obj); s.write((char *)&obj, 8); } template <typename Stream> inline uint8_t ser_readdata8(Stream &s) { uint8_t obj; s.read((char *)&obj, 1); return obj; } template <typename Stream> inline uint16_t ser_readdata16(Stream &s) { uint16_t obj; s.read((char *)&obj, 2); return le16toh(obj); } template <typename Stream> inline uint16_t ser_readdata16be(Stream &s) { uint16_t obj; s.read((char *)&obj, 2); return be16toh(obj); } template <typename Stream> inline uint32_t ser_readdata32(Stream &s) { uint32_t obj; s.read((char *)&obj, 4); return le32toh(obj); } template <typename Stream> inline uint32_t ser_readdata32be(Stream &s) { uint32_t obj; s.read((char *)&obj, 4); return be32toh(obj); } template <typename Stream> inline uint64_t ser_readdata64(Stream &s) { uint64_t obj; s.read((char *)&obj, 8); return le64toh(obj); } inline uint64_t ser_double_to_uint64(double x) { uint64_t tmp; std::memcpy(&tmp, &x, sizeof(x)); static_assert(sizeof(tmp) == sizeof(x), "double and uint64_t assumed to have the same size"); return tmp; } inline uint32_t ser_float_to_uint32(float x) { uint32_t tmp; std::memcpy(&tmp, &x, sizeof(x)); static_assert(sizeof(tmp) == sizeof(x), "float and uint32_t assumed to have the same size"); return tmp; } inline double ser_uint64_to_double(uint64_t y) { double tmp; std::memcpy(&tmp, &y, sizeof(y)); static_assert(sizeof(tmp) == sizeof(y), "double and uint64_t assumed to have the same size"); return tmp; } inline float ser_uint32_to_float(uint32_t y) { float tmp; std::memcpy(&tmp, &y, sizeof(y)); static_assert(sizeof(tmp) == sizeof(y), "float and uint32_t assumed to have the same size"); return tmp; } ///////////////////////////////////////////////////////////////// // // Templates for serializing to anything that looks like a stream, // i.e. anything that supports .read(char*, size_t) and .write(char*, size_t) // class CSizeComputer; enum { // primary actions SER_NETWORK = (1 << 0), SER_DISK = (1 << 1), SER_GETHASH = (1 << 2), }; //! Convert the reference base type to X, without changing constness or //! reference type. template <typename X> X &ReadWriteAsHelper(X &x) { return x; } template <typename X> const X &ReadWriteAsHelper(const X &x) { return x; } #define READWRITE(...) (::SerReadWriteMany(s, ser_action, __VA_ARGS__)) #define READWRITEAS(type, obj) \ (::SerReadWriteMany(s, ser_action, ReadWriteAsHelper<type>(obj))) #define SER_READ(obj, code) \ ::SerRead( \ s, ser_action, obj, \ [&](Stream &s, typename std::remove_const<Type>::type &obj) { code; }) #define SER_WRITE(obj, code) \ ::SerWrite(s, ser_action, obj, [&](Stream &s, const Type &obj) { code; }) /** * Implement the Ser and Unser methods needed for implementing a formatter * (see Using below). * * Both Ser and Unser are delegated to a single static method SerializationOps, * which is polymorphic in the serialized/deserialized type (allowing it to be * const when serializing, and non-const when deserializing). * * Example use: * struct FooFormatter { * FORMATTER_METHODS(Class, obj) { READWRITE(obj.val1, VARINT(obj.val2)); } * } * would define a class FooFormatter that defines a serialization of Class * objects consisting of serializing its val1 member using the default * serialization, and its val2 member using VARINT serialization. That * FooFormatter can then be used in statements like * READWRITE(Using<FooFormatter>(obj.bla)). */ #define FORMATTER_METHODS(cls, obj) \ template <typename Stream> static void Ser(Stream &s, const cls &obj) { \ SerializationOps(obj, s, CSerActionSerialize()); \ } \ template <typename Stream> static void Unser(Stream &s, cls &obj) { \ SerializationOps(obj, s, CSerActionUnserialize()); \ } \ template <typename Stream, typename Type, typename Operation> \ static inline void SerializationOps(Type &obj, Stream &s, \ Operation ser_action) /** * Implement the Serialize and Unserialize methods by delegating to a * single templated static method that takes the to-be-(de)serialized * object as a parameter. This approach has the advantage that the * constness of the object becomes a template parameter, and thus * allows a single implementation that sees the object as const for * serializing and non-const for deserializing, without casts. */ #define SERIALIZE_METHODS(cls, obj) \ template <typename Stream> void Serialize(Stream &s) const { \ static_assert(std::is_same<const cls &, decltype(*this)>::value, \ "Serialize type mismatch"); \ Ser(s, *this); \ } \ template <typename Stream> void Unserialize(Stream &s) { \ static_assert(std::is_same<cls &, decltype(*this)>::value, \ "Unserialize type mismatch"); \ Unser(s, *this); \ } \ FORMATTER_METHODS(cls, obj) #ifndef CHAR_EQUALS_INT8 // TODO Get rid of bare char template <typename Stream> inline void Serialize(Stream &s, char a) { ser_writedata8(s, a); } #endif template <typename Stream> inline void Serialize(Stream &s, int8_t a) { ser_writedata8(s, a); } template <typename Stream> inline void Serialize(Stream &s, uint8_t a) { ser_writedata8(s, a); } template <typename Stream> inline void Serialize(Stream &s, int16_t a) { ser_writedata16(s, a); } template <typename Stream> inline void Serialize(Stream &s, uint16_t a) { ser_writedata16(s, a); } template <typename Stream> inline void Serialize(Stream &s, int32_t a) { ser_writedata32(s, a); } template <typename Stream> inline void Serialize(Stream &s, uint32_t a) { ser_writedata32(s, a); } template <typename Stream> inline void Serialize(Stream &s, int64_t a) { ser_writedata64(s, a); } template <typename Stream> inline void Serialize(Stream &s, uint64_t a) { ser_writedata64(s, a); } template <typename Stream> inline void Serialize(Stream &s, float a) { ser_writedata32(s, ser_float_to_uint32(a)); } template <typename Stream> inline void Serialize(Stream &s, double a) { ser_writedata64(s, ser_double_to_uint64(a)); } template <typename Stream, size_t N> inline void Serialize(Stream &s, const int8_t (&a)[N]) { s.write(a, N); } template <typename Stream, size_t N> inline void Serialize(Stream &s, const uint8_t (&a)[N]) { s.write(CharCast(a), N); } template <typename Stream, size_t N> inline void Serialize(Stream &s, const std::array<int8_t, N> &a) { s.write(a.data(), N); } template <typename Stream, size_t N> inline void Serialize(Stream &s, const std::array<uint8_t, N> &a) { s.write(CharCast(a.data()), N); } #ifndef CHAR_EQUALS_INT8 // TODO Get rid of bare char template <typename Stream> inline void Unserialize(Stream &s, char &a) { a = ser_readdata8(s); } template <typename Stream, size_t N> inline void Serialize(Stream &s, const char (&a)[N]) { s.write(a, N); } template <typename Stream, size_t N> inline void Serialize(Stream &s, const std::array<char, N> &a) { s.write(a.data(), N); } #endif template <typename Stream> inline void Serialize(Stream &s, const Span<const uint8_t> &span) { s.write(CharCast(span.data()), span.size()); } template <typename Stream> inline void Serialize(Stream &s, const Span<uint8_t> &span) { s.write(CharCast(span.data()), span.size()); } template <typename Stream> inline void Unserialize(Stream &s, int8_t &a) { a = ser_readdata8(s); } template <typename Stream> inline void Unserialize(Stream &s, uint8_t &a) { a = ser_readdata8(s); } template <typename Stream> inline void Unserialize(Stream &s, int16_t &a) { a = ser_readdata16(s); } template <typename Stream> inline void Unserialize(Stream &s, uint16_t &a) { a = ser_readdata16(s); } template <typename Stream> inline void Unserialize(Stream &s, int32_t &a) { a = ser_readdata32(s); } template <typename Stream> inline void Unserialize(Stream &s, uint32_t &a) { a = ser_readdata32(s); } template <typename Stream> inline void Unserialize(Stream &s, int64_t &a) { a = ser_readdata64(s); } template <typename Stream> inline void Unserialize(Stream &s, uint64_t &a) { a = ser_readdata64(s); } template <typename Stream> inline void Unserialize(Stream &s, float &a) { a = ser_uint32_to_float(ser_readdata32(s)); } template <typename Stream> inline void Unserialize(Stream &s, double &a) { a = ser_uint64_to_double(ser_readdata64(s)); } template <typename Stream, size_t N> inline void Unserialize(Stream &s, int8_t (&a)[N]) { s.read(a, N); } template <typename Stream, size_t N> inline void Unserialize(Stream &s, uint8_t (&a)[N]) { s.read(CharCast(a), N); } template <typename Stream, size_t N> inline void Unserialize(Stream &s, std::array<int8_t, N> &a) { s.read(a.data(), N); } template <typename Stream, size_t N> inline void Unserialize(Stream &s, std::array<uint8_t, N> &a) { s.read(CharCast(a.data()), N); } #ifndef CHAR_EQUALS_INT8 template <typename Stream, size_t N> inline void Unserialize(Stream &s, char (&a)[N]) { s.read(CharCast(a), N); } template <typename Stream, size_t N> inline void Unserialize(Stream &s, std::array<char, N> &a) { s.read(CharCast(a.data()), N); } #endif template <typename Stream> inline void Serialize(Stream &s, bool a) { char f = a; ser_writedata8(s, f); } template <typename Stream> inline void Unserialize(Stream &s, bool &a) { char f = ser_readdata8(s); a = f; } template <typename Stream> inline void Unserialize(Stream &s, Span<uint8_t> &span) { s.read(CharCast(span.data()), span.size()); } /** * Compact Size * size < 253 -- 1 byte * size <= USHRT_MAX -- 3 bytes (253 + 2 bytes) * size <= UINT_MAX -- 5 bytes (254 + 4 bytes) * size > UINT_MAX -- 9 bytes (255 + 8 bytes) */ inline uint32_t GetSizeOfCompactSize(uint64_t nSize) { if (nSize < 253) { return sizeof(uint8_t); } if (nSize <= std::numeric_limits<uint16_t>::max()) { return sizeof(uint8_t) + sizeof(uint16_t); } if (nSize <= std::numeric_limits<uint32_t>::max()) { return sizeof(uint8_t) + sizeof(uint32_t); } return sizeof(uint8_t) + sizeof(uint64_t); } inline void WriteCompactSize(CSizeComputer &os, uint64_t nSize); template <typename Stream> void WriteCompactSize(Stream &os, uint64_t nSize) { if (nSize < 253) { ser_writedata8(os, nSize); } else if (nSize <= std::numeric_limits<uint16_t>::max()) { ser_writedata8(os, 253); ser_writedata16(os, nSize); } else if (nSize <= std::numeric_limits<uint32_t>::max()) { ser_writedata8(os, 254); ser_writedata32(os, nSize); } else { ser_writedata8(os, 255); ser_writedata64(os, nSize); } return; } /** * Decode a CompactSize-encoded variable-length integer. * * As these are primarily used to encode the size of vector-like serializations, * by default a range check is performed. When used as a generic number * encoding, range_check should be set to false. */ template <typename Stream> uint64_t ReadCompactSize(Stream &is, bool range_check = true) { uint8_t chSize = ser_readdata8(is); uint64_t nSizeRet = 0; if (chSize < 253) { nSizeRet = chSize; } else if (chSize == 253) { nSizeRet = ser_readdata16(is); if (nSizeRet < 253) { throw std::ios_base::failure("non-canonical ReadCompactSize()"); } } else if (chSize == 254) { nSizeRet = ser_readdata32(is); if (nSizeRet < 0x10000u) { throw std::ios_base::failure("non-canonical ReadCompactSize()"); } } else { nSizeRet = ser_readdata64(is); if (nSizeRet < 0x100000000ULL) { throw std::ios_base::failure("non-canonical ReadCompactSize()"); } } if (range_check && nSizeRet > MAX_SIZE) { throw std::ios_base::failure("ReadCompactSize(): size too large"); } return nSizeRet; } /** * Variable-length integers: bytes are a MSB base-128 encoding of the number. * The high bit in each byte signifies whether another digit follows. To make * sure the encoding is one-to-one, one is subtracted from all but the last * digit. Thus, the byte sequence a[] with length len, where all but the last * byte has bit 128 set, encodes the number: * * (a[len-1] & 0x7F) + sum(i=1..len-1, 128^i*((a[len-i-1] & 0x7F)+1)) * * Properties: * * Very small (0-127: 1 byte, 128-16511: 2 bytes, 16512-2113663: 3 bytes) * * Every integer has exactly one encoding * * Encoding does not depend on size of original integer type * * No redundancy: every (infinite) byte sequence corresponds to a list * of encoded integers. * * 0: [0x00] 256: [0x81 0x00] * 1: [0x01] 16383: [0xFE 0x7F] * 127: [0x7F] 16384: [0xFF 0x00] * 128: [0x80 0x00] 16511: [0xFF 0x7F] * 255: [0x80 0x7F] 65535: [0x82 0xFE 0x7F] * 2^32: [0x8E 0xFE 0xFE 0xFF 0x00] */ /** * Mode for encoding VarInts. * * Currently there is no support for signed encodings. The default mode will not * compile with signed values, and the legacy "nonnegative signed" mode will * accept signed values, but improperly encode and decode them if they are * negative. In the future, the DEFAULT mode could be extended to support * negative numbers in a backwards compatible way, and additional modes could be * added to support different varint formats (e.g. zigzag encoding). */ enum class VarIntMode { DEFAULT, NONNEGATIVE_SIGNED }; template <VarIntMode Mode, typename I> struct CheckVarIntMode { constexpr CheckVarIntMode() { static_assert(Mode != VarIntMode::DEFAULT || std::is_unsigned<I>::value, "Unsigned type required with mode DEFAULT."); static_assert(Mode != VarIntMode::NONNEGATIVE_SIGNED || std::is_signed<I>::value, "Signed type required with mode NONNEGATIVE_SIGNED."); } }; template <VarIntMode Mode, typename I> inline unsigned int GetSizeOfVarInt(I n) { CheckVarIntMode<Mode, I>(); int nRet = 0; while (true) { nRet++; if (n <= 0x7F) { return nRet; } n = (n >> 7) - 1; } } template <typename I> inline void WriteVarInt(CSizeComputer &os, I n); template <typename Stream, VarIntMode Mode, typename I> void WriteVarInt(Stream &os, I n) { CheckVarIntMode<Mode, I>(); uint8_t tmp[(sizeof(n) * 8 + 6) / 7]; int len = 0; while (true) { tmp[len] = (n & 0x7F) | (len ? 0x80 : 0x00); if (n <= 0x7F) { break; } n = (n >> 7) - 1; len++; } do { ser_writedata8(os, tmp[len]); } while (len--); } template <typename Stream, VarIntMode Mode, typename I> I ReadVarInt(Stream &is) { CheckVarIntMode<Mode, I>(); I n = 0; while (true) { uint8_t chData = ser_readdata8(is); if (n > (std::numeric_limits<I>::max() >> 7)) { throw std::ios_base::failure("ReadVarInt(): size too large"); } n = (n << 7) | (chData & 0x7F); if ((chData & 0x80) == 0) { return n; } if (n == std::numeric_limits<I>::max()) { throw std::ios_base::failure("ReadVarInt(): size too large"); } n++; } } /** * Simple wrapper class to serialize objects using a formatter; used by * Using(). */ template <typename Formatter, typename T> class Wrapper { static_assert(std::is_lvalue_reference<T>::value, "Wrapper needs an lvalue reference type T"); protected: T m_object; public: explicit Wrapper(T obj) : m_object(obj) {} template <typename Stream> void Serialize(Stream &s) const { Formatter().Ser(s, m_object); } template <typename Stream> void Unserialize(Stream &s) { Formatter().Unser(s, m_object); } }; /** * Cause serialization/deserialization of an object to be done using a * specified formatter class. * * To use this, you need a class Formatter that has public functions Ser(stream, * const object&) for serialization, and Unser(stream, object&) for * deserialization. Serialization routines (inside READWRITE, or directly with * << and >> operators), can then use Using<Formatter>(object). * * This works by constructing a Wrapper<Formatter, T>-wrapped version of object, * where T is const during serialization, and non-const during deserialization, * which maintains const correctness. */ template <typename Formatter, typename T> static inline Wrapper<Formatter, T &> Using(T &&t) { return Wrapper<Formatter, T &>(t); } #define VARINT_MODE(obj, mode) Using<VarIntFormatter<mode>>(obj) #define VARINT(obj) Using<VarIntFormatter<VarIntMode::DEFAULT>>(obj) #define COMPACTSIZE(obj) Using<CompactSizeFormatter<true>>(obj) #define LIMITED_STRING(obj, n) Using<LimitedStringFormatter<n>>(obj) /** * Serialization wrapper class for integers in VarInt format. */ template <VarIntMode Mode> struct VarIntFormatter { template <typename Stream, typename I> void Ser(Stream &s, I v) { WriteVarInt<Stream, Mode, typename std::remove_cv<I>::type>(s, v); } template <typename Stream, typename I> void Unser(Stream &s, I &v) { v = ReadVarInt<Stream, Mode, typename std::remove_cv<I>::type>(s); } }; /** * Serialization wrapper class for custom integers and enums. * * It permits specifying the serialized size (1 to 8 bytes) and endianness. * * Use the big endian mode for values that are stored in memory in native * byte order, but serialized in big endian notation. This is only intended * to implement serializers that are compatible with existing formats, and * its use is not recommended for new data structures. */ template <int Bytes, bool BigEndian = false> struct CustomUintFormatter { static_assert(Bytes > 0 && Bytes <= 8, "CustomUintFormatter Bytes out of range"); static constexpr uint64_t MAX = 0xffffffffffffffff >> (8 * (8 - Bytes)); template <typename Stream, typename I> void Ser(Stream &s, I v) { if (v < 0 || v > MAX) { throw std::ios_base::failure( "CustomUintFormatter value out of range"); } if (BigEndian) { uint64_t raw = htobe64(v); s.write(((const char *)&raw) + 8 - Bytes, Bytes); } else { uint64_t raw = htole64(v); s.write((const char *)&raw, Bytes); } } template <typename Stream, typename I> void Unser(Stream &s, I &v) { using U = typename std::conditional<std::is_enum<I>::value, std::underlying_type<I>, std::common_type<I>>::type::type; static_assert(std::numeric_limits<U>::max() >= MAX && std::numeric_limits<U>::min() <= 0, "Assigned type too small"); uint64_t raw = 0; if (BigEndian) { s.read(((char *)&raw) + 8 - Bytes, Bytes); v = static_cast<I>(be64toh(raw)); } else { s.read((char *)&raw, Bytes); v = static_cast<I>(le64toh(raw)); } } }; template <int Bytes> using BigEndianFormatter = CustomUintFormatter<Bytes, true>; /** Formatter for integers in CompactSize format. */ template <bool RangeCheck> struct CompactSizeFormatter { template <typename Stream, typename I> void Unser(Stream &s, I &v) { uint64_t n = ReadCompactSize<Stream>(s, RangeCheck); if (n < std::numeric_limits<I>::min() || n > std::numeric_limits<I>::max()) { throw std::ios_base::failure("CompactSize exceeds limit of type"); } v = n; } template <typename Stream, typename I> void Ser(Stream &s, I v) { static_assert(std::is_unsigned<I>::value, "CompactSize only supported for unsigned integers"); static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max(), "CompactSize only supports 64-bit integers and below"); WriteCompactSize<Stream>(s, v); } }; template <size_t Limit> struct LimitedStringFormatter { template <typename Stream> void Unser(Stream &s, std::string &v) { size_t size = ReadCompactSize(s); if (size > Limit) { throw std::ios_base::failure("String length limit exceeded"); } v.resize(size); if (size != 0) { s.read((char *)v.data(), size); } } template <typename Stream> void Ser(Stream &s, const std::string &v) { s << v; } }; /** * Formatter to serialize/deserialize vector elements using another formatter * * Example: * struct X { * std::vector<uint64_t> v; * SERIALIZE_METHODS(X, obj) { * READWRITE(Using<VectorFormatter<VarInt>>(obj.v)); * } * }; * will define a struct that contains a vector of uint64_t, which is serialized * as a vector of VarInt-encoded integers. * * V is not required to be an std::vector type. It works for any class that * exposes a value_type, size, reserve, emplace_back, back, and const iterators. */ template <class Formatter> struct VectorFormatter { template <typename Stream, typename V> void Ser(Stream &s, const V &v) { Formatter formatter; WriteCompactSize(s, v.size()); for (const typename V::value_type &elem : v) { formatter.Ser(s, elem); } } template <typename Stream, typename V> void Unser(Stream &s, V &v) { Formatter formatter; v.clear(); size_t size = ReadCompactSize(s); size_t allocated = 0; while (allocated < size) { // For DoS prevention, do not blindly allocate as much as the stream // claims to contain. Instead, allocate in 5MiB batches, so that an // attacker actually needs to provide X MiB of data to make us // allocate X+5 Mib. static_assert(sizeof(typename V::value_type) <= MAX_VECTOR_ALLOCATE, "Vector element size too large"); allocated = std::min(size, allocated + MAX_VECTOR_ALLOCATE / sizeof(typename V::value_type)); v.reserve(allocated); while (v.size() < allocated) { v.emplace_back(); formatter.Unser(s, v.back()); } } }; }; /** * Forward declarations */ /** * string */ template <typename Stream, typename C> void Serialize(Stream &os, const std::basic_string<C> &str); template <typename Stream, typename C> void Unserialize(Stream &is, std::basic_string<C> &str); /** * prevector * prevectors of uint8_t are a special case and are intended to be serialized as * a single opaque blob. */ template <typename Stream, unsigned int N, typename T> void Serialize_impl(Stream &os, const prevector<N, T> &v, const uint8_t &); template <typename Stream, unsigned int N, typename T, typename V> void Serialize_impl(Stream &os, const prevector<N, T> &v, const V &); template <typename Stream, unsigned int N, typename T> inline void Serialize(Stream &os, const prevector<N, T> &v); template <typename Stream, unsigned int N, typename T> void Unserialize_impl(Stream &is, prevector<N, T> &v, const uint8_t &); template <typename Stream, unsigned int N, typename T, typename V> void Unserialize_impl(Stream &is, prevector<N, T> &v, const V &); template <typename Stream, unsigned int N, typename T> inline void Unserialize(Stream &is, prevector<N, T> &v); /** * vector * vectors of uint8_t are a special case and are intended to be serialized as a * single opaque blob. */ template <typename Stream, typename T, typename A> void Serialize_impl(Stream &os, const std::vector<T, A> &v, const uint8_t &); template <typename Stream, typename T, typename A> void Serialize_impl(Stream &os, const std::vector<T, A> &v, const bool &); template <typename Stream, typename T, typename A, typename V> void Serialize_impl(Stream &os, const std::vector<T, A> &v, const V &); template <typename Stream, typename T, typename A> inline void Serialize(Stream &os, const std::vector<T, A> &v); template <typename Stream, typename T, typename A> void Unserialize_impl(Stream &is, std::vector<T, A> &v, const uint8_t &); template <typename Stream, typename T, typename A, typename V> void Unserialize_impl(Stream &is, std::vector<T, A> &v, const V &); template <typename Stream, typename T, typename A> inline void Unserialize(Stream &is, std::vector<T, A> &v); /** * pair */ template <typename Stream, typename K, typename T> void Serialize(Stream &os, const std::pair<K, T> &item); template <typename Stream, typename K, typename T> void Unserialize(Stream &is, std::pair<K, T> &item); /** * map */ template <typename Stream, typename K, typename T, typename Pred, typename A> void Serialize(Stream &os, const std::map<K, T, Pred, A> &m); template <typename Stream, typename K, typename T, typename Pred, typename A> void Unserialize(Stream &is, std::map<K, T, Pred, A> &m); /** * set */ template <typename Stream, typename K, typename Pred, typename A> void Serialize(Stream &os, const std::set<K, Pred, A> &m); template <typename Stream, typename K, typename Pred, typename A> void Unserialize(Stream &is, std::set<K, Pred, A> &m); /** * shared_ptr */ template <typename Stream, typename T> void Serialize(Stream &os, const std::shared_ptr<const T> &p); template <typename Stream, typename T> void Unserialize(Stream &os, std::shared_ptr<const T> &p); /** * unique_ptr */ template <typename Stream, typename T> void Serialize(Stream &os, const std::unique_ptr<const T> &p); template <typename Stream, typename T> void Unserialize(Stream &os, std::unique_ptr<const T> &p); /** * If none of the specialized versions above matched, default to calling member * function. */ template <typename Stream, typename T> inline void Serialize(Stream &os, const T &a) { a.Serialize(os); } template <typename Stream, typename T> inline void Unserialize(Stream &is, T &&a) { a.Unserialize(is); } /** * Default formatter. Serializes objects as themselves. * * The vector/prevector serialization code passes this to VectorFormatter * to enable reusing that logic. It shouldn't be needed elsewhere. */ struct DefaultFormatter { template <typename Stream, typename T> static void Ser(Stream &s, const T &t) { Serialize(s, t); } template <typename Stream, typename T> static void Unser(Stream &s, T &t) { Unserialize(s, t); } }; /** * string */ template <typename Stream, typename C> void Serialize(Stream &os, const std::basic_string<C> &str) { WriteCompactSize(os, str.size()); if (!str.empty()) { os.write((char *)str.data(), str.size() * sizeof(C)); } } template <typename Stream, typename C> void Unserialize(Stream &is, std::basic_string<C> &str) { size_t nSize = ReadCompactSize(is); str.resize(nSize); if (nSize != 0) { is.read((char *)str.data(), nSize * sizeof(C)); } } /** * prevector */ template <typename Stream, unsigned int N, typename T> void Serialize_impl(Stream &os, const prevector<N, T> &v, const uint8_t &) { WriteCompactSize(os, v.size()); if (!v.empty()) { os.write((char *)v.data(), v.size() * sizeof(T)); } } template <typename Stream, unsigned int N, typename T, typename V> void Serialize_impl(Stream &os, const prevector<N, T> &v, const V &) { Serialize(os, Using<VectorFormatter<DefaultFormatter>>(v)); } template <typename Stream, unsigned int N, typename T> inline void Serialize(Stream &os, const prevector<N, T> &v) { Serialize_impl(os, v, T()); } template <typename Stream, unsigned int N, typename T> void Unserialize_impl(Stream &is, prevector<N, T> &v, const uint8_t &) { // Limit size per read so bogus size value won't cause out of memory v.clear(); size_t nSize = ReadCompactSize(is); size_t i = 0; while (i < nSize) { size_t blk = std::min(nSize - i, size_t(1 + 4999999 / sizeof(T))); v.resize_uninitialized(i + blk); is.read((char *)&v[i], blk * sizeof(T)); i += blk; } } template <typename Stream, unsigned int N, typename T, typename V> void Unserialize_impl(Stream &is, prevector<N, T> &v, const V &) { Unserialize(is, Using<VectorFormatter<DefaultFormatter>>(v)); } template <typename Stream, unsigned int N, typename T> inline void Unserialize(Stream &is, prevector<N, T> &v) { Unserialize_impl(is, v, T()); } /** * vector */ template <typename Stream, typename T, typename A> void Serialize_impl(Stream &os, const std::vector<T, A> &v, const uint8_t &) { WriteCompactSize(os, v.size()); if (!v.empty()) { os.write((char *)v.data(), v.size() * sizeof(T)); } } template <typename Stream, typename T, typename A> void Serialize_impl(Stream &os, const std::vector<T, A> &v, const bool &) { // A special case for std::vector<bool>, as dereferencing // std::vector<bool>::const_iterator does not result in a const bool& // due to std::vector's special casing for bool arguments. WriteCompactSize(os, v.size()); for (bool elem : v) { ::Serialize(os, elem); } } template <typename Stream, typename T, typename A, typename V> void Serialize_impl(Stream &os, const std::vector<T, A> &v, const V &) { Serialize(os, Using<VectorFormatter<DefaultFormatter>>(v)); } template <typename Stream, typename T, typename A> inline void Serialize(Stream &os, const std::vector<T, A> &v) { Serialize_impl(os, v, T()); } template <typename Stream, typename T, typename A> void Unserialize_impl(Stream &is, std::vector<T, A> &v, const uint8_t &) { // Limit size per read so bogus size value won't cause out of memory v.clear(); size_t nSize = ReadCompactSize(is); size_t i = 0; while (i < nSize) { size_t blk = std::min(nSize - i, size_t(1 + 4999999 / sizeof(T))); v.resize(i + blk); is.read((char *)&v[i], blk * sizeof(T)); i += blk; } } template <typename Stream, typename T, typename A, typename V> void Unserialize_impl(Stream &is, std::vector<T, A> &v, const V &) { Unserialize(is, Using<VectorFormatter<DefaultFormatter>>(v)); } template <typename Stream, typename T, typename A> inline void Unserialize(Stream &is, std::vector<T, A> &v) { Unserialize_impl(is, v, T()); } /** * pair */ template <typename Stream, typename K, typename T> void Serialize(Stream &os, const std::pair<K, T> &item) { Serialize(os, item.first); Serialize(os, item.second); } template <typename Stream, typename K, typename T> void Unserialize(Stream &is, std::pair<K, T> &item) { Unserialize(is, item.first); Unserialize(is, item.second); } /** * map */ template <typename Stream, typename K, typename T, typename Pred, typename A> void Serialize(Stream &os, const std::map<K, T, Pred, A> &m) { WriteCompactSize(os, m.size()); for (const auto &entry : m) { Serialize(os, entry); } } template <typename Stream, typename K, typename T, typename Pred, typename A> void Unserialize(Stream &is, std::map<K, T, Pred, A> &m) { m.clear(); size_t nSize = ReadCompactSize(is); typename std::map<K, T, Pred, A>::iterator mi = m.begin(); for (size_t i = 0; i < nSize; i++) { std::pair<K, T> item; Unserialize(is, item); mi = m.insert(mi, item); } } /** * set */ template <typename Stream, typename K, typename Pred, typename A> void Serialize(Stream &os, const std::set<K, Pred, A> &m) { WriteCompactSize(os, m.size()); for (const K &i : m) { Serialize(os, i); } } template <typename Stream, typename K, typename Pred, typename A> void Unserialize(Stream &is, std::set<K, Pred, A> &m) { m.clear(); size_t nSize = ReadCompactSize(is); typename std::set<K, Pred, A>::iterator it = m.begin(); for (size_t i = 0; i < nSize; i++) { K key; Unserialize(is, key); it = m.insert(it, key); } } /** * unique_ptr */ template <typename Stream, typename T> void Serialize(Stream &os, const std::unique_ptr<const T> &p) { Serialize(os, *p); } template <typename Stream, typename T> void Unserialize(Stream &is, std::unique_ptr<const T> &p) { p.reset(new T(deserialize, is)); } /** * shared_ptr */ template <typename Stream, typename T> void Serialize(Stream &os, const std::shared_ptr<const T> &p) { Serialize(os, *p); } template <typename Stream, typename T> void Unserialize(Stream &is, std::shared_ptr<const T> &p) { p = std::make_shared<const T>(deserialize, is); } /** * Support for SERIALIZE_METHODS and READWRITE macro. */ struct CSerActionSerialize { constexpr bool ForRead() const { return false; } }; struct CSerActionUnserialize { constexpr bool ForRead() const { return true; } }; /** * ::GetSerializeSize implementations * * Computing the serialized size of objects is done through a special stream * object of type CSizeComputer, which only records the number of bytes written * to it. * * If your Serialize or SerializationOp method has non-trivial overhead for * serialization, it may be worthwhile to implement a specialized version for * CSizeComputer, which uses the s.seek() method to record bytes that would * be written instead. */ class CSizeComputer { protected: size_t nSize; const int nVersion; public: explicit CSizeComputer(int nVersionIn) : nSize(0), nVersion(nVersionIn) {} void write(const char *psz, size_t _nSize) { this->nSize += _nSize; } /** Pretend _nSize bytes are written, without specifying them. */ void seek(size_t _nSize) { this->nSize += _nSize; } template <typename T> CSizeComputer &operator<<(const T &obj) { ::Serialize(*this, obj); return (*this); } size_t size() const { return nSize; } int GetVersion() const { return nVersion; } }; template <typename Stream> void SerializeMany(Stream &s) {} template <typename Stream, typename Arg, typename... Args> -void SerializeMany(Stream &s, const Arg &arg, const Args &... args) { +void SerializeMany(Stream &s, const Arg &arg, const Args &...args) { ::Serialize(s, arg); ::SerializeMany(s, args...); } template <typename Stream> inline void UnserializeMany(Stream &s) {} template <typename Stream, typename Arg, typename... Args> -inline void UnserializeMany(Stream &s, Arg &&arg, Args &&... args) { +inline void UnserializeMany(Stream &s, Arg &&arg, Args &&...args) { ::Unserialize(s, arg); ::UnserializeMany(s, args...); } template <typename Stream, typename... Args> inline void SerReadWriteMany(Stream &s, CSerActionSerialize ser_action, - const Args &... args) { + const Args &...args) { ::SerializeMany(s, args...); } template <typename Stream, typename... Args> inline void SerReadWriteMany(Stream &s, CSerActionUnserialize ser_action, - Args &&... args) { + Args &&...args) { ::UnserializeMany(s, args...); } template <typename Stream, typename Type, typename Fn> inline void SerRead(Stream &s, CSerActionSerialize ser_action, Type &&, Fn &&) { } template <typename Stream, typename Type, typename Fn> inline void SerRead(Stream &s, CSerActionUnserialize ser_action, Type &&obj, Fn &&fn) { fn(s, std::forward<Type>(obj)); } template <typename Stream, typename Type, typename Fn> inline void SerWrite(Stream &s, CSerActionSerialize ser_action, Type &&obj, Fn &&fn) { fn(s, std::forward<Type>(obj)); } template <typename Stream, typename Type, typename Fn> inline void SerWrite(Stream &s, CSerActionUnserialize ser_action, Type &&, Fn &&) {} template <typename I> inline void WriteVarInt(CSizeComputer &s, I n) { s.seek(GetSizeOfVarInt<I>(n)); } inline void WriteCompactSize(CSizeComputer &s, uint64_t nSize) { s.seek(GetSizeOfCompactSize(nSize)); } template <typename T> size_t GetSerializeSize(const T &t, int nVersion = 0) { return (CSizeComputer(nVersion) << t).size(); } template <typename... T> -size_t GetSerializeSizeMany(int nVersion, const T &... t) { +size_t GetSerializeSizeMany(int nVersion, const T &...t) { CSizeComputer sc(nVersion); SerializeMany(sc, t...); return sc.size(); } #endif // BITCOIN_SERIALIZE_H diff --git a/src/streams.h b/src/streams.h index ba76db67b..fa6f8b4b1 100644 --- a/src/streams.h +++ b/src/streams.h @@ -1,849 +1,849 @@ // 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. #ifndef BITCOIN_STREAMS_H #define BITCOIN_STREAMS_H #include <serialize.h> #include <support/allocators/zeroafterfree.h> #include <algorithm> #include <cassert> #include <cstdint> #include <cstdio> #include <cstring> #include <ios> #include <limits> #include <string> #include <utility> #include <vector> template <typename Stream> class OverrideStream { Stream *stream; const int nType; const int nVersion; public: OverrideStream(Stream *stream_, int nType_, int nVersion_) : stream(stream_), nType(nType_), nVersion(nVersion_) {} template <typename T> OverrideStream<Stream> &operator<<(const T &obj) { // Serialize to this stream ::Serialize(*this, obj); return (*this); } template <typename T> OverrideStream<Stream> &operator>>(T &&obj) { // Unserialize from this stream ::Unserialize(*this, obj); return (*this); } void write(const char *pch, size_t nSize) { stream->write(pch, nSize); } void read(char *pch, size_t nSize) { stream->read(pch, nSize); } int GetVersion() const { return nVersion; } int GetType() const { return nType; } void ignore(size_t size) { return stream->ignore(size); } }; template <typename S> OverrideStream<S> WithOrVersion(S *s, int nVersionFlag) { return OverrideStream<S>(s, s->GetType(), s->GetVersion() | nVersionFlag); } /** * Minimal stream for overwriting and/or appending to an existing byte vector. * * The referenced vector will grow as necessary. */ class CVectorWriter { public: /** * @param[in] nTypeIn Serialization Type * @param[in] nVersionIn Serialization Version (including any flags) * @param[in] vchDataIn Referenced byte vector to overwrite/append * @param[in] nPosIn Starting position. Vector index where writes should * start. The vector will initially grow as necessary to max(nPosIn, * vec.size()). So to append, use vec.size(). */ CVectorWriter(int nTypeIn, int nVersionIn, std::vector<uint8_t> &vchDataIn, size_t nPosIn) : nType(nTypeIn), nVersion(nVersionIn), vchData(vchDataIn), nPos(nPosIn) { if (nPos > vchData.size()) { vchData.resize(nPos); } } /** * (other params same as above) * @param[in] args A list of items to serialize starting at nPosIn. */ template <typename... Args> CVectorWriter(int nTypeIn, int nVersionIn, std::vector<uint8_t> &vchDataIn, - size_t nPosIn, Args &&... args) + size_t nPosIn, Args &&...args) : CVectorWriter(nTypeIn, nVersionIn, vchDataIn, nPosIn) { ::SerializeMany(*this, std::forward<Args>(args)...); } void write(const char *pch, size_t nSize) { assert(nPos <= vchData.size()); size_t nOverwrite = std::min(nSize, vchData.size() - nPos); if (nOverwrite) { memcpy(vchData.data() + nPos, reinterpret_cast<const uint8_t *>(pch), nOverwrite); } if (nOverwrite < nSize) { vchData.insert(vchData.end(), reinterpret_cast<const uint8_t *>(pch) + nOverwrite, reinterpret_cast<const uint8_t *>(pch) + nSize); } nPos += nSize; } template <typename T> CVectorWriter &operator<<(const T &obj) { // Serialize to this stream ::Serialize(*this, obj); return (*this); } int GetVersion() const { return nVersion; } int GetType() const { return nType; } void seek(size_t nSize) { nPos += nSize; if (nPos > vchData.size()) { vchData.resize(nPos); } } private: const int nType; const int nVersion; std::vector<uint8_t> &vchData; size_t nPos; }; /** * Minimal stream for reading from an existing vector by reference */ class VectorReader { private: const int m_type; const int m_version; const std::vector<uint8_t> &m_data; size_t m_pos = 0; public: /** * @param[in] type Serialization Type * @param[in] version Serialization Version (including any flags) * @param[in] data Referenced byte vector to overwrite/append * @param[in] pos Starting position. Vector index where reads should start. */ VectorReader(int type, int version, const std::vector<uint8_t> &data, size_t pos) : m_type(type), m_version(version), m_data(data), m_pos(pos) { if (m_pos > m_data.size()) { throw std::ios_base::failure( "VectorReader(...): end of data (m_pos > m_data.size())"); } } /** * (other params same as above) * @param[in] args A list of items to deserialize starting at pos. */ template <typename... Args> VectorReader(int type, int version, const std::vector<uint8_t> &data, - size_t pos, Args &&... args) + size_t pos, Args &&...args) : VectorReader(type, version, data, pos) { ::UnserializeMany(*this, std::forward<Args>(args)...); } template <typename T> VectorReader &operator>>(T &obj) { // Unserialize from this stream ::Unserialize(*this, obj); return (*this); } int GetVersion() const { return m_version; } int GetType() const { return m_type; } size_t size() const { return m_data.size() - m_pos; } bool empty() const { return m_data.size() == m_pos; } void read(char *dst, size_t n) { if (n == 0) { return; } // Read from the beginning of the buffer size_t pos_next = m_pos + n; if (pos_next > m_data.size()) { throw std::ios_base::failure("VectorReader::read(): end of data"); } memcpy(dst, m_data.data() + m_pos, n); m_pos = pos_next; } }; /** * Double ended buffer combining vector and stream-like interfaces. * * >> and << read and write unformatted data using the above serialization * templates. Fills with data in linear time; some stringstream implementations * take N^2 time. */ class CDataStream { protected: typedef CSerializeData vector_type; vector_type vch; unsigned int nReadPos; int nType; int nVersion; public: typedef vector_type::allocator_type allocator_type; typedef vector_type::size_type size_type; typedef vector_type::difference_type difference_type; typedef vector_type::reference reference; typedef vector_type::const_reference const_reference; typedef vector_type::value_type value_type; typedef vector_type::iterator iterator; typedef vector_type::const_iterator const_iterator; typedef vector_type::reverse_iterator reverse_iterator; explicit CDataStream(int nTypeIn, int nVersionIn) { Init(nTypeIn, nVersionIn); } CDataStream(const_iterator pbegin, const_iterator pend, int nTypeIn, int nVersionIn) : vch(pbegin, pend) { Init(nTypeIn, nVersionIn); } CDataStream(const char *pbegin, const char *pend, int nTypeIn, int nVersionIn) : vch(pbegin, pend) { Init(nTypeIn, nVersionIn); } CDataStream(const vector_type &vchIn, int nTypeIn, int nVersionIn) : vch(vchIn.begin(), vchIn.end()) { Init(nTypeIn, nVersionIn); } CDataStream(const std::vector<char> &vchIn, int nTypeIn, int nVersionIn) : vch(vchIn.begin(), vchIn.end()) { Init(nTypeIn, nVersionIn); } CDataStream(const std::vector<uint8_t> &vchIn, int nTypeIn, int nVersionIn) : vch(vchIn.begin(), vchIn.end()) { Init(nTypeIn, nVersionIn); } template <typename... Args> - CDataStream(int nTypeIn, int nVersionIn, Args &&... args) { + CDataStream(int nTypeIn, int nVersionIn, Args &&...args) { Init(nTypeIn, nVersionIn); ::SerializeMany(*this, std::forward<Args>(args)...); } void Init(int nTypeIn, int nVersionIn) { nReadPos = 0; nType = nTypeIn; nVersion = nVersionIn; } CDataStream &operator+=(const CDataStream &b) { vch.insert(vch.end(), b.begin(), b.end()); return *this; } friend CDataStream operator+(const CDataStream &a, const CDataStream &b) { CDataStream ret = a; ret += b; return (ret); } std::string str() const { return (std::string(begin(), end())); } // // Vector subset // const_iterator begin() const { return vch.begin() + nReadPos; } iterator begin() { return vch.begin() + nReadPos; } const_iterator end() const { return vch.end(); } iterator end() { return vch.end(); } size_type size() const { return vch.size() - nReadPos; } bool empty() const { return vch.size() == nReadPos; } void resize(size_type n, value_type c = 0) { vch.resize(n + nReadPos, c); } void reserve(size_type n) { vch.reserve(n + nReadPos); } const_reference operator[](size_type pos) const { return vch[pos + nReadPos]; } reference operator[](size_type pos) { return vch[pos + nReadPos]; } void clear() { vch.clear(); nReadPos = 0; } iterator insert(iterator it, const char x = char()) { return vch.insert(it, x); } void insert(iterator it, size_type n, const char x) { vch.insert(it, n, x); } value_type *data() { return vch.data() + nReadPos; } const value_type *data() const { return vch.data() + nReadPos; } void insert(iterator it, std::vector<char>::const_iterator first, std::vector<char>::const_iterator last) { if (last == first) { return; } assert(last - first > 0); if (it == vch.begin() + nReadPos && (unsigned int)(last - first) <= nReadPos) { // special case for inserting at the front when there's room nReadPos -= (last - first); memcpy(&vch[nReadPos], &first[0], last - first); } else { vch.insert(it, first, last); } } void insert(iterator it, const char *first, const char *last) { if (last == first) { return; } assert(last - first > 0); if (it == vch.begin() + nReadPos && (unsigned int)(last - first) <= nReadPos) { // special case for inserting at the front when there's room nReadPos -= (last - first); memcpy(&vch[nReadPos], &first[0], last - first); } else { vch.insert(it, first, last); } } iterator erase(iterator it) { if (it == vch.begin() + nReadPos) { // special case for erasing from the front if (++nReadPos >= vch.size()) { // whenever we reach the end, we take the opportunity to clear // the buffer nReadPos = 0; return vch.erase(vch.begin(), vch.end()); } return vch.begin() + nReadPos; } else { return vch.erase(it); } } iterator erase(iterator first, iterator last) { if (first == vch.begin() + nReadPos) { // special case for erasing from the front if (last == vch.end()) { nReadPos = 0; return vch.erase(vch.begin(), vch.end()); } else { nReadPos = (last - vch.begin()); return last; } } else return vch.erase(first, last); } inline void Compact() { vch.erase(vch.begin(), vch.begin() + nReadPos); nReadPos = 0; } bool Rewind(size_type n) { // Rewind by n characters if the buffer hasn't been compacted yet if (n > nReadPos) { return false; } nReadPos -= n; return true; } // // Stream subset // bool eof() const { return size() == 0; } CDataStream *rdbuf() { return this; } int in_avail() const { return size(); } void SetType(int n) { nType = n; } int GetType() const { return nType; } void SetVersion(int n) { nVersion = n; } int GetVersion() const { return nVersion; } void read(char *pch, size_t nSize) { if (nSize == 0) { return; } // Read from the beginning of the buffer unsigned int nReadPosNext = nReadPos + nSize; if (nReadPosNext > vch.size()) { throw std::ios_base::failure("CDataStream::read(): end of data"); } memcpy(pch, &vch[nReadPos], nSize); if (nReadPosNext == vch.size()) { nReadPos = 0; vch.clear(); return; } nReadPos = nReadPosNext; } void ignore(int nSize) { // Ignore from the beginning of the buffer if (nSize < 0) { throw std::ios_base::failure( "CDataStream::ignore(): nSize negative"); } unsigned int nReadPosNext = nReadPos + nSize; if (nReadPosNext >= vch.size()) { if (nReadPosNext > vch.size()) { throw std::ios_base::failure( "CDataStream::ignore(): end of data"); } nReadPos = 0; vch.clear(); return; } nReadPos = nReadPosNext; } void write(const char *pch, size_t nSize) { // Write to the end of the buffer vch.insert(vch.end(), pch, pch + nSize); } template <typename Stream> void Serialize(Stream &s) const { // Special case: stream << stream concatenates like stream += stream if (!vch.empty()) { s.write((char *)vch.data(), vch.size() * sizeof(value_type)); } } template <typename T> CDataStream &operator<<(const T &obj) { // Serialize to this stream ::Serialize(*this, obj); return (*this); } template <typename T> CDataStream &operator>>(T &&obj) { // Unserialize from this stream ::Unserialize(*this, obj); return (*this); } void GetAndClear(CSerializeData &d) { d.insert(d.end(), begin(), end()); clear(); } /** * XOR the contents of this stream with a certain key. * * @param[in] key The key used to XOR the data in this stream. */ void Xor(const std::vector<uint8_t> &key) { if (key.size() == 0) { return; } for (size_type i = 0, j = 0; i != size(); i++) { vch[i] ^= key[j++]; // This potentially acts on very many bytes of data, so it's // important that we calculate `j`, i.e. the `key` index in this way // instead of doing a %, which would effectively be a division for // each byte Xor'd -- much slower than need be. if (j == key.size()) j = 0; } } }; template <typename IStream> class BitStreamReader { private: IStream &m_istream; /// Buffered byte read in from the input stream. A new byte is read into the /// buffer when m_offset reaches 8. uint8_t m_buffer{0}; /// Number of high order bits in m_buffer already returned by previous /// Read() calls. The next bit to be returned is at this offset from the /// most significant bit position. int m_offset{8}; public: explicit BitStreamReader(IStream &istream) : m_istream(istream) {} /** * Read the specified number of bits from the stream. The data is returned * in the nbits least significant bits of a 64-bit uint. */ uint64_t Read(int nbits) { if (nbits < 0 || nbits > 64) { throw std::out_of_range("nbits must be between 0 and 64"); } uint64_t data = 0; while (nbits > 0) { if (m_offset == 8) { m_istream >> m_buffer; m_offset = 0; } int bits = std::min(8 - m_offset, nbits); data <<= bits; data |= static_cast<uint8_t>(m_buffer << m_offset) >> (8 - bits); m_offset += bits; nbits -= bits; } return data; } }; template <typename OStream> class BitStreamWriter { private: OStream &m_ostream; /// Buffered byte waiting to be written to the output stream. The byte is /// written buffer when m_offset reaches 8 or Flush() is called. uint8_t m_buffer{0}; /// Number of high order bits in m_buffer already written by previous /// Write() calls and not yet flushed to the stream. The next bit to be /// written to is at this offset from the most significant bit position. int m_offset{0}; public: explicit BitStreamWriter(OStream &ostream) : m_ostream(ostream) {} ~BitStreamWriter() { Flush(); } /** * Write the nbits least significant bits of a 64-bit int to the output * stream. Data is buffered until it completes an octet. */ void Write(uint64_t data, int nbits) { if (nbits < 0 || nbits > 64) { throw std::out_of_range("nbits must be between 0 and 64"); } while (nbits > 0) { int bits = std::min(8 - m_offset, nbits); m_buffer |= (data << (64 - nbits)) >> (64 - 8 + m_offset); m_offset += bits; nbits -= bits; if (m_offset == 8) { Flush(); } } } /** * Flush any unwritten bits to the output stream, padding with 0's to the * next byte boundary. */ void Flush() { if (m_offset == 0) { return; } m_ostream << m_buffer; m_buffer = 0; m_offset = 0; } }; /** * Non-refcounted RAII wrapper for FILE* * * Will automatically close the file when it goes out of scope if not null. If * you're returning the file pointer, return file.release(). If you need to * close the file early, use file.fclose() instead of fclose(file). */ class CAutoFile { private: const int nType; const int nVersion; FILE *file; public: CAutoFile(FILE *filenew, int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn) { file = filenew; } ~CAutoFile() { fclose(); } // Disallow copies CAutoFile(const CAutoFile &) = delete; CAutoFile &operator=(const CAutoFile &) = delete; void fclose() { if (file) { ::fclose(file); file = nullptr; } } /** * Get wrapped FILE* with transfer of ownership. * @note This will invalidate the CAutoFile object, and makes it the * responsibility of the caller of this function to clean up the returned * FILE*. */ FILE *release() { FILE *ret = file; file = nullptr; return ret; } /** * Get wrapped FILE* without transfer of ownership. * @note Ownership of the FILE* will remain with this class. Use this only * if the scope of the CAutoFile outlives use of the passed pointer. */ FILE *Get() const { return file; } /** Return true if the wrapped FILE* is nullptr, false otherwise. */ bool IsNull() const { return (file == nullptr); } // // Stream subset // int GetType() const { return nType; } int GetVersion() const { return nVersion; } void read(char *pch, size_t nSize) { if (!file) { throw std::ios_base::failure( "CAutoFile::read: file handle is nullptr"); } if (fread(pch, 1, nSize, file) != nSize) { throw std::ios_base::failure(feof(file) ? "CAutoFile::read: end of file" : "CAutoFile::read: fread failed"); } } void ignore(size_t nSize) { if (!file) { throw std::ios_base::failure( "CAutoFile::ignore: file handle is nullptr"); } uint8_t data[4096]; while (nSize > 0) { size_t nNow = std::min<size_t>(nSize, sizeof(data)); if (fread(data, 1, nNow, file) != nNow) { throw std::ios_base::failure( feof(file) ? "CAutoFile::ignore: end of file" : "CAutoFile::read: fread failed"); } nSize -= nNow; } } void write(const char *pch, size_t nSize) { if (!file) { throw std::ios_base::failure( "CAutoFile::write: file handle is nullptr"); } if (fwrite(pch, 1, nSize, file) != nSize) { throw std::ios_base::failure("CAutoFile::write: write failed"); } } template <typename T> CAutoFile &operator<<(const T &obj) { // Serialize to this stream if (!file) { throw std::ios_base::failure( "CAutoFile::operator<<: file handle is nullptr"); } ::Serialize(*this, obj); return (*this); } template <typename T> CAutoFile &operator>>(T &&obj) { // Unserialize from this stream if (!file) { throw std::ios_base::failure( "CAutoFile::operator>>: file handle is nullptr"); } ::Unserialize(*this, obj); return (*this); } }; /** * Non-refcounted RAII wrapper around a FILE* that implements a ring buffer to * deserialize from. It guarantees the ability to rewind a given number of * bytes. * * Will automatically close the file when it goes out of scope if not null. If * you need to close the file early, use file.fclose() instead of fclose(file). */ class CBufferedFile { private: const int nType; const int nVersion; //! source file FILE *src; //! how many bytes have been read from source uint64_t nSrcPos; //! how many bytes have been read from this uint64_t nReadPos; //! up to which position we're allowed to read uint64_t nReadLimit; //! how many bytes we guarantee to rewind uint64_t nRewind; //! the buffer std::vector<char> vchBuf; protected: //! read data from the source to fill the buffer bool Fill() { unsigned int pos = nSrcPos % vchBuf.size(); unsigned int readNow = vchBuf.size() - pos; unsigned int nAvail = vchBuf.size() - (nSrcPos - nReadPos) - nRewind; if (nAvail < readNow) { readNow = nAvail; } if (readNow == 0) { return false; } size_t nBytes = fread((void *)&vchBuf[pos], 1, readNow, src); if (nBytes == 0) { throw std::ios_base::failure( feof(src) ? "CBufferedFile::Fill: end of file" : "CBufferedFile::Fill: fread failed"); } nSrcPos += nBytes; return true; } public: CBufferedFile(FILE *fileIn, uint64_t nBufSize, uint64_t nRewindIn, int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn), nSrcPos(0), nReadPos(0), nReadLimit(std::numeric_limits<uint64_t>::max()), nRewind(nRewindIn), vchBuf(nBufSize, 0) { if (nRewindIn >= nBufSize) { throw std::ios_base::failure( "Rewind limit must be less than buffer size"); } src = fileIn; } ~CBufferedFile() { fclose(); } // Disallow copies CBufferedFile(const CBufferedFile &) = delete; CBufferedFile &operator=(const CBufferedFile &) = delete; int GetVersion() const { return nVersion; } int GetType() const { return nType; } void fclose() { if (src) { ::fclose(src); src = nullptr; } } //! check whether we're at the end of the source file bool eof() const { return nReadPos == nSrcPos && feof(src); } //! read a number of bytes void read(char *pch, size_t nSize) { if (nSize + nReadPos > nReadLimit) { throw std::ios_base::failure("Read attempted past buffer limit"); } while (nSize > 0) { if (nReadPos == nSrcPos) { Fill(); } unsigned int pos = nReadPos % vchBuf.size(); size_t nNow = nSize; if (nNow + pos > vchBuf.size()) { nNow = vchBuf.size() - pos; } if (nNow + nReadPos > nSrcPos) { nNow = nSrcPos - nReadPos; } memcpy(pch, &vchBuf[pos], nNow); nReadPos += nNow; pch += nNow; nSize -= nNow; } } //! return the current reading position uint64_t GetPos() const { return nReadPos; } //! rewind to a given reading position bool SetPos(uint64_t nPos) { size_t bufsize = vchBuf.size(); if (nPos + bufsize < nSrcPos) { // rewinding too far, rewind as far as possible nReadPos = nSrcPos - bufsize; return false; } if (nPos > nSrcPos) { // can't go this far forward, go as far as possible nReadPos = nSrcPos; return false; } nReadPos = nPos; return true; } //! Prevent reading beyond a certain position. No argument removes the //! limit. bool SetLimit(uint64_t nPos = std::numeric_limits<uint64_t>::max()) { if (nPos < nReadPos) { return false; } nReadLimit = nPos; return true; } template <typename T> CBufferedFile &operator>>(T &&obj) { // Unserialize from this stream ::Unserialize(*this, obj); return (*this); } //! search for a given byte in the stream, and remain positioned on it void FindByte(char ch) { while (true) { if (nReadPos == nSrcPos) { Fill(); } if (vchBuf[nReadPos % vchBuf.size()] == ch) { break; } nReadPos++; } } }; #endif // BITCOIN_STREAMS_H diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 38462bfc2..8271cd6e9 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -1,940 +1,940 @@ // Copyright (c) 2014-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <coins.h> #include <attributes.h> #include <clientversion.h> #include <script/standard.h> #include <streams.h> #include <txdb.h> #include <undo.h> #include <util/strencodings.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> #include <map> #include <vector> void UpdateCoins(CCoinsViewCache &inputs, const CTransaction &tx, CTxUndo &txundo, int nHeight); namespace { //! equality test bool operator==(const Coin &a, const Coin &b) { // Empty Coin objects are always equal. if (a.IsSpent() && b.IsSpent()) { return true; } return a.IsCoinBase() == b.IsCoinBase() && a.GetHeight() == b.GetHeight() && a.GetTxOut() == b.GetTxOut(); } class CCoinsViewTest : public CCoinsView { BlockHash hashBestBlock_; std::map<COutPoint, Coin> map_; public: [[nodiscard]] bool GetCoin(const COutPoint &outpoint, Coin &coin) const override { std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint); if (it == map_.end()) { return false; } coin = it->second; if (coin.IsSpent() && InsecureRandBool() == 0) { // Randomly return false in case of an empty entry. return false; } return true; } BlockHash GetBestBlock() const override { return hashBestBlock_; } bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock) override { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Same optimization used in CCoinsViewDB is to only write dirty // entries. map_[it->first] = it->second.coin; if (it->second.coin.IsSpent() && InsecureRandRange(3) == 0) { // Randomly delete empty entries on write. map_.erase(it->first); } } mapCoins.erase(it++); } if (!hashBlock.IsNull()) { hashBestBlock_ = hashBlock; } return true; } }; class CCoinsViewCacheTest : public CCoinsViewCache { public: explicit CCoinsViewCacheTest(CCoinsView *_base) : CCoinsViewCache(_base) {} void SelfTest() const { // Manually recompute the dynamic usage of the whole data, and compare // it. size_t ret = memusage::DynamicUsage(cacheCoins); size_t count = 0; for (const auto &entry : cacheCoins) { ret += entry.second.coin.DynamicMemoryUsage(); count++; } BOOST_CHECK_EQUAL(GetCacheSize(), count); BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret); } CCoinsMap &map() const { return cacheCoins; } size_t &usage() const { return cachedCoinsUsage; } }; } // namespace BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup) static const unsigned int NUM_SIMULATION_ITERATIONS = 40000; // This is a large randomized insert/remove simulation test on a variable-size // stack of caches on top of CCoinsViewTest. // // It will randomly create/update/delete Coin entries to a tip of caches, with // txids picked from a limited list of random 256-bit hashes. Occasionally, a // new tip is added to the stack of caches, or the tip is flushed and removed. // // During the process, booleans are kept to make sure that the randomized // operation hits all branches. // // If fake_best_block is true, assign a random BlockHash to mock the recording // of best block on flush. This is necessary when using CCoinsViewDB as the // base, otherwise we'll hit an assertion in BatchWrite. // void SimulationTest(CCoinsView *base, bool fake_best_block) { // Various coverage trackers. bool removed_all_caches = false; bool reached_4_caches = false; bool added_an_entry = false; bool added_an_unspendable_entry = false; bool removed_an_entry = false; bool updated_an_entry = false; bool found_an_entry = false; bool missed_an_entry = false; bool uncached_an_entry = false; // A simple map to track what we expect the cache stack to represent. std::map<COutPoint, Coin> result; // The cache stack. // A stack of CCoinsViewCaches on top. std::vector<CCoinsViewCacheTest *> stack; // Start with one cache. stack.push_back(new CCoinsViewCacheTest(base)); // Use a limited set of random transaction ids, so we do test overwriting // entries. std::vector<TxId> txids; txids.resize(NUM_SIMULATION_ITERATIONS / 8); for (size_t i = 0; i < txids.size(); i++) { txids[i] = TxId(InsecureRand256()); } for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { // Do a random modification. { // txid we're going to modify in this iteration. const TxId txid = txids[InsecureRandRange(txids.size())]; Coin &coin = result[COutPoint(txid, 0)]; // Determine whether to test HaveCoin before or after Access* (or // both). As these functions can influence each other's behaviour by // pulling things into the cache, all combinations are tested. bool test_havecoin_before = InsecureRandBits(2) == 0; bool test_havecoin_after = InsecureRandBits(2) == 0; bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(COutPoint(txid, 0)) : false; const Coin &entry = (InsecureRandRange(500) == 0) ? AccessByTxid(*stack.back(), txid) : stack.back()->AccessCoin(COutPoint(txid, 0)); BOOST_CHECK(coin == entry); BOOST_CHECK(!test_havecoin_before || result_havecoin == !entry.IsSpent()); if (test_havecoin_after) { bool ret = stack.back()->HaveCoin(COutPoint(txid, 0)); BOOST_CHECK(ret == !entry.IsSpent()); } if (InsecureRandRange(5) == 0 || coin.IsSpent()) { CTxOut txout; txout.nValue = int64_t(InsecureRand32()) * SATOSHI; if (InsecureRandRange(16) == 0 && coin.IsSpent()) { txout.scriptPubKey.assign(1 + InsecureRandBits(6), OP_RETURN); BOOST_CHECK(txout.scriptPubKey.IsUnspendable()); added_an_unspendable_entry = true; } else { // Random sizes so we can test memory usage accounting txout.scriptPubKey.assign(InsecureRandBits(6), 0); (coin.IsSpent() ? added_an_entry : updated_an_entry) = true; coin = Coin(txout, 1, false); } Coin newcoin(txout, 1, false); stack.back()->AddCoin(COutPoint(txid, 0), newcoin, !coin.IsSpent() || InsecureRand32() & 1); } else { removed_an_entry = true; coin.Clear(); BOOST_CHECK(stack.back()->SpendCoin(COutPoint(txid, 0))); } } // One every 10 iterations, remove a random entry from the cache if (InsecureRandRange(10) == 0) { COutPoint out(txids[InsecureRand32() % txids.size()], 0); int cacheid = InsecureRand32() % stack.size(); stack[cacheid]->Uncache(out); uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out); } // Once every 1000 iterations and at the end, verify the full cache. if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { for (const auto &entry : result) { bool have = stack.back()->HaveCoin(entry.first); const Coin &coin = stack.back()->AccessCoin(entry.first); BOOST_CHECK(have == !coin.IsSpent()); BOOST_CHECK(coin == entry.second); if (coin.IsSpent()) { missed_an_entry = true; } else { BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first)); found_an_entry = true; } } for (const CCoinsViewCacheTest *test : stack) { test->SelfTest(); } } // Every 100 iterations, flush an intermediate cache if (InsecureRandRange(100) == 0) { if (stack.size() > 1 && InsecureRandBool() == 0) { unsigned int flushIndex = InsecureRandRange(stack.size() - 1); if (fake_best_block) { stack[flushIndex]->SetBestBlock( BlockHash(InsecureRand256())); } BOOST_CHECK(stack[flushIndex]->Flush()); } } if (InsecureRandRange(100) == 0) { // Every 100 iterations, change the cache stack. if (stack.size() > 0 && InsecureRandBool() == 0) { // Remove the top cache if (fake_best_block) { stack.back()->SetBestBlock(BlockHash(InsecureRand256())); } BOOST_CHECK(stack.back()->Flush()); delete stack.back(); stack.pop_back(); } if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) { // Add a new cache CCoinsView *tip = base; if (stack.size() > 0) { tip = stack.back(); } else { removed_all_caches = true; } stack.push_back(new CCoinsViewCacheTest(tip)); if (stack.size() == 4) { reached_4_caches = true; } } } } // Clean up the stack. while (stack.size() > 0) { delete stack.back(); stack.pop_back(); } // Verify coverage. BOOST_CHECK(removed_all_caches); BOOST_CHECK(reached_4_caches); BOOST_CHECK(added_an_entry); BOOST_CHECK(added_an_unspendable_entry); BOOST_CHECK(removed_an_entry); BOOST_CHECK(updated_an_entry); BOOST_CHECK(found_an_entry); BOOST_CHECK(missed_an_entry); BOOST_CHECK(uncached_an_entry); } // Run the above simulation for multiple base types. BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) { CCoinsViewTest base; SimulationTest(&base, false); CCoinsViewDB db_base{"test", /*nCacheSize*/ 1 << 23, /*fMemory*/ true, /*fWipe*/ false}; SimulationTest(&db_base, true); } // Store of all necessary tx and undo data for next test typedef std::map<COutPoint, std::tuple<CTransaction, CTxUndo, Coin>> UtxoData; UtxoData utxoData; UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) { assert(utxoSet.size()); auto utxoSetIt = utxoSet.lower_bound(COutPoint(TxId(InsecureRand256()), 0)); if (utxoSetIt == utxoSet.end()) { utxoSetIt = utxoSet.begin(); } auto utxoDataIt = utxoData.find(*utxoSetIt); assert(utxoDataIt != utxoData.end()); return utxoDataIt; } // This test is similar to the previous test except the emphasis is on testing // the functionality of UpdateCoins random txs are created and UpdateCoins is // used to update the cache stack. In particular it is tested that spending a // duplicate coinbase tx has the expected effect (the other duplicate is // overwritten at all cache levels) BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) { SeedInsecureRand(SeedRand::ZEROS); g_mock_deterministic_tests = true; bool spent_a_duplicate_coinbase = false; // A simple map to track what we expect the cache stack to represent. std::map<COutPoint, Coin> result; // The cache stack. // A CCoinsViewTest at the bottom. CCoinsViewTest base; // A stack of CCoinsViewCaches on top. std::vector<CCoinsViewCacheTest *> stack; // Start with one cache. stack.push_back(new CCoinsViewCacheTest(&base)); // Track the txids we've used in various sets std::set<COutPoint> coinbase_coins; std::set<COutPoint> disconnected_coins; std::set<COutPoint> duplicate_coins; std::set<COutPoint> utxoset; for (int64_t i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { uint32_t randiter = InsecureRand32(); // 19/20 txs add a new transaction if (randiter % 20 < 19) { CMutableTransaction tx; tx.vin.resize(1); tx.vout.resize(1); // Keep txs unique unless intended to duplicate. tx.vout[0].nValue = i * SATOSHI; // Random sizes so we can test memory usage accounting tx.vout[0].scriptPubKey.assign(InsecureRand32() & 0x3F, 0); unsigned int height = InsecureRand32(); Coin old_coin; // 2/20 times create a new coinbase if (randiter % 20 < 2 || coinbase_coins.size() < 10) { // 1/10 of those times create a duplicate coinbase if (InsecureRandRange(10) == 0 && coinbase_coins.size()) { auto utxod = FindRandomFrom(coinbase_coins); // Reuse the exact same coinbase tx = CMutableTransaction{std::get<0>(utxod->second)}; // shouldn't be available for reconnection if it's been // duplicated disconnected_coins.erase(utxod->first); duplicate_coins.insert(utxod->first); } else { coinbase_coins.insert(COutPoint(tx.GetId(), 0)); } assert(CTransaction(tx).IsCoinBase()); } // 17/20 times reconnect previous or add a regular tx else { COutPoint prevout; // 1/20 times reconnect a previously disconnected tx if (randiter % 20 == 2 && disconnected_coins.size()) { auto utxod = FindRandomFrom(disconnected_coins); tx = CMutableTransaction{std::get<0>(utxod->second)}; prevout = tx.vin[0].prevout; if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) { disconnected_coins.erase(utxod->first); continue; } // If this tx is already IN the UTXO, then it must be a // coinbase, and it must be a duplicate if (utxoset.count(utxod->first)) { assert(CTransaction(tx).IsCoinBase()); assert(duplicate_coins.count(utxod->first)); } disconnected_coins.erase(utxod->first); } // 16/20 times create a regular tx else { auto utxod = FindRandomFrom(utxoset); prevout = utxod->first; // Construct the tx to spend the coins of prevouthash tx.vin[0].prevout = COutPoint(prevout.GetTxId(), 0); assert(!CTransaction(tx).IsCoinBase()); } // In this simple test coins only have two states, spent or // unspent, save the unspent state to restore old_coin = result[prevout]; // Update the expected result of prevouthash to know these coins // are spent result[prevout].Clear(); utxoset.erase(prevout); // The test is designed to ensure spending a duplicate coinbase // will work properly if that ever happens and not resurrect the // previously overwritten coinbase if (duplicate_coins.count(prevout)) { spent_a_duplicate_coinbase = true; } } // Update the expected result to know about the new output coins assert(tx.vout.size() == 1); const COutPoint outpoint(tx.GetId(), 0); result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase()); // Call UpdateCoins on the top cache CTxUndo undo; UpdateCoins(*(stack.back()), CTransaction(tx), undo, height); // Update the utxo set for future spends utxoset.insert(outpoint); // Track this tx and undo info to use later utxoData.emplace(outpoint, std::make_tuple(CTransaction(tx), undo, old_coin)); } // 1/20 times undo a previous transaction else if (utxoset.size()) { auto utxod = FindRandomFrom(utxoset); CTransaction &tx = std::get<0>(utxod->second); CTxUndo &undo = std::get<1>(utxod->second); Coin &orig_coin = std::get<2>(utxod->second); // Update the expected result // Remove new outputs result[utxod->first].Clear(); // If not coinbase restore prevout if (!tx.IsCoinBase()) { result[tx.vin[0].prevout] = orig_coin; } // Disconnect the tx from the current UTXO // See code in DisconnectBlock // remove outputs BOOST_CHECK(stack.back()->SpendCoin(utxod->first)); // restore inputs if (!tx.IsCoinBase()) { const COutPoint &out = tx.vin[0].prevout; UndoCoinSpend(undo.vprevout[0], *(stack.back()), out); } // Store as a candidate for reconnection disconnected_coins.insert(utxod->first); // Update the utxoset utxoset.erase(utxod->first); if (!tx.IsCoinBase()) { utxoset.insert(tx.vin[0].prevout); } } // Once every 1000 iterations and at the end, verify the full cache. if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { for (const auto &entry : result) { bool have = stack.back()->HaveCoin(entry.first); const Coin &coin = stack.back()->AccessCoin(entry.first); BOOST_CHECK(have == !coin.IsSpent()); BOOST_CHECK(coin == entry.second); } } // One every 10 iterations, remove a random entry from the cache if (utxoset.size() > 1 && InsecureRandRange(30) == 0) { stack[InsecureRand32() % stack.size()]->Uncache( FindRandomFrom(utxoset)->first); } if (disconnected_coins.size() > 1 && InsecureRandRange(30) == 0) { stack[InsecureRand32() % stack.size()]->Uncache( FindRandomFrom(disconnected_coins)->first); } if (duplicate_coins.size() > 1 && InsecureRandRange(30) == 0) { stack[InsecureRand32() % stack.size()]->Uncache( FindRandomFrom(duplicate_coins)->first); } if (InsecureRandRange(100) == 0) { // Every 100 iterations, flush an intermediate cache if (stack.size() > 1 && InsecureRandBool() == 0) { unsigned int flushIndex = InsecureRandRange(stack.size() - 1); BOOST_CHECK(stack[flushIndex]->Flush()); } } if (InsecureRandRange(100) == 0) { // Every 100 iterations, change the cache stack. if (stack.size() > 0 && InsecureRandBool() == 0) { BOOST_CHECK(stack.back()->Flush()); delete stack.back(); stack.pop_back(); } if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) { CCoinsView *tip = &base; if (stack.size() > 0) { tip = stack.back(); } stack.push_back(new CCoinsViewCacheTest(tip)); } } } // Clean up the stack. while (stack.size() > 0) { delete stack.back(); stack.pop_back(); } // Verify coverage. BOOST_CHECK(spent_a_duplicate_coinbase); g_mock_deterministic_tests = false; } BOOST_AUTO_TEST_CASE(coin_serialization) { // Good example CDataStream ss1( ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"), SER_DISK, CLIENT_VERSION); Coin c1; ss1 >> c1; BOOST_CHECK_EQUAL(c1.IsCoinBase(), false); BOOST_CHECK_EQUAL(c1.GetHeight(), 203998U); BOOST_CHECK_EQUAL(c1.GetTxOut().nValue, int64_t(60000000000) * SATOSHI); BOOST_CHECK_EQUAL(HexStr(c1.GetTxOut().scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160(ParseHex( "816115944e077fe7c803cfa57f29b36bf87c1d35")))))); // Good example CDataStream ss2( ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION); Coin c2; ss2 >> c2; BOOST_CHECK_EQUAL(c2.IsCoinBase(), true); BOOST_CHECK_EQUAL(c2.GetHeight(), 120891U); BOOST_CHECK_EQUAL(c2.GetTxOut().nValue, 110397 * SATOSHI); BOOST_CHECK_EQUAL(HexStr(c2.GetTxOut().scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160(ParseHex( "8c988f1a4a4de2161e0f50aac7f17e7f9555caa4")))))); // Smallest possible example CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION); Coin c3; ss3 >> c3; BOOST_CHECK_EQUAL(c3.IsCoinBase(), false); BOOST_CHECK_EQUAL(c3.GetHeight(), 0U); BOOST_CHECK_EQUAL(c3.GetTxOut().nValue, Amount::zero()); BOOST_CHECK_EQUAL(c3.GetTxOut().scriptPubKey.size(), 0U); // scriptPubKey that ends beyond the end of the stream CDataStream ss4(ParseHex("000007"), SER_DISK, CLIENT_VERSION); try { Coin c4; ss4 >> c4; BOOST_CHECK_MESSAGE(false, "We should have thrown"); } catch (const std::ios_base::failure &) { } // Very large scriptPubKey (3*10^9 bytes) past the end of the stream CDataStream tmp(SER_DISK, CLIENT_VERSION); uint64_t x = 3000000000ULL; tmp << VARINT(x); BOOST_CHECK_EQUAL(HexStr(tmp), "8a95c0bb00"); CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION); try { Coin c5; ss5 >> c5; BOOST_CHECK_MESSAGE(false, "We should have thrown"); } catch (const std::ios_base::failure &) { } } static const COutPoint OUTPOINT; static const Amount SPENT(-1 * SATOSHI); static const Amount ABSENT(-2 * SATOSHI); static const Amount FAIL(-3 * SATOSHI); static const Amount VALUE1(100 * SATOSHI); static const Amount VALUE2(200 * SATOSHI); static const Amount VALUE3(300 * SATOSHI); static const char DIRTY = CCoinsCacheEntry::DIRTY; static const char FRESH = CCoinsCacheEntry::FRESH; static const char NO_ENTRY = -1; static const auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)}; static const auto CLEAN_FLAGS = {char(0), FRESH}; static const auto ABSENT_FLAGS = {NO_ENTRY}; static void SetCoinValue(const Amount value, Coin &coin) { assert(value != ABSENT); coin.Clear(); assert(coin.IsSpent()); if (value != SPENT) { CTxOut out; out.nValue = value; coin = Coin(std::move(out), 1, false); assert(!coin.IsSpent()); } } static size_t InsertCoinMapEntry(CCoinsMap &map, const Amount value, char flags) { if (value == ABSENT) { assert(flags == NO_ENTRY); return 0; } assert(flags != NO_ENTRY); CCoinsCacheEntry entry; entry.flags = flags; SetCoinValue(value, entry.coin); auto inserted = map.emplace(OUTPOINT, std::move(entry)); assert(inserted.second); return inserted.first->second.coin.DynamicMemoryUsage(); } void GetCoinMapEntry(const CCoinsMap &map, Amount &value, char &flags) { auto it = map.find(OUTPOINT); if (it == map.end()) { value = ABSENT; flags = NO_ENTRY; } else { if (it->second.coin.IsSpent()) { value = SPENT; } else { value = it->second.coin.GetTxOut().nValue; } flags = it->second.flags; assert(flags != NO_ENTRY); } } void WriteCoinViewEntry(CCoinsView &view, const Amount value, char flags) { CCoinsMap map; InsertCoinMapEntry(map, value, flags); BOOST_CHECK(view.BatchWrite(map, BlockHash())); } class SingleEntryCacheTest { public: SingleEntryCacheTest(const Amount base_value, const Amount cache_value, char cache_flags) { WriteCoinViewEntry(base, base_value, base_value == ABSENT ? NO_ENTRY : DIRTY); cache.usage() += InsertCoinMapEntry(cache.map(), cache_value, cache_flags); } CCoinsView root; CCoinsViewCacheTest base{&root}; CCoinsViewCacheTest cache{&base}; }; static void CheckAccessCoin(const Amount base_value, const Amount cache_value, const Amount expected_value, char cache_flags, char expected_flags) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); test.cache.AccessCoin(OUTPOINT); test.cache.SelfTest(); Amount result_value; char result_flags; GetCoinMapEntry(test.cache.map(), result_value, result_flags); BOOST_CHECK_EQUAL(result_value, expected_value); BOOST_CHECK_EQUAL(result_flags, expected_flags); } BOOST_AUTO_TEST_CASE(coin_access) { /* Check AccessCoin behavior, requesting a coin from a cache view layered on * top of a base view, and checking the resulting entry in the cache after * the access. * * Base Cache Result Cache Result * Value Value Value Flags Flags */ CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); CheckAccessCoin(ABSENT, SPENT, SPENT, 0, 0); CheckAccessCoin(ABSENT, SPENT, SPENT, FRESH, FRESH); CheckAccessCoin(ABSENT, SPENT, SPENT, DIRTY, DIRTY); CheckAccessCoin(ABSENT, SPENT, SPENT, DIRTY | FRESH, DIRTY | FRESH); CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0, 0); CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH, FRESH); CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY, DIRTY); CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH); CheckAccessCoin(SPENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); CheckAccessCoin(SPENT, SPENT, SPENT, 0, 0); CheckAccessCoin(SPENT, SPENT, SPENT, FRESH, FRESH); CheckAccessCoin(SPENT, SPENT, SPENT, DIRTY, DIRTY); CheckAccessCoin(SPENT, SPENT, SPENT, DIRTY | FRESH, DIRTY | FRESH); CheckAccessCoin(SPENT, VALUE2, VALUE2, 0, 0); CheckAccessCoin(SPENT, VALUE2, VALUE2, FRESH, FRESH); CheckAccessCoin(SPENT, VALUE2, VALUE2, DIRTY, DIRTY); CheckAccessCoin(SPENT, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH); CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY, 0); CheckAccessCoin(VALUE1, SPENT, SPENT, 0, 0); CheckAccessCoin(VALUE1, SPENT, SPENT, FRESH, FRESH); CheckAccessCoin(VALUE1, SPENT, SPENT, DIRTY, DIRTY); CheckAccessCoin(VALUE1, SPENT, SPENT, DIRTY | FRESH, DIRTY | FRESH); CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0, 0); CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH, FRESH); CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY); CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH); } static void CheckSpendCoin(Amount base_value, Amount cache_value, Amount expected_value, char cache_flags, char expected_flags) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); test.cache.SpendCoin(OUTPOINT); test.cache.SelfTest(); Amount result_value; char result_flags; GetCoinMapEntry(test.cache.map(), result_value, result_flags); BOOST_CHECK_EQUAL(result_value, expected_value); BOOST_CHECK_EQUAL(result_flags, expected_flags); }; BOOST_AUTO_TEST_CASE(coin_spend) { /** * Check SpendCoin behavior, requesting a coin from a cache view layered on * top of a base view, spending, and then checking the resulting entry in * the cache after the modification. * * Base Cache Result Cache Result * Value Value Value Flags Flags */ CheckSpendCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); CheckSpendCoin(ABSENT, SPENT, SPENT, 0, DIRTY); CheckSpendCoin(ABSENT, SPENT, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(ABSENT, SPENT, SPENT, DIRTY, DIRTY); CheckSpendCoin(ABSENT, SPENT, ABSENT, DIRTY | FRESH, NO_ENTRY); CheckSpendCoin(ABSENT, VALUE2, SPENT, 0, DIRTY); CheckSpendCoin(ABSENT, VALUE2, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(ABSENT, VALUE2, SPENT, DIRTY, DIRTY); CheckSpendCoin(ABSENT, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); CheckSpendCoin(SPENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); CheckSpendCoin(SPENT, SPENT, SPENT, 0, DIRTY); CheckSpendCoin(SPENT, SPENT, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(SPENT, SPENT, SPENT, DIRTY, DIRTY); CheckSpendCoin(SPENT, SPENT, ABSENT, DIRTY | FRESH, NO_ENTRY); CheckSpendCoin(SPENT, VALUE2, SPENT, 0, DIRTY); CheckSpendCoin(SPENT, VALUE2, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(SPENT, VALUE2, SPENT, DIRTY, DIRTY); CheckSpendCoin(SPENT, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); CheckSpendCoin(VALUE1, ABSENT, SPENT, NO_ENTRY, DIRTY); CheckSpendCoin(VALUE1, SPENT, SPENT, 0, DIRTY); CheckSpendCoin(VALUE1, SPENT, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(VALUE1, SPENT, SPENT, DIRTY, DIRTY); CheckSpendCoin(VALUE1, SPENT, ABSENT, DIRTY | FRESH, NO_ENTRY); CheckSpendCoin(VALUE1, VALUE2, SPENT, 0, DIRTY); CheckSpendCoin(VALUE1, VALUE2, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(VALUE1, VALUE2, SPENT, DIRTY, DIRTY); CheckSpendCoin(VALUE1, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); } static void CheckAddCoinBase(Amount base_value, Amount cache_value, Amount modify_value, Amount expected_value, char cache_flags, char expected_flags, bool coinbase) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); Amount result_value; char result_flags; try { CTxOut output; output.nValue = modify_value; test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase); test.cache.SelfTest(); GetCoinMapEntry(test.cache.map(), result_value, result_flags); } catch (std::logic_error &) { result_value = FAIL; result_flags = NO_ENTRY; } BOOST_CHECK_EQUAL(result_value, expected_value); BOOST_CHECK_EQUAL(result_flags, expected_flags); } // Simple wrapper for CheckAddCoinBase function above that loops through // different possible base_values, making sure each one gives the same results. // This wrapper lets the coin_add test below be shorter and less repetitive, // while still verifying that the CoinsViewCache::AddCoin implementation ignores // base values. -template <typename... Args> static void CheckAddCoin(Args &&... args) { +template <typename... Args> static void CheckAddCoin(Args &&...args) { for (const Amount &base_value : {ABSENT, SPENT, VALUE1}) { CheckAddCoinBase(base_value, std::forward<Args>(args)...); } } BOOST_AUTO_TEST_CASE(coin_add) { /** * Check AddCoin behavior, requesting a new coin from a cache view, writing * a modification to the coin, and then checking the resulting entry in the * cache after the modification. Verify behavior with the AddCoin * possible_overwrite argument set to false, and to true. * * Cache Write Result Cache Result possible_overwrite * Value Value Value Flags Flags */ CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY | FRESH, false); CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY, true); CheckAddCoin(SPENT, VALUE3, VALUE3, 0, DIRTY | FRESH, false); CheckAddCoin(SPENT, VALUE3, VALUE3, 0, DIRTY, true); CheckAddCoin(SPENT, VALUE3, VALUE3, FRESH, DIRTY | FRESH, false); CheckAddCoin(SPENT, VALUE3, VALUE3, FRESH, DIRTY | FRESH, true); CheckAddCoin(SPENT, VALUE3, VALUE3, DIRTY, DIRTY, false); CheckAddCoin(SPENT, VALUE3, VALUE3, DIRTY, DIRTY, true); CheckAddCoin(SPENT, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, false); CheckAddCoin(SPENT, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, true); CheckAddCoin(VALUE2, VALUE3, FAIL, 0, NO_ENTRY, false); CheckAddCoin(VALUE2, VALUE3, VALUE3, 0, DIRTY, true); CheckAddCoin(VALUE2, VALUE3, FAIL, FRESH, NO_ENTRY, false); CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH, DIRTY | FRESH, true); CheckAddCoin(VALUE2, VALUE3, FAIL, DIRTY, NO_ENTRY, false); CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY, DIRTY, true); CheckAddCoin(VALUE2, VALUE3, FAIL, DIRTY | FRESH, NO_ENTRY, false); CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, true); } void CheckWriteCoin(Amount parent_value, Amount child_value, Amount expected_value, char parent_flags, char child_flags, char expected_flags) { SingleEntryCacheTest test(ABSENT, parent_value, parent_flags); Amount result_value; char result_flags; try { WriteCoinViewEntry(test.cache, child_value, child_flags); test.cache.SelfTest(); GetCoinMapEntry(test.cache.map(), result_value, result_flags); } catch (std::logic_error &) { result_value = FAIL; result_flags = NO_ENTRY; } BOOST_CHECK_EQUAL(result_value, expected_value); BOOST_CHECK_EQUAL(result_flags, expected_flags); } BOOST_AUTO_TEST_CASE(coin_write) { /* Check BatchWrite behavior, flushing one entry from a child cache to a * parent cache, and checking the resulting entry in the parent cache * after the write. * * Parent Child Result Parent Child Result * Value Value Value Flags Flags Flags */ CheckWriteCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY, NO_ENTRY); CheckWriteCoin(ABSENT, SPENT, SPENT, NO_ENTRY, DIRTY, DIRTY); CheckWriteCoin(ABSENT, SPENT, ABSENT, NO_ENTRY, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY, DIRTY); CheckWriteCoin(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY | FRESH, DIRTY | FRESH); CheckWriteCoin(SPENT, ABSENT, SPENT, 0, NO_ENTRY, 0); CheckWriteCoin(SPENT, ABSENT, SPENT, FRESH, NO_ENTRY, FRESH); CheckWriteCoin(SPENT, ABSENT, SPENT, DIRTY, NO_ENTRY, DIRTY); CheckWriteCoin(SPENT, ABSENT, SPENT, DIRTY | FRESH, NO_ENTRY, DIRTY | FRESH); CheckWriteCoin(SPENT, SPENT, SPENT, 0, DIRTY, DIRTY); CheckWriteCoin(SPENT, SPENT, SPENT, 0, DIRTY | FRESH, DIRTY); CheckWriteCoin(SPENT, SPENT, ABSENT, FRESH, DIRTY, NO_ENTRY); CheckWriteCoin(SPENT, SPENT, ABSENT, FRESH, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(SPENT, SPENT, SPENT, DIRTY, DIRTY, DIRTY); CheckWriteCoin(SPENT, SPENT, SPENT, DIRTY, DIRTY | FRESH, DIRTY); CheckWriteCoin(SPENT, SPENT, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY); CheckWriteCoin(SPENT, SPENT, ABSENT, DIRTY | FRESH, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(SPENT, VALUE2, VALUE2, 0, DIRTY, DIRTY); CheckWriteCoin(SPENT, VALUE2, VALUE2, 0, DIRTY | FRESH, DIRTY); CheckWriteCoin(SPENT, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH); CheckWriteCoin(SPENT, VALUE2, VALUE2, FRESH, DIRTY | FRESH, DIRTY | FRESH); CheckWriteCoin(SPENT, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY); CheckWriteCoin(SPENT, VALUE2, VALUE2, DIRTY, DIRTY | FRESH, DIRTY); CheckWriteCoin(SPENT, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, DIRTY | FRESH); CheckWriteCoin(SPENT, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH, DIRTY | FRESH); CheckWriteCoin(VALUE1, ABSENT, VALUE1, 0, NO_ENTRY, 0); CheckWriteCoin(VALUE1, ABSENT, VALUE1, FRESH, NO_ENTRY, FRESH); CheckWriteCoin(VALUE1, ABSENT, VALUE1, DIRTY, NO_ENTRY, DIRTY); CheckWriteCoin(VALUE1, ABSENT, VALUE1, DIRTY | FRESH, NO_ENTRY, DIRTY | FRESH); CheckWriteCoin(VALUE1, SPENT, SPENT, 0, DIRTY, DIRTY); CheckWriteCoin(VALUE1, SPENT, FAIL, 0, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, SPENT, ABSENT, FRESH, DIRTY, NO_ENTRY); CheckWriteCoin(VALUE1, SPENT, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, SPENT, SPENT, DIRTY, DIRTY, DIRTY); CheckWriteCoin(VALUE1, SPENT, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, SPENT, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY); CheckWriteCoin(VALUE1, SPENT, FAIL, DIRTY | FRESH, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, VALUE2, VALUE2, 0, DIRTY, DIRTY); CheckWriteCoin(VALUE1, VALUE2, FAIL, 0, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH); CheckWriteCoin(VALUE1, VALUE2, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY); CheckWriteCoin(VALUE1, VALUE2, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, DIRTY | FRESH); CheckWriteCoin(VALUE1, VALUE2, FAIL, DIRTY | FRESH, DIRTY | FRESH, NO_ENTRY); // The checks above omit cases where the child flags are not DIRTY, since // they would be too repetitive (the parent cache is never updated in these // cases). The loop below covers these cases and makes sure the parent cache // is always left unchanged. for (const Amount &parent_value : {ABSENT, SPENT, VALUE1}) { for (const Amount &child_value : {ABSENT, SPENT, VALUE2}) { for (const char parent_flags : parent_value == ABSENT ? ABSENT_FLAGS : FLAGS) { for (const char child_flags : child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS) { CheckWriteCoin(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags); } } } } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/sigencoding_tests.cpp b/src/test/sigencoding_tests.cpp index b2839d334..ebec9b513 100644 --- a/src/test/sigencoding_tests.cpp +++ b/src/test/sigencoding_tests.cpp @@ -1,439 +1,437 @@ // Copyright (c) 2018-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <script/sigencoding.h> #include <script/script_flags.h> #include <test/lcg.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> BOOST_FIXTURE_TEST_SUITE(sigencoding_tests, BasicTestingSetup) static valtype SignatureWithHashType(valtype vchSig, SigHashType sigHash) { vchSig.push_back(static_cast<uint8_t>(sigHash.getRawSigHashType())); return vchSig; } static void CheckSignatureEncodingWithSigHashType(const valtype &vchSig, uint32_t flags) { ScriptError err = ScriptError::OK; BOOST_CHECK(CheckDataSignatureEncoding(vchSig, flags, &err)); const bool hasForkId = (flags & SCRIPT_ENABLE_SIGHASH_FORKID) != 0; const bool hasStrictEnc = (flags & SCRIPT_VERIFY_STRICTENC) != 0; const bool is64 = (vchSig.size() == 64); std::vector<BaseSigHashType> allBaseTypes{ BaseSigHashType::ALL, BaseSigHashType::NONE, BaseSigHashType::SINGLE}; std::vector<SigHashType> baseSigHashes; for (const BaseSigHashType &baseType : allBaseTypes) { const SigHashType baseSigHash = SigHashType().withBaseType(baseType); baseSigHashes.push_back(baseSigHash); baseSigHashes.push_back(baseSigHash.withAnyoneCanPay(true)); } for (const SigHashType &baseSigHash : baseSigHashes) { // Check the signature with the proper forkid flag. SigHashType sigHash = baseSigHash.withForkId(hasForkId); valtype validSig = SignatureWithHashType(vchSig, sigHash); BOOST_CHECK(CheckTransactionSignatureEncoding(validSig, flags, &err)); BOOST_CHECK_EQUAL(!is64, CheckTransactionECDSASignatureEncoding( validSig, flags, &err)); BOOST_CHECK_EQUAL(is64, CheckTransactionSchnorrSignatureEncoding( validSig, flags, &err)); // If we have strict encoding, we prevent the use of undefined flags. std::array<SigHashType, 2> undefSigHashes{ {SigHashType(sigHash.getRawSigHashType() | 0x20), sigHash.withBaseType(BaseSigHashType::UNSUPPORTED)}}; for (SigHashType undefSigHash : undefSigHashes) { valtype undefSighash = SignatureWithHashType(vchSig, undefSigHash); BOOST_CHECK_EQUAL( CheckTransactionSignatureEncoding(undefSighash, flags, &err), !hasStrictEnc); if (hasStrictEnc) { BOOST_CHECK(err == ScriptError::SIG_HASHTYPE); } BOOST_CHECK_EQUAL(CheckTransactionECDSASignatureEncoding( undefSighash, flags, &err), !(hasStrictEnc || is64)); if (is64 || hasStrictEnc) { BOOST_CHECK(err == (is64 ? ScriptError::SIG_BADLENGTH : ScriptError::SIG_HASHTYPE)); } BOOST_CHECK_EQUAL(CheckTransactionSchnorrSignatureEncoding( undefSighash, flags, &err), !(hasStrictEnc || !is64)); if (!is64 || hasStrictEnc) { BOOST_CHECK(err == (!is64 ? ScriptError::SIG_NONSCHNORR : ScriptError::SIG_HASHTYPE)); } } // If we check strict encoding, then invalid forkid is an error. SigHashType invalidSigHash = baseSigHash.withForkId(!hasForkId); valtype invalidSig = SignatureWithHashType(vchSig, invalidSigHash); BOOST_CHECK_EQUAL( CheckTransactionSignatureEncoding(invalidSig, flags, &err), !hasStrictEnc); if (hasStrictEnc) { BOOST_CHECK(err == (hasForkId ? ScriptError::MUST_USE_FORKID : ScriptError::ILLEGAL_FORKID)); } BOOST_CHECK_EQUAL( CheckTransactionECDSASignatureEncoding(invalidSig, flags, &err), !(hasStrictEnc || is64)); if (is64 || hasStrictEnc) { - BOOST_CHECK(err == (is64 - ? ScriptError::SIG_BADLENGTH - : hasForkId ? ScriptError::MUST_USE_FORKID - : ScriptError::ILLEGAL_FORKID)); + BOOST_CHECK(err == (is64 ? ScriptError::SIG_BADLENGTH + : hasForkId ? ScriptError::MUST_USE_FORKID + : ScriptError::ILLEGAL_FORKID)); } BOOST_CHECK_EQUAL( CheckTransactionSchnorrSignatureEncoding(invalidSig, flags, &err), !(hasStrictEnc || !is64)); if (!is64 || hasStrictEnc) { - BOOST_CHECK(err == (!is64 - ? ScriptError::SIG_NONSCHNORR - : hasForkId ? ScriptError::MUST_USE_FORKID - : ScriptError::ILLEGAL_FORKID)); + BOOST_CHECK(err == (!is64 ? ScriptError::SIG_NONSCHNORR + : hasForkId ? ScriptError::MUST_USE_FORKID + : ScriptError::ILLEGAL_FORKID)); } } } BOOST_AUTO_TEST_CASE(checksignatureencoding_test) { valtype minimalSig{0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01}; valtype highSSig{ 0x30, 0x45, 0x02, 0x20, 0x3e, 0x45, 0x16, 0xda, 0x72, 0x53, 0xcf, 0x06, 0x8e, 0xff, 0xec, 0x6b, 0x95, 0xc4, 0x12, 0x21, 0xc0, 0xcf, 0x3a, 0x8e, 0x6c, 0xcb, 0x8c, 0xbf, 0x17, 0x25, 0xb5, 0x62, 0xe9, 0xaf, 0xde, 0x2c, 0x02, 0x21, 0x00, 0xab, 0x1e, 0x3d, 0xa7, 0x3d, 0x67, 0xe3, 0x20, 0x45, 0xa2, 0x0e, 0x0b, 0x99, 0x9e, 0x04, 0x99, 0x78, 0xea, 0x8d, 0x6e, 0xe5, 0x48, 0x0d, 0x48, 0x5f, 0xcf, 0x2c, 0xe0, 0xd0, 0x3b, 0x2e, 0xf0}; std::vector<valtype> nonDERSigs{ // Non canonical total length. {0x30, 0x80, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01}, // Zero length R. {0x30, 0x2f, 0x02, 0x00, 0x02, 0x21, 0x00, 0xab, 0x1e, 0x3d, 0xa7, 0x3d, 0x67, 0xe3, 0x20, 0x45, 0xa2, 0x0e, 0x0b, 0x99, 0x9e, 0x04, 0x99, 0x78, 0xea, 0x8d, 0x6e, 0xe5, 0x48, 0x0d, 0x48, 0x5f, 0xcf, 0x2c, 0xe0, 0xd0, 0x3b, 0x2e, 0xf0}, // Non canonical length for R. {0x30, 0x31, 0x02, 0x80, 0x01, 0x6c, 0x02, 0x21, 0x00, 0xab, 0x1e, 0x3d, 0xa7, 0x3d, 0x67, 0xe3, 0x20, 0x45, 0xa2, 0x0e, 0x0b, 0x99, 0x9e, 0x04, 0x99, 0x78, 0xea, 0x8d, 0x6e, 0xe5, 0x48, 0x0d, 0x48, 0x5f, 0xcf, 0x2c, 0xe0, 0xd0, 0x3b, 0x2e, 0xf0}, // Negative R. {0x30, 0x30, 0x02, 0x01, 0x80, 0x02, 0x21, 0x00, 0xab, 0x1e, 0x3d, 0xa7, 0x3d, 0x67, 0xe3, 0x20, 0x45, 0xa2, 0x0e, 0x0b, 0x99, 0x9e, 0x04, 0x99, 0x78, 0xea, 0x8d, 0x6e, 0xe5, 0x48, 0x0d, 0x48, 0x5f, 0xcf, 0x2c, 0xe0, 0xd0, 0x3b, 0x2e, 0xf0}, // Null prefixed R. {0x30, 0x31, 0x02, 0x02, 0x00, 0x01, 0x02, 0x21, 0x00, 0xab, 0x1e, 0x3d, 0xa7, 0x3d, 0x67, 0xe3, 0x20, 0x45, 0xa2, 0x0e, 0x0b, 0x99, 0x9e, 0x04, 0x99, 0x78, 0xea, 0x8d, 0x6e, 0xe5, 0x48, 0x0d, 0x48, 0x5f, 0xcf, 0x2c, 0xe0, 0xd0, 0x3b, 0x2e, 0xf0}, // Zero length S. {0x30, 0x2f, 0x02, 0x21, 0x00, 0xab, 0x1e, 0x3d, 0xa7, 0x3d, 0x67, 0xe3, 0x20, 0x45, 0xa2, 0x0e, 0x0b, 0x99, 0x9e, 0x04, 0x99, 0x78, 0xea, 0x8d, 0x6e, 0xe5, 0x48, 0x0d, 0x48, 0x5f, 0xcf, 0x2c, 0xe0, 0xd0, 0x3b, 0x2e, 0xf0, 0x02, 0x00}, // Non canonical length for S. {0x30, 0x31, 0x02, 0x21, 0x00, 0xab, 0x1e, 0x3d, 0xa7, 0x3d, 0x67, 0xe3, 0x20, 0x45, 0xa2, 0x0e, 0x0b, 0x99, 0x9e, 0x04, 0x99, 0x78, 0xea, 0x8d, 0x6e, 0xe5, 0x48, 0x0d, 0x48, 0x5f, 0xcf, 0x2c, 0xe0, 0xd0, 0x3b, 0x2e, 0xf0, 0x02, 0x80, 0x01, 0x6c}, // Negative S. {0x30, 0x30, 0x02, 0x21, 0x00, 0xab, 0x1e, 0x3d, 0xa7, 0x3d, 0x67, 0xe3, 0x20, 0x45, 0xa2, 0x0e, 0x0b, 0x99, 0x9e, 0x04, 0x99, 0x78, 0xea, 0x8d, 0x6e, 0xe5, 0x48, 0x0d, 0x48, 0x5f, 0xcf, 0x2c, 0xe0, 0xd0, 0x3b, 0x2e, 0xf0, 0x02, 0x01, 0x80}, // Null prefixed S. {0x30, 0x31, 0x02, 0x21, 0x00, 0xab, 0x1e, 0x3d, 0xa7, 0x3d, 0x67, 0xe3, 0x20, 0x45, 0xa2, 0x0e, 0x0b, 0x99, 0x9e, 0x04, 0x99, 0x78, 0xea, 0x8d, 0x6e, 0xe5, 0x48, 0x0d, 0x48, 0x5f, 0xcf, 0x2c, 0xe0, 0xd0, 0x3b, 0x2e, 0xf0, 0x02, 0x02, 0x00, 0x01}, }; std::vector<valtype> nonParsableSigs{ // Too short. {0x30}, {0x30, 0x06}, {0x30, 0x06, 0x02}, {0x30, 0x06, 0x02, 0x01}, {0x30, 0x06, 0x02, 0x01, 0x01}, {0x30, 0x06, 0x02, 0x01, 0x01, 0x02}, {0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01}, // Invalid type (must be 0x30, coumpound). {0x42, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01}, // Invalid sizes. {0x30, 0x05, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01}, {0x30, 0x07, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01}, // Invalid R and S sizes. {0x30, 0x06, 0x02, 0x00, 0x01, 0x02, 0x01, 0x01}, {0x30, 0x06, 0x02, 0x02, 0x01, 0x02, 0x01, 0x01}, {0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x00, 0x01}, {0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01}, // Invalid R and S types. {0x30, 0x06, 0x42, 0x01, 0x01, 0x02, 0x01, 0x01}, {0x30, 0x06, 0x02, 0x01, 0x01, 0x42, 0x01, 0x01}, // S out of bounds. {0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x02, 0x00}, // Too long. {0x30, 0x47, 0x02, 0x21, 0x00, 0x8e, 0x45, 0x16, 0xda, 0x72, 0x53, 0xcf, 0x06, 0x8e, 0xff, 0xec, 0x6b, 0x95, 0xc4, 0x12, 0x21, 0xc0, 0xcf, 0x3a, 0x8e, 0x6c, 0xcb, 0x8c, 0xbf, 0x17, 0x25, 0xb5, 0x62, 0xe9, 0xaf, 0xde, 0x2c, 0x02, 0x22, 0x00, 0xab, 0x1e, 0x3d, 0x00, 0xa7, 0x3d, 0x67, 0xe3, 0x20, 0x45, 0xa2, 0x0e, 0x0b, 0x99, 0x9e, 0x04, 0x99, 0x78, 0xea, 0x8d, 0x6e, 0xe5, 0x48, 0x0d, 0x48, 0x5f, 0xcf, 0x2c, 0xe0, 0xd0, 0x3b, 0x2e, 0xf0}, }; valtype Zero64(64, 0); MMIXLinearCongruentialGenerator lcg; for (int i = 0; i < 4096; i++) { uint32_t flags = lcg.next(); ScriptError err = ScriptError::OK; // Empty sig is always validly encoded. BOOST_CHECK(CheckDataSignatureEncoding({}, flags, &err)); BOOST_CHECK(CheckTransactionSignatureEncoding({}, flags, &err)); BOOST_CHECK(CheckTransactionECDSASignatureEncoding({}, flags, &err)); BOOST_CHECK(CheckTransactionSchnorrSignatureEncoding({}, flags, &err)); // 64-byte signatures are valid as long as the hashtype is correct. CheckSignatureEncodingWithSigHashType(Zero64, flags); // Signatures are valid as long as the hashtype is correct. CheckSignatureEncodingWithSigHashType(minimalSig, flags); if (flags & SCRIPT_VERIFY_LOW_S) { // If we do enforce low S, then high S sigs are rejected. BOOST_CHECK(!CheckDataSignatureEncoding(highSSig, flags, &err)); BOOST_CHECK(err == ScriptError::SIG_HIGH_S); } else { // If we do not enforce low S, then high S sigs are accepted. CheckSignatureEncodingWithSigHashType(highSSig, flags); } for (const valtype &nonDERSig : nonDERSigs) { if (flags & (SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC)) { // If we get any of the dersig flags, the non canonical dersig // signature fails. BOOST_CHECK( !CheckDataSignatureEncoding(nonDERSig, flags, &err)); BOOST_CHECK(err == ScriptError::SIG_DER); } else { // If we do not check, then it is accepted. BOOST_CHECK(CheckDataSignatureEncoding(nonDERSig, flags, &err)); } } for (const valtype &nonDERSig : nonParsableSigs) { if (flags & (SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC)) { // If we get any of the dersig flags, the high S but non dersig // signature fails. BOOST_CHECK( !CheckDataSignatureEncoding(nonDERSig, flags, &err)); BOOST_CHECK(err == ScriptError::SIG_DER); } else { // If we do not check, then it is accepted. BOOST_CHECK(CheckDataSignatureEncoding(nonDERSig, flags, &err)); } } } } BOOST_AUTO_TEST_CASE(checkpubkeyencoding_test) { valtype compressedKey0{0x02, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}; valtype compressedKey1{0x03, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}; valtype fullKey{0x04, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}; std::vector<valtype> invalidKeys{ // Degenerate keys. {}, {0x00}, {0x01}, {0x02}, {0x03}, {0x04}, {0x05}, {0x42}, {0xff}, // Invalid first byte 0x00. {0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, {0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}, // Invalid first byte 0x01. {0x01, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, {0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}, // Invalid first byte 0x05. {0x05, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, {0x05, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}, // Invalid first byte 0xff. {0xff, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, {0xff, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}, // Compressed key too short. {0x02, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, {0x03, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}, // Compressed key too long. {0x02, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0xab, 0xba, 0x9a, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, {0x03, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xab, 0xba, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}, // Compressed key, full key size. {0x02, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}, {0x03, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}, // Full key, too short. {0x04, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}, // Full key, too long. {0x04, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x56, 0x78, 0xab, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0f, 0xff}, // Full key, compressed key size. {0x04, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0xab, 0xba, 0x9a, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, }; MMIXLinearCongruentialGenerator lcg; for (int i = 0; i < 4096; i++) { uint32_t flags = lcg.next(); ScriptError err = ScriptError::OK; // Compressed and uncompressed pubkeys are always valid. BOOST_CHECK(CheckPubKeyEncoding(compressedKey0, flags, &err)); BOOST_CHECK(CheckPubKeyEncoding(compressedKey1, flags, &err)); BOOST_CHECK(CheckPubKeyEncoding(fullKey, flags, &err)); // If SCRIPT_VERIFY_STRICTENC is specified, we rule out invalid keys. const bool hasStrictEnc = (flags & SCRIPT_VERIFY_STRICTENC) != 0; const bool allowInvalidKeys = !hasStrictEnc; for (const valtype &key : invalidKeys) { BOOST_CHECK_EQUAL(CheckPubKeyEncoding(key, flags, &err), allowInvalidKeys); if (!allowInvalidKeys) { BOOST_CHECK(err == ScriptError::PUBKEYTYPE); } } } } BOOST_AUTO_TEST_CASE(checkschnorr_test) { // tests using 64 byte sigs (+hashtype byte where relevant) valtype Zero64(64, 0); valtype DER64{0x30, 0x3e, 0x02, 0x1d, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x02, 0x1d, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44}; BOOST_REQUIRE_EQUAL(Zero64.size(), 64); BOOST_REQUIRE_EQUAL(DER64.size(), 64); MMIXLinearCongruentialGenerator lcg; for (int i = 0; i < 4096; i++) { uint32_t flags = lcg.next(); const bool hasForkId = (flags & SCRIPT_ENABLE_SIGHASH_FORKID) != 0; ScriptError err = ScriptError::OK; valtype DER65_hb = SignatureWithHashType(DER64, SigHashType().withForkId(hasForkId)); valtype Zero65_hb = SignatureWithHashType(Zero64, SigHashType().withForkId(hasForkId)); BOOST_CHECK(CheckDataSignatureEncoding(DER64, flags, &err)); BOOST_CHECK(CheckTransactionSignatureEncoding(DER65_hb, flags, &err)); BOOST_CHECK( !CheckTransactionECDSASignatureEncoding(DER65_hb, flags, &err)); BOOST_CHECK(err == ScriptError::SIG_BADLENGTH); BOOST_CHECK( CheckTransactionSchnorrSignatureEncoding(DER65_hb, flags, &err)); BOOST_CHECK(CheckDataSignatureEncoding(Zero64, flags, &err)); BOOST_CHECK(CheckTransactionSignatureEncoding(Zero65_hb, flags, &err)); BOOST_CHECK( !CheckTransactionECDSASignatureEncoding(Zero65_hb, flags, &err)); BOOST_CHECK(err == ScriptError::SIG_BADLENGTH); BOOST_CHECK( CheckTransactionSchnorrSignatureEncoding(Zero65_hb, flags, &err)); } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tinyformat.h b/src/tinyformat.h index 4d5fd0d7b..1891eccf6 100644 --- a/src/tinyformat.h +++ b/src/tinyformat.h @@ -1,1203 +1,1203 @@ // tinyformat.h // Copyright (C) 2011, Chris Foster [chris42f (at) gmail (d0t) com] // // Boost Software License - Version 1.0 // // Permission is hereby granted, free of charge, to any person or organization // obtaining a copy of the software and accompanying documentation covered by // this license (the "Software") to use, reproduce, display, distribute, // execute, and transmit the Software, and to prepare derivative works of the // Software, and to permit third-parties to whom the Software is furnished to // do so, all subject to the following: // // The copyright notices in the Software and this entire statement, including // the above license grant, this restriction and the following disclaimer, // must be included in all copies of the Software, in whole or in part, and // all derivative works of the Software, unless such copies or derivative // works are solely in the form of machine-executable object code generated by // a source language processor. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT // SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE // FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. //------------------------------------------------------------------------------ // Tinyformat: A minimal type safe printf replacement // // tinyformat.h is a type safe printf replacement library in a single C++ // header file. Design goals include: // // * Type safety and extensibility for user defined types. // * C99 printf() compatibility, to the extent possible using std::ostream // * POSIX extension for positional arguments // * Simplicity and minimalism. A single header file to include and distribute // with your projects. // * Augment rather than replace the standard stream formatting mechanism // * C++98 support, with optional C++11 niceties // // // Main interface example usage // ---------------------------- // // To print a date to std::cout for American usage: // // std::string weekday = "Wednesday"; // const char* month = "July"; // size_t day = 27; // long hour = 14; // int min = 44; // // tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min); // // POSIX extension for positional arguments is available. // The ability to rearrange formatting arguments is an important feature // for localization because the word order may vary in different languages. // // Previous example for German usage. Arguments are reordered: // // tfm::printf("%1$s, %3$d. %2$s, %4$d:%5$.2d\n", weekday, month, day, hour, // min); // // The strange types here emphasize the type safety of the interface; it is // possible to print a std::string using the "%s" conversion, and a // size_t using the "%d" conversion. A similar result could be achieved // using either of the tfm::format() functions. One prints on a user provided // stream: // // tfm::format(std::cerr, "%s, %s %d, %.2d:%.2d\n", // weekday, month, day, hour, min); // // The other returns a std::string: // // std::string date = tfm::format("%s, %s %d, %.2d:%.2d\n", // weekday, month, day, hour, min); // std::cout << date; // // These are the three primary interface functions. There is also a // convenience function printfln() which appends a newline to the usual result // of printf() for super simple logging. // // // User defined format functions // ----------------------------- // // Simulating variadic templates in C++98 is pretty painful since it requires // writing out the same function for each desired number of arguments. To make // this bearable tinyformat comes with a set of macros which are used // internally to generate the API, but which may also be used in user code. // // The three macros TINYFORMAT_ARGTYPES(n), TINYFORMAT_VARARGS(n) and // TINYFORMAT_PASSARGS(n) will generate a list of n argument types, // type/name pairs and argument names respectively when called with an integer // n between 1 and 16. We can use these to define a macro which generates the // desired user defined function with n arguments. To generate all 16 user // defined function bodies, use the macro TINYFORMAT_FOREACH_ARGNUM. For an // example, see the implementation of printf() at the end of the source file. // // Sometimes it's useful to be able to pass a list of format arguments through // to a non-template function. The FormatList class is provided as a way to do // this by storing the argument list in a type-opaque way. Continuing the // example from above, we construct a FormatList using makeFormatList(): // // FormatListRef formatList = tfm::makeFormatList(weekday, month, day, hour, // min); // // The format list can now be passed into any non-template function and used // via a call to the vformat() function: // // tfm::vformat(std::cout, "%s, %s %d, %.2d:%.2d\n", formatList); // // // Additional API information // -------------------------- // // Error handling: Define TINYFORMAT_ERROR to customize the error handling for // format strings which are unsupported or have the wrong number of format // specifiers (calls assert() by default). // // User defined types: Uses operator<< for user defined types by default. // Overload formatValue() for more control. #ifndef TINYFORMAT_H_INCLUDED #define TINYFORMAT_H_INCLUDED namespace tinyformat {} //------------------------------------------------------------------------------ // Config section. Customize to your liking! // Namespace alias to encourage brevity namespace tfm = tinyformat; // Error handling; calls assert() by default. #define TINYFORMAT_ERROR(reasonString) \ throw tinyformat::format_error(reasonString) // Define for C++11 variadic templates which make the code shorter & more // general. If you don't define this, C++11 support is autodetected below. #define TINYFORMAT_USE_VARIADIC_TEMPLATES //------------------------------------------------------------------------------ // Implementation details. #include <algorithm> #include <iostream> #include <sstream> #include <stdexcept> // Added for Bitcoin #ifndef TINYFORMAT_ASSERT #include <cassert> #define TINYFORMAT_ASSERT(cond) assert(cond) #endif #ifndef TINYFORMAT_ERROR #include <cassert> #define TINYFORMAT_ERROR(reason) assert(0 && reason) #endif #if !defined(TINYFORMAT_USE_VARIADIC_TEMPLATES) && \ !defined(TINYFORMAT_NO_VARIADIC_TEMPLATES) #ifdef __GXX_EXPERIMENTAL_CXX0X__ #define TINYFORMAT_USE_VARIADIC_TEMPLATES #endif #endif #if defined(__GLIBCXX__) && __GLIBCXX__ < 20080201 // std::showpos is broken on old libstdc++ as provided with macOS. See // http://gcc.gnu.org/ml/libstdc++/2007-11/msg00075.html #define TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND #endif #ifdef __APPLE__ // Workaround macOS linker warning: Xcode uses different default symbol // visibilities for static libs vs executables (see issue #25) #define TINYFORMAT_HIDDEN __attribute__((visibility("hidden"))) #else #define TINYFORMAT_HIDDEN #endif namespace tinyformat { // Added for Bitcoin class format_error : public std::runtime_error { public: explicit format_error(const std::string &what) : std::runtime_error(what) {} }; //------------------------------------------------------------------------------ namespace detail { // Test whether type T1 is convertible to type T2 template <typename T1, typename T2> struct is_convertible { private: // two types of different size struct fail { char dummy[2]; }; struct succeed { char dummy; }; // Try to convert a T1 to a T2 by plugging into tryConvert static fail tryConvert(...); static succeed tryConvert(const T2 &); static const T1 &makeT1(); public: #ifdef _MSC_VER // Disable spurious loss of precision warnings in tryConvert(makeT1()) #pragma warning(push) #pragma warning(disable : 4244) #pragma warning(disable : 4267) #endif // Standard trick: the (...) version of tryConvert will be chosen from // the overload set only if the version taking a T2 doesn't match. Then // we compare the sizes of the return types to check which function // matched. Very neat, in a disgusting kind of way :) static const bool value = sizeof(tryConvert(makeT1())) == sizeof(succeed); #ifdef _MSC_VER #pragma warning(pop) #endif }; // Detect when a type is not a wchar_t string template <typename T> struct is_wchar { typedef int tinyformat_wchar_is_not_supported; }; template <> struct is_wchar<wchar_t *> {}; template <> struct is_wchar<const wchar_t *> {}; template <int n> struct is_wchar<const wchar_t[n]> {}; template <int n> struct is_wchar<wchar_t[n]> {}; // Format the value by casting to type fmtT. This default implementation // should never be called. template <typename T, typename fmtT, bool convertible = is_convertible<T, fmtT>::value> struct formatValueAsType { static void invoke(std::ostream & /*out*/, const T & /*value*/) { TINYFORMAT_ASSERT(0); } }; // Specialized version for types that can actually be converted to fmtT, as // indicated by the "convertible" template parameter. template <typename T, typename fmtT> struct formatValueAsType<T, fmtT, true> { static void invoke(std::ostream &out, const T &value) { out << static_cast<fmtT>(value); } }; #ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND template <typename T, bool convertible = is_convertible<T, int>::value> struct formatZeroIntegerWorkaround { static bool invoke(std::ostream & /**/, const T & /**/) { return false; } }; template <typename T> struct formatZeroIntegerWorkaround<T, true> { static bool invoke(std::ostream &out, const T &value) { if (static_cast<int>(value) == 0 && out.flags() & std::ios::showpos) { out << "+0"; return true; } return false; } }; #endif // TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND // Convert an arbitrary type to integer. The version with convertible=false // throws an error. template <typename T, bool convertible = is_convertible<T, int>::value> struct convertToInt { static int invoke(const T & /*value*/) { TINYFORMAT_ERROR("tinyformat: Cannot convert from argument type to " "integer for use as variable width or precision"); return 0; } }; // Specialization for convertToInt when conversion is possible template <typename T> struct convertToInt<T, true> { static int invoke(const T &value) { return static_cast<int>(value); } }; // Format at most ntrunc characters to the given stream. template <typename T> inline void formatTruncated(std::ostream &out, const T &value, int ntrunc) { std::ostringstream tmp; tmp << value; std::string result = tmp.str(); out.write(result.c_str(), (std::min)(ntrunc, static_cast<int>(result.size()))); } #define TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(type) \ inline void formatTruncated(std::ostream &out, type *value, int ntrunc) { \ std::streamsize len = 0; \ while (len < ntrunc && value[len] != 0) \ ++len; \ out.write(value, len); \ } // Overload for const char* and char*. Could overload for signed & unsigned // char too, but these are technically unneeded for printf compatibility. TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(const char) TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(char) #undef TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR } // namespace detail //------------------------------------------------------------------------------ // Variable formatting functions. May be overridden for user-defined types if // desired. /// Format a value into a stream, delegating to operator<< by default. /// /// Users may override this for their own types. When this function is called, /// the stream flags will have been modified according to the format string. /// The format specification is provided in the range [fmtBegin, fmtEnd). For /// truncating conversions, ntrunc is set to the desired maximum number of /// characters, for example "%.7s" calls formatValue with ntrunc = 7. /// /// By default, formatValue() uses the usual stream insertion operator /// operator<< to format the type T, with special cases for the %c and %p /// conversions. template <typename T> inline void formatValue(std::ostream &out, const char * /*fmtBegin*/, const char *fmtEnd, int ntrunc, const T &value) { #ifndef TINYFORMAT_ALLOW_WCHAR_STRINGS // Since we don't support printing of wchar_t using "%ls", make it fail at // compile time in preference to printing as a void* at runtime. typedef typename detail::is_wchar<T>::tinyformat_wchar_is_not_supported DummyType; (void)DummyType(); // avoid unused type warning with gcc-4.8 #endif // The mess here is to support the %c and %p conversions: if these // conversions are active we try to convert the type to a char or const // void* respectively and format that instead of the value itself. For the // %p conversion it's important to avoid dereferencing the pointer, which // could otherwise lead to a crash when printing a dangling (const char*). const bool canConvertToChar = detail::is_convertible<T, char>::value; const bool canConvertToVoidPtr = detail::is_convertible<T, const void *>::value; if (canConvertToChar && *(fmtEnd - 1) == 'c') detail::formatValueAsType<T, char>::invoke(out, value); else if (canConvertToVoidPtr && *(fmtEnd - 1) == 'p') detail::formatValueAsType<T, const void *>::invoke(out, value); #ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND else if (detail::formatZeroIntegerWorkaround<T>::invoke(out, value)) /**/ ; #endif else if (ntrunc >= 0) { // Take care not to overread C strings in truncating conversions like // "%.4s" where at most 4 characters may be read. detail::formatTruncated(out, value, ntrunc); } else out << value; } // Overloaded version for char types to support printing as an integer #define TINYFORMAT_DEFINE_FORMATVALUE_CHAR(charType) \ inline void formatValue(std::ostream &out, const char * /*fmtBegin*/, \ const char *fmtEnd, int /**/, charType value) { \ switch (*(fmtEnd - 1)) { \ case 'u': \ case 'd': \ case 'i': \ case 'o': \ case 'X': \ case 'x': \ out << static_cast<int>(value); \ break; \ default: \ out << value; \ break; \ } \ } // per 3.9.1: char, signed char and uint8_t are all distinct types TINYFORMAT_DEFINE_FORMATVALUE_CHAR(char) TINYFORMAT_DEFINE_FORMATVALUE_CHAR(signed char) TINYFORMAT_DEFINE_FORMATVALUE_CHAR(uint8_t) #undef TINYFORMAT_DEFINE_FORMATVALUE_CHAR //------------------------------------------------------------------------------ // Tools for emulating variadic templates in C++98. The basic idea here is // stolen from the boost preprocessor metaprogramming library and cut down to // be just general enough for what we need. #define TINYFORMAT_ARGTYPES(n) TINYFORMAT_ARGTYPES_##n #define TINYFORMAT_VARARGS(n) TINYFORMAT_VARARGS_##n #define TINYFORMAT_PASSARGS(n) TINYFORMAT_PASSARGS_##n #define TINYFORMAT_PASSARGS_TAIL(n) TINYFORMAT_PASSARGS_TAIL_##n // To keep it as transparent as possible, the macros below have been generated // using python via the excellent cog.py code generation script. This avoids // the need for a bunch of complex (but more general) preprocessor tricks as // used in boost.preprocessor. // // To rerun the code generation in place, use `cog.py -r tinyformat.h` // (see http://nedbatchelder.com/code/cog). Alternatively you can just create // extra versions by hand. /*[[[cog maxParams = 16 def makeCommaSepLists(lineTemplate, elemTemplate, startInd=1): for j in range(startInd,maxParams+1): list = ', '.join([elemTemplate % {'i':i} for i in range(startInd,j+1)]) cog.outl(lineTemplate % {'j':j, 'list':list}) makeCommaSepLists('#define TINYFORMAT_ARGTYPES_%(j)d %(list)s', 'class T%(i)d') cog.outl() makeCommaSepLists('#define TINYFORMAT_VARARGS_%(j)d %(list)s', 'const T%(i)d& v%(i)d') cog.outl() makeCommaSepLists('#define TINYFORMAT_PASSARGS_%(j)d %(list)s', 'v%(i)d') cog.outl() cog.outl('#define TINYFORMAT_PASSARGS_TAIL_1') makeCommaSepLists('#define TINYFORMAT_PASSARGS_TAIL_%(j)d , %(list)s', 'v%(i)d', startInd = 2) cog.outl() cog.outl('#define TINYFORMAT_FOREACH_ARGNUM(m) \\\n ' + ' '.join(['m(%d)' % (j,) for j in range(1,maxParams+1)])) ]]]*/ #define TINYFORMAT_ARGTYPES_1 class T1 #define TINYFORMAT_ARGTYPES_2 class T1, class T2 #define TINYFORMAT_ARGTYPES_3 class T1, class T2, class T3 #define TINYFORMAT_ARGTYPES_4 class T1, class T2, class T3, class T4 #define TINYFORMAT_ARGTYPES_5 class T1, class T2, class T3, class T4, class T5 #define TINYFORMAT_ARGTYPES_6 \ class T1, class T2, class T3, class T4, class T5, class T6 #define TINYFORMAT_ARGTYPES_7 \ class T1, class T2, class T3, class T4, class T5, class T6, class T7 #define TINYFORMAT_ARGTYPES_8 \ class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ class T8 #define TINYFORMAT_ARGTYPES_9 \ class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ class T8, class T9 #define TINYFORMAT_ARGTYPES_10 \ class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ class T8, class T9, class T10 #define TINYFORMAT_ARGTYPES_11 \ class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ class T8, class T9, class T10, class T11 #define TINYFORMAT_ARGTYPES_12 \ class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ class T8, class T9, class T10, class T11, class T12 #define TINYFORMAT_ARGTYPES_13 \ class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ class T8, class T9, class T10, class T11, class T12, class T13 #define TINYFORMAT_ARGTYPES_14 \ class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ class T8, class T9, class T10, class T11, class T12, class T13, \ class T14 #define TINYFORMAT_ARGTYPES_15 \ class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ class T8, class T9, class T10, class T11, class T12, class T13, \ class T14, class T15 #define TINYFORMAT_ARGTYPES_16 \ class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ class T8, class T9, class T10, class T11, class T12, class T13, \ class T14, class T15, class T16 #define TINYFORMAT_VARARGS_1 const T1 &v1 #define TINYFORMAT_VARARGS_2 const T1 &v1, const T2 &v2 #define TINYFORMAT_VARARGS_3 const T1 &v1, const T2 &v2, const T3 &v3 #define TINYFORMAT_VARARGS_4 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4 #define TINYFORMAT_VARARGS_5 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5 #define TINYFORMAT_VARARGS_6 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6 #define TINYFORMAT_VARARGS_7 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6, const T7 &v7 #define TINYFORMAT_VARARGS_8 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6, const T7 &v7, const T8 &v8 #define TINYFORMAT_VARARGS_9 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9 #define TINYFORMAT_VARARGS_10 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, const T10 &v10 #define TINYFORMAT_VARARGS_11 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, \ const T10 &v10, const T11 &v11 #define TINYFORMAT_VARARGS_12 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, \ const T10 &v10, const T11 &v11, const T12 &v12 #define TINYFORMAT_VARARGS_13 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, \ const T10 &v10, const T11 &v11, const T12 &v12, const T13 &v13 #define TINYFORMAT_VARARGS_14 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, \ const T10 &v10, const T11 &v11, const T12 &v12, const T13 &v13, \ const T14 &v14 #define TINYFORMAT_VARARGS_15 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, \ const T10 &v10, const T11 &v11, const T12 &v12, const T13 &v13, \ const T14 &v14, const T15 &v15 #define TINYFORMAT_VARARGS_16 \ const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, \ const T10 &v10, const T11 &v11, const T12 &v12, const T13 &v13, \ const T14 &v14, const T15 &v15, const T16 &v16 #define TINYFORMAT_PASSARGS_1 v1 #define TINYFORMAT_PASSARGS_2 v1, v2 #define TINYFORMAT_PASSARGS_3 v1, v2, v3 #define TINYFORMAT_PASSARGS_4 v1, v2, v3, v4 #define TINYFORMAT_PASSARGS_5 v1, v2, v3, v4, v5 #define TINYFORMAT_PASSARGS_6 v1, v2, v3, v4, v5, v6 #define TINYFORMAT_PASSARGS_7 v1, v2, v3, v4, v5, v6, v7 #define TINYFORMAT_PASSARGS_8 v1, v2, v3, v4, v5, v6, v7, v8 #define TINYFORMAT_PASSARGS_9 v1, v2, v3, v4, v5, v6, v7, v8, v9 #define TINYFORMAT_PASSARGS_10 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10 #define TINYFORMAT_PASSARGS_11 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 #define TINYFORMAT_PASSARGS_12 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 #define TINYFORMAT_PASSARGS_13 \ v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 #define TINYFORMAT_PASSARGS_14 \ v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14 #define TINYFORMAT_PASSARGS_15 \ v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 #define TINYFORMAT_PASSARGS_16 \ v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 #define TINYFORMAT_PASSARGS_TAIL_1 #define TINYFORMAT_PASSARGS_TAIL_2 , v2 #define TINYFORMAT_PASSARGS_TAIL_3 , v2, v3 #define TINYFORMAT_PASSARGS_TAIL_4 , v2, v3, v4 #define TINYFORMAT_PASSARGS_TAIL_5 , v2, v3, v4, v5 #define TINYFORMAT_PASSARGS_TAIL_6 , v2, v3, v4, v5, v6 #define TINYFORMAT_PASSARGS_TAIL_7 , v2, v3, v4, v5, v6, v7 #define TINYFORMAT_PASSARGS_TAIL_8 , v2, v3, v4, v5, v6, v7, v8 #define TINYFORMAT_PASSARGS_TAIL_9 , v2, v3, v4, v5, v6, v7, v8, v9 #define TINYFORMAT_PASSARGS_TAIL_10 , v2, v3, v4, v5, v6, v7, v8, v9, v10 #define TINYFORMAT_PASSARGS_TAIL_11 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 #define TINYFORMAT_PASSARGS_TAIL_12 \ , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 #define TINYFORMAT_PASSARGS_TAIL_13 \ , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 #define TINYFORMAT_PASSARGS_TAIL_14 \ , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14 #define TINYFORMAT_PASSARGS_TAIL_15 \ , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 #define TINYFORMAT_PASSARGS_TAIL_16 \ , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 #define TINYFORMAT_FOREACH_ARGNUM(m) \ m(1) m(2) m(3) m(4) m(5) m(6) m(7) m(8) m(9) m(10) m(11) m(12) m(13) m(14) \ m(15) m(16) //[[[end]]] namespace detail { // Type-opaque holder for an argument to format(), with associated actions // on the type held as explicit function pointers. This allows FormatArg's // for each argument to be allocated as a homogeneous array inside // FormatList whereas a naive implementation based on inheritance does not. class FormatArg { public: FormatArg() : m_value(NULL), m_formatImpl(NULL), m_toIntImpl(NULL) {} template <typename T> FormatArg(const T &value) : m_value(static_cast<const void *>(&value)), m_formatImpl(&formatImpl<T>), m_toIntImpl(&toIntImpl<T>) {} void format(std::ostream &out, const char *fmtBegin, const char *fmtEnd, int ntrunc) const { TINYFORMAT_ASSERT(m_value); TINYFORMAT_ASSERT(m_formatImpl); m_formatImpl(out, fmtBegin, fmtEnd, ntrunc, m_value); } int toInt() const { TINYFORMAT_ASSERT(m_value); TINYFORMAT_ASSERT(m_toIntImpl); return m_toIntImpl(m_value); } private: template <typename T> TINYFORMAT_HIDDEN static void formatImpl(std::ostream &out, const char *fmtBegin, const char *fmtEnd, int ntrunc, const void *value) { formatValue(out, fmtBegin, fmtEnd, ntrunc, *static_cast<const T *>(value)); } template <typename T> TINYFORMAT_HIDDEN static int toIntImpl(const void *value) { return convertToInt<T>::invoke(*static_cast<const T *>(value)); } const void *m_value; void (*m_formatImpl)(std::ostream &out, const char *fmtBegin, const char *fmtEnd, int ntrunc, const void *value); int (*m_toIntImpl)(const void *value); }; // Parse and return an integer from the string c, as atoi() // On return, c is set to one past the end of the integer. inline int parseIntAndAdvance(const char *&c) { int i = 0; for (; *c >= '0' && *c <= '9'; ++c) i = 10 * i + (*c - '0'); return i; } // Parse width or precision `n` from format string pointer `c`, and advance // it to the next character. If an indirection is requested with `*`, the // argument is read from `args[argIndex]` and `argIndex` is incremented (or // read from `args[n]` in positional mode). Returns true if one or more // characters were read. inline bool parseWidthOrPrecision(int &n, const char *&c, bool positionalMode, const detail::FormatArg *args, int &argIndex, int numArgs) { if (*c >= '0' && *c <= '9') { n = parseIntAndAdvance(c); } else if (*c == '*') { ++c; n = 0; if (positionalMode) { int pos = parseIntAndAdvance(c) - 1; if (*c != '$') TINYFORMAT_ERROR("tinyformat: Non-positional argument used " "after a positional one"); if (pos >= 0 && pos < numArgs) n = args[pos].toInt(); else TINYFORMAT_ERROR( "tinyformat: Positional argument out of range"); ++c; } else { if (argIndex < numArgs) n = args[argIndex++].toInt(); else TINYFORMAT_ERROR("tinyformat: Not enough arguments to read " "variable width or precision"); } } else { return false; } return true; } // Print literal part of format string and return next format spec position. // // Skips over any occurrences of '%%', printing a literal '%' to the output. // The position of the first % character of the next nontrivial format spec // is returned, or the end of string. inline const char *printFormatStringLiteral(std::ostream &out, const char *fmt) { const char *c = fmt; for (;; ++c) { if (*c == '\0') { out.write(fmt, c - fmt); return c; } else if (*c == '%') { out.write(fmt, c - fmt); if (*(c + 1) != '%') return c; // for "%%", tack trailing % onto next literal section. fmt = ++c; } } } // Parse a format string and set the stream state accordingly. // // The format mini-language recognized here is meant to be the one from C99, // with the form "%[flags][width][.precision][length]type" with POSIX // positional arguments extension. // // POSIX positional arguments extension: // Conversions can be applied to the nth argument after the format in // the argument list, rather than to the next unused argument. In this case, // the conversion specifier character % (see below) is replaced by the // sequence // "%n$", where n is a decimal integer in the range [1,{NL_ARGMAX}], // giving the position of the argument in the argument list. This feature // provides for the definition of format strings that select arguments // in an order appropriate to specific languages. // // The format can contain either numbered argument conversion specifications // (that is, "%n$" and "*m$"), or unnumbered argument conversion // specifications (that is, % and * ), but not both. The only exception to // this is that %% can be mixed with the "%n$" form. The results of mixing // numbered and unnumbered argument specifications in a format string are // undefined. When numbered argument specifications are used, specifying the // Nth argument requires that all the leading arguments, from the first to // the (N-1)th, are specified in the format string. // // In format strings containing the "%n$" form of conversion specification, // numbered arguments in the argument list can be referenced from the format // string as many times as required. // // Formatting options which can't be natively represented using the ostream // state are returned in spacePadPositive (for space padded positive // numbers) and ntrunc (for truncating conversions). argIndex is incremented // if necessary to pull out variable width and precision. The function // returns a pointer to the character after the end of the current format // spec. inline const char *streamStateFromFormat(std::ostream &out, bool &positionalMode, bool &spacePadPositive, int &ntrunc, const char *fmtStart, const detail::FormatArg *args, int &argIndex, int numArgs) { TINYFORMAT_ASSERT(*fmtStart == '%'); // Reset stream state to defaults. out.width(0); out.precision(6); out.fill(' '); // Reset most flags; ignore irrelevant unitbuf & skipws. out.unsetf(std::ios::adjustfield | std::ios::basefield | std::ios::floatfield | std::ios::showbase | std::ios::boolalpha | std::ios::showpoint | std::ios::showpos | std::ios::uppercase); bool precisionSet = false; bool widthSet = false; int widthExtra = 0; const char *c = fmtStart + 1; // 1) Parse an argument index (if followed by '$') or a width possibly // preceded with '0' flag. if (*c >= '0' && *c <= '9') { const char tmpc = *c; int value = parseIntAndAdvance(c); if (*c == '$') { // value is an argument index if (value > 0 && value <= numArgs) argIndex = value - 1; else TINYFORMAT_ERROR( "tinyformat: Positional argument out of range"); ++c; positionalMode = true; } else if (positionalMode) { TINYFORMAT_ERROR("tinyformat: Non-positional argument used " "after a positional one"); } else { if (tmpc == '0') { // Use internal padding so that numeric values are // formatted correctly, eg -00010 rather than 000-10 out.fill('0'); out.setf(std::ios::internal, std::ios::adjustfield); } if (value != 0) { // Nonzero value means that we parsed width. widthSet = true; out.width(value); } } } else if (positionalMode) { TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a " "positional one"); } // 2) Parse flags and width if we did not do it in previous step. if (!widthSet) { // Parse flags for (;; ++c) { switch (*c) { case '#': out.setf(std::ios::showpoint | std::ios::showbase); continue; case '0': // overridden by left alignment ('-' flag) if (!(out.flags() & std::ios::left)) { // Use internal padding so that numeric values are // formatted correctly, eg -00010 rather than 000-10 out.fill('0'); out.setf(std::ios::internal, std::ios::adjustfield); } continue; case '-': out.fill(' '); out.setf(std::ios::left, std::ios::adjustfield); continue; case ' ': // overridden by show positive sign, '+' flag. if (!(out.flags() & std::ios::showpos)) spacePadPositive = true; continue; case '+': out.setf(std::ios::showpos); spacePadPositive = false; widthExtra = 1; continue; default: break; } break; } // Parse width int width = 0; widthSet = parseWidthOrPrecision(width, c, positionalMode, args, argIndex, numArgs); if (widthSet) { if (width < 0) { // negative widths correspond to '-' flag set out.fill(' '); out.setf(std::ios::left, std::ios::adjustfield); width = -width; } out.width(width); } } // 3) Parse precision if (*c == '.') { ++c; int precision = 0; parseWidthOrPrecision(precision, c, positionalMode, args, argIndex, numArgs); // Presence of `.` indicates precision set, unless the inferred // value was negative in which case the default is used. precisionSet = precision >= 0; if (precisionSet) out.precision(precision); } // 4) Ignore any C99 length modifier while (*c == 'l' || *c == 'h' || *c == 'L' || *c == 'j' || *c == 'z' || *c == 't') { ++c; } // 5) We're up to the conversion specifier character. // Set stream flags based on conversion specifier (thanks to the // boost::format class for forging the way here). bool intConversion = false; switch (*c) { case 'u': case 'd': case 'i': out.setf(std::ios::dec, std::ios::basefield); intConversion = true; break; case 'o': out.setf(std::ios::oct, std::ios::basefield); intConversion = true; break; case 'X': out.setf(std::ios::uppercase); // Falls through case 'x': case 'p': out.setf(std::ios::hex, std::ios::basefield); intConversion = true; break; case 'E': out.setf(std::ios::uppercase); // Falls through case 'e': out.setf(std::ios::scientific, std::ios::floatfield); out.setf(std::ios::dec, std::ios::basefield); break; case 'F': out.setf(std::ios::uppercase); // Falls through case 'f': out.setf(std::ios::fixed, std::ios::floatfield); break; case 'A': out.setf(std::ios::uppercase); // Falls through case 'a': #ifdef _MSC_VER // Workaround // https://developercommunity.visualstudio.com/content/problem/520472/hexfloat-stream-output-does-not-ignore-precision-a.html // by always setting maximum precision on MSVC to avoid // precision loss for doubles. out.precision(13); #endif out.setf(std::ios::fixed | std::ios::scientific, std::ios::floatfield); break; case 'G': out.setf(std::ios::uppercase); // Falls through case 'g': out.setf(std::ios::dec, std::ios::basefield); // As in boost::format, let stream decide float format. out.flags(out.flags() & ~std::ios::floatfield); break; case 'c': // Handled as special case inside formatValue() break; case 's': if (precisionSet) ntrunc = static_cast<int>(out.precision()); // Make %s print Booleans as "true" and "false" out.setf(std::ios::boolalpha); break; case 'n': // Not supported - will cause problems! TINYFORMAT_ERROR( "tinyformat: %n conversion spec not supported"); break; case '\0': TINYFORMAT_ERROR("tinyformat: Conversion spec incorrectly " "terminated by end of string"); return c; default: break; } if (intConversion && precisionSet && !widthSet) { // "precision" for integers gives the minimum number of digits (to // be padded with zeros on the left). This isn't really supported by // the iostreams, but we can approximately simulate it with the // width if the width isn't otherwise used. out.width(out.precision() + widthExtra); out.setf(std::ios::internal, std::ios::adjustfield); out.fill('0'); } return c + 1; } //------------------------------------------------------------------------------ inline void formatImpl(std::ostream &out, const char *fmt, const detail::FormatArg *args, int numArgs) { // Saved stream state std::streamsize origWidth = out.width(); std::streamsize origPrecision = out.precision(); std::ios::fmtflags origFlags = out.flags(); char origFill = out.fill(); // "Positional mode" means all format specs should be of the form // "%n$..." with `n` an integer. We detect this in // `streamStateFromFormat`. bool positionalMode = false; int argIndex = 0; while (true) { fmt = printFormatStringLiteral(out, fmt); if (*fmt == '\0') { if (!positionalMode && argIndex < numArgs) { TINYFORMAT_ERROR("tinyformat: Not enough conversion " "specifiers in format string"); } break; } bool spacePadPositive = false; int ntrunc = -1; const char *fmtEnd = streamStateFromFormat(out, positionalMode, spacePadPositive, ntrunc, fmt, args, argIndex, numArgs); // NB: argIndex may be incremented by reading variable // width/precision in `streamStateFromFormat`, so do the bounds // check here. if (argIndex >= numArgs) { TINYFORMAT_ERROR("tinyformat: Too many conversion specifiers " "in format string"); return; } const FormatArg &arg = args[argIndex]; // Format the arg into the stream. if (!spacePadPositive) { arg.format(out, fmt, fmtEnd, ntrunc); } else { // The following is a special case with no direct correspondence // between stream formatting and the printf() behaviour. // Simulate it crudely by formatting into a temporary string // stream and munging the resulting string. std::ostringstream tmpStream; tmpStream.copyfmt(out); tmpStream.setf(std::ios::showpos); arg.format(tmpStream, fmt, fmtEnd, ntrunc); std::string result = tmpStream.str(); // allocates... yuck. for (size_t i = 0, iend = result.size(); i < iend; ++i) { if (result[i] == '+') result[i] = ' '; } out << result; } if (!positionalMode) ++argIndex; fmt = fmtEnd; } // Restore stream state out.width(origWidth); out.precision(origPrecision); out.flags(origFlags); out.fill(origFill); } } // namespace detail /// List of template arguments format(), held in a type-opaque way. /// /// A const reference to FormatList (typedef'd as FormatListRef) may be /// conveniently used to pass arguments to non-template functions: All type /// information has been stripped from the arguments, leaving just enough of a /// common interface to perform formatting as required. class FormatList { public: FormatList(detail::FormatArg *args, int N) : m_args(args), m_N(N) {} friend void vformat(std::ostream &out, const char *fmt, const FormatList &list); private: const detail::FormatArg *m_args; int m_N; }; /// Reference to type-opaque format list for passing to vformat() typedef const FormatList &FormatListRef; namespace detail { // Format list subclass with fixed storage to avoid dynamic allocation template <int N> class FormatListN : public FormatList { public: #ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES template <typename... Args> - FormatListN(const Args &... args) + FormatListN(const Args &...args) : FormatList(&m_formatterStore[0], N), m_formatterStore{ FormatArg(args)...} { static_assert(sizeof...(args) == N, "Number of args must be N"); } #else // C++98 version void init(int) {} #define TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR(n) \ \ template <TINYFORMAT_ARGTYPES(n)> \ FormatListN(TINYFORMAT_VARARGS(n)) : FormatList(&m_formatterStore[0], n) { \ TINYFORMAT_ASSERT(n == N); \ init(0, TINYFORMAT_PASSARGS(n)); \ } \ \ template <TINYFORMAT_ARGTYPES(n)> \ void init(int i, TINYFORMAT_VARARGS(n)) { \ m_formatterStore[i] = FormatArg(v1); \ init(i + 1 TINYFORMAT_PASSARGS_TAIL(n)); \ } TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR) #undef TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR #endif FormatListN(const FormatListN &other) : FormatList(&m_formatterStore[0], N) { std::copy(&other.m_formatterStore[0], &other.m_formatterStore[N], &m_formatterStore[0]); } private: FormatArg m_formatterStore[N]; }; // Special 0-arg version - MSVC says zero-sized C array in struct is // nonstandard. template <> class FormatListN<0> : public FormatList { public: FormatListN() : FormatList(0, 0) {} }; } // namespace detail //------------------------------------------------------------------------------ // Primary API functions #ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES /// Make type-agnostic format list from list of template arguments. /// /// The exact return type of this function is an implementation detail and /// shouldn't be relied upon. Instead it should be stored as a FormatListRef: /// /// FormatListRef formatList = makeFormatList( /*...*/ ); template <typename... Args> -detail::FormatListN<sizeof...(Args)> makeFormatList(const Args &... args) { +detail::FormatListN<sizeof...(Args)> makeFormatList(const Args &...args) { return detail::FormatListN<sizeof...(args)>(args...); } #else // C++98 version inline detail::FormatListN<0> makeFormatList() { return detail::FormatListN<0>(); } #define TINYFORMAT_MAKE_MAKEFORMATLIST(n) \ template <TINYFORMAT_ARGTYPES(n)> \ detail::FormatListN<n> makeFormatList(TINYFORMAT_VARARGS(n)) { \ return detail::FormatListN<n>(TINYFORMAT_PASSARGS(n)); \ } TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_MAKEFORMATLIST) #undef TINYFORMAT_MAKE_MAKEFORMATLIST #endif /// Format list of arguments to the stream according to the given format string. /// /// The name vformat() is chosen for the semantic similarity to vprintf(): the /// list of format arguments is held in a single function argument. inline void vformat(std::ostream &out, const char *fmt, FormatListRef list) { detail::formatImpl(out, fmt, list.m_args, list.m_N); } #ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES /// Format list of arguments to the stream according to given format string. template <typename... Args> -void format(std::ostream &out, const char *fmt, const Args &... args) { +void format(std::ostream &out, const char *fmt, const Args &...args) { vformat(out, fmt, makeFormatList(args...)); } /// Format list of arguments according to the given format string and return the /// result as a string. template <typename... Args> -std::string format(const char *fmt, const Args &... args) { +std::string format(const char *fmt, const Args &...args) { std::ostringstream oss; format(oss, fmt, args...); return oss.str(); } /// Format list of arguments to std::cout, according to the given format string -template <typename... Args> void printf(const char *fmt, const Args &... args) { +template <typename... Args> void printf(const char *fmt, const Args &...args) { format(std::cout, fmt, args...); } template <typename... Args> -void printfln(const char *fmt, const Args &... args) { +void printfln(const char *fmt, const Args &...args) { format(std::cout, fmt, args...); std::cout << '\n'; } #else // C++98 version inline void format(std::ostream &out, const char *fmt) { vformat(out, fmt, makeFormatList()); } inline std::string format(const char *fmt) { std::ostringstream oss; format(oss, fmt); return oss.str(); } inline void printf(const char *fmt) { format(std::cout, fmt); } inline void printfln(const char *fmt) { format(std::cout, fmt); std::cout << '\n'; } #define TINYFORMAT_MAKE_FORMAT_FUNCS(n) \ \ template <TINYFORMAT_ARGTYPES(n)> \ void format(std::ostream &out, const char *fmt, TINYFORMAT_VARARGS(n)) { \ vformat(out, fmt, makeFormatList(TINYFORMAT_PASSARGS(n))); \ } \ \ template <TINYFORMAT_ARGTYPES(n)> \ std::string format(const char *fmt, TINYFORMAT_VARARGS(n)) { \ std::ostringstream oss; \ format(oss, fmt, TINYFORMAT_PASSARGS(n)); \ return oss.str(); \ } \ \ template <TINYFORMAT_ARGTYPES(n)> \ void printf(const char *fmt, TINYFORMAT_VARARGS(n)) { \ format(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \ } \ \ template <TINYFORMAT_ARGTYPES(n)> \ void printfln(const char *fmt, TINYFORMAT_VARARGS(n)) { \ format(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \ std::cout << '\n'; \ } TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_FORMAT_FUNCS) #undef TINYFORMAT_MAKE_FORMAT_FUNCS #endif // Added for Bitcoin template <typename... Args> -std::string format(const std::string &fmt, const Args &... args) { +std::string format(const std::string &fmt, const Args &...args) { std::ostringstream oss; format(oss, fmt.c_str(), args...); return oss.str(); } } // namespace tinyformat // Added for Bitcoin: /** * Format arguments and return the string or write to given std::ostream (see * tinyformat::format doc for details) */ #define strprintf tfm::format #endif // TINYFORMAT_H_INCLUDED diff --git a/src/util/system.cpp b/src/util/system.cpp index df23a77f0..45a7a6d8a 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1,1420 +1,1420 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/system.h> #include <chainparamsbase.h> #include <sync.h> #include <util/strencodings.h> #include <util/string.h> #include <util/translation.h> #include <univalue.h> #include <memory> #include <thread> #include <typeinfo> #if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) #include <pthread.h> #include <pthread_np.h> #endif #ifndef WIN32 // for posix_fallocate, in config/CMakeLists.txt we check if it is present after // this #ifdef __linux__ #ifdef _POSIX_C_SOURCE #undef _POSIX_C_SOURCE #endif #define _POSIX_C_SOURCE 200112L #endif // __linux__ #include <algorithm> #include <cassert> #include <fcntl.h> #include <sched.h> #include <sys/resource.h> #include <sys/stat.h> #else #ifdef _MSC_VER #pragma warning(disable : 4786) #pragma warning(disable : 4804) #pragma warning(disable : 4805) #pragma warning(disable : 4717) #endif #ifndef NOMINMAX #define NOMINMAX #endif #include <codecvt> #include <io.h> /* for _commit */ #include <shellapi.h> #include <shlobj.h> #endif #ifdef HAVE_MALLOPT_ARENA_MAX #include <malloc.h> #endif #include <boost/algorithm/string/replace.hpp> // Application startup time (used for uptime calculation) const int64_t nStartupTime = GetTime(); const char *const BITCOIN_CONF_FILENAME = "bitcoin.conf"; const char *const BITCOIN_SETTINGS_FILENAME = "settings.json"; ArgsManager gArgs; /** Mutex to protect dir_locks. */ static Mutex cs_dir_locks; /** * A map that contains all the currently held directory locks. After successful * locking, these will be held here until the global destructor cleans them up * and thus automatically unlocks them, or ReleaseDirectoryLocks is called. */ static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks GUARDED_BY(cs_dir_locks); bool LockDirectory(const fs::path &directory, const std::string lockfile_name, bool probe_only) { LOCK(cs_dir_locks); fs::path pathLockFile = directory / lockfile_name; // If a lock for this directory already exists in the map, don't try to // re-lock it if (dir_locks.count(fs::PathToString(pathLockFile))) { return true; } // Create empty lock file if it doesn't exist. FILE *file = fsbridge::fopen(pathLockFile, "a"); if (file) { fclose(file); } auto lock = std::make_unique<fsbridge::FileLock>(pathLockFile); if (!lock->TryLock()) { return error("Error while attempting to lock directory %s: %s", fs::PathToString(directory), lock->GetReason()); } if (!probe_only) { // Lock successful and we're not just probing, put it into the map dir_locks.emplace(fs::PathToString(pathLockFile), std::move(lock)); } return true; } void UnlockDirectory(const fs::path &directory, const std::string &lockfile_name) { LOCK(cs_dir_locks); dir_locks.erase(fs::PathToString(directory / lockfile_name)); } void ReleaseDirectoryLocks() { LOCK(cs_dir_locks); dir_locks.clear(); } bool DirIsWritable(const fs::path &directory) { fs::path tmpFile = directory / fs::unique_path(); FILE *file = fsbridge::fopen(tmpFile, "a"); if (!file) { return false; } fclose(file); remove(tmpFile); return true; } bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes) { // 50 MiB constexpr uint64_t min_disk_space = 52428800; uint64_t free_bytes_available = fs::space(dir).available; return free_bytes_available >= min_disk_space + additional_bytes; } std::streampos GetFileSize(const char *path, std::streamsize max) { std::ifstream file(path, std::ios::binary); file.ignore(max); return file.gcount(); } /** * Interpret a string argument as a boolean. * * The definition of atoi() requires that non-numeric string values like "foo", * return 0. This means that if a user unintentionally supplies a non-integer * argument here, the return value is always false. This means that -foo=false * does what the user probably expects, but -foo=true is well defined but does * not do what they probably expected. * * The return value of atoi() is undefined when given input not representable as * an int. On most systems this means string value between "-2147483648" and * "2147483647" are well defined (this method will return true). Setting * -txindex=2147483648 on most systems, however, is probably undefined. * * For a more extensive discussion of this topic (and a wide range of opinions * on the Right Way to change this code), see PR12713. */ static bool InterpretBool(const std::string &strValue) { if (strValue.empty()) { return true; } return (atoi(strValue) != 0); } static std::string SettingName(const std::string &arg) { return arg.size() > 0 && arg[0] == '-' ? arg.substr(1) : arg; } /** * Interpret -nofoo as if the user supplied -foo=0. * * This method also tracks when the -no form was supplied, and if so, checks * whether there was a double-negative (-nofoo=0 -> -foo=1). * * If there was not a double negative, it removes the "no" from the key * and returns false. * * If there was a double negative, it removes "no" from the key, and * returns true. * * If there was no "no", it returns the string value untouched. * * Where an option was negated can be later checked using the IsArgNegated() * method. One use case for this is to have a way to disable options that are * not normally boolean (e.g. using -nodebuglogfile to request that debug log * output is not sent to any file at all). */ static util::SettingsValue InterpretOption(std::string §ion, std::string &key, const std::string &value) { // Split section name from key name for keys like "testnet.foo" or // "regtest.bar" size_t option_index = key.find('.'); if (option_index != std::string::npos) { section = key.substr(0, option_index); key.erase(0, option_index + 1); } if (key.substr(0, 2) == "no") { key.erase(0, 2); // Double negatives like -nofoo=0 are supported (but discouraged) if (!InterpretBool(value)) { LogPrintf("Warning: parsed potentially confusing double-negative " "-%s=%s\n", key, value); return true; } return false; } return value; } /** * Check settings value validity according to flags. * * TODO: Add more meaningful error checks here in the future * See "here's how the flags are meant to behave" in * https://github.com/bitcoin/bitcoin/pull/16097#issuecomment-514627823 */ static bool CheckValid(const std::string &key, const util::SettingsValue &val, unsigned int flags, std::string &error) { if (val.isBool() && !(flags & ArgsManager::ALLOW_BOOL)) { error = strprintf( "Negating of -%s is meaningless and therefore forbidden", key); return false; } return true; } namespace { fs::path StripRedundantLastElementsOfPath(const fs::path &path) { auto result = path; while (fs::PathToString(result.filename()) == ".") { result = result.parent_path(); } assert(fs::equivalent(result, path)); return result; } } // namespace // Define default constructor and destructor that are not inline, so code // instantiating this class doesn't need to #include class definitions for all // members. For example, m_settings has an internal dependency on univalue. ArgsManager::ArgsManager() {} ArgsManager::~ArgsManager() {} const std::set<std::string> ArgsManager::GetUnsuitableSectionOnlyArgs() const { std::set<std::string> unsuitables; LOCK(cs_args); // if there's no section selected, don't worry if (m_network.empty()) { return std::set<std::string>{}; } // if it's okay to use the default section for this network, don't worry if (m_network == CBaseChainParams::MAIN) { return std::set<std::string>{}; } for (const auto &arg : m_network_only_args) { if (OnlyHasDefaultSectionSetting(m_settings, m_network, SettingName(arg))) { unsuitables.insert(arg); } } return unsuitables; } const std::list<SectionInfo> ArgsManager::GetUnrecognizedSections() const { // Section names to be recognized in the config file. static const std::set<std::string> available_sections{ CBaseChainParams::REGTEST, CBaseChainParams::TESTNET, CBaseChainParams::MAIN}; LOCK(cs_args); std::list<SectionInfo> unrecognized = m_config_sections; unrecognized.remove_if([](const SectionInfo &appeared) { return available_sections.find(appeared.m_name) != available_sections.end(); }); return unrecognized; } void ArgsManager::SelectConfigNetwork(const std::string &network) { LOCK(cs_args); m_network = network; } bool ParseKeyValue(std::string &key, std::string &val) { size_t is_index = key.find('='); if (is_index != std::string::npos) { val = key.substr(is_index + 1); key.erase(is_index); } #ifdef WIN32 key = ToLower(key); if (key[0] == '/') { key[0] = '-'; } #endif if (key[0] != '-') { return false; } // Transform --foo to -foo if (key.length() > 1 && key[1] == '-') { key.erase(0, 1); } return true; } bool ArgsManager::ParseParameters(int argc, const char *const argv[], std::string &error) { LOCK(cs_args); m_settings.command_line_options.clear(); for (int i = 1; i < argc; i++) { std::string key(argv[i]); #ifdef MAC_OSX // At the first time when a user gets the "App downloaded from the // internet" warning, and clicks the Open button, macOS passes // a unique process serial number (PSN) as -psn_... command-line // argument, which we filter out. if (key.substr(0, 5) == "-psn_") { continue; } #endif if (key == "-") { // bitcoin-tx using stdin break; } std::string val; if (!ParseKeyValue(key, val)) { break; } // Transform -foo to foo key.erase(0, 1); std::string section; util::SettingsValue value = InterpretOption(section, key, val); std::optional<unsigned int> flags = GetArgFlags('-' + key); // Unknown command line options and command line options with dot // characters (which are returned from InterpretOption with nonempty // section strings) are not valid. if (!flags || !section.empty()) { error = strprintf("Invalid parameter %s", argv[i]); return false; } if (!CheckValid(key, value, *flags, error)) { return false; } m_settings.command_line_options[key].push_back(value); } // we do not allow -includeconf from command line bool success = true; if (auto *includes = util::FindKey(m_settings.command_line_options, "includeconf")) { for (const auto &include : util::SettingsSpan(*includes)) { error += "-includeconf cannot be used from commandline; -includeconf=" + include.get_str() + "\n"; success = false; } } return success; } std::optional<unsigned int> ArgsManager::GetArgFlags(const std::string &name) const { LOCK(cs_args); for (const auto &arg_map : m_available_args) { const auto search = arg_map.second.find(name); if (search != arg_map.second.end()) { return search->second.m_flags; } } return std::nullopt; } const fs::path &ArgsManager::GetBlocksDirPath() { LOCK(cs_args); fs::path &path = m_cached_blocks_path; // Cache the path to avoid calling fs::create_directories on every call of // this function if (!path.empty()) { return path; } if (IsArgSet("-blocksdir")) { path = fs::system_complete(fs::PathFromString(GetArg("-blocksdir", ""))); if (!fs::is_directory(path)) { path = ""; return path; } } else { path = GetDataDirPath(false); } path /= fs::PathFromString(BaseParams().DataDir()); path /= "blocks"; fs::create_directories(path); path = StripRedundantLastElementsOfPath(path); return path; } const fs::path &ArgsManager::GetDataDirPath(bool net_specific) const { LOCK(cs_args); fs::path &path = net_specific ? m_cached_network_datadir_path : m_cached_datadir_path; // Cache the path to avoid calling fs::create_directories on every call of // this function if (!path.empty()) { return path; } std::string datadir = GetArg("-datadir", ""); if (!datadir.empty()) { path = fs::system_complete(fs::PathFromString(datadir)); if (!fs::is_directory(path)) { path = ""; return path; } } else { path = GetDefaultDataDir(); } if (net_specific) { path /= fs::PathFromString(BaseParams().DataDir()); } if (fs::create_directories(path)) { // This is the first run, create wallets subdirectory too fs::create_directories(path / "wallets"); } path = StripRedundantLastElementsOfPath(path); return path; } void ArgsManager::ClearPathCache() { LOCK(cs_args); m_cached_datadir_path = fs::path(); m_cached_network_datadir_path = fs::path(); m_cached_blocks_path = fs::path(); } std::vector<std::string> ArgsManager::GetArgs(const std::string &strArg) const { std::vector<std::string> result; for (const util::SettingsValue &value : GetSettingsList(strArg)) { - result.push_back( - value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str()); + result.push_back(value.isFalse() ? "0" + : value.isTrue() ? "1" + : value.get_str()); } return result; } bool ArgsManager::IsArgSet(const std::string &strArg) const { return !GetSetting(strArg).isNull(); } bool ArgsManager::InitSettings(std::string &error) { if (!GetSettingsPath()) { return true; // Do nothing if settings file disabled. } std::vector<std::string> errors; if (!ReadSettingsFile(&errors)) { error = strprintf("Failed loading settings file:\n- %s\n", Join(errors, "\n- ")); return false; } if (!WriteSettingsFile(&errors)) { error = strprintf("Failed saving settings file:\n- %s\n", Join(errors, "\n- ")); return false; } return true; } bool ArgsManager::GetSettingsPath(fs::path *filepath, bool temp) const { if (IsArgNegated("-settings")) { return false; } if (filepath) { std::string settings = GetArg("-settings", BITCOIN_SETTINGS_FILENAME); *filepath = fs::absolute( fs::PathFromString(temp ? settings + ".tmp" : settings), GetDataDirPath(/* net_specific= */ true)); } return true; } static void SaveErrors(const std::vector<std::string> errors, std::vector<std::string> *error_out) { for (const auto &error : errors) { if (error_out) { error_out->emplace_back(error); } else { LogPrintf("%s\n", error); } } } bool ArgsManager::ReadSettingsFile(std::vector<std::string> *errors) { fs::path path; if (!GetSettingsPath(&path, /* temp= */ false)) { return true; // Do nothing if settings file disabled. } LOCK(cs_args); m_settings.rw_settings.clear(); std::vector<std::string> read_errors; if (!util::ReadSettings(path, m_settings.rw_settings, read_errors)) { SaveErrors(read_errors, errors); return false; } for (const auto &setting : m_settings.rw_settings) { std::string section; std::string key = setting.first; // Split setting key into section and argname (void)InterpretOption(section, key, /* value */ {}); if (!GetArgFlags('-' + key)) { LogPrintf("Ignoring unknown rw_settings value %s\n", setting.first); } } return true; } bool ArgsManager::WriteSettingsFile(std::vector<std::string> *errors) const { fs::path path, path_tmp; if (!GetSettingsPath(&path, /* temp= */ false) || !GetSettingsPath(&path_tmp, /* temp= */ true)) { throw std::logic_error("Attempt to write settings file when dynamic " "settings are disabled."); } LOCK(cs_args); std::vector<std::string> write_errors; if (!util::WriteSettings(path_tmp, m_settings.rw_settings, write_errors)) { SaveErrors(write_errors, errors); return false; } if (!RenameOver(path_tmp, path)) { SaveErrors( {strprintf("Failed renaming settings file %s to %s\n", fs::PathToString(path_tmp), fs::PathToString(path))}, errors); return false; } return true; } bool ArgsManager::IsArgNegated(const std::string &strArg) const { return GetSetting(strArg).isFalse(); } std::string ArgsManager::GetArg(const std::string &strArg, const std::string &strDefault) const { const util::SettingsValue value = GetSetting(strArg); - return value.isNull() - ? strDefault - : value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str(); + return value.isNull() ? strDefault + : value.isFalse() ? "0" + : value.isTrue() ? "1" + : value.get_str(); } int64_t ArgsManager::GetArg(const std::string &strArg, int64_t nDefault) const { const util::SettingsValue value = GetSetting(strArg); - return value.isNull() - ? nDefault - : value.isFalse() - ? 0 - : value.isTrue() ? 1 - : value.isNum() ? value.get_int64() - : atoi64(value.get_str()); + return value.isNull() ? nDefault + : value.isFalse() ? 0 + : value.isTrue() ? 1 + : value.isNum() ? value.get_int64() + : atoi64(value.get_str()); } bool ArgsManager::GetBoolArg(const std::string &strArg, bool fDefault) const { const util::SettingsValue value = GetSetting(strArg); - return value.isNull() ? fDefault - : value.isBool() ? value.get_bool() - : InterpretBool(value.get_str()); + return value.isNull() ? fDefault + : value.isBool() ? value.get_bool() + : InterpretBool(value.get_str()); } bool ArgsManager::SoftSetArg(const std::string &strArg, const std::string &strValue) { LOCK(cs_args); if (IsArgSet(strArg)) { return false; } ForceSetArg(strArg, strValue); return true; } bool ArgsManager::SoftSetBoolArg(const std::string &strArg, bool fValue) { if (fValue) { return SoftSetArg(strArg, std::string("1")); } else { return SoftSetArg(strArg, std::string("0")); } } void ArgsManager::ForceSetArg(const std::string &strArg, const std::string &strValue) { LOCK(cs_args); m_settings.forced_settings[SettingName(strArg)] = strValue; } /** * This function is only used for testing purpose so * so we should not worry about element uniqueness and * integrity of mapMultiArgs data structure */ void ArgsManager::ForceSetMultiArg(const std::string &strArg, const std::vector<std::string> &values) { LOCK(cs_args); util::SettingsValue value; value.setArray(); for (const std::string &s : values) { value.push_back(s); } m_settings.forced_settings[SettingName(strArg)] = value; } void ArgsManager::AddArg(const std::string &name, const std::string &help, unsigned int flags, const OptionsCategory &cat) { // Split arg name from its help param size_t eq_index = name.find('='); if (eq_index == std::string::npos) { eq_index = name.size(); } std::string arg_name = name.substr(0, eq_index); LOCK(cs_args); std::map<std::string, Arg> &arg_map = m_available_args[cat]; auto ret = arg_map.emplace( arg_name, Arg{name.substr(eq_index, name.size() - eq_index), help, flags}); // Make sure an insertion actually happened. assert(ret.second); if (flags & ArgsManager::NETWORK_ONLY) { m_network_only_args.emplace(arg_name); } } void ArgsManager::AddHiddenArgs(const std::vector<std::string> &names) { for (const std::string &name : names) { AddArg(name, "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN); } } void ArgsManager::ClearForcedArg(const std::string &strArg) { LOCK(cs_args); m_settings.forced_settings.erase(SettingName(strArg)); } std::string ArgsManager::GetHelpMessage() const { const bool show_debug = GetBoolArg("-help-debug", false); std::string usage = ""; LOCK(cs_args); for (const auto &arg_map : m_available_args) { switch (arg_map.first) { case OptionsCategory::OPTIONS: usage += HelpMessageGroup("Options:"); break; case OptionsCategory::CONNECTION: usage += HelpMessageGroup("Connection options:"); break; case OptionsCategory::ZMQ: usage += HelpMessageGroup("ZeroMQ notification options:"); break; case OptionsCategory::DEBUG_TEST: usage += HelpMessageGroup("Debugging/Testing options:"); break; case OptionsCategory::NODE_RELAY: usage += HelpMessageGroup("Node relay options:"); break; case OptionsCategory::BLOCK_CREATION: usage += HelpMessageGroup("Block creation options:"); break; case OptionsCategory::RPC: usage += HelpMessageGroup("RPC server options:"); break; case OptionsCategory::WALLET: usage += HelpMessageGroup("Wallet options:"); break; case OptionsCategory::WALLET_DEBUG_TEST: if (show_debug) { usage += HelpMessageGroup("Wallet debugging/testing options:"); } break; case OptionsCategory::CHAINPARAMS: usage += HelpMessageGroup("Chain selection options:"); break; case OptionsCategory::GUI: usage += HelpMessageGroup("UI Options:"); break; case OptionsCategory::COMMANDS: usage += HelpMessageGroup("Commands:"); break; case OptionsCategory::REGISTER_COMMANDS: usage += HelpMessageGroup("Register Commands:"); break; default: break; } // When we get to the hidden options, stop if (arg_map.first == OptionsCategory::HIDDEN) { break; } for (const auto &arg : arg_map.second) { if (show_debug || !(arg.second.m_flags & ArgsManager::DEBUG_ONLY)) { std::string name; if (arg.second.m_help_param.empty()) { name = arg.first; } else { name = arg.first + arg.second.m_help_param; } usage += HelpMessageOpt(name, arg.second.m_help_text); } } } return usage; } bool HelpRequested(const ArgsManager &args) { return args.IsArgSet("-?") || args.IsArgSet("-h") || args.IsArgSet("-help") || args.IsArgSet("-help-debug"); } void SetupHelpOptions(ArgsManager &args) { args.AddArg("-?", "Print this help message and exit", false, OptionsCategory::OPTIONS); args.AddHiddenArgs({"-h", "-help"}); } static const int screenWidth = 79; static const int optIndent = 2; static const int msgIndent = 7; std::string HelpMessageGroup(const std::string &message) { return std::string(message) + std::string("\n\n"); } std::string HelpMessageOpt(const std::string &option, const std::string &message) { return std::string(optIndent, ' ') + std::string(option) + std::string("\n") + std::string(msgIndent, ' ') + FormatParagraph(message, screenWidth - msgIndent, msgIndent) + std::string("\n\n"); } static std::string FormatException(const std::exception *pex, const char *pszThread) { #ifdef WIN32 char pszModule[MAX_PATH] = ""; GetModuleFileNameA(nullptr, pszModule, sizeof(pszModule)); #else const char *pszModule = "bitcoin"; #endif if (pex) { return strprintf("EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread); } else { return strprintf("UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread); } } void PrintExceptionContinue(const std::exception *pex, const char *pszThread) { std::string message = FormatException(pex, pszThread); LogPrintf("\n\n************************\n%s\n", message); tfm::format(std::cerr, "\n\n************************\n%s\n", message); } fs::path GetDefaultDataDir() { // Windows: C:\Users\Username\AppData\Roaming\Bitcoin // macOS: ~/Library/Application Support/Bitcoin // Unix-like: ~/.bitcoin #ifdef WIN32 // Windows return GetSpecialFolderPath(CSIDL_APPDATA) / "Bitcoin"; #else fs::path pathRet; char *pszHome = getenv("HOME"); if (pszHome == nullptr || strlen(pszHome) == 0) { pathRet = fs::path("/"); } else { pathRet = fs::path(pszHome); } #ifdef MAC_OSX // macOS return pathRet / "Library/Application Support/Bitcoin"; #else // Unix-like return pathRet / ".bitcoin"; #endif #endif } const fs::path &GetDataDir(bool fNetSpecific) { return gArgs.GetDataDirPath(fNetSpecific); } bool CheckDataDirOption() { std::string datadir = gArgs.GetArg("-datadir", ""); return datadir.empty() || fs::is_directory(fs::system_complete(fs::PathFromString(datadir))); } fs::path GetConfigFile(const std::string &confPath) { return AbsPathForConfigVal(fs::PathFromString(confPath), false); } static bool GetConfigOptions(std::istream &stream, const std::string &filepath, std::string &error, std::vector<std::pair<std::string, std::string>> &options, std::list<SectionInfo> §ions) { std::string str, prefix; std::string::size_type pos; int linenr = 1; while (std::getline(stream, str)) { bool used_hash = false; if ((pos = str.find('#')) != std::string::npos) { str = str.substr(0, pos); used_hash = true; } const static std::string pattern = " \t\r\n"; str = TrimString(str, pattern); if (!str.empty()) { if (*str.begin() == '[' && *str.rbegin() == ']') { const std::string section = str.substr(1, str.size() - 2); sections.emplace_back(SectionInfo{section, filepath, linenr}); prefix = section + '.'; } else if (*str.begin() == '-') { error = strprintf( "parse error on line %i: %s, options in configuration file " "must be specified without leading -", linenr, str); return false; } else if ((pos = str.find('=')) != std::string::npos) { std::string name = prefix + TrimString(str.substr(0, pos), pattern); std::string value = TrimString(str.substr(pos + 1), pattern); if (used_hash && name.find("rpcpassword") != std::string::npos) { error = strprintf( "parse error on line %i, using # in rpcpassword can be " "ambiguous and should be avoided", linenr); return false; } options.emplace_back(name, value); if ((pos = name.rfind('.')) != std::string::npos && prefix.length() <= pos) { sections.emplace_back( SectionInfo{name.substr(0, pos), filepath, linenr}); } } else { error = strprintf("parse error on line %i: %s", linenr, str); if (str.size() >= 2 && str.substr(0, 2) == "no") { error += strprintf(", if you intended to specify a negated " "option, use %s=1 instead", str); } return false; } } ++linenr; } return true; } bool ArgsManager::ReadConfigStream(std::istream &stream, const std::string &filepath, std::string &error, bool ignore_invalid_keys) { LOCK(cs_args); std::vector<std::pair<std::string, std::string>> options; if (!GetConfigOptions(stream, filepath, error, options, m_config_sections)) { return false; } for (const std::pair<std::string, std::string> &option : options) { std::string section; std::string key = option.first; util::SettingsValue value = InterpretOption(section, key, option.second); std::optional<unsigned int> flags = GetArgFlags('-' + key); if (flags) { if (!CheckValid(key, value, *flags, error)) { return false; } m_settings.ro_config[section][key].push_back(value); } else { if (ignore_invalid_keys) { LogPrintf("Ignoring unknown configuration value %s\n", option.first); } else { error = strprintf("Invalid configuration value %s", option.first.c_str()); return false; } } } return true; } bool ArgsManager::ReadConfigFiles(std::string &error, bool ignore_invalid_keys) { { LOCK(cs_args); m_settings.ro_config.clear(); m_config_sections.clear(); } const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME); fsbridge::ifstream stream(GetConfigFile(confPath)); // ok to not have a config file if (stream.good()) { if (!ReadConfigStream(stream, confPath, error, ignore_invalid_keys)) { return false; } // `-includeconf` cannot be included in the command line arguments // except as `-noincludeconf` (which indicates that no included conf // file should be used). bool use_conf_file{true}; { LOCK(cs_args); if (auto *includes = util::FindKey(m_settings.command_line_options, "includeconf")) { // ParseParameters() fails if a non-negated -includeconf is // passed on the command-line assert(util::SettingsSpan(*includes).last_negated()); use_conf_file = false; } } if (use_conf_file) { std::string chain_id = GetChainName(); std::vector<std::string> conf_file_names; auto add_includes = [&](const std::string &network, size_t skip = 0) { size_t num_values = 0; LOCK(cs_args); if (auto *section = util::FindKey(m_settings.ro_config, network)) { if (auto *values = util::FindKey(*section, "includeconf")) { for (size_t i = std::max( skip, util::SettingsSpan(*values).negated()); i < values->size(); ++i) { conf_file_names.push_back((*values)[i].get_str()); } num_values = values->size(); } } return num_values; }; // We haven't set m_network yet (that happens in SelectParams()), so // manually check for network.includeconf args. const size_t chain_includes = add_includes(chain_id); const size_t default_includes = add_includes({}); for (const std::string &conf_file_name : conf_file_names) { fsbridge::ifstream conf_file_stream( GetConfigFile(conf_file_name)); if (conf_file_stream.good()) { if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) { return false; } LogPrintf("Included configuration file %s\n", conf_file_name); } else { error = "Failed to include configuration file " + conf_file_name; return false; } } // Warn about recursive -includeconf conf_file_names.clear(); add_includes(chain_id, /* skip= */ chain_includes); add_includes({}, /* skip= */ default_includes); std::string chain_id_final = GetChainName(); if (chain_id_final != chain_id) { // Also warn about recursive includeconf for the chain that was // specified in one of the includeconfs add_includes(chain_id_final); } for (const std::string &conf_file_name : conf_file_names) { tfm::format(std::cerr, "warning: -includeconf cannot be used from " "included files; ignoring -includeconf=%s\n", conf_file_name); } } } // If datadir is changed in .conf file: gArgs.ClearPathCache(); if (!CheckDataDirOption()) { error = strprintf("specified data directory \"%s\" does not exist.", GetArg("-datadir", "").c_str()); return false; } return true; } std::string ArgsManager::GetChainName() const { auto get_net = [&](const std::string &arg) { LOCK(cs_args); util::SettingsValue value = util::GetSetting(m_settings, /* section= */ "", SettingName(arg), /* ignore_default_section_config= */ false, /* get_chain_name= */ true); - return value.isNull() ? false - : value.isBool() ? value.get_bool() - : InterpretBool(value.get_str()); + return value.isNull() ? false + : value.isBool() ? value.get_bool() + : InterpretBool(value.get_str()); }; const bool fRegTest = get_net("-regtest"); const bool fTestNet = get_net("-testnet"); const bool is_chain_arg_set = IsArgSet("-chain"); if (int(is_chain_arg_set) + int(fRegTest) + int(fTestNet) > 1) { throw std::runtime_error("Invalid combination of -regtest, -testnet " "and -chain. Can use at most one."); } if (fRegTest) { return CBaseChainParams::REGTEST; } if (fTestNet) { return CBaseChainParams::TESTNET; } return GetArg("-chain", CBaseChainParams::MAIN); } bool ArgsManager::UseDefaultSection(const std::string &arg) const { return m_network == CBaseChainParams::MAIN || m_network_only_args.count(arg) == 0; } util::SettingsValue ArgsManager::GetSetting(const std::string &arg) const { LOCK(cs_args); return util::GetSetting(m_settings, m_network, SettingName(arg), !UseDefaultSection(arg), /* get_chain_name= */ false); } std::vector<util::SettingsValue> ArgsManager::GetSettingsList(const std::string &arg) const { LOCK(cs_args); return util::GetSettingsList(m_settings, m_network, SettingName(arg), !UseDefaultSection(arg)); } void ArgsManager::logArgsPrefix( const std::string &prefix, const std::string §ion, const std::map<std::string, std::vector<util::SettingsValue>> &args) const { std::string section_str = section.empty() ? "" : "[" + section + "] "; for (const auto &arg : args) { for (const auto &value : arg.second) { std::optional<unsigned int> flags = GetArgFlags('-' + arg.first); if (flags) { std::string value_str = (*flags & SENSITIVE) ? "****" : value.write(); LogPrintf("%s %s%s=%s\n", prefix, section_str, arg.first, value_str); } } } } void ArgsManager::LogArgs() const { LOCK(cs_args); for (const auto §ion : m_settings.ro_config) { logArgsPrefix("Config file arg:", section.first, section.second); } for (const auto &setting : m_settings.rw_settings) { LogPrintf("Setting file arg: %s = %s\n", setting.first, setting.second.write()); } logArgsPrefix("Command-line arg:", "", m_settings.command_line_options); } bool RenameOver(fs::path src, fs::path dest) { #ifdef WIN32 return MoveFileExW(src.wstring().c_str(), dest.wstring().c_str(), MOVEFILE_REPLACE_EXISTING) != 0; #else int rc = std::rename(src.c_str(), dest.c_str()); return (rc == 0); #endif /* WIN32 */ } /** * Ignores exceptions thrown by Boost's create_directories if the requested * directory exists. Specifically handles case where path p exists, but it * wasn't possible for the user to write to the parent directory. */ bool TryCreateDirectories(const fs::path &p) { try { return fs::create_directories(p); } catch (const fs::filesystem_error &) { if (!fs::exists(p) || !fs::is_directory(p)) { throw; } } // create_directory didn't create the directory, it had to have existed // already. return false; } bool FileCommit(FILE *file) { // harmless if redundantly called if (fflush(file) != 0) { LogPrintf("%s: fflush failed: %d\n", __func__, errno); return false; } #ifdef WIN32 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); if (FlushFileBuffers(hFile) == 0) { LogPrintf("%s: FlushFileBuffers failed: %d\n", __func__, GetLastError()); return false; } #else #if defined(HAVE_FDATASYNC) // Ignore EINVAL for filesystems that don't support sync if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { LogPrintf("%s: fdatasync failed: %d\n", __func__, errno); return false; } #elif defined(MAC_OSX) && defined(F_FULLFSYNC) // Manpage says "value other than -1" is returned on success if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) { LogPrintf("%s: fcntl F_FULLFSYNC failed: %d\n", __func__, errno); return false; } #else if (fsync(fileno(file)) != 0 && errno != EINVAL) { LogPrintf("%s: fsync failed: %d\n", __func__, errno); return false; } #endif #endif return true; } bool TruncateFile(FILE *file, unsigned int length) { #if defined(WIN32) return _chsize(_fileno(file), length) == 0; #else return ftruncate(fileno(file), length) == 0; #endif } /** * This function tries to raise the file descriptor limit to the requested * number. It returns the actual file descriptor limit (which may be more or * less than nMinFD) */ int RaiseFileDescriptorLimit(int nMinFD) { #if defined(WIN32) return 8192; #else struct rlimit limitFD; if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) { if (limitFD.rlim_cur < (rlim_t)nMinFD) { limitFD.rlim_cur = nMinFD; if (limitFD.rlim_cur > limitFD.rlim_max) { limitFD.rlim_cur = limitFD.rlim_max; } setrlimit(RLIMIT_NOFILE, &limitFD); getrlimit(RLIMIT_NOFILE, &limitFD); } return limitFD.rlim_cur; } // getrlimit failed, assume it's fine. return nMinFD; #endif } /** * This function tries to make a particular range of a file allocated * (corresponding to disk space) it is advisory, and the range specified in the * arguments will never contain live data. */ void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) { #if defined(WIN32) // Windows-specific version. HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); LARGE_INTEGER nFileSize; int64_t nEndPos = (int64_t)offset + length; nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF; nFileSize.u.HighPart = nEndPos >> 32; SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN); SetEndOfFile(hFile); #elif defined(MAC_OSX) // OSX specific version // NOTE: Contrary to other OS versions, the OSX version assumes that // NOTE: offset is the size of the file. fstore_t fst; fst.fst_flags = F_ALLOCATECONTIG; fst.fst_posmode = F_PEOFPOSMODE; fst.fst_offset = 0; // mac os fst_length takes the number of free bytes to allocate, // not the desired file size fst.fst_length = length; fst.fst_bytesalloc = 0; if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) { fst.fst_flags = F_ALLOCATEALL; fcntl(fileno(file), F_PREALLOCATE, &fst); } ftruncate(fileno(file), static_cast<off_t>(offset) + length); #elif defined(HAVE_POSIX_FALLOCATE) // Version using posix_fallocate off_t nEndPos = (off_t)offset + length; posix_fallocate(fileno(file), 0, nEndPos); #else // Fallback version // TODO: just write one byte per block static const char buf[65536] = {}; if (fseek(file, offset, SEEK_SET)) { return; } while (length > 0) { unsigned int now = 65536; if (length < now) { now = length; } // Allowed to fail; this function is advisory anyway. fwrite(buf, 1, now, file); length -= now; } #endif } #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate) { WCHAR pszPath[MAX_PATH] = L""; if (SHGetSpecialFolderPathW(nullptr, pszPath, nFolder, fCreate)) { return fs::path(pszPath); } LogPrintf( "SHGetSpecialFolderPathW() failed, could not obtain requested path.\n"); return fs::path(""); } #endif #ifndef WIN32 std::string ShellEscape(const std::string &arg) { std::string escaped = arg; boost::replace_all(escaped, "'", "'\"'\"'"); return "'" + escaped + "'"; } #endif #if defined(HAVE_SYSTEM) void runCommand(const std::string &strCommand) { if (strCommand.empty()) { return; } #ifndef WIN32 int nErr = ::system(strCommand.c_str()); #else int nErr = ::_wsystem( std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t>() .from_bytes(strCommand) .c_str()); #endif if (nErr) { LogPrintf("runCommand error: system(%s) returned %d\n", strCommand, nErr); } } #endif void SetupEnvironment() { #ifdef HAVE_MALLOPT_ARENA_MAX // glibc-specific: On 32-bit systems set the number of arenas to 1. By // default, since glibc 2.10, the C library will create up to two heap // arenas per core. This is known to cause excessive virtual address space // usage in our usage. Work around it by setting the maximum number of // arenas to 1. if (sizeof(void *) == 4) { mallopt(M_ARENA_MAX, 1); } #endif // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale may // be invalid, in which case the "C.UTF-8" locale is used as fallback. #if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && \ !defined(__OpenBSD__) try { // Raises a runtime error if current locale is invalid. std::locale(""); } catch (const std::runtime_error &) { setenv("LC_ALL", "C.UTF-8", 1); } #elif defined(WIN32) // Set the default input/output charset is utf-8 SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8); #endif // The path locale is lazy initialized and to avoid deinitialization errors // in multithreading environments, it is set explicitly by the main thread. // A dummy locale is used to extract the internal default locale, used by // fs::path, which is then used to explicitly imbue the path. std::locale loc = fs::path::imbue(std::locale::classic()); #ifndef WIN32 fs::path::imbue(loc); #else fs::path::imbue(std::locale(loc, new std::codecvt_utf8_utf16<wchar_t>())); #endif } bool SetupNetworking() { #ifdef WIN32 // Initialize Windows Sockets. WSADATA wsadata; int ret = WSAStartup(MAKEWORD(2, 2), &wsadata); if (ret != NO_ERROR || LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { return false; } #endif return true; } int GetNumCores() { return std::thread::hardware_concurrency(); } std::string CopyrightHolders(const std::string &strPrefix) { return strPrefix + strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION); } // Obtain the application startup time (used for uptime calculation) int64_t GetStartupTime() { return nStartupTime; } fs::path AbsPathForConfigVal(const fs::path &path, bool net_specific) { if (path.is_absolute()) { return path; } return fs::absolute(path, GetDataDir(net_specific)); } void ScheduleBatchPriority() { #ifdef SCHED_BATCH const static sched_param param{}; const int rc = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m); if (rc != 0) { LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(rc)); } #endif } namespace util { #ifdef WIN32 WinCmdLineArgs::WinCmdLineArgs() { wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> utf8_cvt; argv = new char *[argc]; args.resize(argc); for (int i = 0; i < argc; i++) { args[i] = utf8_cvt.to_bytes(wargv[i]); argv[i] = &*args[i].begin(); } LocalFree(wargv); } WinCmdLineArgs::~WinCmdLineArgs() { delete[] argv; } std::pair<int, char **> WinCmdLineArgs::get() { return std::make_pair(argc, argv); } #endif } // namespace util diff --git a/src/util/system.h b/src/util/system.h index ecfd6a401..bf6a3e814 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -1,517 +1,517 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. /** * Server/client environment: argument handling, config file parsing, * thread wrappers, startup time */ #ifndef BITCOIN_UTIL_SYSTEM_H #define BITCOIN_UTIL_SYSTEM_H #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> #endif #include <attributes.h> #include <compat.h> #include <compat/assumptions.h> #include <fs.h> #include <logging.h> #include <sync.h> #include <tinyformat.h> #include <util/settings.h> #include <util/threadnames.h> #include <util/time.h> #include <cstdint> #include <exception> #include <map> #include <optional> #include <set> #include <string> #include <utility> #include <vector> // Application startup time (used for uptime calculation) int64_t GetStartupTime(); extern const char *const BITCOIN_CONF_FILENAME; extern const char *const BITCOIN_SETTINGS_FILENAME; void SetupEnvironment(); bool SetupNetworking(); -template <typename... Args> bool error(const char *fmt, const Args &... args) { +template <typename... Args> bool error(const char *fmt, const Args &...args) { LogPrintf("ERROR: %s\n", tfm::format(fmt, args...)); return false; } void PrintExceptionContinue(const std::exception *pex, const char *pszThread); bool FileCommit(FILE *file); bool TruncateFile(FILE *file, unsigned int length); int RaiseFileDescriptorLimit(int nMinFD); void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); [[nodiscard]] bool RenameOver(fs::path src, fs::path dest); bool LockDirectory(const fs::path &directory, const std::string lockfile_name, bool probe_only = false); void UnlockDirectory(const fs::path &directory, const std::string &lockfile_name); bool DirIsWritable(const fs::path &directory); bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes = 0); /** * Get the size of a file by scanning it. * * @param[in] path The file path * @param[in] max Stop seeking beyond this limit * @return The file size or max */ std::streampos GetFileSize(const char *path, std::streamsize max = std::numeric_limits<std::streamsize>::max()); /** * Release all directory locks. This is used for unit testing only, at runtime * the global destructor will take care of the locks. */ void ReleaseDirectoryLocks(); bool TryCreateDirectories(const fs::path &p); fs::path GetDefaultDataDir(); const fs::path &GetDataDir(bool fNetSpecific = true); // Return true if -datadir option points to a valid directory or is not // specified. bool CheckDataDirOption(); fs::path GetConfigFile(const std::string &confPath); #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true); #endif #ifndef WIN32 std::string ShellEscape(const std::string &arg); #endif #if defined(HAVE_SYSTEM) void runCommand(const std::string &strCommand); #endif [[nodiscard]] bool ParseKeyValue(std::string &key, std::string &val); /** * Most paths passed as configuration arguments are treated as relative to * the datadir if they are not absolute. * * @param path The path to be conditionally prefixed with datadir. * @param net_specific Forwarded to GetDataDir(). * @return The normalized path. */ fs::path AbsPathForConfigVal(const fs::path &path, bool net_specific = true); inline bool IsSwitchChar(char c) { #ifdef WIN32 return c == '-' || c == '/'; #else return c == '-'; #endif } enum class OptionsCategory { OPTIONS, CONNECTION, WALLET, WALLET_DEBUG_TEST, ZMQ, DEBUG_TEST, CHAINPARAMS, NODE_RELAY, BLOCK_CREATION, RPC, GUI, COMMANDS, REGISTER_COMMANDS, // Always the last option to avoid printing these in the help HIDDEN, // Avalanche is still experimental, so we keep it hidden for now. AVALANCHE, }; struct SectionInfo { std::string m_name; std::string m_file; int m_line; }; class ArgsManager { public: enum Flags { // Boolean options can accept negation syntax -noOPTION or -noOPTION=1 ALLOW_BOOL = 0x01, ALLOW_INT = 0x02, ALLOW_STRING = 0x04, ALLOW_ANY = ALLOW_BOOL | ALLOW_INT | ALLOW_STRING, DEBUG_ONLY = 0x100, /* Some options would cause cross-contamination if values for * mainnet were used while running on regtest/testnet (or vice-versa). * Setting them as NETWORK_ONLY ensures that sharing a config file * between mainnet and regtest/testnet won't cause problems due to these * parameters by accident. */ NETWORK_ONLY = 0x200, // This argument's value is sensitive (such as a password). SENSITIVE = 0x400, }; protected: struct Arg { std::string m_help_param; std::string m_help_text; unsigned int m_flags; }; mutable RecursiveMutex cs_args; util::Settings m_settings GUARDED_BY(cs_args); std::string m_network GUARDED_BY(cs_args); std::set<std::string> m_network_only_args GUARDED_BY(cs_args); std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args); std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args); fs::path m_cached_blocks_path GUARDED_BY(cs_args); mutable fs::path m_cached_datadir_path GUARDED_BY(cs_args); mutable fs::path m_cached_network_datadir_path GUARDED_BY(cs_args); [[nodiscard]] bool ReadConfigStream(std::istream &stream, const std::string &filepath, std::string &error, bool ignore_invalid_keys = false); /** * Returns true if settings values from the default section should be used, * depending on the current network and whether the setting is * network-specific. */ bool UseDefaultSection(const std::string &arg) const EXCLUSIVE_LOCKS_REQUIRED(cs_args); /** * Get setting value. * * Result will be null if setting was unset, true if "-setting" argument was * passed false if "-nosetting" argument was passed, and a string if a * "-setting=value" argument was passed. */ util::SettingsValue GetSetting(const std::string &arg) const; /** * Get list of setting values. */ std::vector<util::SettingsValue> GetSettingsList(const std::string &arg) const; public: ArgsManager(); ~ArgsManager(); /** * Select the network in use */ void SelectConfigNetwork(const std::string &network); [[nodiscard]] bool ParseParameters(int argc, const char *const argv[], std::string &error); [[nodiscard]] bool ReadConfigFiles(std::string &error, bool ignore_invalid_keys = false); /** * Log warnings for options in m_section_only_args when they are specified * in the default section but not overridden on the command line or in a * network-specific section in the config file. */ const std::set<std::string> GetUnsuitableSectionOnlyArgs() const; /** * Log warnings for unrecognized section names in the config file. */ const std::list<SectionInfo> GetUnrecognizedSections() const; /** * Get blocks directory path * * @return Blocks path which is network specific */ const fs::path &GetBlocksDirPath(); /** * Get data directory path * * @param net_specific Append network identifier to the returned path * @return Absolute path on success, otherwise an empty path when a * non-directory path would be returned * @post Returned directory path is created unless it is empty */ const fs::path &GetDataDirPath(bool net_specific = true) const; /** * Clear cached directory paths */ void ClearPathCache(); /** * Return a vector of strings of the given argument * * @param strArg Argument to get (e.g. "-foo") * @return command-line arguments */ std::vector<std::string> GetArgs(const std::string &strArg) const; /** * Return true if the given argument has been manually set. * * @param strArg Argument to get (e.g. "-foo") * @return true if the argument has been set */ bool IsArgSet(const std::string &strArg) const; /** * Return true if the argument was originally passed as a negated option, * i.e. -nofoo. * * @param strArg Argument to get (e.g. "-foo") * @return true if the argument was passed negated */ bool IsArgNegated(const std::string &strArg) const; /** * Return string argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param strDefault (e.g. "1") * @return command-line argument or default value */ std::string GetArg(const std::string &strArg, const std::string &strDefault) const; /** * Return integer argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param nDefault (e.g. 1) * @return command-line argument (0 if invalid number) or default value */ int64_t GetArg(const std::string &strArg, int64_t nDefault) const; /** * Return boolean argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param fDefault (true or false) * @return command-line argument or default value */ bool GetBoolArg(const std::string &strArg, bool fDefault) const; /** * Set an argument if it doesn't already have a value. * * @param strArg Argument to set (e.g. "-foo") * @param strValue Value (e.g. "1") * @return true if argument gets set, false if it already had a value */ bool SoftSetArg(const std::string &strArg, const std::string &strValue); /** * Set a boolean argument if it doesn't already have a value. * * @param strArg Argument to set (e.g. "-foo") * @param fValue Value (e.g. false) * @return true if argument gets set, false if it already had a value */ bool SoftSetBoolArg(const std::string &strArg, bool fValue); // Forces an arg setting. Called by SoftSetArg() if the arg hasn't already // been set. Also called directly in testing. void ForceSetArg(const std::string &strArg, const std::string &strValue); // Forces a multi arg setting, used only in testing void ForceSetMultiArg(const std::string &strArg, const std::vector<std::string> &values); /** * Looks for -regtest, -testnet and returns the appropriate BIP70 chain * name. * @return CBaseChainParams::MAIN by default; raises runtime error if an * invalid combination is given. */ std::string GetChainName() const; /** * Add argument */ void AddArg(const std::string &name, const std::string &help, unsigned int flags, const OptionsCategory &cat); /** * Remove a forced arg setting, used only in testing. */ void ClearForcedArg(const std::string &strArg); /** * Add many hidden arguments */ void AddHiddenArgs(const std::vector<std::string> &args); /** * Clear available arguments */ void ClearArgs() { LOCK(cs_args); m_available_args.clear(); m_network_only_args.clear(); } /** * Get the help string */ std::string GetHelpMessage() const; /** * Return Flags for known arg. * Return std::nullopt for unknown arg. */ std::optional<unsigned int> GetArgFlags(const std::string &name) const; /** * Read and update settings file with saved settings. This needs to be * called after SelectParams() because the settings file location is * network-specific. */ bool InitSettings(std::string &error); /** * Get settings file path, or return false if read-write settings were * disabled with -nosettings. */ bool GetSettingsPath(fs::path *filepath = nullptr, bool temp = false) const; /** * Read settings file. Push errors to vector, or log them if null. */ bool ReadSettingsFile(std::vector<std::string> *errors = nullptr); /** * Write settings file. Push errors to vector, or log them if null. */ bool WriteSettingsFile(std::vector<std::string> *errors = nullptr) const; /** * Access settings with lock held. */ template <typename Fn> void LockSettings(Fn &&fn) { LOCK(cs_args); fn(m_settings); } /** * Log the config file options and the command line arguments, * useful for troubleshooting. */ void LogArgs() const; private: // Helper function for LogArgs(). void logArgsPrefix(const std::string &prefix, const std::string §ion, const std::map<std::string, std::vector<util::SettingsValue>> &args) const; }; extern ArgsManager gArgs; /** * @return true if help has been requested via a command-line arg */ bool HelpRequested(const ArgsManager &args); /** Add help options to the args manager */ void SetupHelpOptions(ArgsManager &args); /** * Format a string to be used as group of options in help messages. * * @param message Group name (e.g. "RPC server options:") * @return the formatted string */ std::string HelpMessageGroup(const std::string &message); /** * Format a string to be used as option description in help messages. * * @param option Option message (e.g. "-rpcuser=<user>") * @param message Option description (e.g. "Username for JSON-RPC connections") * @return the formatted string */ std::string HelpMessageOpt(const std::string &option, const std::string &message); /** * Return the number of cores available on the current system. * @note This does count virtual cores, such as those provided by * HyperThreading. */ int GetNumCores(); /** * .. and a wrapper that just calls func once */ template <typename Callable> void TraceThread(const char *name, Callable func) { util::ThreadRename(name); try { LogPrintf("%s thread start\n", name); func(); LogPrintf("%s thread exit\n", name); } catch (const std::exception &e) { PrintExceptionContinue(&e, name); throw; } catch (...) { PrintExceptionContinue(nullptr, name); throw; } } std::string CopyrightHolders(const std::string &strPrefix); /** * On platforms that support it, tell the kernel the calling thread is * CPU-intensive and non-interactive. See SCHED_BATCH in sched(7) for details. * */ void ScheduleBatchPriority(); namespace util { //! Simplification of std insertion template <typename Tdst, typename Tsrc> inline void insert(Tdst &dst, const Tsrc &src) { dst.insert(dst.begin(), src.begin(), src.end()); } template <typename TsetT, typename Tsrc> inline void insert(std::set<TsetT> &dst, const Tsrc &src) { dst.insert(src.begin(), src.end()); } #ifdef WIN32 class WinCmdLineArgs { public: WinCmdLineArgs(); ~WinCmdLineArgs(); std::pair<int, char **> get(); private: int argc; char **argv; std::vector<std::string> args; }; #endif } // namespace util #endif // BITCOIN_UTIL_SYSTEM_H diff --git a/src/util/translation.h b/src/util/translation.h index 14b6dd1e1..44d35e365 100644 --- a/src/util/translation.h +++ b/src/util/translation.h @@ -1,60 +1,60 @@ // Copyright (c) 2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_UTIL_TRANSLATION_H #define BITCOIN_UTIL_TRANSLATION_H #include <tinyformat.h> #include <functional> /** * Bilingual messages: * - in GUI: user's native language + untranslated (i.e. English) * - in log and stderr: untranslated only */ struct bilingual_str { std::string original; std::string translated; bilingual_str &operator+=(const bilingual_str &rhs) { original += rhs.original; translated += rhs.translated; return *this; } bool empty() const { return original.empty(); } }; inline bilingual_str operator+(bilingual_str lhs, const bilingual_str &rhs) { lhs += rhs; return lhs; } /** Mark a bilingual_str as untranslated */ inline bilingual_str Untranslated(std::string original) { return {original, original}; } namespace tinyformat { template <typename... Args> -bilingual_str format(const bilingual_str &fmt, const Args &... args) { +bilingual_str format(const bilingual_str &fmt, const Args &...args) { return bilingual_str{format(fmt.original, args...), format(fmt.translated, args...)}; } } // namespace tinyformat /** Translate a message to the native language of the user. */ const extern std::function<std::string(const char *)> G_TRANSLATION_FUN; /** * Translation function. * If no translation function is set, simply return the input. */ inline bilingual_str _(const char *psz) { return bilingual_str{psz, G_TRANSLATION_FUN ? (G_TRANSLATION_FUN)(psz) : psz}; } #endif // BITCOIN_UTIL_TRANSLATION_H diff --git a/src/util/vector.h b/src/util/vector.h index 6670d79c5..982a56f2e 100644 --- a/src/util/vector.h +++ b/src/util/vector.h @@ -1,49 +1,49 @@ // Copyright (c) 2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_UTIL_VECTOR_H #define BITCOIN_UTIL_VECTOR_H #include <initializer_list> #include <type_traits> #include <vector> /** Construct a vector with the specified elements. * * This is preferable over the list initializing constructor of std::vector: * - It automatically infers the element type from its arguments. * - If any arguments are rvalue references, they will be moved into the vector * (list initialization always copies). */ template <typename... Args> inline std::vector<typename std::common_type<Args...>::type> -Vector(Args &&... args) { +Vector(Args &&...args) { std::vector<typename std::common_type<Args...>::type> ret; ret.reserve(sizeof...(args)); // The line below uses the trick from // https://www.experts-exchange.com/articles/32502/None-recursive-variadic-templates-with-std-initializer-list.html (void)std::initializer_list<int>{ (ret.emplace_back(std::forward<Args>(args)), 0)...}; return ret; } /** Concatenate two vectors, moving elements. */ template <typename V> inline V Cat(V v1, V &&v2) { v1.reserve(v1.size() + v2.size()); for (auto &arg : v2) { v1.push_back(std::move(arg)); } return v1; } /** Concatenate two vectors. */ template <typename V> inline V Cat(V v1, const V &v2) { v1.reserve(v1.size() + v2.size()); for (const auto &arg : v2) { v1.push_back(arg); } return v1; } #endif // BITCOIN_UTIL_VECTOR_H diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index cb5d6bda9..a0919d7fd 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -1,341 +1,341 @@ #!/usr/bin/env python3 # # Copyright (c) 2018-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. # # Lint format strings: This program checks that the number of arguments passed # to a variadic format string function matches the number of format specifiers # in the format string. import argparse import doctest import re import sys FALSE_POSITIVES = [ ("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"), - ("src/index/base.cpp", "FatalError(const char *fmt, const Args &... args)"), - ("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char *fmt, const Args &... args)"), + ("src/index/base.cpp", "FatalError(const char *fmt, const Args &...args)"), + ("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char *fmt, const Args &...args)"), ("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION)"), ("src/validationinterface.cpp", "LogPrint(BCLog::VALIDATION, fmt \"\\n\", __VA_ARGS__)"), - ("src/tinyformat.h", "printf(const char *fmt, const Args &... args)"), + ("src/tinyformat.h", "printf(const char *fmt, const Args &...args)"), ("src/tinyformat.h", "printf(const char *fmt, TINYFORMAT_VARARGS(n))"), ("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"), ("src/wallet/scriptpubkeyman.h", "WalletLogPrintf(std::string fmt, Params... parameters)"), ("src/wallet/scriptpubkeyman.h", "LogPrintf((\"%s \" + fmt).c_str(), m_storage.GetDisplayName(), parameters...)"), ("src/wallet/scriptpubkeyman.h", "WalletLogPrintf(const std::string& fmt, const Params&... parameters)"), ] FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS = [ ("FatalError", 0), ("fprintf", 1), ("LogConnectFailure", 1), ("LogPrint", 1), ("LogPrintf", 0), ("printf", 0), ("snprintf", 2), ("sprintf", 1), ("strprintf", 0), ("tfm::format", 1), # Assuming tfm::::format(std::ostream&, ... ("vfprintf", 1), ("vprintf", 1), ("vsnprintf", 1), ("vsprintf", 1), ] def parse_function_calls(function_name, source_code): """Return an array with all calls to function function_name in string source_code. Preprocessor directives and C++ style comments ("//") in source_code are removed. >>> len(parse_function_calls("foo", "foo();bar();foo();bar();")) 2 >>> parse_function_calls("foo", "foo(1);bar(1);foo(2);bar(2);")[0].startswith("foo(1);") True >>> parse_function_calls("foo", "foo(1);bar(1);foo(2);bar(2);")[1].startswith("foo(2);") True >>> len(parse_function_calls("foo", "foo();bar();// foo();bar();")) 1 >>> len(parse_function_calls("foo", "#define FOO foo();")) 0 """ assert isinstance(function_name, str) and isinstance( source_code, str) and function_name lines = [re.sub("// .*", " ", line).strip() for line in source_code.split("\n") if not line.strip().startswith("#")] return re.findall( r"[^a-zA-Z_](?=({}\(.*).*)".format(function_name), " " + " ".join(lines)) def normalize(s): """Return a normalized version of string s with newlines, tabs and C style comments ("/* ... */") replaced with spaces. Multiple spaces are replaced with a single space. >>> normalize(" /* nothing */ foo\tfoo /* bar */ foo ") 'foo foo foo' """ assert isinstance(s, str) s = s.replace("\n", " ") s = s.replace("\t", " ") s = re.sub(r"/\*.*?\*/", " ", s) s = re.sub(" {2,}", " ", s) return s.strip() ESCAPE_MAP = { r"\n": "[escaped-newline]", r"\t": "[escaped-tab]", r'\"': "[escaped-quote]", } def escape(s): """Return the escaped version of string s with "\\\"", "\\n" and "\\t" escaped as "[escaped-backslash]", "[escaped-newline]" and "[escaped-tab]". >>> unescape(escape("foo")) == "foo" True >>> escape(r'foo \\t foo \\n foo \\\\ foo \\ foo \\"bar\\"') 'foo [escaped-tab] foo [escaped-newline] foo \\\\\\\\ foo \\\\ foo [escaped-quote]bar[escaped-quote]' """ assert isinstance(s, str) for raw_value, escaped_value in ESCAPE_MAP.items(): s = s.replace(raw_value, escaped_value) return s def unescape(s): """Return the unescaped version of escaped string s. Reverses the replacements made in function escape(s). >>> unescape(escape("bar")) 'bar' >>> unescape("foo [escaped-tab] foo [escaped-newline] foo \\\\\\\\ foo \\\\ foo [escaped-quote]bar[escaped-quote]") 'foo \\\\t foo \\\\n foo \\\\\\\\ foo \\\\ foo \\\\"bar\\\\"' """ assert isinstance(s, str) for raw_value, escaped_value in ESCAPE_MAP.items(): s = s.replace(escaped_value, raw_value) return s def parse_function_call_and_arguments(function_name, function_call): """Split string function_call into an array of strings consisting of: * the string function_call followed by "(" * the function call argument #1 * ... * the function call argument #n * a trailing ");" The strings returned are in escaped form. See escape(...). >>> parse_function_call_and_arguments("foo", 'foo("%s", "foo");') ['foo(', '"%s",', ' "foo"', ')'] >>> parse_function_call_and_arguments("foo", 'foo("%s", "foo");') ['foo(', '"%s",', ' "foo"', ')'] >>> parse_function_call_and_arguments("foo", 'foo("%s %s", "foo", "bar");') ['foo(', '"%s %s",', ' "foo",', ' "bar"', ')'] >>> parse_function_call_and_arguments("fooprintf", 'fooprintf("%050d", i);') ['fooprintf(', '"%050d",', ' i', ')'] >>> parse_function_call_and_arguments("foo", 'foo(bar(foobar(barfoo("foo"))), foobar); barfoo') ['foo(', 'bar(foobar(barfoo("foo"))),', ' foobar', ')'] >>> parse_function_call_and_arguments("foo", "foo()") ['foo(', '', ')'] >>> parse_function_call_and_arguments("foo", "foo(123)") ['foo(', '123', ')'] >>> parse_function_call_and_arguments("foo", 'foo("foo")') ['foo(', '"foo"', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf), err);') ['strprintf(', '"%s (%d)",', ' std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf),', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo<wchar_t>().to_bytes(buf), err);') ['strprintf(', '"%s (%d)",', ' foo<wchar_t>().to_bytes(buf),', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo().to_bytes(buf), err);') ['strprintf(', '"%s (%d)",', ' foo().to_bytes(buf),', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo << 1, err);') ['strprintf(', '"%s (%d)",', ' foo << 1,', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo<bar>() >> 1, err);') ['strprintf(', '"%s (%d)",', ' foo<bar>() >> 1,', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo < 1 ? bar : foobar, err);') ['strprintf(', '"%s (%d)",', ' foo < 1 ? bar : foobar,', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo < 1, err);') ['strprintf(', '"%s (%d)",', ' foo < 1,', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo > 1 ? bar : foobar, err);') ['strprintf(', '"%s (%d)",', ' foo > 1 ? bar : foobar,', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo > 1, err);') ['strprintf(', '"%s (%d)",', ' foo > 1,', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo <= 1, err);') ['strprintf(', '"%s (%d)",', ' foo <= 1,', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo <= bar<1, 2>(1, 2), err);') ['strprintf(', '"%s (%d)",', ' foo <= bar<1, 2>(1, 2),', ' err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo>foo<1,2>(1,2)?bar:foobar,err)'); ['strprintf(', '"%s (%d)",', ' foo>foo<1,2>(1,2)?bar:foobar,', 'err', ')'] >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo>foo<1,2>(1,2),err)'); ['strprintf(', '"%s (%d)",', ' foo>foo<1,2>(1,2),', 'err', ')'] """ assert isinstance(function_name, str) and isinstance( function_call, str) and function_name remaining = normalize(escape(function_call)) expected_function_call = "{}(".format(function_name) assert remaining.startswith(expected_function_call) parts = [expected_function_call] remaining = remaining[len(expected_function_call):] open_parentheses = 1 open_template_arguments = 0 in_string = False parts.append("") for i, char in enumerate(remaining): parts.append(parts.pop() + char) if char == "\"": in_string = not in_string continue if in_string: continue if char == "(": open_parentheses += 1 continue if char == ")": open_parentheses -= 1 if open_parentheses > 1: continue if open_parentheses == 0: parts.append(parts.pop()[:-1]) parts.append(char) break prev_char = remaining[i - 1] if i - 1 >= 0 else None next_char = remaining[i + 1] if i + 1 <= len(remaining) - 1 else None if (char == "<" and next_char not in [" ", "<", "="] and prev_char not in [" ", "<"]): open_template_arguments += 1 continue if (char == ">" and next_char not in [" ", ">", "="] and prev_char not in [" ", ">"] and open_template_arguments > 0): open_template_arguments -= 1 if open_template_arguments > 0: continue if char == ",": parts.append("") return parts def parse_string_content(argument): """Return the text within quotes in string argument. >>> parse_string_content('1 "foo %d bar" 2') 'foo %d bar' >>> parse_string_content('1 foobar 2') '' >>> parse_string_content('1 "bar" 2') 'bar' >>> parse_string_content('1 "foo" 2 "bar" 3') 'foobar' >>> parse_string_content('1 "foo" 2 " " "bar" 3') 'foo bar' >>> parse_string_content('""') '' >>> parse_string_content('') '' >>> parse_string_content('1 2 3') '' """ assert isinstance(argument, str) string_content = "" in_string = False for char in normalize(escape(argument)): if char == "\"": in_string = not in_string elif in_string: string_content += char return string_content def count_format_specifiers(format_string): """Return the number of format specifiers in string format_string. >>> count_format_specifiers("foo bar foo") 0 >>> count_format_specifiers("foo %d bar foo") 1 >>> count_format_specifiers("foo %d bar %i foo") 2 >>> count_format_specifiers("foo %d bar %i foo %% foo") 2 >>> count_format_specifiers("foo %d bar %i foo %% foo %d foo") 3 >>> count_format_specifiers("foo %d bar %i foo %% foo %*d foo") 4 """ assert isinstance(format_string, str) n = 0 in_specifier = False for i, char in enumerate(format_string): if format_string[i - 1:i + 1] == "%%" or format_string[i:i + 2] == "%%": pass elif char == "%": in_specifier = True n += 1 elif char in "aAcdeEfFgGinopsuxX": in_specifier = False elif in_specifier and char == "*": n += 1 return n def main(args_in): """ Return a string output with information on string format errors >>> main(["test/lint/lint-format-strings-tests.txt"]) test/lint/lint-format-strings-tests.txt: Expected 1 argument(s) after format string but found 2 argument(s): printf("%d", 1, 2) test/lint/lint-format-strings-tests.txt: Expected 2 argument(s) after format string but found 3 argument(s): printf("%a %b", 1, 2, "anything") test/lint/lint-format-strings-tests.txt: Expected 1 argument(s) after format string but found 0 argument(s): printf("%d") test/lint/lint-format-strings-tests.txt: Expected 3 argument(s) after format string but found 2 argument(s): printf("%a%b%z", 1, "anything") >>> main(["test/lint/lint-format-strings-tests-skip-arguments.txt"]) test/lint/lint-format-strings-tests-skip-arguments.txt: Expected 1 argument(s) after format string but found 2 argument(s): fprintf(skipped, "%d", 1, 2) test/lint/lint-format-strings-tests-skip-arguments.txt: Expected 1 argument(s) after format string but found 0 argument(s): fprintf(skipped, "%d") test/lint/lint-format-strings-tests-skip-arguments.txt: Expected 1 argument(s) after format string but found 2 argument(s): snprintf(skip1, skip2, "%d", 1, 2) test/lint/lint-format-strings-tests-skip-arguments.txt: Expected 1 argument(s) after format string but found 0 argument(s): snprintf(skip1, skip2, "%d") test/lint/lint-format-strings-tests-skip-arguments.txt: Could not parse function call string "snprintf(...)": snprintf(skip1, "%d") """ parser = argparse.ArgumentParser(description="This program checks that the number of arguments passed " "to a variadic format string function matches the number of format " "specifiers in the format string.") parser.add_argument("file", type=argparse.FileType( "r", encoding="utf-8"), nargs="*", help="C++ source code file (e.g. foo.cpp)") args = parser.parse_args(args_in) for f in args.file: file_content = f.read() for (function_name, skip_arguments) in FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS: for function_call_str in parse_function_calls( function_name, file_content): parts = parse_function_call_and_arguments( function_name, function_call_str) relevant_function_call_str = unescape("".join(parts))[:512] if (f.name, relevant_function_call_str) in FALSE_POSITIVES: continue if len(parts) < 3 + skip_arguments: print("{}: Could not parse function call string \"{}(...)\": {}".format( f.name, function_name, relevant_function_call_str)) continue argument_count = len(parts) - 3 - skip_arguments format_str = parse_string_content(parts[1 + skip_arguments]) format_specifier_count = count_format_specifiers(format_str) if format_specifier_count != argument_count: print("{}: Expected {} argument(s) after format string but found {} argument(s): {}".format( f.name, format_specifier_count, argument_count, relevant_function_call_str)) continue if __name__ == "__main__": doctest.testmod() main(sys.argv[1:])