diff options
106 files changed, 3361 insertions, 2322 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 2aebf1cd54..249d0b8df9 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -33,7 +33,7 @@ before_build: - ps: Start-Process clcache-server - ps: fsutil behavior set disablelastaccess 0 # Enable Access time feature on Windows (for clcache) build_script: -- cmd: msbuild /p:TrackFileAccess=false /p:CLToolExe=clcache.exe build_msvc\bitcoin.sln /m /v:q /nowarn:C4244;C4267;C4715 /nologo +- cmd: msbuild /p:TrackFileAccess=false /p:CLToolExe=clcache.exe build_msvc\bitcoin.sln /m /v:q /nologo after_build: - ps: fsutil behavior set disablelastaccess 1 # Disable Access time feature on Windows (better performance) - ps: clcache -z diff --git a/.travis/lint_04_install.sh b/.travis/lint_04_install.sh index 723e7c56f1..9a22773e57 100755 --- a/.travis/lint_04_install.sh +++ b/.travis/lint_04_install.sh @@ -9,3 +9,7 @@ export LC_ALL=C travis_retry pip install codespell==1.13.0 travis_retry pip install flake8==3.5.0 travis_retry pip install vulture==0.29 + +SHELLCHECK_VERSION=v0.6.0 +curl -s "https://storage.googleapis.com/shellcheck/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar --xz -xf - --directory /tmp/ +export PATH="/tmp/shellcheck-${SHELLCHECK_VERSION}:${PATH}" diff --git a/build_msvc/common.vcxproj b/build_msvc/common.vcxproj index 5c87026efe..c7c20622e4 100644 --- a/build_msvc/common.vcxproj +++ b/build_msvc/common.vcxproj @@ -15,6 +15,8 @@ <ItemDefinitionGroup> <ClCompile> <AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions> + <DisableSpecificWarnings>4018;4244;4267;4715;4805;</DisableSpecificWarnings> + <TreatWarningAsError>true</TreatWarningAsError> </ClCompile> </ItemDefinitionGroup> -</Project>
\ No newline at end of file +</Project> diff --git a/configure.ac b/configure.ac index e1c265f10f..c4b1d63bfe 100644 --- a/configure.ac +++ b/configure.ac @@ -511,17 +511,6 @@ case $host in LEVELDB_TARGET_FLAGS="-DOS_MACOSX" if test x$cross_compiling != xyes; then BUILD_OS=darwin - AC_CHECK_PROG([PORT],port, port) - if test x$PORT = xport; then - dnl add default macports paths - CPPFLAGS="$CPPFLAGS -isystem /opt/local/include" - LIBS="$LIBS -L/opt/local/lib" - if test -d /opt/local/include/db48; then - CPPFLAGS="$CPPFLAGS -I/opt/local/include/db48" - LIBS="$LIBS -L/opt/local/lib/db48" - fi - fi - AC_PATH_PROGS([RSVG_CONVERT], [rsvg-convert rsvg],rsvg-convert) AC_CHECK_PROG([BREW],brew, brew) if test x$BREW = xbrew; then diff --git a/contrib/devtools/README.md b/contrib/devtools/README.md index a0b6225345..6ee65f40be 100644 --- a/contrib/devtools/README.md +++ b/contrib/devtools/README.md @@ -119,7 +119,25 @@ Configuring the github-merge tool for the bitcoin repository is done in the foll git config githubmerge.repository bitcoin/bitcoin git config githubmerge.testcmd "make -j4 check" (adapt to whatever you want to use for testing) - git config --global user.signingkey mykeyid (if you want to GPG sign) + git config --global user.signingkey mykeyid + +Authentication (optional) +-------------------------- + +The API request limit for unauthenticated requests is quite low, but the +limit for authenticated requests is much higher. If you start running +into rate limiting errors it can be useful to set an authentication token +so that the script can authenticate requests. + +- First, go to [Personal access tokens](https://github.com/settings/tokens). +- Click 'Generate new token'. +- Fill in an arbitrary token description. No further privileges are needed. +- Click the `Generate token` button at the bottom of the form. +- Copy the generated token (should be a hexadecimal string) + +Then do: + + git config --global user.ghtoken "pasted token" Create and verify timestamps of merge commits --------------------------------------------- diff --git a/contrib/devtools/copyright_header.py b/contrib/devtools/copyright_header.py index 6d7a592f01..f2987f2260 100755 --- a/contrib/devtools/copyright_header.py +++ b/contrib/devtools/copyright_header.py @@ -48,15 +48,22 @@ def applies_to_file(filename): # obtain list of files in repo according to INCLUDE and EXCLUDE ################################################################################ -GIT_LS_CMD = 'git ls-files' +GIT_LS_CMD = 'git ls-files --full-name'.split(' ') +GIT_TOPLEVEL_CMD = 'git rev-parse --show-toplevel'.split(' ') -def call_git_ls(): - out = subprocess.check_output(GIT_LS_CMD.split(' ')) +def call_git_ls(base_directory): + out = subprocess.check_output([*GIT_LS_CMD, base_directory]) return [f for f in out.decode("utf-8").split('\n') if f != ''] -def get_filenames_to_examine(): - filenames = call_git_ls() - return sorted([filename for filename in filenames if +def call_git_toplevel(): + "Returns the absolute path to the project root" + return subprocess.check_output(GIT_TOPLEVEL_CMD).strip().decode("utf-8") + +def get_filenames_to_examine(base_directory): + "Returns an array of absolute paths to any project files in the base_directory that pass the include/exclude filters" + root = call_git_toplevel() + filenames = call_git_ls(base_directory) + return sorted([os.path.join(root, filename) for filename in filenames if applies_to_file(filename)]) ################################################################################ @@ -83,24 +90,12 @@ def compile_copyright_regex(copyright_style, year_style, name): EXPECTED_HOLDER_NAMES = [ "Satoshi Nakamoto\n", "The Bitcoin Core developers\n", - "The Bitcoin Core developers \n", "Bitcoin Core Developers\n", - "the Bitcoin Core developers\n", - "The Bitcoin developers\n", - "The LevelDB Authors\. All rights reserved\.\n", "BitPay Inc\.\n", - "BitPay, Inc\.\n", "University of Illinois at Urbana-Champaign\.\n", - "MarcoFalke\n", "Pieter Wuille\n", - "Pieter Wuille +\*\n", - "Pieter Wuille, Gregory Maxwell +\*\n", - "Pieter Wuille, Andrew Poelstra +\*\n", - "Andrew Poelstra +\*\n", "Wladimir J. van der Laan\n", "Jeff Garzik\n", - "Diederik Huys, Pieter Wuille +\*\n", - "Thomas Daede, Cory Fields +\*\n", "Jan-Klaas Kollhof\n", "Sam Rushing\n", "ArtForz -- public domain half-a-node\n", @@ -146,7 +141,7 @@ def file_has_without_c_style_copyright_for_holder(contents, holder_name): ################################################################################ def read_file(filename): - return open(os.path.abspath(filename), 'r', encoding="utf8").read() + return open(filename, 'r', encoding="utf8").read() def gather_file_info(filename): info = {} @@ -260,12 +255,9 @@ def print_report(file_infos, verbose): print(SEPARATOR) def exec_report(base_directory, verbose): - original_cwd = os.getcwd() - os.chdir(base_directory) - filenames = get_filenames_to_examine() + filenames = get_filenames_to_examine(base_directory) file_infos = [gather_file_info(f) for f in filenames] print_report(file_infos, verbose) - os.chdir(original_cwd) ################################################################################ # report cmd @@ -325,13 +317,13 @@ def get_most_recent_git_change_year(filename): ################################################################################ def read_file_lines(filename): - f = open(os.path.abspath(filename), 'r', encoding="utf8") + f = open(filename, 'r', encoding="utf8") file_lines = f.readlines() f.close() return file_lines def write_file_lines(filename, file_lines): - f = open(os.path.abspath(filename), 'w', encoding="utf8") + f = open(filename, 'w', encoding="utf8") f.write(''.join(file_lines)) f.close() @@ -399,11 +391,8 @@ def update_updatable_copyright(filename): "Copyright updated! -> %s" % last_git_change_year) def exec_update_header_year(base_directory): - original_cwd = os.getcwd() - os.chdir(base_directory) - for filename in get_filenames_to_examine(): + for filename in get_filenames_to_examine(base_directory): update_updatable_copyright(filename) - os.chdir(original_cwd) ################################################################################ # update cmd diff --git a/contrib/devtools/github-merge.py b/contrib/devtools/github-merge.py index 4f827401fb..ab834b7623 100755 --- a/contrib/devtools/github-merge.py +++ b/contrib/devtools/github-merge.py @@ -23,6 +23,7 @@ import sys import json import codecs from urllib.request import Request, urlopen +from urllib.error import HTTPError # External tools (can be overridden using environment) GIT = os.getenv('GIT','git') @@ -46,17 +47,24 @@ def git_config_get(option, default=None): except subprocess.CalledProcessError: return default -def retrieve_pr_info(repo,pull): +def retrieve_pr_info(repo,pull,ghtoken): ''' Retrieve pull request information from github. Return None if no title can be found, or an error happens. ''' try: req = Request("https://api.github.com/repos/"+repo+"/pulls/"+pull) + if ghtoken is not None: + req.add_header('Authorization', 'token ' + ghtoken) result = urlopen(req) reader = codecs.getreader('utf-8') obj = json.load(reader(result)) return obj + except HTTPError as e: + error_message = e.read() + print('Warning: unable to retrieve pull information from github: %s' % e) + print('Detailed error: %s' % error_message) + return None except Exception as e: print('Warning: unable to retrieve pull information from github: %s' % e) return None @@ -134,6 +142,7 @@ def parse_arguments(): In addition, you can set the following git configuration variables: githubmerge.repository (mandatory), user.signingkey (mandatory), + user.ghtoken (default: none). githubmerge.host (default: git@github.com), githubmerge.branch (no default), githubmerge.testcmd (default: none). @@ -152,6 +161,7 @@ def main(): host = git_config_get('githubmerge.host','git@github.com') opt_branch = git_config_get('githubmerge.branch',None) testcmd = git_config_get('githubmerge.testcmd') + ghtoken = git_config_get('user.ghtoken') signingkey = git_config_get('user.signingkey') if repo is None: print("ERROR: No repository configured. Use this command to set:", file=stderr) @@ -162,14 +172,17 @@ def main(): print("git config --global user.signingkey <key>",file=stderr) sys.exit(1) - host_repo = host+":"+repo # shortcut for push/pull target + if host.startswith(('https:','http:')): + host_repo = host+"/"+repo+".git" + else: + host_repo = host+":"+repo # Extract settings from command line args = parse_arguments() pull = str(args.pull[0]) # Receive pull information from github - info = retrieve_pr_info(repo,pull) + info = retrieve_pr_info(repo,pull,ghtoken) if info is None: sys.exit(1) title = info['title'].strip() diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus index 17ce6c44f9..9da03e5b02 100755 --- a/contrib/macdeploy/macdeployqtplus +++ b/contrib/macdeploy/macdeployqtplus @@ -172,12 +172,6 @@ class DeploymentInfo(object): if os.path.exists(os.path.join(parentDir, "translations")): # Classic layout, e.g. "/usr/local/Trolltech/Qt-4.x.x" self.qtPath = parentDir - elif os.path.exists(os.path.join(parentDir, "share", "qt4", "translations")): - # MacPorts layout, e.g. "/opt/local/share/qt4" - self.qtPath = os.path.join(parentDir, "share", "qt4") - elif os.path.exists(os.path.join(os.path.dirname(parentDir), "share", "qt4", "translations")): - # Newer Macports layout - self.qtPath = os.path.join(os.path.dirname(parentDir), "share", "qt4") else: self.qtPath = os.getenv("QTDIR", None) diff --git a/contrib/verify-commits/verify-commits.py b/contrib/verify-commits/verify-commits.py index b3c8064ec2..6bbed01073 100755 --- a/contrib/verify-commits/verify-commits.py +++ b/contrib/verify-commits/verify-commits.py @@ -91,7 +91,7 @@ def main(): no_sha1 = True prev_commit = "" initial_commit = current_commit - branch = subprocess.check_output([GIT, 'show', '-s', '--format=%H', initial_commit], universal_newlines=True, encoding='utf8').splitlines()[0] + branch = subprocess.check_output([GIT, 'show', '-s', '--format=%H', initial_commit]).decode('utf8').splitlines()[0] # Iterate through commits while True: @@ -112,7 +112,7 @@ def main(): if prev_commit != "": print("No parent of {} was signed with a trusted key!".format(prev_commit), file=sys.stderr) print("Parents are:", file=sys.stderr) - parents = subprocess.check_output([GIT, 'show', '-s', '--format=format:%P', prev_commit], universal_newlines=True, encoding='utf8').splitlines()[0].split(' ') + parents = subprocess.check_output([GIT, 'show', '-s', '--format=format:%P', prev_commit]).decode('utf8').splitlines()[0].split(' ') for parent in parents: subprocess.call([GIT, 'show', '-s', parent], stdout=sys.stderr) else: @@ -122,25 +122,25 @@ def main(): # Check the Tree-SHA512 if (verify_tree or prev_commit == "") and current_commit not in incorrect_sha512_allowed: tree_hash = tree_sha512sum(current_commit) - if ("Tree-SHA512: {}".format(tree_hash)) not in subprocess.check_output([GIT, 'show', '-s', '--format=format:%B', current_commit], universal_newlines=True, encoding='utf8').splitlines(): + if ("Tree-SHA512: {}".format(tree_hash)) not in subprocess.check_output([GIT, 'show', '-s', '--format=format:%B', current_commit]).decode('utf8').splitlines(): print("Tree-SHA512 did not match for commit " + current_commit, file=sys.stderr) sys.exit(1) # Merge commits should only have two parents - parents = subprocess.check_output([GIT, 'show', '-s', '--format=format:%P', current_commit], universal_newlines=True, encoding='utf8').splitlines()[0].split(' ') + parents = subprocess.check_output([GIT, 'show', '-s', '--format=format:%P', current_commit]).decode('utf8').splitlines()[0].split(' ') if len(parents) > 2: print("Commit {} is an octopus merge".format(current_commit), file=sys.stderr) sys.exit(1) # Check that the merge commit is clean - commit_time = int(subprocess.check_output([GIT, 'show', '-s', '--format=format:%ct', current_commit], universal_newlines=True, encoding='utf8').splitlines()[0]) + commit_time = int(subprocess.check_output([GIT, 'show', '-s', '--format=format:%ct', current_commit]).decode('utf8').splitlines()[0]) check_merge = commit_time > time.time() - args.clean_merge * 24 * 60 * 60 # Only check commits in clean_merge days allow_unclean = current_commit in unclean_merge_allowed if len(parents) == 2 and check_merge and not allow_unclean: - current_tree = subprocess.check_output([GIT, 'show', '--format=%T', current_commit], universal_newlines=True, encoding='utf8').splitlines()[0] + current_tree = subprocess.check_output([GIT, 'show', '--format=%T', current_commit]).decode('utf8').splitlines()[0] subprocess.call([GIT, 'checkout', '--force', '--quiet', parents[0]]) subprocess.call([GIT, 'merge', '--no-ff', '--quiet', '--no-gpg-sign', parents[1]], stdout=subprocess.DEVNULL) - recreated_tree = subprocess.check_output([GIT, 'show', '--format=format:%T', 'HEAD'], universal_newlines=True, encoding='utf8').splitlines()[0] + recreated_tree = subprocess.check_output([GIT, 'show', '--format=format:%T', 'HEAD']).decode('utf8').splitlines()[0] if current_tree != recreated_tree: print("Merge commit {} is not clean".format(current_commit), file=sys.stderr) subprocess.call([GIT, 'diff', current_commit]) diff --git a/depends/packages/zeromq.mk b/depends/packages/zeromq.mk index c9068e83a5..dfbc50580c 100644 --- a/depends/packages/zeromq.mk +++ b/depends/packages/zeromq.mk @@ -1,8 +1,8 @@ package=zeromq -$(package)_version=4.2.5 +$(package)_version=4.3.1 $(package)_download_path=https://github.com/zeromq/libzmq/releases/download/v$($(package)_version)/ $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=cc9090ba35713d59bb2f7d7965f877036c49c5558ea0c290b0dcc6f2a17e489f +$(package)_sha256_hash=bcbabe1e2c7d0eec4ed612e10b94b112dd5f06fcefa994a0c79a45d835cd21eb $(package)_patches=0001-fix-build-with-older-mingw64.patch 0002-disable-pthread_set_name_np.patch define $(package)_set_vars diff --git a/doc/JSON-RPC-interface.md b/doc/JSON-RPC-interface.md index 59df541567..982afd5d56 100644 --- a/doc/JSON-RPC-interface.md +++ b/doc/JSON-RPC-interface.md @@ -5,6 +5,85 @@ The headless daemon `bitcoind` has the JSON-RPC API enabled by default, the GUI option. In the GUI it is possible to execute RPC methods in the Debug Console Dialog. +## Security + +The RPC interface allows other programs to control Bitcoin Core, +including the ability to spend funds from your wallets, affect consensus +verification, read private data, and otherwise perform operations that +can cause loss of money, data, or privacy. This section suggests how +you should use and configure Bitcoin Core to reduce the risk that its +RPC interface will be abused. + +- **Securing the executable:** Anyone with physical or remote access to + the computer, container, or virtual machine running Bitcoin Core can + compromise either the whole program or just the RPC interface. This + includes being able to record any passphrases you enter for unlocking + your encrypted wallets or changing settings so that your Bitcoin Core + program tells you that certain transactions have multiple + confirmations even when they aren't part of the best block chain. For + this reason, you should not use Bitcoin Core for security sensitive + operations on systems you do not exclusively control, such as shared + computers or virtual private servers. + +- **Securing local network access:** By default, the RPC interface can + only be accessed by a client running on the same computer and only + after the client provides a valid authentication credential (username + and passphrase). Any program on your computer with access to the file + system and local network can obtain this level of access. + Additionally, other programs on your computer can attempt to provide + an RPC interface on the same port as used by Bitcoin Core in order to + trick you into revealing your authentication credentials. For this + reason, it is important to only use Bitcoin Core for + security-sensitive operations on a computer whose other programs you + trust. + +- **Securing remote network access:** You may optionally allow other + computers to remotely control Bitcoin Core by setting the `rpcallowip` + and `rpcbind` configuration parameters. These settings are only meant + for enabling connections over secure private networks or connections + that have been otherwise secured (e.g. using a VPN or port forwarding + with SSH or stunnel). **Do not enable RPC connections over the public + Internet.** Although Bitcoin Core's RPC interface does use + authentication, it does not use encryption, so your login credentials + are sent as clear text that can be read by anyone on your network + path. Additionally, the RPC interface has not been hardened to + withstand arbitrary Internet traffic, so changing the above settings + to expose it to the Internet (even using something like a Tor hidden + service) could expose you to unconsidered vulnerabilities. See + `bitcoind -help` for more information about these settings and other + settings described in this document. + + Related, if you use Bitcoin Core inside a Docker container, you may + need to expose the RPC port to the host system. The default way to + do this in Docker also exposes the port to the public Internet. + Instead, expose it only on the host system's localhost, for example: + `-p 127.0.0.1:8332:8332` + +- **Secure authentication:** By default, Bitcoin Core generates unique + login credentials each time it restarts and puts them into a file + readable only by the user that started Bitcoin Core, allowing any of + that user's RPC clients with read access to the file to login + automatically. The file is `.cookie` in the Bitcoin Core + configuration directory, and using these credentials is the preferred + RPC authentication method. If you need to generate static login + credentials for your programs, you can use the script in the + `share/rpcauth` directory in the Bitcoin Core source tree. As a final + fallback, you can directly use manually-chosen `rpcuser` and + `rpcpassword` configuration parameters---but you must ensure that you + choose a strong and unique passphrase (and still don't use insecure + networks, as mentioned above). + +- **Secure string handling:** The RPC interface does not guarantee any + escaping of data beyond what's necessary to encode it as JSON, + although it does usually provide serialized data using a hex + representation of the bytes. If you use RPC data in your programs or + provide its data to other programs, you must ensure any problem + strings are properly escaped. For example, multiple websites have + been manipulated because they displayed decoded hex strings that + included HTML `<script>` tags. For this reason, and other + non-security reasons, it is recommended to display all serialized data + in hex form only. + ## RPC consistency guarantees State that can be queried via RPCs is guaranteed to be at least up-to-date with diff --git a/doc/REST-interface.md b/doc/REST-interface.md index 44df698382..d21df36130 100644 --- a/doc/REST-interface.md +++ b/doc/REST-interface.md @@ -27,6 +27,7 @@ For full TX query capability, one must enable the transaction index via "txindex `GET /rest/block/notxdetails/<BLOCK-HASH>.<bin|hex|json>` Given a block hash: returns a block, in binary, hex-encoded binary or JSON formats. +Responds with 404 if the block doesn't exist. The HTTP request and response are both handled entirely in-memory, thus making maximum memory usage at least 2.66MB (1 MB max block, plus hex encoding) per request. @@ -36,6 +37,12 @@ With the /notxdetails/ option JSON response will only contain the transaction ha `GET /rest/headers/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` Given a block hash: returns <COUNT> amount of blockheaders in upward direction. +Returns empty if the block doesn't exist or it isn't in the active chain. + +#### Blockhash by height +`GET /rest/blockhashbyheight/<HEIGHT>.<bin|hex|json>` + +Given a height: returns hash of block in best-block-chain at height provided. #### Chaininfos `GET /rest/chaininfo.json` diff --git a/doc/dependencies.md b/doc/dependencies.md index 50dde02fad..b833e9151f 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -26,5 +26,5 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | Qt | [5.9.7](https://download.qt.io/official_releases/qt/) | [5.2](https://github.com/bitcoin/bitcoin/pull/14725) | No | | | | XCB | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L87) (Linux only) | | xkbcommon | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L86) (Linux only) | -| ZeroMQ | [4.2.5](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | | +| ZeroMQ | [4.3.1](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | | | zlib | [1.2.11](https://zlib.net/) | | | | No | diff --git a/doc/fuzzing.md b/doc/fuzzing.md index dff9e71bba..23317e938e 100644 --- a/doc/fuzzing.md +++ b/doc/fuzzing.md @@ -82,7 +82,7 @@ make ``` The fuzzer needs some inputs to work on, but the inputs or seeds can be used -interchangably between libFuzzer and AFL. +interchangeably between libFuzzer and AFL. See https://llvm.org/docs/LibFuzzer.html#running on how to run the libFuzzer instrumented executable. diff --git a/doc/release-notes.md b/doc/release-notes.md index 53b5a2119f..9e04f11635 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -111,6 +111,14 @@ Configuration option changes ambiguous whether the hash character is meant for the password or as a comment. +- The `whitelistforcerelay` option is used to relay transactions from + whitelisted peers even when not accepted to the mempool. This option now + defaults to being off, so that changes in policy and disconnect/ban behavior + will not cause a node that is whitelisting another to be dropped by peers. + Users can still explicitly enable this behavior with the command line option + (and may want to consider letting the Bitcoin Core project know about their + use-case, as this feature could be deprecated in the future). + Documentation ------------- @@ -262,6 +270,12 @@ Graphical User Interface (GUI) balance shown if the wallet was created using the `createwallet` RPC and the `disable_private_keys` parameter was set to true. +- The launch-on-startup option is no longer available on macOS if + compiled with macosx min version greater than 10.11 (use + CXXFLAGS="-mmacosx-version-min=10.11" + CFLAGS="-mmacosx-version-min=10.11" for setting the deployment + sdk version) + Low-level changes ================= diff --git a/src/Makefile.am b/src/Makefile.am index 4a98a2c1d8..efac2b4695 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -96,6 +96,7 @@ BITCOIN_CORE_H = \ addrdb.h \ addrman.h \ attributes.h \ + banman.h \ base58.h \ bech32.h \ bloom.h \ @@ -226,6 +227,7 @@ libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ addrdb.cpp \ addrman.cpp \ + banman.cpp \ bloom.cpp \ blockencodings.cpp \ blockfilter.cpp \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index f4f84e2a99..ba6523d7c2 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -157,6 +157,7 @@ QT_MOC_CPP = \ qt/moc_transactiontablemodel.cpp \ qt/moc_transactionview.cpp \ qt/moc_utilitydialog.cpp \ + qt/moc_walletcontroller.cpp \ qt/moc_walletframe.cpp \ qt/moc_walletmodel.cpp \ qt/moc_walletview.cpp @@ -237,6 +238,7 @@ BITCOIN_QT_H = \ qt/transactiontablemodel.h \ qt/transactionview.h \ qt/utilitydialog.h \ + qt/walletcontroller.h \ qt/walletframe.h \ qt/walletmodel.h \ qt/walletmodeltransaction.h \ @@ -350,6 +352,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/transactionrecord.cpp \ qt/transactiontablemodel.cpp \ qt/transactionview.cpp \ + qt/walletcontroller.cpp \ qt/walletframe.cpp \ qt/walletmodel.cpp \ qt/walletmodeltransaction.cpp \ diff --git a/src/addrdb.cpp b/src/addrdb.cpp index 1590bce074..c6083f5554 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -105,19 +105,18 @@ bool DeserializeFileDB(const fs::path& path, Data& data) } -CBanDB::CBanDB() +CBanDB::CBanDB(fs::path ban_list_path) : m_ban_list_path(std::move(ban_list_path)) { - pathBanlist = GetDataDir() / "banlist.dat"; } bool CBanDB::Write(const banmap_t& banSet) { - return SerializeFileDB("banlist", pathBanlist, banSet); + return SerializeFileDB("banlist", m_ban_list_path, banSet); } bool CBanDB::Read(banmap_t& banSet) { - return DeserializeFileDB(pathBanlist, banSet); + return DeserializeFileDB(m_ban_list_path, banSet); } CAddrDB::CAddrDB() diff --git a/src/addrdb.h b/src/addrdb.h index 90eca44bdb..290b63dd12 100644 --- a/src/addrdb.h +++ b/src/addrdb.h @@ -43,6 +43,11 @@ public: nCreateTime = nCreateTimeIn; } + explicit CBanEntry(int64_t n_create_time_in, BanReason ban_reason_in) : CBanEntry(n_create_time_in) + { + banReason = ban_reason_in; + } + ADD_SERIALIZE_METHODS; template <typename Stream, typename Operation> @@ -92,9 +97,9 @@ public: class CBanDB { private: - fs::path pathBanlist; + const fs::path m_ban_list_path; public: - CBanDB(); + explicit CBanDB(fs::path ban_list_path); bool Write(const banmap_t& banSet); bool Read(banmap_t& banSet); }; diff --git a/src/banman.cpp b/src/banman.cpp new file mode 100644 index 0000000000..47d64a8f31 --- /dev/null +++ b/src/banman.cpp @@ -0,0 +1,220 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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 <banman.h> + +#include <netaddress.h> +#include <ui_interface.h> +#include <util/system.h> +#include <util/time.h> + + +BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time) + : m_client_interface(client_interface), m_ban_db(std::move(ban_file)), m_default_ban_time(default_ban_time) +{ + if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist...")); + + int64_t n_start = GetTimeMillis(); + m_is_dirty = false; + banmap_t banmap; + if (m_ban_db.Read(banmap)) { + SetBanned(banmap); // thread save setter + SetBannedSetDirty(false); // no need to write down, just read data + SweepBanned(); // sweep out unused entries + + LogPrint(BCLog::NET, "Loaded %d banned node ips/subnets from banlist.dat %dms\n", + banmap.size(), GetTimeMillis() - n_start); + } else { + LogPrintf("Invalid or missing banlist.dat; recreating\n"); + SetBannedSetDirty(true); // force write + DumpBanlist(); + } +} + +BanMan::~BanMan() +{ + DumpBanlist(); +} + +void BanMan::DumpBanlist() +{ + SweepBanned(); // clean unused entries (if bantime has expired) + + if (!BannedSetIsDirty()) return; + + int64_t n_start = GetTimeMillis(); + + banmap_t banmap; + GetBanned(banmap); + if (m_ban_db.Write(banmap)) { + SetBannedSetDirty(false); + } + + LogPrint(BCLog::NET, "Flushed %d banned node ips/subnets to banlist.dat %dms\n", + banmap.size(), GetTimeMillis() - n_start); +} + +void BanMan::ClearBanned() +{ + { + LOCK(m_cs_banned); + m_banned.clear(); + m_is_dirty = true; + } + DumpBanlist(); //store banlist to disk + if (m_client_interface) m_client_interface->BannedListChanged(); +} + +int BanMan::IsBannedLevel(CNetAddr net_addr) +{ + // Returns the most severe level of banning that applies to this address. + // 0 - Not banned + // 1 - Automatic misbehavior ban + // 2 - Any other ban + int level = 0; + auto current_time = GetTime(); + LOCK(m_cs_banned); + for (const auto& it : m_banned) { + CSubNet sub_net = it.first; + CBanEntry ban_entry = it.second; + + if (current_time < ban_entry.nBanUntil && sub_net.Match(net_addr)) { + if (ban_entry.banReason != BanReasonNodeMisbehaving) return 2; + level = 1; + } + } + return level; +} + +bool BanMan::IsBanned(CNetAddr net_addr) +{ + auto current_time = GetTime(); + LOCK(m_cs_banned); + for (const auto& it : m_banned) { + CSubNet sub_net = it.first; + CBanEntry ban_entry = it.second; + + if (current_time < ban_entry.nBanUntil && sub_net.Match(net_addr)) { + return true; + } + } + return false; +} + +bool BanMan::IsBanned(CSubNet sub_net) +{ + auto current_time = GetTime(); + LOCK(m_cs_banned); + banmap_t::iterator i = m_banned.find(sub_net); + if (i != m_banned.end()) { + CBanEntry ban_entry = (*i).second; + if (current_time < ban_entry.nBanUntil) { + return true; + } + } + return false; +} + +void BanMan::Ban(const CNetAddr& net_addr, const BanReason& ban_reason, int64_t ban_time_offset, bool since_unix_epoch) +{ + CSubNet sub_net(net_addr); + Ban(sub_net, ban_reason, ban_time_offset, since_unix_epoch); +} + +void BanMan::Ban(const CSubNet& sub_net, const BanReason& ban_reason, int64_t ban_time_offset, bool since_unix_epoch) +{ + CBanEntry ban_entry(GetTime(), ban_reason); + + int64_t normalized_ban_time_offset = ban_time_offset; + bool normalized_since_unix_epoch = since_unix_epoch; + if (ban_time_offset <= 0) { + normalized_ban_time_offset = m_default_ban_time; + normalized_since_unix_epoch = false; + } + ban_entry.nBanUntil = (normalized_since_unix_epoch ? 0 : GetTime()) + normalized_ban_time_offset; + + { + LOCK(m_cs_banned); + if (m_banned[sub_net].nBanUntil < ban_entry.nBanUntil) { + m_banned[sub_net] = ban_entry; + m_is_dirty = true; + } else + return; + } + if (m_client_interface) m_client_interface->BannedListChanged(); + + //store banlist to disk immediately if user requested ban + if (ban_reason == BanReasonManuallyAdded) DumpBanlist(); +} + +bool BanMan::Unban(const CNetAddr& net_addr) +{ + CSubNet sub_net(net_addr); + return Unban(sub_net); +} + +bool BanMan::Unban(const CSubNet& sub_net) +{ + { + LOCK(m_cs_banned); + if (m_banned.erase(sub_net) == 0) return false; + m_is_dirty = true; + } + if (m_client_interface) m_client_interface->BannedListChanged(); + DumpBanlist(); //store banlist to disk immediately + return true; +} + +void BanMan::GetBanned(banmap_t& banmap) +{ + LOCK(m_cs_banned); + // Sweep the banlist so expired bans are not returned + SweepBanned(); + banmap = m_banned; //create a thread safe copy +} + +void BanMan::SetBanned(const banmap_t& banmap) +{ + LOCK(m_cs_banned); + m_banned = banmap; + m_is_dirty = true; +} + +void BanMan::SweepBanned() +{ + int64_t now = GetTime(); + bool notify_ui = false; + { + LOCK(m_cs_banned); + banmap_t::iterator it = m_banned.begin(); + while (it != m_banned.end()) { + CSubNet sub_net = (*it).first; + CBanEntry ban_entry = (*it).second; + if (now > ban_entry.nBanUntil) { + m_banned.erase(it++); + m_is_dirty = true; + notify_ui = true; + LogPrint(BCLog::NET, "%s: Removed banned node ip/subnet from banlist.dat: %s\n", __func__, sub_net.ToString()); + } else + ++it; + } + } + // update UI + if (notify_ui && m_client_interface) { + m_client_interface->BannedListChanged(); + } +} + +bool BanMan::BannedSetIsDirty() +{ + LOCK(m_cs_banned); + return m_is_dirty; +} + +void BanMan::SetBannedSetDirty(bool dirty) +{ + LOCK(m_cs_banned); //reuse m_banned lock for the m_is_dirty flag + m_is_dirty = dirty; +} diff --git a/src/banman.h b/src/banman.h new file mode 100644 index 0000000000..a1a00309dd --- /dev/null +++ b/src/banman.h @@ -0,0 +1,70 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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_BANMAN_H +#define BITCOIN_BANMAN_H + +#include <cstdint> +#include <memory> + +#include <addrdb.h> +#include <fs.h> +#include <sync.h> + +// NOTE: When adjusting this, update rpcnet:setban's help ("24h") +static constexpr unsigned int DEFAULT_MISBEHAVING_BANTIME = 60 * 60 * 24; // Default 24-hour ban + +class CClientUIInterface; +class CNetAddr; +class CSubNet; + +// Denial-of-service detection/prevention +// The idea is to detect peers that are behaving +// badly and disconnect/ban them, but do it in a +// one-coding-mistake-won't-shatter-the-entire-network +// way. +// IMPORTANT: There should be nothing I can give a +// node that it will forward on that will make that +// node's peers drop it. If there is, an attacker +// can isolate a node and/or try to split the network. +// Dropping a node for sending stuff that is invalid +// now but might be valid in a later version is also +// dangerous, because it can cause a network split +// between nodes running old code and nodes running +// new code. + +class BanMan +{ +public: + ~BanMan(); + BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time); + void Ban(const CNetAddr& net_addr, const BanReason& ban_reason, int64_t ban_time_offset = 0, bool since_unix_epoch = false); + void Ban(const CSubNet& sub_net, const BanReason& ban_reason, int64_t ban_time_offset = 0, bool since_unix_epoch = false); + void ClearBanned(); + int IsBannedLevel(CNetAddr net_addr); + bool IsBanned(CNetAddr net_addr); + bool IsBanned(CSubNet sub_net); + bool Unban(const CNetAddr& net_addr); + bool Unban(const CSubNet& sub_net); + void GetBanned(banmap_t& banmap); + void DumpBanlist(); + +private: + void SetBanned(const banmap_t& banmap); + bool BannedSetIsDirty(); + //!set the "dirty" flag for the banlist + void SetBannedSetDirty(bool dirty = true); + //!clean unused entries (if bantime has expired) + void SweepBanned(); + + CCriticalSection m_cs_banned; + banmap_t m_banned GUARDED_BY(m_cs_banned); + bool m_is_dirty GUARDED_BY(m_cs_banned); + CClientUIInterface* m_client_interface = nullptr; + CBanDB m_ban_db; + const int64_t m_default_ban_time; +}; + +extern std::unique_ptr<BanMan> g_banman; +#endif diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index 32faba86b4..b804a84478 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -6,7 +6,6 @@ #include <crypto/sha256.h> #include <key.h> -#include <random.h> #include <util/system.h> #include <util/strencodings.h> #include <validation.h> @@ -67,7 +66,6 @@ int main(int argc, char** argv) const fs::path bench_datadir{SetDataDir()}; SHA256AutoDetect(); - RandomInit(); ECC_Start(); SetupEnvironment(); diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp index 49ea6e88b5..ac8a182358 100644 --- a/src/bench/mempool_eviction.cpp +++ b/src/bench/mempool_eviction.cpp @@ -9,7 +9,7 @@ #include <list> #include <vector> -static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) +static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) { int64_t nTime = 0; unsigned int nHeight = 1; @@ -108,7 +108,7 @@ static void MempoolEviction(benchmark::State& state) tx7.vout[1].nValue = 10 * COIN; CTxMemPool pool; - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); // Create transaction references outside the "hot loop" const CTransactionRef tx1_r{MakeTransactionRef(tx1)}; const CTransactionRef tx2_r{MakeTransactionRef(tx2)}; diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 2e41adc276..7c0c674a00 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -818,7 +818,7 @@ static int CommandLineRawTx(int argc, char* argv[]) MutateTx(tx, key, value); } - OutputTx(tx); + OutputTx(CTransaction(tx)); } catch (const std::exception& e) { strPrint = std::string("error: ") + e.what(); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index da4832dff8..d3972fdb89 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -127,6 +127,7 @@ public: vSeeds.emplace_back("seed.bitcoin.jonasschnelli.ch"); // Jonas Schnelli, only supports x1, x5, x9, and xd vSeeds.emplace_back("seed.btc.petertodd.org"); // Peter Todd, only supports x1, x5, x9, and xd vSeeds.emplace_back("seed.bitcoin.sprovoost.nl"); // Sjors Provoost + vSeeds.emplace_back("dnsseed.emzy.de"); // Stephan Oeste base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,0); base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,5); diff --git a/src/coins.h b/src/coins.h index 94493453f0..d39ebf9062 100644 --- a/src/coins.h +++ b/src/coins.h @@ -298,17 +298,17 @@ private: }; //! Utility function to add all of a transaction's outputs to a cache. -// When check is false, this assumes that overwrites are only possible for coinbase transactions. -// When check is true, the underlying view may be queried to determine whether an addition is -// an overwrite. +//! When check is false, this assumes that overwrites are only possible for coinbase transactions. +//! When check is true, the underlying view may be queried to determine whether an addition is +//! an overwrite. // TODO: pass in a boolean to limit these possible overwrites to known // (pre-BIP34) cases. void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight, bool check = false); //! Utility function to find any unspent output with a given txid. -// This function can be quite expensive because in the event of a transaction -// which is not found in the cache, it can cause up to MAX_OUTPUTS_PER_BLOCK -// lookups to database, so it should be used with care. +//! This function can be quite expensive because in the event of a transaction +//! which is not found in the cache, it can cause up to MAX_OUTPUTS_PER_BLOCK +//! lookups to database, so it should be used with care. const Coin& AccessByTxid(const CCoinsViewCache& cache, const uint256& txid); #endif // BITCOIN_COINS_H diff --git a/src/crypto/sha512.h b/src/crypto/sha512.h index cd1023bc85..4118ac1b18 100644 --- a/src/crypto/sha512.h +++ b/src/crypto/sha512.h @@ -17,7 +17,7 @@ private: uint64_t bytes; public: - static const size_t OUTPUT_SIZE = 64; + static constexpr size_t OUTPUT_SIZE = 64; CSHA512(); CSHA512& Write(const unsigned char* data, size_t len); diff --git a/src/init.cpp b/src/init.cpp index 679bf80047..77d0505610 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -11,6 +11,7 @@ #include <addrman.h> #include <amount.h> +#include <banman.h> #include <chain.h> #include <chainparams.h> #include <checkpoints.h> @@ -73,8 +74,12 @@ static const bool DEFAULT_PROXYRANDOMIZE = true; static const bool DEFAULT_REST_ENABLE = false; static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false; +// Dump addresses to banlist.dat every 15 minutes (900s) +static constexpr int DUMP_BANS_INTERVAL = 60 * 15; + std::unique_ptr<CConnman> g_connman; std::unique_ptr<PeerLogicValidation> peerLogic; +std::unique_ptr<BanMan> g_banman; #ifdef WIN32 // Win32 LevelDB doesn't use filedescriptors, and the ones used for @@ -199,6 +204,7 @@ void Shutdown(InitInterfaces& interfaces) // destruct and reset all to nullptr. peerLogic.reset(); g_connman.reset(); + g_banman.reset(); g_txindex.reset(); if (g_is_mempool_loaded && gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { @@ -930,7 +936,7 @@ bool AppInitParameterInteraction() // also see: InitParameterInteraction() - if (!fs::is_directory(GetBlocksDir(false))) { + if (!fs::is_directory(GetBlocksDir())) { return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist."), gArgs.GetArg("-blocksdir", "").c_str())); } @@ -1290,11 +1296,12 @@ bool AppInitMain(InitInterfaces& interfaces) // is not yet setup and may end up being set up twice if we // need to reindex later. + assert(!g_banman); + g_banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); assert(!g_connman); g_connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()))); - CConnman& connman = *g_connman; - peerLogic.reset(new PeerLogicValidation(&connman, scheduler, gArgs.GetBoolArg("-enablebip61", DEFAULT_ENABLE_BIP61))); + peerLogic.reset(new PeerLogicValidation(g_connman.get(), g_banman.get(), scheduler, gArgs.GetBoolArg("-enablebip61", DEFAULT_ENABLE_BIP61))); RegisterValidationInterface(peerLogic.get()); // sanitize comments per BIP-0014, format user agent and check total size @@ -1704,6 +1711,7 @@ bool AppInitMain(InitInterfaces& interfaces) connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chain_active_height; connOptions.uiInterface = &uiInterface; + connOptions.m_banman = g_banman.get(); connOptions.m_msgproc = peerLogic.get(); connOptions.nSendBufferMaxSize = 1000*gArgs.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); connOptions.nReceiveFloodSize = 1000*gArgs.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); @@ -1749,7 +1757,7 @@ bool AppInitMain(InitInterfaces& interfaces) connOptions.m_specified_outgoing = connect; } } - if (!connman.Start(scheduler, connOptions)) { + if (!g_connman->Start(scheduler, connOptions)) { return false; } @@ -1762,5 +1770,9 @@ bool AppInitMain(InitInterfaces& interfaces) client->start(scheduler); } + scheduler.scheduleEvery([]{ + g_banman->DumpBanlist(); + }, DUMP_BANS_INTERVAL * 1000); + return true; } diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index acba05fd5e..c574f960e6 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -6,6 +6,7 @@ #include <addrdb.h> #include <amount.h> +#include <banman.h> #include <chain.h> #include <chainparams.h> #include <init.h> @@ -122,28 +123,35 @@ public: } bool getBanned(banmap_t& banmap) override { - if (g_connman) { - g_connman->GetBanned(banmap); + if (g_banman) { + g_banman->GetBanned(banmap); return true; } return false; } bool ban(const CNetAddr& net_addr, BanReason reason, int64_t ban_time_offset) override { - if (g_connman) { - g_connman->Ban(net_addr, reason, ban_time_offset); + if (g_banman) { + g_banman->Ban(net_addr, reason, ban_time_offset); return true; } return false; } bool unban(const CSubNet& ip) override { - if (g_connman) { - g_connman->Unban(ip); + if (g_banman) { + g_banman->Unban(ip); return true; } return false; } + bool disconnect(const CNetAddr& net_addr) override + { + if (g_connman) { + return g_connman->DisconnectNode(net_addr); + } + return false; + } bool disconnect(NodeId id) override { if (g_connman) { diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 7fa5958c51..54c2d78338 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -18,6 +18,7 @@ #include <tuple> #include <vector> +class BanMan; class CCoinControl; class CFeeRate; class CNodeStats; @@ -113,7 +114,10 @@ public: //! Unban node. virtual bool unban(const CSubNet& ip) = 0; - //! Disconnect node. + //! Disconnect node by address. + virtual bool disconnect(const CNetAddr& net_addr) = 0; + + //! Disconnect node by id. virtual bool disconnect(NodeId id) = 0; //! Get total bytes recv. diff --git a/src/net.cpp b/src/net.cpp index 98bd518ecc..be249b4466 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -9,6 +9,7 @@ #include <net.h> +#include <banman.h> #include <chainparams.h> #include <clientversion.h> #include <consensus/consensus.h> @@ -41,8 +42,8 @@ #include <math.h> -// Dump addresses to peers.dat and banlist.dat every 15 minutes (900s) -#define DUMP_ADDRESSES_INTERVAL 900 +// Dump addresses to peers.dat every 15 minutes (900s) +static constexpr int DUMP_PEERS_INTERVAL = 15 * 60; // We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization. #define FEELER_SLEEP_WINDOW 1 @@ -457,26 +458,6 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo return pnode; } -void CConnman::DumpBanlist() -{ - SweepBanned(); // clean unused entries (if bantime has expired) - - if (!BannedSetIsDirty()) - return; - - int64_t nStart = GetTimeMillis(); - - CBanDB bandb; - banmap_t banmap; - GetBanned(banmap); - if (bandb.Write(banmap)) { - SetBannedSetDirty(false); - } - - LogPrint(BCLog::NET, "Flushed %d banned node ips/subnets to banlist.dat %dms\n", - banmap.size(), GetTimeMillis() - nStart); -} - void CNode::CloseSocketDisconnect() { fDisconnect = true; @@ -488,157 +469,6 @@ void CNode::CloseSocketDisconnect() } } -void CConnman::ClearBanned() -{ - { - LOCK(cs_setBanned); - setBanned.clear(); - setBannedIsDirty = true; - } - DumpBanlist(); //store banlist to disk - if(clientInterface) - clientInterface->BannedListChanged(); -} - -bool CConnman::IsBanned(CNetAddr ip) -{ - LOCK(cs_setBanned); - for (const auto& it : setBanned) { - CSubNet subNet = it.first; - CBanEntry banEntry = it.second; - - if (subNet.Match(ip) && GetTime() < banEntry.nBanUntil) { - return true; - } - } - return false; -} - -bool CConnman::IsBanned(CSubNet subnet) -{ - LOCK(cs_setBanned); - banmap_t::iterator i = setBanned.find(subnet); - if (i != setBanned.end()) - { - CBanEntry banEntry = (*i).second; - if (GetTime() < banEntry.nBanUntil) { - return true; - } - } - return false; -} - -void CConnman::Ban(const CNetAddr& addr, const BanReason &banReason, int64_t bantimeoffset, bool sinceUnixEpoch) { - CSubNet subNet(addr); - Ban(subNet, banReason, bantimeoffset, sinceUnixEpoch); -} - -void CConnman::Ban(const CSubNet& subNet, const BanReason &banReason, int64_t bantimeoffset, bool sinceUnixEpoch) { - CBanEntry banEntry(GetTime()); - banEntry.banReason = banReason; - if (bantimeoffset <= 0) - { - bantimeoffset = gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME); - sinceUnixEpoch = false; - } - banEntry.nBanUntil = (sinceUnixEpoch ? 0 : GetTime() )+bantimeoffset; - - { - LOCK(cs_setBanned); - if (setBanned[subNet].nBanUntil < banEntry.nBanUntil) { - setBanned[subNet] = banEntry; - setBannedIsDirty = true; - } - else - return; - } - if(clientInterface) - clientInterface->BannedListChanged(); - { - LOCK(cs_vNodes); - for (CNode* pnode : vNodes) { - if (subNet.Match(static_cast<CNetAddr>(pnode->addr))) - pnode->fDisconnect = true; - } - } - if(banReason == BanReasonManuallyAdded) - DumpBanlist(); //store banlist to disk immediately if user requested ban -} - -bool CConnman::Unban(const CNetAddr &addr) { - CSubNet subNet(addr); - return Unban(subNet); -} - -bool CConnman::Unban(const CSubNet &subNet) { - { - LOCK(cs_setBanned); - if (!setBanned.erase(subNet)) - return false; - setBannedIsDirty = true; - } - if(clientInterface) - clientInterface->BannedListChanged(); - DumpBanlist(); //store banlist to disk immediately - return true; -} - -void CConnman::GetBanned(banmap_t &banMap) -{ - LOCK(cs_setBanned); - // Sweep the banlist so expired bans are not returned - SweepBanned(); - banMap = setBanned; //create a thread safe copy -} - -void CConnman::SetBanned(const banmap_t &banMap) -{ - LOCK(cs_setBanned); - setBanned = banMap; - setBannedIsDirty = true; -} - -void CConnman::SweepBanned() -{ - int64_t now = GetTime(); - bool notifyUI = false; - { - LOCK(cs_setBanned); - banmap_t::iterator it = setBanned.begin(); - while(it != setBanned.end()) - { - CSubNet subNet = (*it).first; - CBanEntry banEntry = (*it).second; - if(now > banEntry.nBanUntil) - { - setBanned.erase(it++); - setBannedIsDirty = true; - notifyUI = true; - LogPrint(BCLog::NET, "%s: Removed banned node ip/subnet from banlist.dat: %s\n", __func__, subNet.ToString()); - } - else - ++it; - } - } - // update UI - if(notifyUI && clientInterface) { - clientInterface->BannedListChanged(); - } -} - -bool CConnman::BannedSetIsDirty() -{ - LOCK(cs_setBanned); - return setBannedIsDirty; -} - -void CConnman::SetBannedSetDirty(bool dirty) -{ - LOCK(cs_setBanned); //reuse setBanned lock for the isDirty flag - setBannedIsDirty = dirty; -} - - bool CConnman::IsWhitelistedRange(const CNetAddr &addr) { for (const CSubNet& subnet : vWhitelistedRange) { if (subnet.Match(addr)) @@ -934,6 +764,7 @@ struct NodeEvictionCandidate bool fBloomFilter; CAddress addr; uint64_t nKeyedNetGroup; + bool prefer_evict; }; static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) @@ -1002,7 +833,8 @@ bool CConnman::AttemptToEvictConnection() NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->nMinPingUsecTime, node->nLastBlockTime, node->nLastTXTime, HasAllDesirableServiceFlags(node->nServices), - node->fRelayTxes, node->pfilter != nullptr, node->addr, node->nKeyedNetGroup}; + node->fRelayTxes, node->pfilter != nullptr, node->addr, node->nKeyedNetGroup, + node->m_prefer_evict}; vEvictionCandidates.push_back(candidate); } } @@ -1027,6 +859,14 @@ bool CConnman::AttemptToEvictConnection() if (vEvictionCandidates.empty()) return false; + // If any remaining peers are preferred for eviction consider only them. + // This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks) + // then we probably don't want to evict it no matter what. + if (std::any_of(vEvictionCandidates.begin(),vEvictionCandidates.end(),[](NodeEvictionCandidate const &n){return n.prefer_evict;})) { + vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.begin(),vEvictionCandidates.end(), + [](NodeEvictionCandidate const &n){return !n.prefer_evict;}),vEvictionCandidates.end()); + } + // Identify the network group with the most connections and youngest member. // (vEvictionCandidates is already sorted by reverse connect time) uint64_t naMostConnections; @@ -1107,7 +947,11 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { // on all platforms. Set it again here just to be sure. SetSocketNoDelay(hSocket); - if (IsBanned(addr) && !whitelisted) + int bannedlevel = m_banman ? m_banman->IsBannedLevel(addr) : 0; + + // Don't accept connections from banned peers, but if our inbound slots aren't almost full, accept + // if the only banning reason was an automatic misbehavior ban. + if (!whitelisted && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0)) { LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString()); CloseSocket(hSocket); @@ -1131,6 +975,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true); pnode->AddRef(); pnode->fWhitelisted = whitelisted; + pnode->m_prefer_evict = bannedlevel > 0; m_msgproc->InitializeNode(pnode); LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString()); @@ -1775,12 +1620,6 @@ void CConnman::DumpAddresses() addrman.size(), GetTimeMillis() - nStart); } -void CConnman::DumpData() -{ - DumpAddresses(); - DumpBanlist(); -} - void CConnman::ProcessOneShot() { std::string strDest; @@ -2085,7 +1924,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai } if (!pszDest) { if (IsLocal(addrConnect) || - FindNode(static_cast<CNetAddr>(addrConnect)) || IsBanned(addrConnect) || + FindNode(static_cast<CNetAddr>(addrConnect)) || (m_banman && m_banman->IsBanned(addrConnect)) || FindNode(addrConnect.ToStringIPPort())) return; } else if (FindNode(std::string(pszDest))) @@ -2386,24 +2225,6 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) DumpAddresses(); } } - if (clientInterface) - clientInterface->InitMessage(_("Loading banlist...")); - // Load addresses from banlist.dat - nStart = GetTimeMillis(); - CBanDB bandb; - banmap_t banmap; - if (bandb.Read(banmap)) { - SetBanned(banmap); // thread save setter - SetBannedSetDirty(false); // no need to write down, just read data - SweepBanned(); // sweep out unused entries - - LogPrint(BCLog::NET, "Loaded %d banned node ips/subnets from banlist.dat %dms\n", - banmap.size(), GetTimeMillis() - nStart); - } else { - LogPrintf("Invalid or missing banlist.dat; recreating\n"); - SetBannedSetDirty(true); // force write - DumpBanlist(); - } uiInterface.InitMessage(_("Starting network threads...")); @@ -2457,7 +2278,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) threadMessageHandler = std::thread(&TraceThread<std::function<void()> >, "msghand", std::function<void()>(std::bind(&CConnman::ThreadMessageHandler, this))); // Dump network addresses - scheduler.scheduleEvery(std::bind(&CConnman::DumpData, this), DUMP_ADDRESSES_INTERVAL * 1000); + scheduler.scheduleEvery(std::bind(&CConnman::DumpAddresses, this), DUMP_PEERS_INTERVAL * 1000); return true; } @@ -2516,7 +2337,7 @@ void CConnman::Stop() if (fAddressesInitialized) { - DumpData(); + DumpAddresses(); fAddressesInitialized = false; } @@ -2643,6 +2464,25 @@ bool CConnman::DisconnectNode(const std::string& strNode) } return false; } + +bool CConnman::DisconnectNode(const CSubNet& subnet) +{ + bool disconnected = false; + LOCK(cs_vNodes); + for (CNode* pnode : vNodes) { + if (subnet.Match(pnode->addr)) { + pnode->fDisconnect = true; + disconnected = true; + } + } + return disconnected; +} + +bool CConnman::DisconnectNode(const CNetAddr& addr) +{ + return DisconnectNode(CSubNet(addr)); +} + bool CConnman::DisconnectNode(NodeId id) { LOCK(cs_vNodes); @@ -37,6 +37,7 @@ class CScheduler; class CNode; +class BanMan; /** Time between pings automatically sent out for latency probing and keepalive (in seconds). */ static const int PING_INTERVAL = 2 * 60; @@ -85,9 +86,6 @@ static const bool DEFAULT_FORCEDNSSEED = false; static const size_t DEFAULT_MAXRECEIVEBUFFER = 5 * 1000; static const size_t DEFAULT_MAXSENDBUFFER = 1 * 1000; -// NOTE: When adjusting this, update rpcnet:setban's help ("24h") -static const unsigned int DEFAULT_MISBEHAVING_BANTIME = 60 * 60 * 24; // Default 24-hour ban - typedef int64_t NodeId; struct AddedNodeInfo @@ -114,6 +112,7 @@ struct CSerializedNetMsg std::string command; }; + class NetEventsInterface; class CConnman { @@ -136,6 +135,7 @@ public: int nBestHeight = 0; CClientUIInterface* uiInterface = nullptr; NetEventsInterface* m_msgproc = nullptr; + BanMan* m_banman = nullptr; unsigned int nSendBufferMaxSize = 0; unsigned int nReceiveFloodSize = 0; uint64_t nMaxOutboundTimeframe = 0; @@ -158,6 +158,7 @@ public: nMaxFeeler = connOptions.nMaxFeeler; nBestHeight = connOptions.nBestHeight; clientInterface = connOptions.uiInterface; + m_banman = connOptions.m_banman; m_msgproc = connOptions.m_msgproc; nSendBufferMaxSize = connOptions.nSendBufferMaxSize; nReceiveFloodSize = connOptions.nReceiveFloodSize; @@ -238,30 +239,6 @@ public: void AddNewAddresses(const std::vector<CAddress>& vAddr, const CAddress& addrFrom, int64_t nTimePenalty = 0); std::vector<CAddress> GetAddresses(); - // Denial-of-service detection/prevention - // The idea is to detect peers that are behaving - // badly and disconnect/ban them, but do it in a - // one-coding-mistake-won't-shatter-the-entire-network - // way. - // IMPORTANT: There should be nothing I can give a - // node that it will forward on that will make that - // node's peers drop it. If there is, an attacker - // can isolate a node and/or try to split the network. - // Dropping a node for sending stuff that is invalid - // now but might be valid in a later version is also - // dangerous, because it can cause a network split - // between nodes running old code and nodes running - // new code. - void Ban(const CNetAddr& netAddr, const BanReason& reason, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false); - void Ban(const CSubNet& subNet, const BanReason& reason, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false); - void ClearBanned(); // needed for unit testing - bool IsBanned(CNetAddr ip); - bool IsBanned(CSubNet subnet); - bool Unban(const CNetAddr &ip); - bool Unban(const CSubNet &ip); - void GetBanned(banmap_t &banmap); - void SetBanned(const banmap_t &banmap); - // This allows temporarily exceeding nMaxOutbound, with the goal of finding // a peer that is better than all our current peers. void SetTryNewOutboundPeer(bool flag); @@ -282,6 +259,8 @@ public: size_t GetNodeCount(NumConnections num); void GetNodeStats(std::vector<CNodeStats>& vstats); bool DisconnectNode(const std::string& node); + bool DisconnectNode(const CSubNet& subnet); + bool DisconnectNode(const CNetAddr& addr); bool DisconnectNode(NodeId id); ServiceFlags GetLocalServices() const; @@ -294,17 +273,17 @@ public: void SetMaxOutboundTimeframe(uint64_t timeframe); uint64_t GetMaxOutboundTimeframe(); - //!check if the outbound target is reached - // if param historicalBlockServingLimit is set true, the function will - // response true if the limit for serving historical blocks has been reached + //! check if the outbound target is reached + //! if param historicalBlockServingLimit is set true, the function will + //! response true if the limit for serving historical blocks has been reached bool OutboundTargetReached(bool historicalBlockServingLimit); - //!response the bytes left in the current max outbound cycle - // in case of no limit, it will always response 0 + //! response the bytes left in the current max outbound cycle + //! in case of no limit, it will always response 0 uint64_t GetOutboundTargetBytesLeft(); - //!response the time in second left in the current max outbound cycle - // in case of no limit, it will always response 0 + //! response the time in second left in the current max outbound cycle + //! in case of no limit, it will always response 0 uint64_t GetMaxOutboundTimeLeftInCycle(); uint64_t GetTotalBytesRecv(); @@ -368,15 +347,7 @@ private: NodeId GetNewNodeId(); size_t SocketSendData(CNode *pnode) const; - //!check is the banlist has unwritten changes - bool BannedSetIsDirty(); - //!set the "dirty" flag for the banlist - void SetBannedSetDirty(bool dirty=true); - //!clean unused entries (if bantime has expired) - void SweepBanned(); void DumpAddresses(); - void DumpData(); - void DumpBanlist(); // Network stats void RecordBytesRecv(uint64_t bytes); @@ -409,9 +380,6 @@ private: std::vector<ListenSocket> vhListenSocket; std::atomic<bool> fNetworkActive{true}; - banmap_t setBanned GUARDED_BY(cs_setBanned); - CCriticalSection cs_setBanned; - bool setBannedIsDirty GUARDED_BY(cs_setBanned){false}; bool fAddressesInitialized{false}; CAddrMan addrman; std::deque<std::string> vOneShots GUARDED_BY(cs_vOneShots); @@ -437,6 +405,7 @@ private: std::atomic<int> nBestHeight; CClientUIInterface* clientInterface; NetEventsInterface* m_msgproc; + BanMan* m_banman; /** SipHasher seeds for deterministic randomness */ const uint64_t nSeed0, nSeed1; @@ -466,6 +435,7 @@ private: friend struct CConnmanTest; }; extern std::unique_ptr<CConnman> g_connman; +extern std::unique_ptr<BanMan> g_banman; void Discover(); void StartMapPort(); void InterruptMapPort(); @@ -681,6 +651,7 @@ public: // the network or wire types and the cleaned string used when displayed or logged. std::string strSubVer GUARDED_BY(cs_SubVer), cleanSubVer GUARDED_BY(cs_SubVer); CCriticalSection cs_SubVer; // used for both cleanSubVer and strSubVer + bool m_prefer_evict{false}; // This peer is preferred for eviction. bool fWhitelisted{false}; // This peer can bypass DoS banning. bool fFeeler{false}; // If true this node is being used as a short lived feeler. bool fOneShot{false}; @@ -689,6 +660,8 @@ public: bool m_limited_node{false}; //after BIP159, set by version message const bool fInbound; std::atomic_bool fSuccessfullyConnected{false}; + // Setting fDisconnect to true will cause the node to be disconnected the + // next time DisconnectNodes() runs std::atomic_bool fDisconnect{false}; // We use fRelayTxes for two purposes - // a) it allows us to not relay tx invs before receiving the peer's version message diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 0e222bdfa4..62b7d4e966 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -6,6 +6,7 @@ #include <net_processing.h> #include <addrman.h> +#include <banman.h> #include <arith_uint256.h> #include <blockencodings.h> #include <chainparams.h> @@ -841,9 +842,8 @@ static bool BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Para (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT); } -PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, CScheduler &scheduler, bool enable_bip61) - : connman(connmanIn), m_stale_tip_check_time(0), m_enable_bip61(enable_bip61) { - +PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, BanMan* banman, CScheduler &scheduler, bool enable_bip61) + : connman(connmanIn), m_banman(banman), m_stale_tip_check_time(0), m_enable_bip61(enable_bip61) { // Initialize global variables that cannot be constructed at startup. recentRejects.reset(new CRollingBloomFilter(120000, 0.000001)); @@ -2943,7 +2943,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr return true; } -static bool SendRejectsAndCheckIfBanned(CNode* pnode, CConnman* connman, bool enable_bip61) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool PeerLogicValidation::SendRejectsAndCheckIfBanned(CNode* pnode, bool enable_bip61) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); CNodeState &state = *State(pnode->GetId()); @@ -2961,14 +2961,16 @@ static bool SendRejectsAndCheckIfBanned(CNode* pnode, CConnman* connman, bool en LogPrintf("Warning: not punishing whitelisted peer %s!\n", pnode->addr.ToString()); else if (pnode->m_manual_connection) LogPrintf("Warning: not punishing manually-connected peer %s!\n", pnode->addr.ToString()); - else { + else if (pnode->addr.IsLocal()) { + // Disconnect but don't ban _this_ local node + LogPrintf("Warning: disconnecting but not banning local peer %s!\n", pnode->addr.ToString()); pnode->fDisconnect = true; - if (pnode->addr.IsLocal()) - LogPrintf("Warning: not banning local peer %s!\n", pnode->addr.ToString()); - else - { - connman->Ban(pnode->addr, BanReasonNodeMisbehaving); + } else { + // Disconnect and ban all nodes sharing the address + if (m_banman) { + m_banman->Ban(pnode->addr, BanReasonNodeMisbehaving); } + connman->DisconnectNode(pnode->addr); } return true; } @@ -3092,7 +3094,7 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter } LOCK(cs_main); - SendRejectsAndCheckIfBanned(pfrom, connman, m_enable_bip61); + SendRejectsAndCheckIfBanned(pfrom, m_enable_bip61); return fMoreWork; } @@ -3293,8 +3295,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) if (!lockMain) return true; - if (SendRejectsAndCheckIfBanned(pto, connman, m_enable_bip61)) - return true; + if (SendRejectsAndCheckIfBanned(pto, m_enable_bip61)) return true; CNodeState &state = *State(pto->GetId()); // Address refresh broadcast diff --git a/src/net_processing.h b/src/net_processing.h index 0113e25f7e..39c22d7118 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -23,9 +23,11 @@ static constexpr bool DEFAULT_ENABLE_BIP61{false}; class PeerLogicValidation final : public CValidationInterface, public NetEventsInterface { private: CConnman* const connman; + BanMan* const m_banman; + bool SendRejectsAndCheckIfBanned(CNode* pnode, bool enable_bip61) EXCLUSIVE_LOCKS_REQUIRED(cs_main); public: - explicit PeerLogicValidation(CConnman* connman, CScheduler &scheduler, bool enable_bip61); + PeerLogicValidation(CConnman* connman, BanMan* banman, CScheduler &scheduler, bool enable_bip61); /** * Overridden from CValidationInterface. diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index c88d5b1ad3..f6f8e31363 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -298,7 +298,7 @@ public: CTransaction(); /** Convert a CMutableTransaction into a CTransaction. */ - CTransaction(const CMutableTransaction &tx); + explicit CTransaction(const CMutableTransaction &tx); CTransaction(CMutableTransaction &&tx); template <typename Stream> diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 893dda1601..ca26131b95 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -24,7 +24,7 @@ #ifdef ENABLE_WALLET #include <qt/paymentserver.h> -#include <qt/walletmodel.h> +#include <qt/walletcontroller.h> #endif #include <interfaces/handler.h> @@ -184,10 +184,6 @@ BitcoinApplication::BitcoinApplication(interfaces::Node& node, int &argc, char * clientModel(nullptr), window(nullptr), pollShutdownTimer(nullptr), -#ifdef ENABLE_WALLET - paymentServer(nullptr), - m_wallet_models(), -#endif returnValue(0), platformStyle(nullptr) { @@ -212,7 +208,7 @@ BitcoinApplication::~BitcoinApplication() if(coreThread) { qDebug() << __func__ << ": Stopping thread"; - Q_EMIT stopThread(); + coreThread->quit(); coreThread->wait(); qDebug() << __func__ << ": Stopped thread"; } @@ -279,8 +275,7 @@ void BitcoinApplication::startThread() connect(this, &BitcoinApplication::requestedInitialize, executor, &BitcoinCore::initialize); connect(this, &BitcoinApplication::requestedShutdown, executor, &BitcoinCore::shutdown); /* make sure executor object is deleted in its own thread */ - connect(this, &BitcoinApplication::stopThread, executor, &QObject::deleteLater); - connect(this, &BitcoinApplication::stopThread, coreThread, &QThread::quit); + connect(coreThread, &QThread::finished, executor, &QObject::deleteLater); coreThread->start(); } @@ -316,11 +311,8 @@ void BitcoinApplication::requestShutdown() pollShutdownTimer->stop(); #ifdef ENABLE_WALLET - window->removeAllWallets(); - for (const WalletModel* walletModel : m_wallet_models) { - delete walletModel; - } - m_wallet_models.clear(); + delete m_wallet_controller; + m_wallet_controller = nullptr; #endif delete clientModel; clientModel = nullptr; @@ -331,35 +323,6 @@ void BitcoinApplication::requestShutdown() Q_EMIT requestedShutdown(); } -void BitcoinApplication::addWallet(WalletModel* walletModel) -{ -#ifdef ENABLE_WALLET - window->addWallet(walletModel); - - if (m_wallet_models.empty()) { - window->setCurrentWallet(walletModel); - } - -#ifdef ENABLE_BIP70 - connect(walletModel, &WalletModel::coinsSent, - paymentServer, &PaymentServer::fetchPaymentACK); -#endif - connect(walletModel, &WalletModel::unload, this, &BitcoinApplication::removeWallet); - - m_wallet_models.push_back(walletModel); -#endif -} - -void BitcoinApplication::removeWallet() -{ -#ifdef ENABLE_WALLET - WalletModel* walletModel = static_cast<WalletModel*>(sender()); - m_wallet_models.erase(std::find(m_wallet_models.begin(), m_wallet_models.end(), walletModel)); - window->removeWallet(walletModel); - walletModel->deleteLater(); -#endif -} - void BitcoinApplication::initializeResult(bool success) { qDebug() << __func__ << ": Initialization result: " << success; @@ -370,26 +333,22 @@ void BitcoinApplication::initializeResult(bool success) // Log this only after AppInitMain finishes, as then logging setup is guaranteed complete qWarning() << "Platform customization:" << platformStyle->getName(); #ifdef ENABLE_WALLET + m_wallet_controller = new WalletController(m_node, platformStyle, optionsModel, this); #ifdef ENABLE_BIP70 PaymentServer::LoadRootCAs(); #endif - if (paymentServer) paymentServer->setOptionsModel(optionsModel); + if (paymentServer) { + paymentServer->setOptionsModel(optionsModel); +#ifdef ENABLE_BIP70 + connect(m_wallet_controller, &WalletController::coinsSent, paymentServer, &PaymentServer::fetchPaymentACK); +#endif + } #endif clientModel = new ClientModel(m_node, optionsModel); window->setClientModel(clientModel); - #ifdef ENABLE_WALLET - m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) { - WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, platformStyle, optionsModel, nullptr); - // Fix wallet model thread affinity. - wallet_model->moveToThread(thread()); - QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model)); - }); - - for (auto& wallet : m_node.getWallets()) { - addWallet(new WalletModel(std::move(wallet), m_node, platformStyle, optionsModel)); - } + window->setWalletController(m_wallet_controller); #endif // If -min option passed, start window minimized (iconified) or minimized to tray @@ -493,9 +452,6 @@ int GuiMain(int argc, char* argv[]) // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType< CAmount >("CAmount"); qRegisterMetaType< std::function<void()> >("std::function<void()>"); -#ifdef ENABLE_WALLET - qRegisterMetaType<WalletModel*>("WalletModel*"); -#endif /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these // Command-line options take precedence: diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 48b5907570..370712d953 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -19,6 +19,7 @@ class NetworkStyle; class OptionsModel; class PaymentServer; class PlatformStyle; +class WalletController; class WalletModel; namespace interfaces { @@ -93,13 +94,10 @@ public Q_SLOTS: void shutdownResult(); /// Handle runaway exceptions. Shows a message box with the problem and quits the program. void handleRunawayException(const QString &message); - void addWallet(WalletModel* walletModel); - void removeWallet(); Q_SIGNALS: void requestedInitialize(); void requestedShutdown(); - void stopThread(); void splashFinished(); void windowShown(BitcoinGUI* window); @@ -111,9 +109,8 @@ private: BitcoinGUI *window; QTimer *pollShutdownTimer; #ifdef ENABLE_WALLET - PaymentServer* paymentServer; - std::vector<WalletModel*> m_wallet_models; - std::unique_ptr<interfaces::Handler> m_handler_load_wallet; + PaymentServer* paymentServer{nullptr}; + WalletController* m_wallet_controller{nullptr}; #endif int returnValue; const PlatformStyle *platformStyle; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index e51aa02837..ba7e8c7daf 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -19,6 +19,7 @@ #include <qt/utilitydialog.h> #ifdef ENABLE_WALLET +#include <qt/walletcontroller.h> #include <qt/walletframe.h> #include <qt/walletmodel.h> #include <qt/walletview.h> @@ -77,7 +78,8 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty QMainWindow(parent), m_node(node), trayIconMenu{new QMenu()}, - platformStyle(_platformStyle) + platformStyle(_platformStyle), + m_network_style(networkStyle) { QSettings settings; if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) { @@ -85,20 +87,12 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); } - QString windowTitle = tr(PACKAGE_NAME) + " - "; #ifdef ENABLE_WALLET enableWallet = WalletModel::isWalletEnabled(); #endif // ENABLE_WALLET - if(enableWallet) - { - windowTitle += tr("Wallet"); - } else { - windowTitle += tr("Node"); - } - windowTitle += " " + networkStyle->getTitleAddText(); - QApplication::setWindowIcon(networkStyle->getTrayAndWindowIcon()); - setWindowIcon(networkStyle->getTrayAndWindowIcon()); - setWindowTitle(windowTitle); + QApplication::setWindowIcon(m_network_style->getTrayAndWindowIcon()); + setWindowIcon(m_network_style->getTrayAndWindowIcon()); + updateWindowTitle(); rpcConsole = new RPCConsole(node, _platformStyle, nullptr); helpMessageDialog = new HelpMessageDialog(node, this, false); @@ -133,7 +127,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty // Create system tray icon and notification if (QSystemTrayIcon::isSystemTrayAvailable()) { - createTrayIcon(networkStyle); + createTrayIcon(); } notificator = new Notificator(QApplication::applicationName(), trayIcon, this); @@ -490,6 +484,7 @@ void BitcoinGUI::createToolBars() 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(); @@ -572,24 +567,38 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel) } #ifdef ENABLE_WALLET -bool BitcoinGUI::addWallet(WalletModel *walletModel) +void BitcoinGUI::setWalletController(WalletController* wallet_controller) { - if(!walletFrame) - return false; + assert(!m_wallet_controller); + assert(wallet_controller); + + m_wallet_controller = wallet_controller; + + connect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet); + connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet); + + for (WalletModel* wallet_model : m_wallet_controller->getWallets()) { + addWallet(wallet_model); + } +} + +void BitcoinGUI::addWallet(WalletModel* walletModel) +{ + if (!walletFrame) return; const QString display_name = walletModel->getDisplayName(); setWalletActionsEnabled(true); + rpcConsole->addWallet(walletModel); + walletFrame->addWallet(walletModel); m_wallet_selector->addItem(display_name, QVariant::fromValue(walletModel)); if (m_wallet_selector->count() == 2) { m_wallet_selector_label_action->setVisible(true); m_wallet_selector_action->setVisible(true); } - rpcConsole->addWallet(walletModel); - return walletFrame->addWallet(walletModel); } -bool BitcoinGUI::removeWallet(WalletModel* walletModel) +void BitcoinGUI::removeWallet(WalletModel* walletModel) { - if (!walletFrame) return false; + if (!walletFrame) return; int index = m_wallet_selector->findData(QVariant::fromValue(walletModel)); m_wallet_selector->removeItem(index); if (m_wallet_selector->count() == 0) { @@ -599,20 +608,27 @@ bool BitcoinGUI::removeWallet(WalletModel* walletModel) m_wallet_selector_action->setVisible(false); } rpcConsole->removeWallet(walletModel); - return walletFrame->removeWallet(walletModel); + walletFrame->removeWallet(walletModel); + updateWindowTitle(); } -bool BitcoinGUI::setCurrentWallet(WalletModel* wallet_model) +void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model) { - if(!walletFrame) - return false; - return walletFrame->setCurrentWallet(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(); } -bool BitcoinGUI::setCurrentWalletBySelectorIndex(int index) +void BitcoinGUI::setCurrentWalletBySelectorIndex(int index) { WalletModel* wallet_model = m_wallet_selector->itemData(index).value<WalletModel*>(); - return setCurrentWallet(wallet_model); + if (wallet_model) setCurrentWallet(wallet_model); } void BitcoinGUI::removeAllWallets() @@ -642,14 +658,14 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) openAction->setEnabled(enabled); } -void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle) +void BitcoinGUI::createTrayIcon() { assert(QSystemTrayIcon::isSystemTrayAvailable()); #ifndef Q_OS_MAC if (QSystemTrayIcon::isSystemTrayAvailable()) { - trayIcon = new QSystemTrayIcon(networkStyle->getTrayAndWindowIcon(), this); - QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + networkStyle->getTitleAddText(); + trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this); + QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + m_network_style->getTitleAddText(); trayIcon->setToolTip(toolTip); } #endif @@ -1208,6 +1224,23 @@ void BitcoinGUI::updateProxyIcon() } } +void BitcoinGUI::updateWindowTitle() +{ + QString window_title = tr(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) @@ -1237,25 +1270,21 @@ void BitcoinGUI::detectShutdown() void BitcoinGUI::showProgress(const QString &title, int nProgress) { - if (nProgress == 0) - { - progressDialog = new QProgressDialog(title, "", 0, 100); + if (nProgress == 0) { + progressDialog = new QProgressDialog(title, QString(), 0, 100); + GUIUtil::PolishProgressDialog(progressDialog); progressDialog->setWindowModality(Qt::ApplicationModal); progressDialog->setMinimumDuration(0); - progressDialog->setCancelButton(nullptr); progressDialog->setAutoClose(false); progressDialog->setValue(0); - } - else if (nProgress == 100) - { - if (progressDialog) - { + } else if (nProgress == 100) { + if (progressDialog) { progressDialog->close(); progressDialog->deleteLater(); } - } - else if (progressDialog) + } else if (progressDialog) { progressDialog->setValue(nProgress); + } } void BitcoinGUI::setTrayIconVisible(bool fHideTrayIcon) diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 4e52322521..f1b76a6b64 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -33,6 +33,7 @@ class PlatformStyle; class RPCConsole; class SendCoinsRecipient; class UnitDisplayStatusBarControl; +class WalletController; class WalletFrame; class WalletModel; class HelpMessageDialog; @@ -74,14 +75,17 @@ public: The client model represents the part of the core that communicates with the P2P network, and is wallet-agnostic. */ void setClientModel(ClientModel *clientModel); +#ifdef ENABLE_WALLET + void setWalletController(WalletController* wallet_controller); +#endif #ifdef ENABLE_WALLET /** Set the wallet model. The wallet model represents a bitcoin wallet, and offers access to the list of transactions, address book and sending functionality. */ - bool addWallet(WalletModel *walletModel); - bool removeWallet(WalletModel* walletModel); + void addWallet(WalletModel* walletModel); + void removeWallet(WalletModel* walletModel); void removeAllWallets(); #endif // ENABLE_WALLET bool enableWallet = false; @@ -101,6 +105,7 @@ protected: private: interfaces::Node& m_node; + WalletController* m_wallet_controller{nullptr}; std::unique_ptr<interfaces::Handler> m_handler_message_box; std::unique_ptr<interfaces::Handler> m_handler_question; ClientModel* clientModel = nullptr; @@ -161,6 +166,7 @@ private: int spinnerFrame = 0; const PlatformStyle *platformStyle; + const NetworkStyle* const m_network_style; /** Create the main UI actions. */ void createActions(); @@ -169,7 +175,7 @@ private: /** Create the toolbars */ void createToolBars(); /** Create system tray icon and notification */ - void createTrayIcon(const NetworkStyle *networkStyle); + void createTrayIcon(); /** Create system tray menu (or setup the dock menu) */ void createTrayIconMenu(); @@ -213,8 +219,8 @@ public Q_SLOTS: void message(const QString &title, const QString &message, unsigned int style, bool *ret = nullptr); #ifdef ENABLE_WALLET - bool setCurrentWallet(WalletModel* wallet_model); - bool setCurrentWalletBySelectorIndex(int index); + void setCurrentWallet(WalletModel* wallet_model); + void setCurrentWalletBySelectorIndex(int index); /** Set the UI status indicators based on the currently selected wallet. */ void updateWalletStatus(); @@ -242,6 +248,7 @@ public Q_SLOTS: private: /** Set the proxy-enabled icon as shown in the UI. */ void updateProxyIcon(); + void updateWindowTitle(); public Q_SLOTS: #ifdef ENABLE_WALLET diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index dca16d6f78..f0b976001e 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -453,6 +453,9 @@ </item> <item> <widget class="QComboBox" name="WalletSelector"> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> <item> <property name="text"> <string>(none)</string> @@ -896,570 +899,593 @@ <attribute name="title"> <string>&Peers</string> </attribute> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="0" rowspan="2"> - <layout class="QVBoxLayout" name="verticalLayout_101"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <widget class="QTableView" name="peerWidget"> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAsNeeded</enum> - </property> - <property name="tabKeyNavigation"> - <bool>false</bool> - </property> - <property name="sortingEnabled"> - <bool>true</bool> - </property> - <attribute name="horizontalHeaderHighlightSections"> - <bool>false</bool> - </attribute> - </widget> - </item> - <item> - <widget class="QLabel" name="banHeading"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>300</width> - <height>32</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>16777215</width> - <height>32</height> - </size> - </property> - <property name="font"> - <font> - <pointsize>12</pointsize> - </font> - </property> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>Banned peers</string> - </property> - <property name="alignment"> - <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - <property name="textInteractionFlags"> - <set>Qt::NoTextInteraction</set> - </property> - </widget> - </item> - <item> - <widget class="QTableView" name="banlistWidget"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAsNeeded</enum> - </property> - <property name="tabKeyNavigation"> - <bool>false</bool> - </property> - <property name="sortingEnabled"> - <bool>true</bool> - </property> - <attribute name="horizontalHeaderHighlightSections"> - <bool>false</bool> - </attribute> - </widget> - </item> - </layout> - </item> - <item row="0" column="1"> - <widget class="QLabel" name="peerHeading"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>300</width> - <height>32</height> - </size> - </property> - <property name="font"> - <font> - <pointsize>10</pointsize> - </font> - </property> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>Select a peer to view detailed information.</string> - </property> - <property name="alignment"> - <set>Qt::AlignHCenter|Qt::AlignTop</set> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QWidget" name="detailWidget" native="true"> - <property name="minimumSize"> - <size> - <width>300</width> - <height>0</height> - </size> + <property name="childrenCollapsible"> + <bool>false</bool> </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="0" column="0"> - <widget class="QLabel" name="label_30"> - <property name="text"> - <string>Whitelisted</string> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QLabel" name="peerWhitelisted"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_23"> - <property name="text"> - <string>Direction</string> - </property> - </widget> - </item> - <item row="1" column="2"> - <widget class="QLabel" name="peerDirection"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_21"> - <property name="text"> - <string>Version</string> - </property> - </widget> - </item> - <item row="2" column="2"> - <widget class="QLabel" name="peerVersion"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_28"> - <property name="text"> - <string>User Agent</string> - </property> - </widget> - </item> - <item row="3" column="2"> - <widget class="QLabel" name="peerSubversion"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Services</string> - </property> - </widget> - </item> - <item row="4" column="2"> - <widget class="QLabel" name="peerServices"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="5" column="0"> - <widget class="QLabel" name="label_29"> - <property name="text"> - <string>Starting Block</string> - </property> - </widget> - </item> - <item row="5" column="2"> - <widget class="QLabel" name="peerHeight"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="6" column="0"> - <widget class="QLabel" name="label_27"> - <property name="text"> - <string>Synced Headers</string> - </property> - </widget> - </item> - <item row="6" column="2"> - <widget class="QLabel" name="peerSyncHeight"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="7" column="0"> - <widget class="QLabel" name="label_25"> - <property name="text"> - <string>Synced Blocks</string> - </property> - </widget> - </item> - <item row="7" column="2"> - <widget class="QLabel" name="peerCommonHeight"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="8" column="0"> - <widget class="QLabel" name="label_24"> - <property name="text"> - <string>Ban Score</string> - </property> - </widget> - </item> - <item row="8" column="2"> - <widget class="QLabel" name="peerBanScore"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="9" column="0"> - <widget class="QLabel" name="label_22"> - <property name="text"> - <string>Connection Time</string> - </property> - </widget> - </item> - <item row="9" column="2"> - <widget class="QLabel" name="peerConnTime"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="10" column="0"> - <widget class="QLabel" name="label_15"> - <property name="text"> - <string>Last Send</string> - </property> - </widget> - </item> - <item row="10" column="2"> - <widget class="QLabel" name="peerLastSend"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="11" column="0"> - <widget class="QLabel" name="label_19"> - <property name="text"> - <string>Last Receive</string> - </property> - </widget> - </item> - <item row="11" column="2"> - <widget class="QLabel" name="peerLastRecv"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="12" column="0"> - <widget class="QLabel" name="label_18"> - <property name="text"> - <string>Sent</string> - </property> - </widget> - </item> - <item row="12" column="2"> - <widget class="QLabel" name="peerBytesSent"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="13" column="0"> - <widget class="QLabel" name="label_20"> - <property name="text"> - <string>Received</string> - </property> - </widget> - </item> - <item row="13" column="2"> - <widget class="QLabel" name="peerBytesRecv"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="14" column="0"> - <widget class="QLabel" name="label_26"> - <property name="text"> - <string>Ping Time</string> - </property> - </widget> - </item> - <item row="14" column="2"> - <widget class="QLabel" name="peerPingTime"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="15" column="0"> - <widget class="QLabel" name="peerPingWaitLabel"> - <property name="toolTip"> - <string>The duration of a currently outstanding ping.</string> - </property> - <property name="text"> - <string>Ping Wait</string> - </property> - </widget> - </item> - <item row="15" column="2"> - <widget class="QLabel" name="peerPingWait"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="16" column="0"> - <widget class="QLabel" name="peerMinPingLabel"> - <property name="text"> - <string>Min Ping</string> - </property> - </widget> - </item> - <item row="16" column="2"> - <widget class="QLabel" name="peerMinPing"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="17" column="0"> - <widget class="QLabel" name="label_timeoffset"> - <property name="text"> - <string>Time Offset</string> - </property> - </widget> - </item> - <item row="17" column="2"> - <widget class="QLabel" name="timeoffset"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="18" column="1"> - <spacer name="verticalSpacer_3"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> + <widget class="QWidget" name="widget_1" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>400</width> + <height>0</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_7"> + <item> + <widget class="QTableView" name="peerWidget"> + <property name="tabKeyNavigation"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <attribute name="horizontalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <widget class="QLabel" name="banHeading"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>32</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>32</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>12</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>Banned peers</string> + </property> + <property name="alignment"> + <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::NoTextInteraction</set> + </property> + </widget> + </item> + <item> + <widget class="QTableView" name="banlistWidget"> + <property name="tabKeyNavigation"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <attribute name="horizontalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="widget_2" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>0</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_8"> + <item> + <widget class="QLabel" name="peerHeading"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>32</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>Select a peer to view detailed information.</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item> + <widget class="QScrollArea" name="scrollArea"> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="detailWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>426</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>0</height> + </size> + </property> + <layout class="QGridLayout" name="gridLayout_2" columnstretch="0,1"> + <item row="0" column="0"> + <widget class="QLabel" name="label_30"> + <property name="text"> + <string>Whitelisted</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="peerWhitelisted"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_23"> + <property name="text"> + <string>Direction</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="peerDirection"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_21"> + <property name="text"> + <string>Version</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="peerVersion"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_28"> + <property name="text"> + <string>User Agent</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="peerSubversion"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Services</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="peerServices"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_29"> + <property name="text"> + <string>Starting Block</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="peerHeight"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_27"> + <property name="text"> + <string>Synced Headers</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QLabel" name="peerSyncHeight"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_25"> + <property name="text"> + <string>Synced Blocks</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QLabel" name="peerCommonHeight"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_24"> + <property name="text"> + <string>Ban Score</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QLabel" name="peerBanScore"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="9" column="0"> + <widget class="QLabel" name="label_22"> + <property name="text"> + <string>Connection Time</string> + </property> + </widget> + </item> + <item row="9" column="1"> + <widget class="QLabel" name="peerConnTime"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="10" column="0"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Last Send</string> + </property> + </widget> + </item> + <item row="10" column="1"> + <widget class="QLabel" name="peerLastSend"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="11" column="0"> + <widget class="QLabel" name="label_19"> + <property name="text"> + <string>Last Receive</string> + </property> + </widget> + </item> + <item row="11" column="1"> + <widget class="QLabel" name="peerLastRecv"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="12" column="0"> + <widget class="QLabel" name="label_18"> + <property name="text"> + <string>Sent</string> + </property> + </widget> + </item> + <item row="12" column="1"> + <widget class="QLabel" name="peerBytesSent"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="13" column="0"> + <widget class="QLabel" name="label_20"> + <property name="text"> + <string>Received</string> + </property> + </widget> + </item> + <item row="13" column="1"> + <widget class="QLabel" name="peerBytesRecv"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="14" column="0"> + <widget class="QLabel" name="label_26"> + <property name="text"> + <string>Ping Time</string> + </property> + </widget> + </item> + <item row="14" column="1"> + <widget class="QLabel" name="peerPingTime"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="15" column="0"> + <widget class="QLabel" name="peerPingWaitLabel"> + <property name="toolTip"> + <string>The duration of a currently outstanding ping.</string> + </property> + <property name="text"> + <string>Ping Wait</string> + </property> + </widget> + </item> + <item row="15" column="1"> + <widget class="QLabel" name="peerPingWait"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="16" column="0"> + <widget class="QLabel" name="peerMinPingLabel"> + <property name="text"> + <string>Min Ping</string> + </property> + </widget> + </item> + <item row="16" column="1"> + <widget class="QLabel" name="peerMinPing"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="17" column="0"> + <widget class="QLabel" name="label_timeoffset"> + <property name="text"> + <string>Time Offset</string> + </property> + </widget> + </item> + <item row="17" column="1"> + <widget class="QLabel" name="timeoffset"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> </widget> </item> </layout> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 2fc166b0c5..71e987c8f4 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -48,13 +48,15 @@ #include <QFileDialog> #include <QFont> #include <QFontDatabase> +#include <QFontMetrics> #include <QKeyEvent> #include <QLineEdit> +#include <QMouseEvent> +#include <QProgressDialog> #include <QSettings> #include <QTextDocument> // for Qt::mightBeRichText #include <QThread> #include <QUrlQuery> -#include <QMouseEvent> #if defined(Q_OS_MAC) #pragma GCC diagnostic push @@ -681,13 +683,11 @@ bool SetStartOnSystemStartup(bool fAutoStart) } -#elif defined(Q_OS_MAC) +#elif defined(Q_OS_MAC) && defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED <= 101100 // based on: https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m -LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl); -LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl) +LSSharedFileListItemRef findStartupItemInList(CFArrayRef listSnapshot, LSSharedFileListRef list, CFURLRef findUrl) { - CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(list, nullptr); if (listSnapshot == nullptr) { return nullptr; } @@ -712,15 +712,12 @@ LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef if(currentItemURL) { if (CFEqual(currentItemURL, findUrl)) { // found - CFRelease(listSnapshot); CFRelease(currentItemURL); return item; } CFRelease(currentItemURL); } } - - CFRelease(listSnapshot); return nullptr; } @@ -732,10 +729,12 @@ bool GetStartOnSystemStartup() } LSSharedFileListRef loginItems = LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems, nullptr); - LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); - + CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(loginItems, nullptr); + bool res = (findStartupItemInList(listSnapshot, loginItems, bitcoinAppUrl) != nullptr); CFRelease(bitcoinAppUrl); - return !!foundItem; // return boolified object + CFRelease(loginItems); + CFRelease(listSnapshot); + return res; } bool SetStartOnSystemStartup(bool fAutoStart) @@ -746,7 +745,8 @@ bool SetStartOnSystemStartup(bool fAutoStart) } LSSharedFileListRef loginItems = LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems, nullptr); - LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); + CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(loginItems, nullptr); + LSSharedFileListItemRef foundItem = findStartupItemInList(listSnapshot, loginItems, bitcoinAppUrl); if(fAutoStart && !foundItem) { // add bitcoin app to startup item list @@ -758,6 +758,8 @@ bool SetStartOnSystemStartup(bool fAutoStart) } CFRelease(bitcoinAppUrl); + CFRelease(loginItems); + CFRelease(listSnapshot); return true; } #pragma GCC diagnostic pop @@ -933,4 +935,16 @@ bool ItemDelegate::eventFilter(QObject *object, QEvent *event) return QItemDelegate::eventFilter(object, event); } +void PolishProgressDialog(QProgressDialog* dialog) +{ +#ifdef Q_OS_MAC + // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357. + const int margin = dialog->fontMetrics().width("X"); + dialog->resize(dialog->width() + 2 * margin, dialog->height()); + dialog->show(); +#else + Q_UNUSED(dialog); +#endif +} + } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index ecb770d147..cbec73a882 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -31,6 +31,7 @@ class QAbstractItemView; class QDateTime; class QFont; class QLineEdit; +class QProgressDialog; class QUrl; class QWidget; QT_END_NAMESPACE @@ -248,6 +249,9 @@ namespace GUIUtil private: bool eventFilter(QObject *object, QEvent *event); }; + + // Fix known bugs in QProgressDialog class. + void PolishProgressDialog(QProgressDialog* dialog); } // namespace GUIUtil #endif // BITCOIN_QT_GUIUTIL_H diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index fa9a50b1ed..69972fce3b 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -156,7 +156,7 @@ Intro::~Intro() { delete ui; /* Ensure thread is finished before it is deleted */ - Q_EMIT stopThread(); + thread->quit(); thread->wait(); } @@ -311,8 +311,7 @@ void Intro::startThread() connect(executor, &FreespaceChecker::reply, this, &Intro::setStatus); connect(this, &Intro::requestCheck, executor, &FreespaceChecker::check); /* make sure executor object is deleted in its own thread */ - connect(this, &Intro::stopThread, executor, &QObject::deleteLater); - connect(this, &Intro::stopThread, thread, &QThread::quit); + connect(thread, &QThread::finished, executor, &QObject::deleteLater); thread->start(); } diff --git a/src/qt/intro.h b/src/qt/intro.h index 3da8a16114..b537c94f7d 100644 --- a/src/qt/intro.h +++ b/src/qt/intro.h @@ -55,7 +55,6 @@ public: Q_SIGNALS: void requestCheck(); - void stopThread(); public Q_SLOTS: void setStatus(int status, const QString &message, quint64 bytesAvailable); diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 27cec06d4b..849bc2e477 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -74,6 +74,12 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : #ifdef Q_OS_MAC /* remove Window tab on Mac */ ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWindow)); +#if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED > 101100 + /* hide launch at startup option if compiled against macOS > 10.11 (removed API) */ + ui->bitcoinAtStartup->setVisible(false); + ui->verticalLayout_Main->removeWidget(ui->bitcoinAtStartup); + ui->verticalLayout_Main->removeItem(ui->horizontalSpacer_0_Main); +#endif #endif /* remove Wallet tab in case of -disablewallet */ diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 3d7211aca2..fc1e14b031 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -396,13 +396,12 @@ void RPCExecutor::request(const QString &command, const WalletModel* wallet_mode std::string executableCommand = command.toStdString() + "\n"; // Catch the console-only-help command before RPC call is executed and reply with help text as-if a RPC reply. - if(executableCommand == "help-console\n") - { + if(executableCommand == "help-console\n") { Q_EMIT reply(RPCConsole::CMD_REPLY, QString(("\n" "This console accepts RPC commands using the standard syntax.\n" " example: getblockhash 0\n\n" - "This console can also accept RPC commands using parenthesized syntax.\n" + "This console can also accept RPC commands using the parenthesized syntax.\n" " example: getblockhash(0)\n\n" "Commands may be nested when specified with the parenthesized syntax.\n" @@ -412,11 +411,11 @@ void RPCExecutor::request(const QString &command, const WalletModel* wallet_mode " example: getblockhash 0\n" " getblockhash,0\n\n" - "Named results can be queried with a non-quoted key string in brackets.\n" - " example: getblock(getblockhash(0) true)[tx]\n\n" + "Named results can be queried with a non-quoted key string in brackets using the parenthesized syntax.\n" + " example: getblock(getblockhash(0) 1)[tx]\n\n" - "Results without keys can be queried using an integer in brackets.\n" - " example: getblock(getblockhash(0),true)[tx][0]\n\n"))); + "Results without keys can be queried with an integer in brackets using the parenthesized syntax.\n" + " example: getblock(getblockhash(0),1)[tx][0]\n\n"))); return; } if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, wallet_model)) { @@ -688,8 +687,7 @@ void RPCConsole::setClientModel(ClientModel *model) } if (!model) { // Client model is being set to 0, this means shutdown() is about to be called. - // Make sure we clean up the executor thread - Q_EMIT stopExecutor(); + thread.quit(); thread.wait(); } } @@ -975,11 +973,8 @@ void RPCConsole::startExecutor() // Requests from this object must go to executor connect(this, &RPCConsole::cmdRequest, executor, &RPCExecutor::request); - // On stopExecutor signal - // - quit the Qt event loop in the execution thread - connect(this, &RPCConsole::stopExecutor, &thread, &QThread::quit); - // - queue executor for deletion (in execution thread) - connect(&thread, &QThread::finished, executor, &RPCExecutor::deleteLater, Qt::DirectConnection); + // Make sure executor object is deleted in its own thread + connect(&thread, &QThread::finished, executor, &RPCExecutor::deleteLater); // Default implementation of QThread::run() simply spins up an event loop in the thread, // which is what we want. @@ -988,10 +983,9 @@ void RPCConsole::startExecutor() void RPCConsole::on_tabWidget_currentChanged(int index) { - if (ui->tabWidget->widget(index) == ui->tab_console) + if (ui->tabWidget->widget(index) == ui->tab_console) { ui->lineEdit->setFocus(); - else if (ui->tabWidget->widget(index) != ui->tab_peers) - clearSelectedNode(); + } } void RPCConsole::on_openDebugLogfileButton_clicked() @@ -1217,16 +1211,16 @@ void RPCConsole::banSelectedNode(int bantime) // Get currently selected peer address NodeId id = nodes.at(i).data().toLongLong(); - // Get currently selected peer address - int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(id); - if(detailNodeRow < 0) - return; + // Get currently selected peer address + int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(id); + if (detailNodeRow < 0) return; - // Find possible nodes, ban it and clear the selected node - const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow); - if(stats) { + // Find possible nodes, ban it and clear the selected node + const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow); + if (stats) { m_node.ban(stats->nodeStats.addr, BanReasonManuallyAdded, bantime); - } + m_node.disconnect(stats->nodeStats.addr); + } } clearSelectedNode(); clientModel->getBanTableModel()->refresh(); diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 6c000ba096..79b0f3b19c 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -132,7 +132,6 @@ public Q_SLOTS: Q_SIGNALS: // For RPC command executor - void stopExecutor(); void cmdRequest(const QString &command, const WalletModel* wallet_model); private: diff --git a/src/qt/test/paymentservertests.cpp b/src/qt/test/paymentservertests.cpp index 94907595f5..f0eca899fc 100644 --- a/src/qt/test/paymentservertests.cpp +++ b/src/qt/test/paymentservertests.cpp @@ -181,12 +181,12 @@ void PaymentServerTests::paymentServerTests() QCOMPARE(PaymentServer::verifyExpired(r.paymentRequest.getDetails()), true); // Test BIP70 DoS protection: - unsigned char randData[BIP70_MAX_PAYMENTREQUEST_SIZE + 1]; - GetRandBytes(randData, sizeof(randData)); + auto randdata = FastRandomContext().randbytes(BIP70_MAX_PAYMENTREQUEST_SIZE + 1); + // Write data to a temp file: QTemporaryFile tempFile; tempFile.open(); - tempFile.write((const char*)randData, sizeof(randData)); + tempFile.write((const char*)randdata.data(), randdata.size()); tempFile.close(); // compares 50001 <= BIP70_MAX_PAYMENTREQUEST_SIZE == false QCOMPARE(PaymentServer::verifySize(tempFile.size()), false); diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp new file mode 100644 index 0000000000..df2b7a3f9b --- /dev/null +++ b/src/qt/walletcontroller.cpp @@ -0,0 +1,95 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <qt/walletcontroller.h> + +#include <interfaces/handler.h> +#include <interfaces/node.h> + +#include <algorithm> + +#include <QMutexLocker> +#include <QThread> + +WalletController::WalletController(interfaces::Node& node, const PlatformStyle* platform_style, OptionsModel* options_model, QObject* parent) + : QObject(parent) + , m_node(node) + , m_platform_style(platform_style) + , m_options_model(options_model) +{ + m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) { + getOrCreateWallet(std::move(wallet)); + }); + + for (std::unique_ptr<interfaces::Wallet>& wallet : m_node.getWallets()) { + getOrCreateWallet(std::move(wallet)); + } +} + +// Not using the default destructor because not all member types definitions are +// available in the header, just forward declared. +WalletController::~WalletController() {} + +std::vector<WalletModel*> WalletController::getWallets() const +{ + QMutexLocker locker(&m_mutex); + return m_wallets; +} + +WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet) +{ + QMutexLocker locker(&m_mutex); + + // Return model instance if exists. + if (!m_wallets.empty()) { + std::string name = wallet->getWalletName(); + for (WalletModel* wallet_model : m_wallets) { + if (wallet_model->wallet().getWalletName() == name) { + return wallet_model; + } + } + } + + // Instantiate model and register it. + WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, m_platform_style, m_options_model, nullptr); + m_wallets.push_back(wallet_model); + + connect(wallet_model, &WalletModel::unload, [this, wallet_model] { + removeAndDeleteWallet(wallet_model); + }); + + // Re-emit coinsSent signal from wallet model. + connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent); + + // Notify walletAdded signal on the GUI thread. + if (QThread::currentThread() == thread()) { + addWallet(wallet_model); + } else { + // Handler callback runs in a different thread so fix wallet model thread affinity. + wallet_model->moveToThread(thread()); + QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model)); + } + + return wallet_model; +} + +void WalletController::addWallet(WalletModel* wallet_model) +{ + // Take ownership of the wallet model and register it. + wallet_model->setParent(this); + Q_EMIT walletAdded(wallet_model); +} + +void WalletController::removeAndDeleteWallet(WalletModel* wallet_model) +{ + // Unregister wallet model. + { + QMutexLocker locker(&m_mutex); + m_wallets.erase(std::remove(m_wallets.begin(), m_wallets.end(), wallet_model)); + } + Q_EMIT walletRemoved(wallet_model); + // Currently this can trigger the unload since the model can hold the last + // CWallet shared pointer. + delete wallet_model; +} diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h new file mode 100644 index 0000000000..22b71b07ff --- /dev/null +++ b/src/qt/walletcontroller.h @@ -0,0 +1,59 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_WALLETCONTROLLER_H +#define BITCOIN_QT_WALLETCONTROLLER_H + +#include <qt/walletmodel.h> +#include <sync.h> + +#include <list> +#include <memory> +#include <vector> + +#include <QMutex> + +class OptionsModel; +class PlatformStyle; + +namespace interfaces { +class Handler; +class Node; +} // namespace interfaces + +/** + * Controller between interfaces::Node, WalletModel instances and the GUI. + */ +class WalletController : public QObject +{ + Q_OBJECT + + WalletModel* getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet); + void removeAndDeleteWallet(WalletModel* wallet_model); + +public: + WalletController(interfaces::Node& node, const PlatformStyle* platform_style, OptionsModel* options_model, QObject* parent); + ~WalletController(); + + std::vector<WalletModel*> getWallets() const; + +private Q_SLOTS: + void addWallet(WalletModel* wallet_model); + +Q_SIGNALS: + void walletAdded(WalletModel* wallet_model); + void walletRemoved(WalletModel* wallet_model); + + void coinsSent(WalletModel* wallet_model, SendCoinsRecipient recipient, QByteArray transaction); + +private: + interfaces::Node& m_node; + const PlatformStyle* const m_platform_style; + OptionsModel* const m_options_model; + mutable QMutex m_mutex; + std::vector<WalletModel*> m_wallets; + std::unique_ptr<interfaces::Handler> m_handler_load_wallet; +}; + +#endif // BITCOIN_QT_WALLETCONTROLLER_H diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 4f8b6d363e..466f2278eb 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -208,11 +208,17 @@ void WalletFrame::usedReceivingAddresses() walletView->usedReceivingAddresses(); } -WalletView *WalletFrame::currentWalletView() +WalletView* WalletFrame::currentWalletView() const { return qobject_cast<WalletView*>(walletStack->currentWidget()); } +WalletModel* WalletFrame::currentWalletModel() const +{ + WalletView* wallet_view = currentWalletView(); + return wallet_view ? wallet_view->getWalletModel() : nullptr; +} + void WalletFrame::outOfSyncWarningClicked() { Q_EMIT requestedSyncWarningInfo(); diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 10d063b2b7..6a74fde9fd 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -60,7 +60,8 @@ private: const PlatformStyle *platformStyle; public: - WalletView *currentWalletView(); + WalletView* currentWalletView() const; + WalletModel* currentWalletModel() const; public Q_SLOTS: /** Switch to overview (home) page */ diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e1fb4819f1..f139152042 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -376,7 +376,7 @@ bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureStri static void NotifyUnload(WalletModel* walletModel) { qDebug() << "NotifyUnload"; - QMetaObject::invokeMethod(walletModel, "unload", Qt::QueuedConnection); + QMetaObject::invokeMethod(walletModel, "unload"); } static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel) diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index dd089d8310..5f6f93d948 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -305,24 +305,19 @@ void WalletView::usedReceivingAddresses() void WalletView::showProgress(const QString &title, int nProgress) { - if (nProgress == 0) - { - progressDialog = new QProgressDialog(title, "", 0, 100); + if (nProgress == 0) { + progressDialog = new QProgressDialog(title, tr("Cancel"), 0, 100); + GUIUtil::PolishProgressDialog(progressDialog); progressDialog->setWindowModality(Qt::ApplicationModal); progressDialog->setMinimumDuration(0); progressDialog->setAutoClose(false); progressDialog->setValue(0); - progressDialog->setCancelButtonText(tr("Cancel")); - } - else if (nProgress == 100) - { - if (progressDialog) - { + } else if (nProgress == 100) { + if (progressDialog) { progressDialog->close(); progressDialog->deleteLater(); } - } - else if (progressDialog) { + } else if (progressDialog) { if (progressDialog->wasCanceled()) { getWalletModel()->wallet().abortRescan(); } else { diff --git a/src/random.cpp b/src/random.cpp index f8ffda136d..3b7f7910b0 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -19,6 +19,8 @@ #include <chrono> #include <thread> +#include <support/allocators/secure.h> + #ifndef WIN32 #include <fcntl.h> #include <sys/time.h> @@ -47,6 +49,7 @@ #include <openssl/err.h> #include <openssl/rand.h> +#include <openssl/conf.h> [[noreturn]] static void RandFailure() { @@ -54,7 +57,7 @@ std::abort(); } -static inline int64_t GetPerformanceCounter() +static inline int64_t GetPerformanceCounter() noexcept { // Read the hardware time stamp counter when available. // See https://en.wikipedia.org/wiki/Time_Stamp_Counter for more information. @@ -74,27 +77,38 @@ static inline int64_t GetPerformanceCounter() #endif } - #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) -static std::atomic<bool> hwrand_initialized{false}; static bool rdrand_supported = false; static constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000; -static void RDRandInit() +static void InitHardwareRand() { uint32_t eax, ebx, ecx, edx; if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) && (ecx & CPUID_F1_ECX_RDRAND)) { - LogPrintf("Using RdRand as an additional entropy source\n"); rdrand_supported = true; } - hwrand_initialized.store(true); } + +static void ReportHardwareRand() +{ + if (rdrand_supported) { + // This must be done in a separate function, as HWRandInit() may be indirectly called + // from global constructors, before logging is initialized. + LogPrintf("Using RdRand as an additional entropy source\n"); + } +} + #else -static void RDRandInit() {} +/* Access to other hardware random number generators could be added here later, + * assuming it is sufficiently fast (in the order of a few hundred CPU cycles). + * Slower sources should probably be invoked separately, and/or only from + * RandAddSeedSleep (which is called during idle background operation). + */ +static void InitHardwareRand() {} +static void ReportHardwareRand() {} #endif -static bool GetHWRand(unsigned char* ent32) { +static bool GetHardwareRand(unsigned char* ent32) noexcept { #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) - assert(hwrand_initialized.load(std::memory_order_relaxed)); if (rdrand_supported) { uint8_t ok; // Not all assemblers support the rdrand instruction, write it in hex. @@ -129,18 +143,8 @@ static bool GetHWRand(unsigned char* ent32) { return false; } -void RandAddSeed() +static void RandAddSeedPerfmon(CSHA512& hasher) { - // Seed with CPU performance counter - int64_t nCounter = GetPerformanceCounter(); - RAND_add(&nCounter, sizeof(nCounter), 1.5); - memory_cleanse((void*)&nCounter, sizeof(nCounter)); -} - -static void RandAddSeedPerfmon() -{ - RandAddSeed(); - #ifdef WIN32 // Don't need this on Linux, OpenSSL automatically uses /dev/urandom // Seed with the entire set of perfmon data @@ -164,15 +168,15 @@ static void RandAddSeedPerfmon() } RegCloseKey(HKEY_PERFORMANCE_DATA); if (ret == ERROR_SUCCESS) { - RAND_add(vData.data(), nSize, nSize / 100.0); + hasher.Write(vData.data(), nSize); memory_cleanse(vData.data(), nSize); - LogPrint(BCLog::RAND, "%s: %lu bytes\n", __func__, nSize); } else { - static bool warned = false; // Warn only once - if (!warned) { - LogPrintf("%s: Warning: RegQueryValueExA(HKEY_PERFORMANCE_DATA) failed with code %i\n", __func__, ret); - warned = true; - } + // Performance data is only a best-effort attempt at improving the + // situation when the OS randomness (and other sources) aren't + // adequate. As a result, failure to read it is isn't considered critical, + // so we don't call RandFailure(). + // TODO: Add logging when the logger is made functional before global + // constructors have been invoked. } #endif } @@ -272,106 +276,255 @@ void GetOSRand(unsigned char *ent32) #endif } -void GetRandBytes(unsigned char* buf, int num) +void LockingCallbackOpenSSL(int mode, int i, const char* file, int line); + +namespace { + +class RNGState { + Mutex m_mutex; + /* The RNG state consists of 256 bits of entropy, taken from the output of + * one operation's SHA512 output, and fed as input to the next one. + * Carrying 256 bits of entropy should be sufficient to guarantee + * unpredictability as long as any entropy source was ever unpredictable + * to an attacker. To protect against situations where an attacker might + * observe the RNG's state, fresh entropy is always mixed when + * GetStrongRandBytes is called. + */ + unsigned char m_state[32] GUARDED_BY(m_mutex) = {0}; + uint64_t m_counter GUARDED_BY(m_mutex) = 0; + bool m_strongly_seeded GUARDED_BY(m_mutex) = false; + std::unique_ptr<Mutex[]> m_mutex_openssl; + +public: + RNGState() noexcept + { + InitHardwareRand(); + + // Init OpenSSL library multithreading support + m_mutex_openssl.reset(new Mutex[CRYPTO_num_locks()]); + CRYPTO_set_locking_callback(LockingCallbackOpenSSL); + + // OpenSSL can optionally load a config file which lists optional loadable modules and engines. + // We don't use them so we don't require the config. However some of our libs may call functions + // which attempt to load the config file, possibly resulting in an exit() or crash if it is missing + // or corrupt. Explicitly tell OpenSSL not to try to load the file. The result for our libs will be + // that the config appears to have been loaded and there are no modules/engines available. + OPENSSL_no_config(); + } + + ~RNGState() + { + // Securely erase the memory used by the OpenSSL PRNG + RAND_cleanup(); + // Shutdown OpenSSL library multithreading support + CRYPTO_set_locking_callback(nullptr); + } + + /** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher. + * + * If this function has never been called with strong_seed = true, false is returned. + */ + bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) noexcept + { + assert(num <= 32); + unsigned char buf[64]; + static_assert(sizeof(buf) == CSHA512::OUTPUT_SIZE, "Buffer needs to have hasher's output size"); + bool ret; + { + LOCK(m_mutex); + ret = (m_strongly_seeded |= strong_seed); + // Write the current state of the RNG into the hasher + hasher.Write(m_state, 32); + // Write a new counter number into the state + hasher.Write((const unsigned char*)&m_counter, sizeof(m_counter)); + ++m_counter; + // Finalize the hasher + hasher.Finalize(buf); + // Store the last 32 bytes of the hash output as new RNG state. + memcpy(m_state, buf + 32, 32); + } + // If desired, copy (up to) the first 32 bytes of the hash output as output. + if (num) { + assert(out != nullptr); + memcpy(out, buf, num); + } + // Best effort cleanup of internal state + hasher.Reset(); + memory_cleanse(buf, 64); + return ret; + } + + Mutex& GetOpenSSLMutex(int i) { return m_mutex_openssl[i]; } +}; + +RNGState& GetRNGState() noexcept { - if (RAND_bytes(buf, num) != 1) { - RandFailure(); + // This C++11 idiom relies on the guarantee that static variable are initialized + // on first call, even when multiple parallel calls are permitted. + static std::vector<RNGState, secure_allocator<RNGState>> g_rng(1); + return g_rng[0]; +} +} + +void LockingCallbackOpenSSL(int mode, int i, const char* file, int line) NO_THREAD_SAFETY_ANALYSIS +{ + RNGState& rng = GetRNGState(); + + if (mode & CRYPTO_LOCK) { + rng.GetOpenSSLMutex(i).lock(); + } else { + rng.GetOpenSSLMutex(i).unlock(); } } -static void AddDataToRng(void* data, size_t len); +/* A note on the use of noexcept in the seeding functions below: + * + * None of the RNG code should ever throw any exception, with the sole exception + * of MilliSleep in SeedSleep, which can (and does) support interruptions which + * cause a boost::thread_interrupted to be thrown. + * + * This means that SeedSleep, and all functions that invoke it are throwing. + * However, we know that GetRandBytes() and GetStrongRandBytes() never trigger + * this sleeping logic, so they are noexcept. The same is true for all the + * GetRand*() functions that use GetRandBytes() indirectly. + * + * TODO: After moving away from interruptible boost-based thread management, + * everything can become noexcept here. + */ -void RandAddSeedSleep() +static void SeedTimestamp(CSHA512& hasher) noexcept { - int64_t nPerfCounter1 = GetPerformanceCounter(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - int64_t nPerfCounter2 = GetPerformanceCounter(); + int64_t perfcounter = GetPerformanceCounter(); + hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter)); +} - // Combine with and update state - AddDataToRng(&nPerfCounter1, sizeof(nPerfCounter1)); - AddDataToRng(&nPerfCounter2, sizeof(nPerfCounter2)); +static void SeedFast(CSHA512& hasher) noexcept +{ + unsigned char buffer[32]; + + // Stack pointer to indirectly commit to thread/callstack + const unsigned char* ptr = buffer; + hasher.Write((const unsigned char*)&ptr, sizeof(ptr)); + + // Hardware randomness is very fast when available; use it always. + bool have_hw_rand = GetHardwareRand(buffer); + if (have_hw_rand) hasher.Write(buffer, sizeof(buffer)); - memory_cleanse(&nPerfCounter1, sizeof(nPerfCounter1)); - memory_cleanse(&nPerfCounter2, sizeof(nPerfCounter2)); + // High-precision timestamp + SeedTimestamp(hasher); } +static void SeedSlow(CSHA512& hasher) noexcept +{ + unsigned char buffer[32]; -static Mutex cs_rng_state; -static unsigned char rng_state[32] = {0}; -static uint64_t rng_counter = 0; + // Everything that the 'fast' seeder includes + SeedFast(hasher); -static void AddDataToRng(void* data, size_t len) { - CSHA512 hasher; - hasher.Write((const unsigned char*)&len, sizeof(len)); - hasher.Write((const unsigned char*)data, len); - unsigned char buf[64]; - { - WAIT_LOCK(cs_rng_state, lock); - hasher.Write(rng_state, sizeof(rng_state)); - hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter)); - ++rng_counter; - hasher.Finalize(buf); - memcpy(rng_state, buf + 32, 32); - } - memory_cleanse(buf, 64); + // OS randomness + GetOSRand(buffer); + hasher.Write(buffer, sizeof(buffer)); + + // OpenSSL RNG (for now) + RAND_bytes(buffer, sizeof(buffer)); + hasher.Write(buffer, sizeof(buffer)); + + // High-precision timestamp. + // + // Note that we also commit to a timestamp in the Fast seeder, so we indirectly commit to a + // benchmark of all the entropy gathering sources in this function). + SeedTimestamp(hasher); } -void GetStrongRandBytes(unsigned char* out, int num) +static void SeedSleep(CSHA512& hasher) { - assert(num <= 32); - CSHA512 hasher; - unsigned char buf[64]; + // Everything that the 'fast' seeder includes + SeedFast(hasher); + + // High-precision timestamp + SeedTimestamp(hasher); + + // Sleep for 1ms + MilliSleep(1); + + // High-precision timestamp after sleeping (as we commit to both the time before and after, this measures the delay) + SeedTimestamp(hasher); - // First source: OpenSSL's RNG - RandAddSeedPerfmon(); - GetRandBytes(buf, 32); - hasher.Write(buf, 32); + // Windows performance monitor data (once every 10 minutes) + RandAddSeedPerfmon(hasher); +} + +static void SeedStartup(CSHA512& hasher) noexcept +{ +#ifdef WIN32 + RAND_screen(); +#endif - // Second source: OS RNG - GetOSRand(buf); - hasher.Write(buf, 32); + // Everything that the 'slow' seeder includes. + SeedSlow(hasher); + + // Windows performance monitor data. + RandAddSeedPerfmon(hasher); +} + +enum class RNGLevel { + FAST, //!< Automatically called by GetRandBytes + SLOW, //!< Automatically called by GetStrongRandBytes + SLEEP, //!< Called by RandAddSeedSleep() +}; + +static void ProcRand(unsigned char* out, int num, RNGLevel level) +{ + // Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available). + RNGState& rng = GetRNGState(); - // Third source: HW RNG, if available. - if (GetHWRand(buf)) { - hasher.Write(buf, 32); + assert(num <= 32); + + CSHA512 hasher; + switch (level) { + case RNGLevel::FAST: + SeedFast(hasher); + break; + case RNGLevel::SLOW: + SeedSlow(hasher); + break; + case RNGLevel::SLEEP: + SeedSleep(hasher); + break; } // Combine with and update state - { - WAIT_LOCK(cs_rng_state, lock); - hasher.Write(rng_state, sizeof(rng_state)); - hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter)); - ++rng_counter; - hasher.Finalize(buf); - memcpy(rng_state, buf + 32, 32); + if (!rng.MixExtract(out, num, std::move(hasher), false)) { + // On the first invocation, also seed with SeedStartup(). + CSHA512 startup_hasher; + SeedStartup(startup_hasher); + rng.MixExtract(out, num, std::move(startup_hasher), true); } - // Produce output - memcpy(out, buf, num); - memory_cleanse(buf, 64); + // For anything but the 'fast' level, feed the resulting RNG output (after an additional hashing step) back into OpenSSL. + if (level != RNGLevel::FAST) { + unsigned char buf[64]; + CSHA512().Write(out, num).Finalize(buf); + RAND_add(buf, sizeof(buf), num); + memory_cleanse(buf, 64); + } } -uint64_t GetRand(uint64_t nMax) -{ - if (nMax == 0) - return 0; +void GetRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::FAST); } +void GetStrongRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::SLOW); } +void RandAddSeedSleep() { ProcRand(nullptr, 0, RNGLevel::SLEEP); } - // The range of the random source must be a multiple of the modulus - // to give every possible output value an equal possibility - uint64_t nRange = (std::numeric_limits<uint64_t>::max() / nMax) * nMax; - uint64_t nRand = 0; - do { - GetRandBytes((unsigned char*)&nRand, sizeof(nRand)); - } while (nRand >= nRange); - return (nRand % nMax); +uint64_t GetRand(uint64_t nMax) noexcept +{ + return FastRandomContext().randrange(nMax); } -int GetRandInt(int nMax) +int GetRandInt(int nMax) noexcept { return GetRand(nMax); } -uint256 GetRandHash() +uint256 GetRandHash() noexcept { uint256 hash; GetRandBytes((unsigned char*)&hash, sizeof(hash)); @@ -385,7 +538,7 @@ void FastRandomContext::RandomSeed() requires_seed = false; } -uint256 FastRandomContext::rand256() +uint256 FastRandomContext::rand256() noexcept { if (bytebuf_size < 32) { FillByteBuffer(); @@ -406,7 +559,7 @@ std::vector<unsigned char> FastRandomContext::randbytes(size_t len) return ret; } -FastRandomContext::FastRandomContext(const uint256& seed) : requires_seed(false), bytebuf_size(0), bitbuf_size(0) +FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bytebuf_size(0), bitbuf_size(0) { rng.SetKey(seed.begin(), 32); } @@ -449,13 +602,15 @@ bool Random_SanityCheck() if (stop == start) return false; // We called GetPerformanceCounter. Use it as entropy. - RAND_add((const unsigned char*)&start, sizeof(start), 1); - RAND_add((const unsigned char*)&stop, sizeof(stop), 1); + CSHA512 to_add; + to_add.Write((const unsigned char*)&start, sizeof(start)); + to_add.Write((const unsigned char*)&stop, sizeof(stop)); + GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false); return true; } -FastRandomContext::FastRandomContext(bool fDeterministic) : requires_seed(!fDeterministic), bytebuf_size(0), bitbuf_size(0) +FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bytebuf_size(0), bitbuf_size(0) { if (!fDeterministic) { return; @@ -480,5 +635,8 @@ FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexce void RandomInit() { - RDRandInit(); + // Invoke RNG code to trigger initialization (if not already performed) + ProcRand(nullptr, 0, RNGLevel::FAST); + + ReportHardwareRand(); } diff --git a/src/random.h b/src/random.h index 00e90abbc5..4c73f3822a 100644 --- a/src/random.h +++ b/src/random.h @@ -13,33 +13,83 @@ #include <stdint.h> #include <limits> -/* Seed OpenSSL PRNG with additional entropy data */ -void RandAddSeed(); +/** + * Overall design of the RNG and entropy sources. + * + * We maintain a single global 256-bit RNG state for all high-quality randomness. + * The following (classes of) functions interact with that state by mixing in new + * entropy, and optionally extracting random output from it: + * + * - The GetRand*() class of functions, as well as construction of FastRandomContext objects, + * perform 'fast' seeding, consisting of mixing in: + * - A stack pointer (indirectly committing to calling thread and call stack) + * - A high-precision timestamp (rdtsc when available, c++ high_resolution_clock otherwise) + * - Hardware RNG (rdrand) when available. + * These entropy sources are very fast, and only designed to protect against situations + * where a VM state restore/copy results in multiple systems with the same randomness. + * FastRandomContext on the other hand does not protect against this once created, but + * is even faster (and acceptable to use inside tight loops). + * + * - The GetStrongRand*() class of function perform 'slow' seeding, including everything + * that fast seeding includes, but additionally: + * - OS entropy (/dev/urandom, getrandom(), ...). The application will terminate if + * this entropy source fails. + * - Bytes from OpenSSL's RNG (which itself may be seeded from various sources) + * - Another high-precision timestamp (indirectly committing to a benchmark of all the + * previous sources). + * These entropy sources are slower, but designed to make sure the RNG state contains + * fresh data that is unpredictable to attackers. + * + * - RandAddSeedSleep() seeds everything that fast seeding includes, but additionally: + * - A high-precision timestamp before and after sleeping 1ms. + * - (On Windows) Once every 10 minutes, performance monitoring data from the OS. + * These just exploit the fact the system is idle to improve the quality of the RNG + * slightly. + * + * On first use of the RNG (regardless of what function is called first), all entropy + * sources used in the 'slow' seeder are included, but also: + * - (On Windows) Performance monitoring data from the OS. + * - (On Windows) Through OpenSSL, the screen contents. + * + * When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and + * (up to) the first 32 bytes of H are produced as output, while the last 32 bytes + * become the new RNG state. +*/ /** - * Functions to gather random data via the OpenSSL PRNG + * Generate random data via the internal PRNG. + * + * These functions are designed to be fast (sub microsecond), but do not necessarily + * meaningfully add entropy to the PRNG state. + * + * Thread-safe. */ -void GetRandBytes(unsigned char* buf, int num); -uint64_t GetRand(uint64_t nMax); -int GetRandInt(int nMax); -uint256 GetRandHash(); +void GetRandBytes(unsigned char* buf, int num) noexcept; +uint64_t GetRand(uint64_t nMax) noexcept; +int GetRandInt(int nMax) noexcept; +uint256 GetRandHash() noexcept; /** - * Add a little bit of randomness to the output of GetStrongRangBytes. - * This sleeps for a millisecond, so should only be called when there is - * no other work to be done. + * Gather entropy from various sources, feed it into the internal PRNG, and + * generate random data using it. + * + * This function will cause failure whenever the OS RNG fails. + * + * Thread-safe. */ -void RandAddSeedSleep(); +void GetStrongRandBytes(unsigned char* buf, int num) noexcept; /** - * Function to gather random data from multiple sources, failing whenever any - * of those sources fail to provide a result. + * Sleep for 1ms, gather entropy from various sources, and feed them to the PRNG state. + * + * Thread-safe. */ -void GetStrongRandBytes(unsigned char* buf, int num); +void RandAddSeedSleep(); /** * Fast randomness source. This is seeded once with secure random data, but - * is completely deterministic and insecure after that. + * is completely deterministic and does not gather more entropy after that. + * * This class is not thread-safe. */ class FastRandomContext { @@ -71,10 +121,10 @@ private: } public: - explicit FastRandomContext(bool fDeterministic = false); + explicit FastRandomContext(bool fDeterministic = false) noexcept; /** Initialize with explicit seed (only for testing) */ - explicit FastRandomContext(const uint256& seed); + explicit FastRandomContext(const uint256& seed) noexcept; // Do not permit copying a FastRandomContext (move it, or create a new one to get reseeded). FastRandomContext(const FastRandomContext&) = delete; @@ -85,7 +135,7 @@ public: FastRandomContext& operator=(FastRandomContext&& from) noexcept; /** Generate a random 64-bit integer. */ - uint64_t rand64() + uint64_t rand64() noexcept { if (bytebuf_size < 8) FillByteBuffer(); uint64_t ret = ReadLE64(bytebuf + 64 - bytebuf_size); @@ -94,7 +144,7 @@ public: } /** Generate a random (bits)-bit integer. */ - uint64_t randbits(int bits) { + uint64_t randbits(int bits) noexcept { if (bits == 0) { return 0; } else if (bits > 32) { @@ -109,7 +159,7 @@ public: } /** Generate a random integer in the range [0..range). */ - uint64_t randrange(uint64_t range) + uint64_t randrange(uint64_t range) noexcept { --range; int bits = CountBits(range); @@ -123,19 +173,19 @@ public: std::vector<unsigned char> randbytes(size_t len); /** Generate a random 32-bit integer. */ - uint32_t rand32() { return randbits(32); } + uint32_t rand32() noexcept { return randbits(32); } /** generate a random uint256. */ - uint256 rand256(); + uint256 rand256() noexcept; /** Generate a random boolean. */ - bool randbool() { return randbits(1); } + bool randbool() noexcept { return randbits(1); } // Compatibility with the C++11 UniformRandomBitGenerator concept typedef uint64_t result_type; static constexpr uint64_t min() { return 0; } static constexpr uint64_t max() { return std::numeric_limits<uint64_t>::max(); } - inline uint64_t operator()() { return rand64(); } + inline uint64_t operator()() noexcept { return rand64(); } }; /** More efficient than using std::shuffle on a FastRandomContext. @@ -178,7 +228,12 @@ void GetOSRand(unsigned char *ent32); */ bool Random_SanityCheck(); -/** Initialize the RNG. */ +/** + * Initialize global RNG state and log any CPU features that are used. + * + * Calling this function is optional. RNG state will be initialized when first + * needed if it is not called. + */ void RandomInit(); #endif // BITCOIN_RANDOM_H diff --git a/src/rest.cpp b/src/rest.cpp index 4f26e3afb5..c7a627d14e 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -575,6 +575,52 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart) } } +static bool rest_blockhash_by_height(HTTPRequest* req, + const std::string& str_uri_part) +{ + if (!CheckWarmup(req)) return false; + std::string height_str; + const RetFormat rf = ParseDataFormat(height_str, str_uri_part); + + int32_t blockheight; + if (!ParseInt32(height_str, &blockheight) || blockheight < 0) { + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid height: " + SanitizeString(height_str)); + } + + CBlockIndex* pblockindex = nullptr; + { + LOCK(cs_main); + if (blockheight > chainActive.Height()) { + return RESTERR(req, HTTP_NOT_FOUND, "Block height out of range"); + } + pblockindex = chainActive[blockheight]; + } + switch (rf) { + case RetFormat::BINARY: { + CDataStream ss_blockhash(SER_NETWORK, PROTOCOL_VERSION); + ss_blockhash << pblockindex->GetBlockHash(); + req->WriteHeader("Content-Type", "application/octet-stream"); + req->WriteReply(HTTP_OK, ss_blockhash.str()); + return true; + } + case RetFormat::HEX: { + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(HTTP_OK, pblockindex->GetBlockHash().GetHex() + "\n"); + return true; + } + case RetFormat::JSON: { + req->WriteHeader("Content-Type", "application/json"); + UniValue resp = UniValue(UniValue::VOBJ); + resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex()); + req->WriteReply(HTTP_OK, resp.write() + "\n"); + return true; + } + default: { + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + } + } +} + static const struct { const char* prefix; bool (*handler)(HTTPRequest* req, const std::string& strReq); @@ -587,6 +633,7 @@ static const struct { {"/rest/mempool/contents", rest_mempool_contents}, {"/rest/headers/", rest_headers}, {"/rest/getutxos", rest_getutxos}, + {"/rest/blockhashbyheight/", rest_blockhash_by_height}, }; void StartREST() diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 315f69d46b..86cf121da2 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -162,14 +162,16 @@ static UniValue getblockcount(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getblockcount", - "\nReturns the number of blocks in the longest blockchain.\n", {}} - .ToString() + - "\nResult:\n" + "\nReturns the number of blocks in the longest blockchain.\n", + {}, + RPCResult{ "n (numeric) The current block count\n" - "\nExamples:\n" - + HelpExampleCli("getblockcount", "") + }, + RPCExamples{ + HelpExampleCli("getblockcount", "") + HelpExampleRpc("getblockcount", "") - ); + }, + }.ToString()); LOCK(cs_main); return chainActive.Height(); @@ -180,14 +182,16 @@ static UniValue getbestblockhash(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getbestblockhash", - "\nReturns the hash of the best (tip) block in the longest blockchain.\n", {}} - .ToString() + - "\nResult:\n" + "\nReturns the hash of the best (tip) block in the longest blockchain.\n", + {}, + RPCResult{ "\"hex\" (string) the block hash, hex-encoded\n" - "\nExamples:\n" - + HelpExampleCli("getbestblockhash", "") + }, + RPCExamples{ + HelpExampleCli("getbestblockhash", "") + HelpExampleRpc("getbestblockhash", "") - ); + }, + }.ToString()); LOCK(cs_main); return chainActive.Tip()->GetBlockHash().GetHex(); @@ -212,17 +216,18 @@ static UniValue waitfornewblock(const JSONRPCRequest& request) "\nReturns the current block on timeout or exit.\n", { {"timeout", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Time in milliseconds to wait for a response. 0 indicates no timeout."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" " \"height\" : { (int) Block height\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("waitfornewblock", "1000") + }, + RPCExamples{ + HelpExampleCli("waitfornewblock", "1000") + HelpExampleRpc("waitfornewblock", "1000") - ); + }, + }.ToString()); int timeout = 0; if (!request.params[0].isNull()) timeout = request.params[0].get_int(); @@ -253,17 +258,18 @@ static UniValue waitforblock(const JSONRPCRequest& request) { {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "Block hash to wait for."}, {"timeout", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Time in milliseconds to wait for a response. 0 indicates no timeout."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" " \"height\" : { (int) Block height\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000") + }, + RPCExamples{ + HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000") + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000") - ); + }, + }.ToString()); int timeout = 0; uint256 hash(ParseHashV(request.params[0], "blockhash")); @@ -298,17 +304,18 @@ static UniValue waitforblockheight(const JSONRPCRequest& request) { {"height", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "Block height to wait for."}, {"timeout", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Time in milliseconds to wait for a response. 0 indicates no timeout."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" " \"height\" : { (int) Block height\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("waitforblockheight", "\"100\", 1000") + }, + RPCExamples{ + HelpExampleCli("waitforblockheight", "\"100\", 1000") + HelpExampleRpc("waitforblockheight", "\"100\", 1000") - ); + }, + }.ToString()); int timeout = 0; int height = request.params[0].get_int(); @@ -336,12 +343,14 @@ static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( RPCHelpMan{"syncwithvalidationinterfacequeue", - "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n", {}} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("syncwithvalidationinterfacequeue","") + "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n", + {}, + RPCResults{}, + RPCExamples{ + HelpExampleCli("syncwithvalidationinterfacequeue","") + HelpExampleRpc("syncwithvalidationinterfacequeue","") - ); + }, + }.ToString()); } SyncWithValidationInterfaceQueue(); return NullUniValue; @@ -352,14 +361,16 @@ static UniValue getdifficulty(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getdifficulty", - "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n", {}} - .ToString() + - "\nResult:\n" + "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n", + {}, + RPCResult{ "n.nnn (numeric) the proof-of-work difficulty as a multiple of the minimum difficulty.\n" - "\nExamples:\n" - + HelpExampleCli("getdifficulty", "") + }, + RPCExamples{ + HelpExampleCli("getdifficulty", "") + HelpExampleRpc("getdifficulty", "") - ); + }, + }.ToString()); LOCK(cs_main); return GetDifficulty(chainActive.Tip()); @@ -491,9 +502,8 @@ static UniValue getrawmempool(const JSONRPCRequest& request) "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n", { {"verbose", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "True for a json object, false for array of transaction ids"}, - }} - .ToString() + - "\nResult: (for verbose = false):\n" + }, + RPCResult{"for verbose = false", "[ (json array of string)\n" " \"transactionid\" (string) The transaction id\n" " ,...\n" @@ -504,10 +514,12 @@ static UniValue getrawmempool(const JSONRPCRequest& request) + EntryDescriptionString() + " }, ...\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getrawmempool", "true") + }, + RPCExamples{ + HelpExampleCli("getrawmempool", "true") + HelpExampleRpc("getrawmempool", "true") - ); + }, + }.ToString()); bool fVerbose = false; if (!request.params[0].isNull()) @@ -525,23 +537,27 @@ static UniValue getmempoolancestors(const JSONRPCRequest& request) { {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id (must be in mempool)"}, {"verbose", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "True for a json object, false for array of transaction ids"}, - }} - .ToString() + - "\nResult (for verbose = false):\n" + }, + { + RPCResult{"for verbose = false", "[ (json array of strings)\n" " \"transactionid\" (string) The transaction id of an in-mempool ancestor transaction\n" " ,...\n" "]\n" - "\nResult (for verbose = true):\n" + }, + RPCResult{"for verbose = true", "{ (json object)\n" " \"transactionid\" : { (json object)\n" + EntryDescriptionString() + " }, ...\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getmempoolancestors", "\"mytxid\"") + }, + }, + RPCExamples{ + HelpExampleCli("getmempoolancestors", "\"mytxid\"") + HelpExampleRpc("getmempoolancestors", "\"mytxid\"") - ); + }, + }.ToString()); } bool fVerbose = false; @@ -591,23 +607,27 @@ static UniValue getmempooldescendants(const JSONRPCRequest& request) { {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id (must be in mempool)"}, {"verbose", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "True for a json object, false for array of transaction ids"}, - }} - .ToString() + - "\nResult (for verbose = false):\n" + }, + { + RPCResult{"for verbose = false", "[ (json array of strings)\n" " \"transactionid\" (string) The transaction id of an in-mempool descendant transaction\n" " ,...\n" "]\n" - "\nResult (for verbose = true):\n" + }, + RPCResult{"for verbose = true", "{ (json object)\n" " \"transactionid\" : { (json object)\n" + EntryDescriptionString() + " }, ...\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getmempooldescendants", "\"mytxid\"") + }, + }, + RPCExamples{ + HelpExampleCli("getmempooldescendants", "\"mytxid\"") + HelpExampleRpc("getmempooldescendants", "\"mytxid\"") - ); + }, + }.ToString()); } bool fVerbose = false; @@ -656,16 +676,17 @@ static UniValue getmempoolentry(const JSONRPCRequest& request) "\nReturns mempool data for given transaction\n", { {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id (must be in mempool)"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{ (json object)\n" + EntryDescriptionString() + "}\n" - "\nExamples:\n" - + HelpExampleCli("getmempoolentry", "\"mytxid\"") + }, + RPCExamples{ + HelpExampleCli("getmempoolentry", "\"mytxid\"") + HelpExampleRpc("getmempoolentry", "\"mytxid\"") - ); + }, + }.ToString()); } uint256 hash = ParseHashV(request.params[0], "parameter 1"); @@ -691,14 +712,15 @@ static UniValue getblockhash(const JSONRPCRequest& request) "\nReturns hash of block in best-block-chain at height provided.\n", { {"height", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The height index"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"hash\" (string) The block hash\n" - "\nExamples:\n" - + HelpExampleCli("getblockhash", "1000") + }, + RPCExamples{ + HelpExampleCli("getblockhash", "1000") + HelpExampleRpc("getblockhash", "1000") - ); + }, + }.ToString()); LOCK(cs_main); @@ -720,9 +742,9 @@ static UniValue getblockheader(const JSONRPCRequest& request) { {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The block hash"}, {"verbose", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "true for a json object, false for the hex-encoded data"}, - }} - .ToString() + - "\nResult (for verbose = true):\n" + }, + { + RPCResult{"for verbose = true", "{\n" " \"hash\" : \"hash\", (string) the block hash (same as provided)\n" " \"confirmations\" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain\n" @@ -740,12 +762,16 @@ static UniValue getblockheader(const JSONRPCRequest& request) " \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n" " \"nextblockhash\" : \"hash\", (string) The hash of the next block\n" "}\n" - "\nResult (for verbose=false):\n" + }, + RPCResult{"for verbose=false", "\"data\" (string) A string that is serialized, hex-encoded data for block 'hash'.\n" - "\nExamples:\n" - + HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + }, + }, + RPCExamples{ + HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") - ); + }, + }.ToString()); uint256 hash(ParseHashV(request.params[0], "hash")); @@ -806,11 +832,12 @@ static UniValue getblock(const JSONRPCRequest& request) { {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The block hash"}, {"verbosity", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "0 for hex-encoded data, 1 for a json object, and 2 for json object with transaction data"}, - }} - .ToString() + - "\nResult (for verbosity = 0):\n" + }, + { + RPCResult{"for verbosity = 0", "\"data\" (string) A string that is serialized, hex-encoded data for block 'hash'.\n" - "\nResult (for verbosity = 1):\n" + }, + RPCResult{"for verbosity = 1", "{\n" " \"hash\" : \"hash\", (string) the block hash (same as provided)\n" " \"confirmations\" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain\n" @@ -835,7 +862,8 @@ static UniValue getblock(const JSONRPCRequest& request) " \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n" " \"nextblockhash\" : \"hash\" (string) The hash of the next block\n" "}\n" - "\nResult (for verbosity = 2):\n" + }, + RPCResult{"for verbosity = 2", "{\n" " ..., Same output as verbosity = 1.\n" " \"tx\" : [ (array of Objects) The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 \"tx\" result.\n" @@ -843,10 +871,13 @@ static UniValue getblock(const JSONRPCRequest& request) " ],\n" " ,... Same output as verbosity = 1.\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + }, + }, + RPCExamples{ + HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") - ); + }, + }.ToString()); LOCK(cs_main); @@ -957,13 +988,15 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) { {"height", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The block height to prune up to. May be set to a discrete height, or a unix timestamp\n" " to prune blocks whose block time is at least 2 hours older than the provided timestamp."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "n (numeric) Height of the last block pruned.\n" - "\nExamples:\n" - + HelpExampleCli("pruneblockchain", "1000") - + HelpExampleRpc("pruneblockchain", "1000")); + }, + RPCExamples{ + HelpExampleCli("pruneblockchain", "1000") + + HelpExampleRpc("pruneblockchain", "1000") + }, + }.ToString()); if (!fPruneMode) throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode."); @@ -1007,9 +1040,8 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request) RPCHelpMan{"gettxoutsetinfo", "\nReturns statistics about the unspent transaction output set.\n" "Note this call may take some time.\n", - {}} - .ToString() + - "\nResult:\n" + {}, + RPCResult{ "{\n" " \"height\":n, (numeric) The current block height (index)\n" " \"bestblock\": \"hex\", (string) The hash of the block at the tip of the chain\n" @@ -1020,10 +1052,12 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request) " \"disk_size\": n, (numeric) The estimated size of the chainstate on disk\n" " \"total_amount\": x.xxx (numeric) The total amount\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("gettxoutsetinfo", "") + }, + RPCExamples{ + HelpExampleCli("gettxoutsetinfo", "") + HelpExampleRpc("gettxoutsetinfo", "") - ); + }, + }.ToString()); UniValue ret(UniValue::VOBJ); @@ -1054,9 +1088,8 @@ UniValue gettxout(const JSONRPCRequest& request) {"txid", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The transaction id"}, {"n", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "vout number"}, {"include_mempool", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Whether to include the mempool. Note that an unspent output that is spent in the mempool won't appear."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"bestblock\": \"hash\", (string) The hash of the block at the tip of the chain\n" " \"confirmations\" : n, (numeric) The number of confirmations\n" @@ -1073,15 +1106,16 @@ UniValue gettxout(const JSONRPCRequest& request) " },\n" " \"coinbase\" : true|false (boolean) Coinbase or not\n" "}\n" - - "\nExamples:\n" + }, + 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") - ); + }, + }.ToString()); LOCK(cs_main); @@ -1134,14 +1168,15 @@ static UniValue verifychain(const JSONRPCRequest& request) { {"checklevel", RPCArg::Type::NUM, /* opt */ true, /* default_val */ strprintf("%d, range=0-4", nCheckLevel), "How thorough the block verification is."}, {"nblocks", RPCArg::Type::NUM, /* opt */ true, /* default_val */ strprintf("%d, 0=all", nCheckDepth), "The number of blocks to check."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "true|false (boolean) Verified or not\n" - "\nExamples:\n" - + HelpExampleCli("verifychain", "") + }, + RPCExamples{ + HelpExampleCli("verifychain", "") + HelpExampleRpc("verifychain", "") - ); + }, + }.ToString()); LOCK(cs_main); @@ -1229,9 +1264,9 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getblockchaininfo", - "Returns an object containing various state info regarding blockchain processing.\n", {}} - .ToString() + - "\nResult:\n" + "Returns an object containing various state info regarding blockchain processing.\n", + {}, + RPCResult{ "{\n" " \"chain\": \"xxxx\", (string) current network name as defined in BIP70 (main, test, regtest)\n" " \"blocks\": xxxxxx, (numeric) the current number of blocks processed in the server\n" @@ -1274,10 +1309,12 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) " }\n" " \"warnings\" : \"...\", (string) any network and blockchain warnings.\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getblockchaininfo", "") + }, + RPCExamples{ + HelpExampleCli("getblockchaininfo", "") + HelpExampleRpc("getblockchaininfo", "") - ); + }, + }.ToString()); LOCK(cs_main); @@ -1349,9 +1386,8 @@ static UniValue getchaintips(const JSONRPCRequest& request) RPCHelpMan{"getchaintips", "Return information about all known tips in the block tree," " including the main chain as well as orphaned branches.\n", - {}} - .ToString() + - "\nResult:\n" + {}, + RPCResult{ "[\n" " {\n" " \"height\": xxxx, (numeric) height of the chain tip\n" @@ -1372,10 +1408,12 @@ static UniValue getchaintips(const JSONRPCRequest& request) "3. \"valid-headers\" All blocks are available for this branch, but they were never fully validated\n" "4. \"valid-fork\" This branch is not part of the active chain, but is fully validated\n" "5. \"active\" This is the tip of the active main chain, which is certainly valid\n" - "\nExamples:\n" - + HelpExampleCli("getchaintips", "") + }, + RPCExamples{ + HelpExampleCli("getchaintips", "") + HelpExampleRpc("getchaintips", "") - ); + }, + }.ToString()); LOCK(cs_main); @@ -1466,9 +1504,9 @@ static UniValue getmempoolinfo(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getmempoolinfo", - "\nReturns details on the active state of the TX memory pool.\n", {}} - .ToString() + - "\nResult:\n" + "\nReturns details on the active state of the TX memory pool.\n", + {}, + RPCResult{ "{\n" " \"size\": xxxxx, (numeric) Current tx count\n" " \"bytes\": xxxxx, (numeric) Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted\n" @@ -1477,10 +1515,12 @@ static UniValue getmempoolinfo(const JSONRPCRequest& request) " \"mempoolminfee\": xxxxx (numeric) Minimum fee rate in " + CURRENCY_UNIT + "/kB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee\n" " \"minrelaytxfee\": xxxxx (numeric) Current minimum relay fee for transactions\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getmempoolinfo", "") + }, + RPCExamples{ + HelpExampleCli("getmempoolinfo", "") + HelpExampleRpc("getmempoolinfo", "") - ); + }, + }.ToString()); return mempoolInfoToJSON(); } @@ -1495,13 +1535,13 @@ static UniValue preciousblock(const JSONRPCRequest& request) "\nThe effects of preciousblock are not retained across restarts.\n", { {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hash of the block to mark as precious"}, - }} - .ToString() + - "\nResult:\n" - "\nExamples:\n" - + HelpExampleCli("preciousblock", "\"blockhash\"") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("preciousblock", "\"blockhash\"") + HelpExampleRpc("preciousblock", "\"blockhash\"") - ); + }, + }.ToString()); uint256 hash(ParseHashV(request.params[0], "blockhash")); CBlockIndex* pblockindex; @@ -1532,13 +1572,13 @@ static UniValue invalidateblock(const JSONRPCRequest& request) "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n", { {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hash of the block to mark as invalid"}, - }} - .ToString() + - "\nResult:\n" - "\nExamples:\n" - + HelpExampleCli("invalidateblock", "\"blockhash\"") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("invalidateblock", "\"blockhash\"") + HelpExampleRpc("invalidateblock", "\"blockhash\"") - ); + }, + }.ToString()); uint256 hash(ParseHashV(request.params[0], "blockhash")); CValidationState state; @@ -1573,13 +1613,13 @@ static UniValue reconsiderblock(const JSONRPCRequest& request) "This can be used to undo the effects of invalidateblock.\n", { {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hash of the block to reconsider"}, - }} - .ToString() + - "\nResult:\n" - "\nExamples:\n" - + HelpExampleCli("reconsiderblock", "\"blockhash\"") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("reconsiderblock", "\"blockhash\"") + HelpExampleRpc("reconsiderblock", "\"blockhash\"") - ); + }, + }.ToString()); uint256 hash(ParseHashV(request.params[0], "blockhash")); @@ -1612,9 +1652,8 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) { {"nblocks", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "one month", "Size of the window in number of blocks"}, {"blockhash", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "chain tip", "The hash of the block that ends the window."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"time\": xxxxx, (numeric) The timestamp for the final block in the window in UNIX format.\n" " \"txcount\": xxxxx, (numeric) The total number of transactions in the chain up to that point.\n" @@ -1624,10 +1663,12 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) " \"window_interval\": xxxxx, (numeric) The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0.\n" " \"txrate\": x.xx, (numeric) The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0.\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getchaintxstats", "") + }, + RPCExamples{ + HelpExampleCli("getchaintxstats", "") + HelpExampleRpc("getchaintxstats", "2016") - ); + }, + }.ToString()); const CBlockIndex* pindex; int blockcount = 30 * 24 * 60 * 60 / Params().GetConsensus().nPowTargetSpacing; // By default: 1 month @@ -1751,9 +1792,8 @@ static UniValue getblockstats(const JSONRPCRequest& request) {"time", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "Selected statistic"}, }, "stats"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{ (json object)\n" " \"avgfee\": xxxxx, (numeric) Average fee in the block\n" " \"avgfeerate\": xxxxx, (numeric) Average feerate (in satoshis per virtual byte)\n" @@ -1791,10 +1831,12 @@ static UniValue getblockstats(const JSONRPCRequest& request) " \"utxo_increase\": xxxxx, (numeric) The increase/decrease in the number of unspent outputs\n" " \"utxo_size_inc\": xxxxx, (numeric) The increase/decrease in size for the utxo index (not discounting op_return and similar)\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") + }, + RPCExamples{ + HelpExampleCli("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") + HelpExampleRpc("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") - ); + }, + }.ToString()); } LOCK(cs_main); @@ -2006,12 +2048,14 @@ static UniValue savemempool(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"savemempool", - "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n", {}} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("savemempool", "") + "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n", + {}, + RPCResults{}, + RPCExamples{ + HelpExampleCli("savemempool", "") + HelpExampleRpc("savemempool", "") - ); + }, + }.ToString()); } if (!g_is_mempool_loaded) { @@ -2120,9 +2164,8 @@ UniValue scantxoutset(const JSONRPCRequest& request) }, }, "[scanobjects,...]"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"unspents\": [\n" " {\n" @@ -2136,6 +2179,9 @@ UniValue scantxoutset(const JSONRPCRequest& request) " ,...], \n" " \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n" "]\n" + }, + RPCExamples{""}, + }.ToString() ); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 2fd6f99be5..200dfa107b 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -94,14 +94,15 @@ static UniValue getnetworkhashps(const JSONRPCRequest& request) { {"nblocks", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "120", "The number of blocks, or -1 for blocks since last difficulty change."}, {"height", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "-1", "To estimate at the time of the given height."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "x (numeric) Hashes per second estimated\n" - "\nExamples:\n" - + HelpExampleCli("getnetworkhashps", "") + }, + RPCExamples{ + HelpExampleCli("getnetworkhashps", "") + HelpExampleRpc("getnetworkhashps", "") - ); + }, + }.ToString()); LOCK(cs_main); return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1); @@ -165,16 +166,17 @@ static UniValue generatetoaddress(const JSONRPCRequest& request) {"nblocks", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "How many blocks are generated immediately."}, {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The address to send the newly generated bitcoin to."}, {"maxtries", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1000000", "How many iterations to try."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "[ blockhashes ] (array) hashes of blocks generated\n" - "\nExamples:\n" + }, + RPCExamples{ "\nGenerate 11 blocks to myaddress\n" + HelpExampleCli("generatetoaddress", "11 \"myaddress\"") + "If you are running the bitcoin core wallet, you can get a new address to send the newly generated bitcoin to with:\n" + HelpExampleCli("getnewaddress", "") - ); + }, + }.ToString()); int nGenerate = request.params[0].get_int(); uint64_t nMaxTries = 1000000; @@ -198,9 +200,9 @@ static UniValue getmininginfo(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getmininginfo", - "\nReturns a json object containing mining-related information.", {}} - .ToString() + - "\nResult:\n" + "\nReturns a json object containing mining-related information.", + {}, + RPCResult{ "{\n" " \"blocks\": nnn, (numeric) The current block\n" " \"currentblockweight\": nnn, (numeric) The last block weight\n" @@ -211,10 +213,12 @@ static UniValue getmininginfo(const JSONRPCRequest& request) " \"chain\": \"xxxx\", (string) current network name as defined in BIP70 (main, test, regtest)\n" " \"warnings\": \"...\" (string) any network and blockchain warnings\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getmininginfo", "") + }, + RPCExamples{ + HelpExampleCli("getmininginfo", "") + HelpExampleRpc("getmininginfo", "") - ); + }, + }.ToString()); LOCK(cs_main); @@ -247,14 +251,15 @@ static UniValue prioritisetransaction(const JSONRPCRequest& request) " Note, that this value is not a fee rate. It is a value to modify absolute fee of the TX.\n" " The fee is not actually paid, only the algorithm for selecting transactions into a block\n" " considers the transaction as it would have paid a higher (or lower) fee."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "true (boolean) Returns true\n" - "\nExamples:\n" - + HelpExampleCli("prioritisetransaction", "\"txid\" 0.0 10000") + }, + RPCExamples{ + HelpExampleCli("prioritisetransaction", "\"txid\" 0.0 10000") + HelpExampleRpc("prioritisetransaction", "\"txid\", 0.0, 10000") - ); + }, + }.ToString()); LOCK(cs_main); @@ -326,9 +331,8 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) }, }, "\"template_request\""}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"version\" : n, (numeric) The preferred block version\n" " \"rules\" : [ \"rulename\", ... ], (array of strings) specific block rules that are to be enforced\n" @@ -372,11 +376,12 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) " \"bits\" : \"xxxxxxxx\", (string) compressed target of next block\n" " \"height\" : n (numeric) The height of the next block\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("getblocktemplate", "{\"rules\": [\"segwit\"]}") + }, + RPCExamples{ + HelpExampleCli("getblocktemplate", "{\"rules\": [\"segwit\"]}") + HelpExampleRpc("getblocktemplate", "{\"rules\": [\"segwit\"]}") - ); + }, + }.ToString()); LOCK(cs_main); @@ -713,13 +718,13 @@ static UniValue submitblock(const JSONRPCRequest& request) { {"hexdata", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hex-encoded block data to submit"}, {"dummy", RPCArg::Type::STR, /* opt */ true, /* default_val */ "ignored", "dummy value, for compatibility with BIP22. This value is ignored."}, - }} - .ToString() + - "\nResult:\n" - "\nExamples:\n" - + HelpExampleCli("submitblock", "\"mydata\"") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("submitblock", "\"mydata\"") + HelpExampleRpc("submitblock", "\"mydata\"") - ); + }, + }.ToString()); } std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>(); @@ -777,13 +782,15 @@ static UniValue submitheader(const JSONRPCRequest& request) "\nThrows when the header is invalid.\n", { {"hexdata", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hex-encoded block header data"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "None" - "\nExamples:\n" + - HelpExampleCli("submitheader", "\"aabbcc\"") + - HelpExampleRpc("submitheader", "\"aabbcc\"")); + }, + RPCExamples{ + HelpExampleCli("submitheader", "\"aabbcc\"") + + HelpExampleRpc("submitheader", "\"aabbcc\"") + }, + }.ToString()); } CBlockHeader h; @@ -826,9 +833,8 @@ static UniValue estimatesmartfee(const JSONRPCRequest& request) " \"UNSET\"\n" " \"ECONOMICAL\"\n" " \"CONSERVATIVE\""}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"feerate\" : x.x, (numeric, optional) estimate fee rate in " + CURRENCY_UNIT + "/kB\n" " \"errors\": [ str... ] (json array of strings, optional) Errors encountered during processing\n" @@ -839,9 +845,11 @@ static UniValue estimatesmartfee(const JSONRPCRequest& request) "fee estimation is able to return based on how long it has been running.\n" "An error is returned if not enough transactions and blocks\n" "have been observed to make an estimate for any number of blocks.\n" - "\nExample:\n" - + HelpExampleCli("estimatesmartfee", "6") - ); + }, + RPCExamples{ + HelpExampleCli("estimatesmartfee", "6") + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); @@ -886,9 +894,8 @@ static UniValue estimaterawfee(const JSONRPCRequest& request) {"threshold", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0.95", "The proportion of transactions in a given feerate range that must have been\n" " confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n" " lower buckets."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"short\" : { (json object, optional) estimate for short time horizon\n" " \"feerate\" : x.x, (numeric, optional) estimate fee rate in " + CURRENCY_UNIT + "/kB\n" @@ -910,9 +917,11 @@ static UniValue estimaterawfee(const JSONRPCRequest& request) "}\n" "\n" "Results are returned for any horizon which tracks blocks up to the confirmation target.\n" - "\nExample:\n" - + HelpExampleCli("estimaterawfee", "6 0.9") - ); + }, + RPCExamples{ + HelpExampleCli("estimaterawfee", "6 0.9") + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 18c867bff3..dc3745858b 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -40,9 +40,8 @@ static UniValue validateaddress(const JSONRPCRequest& request) "script, hex, pubkeys, sigsrequired, pubkey, addresses, embedded, iscompressed, account, timestamp, hdkeypath, kdmasterkeyid.\n", { {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to validate"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"isvalid\" : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.\n" " \"address\" : \"address\", (string) The bitcoin address validated\n" @@ -52,10 +51,12 @@ static UniValue validateaddress(const JSONRPCRequest& request) " \"witness_version\" : version (numeric, optional) The version number of the witness program\n" " \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + }, + RPCExamples{ + HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") - ); + }, + }.ToString()); CTxDestination dest = DecodeDestination(request.params[0].get_str()); bool isValid = IsValidDestination(dest); @@ -91,20 +92,20 @@ static UniValue createmultisig(const JSONRPCRequest& request) {"key", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex-encoded public key"}, }}, {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "legacy", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n" " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n" "}\n" - - "\nExamples:\n" + }, + RPCExamples{ "\nCreate a multisig address from 2 public keys\n" + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") - ; + }, + }.ToString(); throw std::runtime_error(msg); } @@ -151,11 +152,11 @@ static UniValue verifymessage(const JSONRPCRequest& request) {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to use for the signature."}, {"signature", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The signature provided by the signer in base 64 encoding (see signmessage)."}, {"message", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The message that was signed."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "true|false (boolean) If the signature is verified or not.\n" - "\nExamples:\n" + }, + RPCExamples{ "\nUnlock the wallet for 30 seconds\n" + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + "\nCreate the signature\n" @@ -164,7 +165,8 @@ static UniValue verifymessage(const JSONRPCRequest& request) + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"") - ); + }, + }.ToString()); LOCK(cs_main); @@ -208,18 +210,19 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request) { {"privkey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The private key to sign the message with."}, {"message", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The message to create a signature of."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"signature\" (string) The signature of the message encoded in base 64\n" - "\nExamples:\n" + }, + RPCExamples{ "\nCreate the signature\n" + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"") - ); + }, + }.ToString()); std::string strPrivkey = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); @@ -249,8 +252,10 @@ static UniValue setmocktime(const JSONRPCRequest& request) { {"timestamp", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "Unix seconds-since-epoch timestamp\n" " Pass 0 to go back to using the system time."}, - }} - .ToString() + }, + RPCResults{}, + RPCExamples{""}, + }.ToString() ); if (!Params().MineBlocksOnDemand()) @@ -314,9 +319,9 @@ static UniValue getmemoryinfo(const JSONRPCRequest& request) {"mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "\"stats\"", "determines what kind of information is returned.\n" " - \"stats\" returns general statistics about memory usage in the daemon.\n" " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+)."}, - }} - .ToString() + - "\nResult (mode \"stats\"):\n" + }, + { + RPCResult{"mode \"stats\"", "{\n" " \"locked\": { (json object) Information about locked memory manager\n" " \"used\": xxxxx, (numeric) Number of bytes used\n" @@ -327,12 +332,16 @@ static UniValue getmemoryinfo(const JSONRPCRequest& request) " \"chunks_free\": xxxxx, (numeric) Number unused chunks\n" " }\n" "}\n" - "\nResult (mode \"mallocinfo\"):\n" + }, + RPCResult{"mode \"mallocinfo\"", "\"<malloc version=\"1\">...\"\n" - "\nExamples:\n" - + HelpExampleCli("getmemoryinfo", "") + }, + }, + RPCExamples{ + HelpExampleCli("getmemoryinfo", "") + HelpExampleRpc("getmemoryinfo", "") - ); + }, + }.ToString()); std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str(); if (mode == "stats") { @@ -392,17 +401,18 @@ UniValue logging(const JSONRPCRequest& request) { {"exclude_category", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "the valid logging category"}, }}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{ (json object where keys are the logging categories, and values indicates its status\n" " \"category\": 0|1, (numeric) if being debug logged or not. 0:inactive, 1:active\n" " ...\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") + }, + RPCExamples{ + HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") + HelpExampleRpc("logging", "[\"all\"], \"[libevent]\"") - ); + }, + }.ToString()); } uint32_t original_log_categories = g_logger->GetCategoryMask(); @@ -446,9 +456,11 @@ static UniValue echo(const JSONRPCRequest& request) "\nSimply echo back the input arguments. This command is for testing.\n" "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in " "bitcoin-cli and the GUI. There is no server-side difference.", - {}} - .ToString() + - ""); + {}, + RPCResults{}, + RPCExamples{""}, + }.ToString() + ); return request.params; } diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 6fdf80dc5f..b50038b085 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -4,6 +4,7 @@ #include <rpc/server.h> +#include <banman.h> #include <chainparams.h> #include <clientversion.h> #include <core_io.h> @@ -29,14 +30,16 @@ static UniValue getconnectioncount(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getconnectioncount", - "\nReturns the number of connections to other nodes.\n", {}} - .ToString() + - "\nResult:\n" + "\nReturns the number of connections to other nodes.\n", + {}, + RPCResult{ "n (numeric) The connection count\n" - "\nExamples:\n" - + HelpExampleCli("getconnectioncount", "") + }, + RPCExamples{ + HelpExampleCli("getconnectioncount", "") + HelpExampleRpc("getconnectioncount", "") - ); + }, + }.ToString()); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -52,12 +55,13 @@ static UniValue ping(const JSONRPCRequest& request) "\nRequests that a ping be sent to all other nodes, to measure ping time.\n" "Results provided in getpeerinfo, pingtime and pingwait fields are decimal seconds.\n" "Ping command is handled in queue with all other commands, so it measures processing backlog, not just network ping.\n", - {}} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("ping", "") + {}, + RPCResults{}, + RPCExamples{ + HelpExampleCli("ping", "") + HelpExampleRpc("ping", "") - ); + }, + }.ToString()); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -74,9 +78,9 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getpeerinfo", - "\nReturns data about each connected network node as a json array of objects.\n", {}} - .ToString() + - "\nResult:\n" + "\nReturns data about each connected network node as a json array of objects.\n", + {}, + RPCResult{ "[\n" " {\n" " \"id\": n, (numeric) Peer index\n" @@ -123,10 +127,12 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) " }\n" " ,...\n" "]\n" - "\nExamples:\n" - + HelpExampleCli("getpeerinfo", "") + }, + RPCExamples{ + HelpExampleCli("getpeerinfo", "") + HelpExampleRpc("getpeerinfo", "") - ); + }, + }.ToString()); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -217,12 +223,13 @@ static UniValue addnode(const JSONRPCRequest& request) { {"node", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The node (see getpeerinfo for nodes)"}, {"command", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "'add' to add a node to the list, 'remove' to remove a node from the list, 'onetry' to try a connection to the node once"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"") + HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\"") - ); + }, + }.ToString()); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -261,14 +268,15 @@ static UniValue disconnectnode(const JSONRPCRequest& request) { {"address", RPCArg::Type::STR, /* opt */ true, /* default_val */ "fallback to nodeid", "The IP address/port of the node"}, {"nodeid", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to address", "The node ID (see getpeerinfo for node IDs)"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") + HelpExampleCli("disconnectnode", "\"\" 1") + HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") + HelpExampleRpc("disconnectnode", "\"\", 1") - ); + }, + }.ToString()); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -304,9 +312,8 @@ static UniValue getaddednodeinfo(const JSONRPCRequest& request) "(note that onetry addnodes are not listed here)\n", { {"node", RPCArg::Type::STR, /* opt */ true, /* default_val */ "all nodes", "If provided, return information about this specific node, otherwise all nodes are returned."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "[\n" " {\n" " \"addednode\" : \"192.168.0.201\", (string) The node IP address or name (as provided to addnode)\n" @@ -320,10 +327,12 @@ static UniValue getaddednodeinfo(const JSONRPCRequest& request) " }\n" " ,...\n" "]\n" - "\nExamples:\n" - + HelpExampleCli("getaddednodeinfo", "\"192.168.0.201\"") + }, + RPCExamples{ + HelpExampleCli("getaddednodeinfo", "\"192.168.0.201\"") + HelpExampleRpc("getaddednodeinfo", "\"192.168.0.201\"") - ); + }, + }.ToString()); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -371,9 +380,8 @@ static UniValue getnettotals(const JSONRPCRequest& request) RPCHelpMan{"getnettotals", "\nReturns information about network traffic, including bytes in, bytes out,\n" "and current time.\n", - {}} - .ToString() + - "\nResult:\n" + {}, + RPCResult{ "{\n" " \"totalbytesrecv\": n, (numeric) Total bytes received\n" " \"totalbytessent\": n, (numeric) Total bytes sent\n" @@ -388,10 +396,12 @@ static UniValue getnettotals(const JSONRPCRequest& request) " \"time_left_in_cycle\": t (numeric) Seconds left in current time cycle\n" " }\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getnettotals", "") + }, + RPCExamples{ + HelpExampleCli("getnettotals", "") + HelpExampleRpc("getnettotals", "") - ); + }, + }.ToString()); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -437,9 +447,9 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getnetworkinfo", - "Returns an object containing various state info regarding P2P networking.\n", {}} - .ToString() + - "\nResult:\n" + "Returns an object containing various state info regarding P2P networking.\n", + {}, + RPCResult{ "{\n" " \"version\": xxxxx, (numeric) the server version\n" " \"subversion\": \"/Satoshi:x.x.x/\", (string) the server subversion string\n" @@ -471,10 +481,12 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) " ]\n" " \"warnings\": \"...\" (string) any network and blockchain warnings\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getnetworkinfo", "") + }, + RPCExamples{ + HelpExampleCli("getnetworkinfo", "") + HelpExampleRpc("getnetworkinfo", "") - ); + }, + }.ToString()); LOCK(cs_main); UniValue obj(UniValue::VOBJ); @@ -524,15 +536,17 @@ static UniValue setban(const JSONRPCRequest& request) {"command", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "'add' to add an IP/Subnet to the list, 'remove' to remove an IP/Subnet from the list"}, {"bantime", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "time in seconds how long (or until when if [absolute] is set) the IP is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)"}, {"absolute", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If set, the bantime must be an absolute timestamp in seconds since epoch (Jan 1 1970 GMT)"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") + HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"") + HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400") - ); - if(!g_connman) - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + }, + }.ToString()); + if (!g_banman) { + throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); + } CSubNet subNet; CNetAddr netAddr; @@ -554,8 +568,9 @@ static UniValue setban(const JSONRPCRequest& request) if (strCommand == "add") { - if (isSubnet ? g_connman->IsBanned(subNet) : g_connman->IsBanned(netAddr)) + if (isSubnet ? g_banman->IsBanned(subNet) : g_banman->IsBanned(netAddr)) { throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned"); + } int64_t banTime = 0; //use standard bantime if not specified if (!request.params[2].isNull()) @@ -565,12 +580,23 @@ static UniValue setban(const JSONRPCRequest& request) if (request.params[3].isTrue()) absolute = true; - isSubnet ? g_connman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute) : g_connman->Ban(netAddr, BanReasonManuallyAdded, banTime, absolute); + if (isSubnet) { + g_banman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute); + if (g_connman) { + g_connman->DisconnectNode(subNet); + } + } else { + g_banman->Ban(netAddr, BanReasonManuallyAdded, banTime, absolute); + if (g_connman) { + g_connman->DisconnectNode(netAddr); + } + } } else if(strCommand == "remove") { - if (!( isSubnet ? g_connman->Unban(subNet) : g_connman->Unban(netAddr) )) + if (!( isSubnet ? g_banman->Unban(subNet) : g_banman->Unban(netAddr) )) { throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Unban failed. Requested address/subnet was not previously banned."); + } } return NullUniValue; } @@ -580,18 +606,21 @@ static UniValue listbanned(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"listbanned", - "\nList all banned IPs/Subnets.\n", {}} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("listbanned", "") + "\nList all banned IPs/Subnets.\n", + {}, + RPCResults{}, + RPCExamples{ + HelpExampleCli("listbanned", "") + HelpExampleRpc("listbanned", "") - ); + }, + }.ToString()); - if(!g_connman) - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + if(!g_banman) { + throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); + } banmap_t banMap; - g_connman->GetBanned(banMap); + g_banman->GetBanned(banMap); UniValue bannedAddresses(UniValue::VARR); for (const auto& entry : banMap) @@ -614,16 +643,19 @@ static UniValue clearbanned(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"clearbanned", - "\nClear all banned IPs.\n", {}} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("clearbanned", "") + "\nClear all banned IPs.\n", + {}, + RPCResults{}, + RPCExamples{ + HelpExampleCli("clearbanned", "") + HelpExampleRpc("clearbanned", "") - ); - if(!g_connman) - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + }, + }.ToString()); + if (!g_banman) { + throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); + } - g_connman->ClearBanned(); + g_banman->ClearBanned(); return NullUniValue; } @@ -636,8 +668,10 @@ static UniValue setnetworkactive(const JSONRPCRequest& request) "\nDisable/enable all p2p network activity.\n", { {"state", RPCArg::Type::BOOL, /* opt */ false, /* default_val */ "", "true to enable networking, false to disable"}, - }} - .ToString() + }, + RPCResults{}, + RPCExamples{""}, + }.ToString() ); } @@ -658,9 +692,8 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request) "\nReturn known addresses which can potentially be used to find new nodes in the network\n", { {"count", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "How many addresses to return. Limited to the smaller of " + std::to_string(ADDRMAN_GETADDR_MAX) + " or " + std::to_string(ADDRMAN_GETADDR_MAX_PCT) + "% of all known addresses."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "[\n" " {\n" " \"time\": ttt, (numeric) Timestamp in seconds since epoch (Jan 1 1970 GMT) keeping track of when the node was last seen\n" @@ -670,10 +703,12 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request) " }\n" " ,....\n" "]\n" - "\nExamples:\n" - + HelpExampleCli("getnodeaddresses", "8") + }, + RPCExamples{ + HelpExampleCli("getnodeaddresses", "8") + HelpExampleRpc("getnodeaddresses", "8") - ); + }, + }.ToString()); } if (!g_connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index bf00870107..68e3fcda33 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -82,12 +82,12 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, {"verbose", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If false, return a string, otherwise return a json object"}, {"blockhash", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "null", "The block in which to look for the transaction"}, - }} - .ToString() + - "\nResult (if verbose is not set or set to false):\n" + }, + { + RPCResult{"if verbose is not set or set to false", "\"data\" (string) The serialized, hex-encoded data for 'txid'\n" - - "\nResult (if verbose is set to true):\n" + }, + RPCResult{"if verbose is set to true", "{\n" " \"in_active_chain\": b, (bool) Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)\n" " \"hex\" : \"data\", (string) The serialized, hex-encoded data for 'txid'\n" @@ -133,14 +133,16 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) " \"blocktime\" : ttt (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n" " \"time\" : ttt, (numeric) Same as \"blocktime\"\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("getrawtransaction", "\"mytxid\"") + }, + }, + RPCExamples{ + HelpExampleCli("getrawtransaction", "\"mytxid\"") + HelpExampleCli("getrawtransaction", "\"mytxid\" true") + HelpExampleRpc("getrawtransaction", "\"mytxid\", true") + HelpExampleCli("getrawtransaction", "\"mytxid\" false \"myblockhash\"") + HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"") - ); + }, + }.ToString()); bool in_active_chain = true; uint256 hash = ParseHashV(request.params[0], "parameter 1"); @@ -219,10 +221,12 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) }, }, {"blockhash", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "null", "If specified, looks for txid in the block with this hash"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"data\" (string) A string that is a serialized, hex-encoded data for the proof.\n" + }, + RPCExamples{""}, + }.ToString() ); std::set<uint256> setTxids; @@ -305,10 +309,12 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) "and throwing an RPC error if the block is not in our best chain\n", { {"proof", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex-encoded proof generated by gettxoutproof"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof can not be validated.\n" + }, + RPCExamples{""}, + }.ToString() ); CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); @@ -445,7 +451,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal } } - if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(rawTx)) { + if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(CTransaction(rawTx))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); } @@ -494,17 +500,17 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) {"locktime", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"}, {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Marks this transaction as BIP125-replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"transaction\" (string) hex string of the transaction\n" - - "\nExamples:\n" - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") + }, + RPCExamples{ + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") - ); + }, + }.ToString()); } RPCTypeCheck(request.params, { @@ -517,7 +523,7 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); - return EncodeHexTx(rawTx); + return EncodeHexTx(CTransaction(rawTx)); } static UniValue decoderawtransaction(const JSONRPCRequest& request) @@ -530,9 +536,8 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request) {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction hex string"}, {"iswitness", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction\n" " If iswitness is not present, heuristic tests will be used in decoding"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"txid\" : \"id\", (string) The transaction id\n" " \"hash\" : \"id\", (string) The transaction hash (differs from txid for witness transactions)\n" @@ -572,11 +577,12 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request) " ,...\n" " ],\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("decoderawtransaction", "\"hexstring\"") + }, + RPCExamples{ + HelpExampleCli("decoderawtransaction", "\"hexstring\"") + HelpExampleRpc("decoderawtransaction", "\"hexstring\"") - ); + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); @@ -603,9 +609,8 @@ static UniValue decodescript(const JSONRPCRequest& request) "\nDecode a hex-encoded script.\n", { {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hex-encoded script"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"asm\":\"asm\", (string) Script public key\n" " \"hex\":\"hex\", (string) hex-encoded public key\n" @@ -617,10 +622,12 @@ static UniValue decodescript(const JSONRPCRequest& request) " ],\n" " \"p2sh\",\"address\" (string) address of P2SH script wrapping this redeem script (not returned if the script is already a P2SH).\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("decodescript", "\"hexstring\"") + }, + RPCExamples{ + HelpExampleCli("decodescript", "\"hexstring\"") + HelpExampleRpc("decodescript", "\"hexstring\"") - ); + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR}); @@ -706,14 +713,14 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "A transaction hash"}, }, }, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"hex\" (string) The hex-encoded raw transaction with signature(s)\n" - - "\nExamples:\n" - + HelpExampleCli("combinerawtransaction", "[\"myhex1\", \"myhex2\", \"myhex3\"]") - ); + }, + RPCExamples{ + HelpExampleCli("combinerawtransaction", "[\"myhex1\", \"myhex2\", \"myhex3\"]") + }, + }.ToString()); UniValue txs = request.params[0].get_array(); @@ -773,7 +780,7 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) UpdateInput(txin, sigdata); } - return EncodeHexTx(mergedTx); + return EncodeHexTx(CTransaction(mergedTx)); } UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType) @@ -906,7 +913,7 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con bool fComplete = vErrors.empty(); UniValue result(UniValue::VOBJ); - result.pushKV("hex", EncodeHexTx(mtx)); + result.pushKV("hex", EncodeHexTx(CTransaction(mtx))); result.pushKV("complete", fComplete); if (!vErrors.empty()) { result.pushKV("errors", vErrors); @@ -953,9 +960,8 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) " \"NONE|ANYONECANPAY\"\n" " \"SINGLE|ANYONECANPAY\"\n" }, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n" " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" @@ -970,11 +976,12 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) " ,...\n" " ]\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("signrawtransactionwithkey", "\"myhex\"") + }, + RPCExamples{ + HelpExampleCli("signrawtransactionwithkey", "\"myhex\"") + HelpExampleRpc("signrawtransactionwithkey", "\"myhex\"") - ); + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); @@ -1015,11 +1022,11 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) { {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex string of the raw transaction"}, {"allowhighfees", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Allow high fees"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"hex\" (string) The transaction hash in hex\n" - "\nExamples:\n" + }, + RPCExamples{ "\nCreate a transaction\n" + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") + "Sign the transaction, and get back the hex\n" @@ -1028,7 +1035,8 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) + HelpExampleCli("sendrawtransaction", "\"signedhex\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") - ); + }, + }.ToString()); std::promise<void> promise; @@ -1118,9 +1126,8 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) }, }, {"allowhighfees", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Allow high fees"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "[ (array) The result of the mempool acceptance test for each raw transaction in the input array.\n" " Length is exactly one for now.\n" " {\n" @@ -1129,7 +1136,8 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) " \"reject-reason\" (string) Rejection string (only present when 'allowed' is false)\n" " }\n" "]\n" - "\nExamples:\n" + }, + RPCExamples{ "\nCreate a transaction\n" + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") + "Sign the transaction, and get back the hex\n" @@ -1138,7 +1146,8 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) + HelpExampleCli("testmempoolaccept", "[\"signedhex\"]") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]") - ); + }, + }.ToString()); } RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VBOOL}); @@ -1212,9 +1221,8 @@ UniValue decodepsbt(const JSONRPCRequest& request) "\nReturn a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.\n", { {"psbt", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The PSBT base64 string"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"tx\" : { (json object) The decoded network-serialized unsigned transaction.\n" " ... The layout is the same as the output of decoderawtransaction.\n" @@ -1301,10 +1309,11 @@ UniValue decodepsbt(const JSONRPCRequest& request) " ]\n" " \"fee\" : fee (numeric, optional) The transaction fee paid if all UTXOs slots in the PSBT have been filled.\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("decodepsbt", "\"psbt\"") - ); + }, + RPCExamples{ + HelpExampleCli("decodepsbt", "\"psbt\"") + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR}); @@ -1492,13 +1501,14 @@ UniValue combinepsbt(const JSONRPCRequest& request) {"psbt", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "A base64 string of a PSBT"}, }, }, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ " \"psbt\" (string) The base64-encoded partially signed transaction\n" - "\nExamples:\n" - + HelpExampleCli("combinepsbt", "[\"mybase64_1\", \"mybase64_2\", \"mybase64_3\"]") - ); + }, + RPCExamples{ + HelpExampleCli("combinepsbt", "[\"mybase64_1\", \"mybase64_2\", \"mybase64_3\"]") + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VARR}, true); @@ -1546,19 +1556,19 @@ UniValue finalizepsbt(const JSONRPCRequest& request) {"psbt", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "A base64 string of a PSBT"}, {"extract", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "If true and the transaction is complete,\n" " extract and return the complete transaction in normal network serialization instead of the PSBT."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction if not extracted\n" " \"hex\" : \"value\", (string) The hex-encoded network transaction if extracted\n" " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" " ]\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("finalizepsbt", "\"psbt\"") - ); + }, + RPCExamples{ + HelpExampleCli("finalizepsbt", "\"psbt\"") + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); @@ -1637,13 +1647,14 @@ UniValue createpsbt(const JSONRPCRequest& request) {"locktime", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"}, {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" - "\nExamples:\n" - + HelpExampleCli("createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") - ); + }, + RPCExamples{ + HelpExampleCli("createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + }, + }.ToString()); RPCTypeCheck(request.params, { @@ -1688,16 +1699,17 @@ UniValue converttopsbt(const JSONRPCRequest& request) " If iswitness is not present, heuristic tests will be used in decoding. If true, only witness deserializaion\n" " will be tried. If false, only non-witness deserialization will be tried. Only has an effect if\n" " permitsigdata is true."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" - "\nExamples:\n" + }, + RPCExamples{ "\nCreate a transaction\n" + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + "\nConvert the transaction to a PSBT\n" + HelpExampleCli("converttopsbt", "\"rawtransaction\"") - ); + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index edaf64f3e1..2ed74547b9 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -51,7 +51,7 @@ struct RPCCommandExecution explicit RPCCommandExecution(const std::string& method) { LOCK(g_rpc_server_info.mutex); - it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.cend(), {method, GetTimeMicros()}); + it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, GetTimeMicros()}); } ~RPCCommandExecution() { @@ -231,10 +231,12 @@ UniValue help(const JSONRPCRequest& jsonRequest) "\nList all commands, or get help for a specified command.\n", { {"command", RPCArg::Type::STR, /* opt */ true, /* default_val */ "all commands", "The command to get help on"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"text\" (string) The help text\n" + }, + RPCExamples{""}, + }.ToString() ); std::string strCommand; @@ -254,8 +256,11 @@ UniValue stop(const JSONRPCRequest& jsonRequest) if (jsonRequest.fHelp || jsonRequest.params.size() > 1) throw std::runtime_error( RPCHelpMan{"stop", - "\nStop Bitcoin server.", {}} - .ToString()); + "\nStop Bitcoin server.", + {}, + RPCResults{}, + RPCExamples{""}, + }.ToString()); // Event loop will exit after current HTTP requests have been handled, so // this reply will get back to the client. StartShutdown(); @@ -270,14 +275,16 @@ static UniValue uptime(const JSONRPCRequest& jsonRequest) if (jsonRequest.fHelp || jsonRequest.params.size() > 0) throw std::runtime_error( RPCHelpMan{"uptime", - "\nReturns the total uptime of the server.\n", {}} - .ToString() + - "\nResult:\n" + "\nReturns the total uptime of the server.\n", + {}, + RPCResult{ "ttt (numeric) The number of seconds that the server has been running\n" - "\nExamples:\n" - + HelpExampleCli("uptime", "") + }, + RPCExamples{ + HelpExampleCli("uptime", "") + HelpExampleRpc("uptime", "") - ); + }, + }.ToString()); return GetTime() - GetStartupTime(); } @@ -287,8 +294,11 @@ static UniValue getrpcinfo(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( RPCHelpMan{"getrpcinfo", - "\nReturns details of the RPC server.\n", {}} - .ToString() + "\nReturns details of the RPC server.\n", + {}, + RPCResults{}, + RPCExamples{""}, + }.ToString() ); } diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index b91baee4ac..4275cc09a8 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -242,8 +242,12 @@ struct Sections { } }; -RPCHelpMan::RPCHelpMan(const std::string& name, const std::string& description, const std::vector<RPCArg>& args) - : m_name{name}, m_description{description}, m_args{args} +RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples) + : m_name{std::move(name)}, + m_description{std::move(description)}, + m_args{std::move(args)}, + m_results{std::move(results)}, + m_examples{std::move(examples)} { std::set<std::string> named_args; for (const auto& arg : m_args) { @@ -252,6 +256,25 @@ RPCHelpMan::RPCHelpMan(const std::string& name, const std::string& description, } } +std::string RPCResults::ToDescriptionString() const +{ + std::string result; + for (const auto& r : m_results) { + if (r.m_cond.empty()) { + result += "\nResult:\n"; + } else { + result += "\nResult (" + r.m_cond + "):\n"; + } + result += r.m_result; + } + return result; +} + +std::string RPCExamples::ToDescriptionString() const +{ + return m_examples.empty() ? m_examples : "\nExamples:\n" + m_examples; +} + std::string RPCHelpMan::ToString() const { std::string ret; @@ -292,6 +315,12 @@ std::string RPCHelpMan::ToString() const } ret += sections.ToString(); + // Result + ret += m_results.ToDescriptionString(); + + // Examples + ret += m_examples.ToDescriptionString(); + return ret; } diff --git a/src/rpc/util.h b/src/rpc/util.h index 9e97b3ae0b..4a9d4be787 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -106,10 +106,62 @@ struct RPCArg { std::string ToDescriptionString(bool implicitly_required = false) const; }; +struct RPCResult { + const std::string m_cond; + const std::string m_result; + + explicit RPCResult(std::string result) + : m_cond{}, m_result{std::move(result)} + { + assert(!m_result.empty()); + } + + RPCResult(std::string cond, std::string result) + : m_cond{std::move(cond)}, m_result{std::move(result)} + { + assert(!m_cond.empty()); + assert(!m_result.empty()); + } +}; + +struct RPCResults { + const std::vector<RPCResult> m_results; + + RPCResults() + : m_results{} + { + } + + RPCResults(RPCResult result) + : m_results{{result}} + { + } + + RPCResults(std::initializer_list<RPCResult> results) + : m_results{results} + { + } + + /** + * Return the description string. + */ + std::string ToDescriptionString() const; +}; + +struct RPCExamples { + const std::string m_examples; + RPCExamples( + std::string examples) + : m_examples(std::move(examples)) + { + } + std::string ToDescriptionString() const; +}; + class RPCHelpMan { public: - RPCHelpMan(const std::string& name, const std::string& description, const std::vector<RPCArg>& args); + RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples); std::string ToString() const; @@ -117,6 +169,8 @@ private: const std::string m_name; const std::string m_description; const std::vector<RPCArg> m_args; + const RPCResults m_results; + const RPCExamples m_examples; }; #endif // BITCOIN_RPC_UTIL_H diff --git a/src/scheduler.cpp b/src/scheduler.cpp index b2da62fc75..fdc859b3a0 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -41,7 +41,7 @@ void CScheduler::serviceQueue() try { if (!shouldStop() && taskQueue.empty()) { reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock); - // Use this chance to get a tiny bit more entropy + // Use this chance to get more entropy RandAddSeedSleep(); } while (!shouldStop() && taskQueue.empty()) { diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 635e4fa3d2..792fb2997f 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -509,7 +509,7 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script) return false; } -PartiallySignedTransaction::PartiallySignedTransaction(const CTransaction& tx) : tx(tx) +PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx) { inputs.resize(tx.vin.size()); outputs.resize(tx.vout.size()); diff --git a/src/script/sign.h b/src/script/sign.h index 20c7203b26..e884f4c480 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -574,7 +574,7 @@ struct PartiallySignedTransaction bool IsSane() const; PartiallySignedTransaction() {} PartiallySignedTransaction(const PartiallySignedTransaction& psbt_in) : tx(psbt_in.tx), inputs(psbt_in.inputs), outputs(psbt_in.outputs), unknown(psbt_in.unknown) {} - explicit PartiallySignedTransaction(const CTransaction& tx); + explicit PartiallySignedTransaction(const CMutableTransaction& tx); // Only checks if they refer to the same transaction friend bool operator==(const PartiallySignedTransaction& a, const PartiallySignedTransaction &b) diff --git a/src/streams.h b/src/streams.h index 0809c96be1..4e600f1826 100644 --- a/src/streams.h +++ b/src/streams.h @@ -139,7 +139,7 @@ private: public: - /* + /** * @param[in] type Serialization Type * @param[in] version Serialization Version (including any flags) * @param[in] data Referenced byte vector to overwrite/append @@ -153,7 +153,7 @@ public: } } - /* + /** * (other params same as above) * @param[in] args A list of items to deserialize starting at pos. */ @@ -715,15 +715,15 @@ private: const int nType; const int nVersion; - FILE *src; // source file - uint64_t nSrcPos; // how many bytes have been read from source - uint64_t nReadPos; // how many bytes have been read from this - uint64_t nReadLimit; // up to which position we're allowed to read - uint64_t nRewind; // how many bytes we guarantee to rewind - std::vector<char> vchBuf; // the buffer + FILE *src; //!< source file + uint64_t nSrcPos; //!< how many bytes have been read from source + uint64_t nReadPos; //!< how many bytes have been read from this + uint64_t nReadLimit; //!< up to which position we're allowed to read + uint64_t nRewind; //!< how many bytes we guarantee to rewind + std::vector<char> vchBuf; //!< the buffer protected: - // read data from the source to fill the buffer + //! read data from the source to fill the buffer bool Fill() { unsigned int pos = nSrcPos % vchBuf.size(); unsigned int readNow = vchBuf.size() - pos; @@ -768,12 +768,12 @@ public: } } - // check whether we're at the end of the source file + //! check whether we're at the end of the source file bool eof() const { return nReadPos == nSrcPos && feof(src); } - // read a number of bytes + //! 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"); @@ -795,12 +795,12 @@ public: } } - // return the current reading position + //! return the current reading position uint64_t GetPos() const { return nReadPos; } - // rewind to a given reading position + //! rewind to a given reading position bool SetPos(uint64_t nPos) { nReadPos = nPos; if (nReadPos + nRewind < nSrcPos) { @@ -826,8 +826,8 @@ public: return true; } - // prevent reading beyond a certain position - // no argument removes the limit + //! 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; @@ -842,7 +842,7 @@ public: return (*this); } - // search for a given byte in the stream, and remain positioned on it + //! search for a given byte in the stream, and remain positioned on it void FindByte(char ch) { while (true) { if (nReadPos == nSrcPos) diff --git a/src/sync.cpp b/src/sync.cpp index 30811f5f89..23ca866e53 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -73,7 +73,11 @@ struct LockData { LockOrders lockorders; InvLockOrders invlockorders; std::mutex dd_mutex; -} static lockdata; +}; +LockData& GetLockData() { + static LockData lockdata; + return lockdata; +} static thread_local LockStack g_lockstack; @@ -109,6 +113,7 @@ static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch, static void push_lock(void* c, const CLockLocation& locklocation) { + LockData& lockdata = GetLockData(); std::lock_guard<std::mutex> lock(lockdata.dd_mutex); g_lockstack.push_back(std::make_pair(c, locklocation)); @@ -173,6 +178,7 @@ void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLi void DeleteLock(void* cs) { + LockData& lockdata = GetLockData(); if (!lockdata.available) { // We're already shutting down. return; diff --git a/src/sync.h b/src/sync.h index 40709bdd7f..3857eda56b 100644 --- a/src/sync.h +++ b/src/sync.h @@ -20,7 +20,7 @@ //////////////////////////////////////////////// /* -CCriticalSection mutex; +RecursiveMutex mutex; std::recursive_mutex mutex; LOCK(mutex); @@ -104,6 +104,7 @@ public: * Wrapped mutex: supports recursive locking, but no waiting * TODO: We should move away from using the recursive lock by default. */ +using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>; typedef AnnotatedMixin<std::recursive_mutex> CCriticalSection; /** Wrapped mutex: supports waiting but not recursive locking */ diff --git a/src/test/allocator_tests.cpp b/src/test/allocator_tests.cpp index 80328458c4..9eded4f5b2 100644 --- a/src/test/allocator_tests.cpp +++ b/src/test/allocator_tests.cpp @@ -144,7 +144,7 @@ public: *lockingSuccess = true; } - return reinterpret_cast<void*>(0x08000000 + (count<<24)); // Fake address, do not actually use this memory + return reinterpret_cast<void*>(uint64_t{static_cast<uint64_t>(0x08000000) + (count << 24)}); // Fake address, do not actually use this memory } return nullptr; } diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index 309b8d2d06..607af8a32a 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -62,7 +62,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest) TestMemPoolEntryHelper entry; CBlock block(BuildBlockTestCase()); - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); pool.addUnchecked(entry.FromTx(block.vtx[2])); BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); @@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest) TestMemPoolEntryHelper entry; CBlock block(BuildBlockTestCase()); - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); pool.addUnchecked(entry.FromTx(block.vtx[2])); BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); @@ -232,7 +232,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest) TestMemPoolEntryHelper entry; CBlock block(BuildBlockTestCase()); - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); pool.addUnchecked(entry.FromTx(block.vtx[1])); BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index f3fd83a0cc..86cb00a78f 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -532,7 +532,7 @@ BOOST_AUTO_TEST_CASE(countbits_tests) // Check handling of zero. BOOST_CHECK_EQUAL(CountBits(0), 0U); } else if (i < 10) { - for (uint64_t j = 1 << (i - 1); (j >> i) == 0; ++j) { + for (uint64_t j = (uint64_t)1 << (i - 1); (j >> i) == 0; ++j) { // Exhaustively test up to 10 bits BOOST_CHECK_EQUAL(CountBits(j), i); } diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 8cf614bc8d..e5d62a3ab2 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -4,6 +4,7 @@ // Unit tests for denial-of-service detection/prevention code +#include <banman.h> #include <chainparams.h> #include <keystore.h> #include <net.h> @@ -20,6 +21,23 @@ #include <boost/test/unit_test.hpp> +struct CConnmanTest : public CConnman { + using CConnman::CConnman; + void AddNode(CNode& node) + { + LOCK(cs_vNodes); + vNodes.push_back(&node); + } + void ClearNodes() + { + LOCK(cs_vNodes); + for (CNode* node : vNodes) { + delete node; + } + vNodes.clear(); + } +}; + // Tests these internal-to-net_processing.cpp methods: extern bool AddOrphanTx(const CTransactionRef& tx, NodeId peer); extern void EraseOrphansFor(NodeId peer); @@ -57,6 +75,8 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) // work. BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) { + auto connman = MakeUnique<CConnman>(0x1337, 0x1337); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler, false); // Mock an outbound peer CAddress addr1(ip(0xa0b0c001), NODE_NONE); @@ -109,7 +129,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) peerLogic->FinalizeNode(dummyNode1.GetId(), dummy); } -static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidation &peerLogic) +static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidation &peerLogic, CConnmanTest* connman) { CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE); vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK|NODE_WITNESS), 0, INVALID_SOCKET, addr, 0, 0, CAddress(), "", /*fInboundIn=*/ false)); @@ -120,11 +140,14 @@ static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidat node.nVersion = 1; node.fSuccessfullyConnected = true; - CConnmanTest::AddNode(node); + connman->AddNode(node); } BOOST_AUTO_TEST_CASE(stale_tip_peer_management) { + auto connman = MakeUnique<CConnmanTest>(0x1337, 0x1337); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler, false); + const Consensus::Params& consensusParams = Params().GetConsensus(); constexpr int nMaxOutbound = 8; CConnman::Options options; @@ -137,7 +160,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) // Mock some outbound peers for (int i=0; i<nMaxOutbound; ++i) { - AddRandomOutboundPeer(vNodes, *peerLogic); + AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); } peerLogic->CheckForStaleTipAndEvictPeers(consensusParams); @@ -162,7 +185,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) // If we add one more peer, something should get marked for eviction // on the next check (since we're mocking the time to be in the future, the // required time connected check should be satisfied). - AddRandomOutboundPeer(vNodes, *peerLogic); + AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); peerLogic->CheckForStaleTipAndEvictPeers(consensusParams); for (int i=0; i<nMaxOutbound; ++i) { @@ -189,13 +212,16 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) peerLogic->FinalizeNode(node->GetId(), dummy); } - CConnmanTest::ClearNodes(); + connman->ClearNodes(); } BOOST_AUTO_TEST_CASE(DoS_banning) { + auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + auto connman = MakeUnique<CConnman>(0x1337, 0x1337); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler, false); - connman->ClearBanned(); + banman->ClearBanned(); CAddress addr1(ip(0xa0b0c001), NODE_NONE); CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 0, 0, CAddress(), "", true); dummyNode1.SetSendVersion(PROTOCOL_VERSION); @@ -210,8 +236,8 @@ BOOST_AUTO_TEST_CASE(DoS_banning) LOCK2(cs_main, dummyNode1.cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); } - BOOST_CHECK(connman->IsBanned(addr1)); - BOOST_CHECK(!connman->IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different IP, not banned + BOOST_CHECK(banman->IsBanned(addr1)); + BOOST_CHECK(!banman->IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different IP, not banned CAddress addr2(ip(0xa0b0c002), NODE_NONE); CNode dummyNode2(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr2, 1, 1, CAddress(), "", true); @@ -227,8 +253,8 @@ BOOST_AUTO_TEST_CASE(DoS_banning) LOCK2(cs_main, dummyNode2.cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(&dummyNode2)); } - BOOST_CHECK(!connman->IsBanned(addr2)); // 2 not banned yet... - BOOST_CHECK(connman->IsBanned(addr1)); // ... but 1 still should be + BOOST_CHECK(!banman->IsBanned(addr2)); // 2 not banned yet... + BOOST_CHECK(banman->IsBanned(addr1)); // ... but 1 still should be { LOCK(cs_main); Misbehaving(dummyNode2.GetId(), 50); @@ -237,7 +263,7 @@ BOOST_AUTO_TEST_CASE(DoS_banning) LOCK2(cs_main, dummyNode2.cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(&dummyNode2)); } - BOOST_CHECK(connman->IsBanned(addr2)); + BOOST_CHECK(banman->IsBanned(addr2)); bool dummy; peerLogic->FinalizeNode(dummyNode1.GetId(), dummy); @@ -246,8 +272,11 @@ BOOST_AUTO_TEST_CASE(DoS_banning) BOOST_AUTO_TEST_CASE(DoS_banscore) { + auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + auto connman = MakeUnique<CConnman>(0x1337, 0x1337); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler, false); - connman->ClearBanned(); + banman->ClearBanned(); gArgs.ForceSetArg("-banscore", "111"); // because 11 is my favorite number CAddress addr1(ip(0xa0b0c001), NODE_NONE); CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 3, 1, CAddress(), "", true); @@ -263,7 +292,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) LOCK2(cs_main, dummyNode1.cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); } - BOOST_CHECK(!connman->IsBanned(addr1)); + BOOST_CHECK(!banman->IsBanned(addr1)); { LOCK(cs_main); Misbehaving(dummyNode1.GetId(), 10); @@ -272,7 +301,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) LOCK2(cs_main, dummyNode1.cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); } - BOOST_CHECK(!connman->IsBanned(addr1)); + BOOST_CHECK(!banman->IsBanned(addr1)); { LOCK(cs_main); Misbehaving(dummyNode1.GetId(), 1); @@ -281,7 +310,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) LOCK2(cs_main, dummyNode1.cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); } - BOOST_CHECK(connman->IsBanned(addr1)); + BOOST_CHECK(banman->IsBanned(addr1)); gArgs.ForceSetArg("-banscore", std::to_string(DEFAULT_BANSCORE_THRESHOLD)); bool dummy; @@ -290,8 +319,11 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) BOOST_AUTO_TEST_CASE(DoS_bantime) { + auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + auto connman = MakeUnique<CConnman>(0x1337, 0x1337); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler, false); - connman->ClearBanned(); + banman->ClearBanned(); int64_t nStartTime = GetTime(); SetMockTime(nStartTime); // Overrides future calls to GetTime() @@ -310,13 +342,13 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) LOCK2(cs_main, dummyNode.cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(&dummyNode)); } - BOOST_CHECK(connman->IsBanned(addr)); + BOOST_CHECK(banman->IsBanned(addr)); SetMockTime(nStartTime+60*60); - BOOST_CHECK(connman->IsBanned(addr)); + BOOST_CHECK(banman->IsBanned(addr)); SetMockTime(nStartTime+60*60*24+1); - BOOST_CHECK(!connman->IsBanned(addr)); + BOOST_CHECK(!banman->IsBanned(addr)); bool dummy; peerLogic->FinalizeNode(dummyNode.GetId(), dummy); diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index 2396aba0f1..23ca9d89ae 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) CTxMemPool testPool; - LOCK(testPool.cs); + LOCK2(cs_main, testPool.cs); // Nothing in pool, remove should do nothing: unsigned int poolSize = testPool.size(); @@ -120,7 +120,7 @@ static void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) E BOOST_AUTO_TEST_CASE(MempoolIndexingTest) { CTxMemPool pool; - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; /* 3rd highest fee */ @@ -293,7 +293,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) { CTxMemPool pool; - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; /* 3rd highest fee */ @@ -422,7 +422,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest) { CTxMemPool pool; - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; CMutableTransaction tx1 = CMutableTransaction(); @@ -595,7 +595,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) size_t ancestors, descendants; CTxMemPool pool; - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; /* Base transaction */ diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index f3648e2eee..5ba1df2ec2 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -99,7 +99,7 @@ static bool TestSequenceLocks(const CTransaction &tx, int flags) EXCLUSIVE_LOCKS // Test suite for ancestor feerate transaction selection. // Implemented as an additional function, rather than a separate test case, // to allow reusing the blockchain created in CreateNewBlock_validity. -static void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::mempool.cs) +static void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs) { // Test the ancestor feerate transaction selection. TestMemPoolEntryHelper entry; diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp index 431b16cfc2..7b274a1658 100644 --- a/src/test/policyestimator_tests.cpp +++ b/src/test/policyestimator_tests.cpp @@ -18,7 +18,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) { CBlockPolicyEstimator feeEst; CTxMemPool mpool(&feeEst); - LOCK(mpool.cs); + LOCK2(cs_main, mpool.cs); TestMemPoolEntryHelper entry; CAmount basefee(2000); CAmount deltaFee(100); diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 1b394753ef..87c3e74df0 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -184,11 +184,12 @@ void DoTest(const CScript& scriptPubKey, const CScript& scriptSig, const CScript stream << tx2; int libconsensus_flags = flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL; if (libconsensus_flags == flags) { + int expectedSuccessCode = expect ? 1 : 0; if (flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS) { - BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), txCredit.vout[0].nValue, (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expect, message); + BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), txCredit.vout[0].nValue, (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message); } else { - BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), 0, (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expect, message); - BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expect,message); + BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), 0, (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message); + BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message); } } #endif diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 858bb512fc..0c3fb7c398 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -4,6 +4,7 @@ #include <test/test_bitcoin.h> +#include <banman.h> #include <chainparams.h> #include <consensus/consensus.h> #include <consensus/params.h> @@ -24,21 +25,6 @@ const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; FastRandomContext g_insecure_rand_ctx; -void CConnmanTest::AddNode(CNode& node) -{ - LOCK(g_connman->cs_vNodes); - g_connman->vNodes.push_back(&node); -} - -void CConnmanTest::ClearNodes() -{ - LOCK(g_connman->cs_vNodes); - for (const CNode* node : g_connman->vNodes) { - delete node; - } - g_connman->vNodes.clear(); -} - std::ostream& operator<<(std::ostream& os, const uint256& num) { os << num.ToString(); @@ -49,7 +35,6 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName) : m_path_root(fs::temp_directory_path() / "test_bitcoin" / strprintf("%lu_%i", (unsigned long)GetTime(), (int)(InsecureRandRange(1 << 30)))) { SHA256AutoDetect(); - RandomInit(); ECC_Start(); SetupEnvironment(); SetupNetworking(); @@ -108,9 +93,9 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha nScriptCheckThreads = 3; for (int i=0; i < nScriptCheckThreads-1; i++) threadGroup.create_thread(&ThreadScriptCheck); + + g_banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); g_connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests. - connman = g_connman.get(); - peerLogic.reset(new PeerLogicValidation(connman, scheduler, /*enable_bip61=*/true)); } TestingSetup::~TestingSetup() @@ -120,7 +105,7 @@ TestingSetup::~TestingSetup() GetMainSignals().FlushBackgroundCallbacks(); GetMainSignals().UnregisterBackgroundSignalScheduler(); g_connman.reset(); - peerLogic.reset(); + g_banman.reset(); UnloadBlockIndex(); pcoinsTip.reset(); pcoinsdbview.reset(); diff --git a/src/test/test_bitcoin.h b/src/test/test_bitcoin.h index 31d90c0151..71520232ac 100644 --- a/src/test/test_bitcoin.h +++ b/src/test/test_bitcoin.h @@ -68,17 +68,11 @@ private: */ class CConnman; class CNode; -struct CConnmanTest { - static void AddNode(CNode& node); - static void ClearNodes(); -}; class PeerLogicValidation; struct TestingSetup : public BasicTestingSetup { boost::thread_group threadGroup; - CConnman* connman; CScheduler scheduler; - std::unique_ptr<PeerLogicValidation> peerLogic; explicit TestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~TestingSetup(); diff --git a/src/test/test_bitcoin_main.cpp b/src/test/test_bitcoin_main.cpp index 6c066d3fea..46b63b93b4 100644 --- a/src/test/test_bitcoin_main.cpp +++ b/src/test/test_bitcoin_main.cpp @@ -4,6 +4,7 @@ #define BOOST_TEST_MODULE Bitcoin Test Suite +#include <banman.h> #include <net.h> #include <memory> @@ -11,6 +12,7 @@ #include <boost/test/unit_test.hpp> std::unique_ptr<CConnman> g_connman; +std::unique_ptr<BanMan> g_banman; [[noreturn]] void Shutdown(void* parg) { diff --git a/src/txmempool.h b/src/txmempool.h index fadb554723..b10c9f099f 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -485,7 +485,43 @@ public: > > indexed_transaction_set; - mutable CCriticalSection cs; + /** + * This mutex needs to be locked when accessing `mapTx` or other members + * that are guarded by it. + * + * @par Consistency guarantees + * + * By design, it is guaranteed that: + * + * 1. Locking both `cs_main` and `mempool.cs` will give a view of mempool + * that is consistent with current chain tip (`chainActive` and + * `pcoinsTip`) and is fully populated. Fully populated means that if the + * current active chain is missing transactions that were present in a + * previously active chain, all the missing transactions will have been + * re-added to the mempool and should be present if they meet size and + * consistency constraints. + * + * 2. Locking `mempool.cs` without `cs_main` will give a view of a mempool + * consistent with some chain that was active since `cs_main` was last + * locked, and that is fully populated as described above. It is ok for + * code that only needs to query or remove transactions from the mempool + * to lock just `mempool.cs` without `cs_main`. + * + * To provide these guarantees, it is necessary to lock both `cs_main` and + * `mempool.cs` whenever adding transactions to the mempool and whenever + * changing the chain tip. It's necessary to keep both mutexes locked until + * the mempool is consistent with the new chain tip and fully populated. + * + * @par Consistency bug + * + * The second guarantee above is not currently enforced, but + * https://github.com/bitcoin/bitcoin/pull/14193 will fix it. No known code + * in bitcoin currently depends on second guarantee, but it is important to + * fix for third party code that needs be able to frequently poll the + * mempool without locking `cs_main` and without encountering missing + * transactions during reorgs. + */ + mutable RecursiveMutex cs; indexed_transaction_set mapTx GUARDED_BY(cs); using txiter = indexed_transaction_set::nth_index<0>::type::const_iterator; @@ -541,8 +577,8 @@ public: // Note that addUnchecked is ONLY called from ATMP outside of tests // and any other callers may break wallet's in-mempool tracking (due to // lack of CValidationInterface::TransactionAddedToMempool callbacks). - void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs); - void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs); + void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); + void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN); void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -594,7 +630,7 @@ public: * for). Note: vHashesToUpdate should be the set of transactions from the * disconnected block that have been accepted back into the mempool. */ - void UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate); + void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Try to calculate all in-mempool ancestors of entry. * (these are all calculated including the tx itself) @@ -636,7 +672,7 @@ public: */ void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) const; - unsigned long size() + unsigned long size() const { LOCK(cs); return mapTx.size(); diff --git a/src/util/system.cpp b/src/util/system.cpp index a4bf126900..06317a3a90 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -73,9 +73,6 @@ #include <malloc.h> #endif -#include <openssl/crypto.h> -#include <openssl/rand.h> -#include <openssl/conf.h> #include <thread> // Application startup time (used for uptime calculation) @@ -86,54 +83,6 @@ const char * const BITCOIN_PID_FILENAME = "bitcoind.pid"; ArgsManager gArgs; -/** Init OpenSSL library multithreading support */ -static std::unique_ptr<CCriticalSection[]> ppmutexOpenSSL; -void locking_callback(int mode, int i, const char* file, int line) NO_THREAD_SAFETY_ANALYSIS -{ - if (mode & CRYPTO_LOCK) { - ENTER_CRITICAL_SECTION(ppmutexOpenSSL[i]); - } else { - LEAVE_CRITICAL_SECTION(ppmutexOpenSSL[i]); - } -} - -// Singleton for wrapping OpenSSL setup/teardown. -class CInit -{ -public: - CInit() - { - // Init OpenSSL library multithreading support - ppmutexOpenSSL.reset(new CCriticalSection[CRYPTO_num_locks()]); - CRYPTO_set_locking_callback(locking_callback); - - // OpenSSL can optionally load a config file which lists optional loadable modules and engines. - // We don't use them so we don't require the config. However some of our libs may call functions - // which attempt to load the config file, possibly resulting in an exit() or crash if it is missing - // or corrupt. Explicitly tell OpenSSL not to try to load the file. The result for our libs will be - // that the config appears to have been loaded and there are no modules/engines available. - OPENSSL_no_config(); - -#ifdef WIN32 - // Seed OpenSSL PRNG with current contents of the screen - RAND_screen(); -#endif - - // Seed OpenSSL PRNG with performance counter - RandAddSeed(); - } - ~CInit() - { - // Securely erase the memory used by the PRNG - RAND_cleanup(); - // Shutdown OpenSSL library multithreading support - CRYPTO_set_locking_callback(nullptr); - // Clear the set of locks now to maintain symmetry with the constructor. - ppmutexOpenSSL.reset(); - } -} -instance_of_cinit; - /** 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 @@ -749,18 +698,17 @@ fs::path GetDefaultDataDir() #endif } -static fs::path g_blocks_path_cached; static fs::path g_blocks_path_cache_net_specific; static fs::path pathCached; static fs::path pathCachedNetSpecific; static CCriticalSection csPathCached; -const fs::path &GetBlocksDir(bool fNetSpecific) +const fs::path &GetBlocksDir() { LOCK(csPathCached); - fs::path &path = fNetSpecific ? g_blocks_path_cache_net_specific : g_blocks_path_cached; + fs::path &path = g_blocks_path_cache_net_specific; // This can be called during exceptions by LogPrintf(), so we cache the // value so we don't have to do memory allocations after that. @@ -776,9 +724,8 @@ const fs::path &GetBlocksDir(bool fNetSpecific) } else { path = GetDataDir(false); } - if (fNetSpecific) - path /= BaseParams().DataDir(); + path /= BaseParams().DataDir(); path /= "blocks"; fs::create_directories(path); return path; @@ -822,7 +769,6 @@ void ClearDatadirCache() pathCached = fs::path(); pathCachedNetSpecific = fs::path(); - g_blocks_path_cached = fs::path(); g_blocks_path_cache_net_specific = fs::path(); } @@ -1293,7 +1239,7 @@ fs::path AbsPathForConfigVal(const fs::path& path, bool net_specific) int ScheduleBatchPriority() { #ifdef SCHED_BATCH - const static sched_param param{0}; + const static sched_param param{}; if (int ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m)) { LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(errno)); return ret; diff --git a/src/util/system.h b/src/util/system.h index dca32cc6fc..5932e55793 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -79,7 +79,8 @@ void ReleaseDirectoryLocks(); bool TryCreateDirectories(const fs::path& p); fs::path GetDefaultDataDir(); -const fs::path &GetBlocksDir(bool fNetSpecific = true); +// The blocks directory is always net specific. +const fs::path &GetBlocksDir(); const fs::path &GetDataDir(bool fNetSpecific = true); void ClearDatadirCache(); fs::path GetConfigFile(const std::string& confPath); diff --git a/src/validation.cpp b/src/validation.cpp index a18e449af6..6a26bf9baa 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -173,7 +173,7 @@ public: CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Block disconnection on our pcoinsTip: - bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions *disconnectpool); + bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Manual block validity manipulation: bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); @@ -210,9 +210,17 @@ private: bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main); } g_chainstate; - - -CCriticalSection cs_main; +/** + * Mutex to guard access to validation specific variables, such as reading + * or changing the chainstate. + * + * This may also need to be locked when updating the transaction pool, e.g. on + * AcceptToMemoryPool. See CTxMemPool::cs comment for details. + * + * The transaction pool has a separate lock to allow reading from it and the + * chainstate at the same time. + */ +RecursiveMutex cs_main; BlockMap& mapBlockIndex = g_chainstate.mapBlockIndex; CChain& chainActive = g_chainstate.chainActive; diff --git a/src/validation.h b/src/validation.h index b5548a9293..c0ffc9b0e4 100644 --- a/src/validation.h +++ b/src/validation.h @@ -50,7 +50,7 @@ struct LockPoints; /** Default for -whitelistrelay. */ static const bool DEFAULT_WHITELISTRELAY = true; /** Default for -whitelistforcerelay. */ -static const bool DEFAULT_WHITELISTFORCERELAY = true; +static const bool DEFAULT_WHITELISTFORCERELAY = false; /** Default for -minrelaytxfee, minimum relay fee for transactions */ static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000; //! -maxtxfee default diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 3677194a40..a2e795056a 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -263,6 +263,45 @@ BerkeleyEnvironment::VerifyResult BerkeleyEnvironment::Verify(const std::string& return (fRecovered ? VerifyResult::RECOVER_OK : VerifyResult::RECOVER_FAIL); } +BerkeleyBatch::SafeDbt::SafeDbt() +{ + m_dbt.set_flags(DB_DBT_MALLOC); +} + +BerkeleyBatch::SafeDbt::SafeDbt(void* data, size_t size) + : m_dbt(data, size) +{ +} + +BerkeleyBatch::SafeDbt::~SafeDbt() +{ + if (m_dbt.get_data() != nullptr) { + // Clear memory, e.g. in case it was a private key + memory_cleanse(m_dbt.get_data(), m_dbt.get_size()); + // under DB_DBT_MALLOC, data is malloced by the Dbt, but must be + // freed by the caller. + // https://docs.oracle.com/cd/E17275_01/html/api_reference/C/dbt.html + if (m_dbt.get_flags() & DB_DBT_MALLOC) { + free(m_dbt.get_data()); + } + } +} + +const void* BerkeleyBatch::SafeDbt::get_data() const +{ + return m_dbt.get_data(); +} + +u_int32_t BerkeleyBatch::SafeDbt::get_size() const +{ + return m_dbt.get_size(); +} + +BerkeleyBatch::SafeDbt::operator Dbt*() +{ + return &m_dbt; +} + bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename) { std::string filename; diff --git a/src/wallet/db.h b/src/wallet/db.h index e453d441d7..9dc373c89b 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -191,10 +191,29 @@ private: bool IsDummy() { return env == nullptr; } }; - /** RAII class that provides access to a Berkeley database */ class BerkeleyBatch { + /** RAII class that automatically cleanses its data on destruction */ + class SafeDbt final + { + Dbt m_dbt; + + public: + // construct Dbt with internally-managed data + SafeDbt(); + // construct Dbt with provided data + SafeDbt(void* data, size_t size); + ~SafeDbt(); + + // delegate to Dbt + const void* get_data() const; + u_int32_t get_size() const; + + // conversion operator to access the underlying Dbt + operator Dbt*(); + }; + protected: Db* pdb; std::string strFile; @@ -222,7 +241,6 @@ public: /* verifies the database file */ static bool VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc); -public: template <typename K, typename T> bool Read(const K& key, T& value) { @@ -233,13 +251,11 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Read - Dbt datValue; - datValue.set_flags(DB_DBT_MALLOC); - int ret = pdb->get(activeTxn, &datKey, &datValue, 0); - memory_cleanse(datKey.get_data(), datKey.get_size()); + SafeDbt datValue; + int ret = pdb->get(activeTxn, datKey, datValue, 0); bool success = false; if (datValue.get_data() != nullptr) { // Unserialize value @@ -250,10 +266,6 @@ public: } catch (const std::exception&) { // In this case success remains 'false' } - - // Clear and free memory - memory_cleanse(datValue.get_data(), datValue.get_size()); - free(datValue.get_data()); } return ret == 0 && success; } @@ -270,20 +282,16 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Value CDataStream ssValue(SER_DISK, CLIENT_VERSION); ssValue.reserve(10000); ssValue << value; - Dbt datValue(ssValue.data(), ssValue.size()); + SafeDbt datValue(ssValue.data(), ssValue.size()); // Write - int ret = pdb->put(activeTxn, &datKey, &datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); - - // Clear memory in case it was a private key - memory_cleanse(datKey.get_data(), datKey.get_size()); - memory_cleanse(datValue.get_data(), datValue.get_size()); + int ret = pdb->put(activeTxn, datKey, datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); return (ret == 0); } @@ -299,13 +307,10 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Erase - int ret = pdb->del(activeTxn, &datKey, 0); - - // Clear memory - memory_cleanse(datKey.get_data(), datKey.get_size()); + int ret = pdb->del(activeTxn, datKey, 0); return (ret == 0 || ret == DB_NOTFOUND); } @@ -319,13 +324,10 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Exists - int ret = pdb->exists(activeTxn, &datKey, 0); - - // Clear memory - memory_cleanse(datKey.get_data(), datKey.get_size()); + int ret = pdb->exists(activeTxn, datKey, 0); return (ret == 0); } @@ -340,20 +342,12 @@ public: return pcursor; } - int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue, bool setRange = false) + int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue) { // Read at cursor - Dbt datKey; - unsigned int fFlags = DB_NEXT; - if (setRange) { - datKey.set_data(ssKey.data()); - datKey.set_size(ssKey.size()); - fFlags = DB_SET_RANGE; - } - Dbt datValue; - datKey.set_flags(DB_DBT_MALLOC); - datValue.set_flags(DB_DBT_MALLOC); - int ret = pcursor->get(&datKey, &datValue, fFlags); + SafeDbt datKey; + SafeDbt datValue; + int ret = pcursor->get(datKey, datValue, DB_NEXT); if (ret != 0) return ret; else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr) @@ -366,16 +360,9 @@ public: ssValue.SetType(SER_DISK); ssValue.clear(); ssValue.write((char*)datValue.get_data(), datValue.get_size()); - - // Clear and free memory - memory_cleanse(datKey.get_data(), datKey.get_size()); - memory_cleanse(datValue.get_data(), datValue.get_size()); - free(datKey.get_data()); - free(datValue.get_data()); return 0; } -public: bool TxnBegin() { if (!pdb || activeTxn) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index c24703968d..2e62a6979f 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -111,16 +111,16 @@ UniValue importprivkey(const JSONRPCRequest& request) throw std::runtime_error( RPCHelpMan{"importprivkey", "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n" - "Hint: use importmulti to import more than one private key.\n", + "Hint: use importmulti to import more than one private key.\n" + "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n", { {"privkey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The private key (see dumpprivkey)"}, {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "current label if address exists, otherwise \"\"", "An optional label"}, {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Rescan the wallet for transactions"}, - }} - .ToString() + - "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" - "\nExamples:\n" + }, + RPCResults{}, + RPCExamples{ "\nDump a private key\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + "\nImport the private key with rescan\n" @@ -131,7 +131,8 @@ UniValue importprivkey(const JSONRPCRequest& request) + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false") - ); + }, + }.ToString()); WalletRescanReserver reserver(pwallet); @@ -209,16 +210,18 @@ UniValue abortrescan(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 0) throw std::runtime_error( RPCHelpMan{"abortrescan", - "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n", {}} - .ToString() + - "\nExamples:\n" + "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n", + {}, + RPCResults{}, + RPCExamples{ "\nImport a private key\n" + HelpExampleCli("importprivkey", "\"mykey\"") + "\nAbort the running wallet rescan\n" + HelpExampleCli("abortrescan", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("abortrescan", "") - ); + }, + }.ToString()); if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; pwallet->AbortRescan(); @@ -272,27 +275,28 @@ UniValue importaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw std::runtime_error( RPCHelpMan{"importaddress", - "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n", + "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" + "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" + "If you have the full public key, you should call importpubkey instead of this.\n" + "\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n" + "as change, and not show up in many RPCs.\n", { {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The Bitcoin address (or hex-encoded script)"}, {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "\"\"", "An optional label"}, {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Rescan the wallet for transactions"}, {"p2sh", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Add the P2SH version of the script as well"}, - }} - .ToString() + - "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" - "If you have the full public key, you should call importpubkey instead of this.\n" - "\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n" - "as change, and not show up in many RPCs.\n" - "\nExamples:\n" + }, + RPCResults{}, + RPCExamples{ "\nImport an address with rescan\n" + HelpExampleCli("importaddress", "\"myaddress\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false") - ); + }, + }.ToString()); std::string strLabel; @@ -358,8 +362,10 @@ UniValue importprunedfunds(const JSONRPCRequest& request) { {"rawtransaction", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "A raw transaction in hex funding an already-existing address in wallet"}, {"txoutproof", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex output from gettxoutproof that contains the transaction"}, - }} - .ToString() + }, + RPCResults{}, + RPCExamples{""}, + }.ToString() ); CMutableTransaction tx; @@ -422,13 +428,14 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n", { {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex-encoded id of the transaction you are deleting"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") - ); + }, + }.ToString()); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -460,23 +467,24 @@ UniValue importpubkey(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( RPCHelpMan{"importpubkey", - "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n", + "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" + "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n", { {"pubkey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The hex-encoded public key"}, {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "\"\"", "An optional label"}, {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Rescan the wallet for transactions"}, - }} - .ToString() + - "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" - "\nExamples:\n" + }, + RPCResults{}, + RPCExamples{ "\nImport a public key with rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false") - ); + }, + }.ToString()); std::string strLabel; @@ -537,16 +545,17 @@ UniValue importwallet(const JSONRPCRequest& request) "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n", { {"filename", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The wallet file"}, - }} - .ToString() + - "\nExamples:\n" + }, + RPCResults{}, + RPCExamples{ "\nDump the wallet\n" + HelpExampleCli("dumpwallet", "\"test\"") + "\nImport the wallet\n" + HelpExampleCli("importwallet", "\"test\"") + "\nImport using the json rpc call\n" + HelpExampleRpc("importwallet", "\"test\"") - ); + }, + }.ToString()); if (fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode"); @@ -671,15 +680,16 @@ UniValue dumpprivkey(const JSONRPCRequest& request) "Then the importprivkey can be used with this output\n", { {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address for the private key"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"key\" (string) The private key\n" - "\nExamples:\n" - + HelpExampleCli("dumpprivkey", "\"myaddress\"") + }, + RPCExamples{ + HelpExampleCli("dumpprivkey", "\"myaddress\"") + HelpExampleCli("importprivkey", "\"mykey\"") + HelpExampleRpc("dumpprivkey", "\"myaddress\"") - ); + }, + }.ToString()); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -720,16 +730,17 @@ UniValue dumpwallet(const JSONRPCRequest& request) "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n", { {"filename", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The filename with path (either absolute or relative to bitcoind)"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{ (json object)\n" " \"filename\" : { (string) The filename with full absolute path\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("dumpwallet", "\"test\"") + }, + RPCExamples{ + HelpExampleCli("dumpwallet", "\"test\"") + HelpExampleRpc("dumpwallet", "\"test\"") - ); + }, + }.ToString()); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -850,9 +861,9 @@ struct ImportData enum class ScriptContext { - TOP, //! Top-level scriptPubKey - P2SH, //! P2SH redeemScript - WITNESS_V0, //! P2WSH witnessScript + TOP, //!< Top-level scriptPubKey + P2SH, //!< P2SH redeemScript + WITNESS_V0, //!< P2WSH witnessScript }; // Analyse the provided scriptPubKey, determining which keys and which redeem scripts from the ImportData struct are needed to spend it, and mark them as used. @@ -1149,7 +1160,11 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) throw std::runtime_error( RPCHelpMan{"importmulti", - "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.\n", + "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), optionally rescanning the blockchain from the earliest creation time of the imported scripts. Requires a new wallet backup.\n" + "If an address/script is imported without all of the private keys required to spend from that address, it will be watchonly. The 'watchonly' option must be set to true in this case or a warning will be returned.\n" + "Conversely, if all the private keys are provided and the address/script is spendable, the watchonly option must be set to false, or a warning will be returned.\n" + "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n", { {"requests", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "Data to be imported", { @@ -1179,7 +1194,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) } }, {"internal", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be treated as not incoming payments (also known as change)"}, - {"watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be considered watched even when not all private keys are provided."}, + {"watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be considered watchonly."}, {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "''", "Label to assign to the address, only allowed with internal=false"}, }, }, @@ -1190,17 +1205,18 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Stating if should rescan the blockchain after all imports"}, }, "\"options\""}, - }} - .ToString() + - "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n" - "\nExamples:\n" + - HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, " - "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + - HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") + - + }, + RPCResult{ "\nResponse is an array with the same size as the input that has the execution result :\n" - " [{\"success\": true}, {\"success\": true, \"warnings\": [\"Ignoring irrelevant private key\"]}, {\"success\": false, \"error\": {\"code\": -1, \"message\": \"Internal Server Error\"}}, ...]\n"); + " [{\"success\": true}, {\"success\": true, \"warnings\": [\"Ignoring irrelevant private key\"]}, {\"success\": false, \"error\": {\"code\": -1, \"message\": \"Internal Server Error\"}}, ...]\n" + }, + RPCExamples{ + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, " + "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") + }, + }.ToString() + ); RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 32173a92d0..270a61ade8 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -160,14 +160,15 @@ static UniValue getnewaddress(const JSONRPCRequest& request) { {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "The label name for the address to be linked to. If not provided, the default label \"\" is used. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name."}, {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -addresstype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"address\" (string) The new bitcoin address\n" - "\nExamples:\n" - + HelpExampleCli("getnewaddress", "") + }, + RPCExamples{ + HelpExampleCli("getnewaddress", "") + HelpExampleRpc("getnewaddress", "") - ); + }, + }.ToString()); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -220,14 +221,15 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) "This is for use with raw transactions, NOT normal use.\n", { {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -changetype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"address\" (string) The address\n" - "\nExamples:\n" - + HelpExampleCli("getrawchangeaddress", "") + }, + RPCExamples{ + HelpExampleCli("getrawchangeaddress", "") + HelpExampleRpc("getrawchangeaddress", "") - ); + }, + }.ToString()); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -276,12 +278,13 @@ static UniValue setlabel(const JSONRPCRequest& request) { {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to be associated with a label."}, {"label", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The label to assign to the address."}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + HelpExampleRpc("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"") - ); + }, + }.ToString()); LOCK(pwallet->cs_wallet); @@ -372,16 +375,17 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) " \"UNSET\"\n" " \"ECONOMICAL\"\n" " \"CONSERVATIVE\""}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"txid\" (string) The transaction id.\n" - "\nExamples:\n" - + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") + }, + RPCExamples{ + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"donation\" \"seans outpost\"") + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true") + HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -449,9 +453,8 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) "\nLists groups of addresses which have had their common ownership\n" "made public by common use as inputs or as the resulting change\n" "in past transactions\n", - {}} - .ToString() + - "\nResult:\n" + {}, + RPCResult{ "[\n" " [\n" " [\n" @@ -463,10 +466,12 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) " ]\n" " ,...\n" "]\n" - "\nExamples:\n" - + HelpExampleCli("listaddressgroupings", "") + }, + RPCExamples{ + HelpExampleCli("listaddressgroupings", "") + HelpExampleRpc("listaddressgroupings", "") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -513,11 +518,11 @@ static UniValue signmessage(const JSONRPCRequest& request) { {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to use for the private key."}, {"message", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The message to create a signature of."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"signature\" (string) The signature of the message encoded in base 64\n" - "\nExamples:\n" + }, + RPCExamples{ "\nUnlock the wallet for 30 seconds\n" + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + "\nCreate the signature\n" @@ -526,7 +531,8 @@ static UniValue signmessage(const JSONRPCRequest& request) + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") - ); + }, + }.ToString()); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -578,11 +584,11 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) { {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address for transactions."}, {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "Only include transactions confirmed at least this many times."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "amount (numeric) The total amount in " + CURRENCY_UNIT + " received at this address.\n" - "\nExamples:\n" + }, + RPCExamples{ "\nThe amount from transactions with at least 1 confirmation\n" + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") + "\nThe amount including unconfirmed transactions, zero confirmations\n" @@ -591,7 +597,8 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -649,11 +656,11 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) { {"label", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The selected label, may be the default label using \"\"."}, {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "Only include transactions confirmed at least this many times."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this label.\n" - "\nExamples:\n" + }, + RPCExamples{ "\nAmount received by the default label with at least 1 confirmation\n" + HelpExampleCli("getreceivedbylabel", "\"\"") + "\nAmount received at the tabby label including unconfirmed amounts with zero confirmations\n" @@ -662,7 +669,8 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) + HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -721,18 +729,19 @@ static UniValue getbalance(const JSONRPCRequest& request) {"dummy", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "Remains for backward compatibility. Must be excluded or set to \"*\"."}, {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Only include transactions confirmed at least this many times."}, {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Also include balance in watch-only addresses (see 'importaddress')"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this wallet.\n" - "\nExamples:\n" + }, + RPCExamples{ "\nThe total amount in the wallet with 1 or more confirmations\n" + HelpExampleCli("getbalance", "") + "\nThe total amount in the wallet at least 6 blocks confirmed\n" + HelpExampleCli("getbalance", "\"*\" 6") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getbalance", "\"*\", 6") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -771,8 +780,11 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) if (request.fHelp || request.params.size() > 0) throw std::runtime_error( RPCHelpMan{"getunconfirmedbalance", - "Returns the server's total unconfirmed balance\n", {}} - .ToString()); + "Returns the server's total unconfirmed balance\n", + {}, + RPCResults{}, + RPCExamples{""}, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -822,12 +834,12 @@ static UniValue sendmany(const JSONRPCRequest& request) " \"UNSET\"\n" " \"ECONOMICAL\"\n" " \"CONSERVATIVE\""}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" " the number of addresses.\n" - "\nExamples:\n" + }, + RPCExamples{ "\nSend two amounts to two different addresses:\n" + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") + "\nSend two amounts to two different addresses setting the confirmation and comment:\n" @@ -836,7 +848,8 @@ static UniValue sendmany(const JSONRPCRequest& request) + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -967,19 +980,20 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) }, {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "A label to assign the addresses to."}, {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -addresstype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n" " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n" "}\n" - "\nExamples:\n" + }, + RPCExamples{ "\nAdd a multisig address from 2 addresses\n" + HelpExampleCli("addmultisigaddress", "2 \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("addmultisigaddress", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") - ; + }, + }.ToString(); throw std::runtime_error(msg); } @@ -1196,9 +1210,8 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) {"include_empty", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include addresses that haven't received any payments."}, {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, {"address_filter", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "If present, only return information on this address."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "[\n" " {\n" " \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n" @@ -1213,13 +1226,14 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) " }\n" " ,...\n" "]\n" - - "\nExamples:\n" - + HelpExampleCli("listreceivedbyaddress", "") + }, + RPCExamples{ + HelpExampleCli("listreceivedbyaddress", "") + HelpExampleCli("listreceivedbyaddress", "6 true") + HelpExampleRpc("listreceivedbyaddress", "6, true, true") + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1248,9 +1262,8 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "The minimum number of confirmations before payments are included."}, {"include_empty", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include labels that haven't received any payments."}, {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "[\n" " {\n" " \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n" @@ -1260,12 +1273,13 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) " }\n" " ,...\n" "]\n" - - "\nExamples:\n" - + HelpExampleCli("listreceivedbylabel", "") + }, + RPCExamples{ + HelpExampleCli("listreceivedbylabel", "") + HelpExampleCli("listreceivedbylabel", "6 true") + HelpExampleRpc("listreceivedbylabel", "6, true, true") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1391,9 +1405,8 @@ UniValue listtransactions(const JSONRPCRequest& request) {"count", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "10", "The number of transactions to return"}, {"skip", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "The number of transactions to skip"}, {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Include transactions to watch-only addresses (see 'importaddress')"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "[\n" " {\n" " \"address\":\"address\", (string) The bitcoin address of the transaction.\n" @@ -1425,15 +1438,16 @@ UniValue listtransactions(const JSONRPCRequest& request) " 'send' category of transactions.\n" " }\n" "]\n" - - "\nExamples:\n" + }, + RPCExamples{ "\nList the most recent 10 transactions in the systems\n" + HelpExampleCli("listtransactions", "") + "\nList transactions 100 to 120\n" + HelpExampleCli("listtransactions", "\"*\" 20 100") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listtransactions", "\"*\", 20, 100") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1526,9 +1540,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Include transactions to watch-only addresses (see 'importaddress')"}, {"include_removed", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Show transactions that were removed due to a reorg in the \"removed\" array\n" " (not guaranteed to work on pruned nodes)"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"transactions\": [\n" " \"address\":\"address\", (string) The bitcoin address of the transaction.\n" @@ -1563,11 +1576,13 @@ static UniValue listsinceblock(const JSONRPCRequest& request) " ],\n" " \"lastblock\": \"lastblockhash\" (string) The hash of the block (target_confirmations-1) from the best block on the main chain. This is typically used to feed back into listsinceblock the next time you call it. So you would generally use a target_confirmations of say 6, so you will be continually re-notified of transactions until they've reached 6 confirmations plus any new ones\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("listsinceblock", "") + }, + RPCExamples{ + HelpExampleCli("listsinceblock", "") + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6") + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1664,9 +1679,8 @@ static UniValue gettransaction(const JSONRPCRequest& request) { {"txid", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The transaction id"}, {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include watch-only addresses in balance calculation and details[]"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"amount\" : x.xxx, (numeric) The transaction amount in " + CURRENCY_UNIT + "\n" " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" @@ -1701,12 +1715,13 @@ static UniValue gettransaction(const JSONRPCRequest& request) " ],\n" " \"hex\" : \"data\" (string) Raw data for transaction\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + }, + RPCExamples{ + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true") + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1769,13 +1784,13 @@ static UniValue abandontransaction(const JSONRPCRequest& request) "It has no effect on transactions which are already abandoned.\n", { {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, - }} - .ToString() + - "\nResult:\n" - "\nExamples:\n" - + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") - ); + }, + }.ToString()); } // Make sure the results are valid at least up to the most recent block @@ -1813,12 +1828,13 @@ static UniValue backupwallet(const JSONRPCRequest& request) "\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n", { {"destination", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The destination directory or file"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("backupwallet", "\"backup.dat\"") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("backupwallet", "\"backup.dat\"") + HelpExampleRpc("backupwallet", "\"backup.dat\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1852,12 +1868,13 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) HelpRequiringPassphrase(pwallet) + "\n", { {"newsize", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "100", "The new keypool size"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("keypoolrefill", "") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("keypoolrefill", "") + HelpExampleRpc("keypoolrefill", "") - ); + }, + }.ToString()); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -1898,23 +1915,24 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) throw std::runtime_error( RPCHelpMan{"walletpassphrase", "\nStores the wallet decryption key in memory for 'timeout' seconds.\n" - "This is needed prior to performing transactions related to private keys such as sending bitcoins\n", + "This is needed prior to performing transactions related to private keys such as sending bitcoins\n" + "\nNote:\n" + "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" + "time that overrides the old one.\n", { {"passphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The wallet passphrase"}, {"timeout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."}, - }} - .ToString() + - "\nNote:\n" - "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" - "time that overrides the old one.\n" - "\nExamples:\n" + }, + RPCResults{}, + RPCExamples{ "\nUnlock the wallet for 60 seconds\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + "\nLock the wallet again (before 60 seconds)\n" + HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60") - ); + }, + }.ToString()); } auto locked_chain = pwallet->chain().lock(); @@ -1987,12 +2005,13 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) { {"oldpassphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The current passphrase"}, {"newpassphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The new passphrase"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"") - ); + }, + }.ToString()); } auto locked_chain = pwallet->chain().lock(); @@ -2039,9 +2058,9 @@ static UniValue walletlock(const JSONRPCRequest& request) "\nRemoves the wallet encryption key from memory, locking the wallet.\n" "After calling this method, you will need to call walletpassphrase again\n" "before being able to call any methods which require the wallet to be unlocked.\n", - {}} - .ToString() + - "\nExamples:\n" + {}, + RPCResults{}, + RPCExamples{ "\nSet the passphrase for 2 minutes to perform a transaction\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") + "\nPerform a send (requires passphrase set)\n" @@ -2050,7 +2069,8 @@ static UniValue walletlock(const JSONRPCRequest& request) + HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletlock", "") - ); + }, + }.ToString()); } auto locked_chain = pwallet->chain().lock(); @@ -2086,9 +2106,9 @@ static UniValue encryptwallet(const JSONRPCRequest& request) "If the wallet is already encrypted, use the walletpassphrasechange call.\n", { {"passphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long."}, - }} - .ToString() + - "\nExamples:\n" + }, + RPCResults{}, + RPCExamples{ "\nEncrypt your wallet\n" + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n" @@ -2099,7 +2119,8 @@ static UniValue encryptwallet(const JSONRPCRequest& request) + HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("encryptwallet", "\"my pass phrase\"") - ); + }, + }.ToString()); } auto locked_chain = pwallet->chain().lock(); @@ -2157,12 +2178,11 @@ static UniValue lockunspent(const JSONRPCRequest& request) }, }, }, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "true|false (boolean) Whether the command was successful or not\n" - - "\nExamples:\n" + }, + RPCExamples{ "\nList the unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nLock an unspent transaction\n" @@ -2173,7 +2193,8 @@ static UniValue lockunspent(const JSONRPCRequest& request) + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2269,9 +2290,8 @@ static UniValue listlockunspent(const JSONRPCRequest& request) RPCHelpMan{"listlockunspent", "\nReturns list of temporarily unspendable outputs.\n" "See the lockunspent call to lock and unlock transactions for spending.\n", - {}} - .ToString() + - "\nResult:\n" + {}, + RPCResult{ "[\n" " {\n" " \"txid\" : \"transactionid\", (string) The transaction id locked\n" @@ -2279,7 +2299,8 @@ static UniValue listlockunspent(const JSONRPCRequest& request) " }\n" " ,...\n" "]\n" - "\nExamples:\n" + }, + RPCExamples{ "\nList the unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nLock an unspent transaction\n" @@ -2290,7 +2311,8 @@ static UniValue listlockunspent(const JSONRPCRequest& request) + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlockunspent", "") - ); + }, + }.ToString()); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2326,14 +2348,15 @@ static UniValue settxfee(const JSONRPCRequest& request) "\nSet the transaction fee per kB for this wallet. Overrides the global -paytxfee command line parameter.\n", { {"amount", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "The transaction fee in " + CURRENCY_UNIT + "/kB"}, - }} - .ToString() + - "\nResult\n" + }, + RPCResult{ "true|false (boolean) Returns true if successful\n" - "\nExamples:\n" - + HelpExampleCli("settxfee", "0.00001") + }, + RPCExamples{ + HelpExampleCli("settxfee", "0.00001") + HelpExampleRpc("settxfee", "0.00001") - ); + }, + }.ToString()); } auto locked_chain = pwallet->chain().lock(); @@ -2365,9 +2388,9 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getwalletinfo", - "Returns an object containing various wallet state info.\n", {}} - .ToString() + - "\nResult:\n" + "Returns an object containing various wallet state info.\n", + {}, + RPCResult{ "{\n" " \"walletname\": xxxxx, (string) the wallet name\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n" @@ -2384,10 +2407,12 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) " \"hdmasterkeyid\": \"<hash160>\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\n" " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getwalletinfo", "") + }, + RPCExamples{ + HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2428,8 +2453,9 @@ static UniValue listwalletdir(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"listwalletdir", - "Returns a list of wallets in the wallet directory.\n", {}} - .ToString() + + "Returns a list of wallets in the wallet directory.\n", + {}, + RPCResult{ "{\n" " \"wallets\" : [ (json array of objects)\n" " {\n" @@ -2438,10 +2464,12 @@ static UniValue listwalletdir(const JSONRPCRequest& request) " ,...\n" " ]\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("listwalletdir", "") + }, + RPCExamples{ + HelpExampleCli("listwalletdir", "") + HelpExampleRpc("listwalletdir", "") - ); + }, + }.ToString()); } UniValue wallets(UniValue::VARR); @@ -2463,17 +2491,18 @@ static UniValue listwallets(const JSONRPCRequest& request) RPCHelpMan{"listwallets", "Returns a list of currently loaded wallets.\n" "For full information on the wallet, use \"getwalletinfo\"\n", - {}} - .ToString() + - "\nResult:\n" + {}, + RPCResult{ "[ (json array of strings)\n" " \"walletname\" (string) the wallet name\n" " ...\n" "]\n" - "\nExamples:\n" - + HelpExampleCli("listwallets", "") + }, + RPCExamples{ + HelpExampleCli("listwallets", "") + HelpExampleRpc("listwallets", "") - ); + }, + }.ToString()); UniValue obj(UniValue::VARR); @@ -2500,17 +2529,18 @@ static UniValue loadwallet(const JSONRPCRequest& request) "\napplied to the new wallet (eg -zapwallettxes, upgradewallet, rescan, etc).\n", { {"filename", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The wallet directory or .dat file."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"name\" : <wallet_name>, (string) The wallet name if loaded successfully.\n" " \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("loadwallet", "\"test.dat\"") + }, + RPCExamples{ + HelpExampleCli("loadwallet", "\"test.dat\"") + HelpExampleRpc("loadwallet", "\"test.dat\"") - ); + }, + }.ToString()); WalletLocation location(request.params[0].get_str()); std::string error; @@ -2554,17 +2584,18 @@ static UniValue createwallet(const JSONRPCRequest& request) { {"wallet_name", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The name for the new wallet. If this is a path, the wallet will be created at the path location."}, {"disable_private_keys", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"name\" : <wallet_name>, (string) The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path.\n" " \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("createwallet", "\"testwallet\"") + }, + RPCExamples{ + HelpExampleCli("createwallet", "\"testwallet\"") + HelpExampleRpc("createwallet", "\"testwallet\"") - ); + }, + }.ToString()); } std::string error; std::string warning; @@ -2608,12 +2639,13 @@ static UniValue unloadwallet(const JSONRPCRequest& request) "Specifying the wallet name on a wallet endpoint is invalid.", { {"wallet_name", RPCArg::Type::STR, /* opt */ true, /* default_val */ "the wallet name from the RPC request", "The name of the wallet to unload."}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("unloadwallet", "wallet_name") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("unloadwallet", "wallet_name") + HelpExampleRpc("unloadwallet", "wallet_name") - ); + }, + }.ToString()); } std::string wallet_name; @@ -2655,12 +2687,15 @@ static UniValue resendwallettransactions(const JSONRPCRequest& request) throw std::runtime_error( RPCHelpMan{"resendwallettransactions", "Immediately re-broadcast unconfirmed wallet transactions to all peers.\n" - "Intended only for testing; the wallet code periodically re-broadcasts\n", - {}} - .ToString() + - "automatically.\n" + "Intended only for testing; the wallet code periodically re-broadcasts\n" + "automatically.\n", + {}, + RPCResult{ "Returns an RPC error if -walletbroadcast is set to false.\n" "Returns array of transaction ids that were re-broadcast.\n" + }, + RPCExamples{""}, + }.ToString() ); if (!g_connman) @@ -2715,9 +2750,8 @@ static UniValue listunspent(const JSONRPCRequest& request) {"minimumSumAmount", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "unlimited", "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""}, }, "query_options"}, - }} - .ToString() + - "\nResult\n" + }, + RPCResult{ "[ (array of json object)\n" " {\n" " \"txid\" : \"txid\", (string) the transaction id \n" @@ -2737,14 +2771,15 @@ static UniValue listunspent(const JSONRPCRequest& request) " }\n" " ,...\n" "]\n" - - "\nExamples\n" - + HelpExampleCli("listunspent", "") + }, + RPCExamples{ + HelpExampleCli("listunspent", "") + HelpExampleCli("listunspent", "6 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + HelpExampleCli("listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") + HelpExampleRpc("listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ") - ); + }, + }.ToString()); int nMinDepth = 1; if (!request.params[0].isNull()) { @@ -3027,15 +3062,15 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) "options"}, {"iswitness", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction \n" " If iswitness is not present, heuristic tests will be used in decoding"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" " \"changepos\": n (numeric) The position of the added change output, or -1\n" "}\n" - "\nExamples:\n" + }, + RPCExamples{ "\nCreate a transaction with no inputs\n" + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + "\nAdd sufficient unsigned inputs to meet the output value\n" @@ -3044,7 +3079,8 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + "\nSend the transaction\n" + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") - ); + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); @@ -3061,7 +3097,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) FundTransaction(pwallet, tx, fee, change_position, request.params[1]); UniValue result(UniValue::VOBJ); - result.pushKV("hex", EncodeHexTx(tx)); + result.pushKV("hex", EncodeHexTx(CTransaction(tx))); result.pushKV("fee", ValueFromAmount(fee)); result.pushKV("changepos", change_position); @@ -3106,9 +3142,8 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) " \"ALL|ANYONECANPAY\"\n" " \"NONE|ANYONECANPAY\"\n" " \"SINGLE|ANYONECANPAY\""}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n" " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" @@ -3123,11 +3158,12 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) " ,...\n" " ]\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + }, + RPCExamples{ + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\"") - ); + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); @@ -3189,18 +3225,20 @@ static UniValue bumpfee(const JSONRPCRequest& request) " \"CONSERVATIVE\""}, }, "options"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"txid\": \"value\", (string) The id of the new transaction\n" " \"origfee\": n, (numeric) Fee of the replaced transaction\n" " \"fee\": n, (numeric) Fee of the new transaction\n" " \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty)\n" "}\n" - "\nExamples:\n" + }, + RPCExamples{ "\nBump the fee, get the new transaction\'s txid\n" + - HelpExampleCli("bumpfee", "<txid>")); + HelpExampleCli("bumpfee", "<txid>") + }, + }.ToString()); } RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); @@ -3315,14 +3353,15 @@ UniValue generate(const JSONRPCRequest& request) { {"nblocks", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "How many blocks are generated immediately."}, {"maxtries", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1000000", "How many iterations to try."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "[ blockhashes ] (array) hashes of blocks generated\n" - "\nExamples:\n" + }, + RPCExamples{ "\nGenerate 11 blocks\n" + HelpExampleCli("generate", "11") - ); + }, + }.ToString()); } if (!IsDeprecatedRPCEnabled("generate")) { @@ -3369,17 +3408,18 @@ UniValue rescanblockchain(const JSONRPCRequest& request) { {"start_height", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "block height where the rescan should start"}, {"stop_height", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "tip height", "the last block height that should be scanned"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"start_height\" (numeric) The block height where the rescan has started. If omitted, rescan started from the genesis block.\n" " \"stop_height\" (numeric) The height of the last rescanned block. If omitted, rescan stopped at the chain tip.\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("rescanblockchain", "100000 120000") + }, + RPCExamples{ + HelpExampleCli("rescanblockchain", "100000 120000") + HelpExampleRpc("rescanblockchain", "100000, 120000") - ); + }, + }.ToString()); } WalletRescanReserver reserver(pwallet); @@ -3576,14 +3616,12 @@ UniValue getaddressinfo(const JSONRPCRequest& request) "to be in the wallet.\n", { {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to get the information of."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"address\" : \"address\", (string) The bitcoin address validated\n" " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n" " \"ismine\" : true|false, (boolean) If the address is yours or not\n" - " \"solvable\" : true|false, (boolean) If the address is solvable by the wallet\n" " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" " \"solvable\" : true|false, (boolean) Whether we know how to spend coins sent to this address, ignoring the possible lack of private keys\n" " \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable)\n" @@ -3602,7 +3640,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) " \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output (only if \"script\" is \"multisig\")\n" " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key, for single-key addresses (possibly embedded in P2SH or P2WSH)\n" " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. It includes all getaddressinfo output fields for the embedded address, excluding metadata (\"timestamp\", \"hdkeypath\", \"hdseedid\") and relation to the wallet (\"ismine\", \"iswatchonly\").\n" - " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" + " \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed\n" " \"label\" : \"label\" (string) The label associated with the address, \"\" is the default label\n" " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n" " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" @@ -3616,10 +3654,12 @@ UniValue getaddressinfo(const JSONRPCRequest& request) " },...\n" " ]\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + }, + RPCExamples{ + HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") - ); + }, + }.ToString()); } LOCK(pwallet->cs_wallet); @@ -3646,7 +3686,6 @@ UniValue getaddressinfo(const JSONRPCRequest& request) ret.pushKV("desc", InferDescriptor(scriptPubKey, *pwallet)->ToString()); } ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); - ret.pushKV("solvable", IsSolvable(*pwallet, scriptPubKey)); UniValue detail = DescribeWalletAddress(pwallet, dest); ret.pushKVs(detail); if (pwallet->mapAddressBook.count(dest)) { @@ -3704,18 +3743,19 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request) "\nReturns the list of addresses assigned the specified label.\n", { {"label", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The label."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{ (json object with addresses as keys)\n" " \"address\": { (json object with information about address)\n" " \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n" " },...\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getaddressesbylabel", "\"tabby\"") + }, + RPCExamples{ + HelpExampleCli("getaddressesbylabel", "\"tabby\"") + HelpExampleRpc("getaddressesbylabel", "\"tabby\"") - ); + }, + }.ToString()); LOCK(pwallet->cs_wallet); @@ -3751,14 +3791,14 @@ static UniValue listlabels(const JSONRPCRequest& request) "\nReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n", { {"purpose", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "Address purpose to list labels for ('send','receive'). An empty string is the same as not providing this argument."}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "[ (json array of string)\n" " \"label\", (string) Label name\n" " ...\n" "]\n" - "\nExamples:\n" + }, + RPCExamples{ "\nList all labels\n" + HelpExampleCli("listlabels", "") + "\nList labels that have receiving addresses\n" @@ -3767,7 +3807,8 @@ static UniValue listlabels(const JSONRPCRequest& request) + HelpExampleCli("listlabels", "send") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlabels", "receive") - ); + }, + }.ToString()); LOCK(pwallet->cs_wallet); @@ -3815,14 +3856,15 @@ UniValue sethdseed(const JSONRPCRequest& request) " keypool will be used until it has been depleted."}, {"seed", RPCArg::Type::STR, /* opt */ true, /* default_val */ "random seed", "The WIF private key to use as the new HD seed.\n" " The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("sethdseed", "") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("sethdseed", "") + HelpExampleCli("sethdseed", "false") + HelpExampleCli("sethdseed", "true \"wifkey\"") + HelpExampleRpc("sethdseed", "true, \"wifkey\"") - ); + }, + }.ToString()); } if (IsInitialBlockDownload()) { @@ -3959,18 +4001,18 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) " \"NONE|ANYONECANPAY\"\n" " \"SINGLE|ANYONECANPAY\""}, {"bip32derivs", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n" " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" " ]\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("walletprocesspsbt", "\"psbt\"") - ); + }, + RPCExamples{ + HelpExampleCli("walletprocesspsbt", "\"psbt\"") + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); @@ -4041,8 +4083,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) }, }, }, - {"locktime", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Raw locktime. Non-0 value also locktime-activates inputs\n" - " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."}, + {"locktime", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"}, {"options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "", { {"changeAddress", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "pool address", "The bitcoin address to receive the change"}, @@ -4069,18 +4110,19 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) }, "options"}, {"bip32derivs", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n" " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" " \"changepos\": n (numeric) The position of the added change output, or -1\n" "}\n" - "\nExamples:\n" + }, + RPCExamples{ "\nCreate a transaction with no inputs\n" + HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") - ); + }, + }.ToString()); RPCTypeCheck(request.params, { UniValue::VARR, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index bea9ebefa3..ddeff8ba09 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1505,7 +1505,7 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall // implies that we can sign for every input. return -1; } - return GetVirtualTransactionSize(txNew); + return GetVirtualTransactionSize(CTransaction(txNew)); } int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) @@ -2857,7 +2857,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std txNew.vin.push_back(CTxIn(coin.outpoint,CScript())); } - nBytes = CalculateMaximumSignedTxSize(txNew, this, coin_control.fAllowWatchOnly); + nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); if (nBytes < 0) { strFailReason = _("Signing transaction failed"); return false; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index fa18e3a6fa..6f787416dd 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -35,8 +35,8 @@ #include <vector> //! Responsible for reading and validating the -wallet arguments and verifying the wallet database. -// This function will perform salvage on the wallet if requested, as long as only one wallet is -// being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). +//! This function will perform salvage on the wallet if requested, as long as only one wallet is +//! being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); //! Load wallet databases. @@ -55,10 +55,10 @@ void StopWallets(); void UnloadWallets(); //! Explicitly unload and delete the wallet. -// Blocks the current thread after signaling the unload intent so that all -// wallet clients release the wallet. -// Note that, when blocking is not required, the wallet is implicitly unloaded -// by the shared pointer deleter. +//! Blocks the current thread after signaling the unload intent so that all +//! wallet clients release the wallet. +//! Note that, when blocking is not required, the wallet is implicitly unloaded +//! by the shared pointer deleter. void UnloadWallet(std::shared_ptr<CWallet>&& wallet); bool AddWallet(const std::shared_ptr<CWallet>& wallet); @@ -587,8 +587,8 @@ public: int64_t nTimeCreated; int64_t nTimeExpires; std::string strComment; - //! todo: add something to note what created it (user, getnewaddress, change) - //! maybe should have a map<string, string> property map + // todo: add something to note what created it (user, getnewaddress, change) + // maybe should have a map<string, string> property map explicit CWalletKey(int64_t nExpires=0); diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp index 15d4ac1b89..ba89d1401d 100644 --- a/src/zmq/zmqpublishnotifier.cpp +++ b/src/zmq/zmqpublishnotifier.cpp @@ -101,6 +101,7 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) else { LogPrint(BCLog::ZMQ, "zmq: Reusing socket for address %s\n", address); + LogPrint(BCLog::ZMQ, "zmq: Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); psocket = i->second->psocket; mapPublishNotifiers.insert(std::make_pair(address, this)); diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp index d3eab46e5f..a34968ef7d 100644 --- a/src/zmq/zmqrpc.cpp +++ b/src/zmq/zmqrpc.cpp @@ -18,9 +18,9 @@ UniValue getzmqnotifications(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"getzmqnotifications", - "\nReturns information about the active ZeroMQ notifications.\n", {}} - .ToString() + - "\nResult:\n" + "\nReturns information about the active ZeroMQ notifications.\n", + {}, + RPCResult{ "[\n" " { (json object)\n" " \"type\": \"pubhashtx\", (string) Type of notification\n" @@ -29,10 +29,12 @@ UniValue getzmqnotifications(const JSONRPCRequest& request) " },\n" " ...\n" "]\n" - "\nExamples:\n" - + HelpExampleCli("getzmqnotifications", "") + }, + RPCExamples{ + HelpExampleCli("getzmqnotifications", "") + HelpExampleRpc("getzmqnotifications", "") - ); + }, + }.ToString()); } UniValue result(UniValue::VARR); diff --git a/test/functional/README.md b/test/functional/README.md index d40052ac93..628c77eb11 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -20,9 +20,13 @@ don't have test cases for. - Where possible, try to adhere to [PEP-8 guidelines](https://www.python.org/dev/peps/pep-0008/) - Use a python linter like flake8 before submitting PRs to catch common style nits (eg trailing whitespace, unused imports, etc) +- The oldest supported Python version is specified in [doc/dependencies.md](/doc/dependencies.md). + Consider using [pyenv](https://github.com/pyenv/pyenv), which checks [.python-version](/.python-version), + to prevent accidentally introducing modern syntax from an unsupported Python version. + The Travis linter also checks this, but [possibly not in all cases](https://github.com/bitcoin/bitcoin/pull/14884#discussion_r239585126). - See [the python lint script](/test/lint/lint-python.sh) that checks for violations that could lead to bugs and issues in the test code. -- Avoid wildcard imports where possible +- Avoid wildcard imports - Use a module-level docstring to describe what the test is testing, and how it is testing it. - When subclassing the BitcoinTestFramwork, place overrides for the diff --git a/test/functional/example_test.py b/test/functional/example_test.py index be3544ee74..f367e4fca8 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -13,7 +13,7 @@ is testing and *how* it's being tested # libraries then local imports). from collections import defaultdict -# Avoid wildcard * imports if possible +# Avoid wildcard * imports from test_framework.blocktools import (create_block, create_coinbase) from test_framework.messages import CInv from test_framework.mininode import ( diff --git a/test/functional/feature_blocksdir.py b/test/functional/feature_blocksdir.py index c170f510c8..3a4889bbe9 100755 --- a/test/functional/feature_blocksdir.py +++ b/test/functional/feature_blocksdir.py @@ -18,6 +18,8 @@ class BlocksdirTest(BitcoinTestFramework): def run_test(self): self.stop_node(0) + assert os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest", "blocks")) + assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "blocks")) shutil.rmtree(self.nodes[0].datadir) initialize_datadir(self.options.tmpdir, 0) self.log.info("Starting with nonexistent blocksdir ...") diff --git a/test/functional/feature_shutdown.py b/test/functional/feature_shutdown.py index b633fabb1f..5084cb1322 100755 --- a/test/functional/feature_shutdown.py +++ b/test/functional/feature_shutdown.py @@ -5,7 +5,7 @@ """Test bitcoind shutdown.""" from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, get_rpc_proxy +from test_framework.util import assert_equal, get_rpc_proxy, wait_until from threading import Thread def test_long_call(node): @@ -20,8 +20,14 @@ class ShutdownTest(BitcoinTestFramework): def run_test(self): node = get_rpc_proxy(self.nodes[0].url, 1, timeout=600, coveragedir=self.nodes[0].coverage_dir) + # Force connection establishment by executing a dummy command. + node.getblockcount() Thread(target=test_long_call, args=(node,)).start() - # wait 1 second to ensure event loop waits for current connections to close + # Wait until the server is executing the above `waitfornewblock`. + wait_until(lambda: len(self.nodes[0].getrpcinfo()['active_commands']) == 2) + # Wait 1 second after requesting shutdown but not before the `stop` call + # finishes. This is to ensure event loop waits for current connections + # to close. self.stop_node(0, wait=1000) if __name__ == '__main__': diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index afa9de580f..d5a1b53408 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -198,9 +198,19 @@ class RESTTest (BitcoinTestFramework): self.nodes[0].generate(1) # generate block to not affect upcoming tests self.sync_all() - self.log.info("Test the /block and /headers URIs") + self.log.info("Test the /block, /blockhashbyheight and /headers URIs") bb_hash = self.nodes[0].getbestblockhash() + # Check result if block does not exists + assert_equal(self.test_rest_request('/headers/1/0000000000000000000000000000000000000000000000000000000000000000'), []) + self.test_rest_request('/block/0000000000000000000000000000000000000000000000000000000000000000', status=404, ret_type=RetType.OBJ) + + # Check result if block is not in the active chain + self.nodes[0].invalidateblock(bb_hash) + assert_equal(self.test_rest_request('/headers/1/{}'.format(bb_hash)), []) + self.test_rest_request('/block/{}'.format(bb_hash)) + self.nodes[0].reconsiderblock(bb_hash) + # Check binary format response = self.test_rest_request("/block/{}".format(bb_hash), req_type=ReqType.BIN, ret_type=RetType.OBJ) assert_greater_than(int(response.getheader('content-length')), 80) @@ -227,6 +237,23 @@ class RESTTest (BitcoinTestFramework): # Check json format block_json_obj = self.test_rest_request("/block/{}".format(bb_hash)) assert_equal(block_json_obj['hash'], bb_hash) + assert_equal(self.test_rest_request("/blockhashbyheight/{}".format(block_json_obj['height']))['blockhash'], bb_hash) + + # Check hex/bin format + resp_hex = self.test_rest_request("/blockhashbyheight/{}".format(block_json_obj['height']), req_type=ReqType.HEX, ret_type=RetType.OBJ) + assert_equal(resp_hex.read().decode('utf-8').rstrip(), bb_hash) + resp_bytes = self.test_rest_request("/blockhashbyheight/{}".format(block_json_obj['height']), req_type=ReqType.BIN, ret_type=RetType.BYTES) + blockhash = binascii.hexlify(resp_bytes[::-1]).decode('utf-8') + assert_equal(blockhash, bb_hash) + + # Check invalid blockhashbyheight requests + resp = self.test_rest_request("/blockhashbyheight/abc", ret_type=RetType.OBJ, status=400) + assert_equal(resp.read().decode('utf-8').rstrip(), "Invalid height: abc") + resp = self.test_rest_request("/blockhashbyheight/1000000", ret_type=RetType.OBJ, status=404) + assert_equal(resp.read().decode('utf-8').rstrip(), "Block height out of range") + resp = self.test_rest_request("/blockhashbyheight/-1", ret_type=RetType.OBJ, status=400) + assert_equal(resp.read().decode('utf-8').rstrip(), "Invalid height: -1") + self.test_rest_request("/blockhashbyheight/", ret_type=RetType.OBJ, status=400) # Compare with json block header json_obj = self.test_rest_request("/headers/1/{}".format(bb_hash)) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index d95da227e5..8f8e89cf15 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -755,7 +755,7 @@ class SegWitTest(BitcoinTestFramework): spend_tx.vin[0].scriptSig = CScript([p2wsh_pubkey, b'a']) spend_tx.rehash() with self.nodes[0].assert_debug_log( - expected_msgs=('Not relaying invalid transaction {}'.format(spend_tx.hash), 'was not accepted: mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)')): + expected_msgs=(spend_tx.hash, 'was not accepted: mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)')): test_transaction_acceptance(self.nodes[0], self.test_node, spend_tx, with_witness=False, accepted=False) # Now put the witness script in the witness, should succeed after diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py index 4facd6c334..c370ce0c04 100755 --- a/test/lint/check-doc.py +++ b/test/lint/check-doc.py @@ -30,8 +30,8 @@ def main(): used = check_output(CMD_GREP_ARGS, shell=True, universal_newlines=True, encoding='utf8') docd = check_output(CMD_GREP_DOCS, shell=True, universal_newlines=True, encoding='utf8') else: - used = check_output(CMD_GREP_ARGS, shell=True, universal_newlines=True) # encoding='utf8' - docd = check_output(CMD_GREP_DOCS, shell=True, universal_newlines=True) # encoding='utf8' + used = check_output(CMD_GREP_ARGS, shell=True).decode('utf8').strip() + docd = check_output(CMD_GREP_DOCS, shell=True).decode('utf8').strip() args_used = set(re.findall(re.compile(REGEX_ARG), used)) args_docd = set(re.findall(re.compile(REGEX_DOC), docd)).union(SET_DOC_OPTIONAL) diff --git a/test/lint/lint-format-strings.sh b/test/lint/lint-format-strings.sh index 2c443abf6b..c994ae3f4d 100755 --- a/test/lint/lint-format-strings.sh +++ b/test/lint/lint-format-strings.sh @@ -11,20 +11,20 @@ export LC_ALL=C 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 - vfprintf,1 - vprintf,1 - vsnprintf,1 - vsprintf,1 - WalletLogPrintf,0 + "FatalError,0" + "fprintf,1" + "LogConnectFailure,1" + "LogPrint,1" + "LogPrintf,0" + "printf,0" + "snprintf,2" + "sprintf,1" + "strprintf,0" + "vfprintf,1" + "vprintf,1" + "vsnprintf,1" + "vsprintf,1" + "WalletLogPrintf,0" ) EXIT_CODE=0 diff --git a/test/lint/lint-python.sh b/test/lint/lint-python.sh index 3dbb9fff28..f5b851aeab 100755 --- a/test/lint/lint-python.sh +++ b/test/lint/lint-python.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying @@ -8,77 +8,79 @@ export LC_ALL=C -# E101 indentation contains mixed spaces and tabs -# E112 expected an indented block -# E113 unexpected indentation -# E115 expected an indented block (comment) -# E116 unexpected indentation (comment) -# E125 continuation line with same indent as next logical line -# E129 visually indented line with same indent as next logical line -# E131 continuation line unaligned for hanging indent -# E133 closing bracket is missing indentation -# E223 tab before operator -# E224 tab after operator -# E242 tab after ',' -# E266 too many leading '#' for block comment -# E271 multiple spaces after keyword -# E272 multiple spaces before keyword -# E273 tab after keyword -# E274 tab before keyword -# E275 missing whitespace after keyword -# E304 blank lines found after function decorator -# E306 expected 1 blank line before a nested definition -# E401 multiple imports on one line -# E402 module level import not at top of file -# F403 'from foo_module import *' used; unable to detect undefined names -# F405 foo_function may be undefined, or defined from star imports: bar_module -# E502 the backslash is redundant between brackets -# E701 multiple statements on one line (colon) -# E702 multiple statements on one line (semicolon) -# E703 statement ends with a semicolon -# E711 comparison to None should be 'if cond is None:' -# E714 test for object identity should be "is not" -# E721 do not compare types, use "isinstance()" -# E741 do not use variables named "l", "O", or "I" -# E742 do not define classes named "l", "O", or "I" -# E743 do not define functions named "l", "O", or "I" -# E901 SyntaxError: invalid syntax -# E902 TokenError: EOF in multi-line string -# F401 module imported but unused -# F402 import module from line N shadowed by loop variable -# F404 future import(s) name after other statements -# F406 "from module import *" only allowed at module level -# F407 an undefined __future__ feature name was imported -# F601 dictionary key name repeated with different values -# F602 dictionary key variable name repeated with different values -# F621 too many expressions in an assignment with star-unpacking -# F622 two or more starred expressions in an assignment (a, *b, *c = d) -# F631 assertion test is a tuple, which are always True -# F701 a break statement outside of a while or for loop -# F702 a continue statement outside of a while or for loop -# F703 a continue statement in a finally block in a loop -# F704 a yield or yield from statement outside of a function -# F705 a return statement with arguments inside a generator -# F706 a return statement outside of a function/method -# F707 an except: block as not the last exception handler -# F811 redefinition of unused name from line N -# F812 list comprehension redefines 'foo' from line N -# F821 undefined name 'Foo' -# F822 undefined name name in __all__ -# F823 local variable name … referenced before assignment -# F831 duplicate argument name in function definition -# F841 local variable 'foo' is assigned to but never used -# W191 indentation contains tabs -# W291 trailing whitespace -# W292 no newline at end of file -# W293 blank line contains whitespace -# W504 line break after binary operator -# W601 .has_key() is deprecated, use "in" -# W602 deprecated form of raising exception -# W603 "<>" is deprecated, use "!=" -# W604 backticks are deprecated, use "repr()" -# W605 invalid escape sequence "x" -# W606 'async' and 'await' are reserved keywords starting with Python 3.7 +enabled=( + E101 # indentation contains mixed spaces and tabs + E112 # expected an indented block + E113 # unexpected indentation + E115 # expected an indented block (comment) + E116 # unexpected indentation (comment) + E125 # continuation line with same indent as next logical line + E129 # visually indented line with same indent as next logical line + E131 # continuation line unaligned for hanging indent + E133 # closing bracket is missing indentation + E223 # tab before operator + E224 # tab after operator + E242 # tab after ',' + E266 # too many leading '#' for block comment + E271 # multiple spaces after keyword + E272 # multiple spaces before keyword + E273 # tab after keyword + E274 # tab before keyword + E275 # missing whitespace after keyword + E304 # blank lines found after function decorator + E306 # expected 1 blank line before a nested definition + E401 # multiple imports on one line + E402 # module level import not at top of file + E502 # the backslash is redundant between brackets + E701 # multiple statements on one line (colon) + E702 # multiple statements on one line (semicolon) + E703 # statement ends with a semicolon + E711 # comparison to None should be 'if cond is None:' + E714 # test for object identity should be "is not" + E721 # do not compare types, use "isinstance()" + E741 # do not use variables named "l", "O", or "I" + E742 # do not define classes named "l", "O", or "I" + E743 # do not define functions named "l", "O", or "I" + E901 # SyntaxError: invalid syntax + E902 # TokenError: EOF in multi-line string + F401 # module imported but unused + F402 # import module from line N shadowed by loop variable + F403 # 'from foo_module import *' used; unable to detect undefined names + F404 # future import(s) name after other statements + F405 # foo_function may be undefined, or defined from star imports: bar_module + F406 # "from module import *" only allowed at module level + F407 # an undefined __future__ feature name was imported + F601 # dictionary key name repeated with different values + F602 # dictionary key variable name repeated with different values + F621 # too many expressions in an assignment with star-unpacking + F622 # two or more starred expressions in an assignment (a, *b, *c = d) + F631 # assertion test is a tuple, which are always True + F701 # a break statement outside of a while or for loop + F702 # a continue statement outside of a while or for loop + F703 # a continue statement in a finally block in a loop + F704 # a yield or yield from statement outside of a function + F705 # a return statement with arguments inside a generator + F706 # a return statement outside of a function/method + F707 # an except: block as not the last exception handler + F811 # redefinition of unused name from line N + F812 # list comprehension redefines 'foo' from line N + F821 # undefined name 'Foo' + F822 # undefined name name in __all__ + F823 # local variable name … referenced before assignment + F831 # duplicate argument name in function definition + F841 # local variable 'foo' is assigned to but never used + W191 # indentation contains tabs + W291 # trailing whitespace + W292 # no newline at end of file + W293 # blank line contains whitespace + W504 # line break after binary operator + W601 # .has_key() is deprecated, use "in" + W602 # deprecated form of raising exception + W603 # "<>" is deprecated, use "!=" + W604 # backticks are deprecated, use "repr()" + W605 # invalid escape sequence "x" + W606 # 'async' and 'await' are reserved keywords starting with Python 3.7 +) if ! command -v flake8 > /dev/null; then echo "Skipping Python linting since flake8 is not installed. Install by running \"pip3 install flake8\"" @@ -88,4 +90,4 @@ elif PYTHONWARNINGS="ignore" flake8 --version | grep -q "Python 2"; then exit 0 fi -PYTHONWARNINGS="ignore" flake8 --ignore=B,C,E,F,I,N,W --select=E101,E112,E113,E115,E116,E125,E129,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E711,E714,E721,E741,E742,E743,E901,E902,F401,F402,F403,F404,F405,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W191,W291,W292,W293,W504,W601,W602,W603,W604,W605,W606 "${@:-.}" +PYTHONWARNINGS="ignore" flake8 --ignore=B,C,E,F,I,N,W --select=$(IFS=","; echo "${enabled[*]}") "${@:-.}" diff --git a/test/lint/lint-shell.sh b/test/lint/lint-shell.sh index 7ed239576e..6f5e6546c5 100755 --- a/test/lint/lint-shell.sh +++ b/test/lint/lint-shell.sh @@ -13,7 +13,7 @@ export LC_ALL=C # respectively. So export LC_ALL=C is set as required by lint-shell-locale.sh # but unset here in case of running in Travis. if [ "$TRAVIS" = "true" ]; then - unset LC_ALL + unset LC_ALL fi if ! command -v shellcheck > /dev/null; then @@ -22,27 +22,26 @@ if ! command -v shellcheck > /dev/null; then fi # Disabled warnings: -# SC1087: Use braces when expanding arrays, e.g. ${array[idx]} (or ${var}[.. to quiet). -# SC1117: Backslash is literal in "\.". Prefer explicit escaping: "\\.". -# SC2001: See if you can use ${variable//search/replace} instead. -# SC2004: $/${} is unnecessary on arithmetic variables. -# SC2005: Useless echo? Instead of 'echo $(cmd)', just use 'cmd'. -# SC2006: Use $(..) instead of legacy `..`. -# SC2016: Expressions don't expand in single quotes, use double quotes for that. -# SC2028: echo won't expand escape sequences. Consider printf. -# SC2046: Quote this to prevent word splitting. -# SC2048: Use "$@" (with quotes) to prevent whitespace problems. -# SC2066: Since you double quoted this, it will not word split, and the loop will only run once. -# SC2086: Double quote to prevent globbing and word splitting. -# SC2116: Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'. -# SC2148: Tips depend on target shell and yours is unknown. Add a shebang. -# SC2162: read without -r will mangle backslashes. -# SC2166: Prefer [ p ] && [ q ] as [ p -a q ] is not well defined. -# SC2166: Prefer [ p ] || [ q ] as [ p -o q ] is not well defined. -# SC2181: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?. -# SC2206: Quote to prevent word splitting, or split robustly with mapfile or read -a. -# SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting). -# SC2230: which is non-standard. Use builtin 'command -v' instead. -# SC2236: Don't force -n instead of ! -z. -shellcheck -e SC1087,SC1117,SC2001,SC2004,SC2005,SC2006,SC2016,SC2028,SC2046,SC2048,SC2066,SC2086,SC2116,SC2148,SC2162,SC2166,SC2181,SC2206,SC2207,SC2230,SC2236 \ +disabled=( + SC1087 # Use braces when expanding arrays, e.g. ${array[idx]} (or ${var}[.. to quiet). + SC2001 # See if you can use ${variable//search/replace} instead. + SC2004 # $/${} is unnecessary on arithmetic variables. + SC2005 # Useless echo? Instead of 'echo $(cmd)', just use 'cmd'. + SC2006 # Use $(..) instead of legacy `..`. + SC2016 # Expressions don't expand in single quotes, use double quotes for that. + SC2028 # echo won't expand escape sequences. Consider printf. + SC2046 # Quote this to prevent word splitting. + SC2048 # Use "$@" (with quotes) to prevent whitespace problems. + SC2066 # Since you double quoted this, it will not word split, and the loop will only run once. + SC2086 # Double quote to prevent globbing and word splitting. + SC2116 # Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'. + SC2162 # read without -r will mangle backslashes. + SC2166 # Prefer [ p ] {&&,||} [ q ] as [ p -{a,o} q ] is not well defined. + SC2181 # Check exit code directly with e.g. 'if mycmd;', not indirectly with $?. + SC2206 # Quote to prevent word splitting, or split robustly with mapfile or read -a. + SC2207 # Prefer mapfile or read -a to split command output (or quote to avoid splitting). + SC2230 # which is non-standard. Use builtin 'command -v' instead. + SC2236 # Don't force -n instead of ! -z. +) +shellcheck -e "$(IFS=","; echo "${disabled[*]}")" \ $(git ls-files -- "*.sh" | grep -vE 'src/(secp256k1|univalue)/') diff --git a/test/lint/lint-whitespace.sh b/test/lint/lint-whitespace.sh index beb7ec42f4..f318e19071 100755 --- a/test/lint/lint-whitespace.sh +++ b/test/lint/lint-whitespace.sh @@ -23,7 +23,7 @@ while getopts "?" opt; do done if [ -z "${TRAVIS_COMMIT_RANGE}" ]; then - if [ "$1" ]; then + if [ -n "$1" ]; then TRAVIS_COMMIT_RANGE="HEAD~$1...HEAD" else TRAVIS_COMMIT_RANGE="HEAD" |