diff options
157 files changed, 3445 insertions, 1892 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 1a972e1e4b..b3ac6d06cb 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -312,16 +312,16 @@ task: FILE_ENV: "./ci/test/00_setup_env_mac.sh" task: - name: 'macOS 12 native x86_64 [gui, system sqlite] [no depends]' + name: 'macOS 13 native arm64 [gui, sqlite only] [no depends]' macos_instance: # Use latest image, but hardcode version to avoid silent upgrades (and breaks) - image: monterey-xcode-13.3 # https://cirrus-ci.org/guide/macOS + image: ghcr.io/cirruslabs/macos-ventura-xcode:14.1 # https://cirrus-ci.org/guide/macOS << : *MACOS_NATIVE_TASK_TEMPLATE env: << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV CI_USE_APT_INSTALL: "no" PACKAGE_MANAGER_INSTALL: "echo" # Nothing to do - FILE_ENV: "./ci/test/00_setup_env_mac_native_x86_64.sh" + FILE_ENV: "./ci/test/00_setup_env_mac_native_arm64.sh" task: name: 'ARM64 Android APK [focal]' diff --git a/.python-version b/.python-version index 8b7b0b52e5..cd337510be 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.6.12 +3.6.15 diff --git a/build_msvc/bitcoin-cli/bitcoin-cli.vcxproj b/build_msvc/bitcoin-cli/bitcoin-cli.vcxproj index e5e0e978f8..738884fb41 100644 --- a/build_msvc/bitcoin-cli/bitcoin-cli.vcxproj +++ b/build_msvc/bitcoin-cli/bitcoin-cli.vcxproj @@ -15,6 +15,9 @@ <ProjectReference Include="..\libbitcoin_cli\libbitcoin_cli.vcxproj"> <Project>{0667528c-d734-4009-adf9-c0d6c4a5a5a6}</Project> </ProjectReference> + <ProjectReference Include="..\libbitcoin_common\libbitcoin_common.vcxproj"> + <Project>{7c87e378-df58-482e-aa2f-1bc129bc19ce}</Project> + </ProjectReference> <ProjectReference Include="..\libbitcoin_crypto\libbitcoin_crypto.vcxproj"> <Project>{6190199c-6cf4-4dad-bfbd-93fa72a760c1}</Project> </ProjectReference> diff --git a/build_msvc/libbitcoin_common/libbitcoin_common.vcxproj.in b/build_msvc/libbitcoin_common/libbitcoin_common.vcxproj.in index b47d62b295..482e4333f7 100644 --- a/build_msvc/libbitcoin_common/libbitcoin_common.vcxproj.in +++ b/build_msvc/libbitcoin_common/libbitcoin_common.vcxproj.in @@ -8,6 +8,7 @@ <ConfigurationType>StaticLibrary</ConfigurationType> </PropertyGroup> <ItemGroup> + <ClCompile Include="..\..\src\common\url.cpp" /> @SOURCE_FILES@ </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> diff --git a/build_msvc/libbitcoin_util/libbitcoin_util.vcxproj.in b/build_msvc/libbitcoin_util/libbitcoin_util.vcxproj.in index 6ec40461c2..adf4fa0354 100644 --- a/build_msvc/libbitcoin_util/libbitcoin_util.vcxproj.in +++ b/build_msvc/libbitcoin_util/libbitcoin_util.vcxproj.in @@ -8,7 +8,6 @@ <ConfigurationType>StaticLibrary</ConfigurationType> </PropertyGroup> <ItemGroup> - <ClCompile Include="..\..\src\util\url.cpp" /> @SOURCE_FILES@ </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> diff --git a/build_msvc/libleveldb/libleveldb.vcxproj b/build_msvc/libleveldb/libleveldb.vcxproj index 009be30dec..2914eb2cfb 100644 --- a/build_msvc/libleveldb/libleveldb.vcxproj +++ b/build_msvc/libleveldb/libleveldb.vcxproj @@ -50,7 +50,7 @@ </ItemGroup> <ItemDefinitionGroup> <ClCompile> - <PreprocessorDefinitions>HAVE_CRC32C=0;HAVE_SNAPPY=0;__STDC_LIMIT_MACROS;LEVELDB_IS_BIG_ENDIAN=0;_UNICODE;UNICODE;_CRT_NONSTDC_NO_DEPRECATE;LEVELDB_PLATFORM_WINDOWS;LEVELDB_ATOMIC_PRESENT;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>HAVE_CRC32C=0;HAVE_SNAPPY=0;LEVELDB_IS_BIG_ENDIAN=0;_UNICODE;UNICODE;_CRT_NONSTDC_NO_DEPRECATE;LEVELDB_PLATFORM_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <DisableSpecificWarnings>4244;4267</DisableSpecificWarnings> <AdditionalIncludeDirectories>..\..\src\leveldb;..\..\src\leveldb\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> diff --git a/ci/test/00_setup_env_mac_native_x86_64.sh b/ci/test/00_setup_env_mac_native_arm64.sh index d176296e76..cb0e13e77c 100755 --- a/ci/test/00_setup_env_mac_native_x86_64.sh +++ b/ci/test/00_setup_env_mac_native_arm64.sh @@ -6,12 +6,11 @@ export LC_ALL=C.UTF-8 -export HOST=x86_64-apple-darwin -export PIP_PACKAGES="zmq lief" +export HOST=arm64-apple-darwin +export PIP_PACKAGES="zmq" export GOAL="install" -export BITCOIN_CONFIG="--with-gui --enable-reduce-exports" +export BITCOIN_CONFIG="--with-gui --with-miniupnpc --with-natpmp --enable-reduce-exports" export CI_OS_NAME="macos" export NO_DEPENDS=1 export OSX_SDK="" export CCACHE_SIZE=300M -export RUN_SECURITY_TESTS="true" diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index b256ae21a5..c25abb99ee 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -27,6 +27,11 @@ export P_CI_DIR="$PWD" if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then echo "Creating $DOCKER_NAME_TAG container to run in" + LOCAL_UID=$(id -u) + LOCAL_GID=$(id -g) + + # the name isn't important, so long as we use the same UID + LOCAL_USER=nonroot ${CI_RETRY_EXE} docker pull "$DOCKER_NAME_TAG" if [ -n "${RESTART_CI_DOCKER_BEFORE_RUN}" ] ; then @@ -44,7 +49,16 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then --env-file /tmp/env \ --name $CONTAINER_NAME \ $DOCKER_NAME_TAG) - export DOCKER_CI_CMD_PREFIX="docker exec $DOCKER_ID" + + # Create a non-root user inside the container which matches the local user. + # + # This prevents the root user in the container modifying the local file system permissions + # on the mounted directories + docker exec "$DOCKER_ID" useradd -u "$LOCAL_UID" -o -m "$LOCAL_USER" + docker exec "$DOCKER_ID" groupmod -o -g "$LOCAL_GID" "$LOCAL_USER" + docker exec "$DOCKER_ID" chown -R "$LOCAL_USER":"$LOCAL_USER" "${BASE_ROOT_DIR}" + export DOCKER_CI_CMD_PREFIX_ROOT="docker exec -u 0 $DOCKER_ID" + export DOCKER_CI_CMD_PREFIX="docker exec -u $LOCAL_UID $DOCKER_ID" else echo "Running on host system without docker wrapper" fi @@ -52,15 +66,19 @@ fi CI_EXEC () { $DOCKER_CI_CMD_PREFIX bash -c "export PATH=$BASE_SCRATCH_DIR/bins/:\$PATH && cd \"$P_CI_DIR\" && $*" } +CI_EXEC_ROOT () { + $DOCKER_CI_CMD_PREFIX_ROOT bash -c "export PATH=$BASE_SCRATCH_DIR/bins/:\$PATH && cd \"$P_CI_DIR\" && $*" +} export -f CI_EXEC +export -f CI_EXEC_ROOT if [ -n "$DPKG_ADD_ARCH" ]; then - CI_EXEC dpkg --add-architecture "$DPKG_ADD_ARCH" + CI_EXEC_ROOT dpkg --add-architecture "$DPKG_ADD_ARCH" fi if [[ $DOCKER_NAME_TAG == *centos* ]]; then - ${CI_RETRY_EXE} CI_EXEC dnf -y install epel-release - ${CI_RETRY_EXE} CI_EXEC dnf -y --allowerasing install "$DOCKER_PACKAGES" "$PACKAGES" + ${CI_RETRY_EXE} CI_EXEC_ROOT dnf -y install epel-release + ${CI_RETRY_EXE} CI_EXEC_ROOT dnf -y --allowerasing install "$DOCKER_PACKAGES" "$PACKAGES" elif [ "$CI_USE_APT_INSTALL" != "no" ]; then if [[ "${ADD_UNTRUSTED_BPFCC_PPA}" == "true" ]]; then # Ubuntu 22.04 LTS and Debian 11 both have an outdated bpfcc-tools packages. @@ -68,17 +86,17 @@ elif [ "$CI_USE_APT_INSTALL" != "no" ]; then # packages. Meanwhile, use an untrusted PPA to install an up-to-date version of the bpfcc-tools # package. # TODO: drop this once we can use newer images in GCE - CI_EXEC add-apt-repository ppa:hadret/bpfcc + CI_EXEC_ROOT add-apt-repository ppa:hadret/bpfcc fi - ${CI_RETRY_EXE} CI_EXEC apt-get update - ${CI_RETRY_EXE} CI_EXEC apt-get install --no-install-recommends --no-upgrade -y "$PACKAGES" "$DOCKER_PACKAGES" + ${CI_RETRY_EXE} CI_EXEC_ROOT apt-get update + ${CI_RETRY_EXE} CI_EXEC_ROOT apt-get install --no-install-recommends --no-upgrade -y "$PACKAGES" "$DOCKER_PACKAGES" fi if [ -n "$PIP_PACKAGES" ]; then if [ "$CI_OS_NAME" == "macos" ]; then sudo -H pip3 install --upgrade pip # shellcheck disable=SC2086 - IN_GETOPT_BIN="/usr/local/opt/gnu-getopt/bin/getopt" ${CI_RETRY_EXE} pip3 install --user $PIP_PACKAGES + IN_GETOPT_BIN="$(brew --prefix gnu-getopt)/bin/getopt" ${CI_RETRY_EXE} pip3 install --user $PIP_PACKAGES else # shellcheck disable=SC2086 ${CI_RETRY_EXE} CI_EXEC pip3 install --user $PIP_PACKAGES @@ -126,7 +144,7 @@ if [[ "${RUN_TIDY}" == "true" ]]; then CI_EXEC "mkdir -p ${DIR_IWYU}/build/" CI_EXEC "git clone --depth=1 https://github.com/include-what-you-use/include-what-you-use -b clang_14 ${DIR_IWYU}/include-what-you-use" CI_EXEC "cd ${DIR_IWYU}/build && cmake -G 'Unix Makefiles' -DCMAKE_PREFIX_PATH=/usr/lib/llvm-14 ../include-what-you-use" - CI_EXEC "cd ${DIR_IWYU}/build && make install $MAKEJOBS" + CI_EXEC_ROOT "cd ${DIR_IWYU}/build && make install $MAKEJOBS" fi fi diff --git a/ci/test/05_before_script.sh b/ci/test/05_before_script.sh index ef3dff86ca..dd2b43d38b 100755 --- a/ci/test/05_before_script.sh +++ b/ci/test/05_before_script.sh @@ -11,6 +11,7 @@ if [ "$CI_OS_NAME" == "macos" ]; then echo > "${HOME}/Library/Application Support/Bitcoin" else CI_EXEC echo \> \$HOME/.bitcoin + CI_EXEC_ROOT echo \> \$HOME/.bitcoin fi CI_EXEC mkdir -p "${DEPENDS_DIR}/SDKs" "${DEPENDS_DIR}/sdk-sources" diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh index 0ee80cf114..14a5c08bb4 100755 --- a/ci/test/06_script_b.sh +++ b/ci/test/06_script_b.sh @@ -40,11 +40,13 @@ if [ "${RUN_TIDY}" = "true" ]; then ( CI_EXEC run-clang-tidy -quiet "${MAKEJOBS}" ) | grep -C5 "error" export P_CI_DIR="${BASE_BUILD_DIR}/bitcoin-$HOST/" CI_EXEC "python3 ${DIR_IWYU}/include-what-you-use/iwyu_tool.py"\ + " src/common/url.cpp"\ " src/compat"\ " src/dbwrapper.cpp"\ " src/init"\ " src/kernel"\ " src/node/chainstate.cpp"\ + " src/node/chainstatemanager_args.cpp"\ " src/node/mempool_args.cpp"\ " src/node/validation_cache_args.cpp"\ " src/policy/feerate.cpp"\ @@ -54,9 +56,9 @@ if [ "${RUN_TIDY}" = "true" ]; then " src/rpc/fees.cpp"\ " src/rpc/signmessage.cpp"\ " src/test/fuzz/txorphan.cpp"\ - " src/threadinterrupt.cpp"\ " src/util/bip32.cpp"\ " src/util/bytevectorhash.cpp"\ + " src/util/check.cpp"\ " src/util/error.cpp"\ " src/util/getuniquepath.cpp"\ " src/util/hasher.cpp"\ @@ -67,7 +69,7 @@ if [ "${RUN_TIDY}" = "true" ]; then " src/util/strencodings.cpp"\ " src/util/string.cpp"\ " src/util/syserror.cpp"\ - " src/util/url.cpp"\ + " src/util/threadinterrupt.cpp"\ " src/zmq"\ " -p . ${MAKEJOBS} -- -Xiwyu --cxx17ns -Xiwyu --mapping_file=${BASE_BUILD_DIR}/bitcoin-$HOST/contrib/devtools/iwyu/bitcoin.core.imp" fi diff --git a/configure.ac b/configure.ac index f2d621ec7f..685c840f2b 100644 --- a/configure.ac +++ b/configure.ac @@ -1413,7 +1413,7 @@ if test "$use_usdt" != "no"; then fi AM_CONDITIONAL([ENABLE_USDT_TRACEPOINTS], [test "$use_usdt" = "yes"]) -if test "$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_util$build_bitcoind$bitcoin_enable_qt$use_bench$use_tests" = "nonononononono"; then +if test "$build_bitcoind$bitcoin_enable_qt$use_bench$use_tests" = "nononono"; then use_upnp=no use_natpmp=no use_zmq=no diff --git a/doc/bips.md b/doc/bips.md index 25381818e4..1d5c91b8bd 100644 --- a/doc/bips.md +++ b/doc/bips.md @@ -28,6 +28,7 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v24.0**): and it is disabled by default at build time since **v0.19.0** ([PR #15584](https://github.com/bitcoin/bitcoin/pull/15584)). It has been removed as of **v0.20.0** ([PR 17165](https://github.com/bitcoin/bitcoin/pull/17165)). * [`BIP 84`](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki): The experimental descriptor wallets introduced in **v0.21.0** by default use the Hierarchical Deterministic Wallet derivation proposed by BIP 84. ([PR #16528](https://github.com/bitcoin/bitcoin/pull/16528)) +* [`BIP 86`](https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki): Descriptor wallets by default use the Hierarchical Deterministic Wallet derivation proposed by BIP 86 since **v23.0** ([PR #22364](https://github.com/bitcoin/bitcoin/pull/22364)). * [`BIP 90`](https://github.com/bitcoin/bips/blob/master/bip-0090.mediawiki): Trigger mechanism for activation of BIPs 34, 65, and 66 has been simplified to block height checks since **v0.14.0** ([PR #8391](https://github.com/bitcoin/bitcoin/pull/8391)). * [`BIP 111`](https://github.com/bitcoin/bips/blob/master/bip-0111.mediawiki): `NODE_BLOOM` service bit added, and enforced for all peer versions as of **v0.13.0** ([PR #6579](https://github.com/bitcoin/bitcoin/pull/6579) and [PR #6641](https://github.com/bitcoin/bitcoin/pull/6641)). * [`BIP 112`](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki): The CHECKSEQUENCEVERIFY opcode has been implemented since **v0.12.1** ([PR #7524](https://github.com/bitcoin/bitcoin/pull/7524)), and has been *buried* since **v0.19.0** ([PR #16060](https://github.com/bitcoin/bitcoin/pull/16060)). diff --git a/doc/descriptors.md b/doc/descriptors.md index de92e3dbcf..1baf652f30 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -266,6 +266,6 @@ ones. For larger numbers of errors, or other types of errors, there is a roughly 1 in a trillion chance of not detecting the errors. All RPCs in Bitcoin Core will include the checksum in their output. Only -certain RPCs require checksums on input, including `deriveaddress` and +certain RPCs require checksums on input, including `deriveaddresses` and `importmulti`. The checksum for a descriptor without one can be computed using the `getdescriptorinfo` RPC. diff --git a/doc/release-notes-25730.md b/doc/release-notes-25730.md new file mode 100644 index 0000000000..33393cf314 --- /dev/null +++ b/doc/release-notes-25730.md @@ -0,0 +1,6 @@ +RPC Wallet +---------- + +- RPC `listunspent` now has a new argument `include_immature_coinbase` + to include coinbase UTXOs that don't meet the minimum spendability + depth requirement (which before were silently skipped). (#25730)
\ No newline at end of file diff --git a/doc/release-notes-empty-template.md b/doc/release-notes-empty-template.md index 8462714898..4cd2314308 100644 --- a/doc/release-notes-empty-template.md +++ b/doc/release-notes-empty-template.md @@ -25,7 +25,7 @@ How to Upgrade If you are running an older version, shut it down. Wait until it has completely shut down (which might take a few minutes in some cases), then run the -installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on Mac) +installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on macOS) or `bitcoind`/`bitcoin-qt` (on Linux). Upgrading directly from a version of Bitcoin Core that has reached its EOL is diff --git a/doc/release-notes/release-notes-24.0.md b/doc/release-notes/release-notes-24.0.md new file mode 100644 index 0000000000..a9240567d7 --- /dev/null +++ b/doc/release-notes/release-notes-24.0.md @@ -0,0 +1,388 @@ +24.0 Release Notes +================== + +Bitcoin Core version 24.0 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-24.0/> + +This release includes new features, various bug fixes and performance +improvements, as well as updated translations. + +Please report bugs using the issue tracker at GitHub: + + <https://github.com/bitcoin/bitcoin/issues> + +To receive security and update notifications, please subscribe to: + + <https://bitcoincore.org/en/list/announcements/join/> + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes in some cases), then run the +installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on macOS) +or `bitcoind`/`bitcoin-qt` (on Linux). + +Upgrading directly from a version of Bitcoin Core that has reached its EOL is +possible, but it might take some time if the data directory needs to be migrated. Old +wallet versions of Bitcoin Core are generally supported. + +Compatibility +============== + +Bitcoin Core is supported and extensively tested on operating systems +using the Linux kernel, macOS 10.15+, and Windows 7 and newer. Bitcoin +Core should also work on most other Unix-like systems but is not as +frequently tested on them. It is not recommended to use Bitcoin Core on +unsupported systems. + +Notice of new option for transaction replacement policies +========================================================= + +This version of Bitcoin Core adds a new `mempoolfullrbf` configuration +option which allows users to change the policy their individual node +will use for relaying and mining unconfirmed transactions. The option +defaults to the same policy that was used in previous releases and no +changes to node policy will occur if everyone uses the default. + +Some Bitcoin services today expect that the first version of an +unconfirmed transaction that they see will be the version of the +transaction that ultimately gets confirmed---a transaction acceptance +policy sometimes called "first-seen". + +The Bitcoin Protocol does not, and cannot, provide any assurance that +the first version of an unconfirmed transaction seen by a particular +node will be the version that gets confirmed. If there are multiple +versions of the same unconfirmed transaction available, only the miner +who includes one of those transactions in a block gets to decide which +version of the transaction gets confirmed. + +Despite this lack of assurance, multiple merchants and services today +still make this assumption. + +There are several benefits to users from removing this *first-seen* +simplification. One key benefit, the ability for the sender of a +transaction to replace it with an alternative version paying higher +fees, was realized in [Bitcoin Core 0.12.0][] (February 2016) with the +introduction of [BIP125][] opt-in Replace By Fee (RBF). + +Since then, there has been discussion about completely removing the +first-seen simplification and allowing users to replace any of their +older unconfirmed transactions with newer transactions, a feature called +*full-RBF*. This release includes a `mempoolfullrbf` configuration +option that allows enabling full-RBF, although it defaults to off +(allowing only opt-in RBF). + +Several alternative node implementations have already enabled full-RBF by +default for years, and several contributors to Bitcoin Core are +advocating for enabling full-RBF by default in a future version of +Bitcoin Core. + +As more nodes that participate in relay and mining begin enabling +full-RBF, replacement of unconfirmed transactions by ones offering higher +fees may rapidly become more reliable. + +Contributors to this project strongly recommend that merchants and services +not accept unconfirmed transactions as final, and if they insist on doing so, +to take the appropriate steps to ensure they have some recourse or plan for +when their assumptions do not hold. + +[Bitcoin Core 0.12.0]: https://bitcoincore.org/en/releases/0.12.0/#opt-in-replace-by-fee-transactions +[bip125]: https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki + +Notable changes +=============== + +P2P and network changes +----------------------- + +- To address a potential denial-of-service, the logic to download headers from peers + has been reworked. This is particularly relevant for nodes starting up for the + first time (or for nodes which are starting up after being offline for a long time). + + Whenever headers are received from a peer that have a total chainwork that is either + less than the node's `-minimumchainwork` value or is sufficiently below the work at + the node's tip, a "presync" phase will begin, in which the node will download the + peer's headers and verify the cumulative work on the peer's chain, prior to storing + those headers permanently. Once that cumulative work is verified to be sufficiently high, + the headers will be redownloaded from that peer and fully validated and stored. + + This may result in initial headers sync taking longer for new nodes starting up for + the first time, both because the headers will be downloaded twice, and because the effect + of a peer disconnecting during the presync phase (or while the node's best headers chain has less + than `-minimumchainwork`), will result in the node needing to use the headers presync mechanism + with the next peer as well (downloading the headers twice, again). (#25717) + +- With I2P connections, a new, transient address is used for each outbound + connection if `-i2pacceptincoming=0`. (#25355) + +Updated RPCs +------------ + +- The `-deprecatedrpc=softforks` configuration option has been removed. The + RPC `getblockchaininfo` no longer returns the `softforks` field, which was + previously deprecated in 23.0. (#23508) Information on soft fork status is + now only available via the `getdeploymentinfo` RPC. + +- The `deprecatedrpc=exclude_coinbase` configuration option has been removed. + The `receivedby` RPCs (`listreceivedbyaddress`, `listreceivedbylabel`, + `getreceivedbyaddress` and `getreceivedbylabel`) now always return results + accounting for received coins from coinbase outputs, without an option to + change that behaviour. Excluding coinbases was previously deprecated in 23.0. + (#25171) + +- The `deprecatedrpc=fees` configuration option has been removed. The top-level + fee fields `fee`, `modifiedfee`, `ancestorfees` and `descendantfees` are no + longer returned by RPCs `getmempoolentry`, `getrawmempool(verbose=true)`, + `getmempoolancestors(verbose=true)` and `getmempooldescendants(verbose=true)`. + The same fee fields can be accessed through the `fees` object in the result. + The top-level fee fields were previously deprecated in 23.0. (#25204) + +- The `getpeerinfo` RPC has been updated with a new `presynced_headers` field, + indicating the progress on the presync phase mentioned in the + "P2P and network changes" section above. + +Changes to wallet related RPCs can be found in the Wallet section below. + +New RPCs +-------- + +- The `sendall` RPC spends specific UTXOs to one or more recipients + without creating change. By default, the `sendall` RPC will spend + every UTXO in the wallet. `sendall` is useful to empty wallets or to + create a changeless payment from select UTXOs. When creating a payment + from a specific amount for which the recipient incurs the transaction + fee, continue to use the `subtractfeefromamount` option via the + `send`, `sendtoaddress`, or `sendmany` RPCs. (#24118) + +- A new `gettxspendingprevout` RPC has been added, which scans the mempool to find + transactions spending any of the given outpoints. (#24408) + +- The `simulaterawtransaction` RPC iterates over the inputs and outputs of the given + transactions, and tallies up the balance change for the given wallet. This can be + useful e.g. when verifying that a coin join like transaction doesn't contain unexpected + inputs that the wallet will then sign for unintentionally. (#22751) + +Updated REST APIs +----------------- + +- The `/headers/` and `/blockfilterheaders/` endpoints have been updated to use + a query parameter instead of path parameter to specify the result count. The + count parameter is now optional, and defaults to 5 for both endpoints. The old + endpoints are still functional, and have no documented behaviour change. + + For `/headers`, use + `GET /rest/headers/<BLOCK-HASH>.<bin|hex|json>?count=<COUNT=5>` + instead of + `GET /rest/headers/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` (deprecated) + + For `/blockfilterheaders/`, use + `GET /rest/blockfilterheaders/<FILTERTYPE>/<BLOCK-HASH>.<bin|hex|json>?count=<COUNT=5>` + instead of + `GET /rest/blockfilterheaders/<FILTERTYPE>/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` (deprecated) + + (#24098) + +Build System +------------ + +- Guix builds are now reproducible across architectures (x86_64 & aarch64). (#21194) + +New settings +------------ + +- A new `mempoolfullrbf` option has been added, which enables the mempool to + accept transaction replacement without enforcing BIP125 replaceability + signaling. (#25353) + +Wallet +------ + +- The `-walletrbf` startup option will now default to `true`. The + wallet will now default to opt-in RBF on transactions that it creates. (#25610) + +- The `replaceable` option for the `createrawtransaction` and + `createpsbt` RPCs will now default to `true`. Transactions created + with these RPCs will default to having opt-in RBF enabled. (#25610) + +- The `wsh()` output descriptor was extended with Miniscript support. You can import Miniscript + descriptors for P2WSH in a watchonly wallet to track coins, but you can't spend from them using + the Bitcoin Core wallet yet. + You can find more about Miniscript on the [reference website](https://bitcoin.sipa.be/miniscript/). (#24148) + +- The `tr()` output descriptor now supports multisig scripts through the `multi_a()` and + `sortedmulti_a()` functions. (#24043) + +- To help prevent fingerprinting transactions created by the Bitcoin Core wallet, change output + amounts are now randomized. (#24494) + +- The `listtransactions`, `gettransaction`, and `listsinceblock` + RPC methods now include a wtxid field (hash of serialized transaction, + including witness data) for each transaction. (#24198) + +- The `listsinceblock`, `listtransactions` and `gettransaction` output now contain a new + `parent_descs` field for every "receive" entry. (#25504) + +- A new optional `include_change` parameter was added to the `listsinceblock` command. + +- RPC `getreceivedbylabel` now returns an error, "Label not found + in wallet" (-4), if the label is not in the address book. (#25122) + +Migrating Legacy Wallets to Descriptor Wallets +--------------------------------------------- + +An experimental RPC `migratewallet` has been added to migrate Legacy (non-descriptor) wallets to +Descriptor wallets. More information about the migration process is available in the +[documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/managing-wallets.md#migrating-legacy-wallets-to-descriptor-wallets). + +GUI changes +----------- + +- A new menu item to restore a wallet from a backup file has been added (gui#471). + +- Configuration changes made in the bitcoin GUI (such as the pruning setting, +proxy settings, UPNP preferences) are now saved to `<datadir>/settings.json` +file rather than to the Qt settings backend (windows registry or unix desktop +config files), so these settings will now apply to bitcoind, instead of being +ignored. (#15936, gui#602) + +- Also, the interaction between GUI settings and `bitcoin.conf` settings is +simplified. Settings from `bitcoin.conf` are now displayed normally in the GUI +settings dialog, instead of in a separate warning message ("Options set in this +dialog are overridden by the configuration file: -setting=value"). And these +settings can now be edited because `settings.json` values take precedence over +`bitcoin.conf` values. (#15936) + +Low-level changes +================= + +RPC +--- + +- The `deriveaddresses`, `getdescriptorinfo`, `importdescriptors` and `scantxoutset` commands now + accept Miniscript expression within a `wsh()` descriptor. (#24148) + +- The `getaddressinfo`, `decodescript`, `listdescriptors` and `listunspent` commands may now output + a Miniscript descriptor inside a `wsh()` where a `wsh(raw())` descriptor was previously returned. (#24148) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- /dev/fd0 +- 0xb10c +- Adam Jonas +- akankshakashyap +- Ali Sherief +- amadeuszpawlik +- Andreas Kouloumos +- Andrew Chow +- Anthony Towns +- Antoine Poinsot +- Antoine Riard +- Aurèle Oulès +- avirgovi +- Ayush Sharma +- Baas +- Ben Woosley +- BrokenProgrammer +- brunoerg +- brydinh +- Bushstar +- Calvin Kim +- CAnon +- Carl Dong +- chinggg +- Cory Fields +- Daniel Kraft +- Daniela Brozzoni +- darosior +- Dave Scotese +- David Bakin +- dergoegge +- dhruv +- Dimitri +- dontbyte +- Duncan Dean +- eugene +- Eunoia +- Fabian Jahr +- furszy +- Gleb Naumenko +- glozow +- Greg Weber +- Gregory Sanders +- gruve-p +- Hennadii Stepanov +- hiago +- Igor Bubelov +- ishaanam +- Jacob P. +- Jadi +- James O'Beirne +- Janna +- Jarol Rodriguez +- Jeremy Rand +- Jeremy Rubin +- jessebarton +- João Barbosa +- John Newbery +- Jon Atack +- Josiah Baker +- Karl-Johan Alm +- KevinMusgrave +- Kiminuo +- klementtan +- Kolby Moroz +- kouloumos +- Kristaps Kaupe +- Larry Ruane +- Luke Dashjr +- MarcoFalke +- Marnix +- Martin Leitner-Ankerl +- Martin Zumsande +- Michael Dietz +- Michael Folkson +- Michael Ford +- Murch +- mutatrum +- muxator +- Oskar Mendel +- Pablo Greco +- pasta +- Patrick Strateman +- Pavol Rusnak +- Peter Bushnell +- phyBrackets +- Pieter Wuille +- practicalswift +- randymcmillan +- Robert Spigler +- Russell Yanofsky +- S3RK +- Samer Afach +- Sebastian Falbesoner +- Seibart Nedor +- Shashwat +- Sjors Provoost +- Smlep +- sogoagain +- Stacie +- Stéphan Vuylsteke +- Suhail Saqan +- Suhas Daftuar +- t-bast +- TakeshiMusgrave +- Vasil Dimov +- W. J. van der Laan +- w0xlt +- whiteh0rse +- willcl-ark +- William Casarin +- Yancy Ribbens + +As well as to everyone that helped with translations on +[Transifex](https://www.transifex.com/bitcoin/bitcoin/). diff --git a/doc/tracing.md b/doc/tracing.md index b6e3b9263a..6e60901496 100644 --- a/doc/tracing.md +++ b/doc/tracing.md @@ -76,7 +76,7 @@ the passed message. #### Tracepoint `net:outbound_message` -Is called when a message is send to a peer over the P2P network. Passes +Is called when a message is sent to a peer over the P2P network. Passes information about our peer, the connection and the message as arguments. Arguments passed: @@ -116,7 +116,7 @@ added to and removed (spent) from the cache when we connect a new block. (`chainstate.CoinsTip()`). For example, the RPCs `generateblock` and `getblocktemplate` call `TestBlockValidity()`, which applies the UTXO set changes to a temporary cache. Similarly, mempool consistency checks, which are -frequent on regtest, also apply the the UTXO set changes to a temporary cache. +frequent on regtest, also apply the UTXO set changes to a temporary cache. Changes to the _main_ UTXO cache and to temporary caches trigger the tracepoints. We can't tell if a temporary cache or the _main_ cache was changed. @@ -253,8 +253,8 @@ TRACE6(net, inbound_message, ### Guidelines and best practices -#### Clear motivation and use-case -Tracepoints need a clear motivation and use-case. The motivation should +#### Clear motivation and use case +Tracepoints need a clear motivation and use case. The motivation should outweigh the impact on, for example, code readability. There is no point in adding tracepoints that don't end up being used. diff --git a/src/Makefile.am b/src/Makefile.am index 70a0ca8915..2e2da54b2d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -134,6 +134,7 @@ BITCOIN_CORE_H = \ coins.h \ common/bloom.h \ common/run_command.h \ + common/url.h \ compat/assumptions.h \ compat/byteswap.h \ compat/compat.h \ @@ -198,6 +199,7 @@ BITCOIN_CORE_H = \ node/blockstorage.h \ node/caches.h \ node/chainstate.h \ + node/chainstatemanager_args.h \ node/coin.h \ node/connection_types.h \ node/context.h \ @@ -256,12 +258,12 @@ BITCOIN_CORE_H = \ support/events.h \ support/lockedpool.h \ sync.h \ - threadinterrupt.h \ threadsafety.h \ timedata.h \ torcontrol.h \ txdb.h \ txmempool.h \ + txmempool_entry.h \ txorphanage.h \ txrequest.h \ undo.h \ @@ -295,6 +297,7 @@ BITCOIN_CORE_H = \ util/syserror.h \ util/system.h \ util/thread.h \ + util/threadinterrupt.h \ util/threadnames.h \ util/time.h \ util/tokenpipe.h \ @@ -302,7 +305,6 @@ BITCOIN_CORE_H = \ util/translation.h \ util/types.h \ util/ui_change_type.h \ - util/url.h \ util/vector.h \ validation.h \ validationinterface.h \ @@ -381,6 +383,7 @@ libbitcoin_node_a_SOURCES = \ node/blockstorage.cpp \ node/caches.cpp \ node/chainstate.cpp \ + node/chainstatemanager_args.cpp \ node/coin.cpp \ node/connection_types.cpp \ node/context.cpp \ @@ -660,6 +663,11 @@ libbitcoin_common_a_SOURCES = \ script/standard.cpp \ warnings.cpp \ $(BITCOIN_CORE_H) + +if USE_LIBEVENT +libbitcoin_common_a_CPPFLAGS += $(EVENT_CFLAGS) +libbitcoin_common_a_SOURCES += common/url.cpp +endif # # util # @@ -679,7 +687,6 @@ libbitcoin_util_a_SOURCES = \ rpc/request.cpp \ support/cleanse.cpp \ sync.cpp \ - threadinterrupt.cpp \ util/asmap.cpp \ util/bip32.cpp \ util/bytevectorhash.cpp \ @@ -697,6 +704,7 @@ libbitcoin_util_a_SOURCES = \ util/readwritefile.cpp \ util/settings.cpp \ util/thread.cpp \ + util/threadinterrupt.cpp \ util/threadnames.cpp \ util/serfloat.cpp \ util/spanparsing.cpp \ @@ -706,10 +714,6 @@ libbitcoin_util_a_SOURCES = \ util/time.cpp \ util/tokenpipe.cpp \ $(BITCOIN_CORE_H) - -if USE_LIBEVENT -libbitcoin_util_a_SOURCES += util/url.cpp -endif # # cli # @@ -773,6 +777,7 @@ endif bitcoin_cli_LDADD = \ $(LIBBITCOIN_CLI) \ $(LIBUNIVALUE) \ + $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CRYPTO) @@ -933,7 +938,6 @@ libbitcoinkernel_la_SOURCES = \ support/cleanse.cpp \ support/lockedpool.cpp \ sync.cpp \ - threadinterrupt.cpp \ txdb.cpp \ txmempool.cpp \ uint256.cpp \ diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index e1e2066877..f1e4e706a1 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -32,6 +32,7 @@ bench_bench_bitcoin_SOURCES = \ bench/examples.cpp \ bench/gcs_filter.cpp \ bench/hashpadding.cpp \ + bench/load_external.cpp \ bench/lockedpool.cpp \ bench/logging.cpp \ bench/mempool_eviction.cpp \ @@ -79,6 +80,7 @@ if ENABLE_WALLET bench_bench_bitcoin_SOURCES += bench/coin_selection.cpp bench_bench_bitcoin_SOURCES += bench/wallet_balance.cpp bench_bench_bitcoin_SOURCES += bench/wallet_loading.cpp +bench_bench_bitcoin_SOURCES += bench/wallet_create_tx.cpp bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(SQLITE_LIBS) endif diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 571a85e5c9..9a9424e84c 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -66,6 +66,7 @@ BITCOIN_TESTS =\ test/addrman_tests.cpp \ test/allocator_tests.cpp \ test/amount_tests.cpp \ + test/argsman_tests.cpp \ test/arith_uint256_tests.cpp \ test/banman_tests.cpp \ test/base32_tests.cpp \ diff --git a/src/Makefile.test_fuzz.include b/src/Makefile.test_fuzz.include index b35d713d57..aa9c052750 100644 --- a/src/Makefile.test_fuzz.include +++ b/src/Makefile.test_fuzz.include @@ -11,7 +11,8 @@ TEST_FUZZ_H = \ test/fuzz/fuzz.h \ test/fuzz/FuzzedDataProvider.h \ test/fuzz/util.h \ - test/fuzz/util/mempool.h + test/fuzz/util/mempool.h \ + test/fuzz/util/net.h libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libtest_fuzz_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) @@ -19,4 +20,5 @@ libtest_fuzz_a_SOURCES = \ test/fuzz/fuzz.cpp \ test/fuzz/util.cpp \ test/fuzz/util/mempool.cpp \ + test/fuzz/util/net.cpp \ $(TEST_FUZZ_H) diff --git a/src/bench/load_external.cpp b/src/bench/load_external.cpp new file mode 100644 index 0000000000..be01b2a483 --- /dev/null +++ b/src/bench/load_external.cpp @@ -0,0 +1,63 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include <bench/bench.h> +#include <bench/data.h> +#include <chainparams.h> +#include <test/util/setup_common.h> +#include <validation.h> + +/** + * The LoadExternalBlockFile() function is used during -reindex and -loadblock. + * + * Create a test file that's similar to a datadir/blocks/blk?????.dat file, + * It contains around 134 copies of the same block (typical size of real block files). + * For each block in the file, LoadExternalBlockFile() won't find its parent, + * and so will skip the block. (In the real system, it will re-read the block + * from disk later when it encounters its parent.) + * + * This benchmark measures the performance of deserializing the block (or just + * its header, beginning with PR 16981). + */ +static void LoadExternalBlockFile(benchmark::Bench& bench) +{ + const auto testing_setup{MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN)}; + + // Create a single block as in the blocks files (magic bytes, block size, + // block data) as a stream object. + const fs::path blkfile{testing_setup.get()->m_path_root / "blk.dat"}; + CDataStream ss(SER_DISK, 0); + auto params{testing_setup->m_node.chainman->GetParams()}; + ss << params.MessageStart(); + ss << static_cast<uint32_t>(benchmark::data::block413567.size()); + // We can't use the streaming serialization (ss << benchmark::data::block413567) + // because that first writes a compact size. + ss.write(MakeByteSpan(benchmark::data::block413567)); + + // Create the test file. + { + // "wb+" is "binary, O_RDWR | O_CREAT | O_TRUNC". + FILE* file{fsbridge::fopen(blkfile, "wb+")}; + // Make the test block file about 128 MB in length. + for (size_t i = 0; i < node::MAX_BLOCKFILE_SIZE / ss.size(); ++i) { + if (fwrite(ss.data(), 1, ss.size(), file) != ss.size()) { + throw std::runtime_error("write to test file failed\n"); + } + } + fclose(file); + } + + Chainstate& chainstate{testing_setup->m_node.chainman->ActiveChainstate()}; + std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent; + FlatFilePos pos; + bench.run([&] { + // "rb" is "binary, O_RDONLY", positioned to the start of the file. + // The file will be closed by LoadExternalBlockFile(). + FILE* file{fsbridge::fopen(blkfile, "rb")}; + chainstate.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent); + }); + fs::remove(blkfile); +} + +BENCHMARK(LoadExternalBlockFile, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp index 878e375a7c..29feb42528 100644 --- a/src/bench/mempool_eviction.cpp +++ b/src/bench/mempool_eviction.cpp @@ -6,6 +6,7 @@ #include <policy/policy.h> #include <test/util/setup_common.h> #include <txmempool.h> +#include <txmempool_entry.h> static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp index 9f5b28dca7..1e40d78aa5 100644 --- a/src/bench/mempool_stress.cpp +++ b/src/bench/mempool_stress.cpp @@ -6,6 +6,7 @@ #include <policy/policy.h> #include <test/util/setup_common.h> #include <txmempool.h> +#include <txmempool_entry.h> #include <validation.h> #include <vector> diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp index 4fdc31ae05..5ae4829d16 100644 --- a/src/bench/rpc_mempool.cpp +++ b/src/bench/rpc_mempool.cpp @@ -7,6 +7,7 @@ #include <rpc/mempool.h> #include <test/util/setup_common.h> #include <txmempool.h> +#include <txmempool_entry.h> #include <univalue.h> diff --git a/src/bench/wallet_create_tx.cpp b/src/bench/wallet_create_tx.cpp new file mode 100644 index 0000000000..8f5c50872b --- /dev/null +++ b/src/bench/wallet_create_tx.cpp @@ -0,0 +1,143 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include <bench/bench.h> +#include <chainparams.h> +#include <wallet/coincontrol.h> +#include <consensus/merkle.h> +#include <kernel/chain.h> +#include <node/context.h> +#include <test/util/setup_common.h> +#include <test/util/wallet.h> +#include <validation.h> +#include <wallet/spend.h> +#include <wallet/wallet.h> + +using wallet::CWallet; +using wallet::CreateMockWalletDatabase; +using wallet::DBErrors; +using wallet::WALLET_FLAG_DESCRIPTORS; + +struct TipBlock +{ + uint256 prev_block_hash; + int64_t prev_block_time; + int tip_height; +}; + +TipBlock getTip(const CChainParams& params, const node::NodeContext& context) +{ + auto tip = WITH_LOCK(::cs_main, return context.chainman->ActiveTip()); + return (tip) ? TipBlock{tip->GetBlockHash(), tip->GetBlockTime(), tip->nHeight} : + TipBlock{params.GenesisBlock().GetHash(), params.GenesisBlock().GetBlockTime(), 0}; +} + +void generateFakeBlock(const CChainParams& params, + const node::NodeContext& context, + CWallet& wallet, + const CScript& coinbase_out_script) +{ + TipBlock tip{getTip(params, context)}; + + // Create block + CBlock block; + CMutableTransaction coinbase_tx; + coinbase_tx.vin.resize(1); + coinbase_tx.vin[0].prevout.SetNull(); + coinbase_tx.vout.resize(2); + coinbase_tx.vout[0].scriptPubKey = coinbase_out_script; + coinbase_tx.vout[0].nValue = 49 * COIN; + coinbase_tx.vin[0].scriptSig = CScript() << ++tip.tip_height << OP_0; + coinbase_tx.vout[1].scriptPubKey = coinbase_out_script; // extra output + coinbase_tx.vout[1].nValue = 1 * COIN; + block.vtx = {MakeTransactionRef(std::move(coinbase_tx))}; + + block.nVersion = VERSIONBITS_LAST_OLD_BLOCK_VERSION; + block.hashPrevBlock = tip.prev_block_hash; + block.hashMerkleRoot = BlockMerkleRoot(block); + block.nTime = ++tip.prev_block_time; + block.nBits = params.GenesisBlock().nBits; + block.nNonce = 0; + + { + LOCK(::cs_main); + // Add it to the index + CBlockIndex* pindex{context.chainman->m_blockman.AddToBlockIndex(block, context.chainman->m_best_header)}; + // add it to the chain + context.chainman->ActiveChain().SetTip(*pindex); + } + + // notify wallet + const auto& pindex = WITH_LOCK(::cs_main, return context.chainman->ActiveChain().Tip()); + wallet.blockConnected(kernel::MakeBlockInfo(pindex, &block)); +} + +struct PreSelectInputs { + // How many coins from the wallet the process should select + int num_of_internal_inputs; + // future: this could have external inputs as well. +}; + +static void WalletCreateTx(benchmark::Bench& bench, const OutputType output_type, bool allow_other_inputs, std::optional<PreSelectInputs> preset_inputs) +{ + const auto test_setup = MakeNoLogFileContext<const TestingSetup>(); + + CWallet wallet{test_setup->m_node.chain.get(), "", gArgs, CreateMockWalletDatabase()}; + { + LOCK(wallet.cs_wallet); + wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + wallet.SetupDescriptorScriptPubKeyMans(); + if (wallet.LoadWallet() != DBErrors::LOAD_OK) assert(false); + } + + // Generate destinations + CScript dest = GetScriptForDestination(getNewDestination(wallet, output_type)); + + // Generate chain; each coinbase will have two outputs to fill-up the wallet + const auto& params = Params(); + unsigned int chain_size = 5000; // 5k blocks means 10k UTXO for the wallet (minus 200 due COINBASE_MATURITY) + for (unsigned int i = 0; i < chain_size; ++i) { + generateFakeBlock(params, test_setup->m_node, wallet, dest); + } + + // Check available balance + auto bal = wallet::GetAvailableBalance(wallet); // Cache + assert(bal == 50 * COIN * (chain_size - COINBASE_MATURITY)); + + wallet::CCoinControl coin_control; + coin_control.m_allow_other_inputs = allow_other_inputs; + + CAmount target = 0; + if (preset_inputs) { + // Select inputs, each has 49 BTC + wallet::CoinFilterParams filter_coins; + filter_coins.max_count = preset_inputs->num_of_internal_inputs; + const auto& res = WITH_LOCK(wallet.cs_wallet, + return wallet::AvailableCoins(wallet, /*coinControl=*/nullptr, /*feerate=*/std::nullopt, filter_coins)); + for (int i=0; i < preset_inputs->num_of_internal_inputs; i++) { + const auto& coin{res.coins.at(output_type)[i]}; + target += coin.txout.nValue; + coin_control.Select(coin.outpoint); + } + } + + // If automatic coin selection is enabled, add the value of another UTXO to the target + if (coin_control.m_allow_other_inputs) target += 50 * COIN; + std::vector<wallet::CRecipient> recipients = {{dest, target, true}}; + + bench.epochIterations(5).run([&] { + LOCK(wallet.cs_wallet); + const auto& tx_res = CreateTransaction(wallet, recipients, -1, coin_control); + assert(tx_res); + }); +} + +static void WalletCreateTxUseOnlyPresetInputs(benchmark::Bench& bench) { WalletCreateTx(bench, OutputType::BECH32, /*allow_other_inputs=*/false, + {{/*num_of_internal_inputs=*/4}}); } + +static void WalletCreateTxUsePresetInputsAndCoinSelection(benchmark::Bench& bench) { WalletCreateTx(bench, OutputType::BECH32, /*allow_other_inputs=*/true, + {{/*num_of_internal_inputs=*/4}}); } + +BENCHMARK(WalletCreateTxUseOnlyPresetInputs, benchmark::PriorityLevel::LOW) +BENCHMARK(WalletCreateTxUsePresetInputsAndCoinSelection, benchmark::PriorityLevel::LOW) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 6d77385584..55fa3116ac 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -9,6 +9,7 @@ #include <chainparamsbase.h> #include <clientversion.h> +#include <common/url.h> #include <compat/compat.h> #include <compat/stdin.h> #include <policy/feerate.h> @@ -21,7 +22,6 @@ #include <util/strencodings.h> #include <util/system.h> #include <util/translation.h> -#include <util/url.h> #include <algorithm> #include <chrono> @@ -82,7 +82,7 @@ static void SetupCliArgs(ArgsManager& argsman) DEFAULT_NBLOCKS, DEFAULT_MAX_TRIES), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-addrinfo", "Get the number of addresses known to the node, per network and total, after filtering for quality and recency. The total number of addresses known to the node may be higher.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-getinfo", "Get general information from the remote server. Note that unlike server-side RPC calls, the results of -getinfo is the result of multiple non-atomic requests. Some entries in the result may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-getinfo", "Get general information from the remote server. Note that unlike server-side RPC calls, the output of -getinfo is the result of multiple non-atomic requests. Some entries in the output may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-netinfo", "Get network peer connection information from the remote server. An optional integer argument from 0 to 4 can be passed for different peers listings (default: 0). Pass \"help\" for detailed help documentation.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); SetupChainParamsBaseOptions(argsman); diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index d556300ee2..78c1a2060c 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -9,6 +9,7 @@ #include <chainparams.h> #include <chainparamsbase.h> #include <clientversion.h> +#include <common/url.h> #include <compat/compat.h> #include <interfaces/init.h> #include <key.h> @@ -17,7 +18,6 @@ #include <tinyformat.h> #include <util/system.h> #include <util/translation.h> -#include <util/url.h> #include <wallet/wallettool.h> #include <exception> diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 9f81640ddb..d8d4e34e47 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -9,6 +9,7 @@ #include <chainparams.h> #include <clientversion.h> +#include <common/url.h> #include <compat/compat.h> #include <init.h> #include <interfaces/chain.h> @@ -25,7 +26,6 @@ #include <util/threadnames.h> #include <util/tokenpipe.h> #include <util/translation.h> -#include <util/url.h> #include <any> #include <functional> diff --git a/src/checkqueue.h b/src/checkqueue.h index bead6f0c6f..c4a64444e9 100644 --- a/src/checkqueue.h +++ b/src/checkqueue.h @@ -199,11 +199,12 @@ public: WITH_LOCK(m_mutex, m_request_stop = false); } + bool HasThreads() const { return !m_worker_threads.empty(); } + ~CCheckQueue() { assert(m_worker_threads.empty()); } - }; /** diff --git a/src/util/url.cpp b/src/common/url.cpp index ea9323e666..5200d55096 100644 --- a/src/util/url.cpp +++ b/src/common/url.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <util/url.h> +#include <common/url.h> #include <event2/http.h> diff --git a/src/util/url.h b/src/common/url.h index 5a7b11fa04..7bbd8b60de 100644 --- a/src/util/url.h +++ b/src/common/url.h @@ -2,8 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_UTIL_URL_H -#define BITCOIN_UTIL_URL_H +#ifndef BITCOIN_COMMON_URL_H +#define BITCOIN_COMMON_URL_H #include <string> @@ -11,4 +11,4 @@ using UrlDecodeFn = std::string(const std::string& url_encoded); UrlDecodeFn urlDecode; extern UrlDecodeFn* const URL_DECODE; -#endif // BITCOIN_UTIL_URL_H +#endif // BITCOIN_COMMON_URL_H diff --git a/src/crypto/sha512.cpp b/src/crypto/sha512.cpp index 85a7bbcb53..59b79609dd 100644 --- a/src/crypto/sha512.cpp +++ b/src/crypto/sha512.cpp @@ -30,7 +30,7 @@ void inline Round(uint64_t a, uint64_t b, uint64_t c, uint64_t& d, uint64_t e, u h = t1 + t2; } -/** Initialize SHA-256 state. */ +/** Initialize SHA-512 state. */ void inline Initialize(uint64_t* s) { s[0] = 0x6a09e667f3bcc908ull; diff --git a/src/external_signer.cpp b/src/external_signer.cpp index 0e582629f7..f255834830 100644 --- a/src/external_signer.cpp +++ b/src/external_signer.cpp @@ -81,6 +81,9 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str for (const auto& entry : input.hd_keypaths) { if (parsed_m_fingerprint == MakeUCharSpan(entry.second.fingerprint)) return true; } + for (const auto& entry : input.m_tap_bip32_paths) { + if (parsed_m_fingerprint == MakeUCharSpan(entry.second.second.fingerprint)) return true; + } return false; }; diff --git a/src/i2p.cpp b/src/i2p.cpp index 28be8009dc..d54486ecff 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -18,6 +18,7 @@ #include <util/spanparsing.h> #include <util/strencodings.h> #include <util/system.h> +#include <util/threadinterrupt.h> #include <chrono> #include <memory> @@ -9,8 +9,8 @@ #include <fs.h> #include <netaddress.h> #include <sync.h> -#include <threadinterrupt.h> #include <util/sock.h> +#include <util/threadinterrupt.h> #include <memory> #include <optional> diff --git a/src/index/base.h b/src/index/base.h index 349178a535..54c59f7557 100644 --- a/src/index/base.h +++ b/src/index/base.h @@ -7,7 +7,7 @@ #include <dbwrapper.h> #include <interfaces/chain.h> -#include <threadinterrupt.h> +#include <util/threadinterrupt.h> #include <validationinterface.h> #include <string> diff --git a/src/init.cpp b/src/init.cpp index cf4102e6a7..6f44ed6c3d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -40,6 +40,7 @@ #include <node/blockstorage.h> #include <node/caches.h> #include <node/chainstate.h> +#include <node/chainstatemanager_args.h> #include <node/context.h> #include <node/interface_ui.h> #include <node/mempool_args.h> @@ -554,7 +555,10 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-capturemessages", "Capture all P2P messages to disk", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_BYTES >> 20), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - argsman.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-maxtipage=<n>", + strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", + Ticks<std::chrono::seconds>(DEFAULT_MAX_TIP_AGE)), + ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-printpriority", strprintf("Log transaction fee rate in " + CURRENCY_UNIT + "/kvB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-uacomment=<cmt>", "Append comment to the user agent string", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); @@ -930,21 +934,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb init::SetLoggingCategories(args); init::SetLoggingLevel(args); - fCheckBlockIndex = args.GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks()); - fCheckpointsEnabled = args.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED); - - hashAssumeValid = uint256S(args.GetArg("-assumevalid", chainparams.GetConsensus().defaultAssumeValid.GetHex())); - - if (args.IsArgSet("-minimumchainwork")) { - const std::string minChainWorkStr = args.GetArg("-minimumchainwork", ""); - if (!IsHexNumber(minChainWorkStr)) { - return InitError(strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), minChainWorkStr)); - } - nMinimumChainWork = UintToArith256(uint256S(minChainWorkStr)); - } else { - nMinimumChainWork = UintToArith256(chainparams.GetConsensus().nMinimumChainWork); - } - // block pruning; get the amount of disk space (in MiB) to allot for block & undo files int64_t nPruneArg = args.GetIntArg("-prune", 0); if (nPruneArg < 0) { @@ -995,8 +984,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb if (args.GetIntArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) > 1) return InitError(Untranslated("Unknown rpcserialversion requested.")); - nMaxTipAge = args.GetIntArg("-maxtipage", DEFAULT_MAX_TIP_AGE); - if (args.GetBoolArg("-reindex-chainstate", false)) { // indexes that must be deactivated to prevent index corruption, see #24630 if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) { @@ -1044,6 +1031,16 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb } #endif // USE_SYSCALL_SANDBOX + // Also report errors from parsing before daemonization + { + ChainstateManager::Options chainman_opts_dummy{ + .chainparams = chainparams, + }; + if (const auto error{ApplyArgsManOptions(args, chainman_opts_dummy)}) { + return InitError(*error); + } + } + return true; } @@ -1146,7 +1143,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) LogPrintf("Script verification uses %d additional threads\n", script_threads); if (script_threads >= 1) { - g_parallel_script_checks = true; StartScriptCheckWorkerThreads(script_threads); } @@ -1435,6 +1431,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) fReindex = args.GetBoolArg("-reindex", false); bool fReindexChainState = args.GetBoolArg("-reindex-chainstate", false); + ChainstateManager::Options chainman_opts{ + .chainparams = chainparams, + .adjusted_time_callback = GetAdjustedTime, + }; + Assert(!ApplyArgsManOptions(args, chainman_opts)); // no error can happen, already checked in AppInitParameterInteraction // cache size calculations CacheSizes cache_sizes = CalculateCacheSizes(args, g_enabled_filter_types.size()); @@ -1471,10 +1472,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) for (bool fLoaded = false; !fLoaded && !ShutdownRequested();) { node.mempool = std::make_unique<CTxMemPool>(mempool_opts); - const ChainstateManager::Options chainman_opts{ - .chainparams = chainparams, - .adjusted_time_callback = GetAdjustedTime, - }; node.chainman = std::make_unique<ChainstateManager>(chainman_opts); ChainstateManager& chainman = *node.chainman; @@ -1616,6 +1613,24 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return false; } + int chain_active_height = WITH_LOCK(cs_main, return chainman.ActiveChain().Height()); + + // On first startup, warn on low block storage space + if (!fReindex && !fReindexChainState && chain_active_height <= 1) { + uint64_t additional_bytes_needed = fPruneMode ? nPruneTarget + : chainparams.AssumedBlockchainSize() * 1024 * 1024 * 1024; + + if (!CheckDiskSpace(args.GetBlocksDirPath(), additional_bytes_needed)) { + InitWarning(strprintf(_( + "Disk space for %s may not accommodate the block files. " \ + "Approximately %u GB of data will be stored in this directory." + ), + fs::quoted(fs::PathToString(args.GetBlocksDirPath())), + chainparams.AssumedBlockchainSize() + )); + } + } + // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly. // No locking, as this happens before any background thread is started. boost::signals2::connection block_notify_genesis_wait_connection; @@ -1665,8 +1680,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 12: start node - int chain_active_height; - //// debug print { LOCK(cs_main); diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 5fc0e540a9..7a3d88b18f 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_INTERFACES_CHAIN_H #define BITCOIN_INTERFACES_CHAIN_H +#include <blockfilter.h> #include <primitives/transaction.h> // For CTransactionRef #include <util/settings.h> // For util::SettingsValue @@ -143,6 +144,13 @@ public: //! or one of its ancestors. virtual std::optional<int> findLocatorFork(const CBlockLocator& locator) = 0; + //! Returns whether a block filter index is available. + virtual bool hasBlockFilterIndex(BlockFilterType filter_type) = 0; + + //! Returns whether any of the elements match the block via a BIP 157 block filter + //! or std::nullopt if the block filter for this block couldn't be found. + virtual std::optional<bool> blockFilterMatchesAny(BlockFilterType filter_type, const uint256& block_hash, const GCSFilter::ElementSet& filter_set) = 0; + //! Return whether node has the block and optionally return block metadata //! or contents. virtual bool findBlock(const uint256& hash, const FoundBlock& block={}) = 0; diff --git a/src/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h index 520d0e8e75..226bb6031e 100644 --- a/src/kernel/chainstatemanager_opts.h +++ b/src/kernel/chainstatemanager_opts.h @@ -5,13 +5,19 @@ #ifndef BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H #define BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H +#include <arith_uint256.h> +#include <uint256.h> #include <util/time.h> #include <cstdint> #include <functional> +#include <optional> class CChainParams; +static constexpr bool DEFAULT_CHECKPOINTS_ENABLED{true}; +static constexpr auto DEFAULT_MAX_TIP_AGE{24h}; + namespace kernel { /** @@ -22,6 +28,14 @@ namespace kernel { struct ChainstateManagerOpts { const CChainParams& chainparams; const std::function<NodeClock::time_point()> adjusted_time_callback{nullptr}; + std::optional<bool> check_block_index{}; + bool checkpoints_enabled{DEFAULT_CHECKPOINTS_ENABLED}; + //! If set, it will override the minimum work we will assume exists on some valid chain. + std::optional<arith_uint256> minimum_chain_work{}; + //! If set, it will override the block hash whose ancestors we will assume to have valid scripts without checking them. + std::optional<uint256> assumed_valid_block{}; + //! If the tip is older than this, the node is considered to be in initial block download. + std::chrono::seconds max_tip_age{DEFAULT_MAX_TIP_AGE}; }; } // namespace kernel diff --git a/src/logging.cpp b/src/logging.cpp index c95c0b7e37..ed0c2a56a5 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -181,6 +181,7 @@ const CLogCategoryDesc LogCategories[] = {BCLog::UTIL, "util"}, {BCLog::BLOCKSTORE, "blockstorage"}, {BCLog::TXRECONCILIATION, "txreconciliation"}, + {BCLog::SCAN, "scan"}, {BCLog::ALL, "1"}, {BCLog::ALL, "all"}, }; @@ -283,6 +284,8 @@ std::string LogCategoryToStr(BCLog::LogFlags category) return "blockstorage"; case BCLog::LogFlags::TXRECONCILIATION: return "txreconciliation"; + case BCLog::LogFlags::SCAN: + return "scan"; case BCLog::LogFlags::ALL: return "all"; } diff --git a/src/logging.h b/src/logging.h index 5ee6665c76..14a0f08f8d 100644 --- a/src/logging.h +++ b/src/logging.h @@ -67,6 +67,7 @@ namespace BCLog { UTIL = (1 << 25), BLOCKSTORE = (1 << 26), TXRECONCILIATION = (1 << 27), + SCAN = (1 << 28), ALL = ~(uint32_t)0, }; enum class Level { diff --git a/src/mapport.cpp b/src/mapport.cpp index 6262e51879..975ec4da6a 100644 --- a/src/mapport.cpp +++ b/src/mapport.cpp @@ -13,10 +13,10 @@ #include <net.h> #include <netaddress.h> #include <netbase.h> -#include <threadinterrupt.h> #include <util/syscall_sandbox.h> #include <util/system.h> #include <util/thread.h> +#include <util/threadinterrupt.h> #ifdef USE_NATPMP #include <compat/compat.h> diff --git a/src/minisketch/configure.ac b/src/minisketch/configure.ac index 9dc66e7fd2..83910448a2 100644 --- a/src/minisketch/configure.ac +++ b/src/minisketch/configure.ac @@ -124,9 +124,6 @@ if test "x$use_ccache" != "xno"; then fi AC_MSG_RESULT($use_ccache) fi -if test "x$use_ccache" = "xyes"; then - AX_CHECK_COMPILE_FLAG([-Qunused-arguments],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Qunused-arguments"],,[[$CXXFLAG_WERROR]]) -fi VERIFY_DEFINES=-DMINISKETCH_VERIFY RELEASE_DEFINES= diff --git a/src/minisketch/src/bench.cpp b/src/minisketch/src/bench.cpp index f55944a448..dc44379fdb 100644 --- a/src/minisketch/src/bench.cpp +++ b/src/minisketch/src/bench.cpp @@ -62,13 +62,11 @@ int main(int argc, char** argv) { if (!states[0]) { printf(" -\t"); } else { - double total = 0.0; for (auto& state : states) { auto start = std::chrono::steady_clock::now(); minisketch_decode(state, 2 * syndromes, roots.data()); auto stop = std::chrono::steady_clock::now(); std::chrono::duration<double> dur(stop - start); - total += dur.count(); benches.push_back(dur.count()); } std::sort(benches.begin(), benches.end()); @@ -98,7 +96,6 @@ int main(int argc, char** argv) { if (!states[0]) { printf(" -\t"); } else { - double total = 0.0; for (auto& state : states) { auto start = std::chrono::steady_clock::now(); for (auto val : data) { @@ -106,7 +103,6 @@ int main(int argc, char** argv) { } auto stop = std::chrono::steady_clock::now(); std::chrono::duration<double> dur(stop - start); - total += dur.count(); benches.push_back(dur.count()); } std::sort(benches.begin(), benches.end()); diff --git a/src/minisketch/src/int_utils.h b/src/minisketch/src/int_utils.h index 62b2c38a29..d21ba56f33 100644 --- a/src/minisketch/src/int_utils.h +++ b/src/minisketch/src/int_utils.h @@ -129,17 +129,7 @@ constexpr inline I Mask() { return ((I((I(-1)) << (std::numeric_limits<I>::digit /** Compute the smallest power of two that is larger than val. */ template<typename I> static inline int CountBits(I val, int max) { -#ifdef HAVE_CLZ - (void)max; - if (val == 0) return 0; - if (std::numeric_limits<unsigned>::digits >= std::numeric_limits<I>::digits) { - return std::numeric_limits<unsigned>::digits - __builtin_clz(val); - } else if (std::numeric_limits<unsigned long>::digits >= std::numeric_limits<I>::digits) { - return std::numeric_limits<unsigned long>::digits - __builtin_clzl(val); - } else { - return std::numeric_limits<unsigned long long>::digits - __builtin_clzll(val); - } -#elif _MSC_VER +#ifdef _MSC_VER (void)max; unsigned long index; unsigned char ret; @@ -149,7 +139,17 @@ static inline int CountBits(I val, int max) { ret = _BitScanReverse64(&index, val); } if (!ret) return 0; - return index; + return index + 1; +#elif HAVE_CLZ + (void)max; + if (val == 0) return 0; + if (std::numeric_limits<unsigned>::digits >= std::numeric_limits<I>::digits) { + return std::numeric_limits<unsigned>::digits - __builtin_clz(val); + } else if (std::numeric_limits<unsigned long>::digits >= std::numeric_limits<I>::digits) { + return std::numeric_limits<unsigned long>::digits - __builtin_clzl(val); + } else { + return std::numeric_limits<unsigned long long>::digits - __builtin_clzll(val); + } #else while (max && (val >> (max - 1) == 0)) --max; return max; diff --git a/src/minisketch/src/test.cpp b/src/minisketch/src/test.cpp index 417937ea5f..85b9e9e396 100644 --- a/src/minisketch/src/test.cpp +++ b/src/minisketch/src/test.cpp @@ -9,6 +9,7 @@ #include <limits> #include <random> #include <stdexcept> +#include <string> #include <vector> #include "../include/minisketch.h" diff --git a/src/net.cpp b/src/net.cpp index 3c28b9eddf..374e93a2bd 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -31,6 +31,7 @@ #include <util/syscall_sandbox.h> #include <util/system.h> #include <util/thread.h> +#include <util/threadinterrupt.h> #include <util/trace.h> #include <util/translation.h> @@ -24,10 +24,10 @@ #include <span.h> #include <streams.h> #include <sync.h> -#include <threadinterrupt.h> #include <uint256.h> #include <util/check.h> #include <util/sock.h> +#include <util/threadinterrupt.h> #include <atomic> #include <condition_variable> @@ -489,10 +489,8 @@ public: /** Whether this peer provides all services that we want. Used for eviction decisions */ std::atomic_bool m_has_all_wanted_services{false}; - /** Whether we should relay transactions to this peer (their version - * message did not include fRelay=false and this is not a block-relay-only - * connection). This only changes from false to true. It will never change - * back to false. Used only in inbound eviction logic. */ + /** Whether we should relay transactions to this peer. This only changes + * from false to true. It will never change back to false. */ std::atomic_bool m_relays_txs{false}; /** Whether this peer has loaded a bloom filter. Used only in inbound diff --git a/src/net_permissions.h b/src/net_permissions.h index 662464083c..c9d5ec2989 100644 --- a/src/net_permissions.h +++ b/src/net_permissions.h @@ -35,7 +35,8 @@ enum class NetPermissionFlags : uint32_t { // unlimited amounts of addrs. Addr = (1U << 7), - // True if the user did not specifically set fine grained permissions + // True if the user did not specifically set fine-grained permissions with + // the -whitebind or -whitelist configuration options. Implicit = (1U << 31), All = BloomFilter | ForceRelay | Relay | NoBan | Mempool | Download | Addr, }; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 3f344005bc..0d5be42e0e 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -34,6 +34,7 @@ #include <timedata.h> #include <tinyformat.h> #include <txmempool.h> +#include <txmempool_entry.h> #include <txorphanage.h> #include <txrequest.h> #include <util/check.h> // For NDEBUG compile time check @@ -395,9 +396,7 @@ struct Peer { private: Mutex m_tx_relay_mutex; - /** Transaction relay data. Will be a nullptr if we're not relaying - * transactions with this peer (e.g. if it's a block-relay-only peer or - * the peer has sent us fRelay=false with bloom filters disabled). */ + /** Transaction relay data. May be a nullptr. */ std::unique_ptr<TxRelay> m_tx_relay GUARDED_BY(m_tx_relay_mutex); }; @@ -638,9 +637,8 @@ private: * @param[in] chain_start_header Where these headers connect in our index. * @param[in,out] headers The headers to be processed. * - * @return True if chain was low work and a headers sync was - * initiated (and headers will be empty after calling); false - * otherwise. + * @return True if chain was low work (headers will be empty after + * calling); false otherwise. */ bool TryLowWorkHeadersSync(Peer& peer, CNode& pfrom, const CBlockIndex* chain_start_header, @@ -1291,7 +1289,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(const Peer& peer, unsigned int co // Make sure pindexBestKnownBlock is up to date, we'll need it. ProcessBlockAvailability(peer.m_id); - if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { + if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < m_chainman.MinimumChainWork()) { // This peer has nothing interesting. return; } @@ -2392,7 +2390,7 @@ arith_uint256 PeerManagerImpl::GetAntiDoSWorkThreshold() // near our tip. near_chaintip_work = tip->nChainWork - std::min<arith_uint256>(144*GetBlockProof(*tip), tip->nChainWork); } - return std::max(near_chaintip_work, arith_uint256(nMinimumChainWork)); + return std::max(near_chaintip_work, m_chainman.MinimumChainWork()); } /** @@ -2563,14 +2561,10 @@ bool PeerManagerImpl::TryLowWorkHeadersSync(Peer& peer, CNode& pfrom, const CBlo peer.m_headers_sync.reset(new HeadersSyncState(peer.m_id, m_chainparams.GetConsensus(), chain_start_header, minimum_chain_work)); - // Now a HeadersSyncState object for tracking this synchronization is created, - // process the headers using it as normal. - if (!IsContinuationOfLowWorkHeadersSync(peer, pfrom, headers)) { - // Something went wrong, reset the headers sync. - peer.m_headers_sync.reset(nullptr); - LOCK(m_headers_presync_mutex); - m_headers_presync_stats.erase(peer.m_id); - } + // Now a HeadersSyncState object for tracking this synchronization + // is created, process the headers using it as normal. Failures are + // handled inside of IsContinuationOfLowWorkHeadersSync. + (void)IsContinuationOfLowWorkHeadersSync(peer, pfrom, headers); } else { LogPrint(BCLog::NET, "Ignoring low-work chain (height=%u) from peer=%d\n", chain_start_header->nHeight + headers.size(), pfrom.GetId()); } @@ -2710,14 +2704,14 @@ void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom, if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !may_have_more_headers) { // If the peer has no more headers to give us, then we know we have // their tip. - if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { + if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < m_chainman.MinimumChainWork()) { // This peer has too little work on their headers chain to help // us sync -- disconnect if it is an outbound disconnection // candidate. - // Note: We compare their tip to nMinimumChainWork (rather than + // Note: We compare their tip to the minimum chain work (rather than // m_chainman.ActiveChain().Tip()) because we won't start block download // until we have a headers chain that has at least - // nMinimumChainWork, even if a peer has a chain past our tip, + // the minimum chain work, even if a peer has a chain past our tip, // as an anti-DoS measure. if (pfrom.IsOutboundOrBlockRelayConn()) { LogPrintf("Disconnecting outbound peer %d -- headers chain has insufficient work\n", pfrom.GetId()); @@ -3261,12 +3255,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } peer->m_starting_height = starting_height; - // We only initialize the Peer::TxRelay m_relay_txs data structure if: + // Only initialize the Peer::TxRelay m_relay_txs data structure if: // - this isn't an outbound block-relay-only connection, and + // - this isn't an outbound feeler connection, and // - fRelay=true (the peer wishes to receive transaction announcements) // or we're offering NODE_BLOOM to this peer. NODE_BLOOM means that // the peer may turn on transaction relay later. if (!pfrom.IsBlockOnlyConn() && + !pfrom.IsFeelerConn() && (fRelay || (peer->m_our_services & NODE_BLOOM))) { auto* const tx_relay = peer->SetTxRelay(); { @@ -3901,12 +3897,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Note that if we were to be on a chain that forks from the checkpointed // chain, then serving those headers to a peer that has seen the // checkpointed chain would cause that peer to disconnect us. Requiring - // that our chainwork exceed nMinimumChainWork is a protection against + // that our chainwork exceed the minimum chain work is a protection against // being fed a bogus chain when we started up for the first time and // getting partitioned off the honest network for serving that chain to // others. if (m_chainman.ActiveTip() == nullptr || - (m_chainman.ActiveTip()->nChainWork < nMinimumChainWork && !pfrom.HasPermission(NetPermissionFlags::Download))) { + (m_chainman.ActiveTip()->nChainWork < m_chainman.MinimumChainWork() && !pfrom.HasPermission(NetPermissionFlags::Download))) { LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because active chain has too little work; sending empty response\n", pfrom.GetId()); // Just respond with an empty headers message, to tell the peer to // go away but not treat us as unresponsive. @@ -4370,7 +4366,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // (eg disk space). Because we only try to reconstruct blocks when // we're close to caught up (via the CanDirectFetch() requirement // above, combined with the behavior of not requesting blocks until - // we have a chain with at least nMinimumChainWork), and we ignore + // we have a chain with at least the minimum chain work), and we ignore // compact blocks with less work than our tip, it is safe to treat // reconstructed compact blocks as having been requested. ProcessBlock(pfrom, pblock, /*force_processing=*/true, /*min_pow_checked=*/true); @@ -5236,7 +5232,7 @@ void PeerManagerImpl::MaybeSendSendHeaders(CNode& node, Peer& peer) LOCK(cs_main); CNodeState &state = *State(node.GetId()); if (state.pindexBestKnownBlock != nullptr && - state.pindexBestKnownBlock->nChainWork > nMinimumChainWork) { + state.pindexBestKnownBlock->nChainWork > m_chainman.MinimumChainWork()) { // Tell our peer we prefer to receive headers rather than inv's // We send this to non-NODE NETWORK peers as well, because even // non-NODE NETWORK peers can announce blocks (such as pruning diff --git a/src/netaddress.cpp b/src/netaddress.cpp index ca148bfa51..eabab3dd99 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -588,7 +588,7 @@ static std::string IPv6ToString(Span<const uint8_t> a, uint32_t scope_id) return r; } -static std::string OnionToString(Span<const uint8_t> addr) +std::string OnionToString(Span<const uint8_t> addr) { uint8_t checksum[torv3::CHECKSUM_LEN]; torv3::Checksum(addr, checksum); diff --git a/src/netaddress.h b/src/netaddress.h index e52beb783d..11086eaee0 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -111,6 +111,8 @@ static constexpr size_t ADDR_INTERNAL_SIZE = 10; /// SAM 3.1 and earlier do not support specifying ports and force the port to 0. static constexpr uint16_t I2P_SAM31_PORT{0}; +std::string OnionToString(Span<const uint8_t> addr); + /** * Network address. */ diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 26af523491..60acb614b4 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -4,9 +4,11 @@ #include <node/chainstate.h> +#include <arith_uint256.h> #include <chain.h> #include <coins.h> #include <consensus/params.h> +#include <logging.h> #include <node/blockstorage.h> #include <node/caches.h> #include <sync.h> @@ -21,6 +23,7 @@ #include <algorithm> #include <atomic> #include <cassert> +#include <limits> #include <memory> #include <vector> @@ -32,13 +35,13 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull(); }; - if (!hashAssumeValid.IsNull()) { - LogPrintf("Assuming ancestors of block %s have valid signatures.\n", hashAssumeValid.GetHex()); + if (!chainman.AssumedValidBlock().IsNull()) { + LogPrintf("Assuming ancestors of block %s have valid signatures.\n", chainman.AssumedValidBlock().GetHex()); } else { LogPrintf("Validating signatures for all blocks.\n"); } - LogPrintf("Setting nMinimumChainWork=%s\n", nMinimumChainWork.GetHex()); - if (nMinimumChainWork < UintToArith256(chainman.GetConsensus().nMinimumChainWork)) { + LogPrintf("Setting nMinimumChainWork=%s\n", chainman.MinimumChainWork().GetHex()); + if (chainman.MinimumChainWork() < UintToArith256(chainman.GetConsensus().nMinimumChainWork)) { LogPrintf("Warning: nMinimumChainWork set below default value of %s\n", chainman.GetConsensus().nMinimumChainWork.GetHex()); } if (nPruneTarget == std::numeric_limits<uint64_t>::max()) { diff --git a/src/node/chainstatemanager_args.cpp b/src/node/chainstatemanager_args.cpp new file mode 100644 index 0000000000..b0d929626b --- /dev/null +++ b/src/node/chainstatemanager_args.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/chainstatemanager_args.h> + +#include <arith_uint256.h> +#include <tinyformat.h> +#include <uint256.h> +#include <util/strencodings.h> +#include <util/system.h> +#include <util/translation.h> +#include <validation.h> + +#include <chrono> +#include <optional> +#include <string> + +namespace node { +std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts) +{ + if (auto value{args.GetBoolArg("-checkblockindex")}) opts.check_block_index = *value; + + if (auto value{args.GetBoolArg("-checkpoints")}) opts.checkpoints_enabled = *value; + + if (auto value{args.GetArg("-minimumchainwork")}) { + if (!IsHexNumber(*value)) { + return strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), *value); + } + opts.minimum_chain_work = UintToArith256(uint256S(*value)); + } + + if (auto value{args.GetArg("-assumevalid")}) opts.assumed_valid_block = uint256S(*value); + + if (auto value{args.GetIntArg("-maxtipage")}) opts.max_tip_age = std::chrono::seconds{*value}; + + return std::nullopt; +} +} // namespace node diff --git a/src/node/chainstatemanager_args.h b/src/node/chainstatemanager_args.h new file mode 100644 index 0000000000..6c46b998f2 --- /dev/null +++ b/src/node/chainstatemanager_args.h @@ -0,0 +1,19 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H +#define BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H + +#include <validation.h> + +#include <optional> + +class ArgsManager; +struct bilingual_str; + +namespace node { +std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts); +} // namespace node + +#endif // BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 8a0011a629..212780b259 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -4,10 +4,12 @@ #include <addrdb.h> #include <banman.h> +#include <blockfilter.h> #include <chain.h> #include <chainparams.h> #include <deploymentstatus.h> #include <external_signer.h> +#include <index/blockfilterindex.h> #include <init.h> #include <interfaces/chain.h> #include <interfaces/handler.h> @@ -37,6 +39,7 @@ #include <support/allocators/secure.h> #include <sync.h> #include <txmempool.h> +#include <txmempool_entry.h> #include <uint256.h> #include <univalue.h> #include <util/check.h> @@ -536,6 +539,20 @@ public: } return std::nullopt; } + bool hasBlockFilterIndex(BlockFilterType filter_type) override + { + return GetBlockFilterIndex(filter_type) != nullptr; + } + std::optional<bool> blockFilterMatchesAny(BlockFilterType filter_type, const uint256& block_hash, const GCSFilter::ElementSet& filter_set) override + { + const BlockFilterIndex* block_filter_index{GetBlockFilterIndex(filter_type)}; + if (!block_filter_index) return std::nullopt; + + BlockFilter filter; + const CBlockIndex* index{WITH_LOCK(::cs_main, return chainman().m_blockman.LookupBlockIndex(block_hash))}; + if (index == nullptr || !block_filter_index->LookupFilter(index, filter)) return std::nullopt; + return filter.GetFilter().MatchAny(filter_set); + } bool findBlock(const uint256& hash, const FoundBlock& block) override { WAIT_LOCK(cs_main, lock); diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index ab5599a1b4..899adc70ca 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -16,7 +16,7 @@ #include <streams.h> #include <sync.h> #include <tinyformat.h> -#include <txmempool.h> +#include <txmempool_entry.h> #include <uint256.h> #include <util/serfloat.h> #include <util/system.h> diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp index 55f47f485b..994e13dd56 100644 --- a/src/policy/rbf.cpp +++ b/src/policy/rbf.cpp @@ -10,6 +10,7 @@ #include <sync.h> #include <tinyformat.h> #include <txmempool.h> +#include <txmempool_entry.h> #include <uint256.h> #include <util/moneystr.h> #include <util/rbf.h> diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 894a401e56..2bd38103ed 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -410,7 +410,7 @@ void BitcoinGUI::createActions() connect(action, &QAction::triggered, [this, path] { auto activity = new OpenWalletActivity(m_wallet_controller, this); - connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet); + connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet, Qt::QueuedConnection); activity->open(path); }); } @@ -512,7 +512,7 @@ void BitcoinGUI::createMenuBar() connect(minimize_action, &QAction::triggered, [] { QApplication::activeWindow()->showMinimized(); }); - connect(qApp, &QApplication::focusWindowChanged, [minimize_action] (QWindow* window) { + connect(qApp, &QApplication::focusWindowChanged, this, [minimize_action] (QWindow* window) { minimize_action->setEnabled(window != nullptr && (window->flags() & Qt::Dialog) != Qt::Dialog && window->windowState() != Qt::WindowMinimized); }); @@ -527,7 +527,7 @@ void BitcoinGUI::createMenuBar() } }); - connect(qApp, &QApplication::focusWindowChanged, [zoom_action] (QWindow* window) { + connect(qApp, &QApplication::focusWindowChanged, this, [zoom_action] (QWindow* window) { zoom_action->setEnabled(window != nullptr); }); #endif diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 33308cd68c..f1b66341d1 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -1185,7 +1185,7 @@ <item row="6" column="0"> <widget class="QLabel" name="peerRelayTxesLabel"> <property name="toolTip"> - <string>Whether we relay transactions to this peer (not available while the peer connection is being set up).</string> + <string>Whether we relay transactions to this peer.</string> </property> <property name="text"> <string>Transaction Relay</string> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index b9f0be41e3..6e88b57e08 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -615,9 +615,10 @@ bool SetStartOnSystemStartup(bool fAutoStart) else { char pszExePath[MAX_PATH+1]; - ssize_t r = readlink("/proc/self/exe", pszExePath, sizeof(pszExePath) - 1); - if (r == -1) + ssize_t r = readlink("/proc/self/exe", pszExePath, sizeof(pszExePath)); + if (r == -1 || r > MAX_PATH) { return false; + } pszExePath[r] = '\0'; fs::create_directories(GetAutostartDir()); diff --git a/src/qt/main.cpp b/src/qt/main.cpp index e8f39584ad..45131a1cf5 100644 --- a/src/qt/main.cpp +++ b/src/qt/main.cpp @@ -4,9 +4,9 @@ #include <qt/bitcoin.h> +#include <common/url.h> #include <compat/compat.h> #include <util/translation.h> -#include <util/url.h> #include <QCoreApplication> diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 53c352b393..57094fc857 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -289,7 +289,9 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa updateCoinControlState(); - prepareStatus = model->prepareTransaction(*m_current_transaction, *m_coin_control); + CCoinControl coin_control = *m_coin_control; + coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value. + prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control); // process prepareStatus and on error generate message shown to user processSendCoinsReturn(prepareStatus, diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index f8ba822f54..027a61ff8b 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -459,6 +459,12 @@ static RPCHelpMan getblockfrompeer() throw JSONRPCError(RPC_MISC_ERROR, "Block header missing"); } + // Fetching blocks before the node has syncing past their height can prevent block files from + // being pruned, so we avoid it if the node is in prune mode. + if (node::fPruneMode && index->nHeight > WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()->nHeight)) { + throw JSONRPCError(RPC_MISC_ERROR, "In prune mode, only blocks that the node has already synced previously can be fetched from a peer"); + } + const bool block_has_data = WITH_LOCK(::cs_main, return index->nStatus & BLOCK_HAVE_DATA); if (block_has_data) { throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded"); @@ -2199,7 +2205,7 @@ static RPCHelpMan scantxoutset() result.pushKV("unspents", unspents); result.pushKV("total_amount", ValueFromAmount(total_in)); } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", request.params[0].get_str())); } return result; }, @@ -2248,12 +2254,13 @@ static RPCHelpMan scanblocks() }, { scan_result_status_none, - RPCResult{"When action=='start'", RPCResult::Type::OBJ, "", "", { + RPCResult{"When action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "from_height", "The height we started the scan from"}, {RPCResult::Type::NUM, "to_height", "The height we ended the scan at"}, - {RPCResult::Type::ARR, "relevant_blocks", "", {{RPCResult::Type::STR_HEX, "blockhash", "A relevant blockhash"},}}, - }, - }, + {RPCResult::Type::ARR, "relevant_blocks", "Blocks that may have matched a scanobject.", { + {RPCResult::Type::STR_HEX, "blockhash", "A relevant blockhash"}, + }}, + }}, RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "progress", "Approximate percent complete"}, {RPCResult::Type::NUM, "current_height", "Height of the block currently being scanned"}, @@ -2396,7 +2403,7 @@ static RPCHelpMan scanblocks() ret.pushKV("relevant_blocks", blocks); } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", request.params[0].get_str())); } return ret; }, diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 706d783942..039a4328e3 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -18,6 +18,7 @@ #include <rpc/server_util.h> #include <rpc/util.h> #include <txmempool.h> +#include <txmempool_entry.h> #include <univalue.h> #include <util/moneystr.h> #include <util/time.h> diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index 744f809814..a980c609e8 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -273,7 +273,7 @@ static RPCHelpMan deriveaddresses() UniValue addresses(UniValue::VARR); - for (int i = range_begin; i <= range_end; ++i) { + for (int64_t i = range_begin; i <= range_end; ++i) { FlatSigningProvider provider; std::vector<CScript> scripts; if (!desc->Expand(i, key_provider, scripts, provider)) { diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 3e98e89791..dd5739faf7 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -65,11 +65,8 @@ void RPCTypeCheckObj(const UniValue& o, if (!fAllowNull && v.isNull()) throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first)); - if (!(t.second.typeAny || v.type() == t.second.type || (fAllowNull && v.isNull()))) { - std::string err = strprintf("Expected type %s for %s, got %s", - uvTypeName(t.second.type), t.first, uvTypeName(v.type())); - throw JSONRPCError(RPC_TYPE_ERROR, err); - } + if (!(t.second.typeAny || v.type() == t.second.type || (fAllowNull && v.isNull()))) + throw JSONRPCError(RPC_TYPE_ERROR, strprintf("JSON value of type %s for field %s is not of expected type %s", uvTypeName(v.type()), t.first, uvTypeName(t.second.type))); } if (fStrict) diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 5da0d076d8..0d74a661a5 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -146,6 +146,16 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const XOnlyPubKey& pubkey, const uint256& leaf_hash, SigVersion sigversion) { + KeyOriginInfo info; + if (provider.GetKeyOriginByXOnly(pubkey, info)) { + auto it = sigdata.taproot_misc_pubkeys.find(pubkey); + if (it == sigdata.taproot_misc_pubkeys.end()) { + sigdata.taproot_misc_pubkeys.emplace(pubkey, std::make_pair(std::set<uint256>({leaf_hash}), info)); + } else { + it->second.first.insert(leaf_hash); + } + } + auto lookup_key = std::make_pair(pubkey, leaf_hash); auto it = sigdata.taproot_script_sigs.find(lookup_key); if (it != sigdata.taproot_script_sigs.end()) { @@ -170,17 +180,6 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu // <xonly pubkey> OP_CHECKSIG if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) { XOnlyPubKey pubkey{Span{script}.subspan(1, 32)}; - - KeyOriginInfo info; - if (provider.GetKeyOriginByXOnly(pubkey, info)) { - auto it = sigdata.taproot_misc_pubkeys.find(pubkey); - if (it == sigdata.taproot_misc_pubkeys.end()) { - sigdata.taproot_misc_pubkeys.emplace(pubkey, std::make_pair(std::set<uint256>({leaf_hash}), info)); - } else { - it->second.first.insert(leaf_hash); - } - } - std::vector<unsigned char> sig; if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) { result = Vector(std::move(sig)); diff --git a/src/streams.h b/src/streams.h index 0178df1c49..84b12f65aa 100644 --- a/src/streams.h +++ b/src/streams.h @@ -612,7 +612,6 @@ private: uint64_t nRewind; //!< how many bytes we guarantee to rewind std::vector<std::byte> vchBuf; //!< the buffer -protected: //! read data from the source to fill the buffer bool Fill() { unsigned int pos = nSrcPos % vchBuf.size(); @@ -630,6 +629,28 @@ protected: return true; } + //! Advance the stream's read pointer (m_read_pos) by up to 'length' bytes, + //! filling the buffer from the file so that at least one byte is available. + //! Return a pointer to the available buffer data and the number of bytes + //! (which may be less than the requested length) that may be accessed + //! beginning at that pointer. + std::pair<std::byte*, size_t> AdvanceStream(size_t length) + { + assert(m_read_pos <= nSrcPos); + if (m_read_pos + length > nReadLimit) { + throw std::ios_base::failure("Attempt to position past buffer limit"); + } + // If there are no bytes available, read from the file. + if (m_read_pos == nSrcPos && length > 0) Fill(); + + size_t buffer_offset{static_cast<size_t>(m_read_pos % vchBuf.size())}; + size_t buffer_available{static_cast<size_t>(vchBuf.size() - buffer_offset)}; + size_t bytes_until_source_pos{static_cast<size_t>(nSrcPos - m_read_pos)}; + size_t advance{std::min({length, buffer_available, bytes_until_source_pos})}; + m_read_pos += advance; + return std::make_pair(&vchBuf[buffer_offset], advance); + } + public: CBufferedFile(FILE* fileIn, uint64_t nBufSize, uint64_t nRewindIn, int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn), nSrcPos(0), m_read_pos(0), nReadLimit(std::numeric_limits<uint64_t>::max()), nRewind(nRewindIn), vchBuf(nBufSize, std::byte{0}) @@ -667,24 +688,21 @@ public: //! read a number of bytes void read(Span<std::byte> dst) { - if (dst.size() + m_read_pos > nReadLimit) { - throw std::ios_base::failure("Read attempted past buffer limit"); - } while (dst.size() > 0) { - if (m_read_pos == nSrcPos) - Fill(); - unsigned int pos = m_read_pos % vchBuf.size(); - size_t nNow = dst.size(); - if (nNow + pos > vchBuf.size()) - nNow = vchBuf.size() - pos; - if (nNow + m_read_pos > nSrcPos) - nNow = nSrcPos - m_read_pos; - memcpy(dst.data(), &vchBuf[pos], nNow); - m_read_pos += nNow; - dst = dst.subspan(nNow); + auto [buffer_pointer, length]{AdvanceStream(dst.size())}; + memcpy(dst.data(), buffer_pointer, length); + dst = dst.subspan(length); } } + //! Move the read position ahead in the stream to the given position. + //! Use SetPos() to back up in the stream, not SkipTo(). + void SkipTo(const uint64_t file_pos) + { + assert(file_pos >= m_read_pos); + while (m_read_pos < file_pos) AdvanceStream(file_pos - m_read_pos); + } + //! return the current reading position uint64_t GetPos() const { return m_read_pos; diff --git a/src/test/argsman_tests.cpp b/src/test/argsman_tests.cpp new file mode 100644 index 0000000000..d00876bc70 --- /dev/null +++ b/src/test/argsman_tests.cpp @@ -0,0 +1,1043 @@ +// Copyright (c) 2011-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <util/system.h> +#include <fs.h> +#include <sync.h> +#include <test/util/logging.h> +#include <test/util/setup_common.h> +#include <test/util/str.h> +#include <util/strencodings.h> +#include <univalue.h> + +#include <array> +#include <optional> +#include <cstdint> +#include <cstring> +#include <vector> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(argsman_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(util_datadir) +{ + // Use local args variable instead of m_args to avoid making assumptions about test setup + ArgsManager args; + args.ForceSetArg("-datadir", fs::PathToString(m_path_root)); + + const fs::path dd_norm = args.GetDataDirBase(); + + args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/"); + args.ClearPathCache(); + BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase()); + + args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/."); + args.ClearPathCache(); + BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase()); + + args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/./"); + args.ClearPathCache(); + BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase()); + + args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/.//"); + args.ClearPathCache(); + BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase()); +} + +struct TestArgsManager : public ArgsManager +{ + TestArgsManager() { m_network_only_args.clear(); } + void ReadConfigString(const std::string str_config) + { + std::istringstream streamConfig(str_config); + { + LOCK(cs_args); + m_settings.ro_config.clear(); + m_config_sections.clear(); + } + std::string error; + BOOST_REQUIRE(ReadConfigStream(streamConfig, "", error)); + } + void SetNetworkOnlyArg(const std::string arg) + { + LOCK(cs_args); + m_network_only_args.insert(arg); + } + void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args) + { + for (const auto& arg : args) { + AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS); + } + } + using ArgsManager::GetSetting; + using ArgsManager::GetSettingsList; + using ArgsManager::ReadConfigStream; + using ArgsManager::cs_args; + using ArgsManager::m_network; + using ArgsManager::m_settings; +}; + +//! Test GetSetting and GetArg type coercion, negation, and default value handling. +class CheckValueTest : public TestChain100Setup +{ +public: + struct Expect { + util::SettingsValue setting; + bool default_string = false; + bool default_int = false; + bool default_bool = false; + const char* string_value = nullptr; + std::optional<int64_t> int_value; + std::optional<bool> bool_value; + std::optional<std::vector<std::string>> list_value; + const char* error = nullptr; + + explicit Expect(util::SettingsValue s) : setting(std::move(s)) {} + Expect& DefaultString() { default_string = true; return *this; } + Expect& DefaultInt() { default_int = true; return *this; } + Expect& DefaultBool() { default_bool = true; return *this; } + Expect& String(const char* s) { string_value = s; return *this; } + Expect& Int(int64_t i) { int_value = i; return *this; } + Expect& Bool(bool b) { bool_value = b; return *this; } + Expect& List(std::vector<std::string> m) { list_value = std::move(m); return *this; } + Expect& Error(const char* e) { error = e; return *this; } + }; + + void CheckValue(unsigned int flags, const char* arg, const Expect& expect) + { + TestArgsManager test; + test.SetupArgs({{"-value", flags}}); + const char* argv[] = {"ignored", arg}; + std::string error; + bool success = test.ParseParameters(arg ? 2 : 1, (char**)argv, error); + + BOOST_CHECK_EQUAL(test.GetSetting("-value").write(), expect.setting.write()); + auto settings_list = test.GetSettingsList("-value"); + if (expect.setting.isNull() || expect.setting.isFalse()) { + BOOST_CHECK_EQUAL(settings_list.size(), 0U); + } else { + BOOST_CHECK_EQUAL(settings_list.size(), 1U); + BOOST_CHECK_EQUAL(settings_list[0].write(), expect.setting.write()); + } + + if (expect.error) { + BOOST_CHECK(!success); + BOOST_CHECK_NE(error.find(expect.error), std::string::npos); + } else { + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(error, ""); + } + + if (expect.default_string) { + BOOST_CHECK_EQUAL(test.GetArg("-value", "zzzzz"), "zzzzz"); + } else if (expect.string_value) { + BOOST_CHECK_EQUAL(test.GetArg("-value", "zzzzz"), expect.string_value); + } else { + BOOST_CHECK(!success); + } + + if (expect.default_int) { + BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), 99999); + } else if (expect.int_value) { + BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), *expect.int_value); + } else { + BOOST_CHECK(!success); + } + + if (expect.default_bool) { + BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), false); + BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), true); + } else if (expect.bool_value) { + BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), *expect.bool_value); + BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), *expect.bool_value); + } else { + BOOST_CHECK(!success); + } + + if (expect.list_value) { + auto l = test.GetArgs("-value"); + BOOST_CHECK_EQUAL_COLLECTIONS(l.begin(), l.end(), expect.list_value->begin(), expect.list_value->end()); + } else { + BOOST_CHECK(!success); + } + } +}; + +BOOST_FIXTURE_TEST_CASE(util_CheckValue, CheckValueTest) +{ + using M = ArgsManager; + + CheckValue(M::ALLOW_ANY, nullptr, Expect{{}}.DefaultString().DefaultInt().DefaultBool().List({})); + CheckValue(M::ALLOW_ANY, "-novalue", Expect{false}.String("0").Int(0).Bool(false).List({})); + CheckValue(M::ALLOW_ANY, "-novalue=", Expect{false}.String("0").Int(0).Bool(false).List({})); + CheckValue(M::ALLOW_ANY, "-novalue=0", Expect{true}.String("1").Int(1).Bool(true).List({"1"})); + CheckValue(M::ALLOW_ANY, "-novalue=1", Expect{false}.String("0").Int(0).Bool(false).List({})); + CheckValue(M::ALLOW_ANY, "-novalue=2", Expect{false}.String("0").Int(0).Bool(false).List({})); + CheckValue(M::ALLOW_ANY, "-novalue=abc", Expect{true}.String("1").Int(1).Bool(true).List({"1"})); + CheckValue(M::ALLOW_ANY, "-value", Expect{""}.String("").Int(0).Bool(true).List({""})); + CheckValue(M::ALLOW_ANY, "-value=", Expect{""}.String("").Int(0).Bool(true).List({""})); + CheckValue(M::ALLOW_ANY, "-value=0", Expect{"0"}.String("0").Int(0).Bool(false).List({"0"})); + CheckValue(M::ALLOW_ANY, "-value=1", Expect{"1"}.String("1").Int(1).Bool(true).List({"1"})); + CheckValue(M::ALLOW_ANY, "-value=2", Expect{"2"}.String("2").Int(2).Bool(true).List({"2"})); + CheckValue(M::ALLOW_ANY, "-value=abc", Expect{"abc"}.String("abc").Int(0).Bool(false).List({"abc"})); +} + +struct NoIncludeConfTest { + std::string Parse(const char* arg) + { + TestArgsManager test; + test.SetupArgs({{"-includeconf", ArgsManager::ALLOW_ANY}}); + std::array argv{"ignored", arg}; + std::string error; + (void)test.ParseParameters(argv.size(), argv.data(), error); + return error; + } +}; + +BOOST_FIXTURE_TEST_CASE(util_NoIncludeConf, NoIncludeConfTest) +{ + BOOST_CHECK_EQUAL(Parse("-noincludeconf"), ""); + BOOST_CHECK_EQUAL(Parse("-includeconf"), "-includeconf cannot be used from commandline; -includeconf=\"\""); + BOOST_CHECK_EQUAL(Parse("-includeconf=file"), "-includeconf cannot be used from commandline; -includeconf=\"file\""); +} + +BOOST_AUTO_TEST_CASE(util_ParseParameters) +{ + TestArgsManager testArgs; + const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY); + const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY); + const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY); + const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY); + + const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"}; + + std::string error; + LOCK(testArgs.cs_args); + testArgs.SetupArgs({a, b, ccc, d}); + BOOST_CHECK(testArgs.ParseParameters(0, (char**)argv_test, error)); + BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty()); + + BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error)); + BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty()); + + BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error)); + // expectation: -ignored is ignored (program name argument), + // -a, -b and -ccc end up in map, -d ignored because it is after + // a non-option argument (non-GNU option parsing) + BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 3 && testArgs.m_settings.ro_config.empty()); + BOOST_CHECK(testArgs.IsArgSet("-a") && testArgs.IsArgSet("-b") && testArgs.IsArgSet("-ccc") + && !testArgs.IsArgSet("f") && !testArgs.IsArgSet("-d")); + BOOST_CHECK(testArgs.m_settings.command_line_options.count("a") && testArgs.m_settings.command_line_options.count("b") && testArgs.m_settings.command_line_options.count("ccc") + && !testArgs.m_settings.command_line_options.count("f") && !testArgs.m_settings.command_line_options.count("d")); + + BOOST_CHECK(testArgs.m_settings.command_line_options["a"].size() == 1); + BOOST_CHECK(testArgs.m_settings.command_line_options["a"].front().get_str() == ""); + BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].size() == 2); + BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].front().get_str() == "argument"); + BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].back().get_str() == "multiple"); + BOOST_CHECK(testArgs.GetArgs("-ccc").size() == 2); +} + +BOOST_AUTO_TEST_CASE(util_ParseInvalidParameters) +{ + TestArgsManager test; + test.SetupArgs({{"-registered", ArgsManager::ALLOW_ANY}}); + + const char* argv[] = {"ignored", "-registered"}; + std::string error; + BOOST_CHECK(test.ParseParameters(2, (char**)argv, error)); + BOOST_CHECK_EQUAL(error, ""); + + argv[1] = "-unregistered"; + BOOST_CHECK(!test.ParseParameters(2, (char**)argv, error)); + BOOST_CHECK_EQUAL(error, "Invalid parameter -unregistered"); + + // Make sure registered parameters prefixed with a chain name trigger errors. + // (Previously, they were accepted and ignored.) + argv[1] = "-test.registered"; + BOOST_CHECK(!test.ParseParameters(2, (char**)argv, error)); + BOOST_CHECK_EQUAL(error, "Invalid parameter -test.registered"); +} + +static void TestParse(const std::string& str, bool expected_bool, int64_t expected_int) +{ + TestArgsManager test; + test.SetupArgs({{"-value", ArgsManager::ALLOW_ANY}}); + std::string arg = "-value=" + str; + const char* argv[] = {"ignored", arg.c_str()}; + std::string error; + BOOST_CHECK(test.ParseParameters(2, (char**)argv, error)); + BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), expected_bool); + BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), expected_bool); + BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99998), expected_int); + BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), expected_int); +} + +// Test bool and int parsing. +BOOST_AUTO_TEST_CASE(util_ArgParsing) +{ + // Some of these cases could be ambiguous or surprising to users, and might + // be worth triggering errors or warnings in the future. But for now basic + // test coverage is useful to avoid breaking backwards compatibility + // unintentionally. + TestParse("", true, 0); + TestParse(" ", false, 0); + TestParse("0", false, 0); + TestParse("0 ", false, 0); + TestParse(" 0", false, 0); + TestParse("+0", false, 0); + TestParse("-0", false, 0); + TestParse("5", true, 5); + TestParse("5 ", true, 5); + TestParse(" 5", true, 5); + TestParse("+5", true, 5); + TestParse("-5", true, -5); + TestParse("0 5", false, 0); + TestParse("5 0", true, 5); + TestParse("050", true, 50); + TestParse("0.", false, 0); + TestParse("5.", true, 5); + TestParse("0.0", false, 0); + TestParse("0.5", false, 0); + TestParse("5.0", true, 5); + TestParse("5.5", true, 5); + TestParse("x", false, 0); + TestParse("x0", false, 0); + TestParse("x5", false, 0); + TestParse("0x", false, 0); + TestParse("5x", true, 5); + TestParse("0x5", false, 0); + TestParse("false", false, 0); + TestParse("true", false, 0); + TestParse("yes", false, 0); + TestParse("no", false, 0); +} + +BOOST_AUTO_TEST_CASE(util_GetBoolArg) +{ + TestArgsManager testArgs; + const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY); + const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY); + const auto c = std::make_pair("-c", ArgsManager::ALLOW_ANY); + const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY); + const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY); + const auto f = std::make_pair("-f", ArgsManager::ALLOW_ANY); + + const char *argv_test[] = { + "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"}; + std::string error; + LOCK(testArgs.cs_args); + testArgs.SetupArgs({a, b, c, d, e, f}); + BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error)); + + // Each letter should be set. + for (const char opt : "abcdef") + BOOST_CHECK(testArgs.IsArgSet({'-', opt}) || !opt); + + // Nothing else should be in the map + BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 6 && + testArgs.m_settings.ro_config.empty()); + + // The -no prefix should get stripped on the way in. + BOOST_CHECK(!testArgs.IsArgSet("-nob")); + + // The -b option is flagged as negated, and nothing else is + BOOST_CHECK(testArgs.IsArgNegated("-b")); + BOOST_CHECK(!testArgs.IsArgNegated("-a")); + + // Check expected values. + BOOST_CHECK(testArgs.GetBoolArg("-a", false) == true); + BOOST_CHECK(testArgs.GetBoolArg("-b", true) == false); + BOOST_CHECK(testArgs.GetBoolArg("-c", true) == false); + BOOST_CHECK(testArgs.GetBoolArg("-d", false) == true); + BOOST_CHECK(testArgs.GetBoolArg("-e", true) == false); + BOOST_CHECK(testArgs.GetBoolArg("-f", true) == false); +} + +BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) +{ + // Test some awful edge cases that hopefully no user will ever exercise. + TestArgsManager testArgs; + + // Params test + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_ANY); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY); + const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"}; + testArgs.SetupArgs({foo, bar}); + std::string error; + BOOST_CHECK(testArgs.ParseParameters(4, (char**)argv_test, error)); + + // This was passed twice, second one overrides the negative setting. + BOOST_CHECK(!testArgs.IsArgNegated("-foo")); + BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == ""); + + // A double negative is a positive, and not marked as negated. + BOOST_CHECK(!testArgs.IsArgNegated("-bar")); + BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1"); + + // Config test + const char *conf_test = "nofoo=1\nfoo=1\nnobar=0\n"; + BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error)); + testArgs.ReadConfigString(conf_test); + + // This was passed twice, second one overrides the negative setting, + // and the value. + BOOST_CHECK(!testArgs.IsArgNegated("-foo")); + BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "1"); + + // A double negative is a positive, and does not count as negated. + BOOST_CHECK(!testArgs.IsArgNegated("-bar")); + BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1"); + + // Combined test + const char *combo_test_args[] = {"ignored", "-nofoo", "-bar"}; + const char *combo_test_conf = "foo=1\nnobar=1\n"; + BOOST_CHECK(testArgs.ParseParameters(3, (char**)combo_test_args, error)); + testArgs.ReadConfigString(combo_test_conf); + + // Command line overrides, but doesn't erase old setting + BOOST_CHECK(testArgs.IsArgNegated("-foo")); + BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "0"); + BOOST_CHECK(testArgs.GetArgs("-foo").size() == 0); + + // Command line overrides, but doesn't erase old setting + BOOST_CHECK(!testArgs.IsArgNegated("-bar")); + BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == ""); + BOOST_CHECK(testArgs.GetArgs("-bar").size() == 1 + && testArgs.GetArgs("-bar").front() == ""); +} + +BOOST_AUTO_TEST_CASE(util_ReadConfigStream) +{ + const char *str_config = + "a=\n" + "b=1\n" + "ccc=argument\n" + "ccc=multiple\n" + "d=e\n" + "nofff=1\n" + "noggg=0\n" + "h=1\n" + "noh=1\n" + "noi=1\n" + "i=1\n" + "sec1.ccc=extend1\n" + "\n" + "[sec1]\n" + "ccc=extend2\n" + "d=eee\n" + "h=1\n" + "[sec2]\n" + "ccc=extend3\n" + "iii=2\n"; + + TestArgsManager test_args; + LOCK(test_args.cs_args); + const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY); + const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY); + const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY); + const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY); + const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY); + const auto fff = std::make_pair("-fff", ArgsManager::ALLOW_ANY); + const auto ggg = std::make_pair("-ggg", ArgsManager::ALLOW_ANY); + const auto h = std::make_pair("-h", ArgsManager::ALLOW_ANY); + const auto i = std::make_pair("-i", ArgsManager::ALLOW_ANY); + const auto iii = std::make_pair("-iii", ArgsManager::ALLOW_ANY); + test_args.SetupArgs({a, b, ccc, d, e, fff, ggg, h, i, iii}); + + test_args.ReadConfigString(str_config); + // expectation: a, b, ccc, d, fff, ggg, h, i end up in map + // so do sec1.ccc, sec1.d, sec1.h, sec2.ccc, sec2.iii + + BOOST_CHECK(test_args.m_settings.command_line_options.empty()); + BOOST_CHECK(test_args.m_settings.ro_config.size() == 3); + BOOST_CHECK(test_args.m_settings.ro_config[""].size() == 8); + BOOST_CHECK(test_args.m_settings.ro_config["sec1"].size() == 3); + BOOST_CHECK(test_args.m_settings.ro_config["sec2"].size() == 2); + + BOOST_CHECK(test_args.m_settings.ro_config[""].count("a")); + BOOST_CHECK(test_args.m_settings.ro_config[""].count("b")); + BOOST_CHECK(test_args.m_settings.ro_config[""].count("ccc")); + BOOST_CHECK(test_args.m_settings.ro_config[""].count("d")); + BOOST_CHECK(test_args.m_settings.ro_config[""].count("fff")); + BOOST_CHECK(test_args.m_settings.ro_config[""].count("ggg")); + BOOST_CHECK(test_args.m_settings.ro_config[""].count("h")); + BOOST_CHECK(test_args.m_settings.ro_config[""].count("i")); + BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("ccc")); + BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("h")); + BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("ccc")); + BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("iii")); + + BOOST_CHECK(test_args.IsArgSet("-a")); + BOOST_CHECK(test_args.IsArgSet("-b")); + BOOST_CHECK(test_args.IsArgSet("-ccc")); + BOOST_CHECK(test_args.IsArgSet("-d")); + BOOST_CHECK(test_args.IsArgSet("-fff")); + BOOST_CHECK(test_args.IsArgSet("-ggg")); + BOOST_CHECK(test_args.IsArgSet("-h")); + BOOST_CHECK(test_args.IsArgSet("-i")); + BOOST_CHECK(!test_args.IsArgSet("-zzz")); + BOOST_CHECK(!test_args.IsArgSet("-iii")); + + BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), ""); + BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1"); + BOOST_CHECK_EQUAL(test_args.GetArg("-ccc", "xxx"), "argument"); + BOOST_CHECK_EQUAL(test_args.GetArg("-d", "xxx"), "e"); + BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0"); + BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1"); + BOOST_CHECK_EQUAL(test_args.GetArg("-h", "xxx"), "0"); + BOOST_CHECK_EQUAL(test_args.GetArg("-i", "xxx"), "1"); + BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx"); + BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx"); + + for (const bool def : {false, true}) { + BOOST_CHECK(test_args.GetBoolArg("-a", def)); + BOOST_CHECK(test_args.GetBoolArg("-b", def)); + BOOST_CHECK(!test_args.GetBoolArg("-ccc", def)); + BOOST_CHECK(!test_args.GetBoolArg("-d", def)); + BOOST_CHECK(!test_args.GetBoolArg("-fff", def)); + BOOST_CHECK(test_args.GetBoolArg("-ggg", def)); + BOOST_CHECK(!test_args.GetBoolArg("-h", def)); + BOOST_CHECK(test_args.GetBoolArg("-i", def)); + BOOST_CHECK(test_args.GetBoolArg("-zzz", def) == def); + BOOST_CHECK(test_args.GetBoolArg("-iii", def) == def); + } + + BOOST_CHECK(test_args.GetArgs("-a").size() == 1 + && test_args.GetArgs("-a").front() == ""); + BOOST_CHECK(test_args.GetArgs("-b").size() == 1 + && test_args.GetArgs("-b").front() == "1"); + BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2 + && test_args.GetArgs("-ccc").front() == "argument" + && test_args.GetArgs("-ccc").back() == "multiple"); + BOOST_CHECK(test_args.GetArgs("-fff").size() == 0); + BOOST_CHECK(test_args.GetArgs("-nofff").size() == 0); + BOOST_CHECK(test_args.GetArgs("-ggg").size() == 1 + && test_args.GetArgs("-ggg").front() == "1"); + BOOST_CHECK(test_args.GetArgs("-noggg").size() == 0); + BOOST_CHECK(test_args.GetArgs("-h").size() == 0); + BOOST_CHECK(test_args.GetArgs("-noh").size() == 0); + BOOST_CHECK(test_args.GetArgs("-i").size() == 1 + && test_args.GetArgs("-i").front() == "1"); + BOOST_CHECK(test_args.GetArgs("-noi").size() == 0); + BOOST_CHECK(test_args.GetArgs("-zzz").size() == 0); + + BOOST_CHECK(!test_args.IsArgNegated("-a")); + BOOST_CHECK(!test_args.IsArgNegated("-b")); + BOOST_CHECK(!test_args.IsArgNegated("-ccc")); + BOOST_CHECK(!test_args.IsArgNegated("-d")); + BOOST_CHECK(test_args.IsArgNegated("-fff")); + BOOST_CHECK(!test_args.IsArgNegated("-ggg")); + BOOST_CHECK(test_args.IsArgNegated("-h")); // last setting takes precedence + BOOST_CHECK(!test_args.IsArgNegated("-i")); // last setting takes precedence + BOOST_CHECK(!test_args.IsArgNegated("-zzz")); + + // Test sections work + test_args.SelectConfigNetwork("sec1"); + + // same as original + BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), ""); + BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1"); + BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0"); + BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1"); + BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx"); + BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx"); + // d is overridden + BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee"); + // section-specific setting + BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1"); + // section takes priority for multiple values + BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend1"); + // check multiple values works + const std::vector<std::string> sec1_ccc_expected = {"extend1","extend2","argument","multiple"}; + const auto& sec1_ccc_res = test_args.GetArgs("-ccc"); + BOOST_CHECK_EQUAL_COLLECTIONS(sec1_ccc_res.begin(), sec1_ccc_res.end(), sec1_ccc_expected.begin(), sec1_ccc_expected.end()); + + test_args.SelectConfigNetwork("sec2"); + + // same as original + BOOST_CHECK(test_args.GetArg("-a", "xxx") == ""); + BOOST_CHECK(test_args.GetArg("-b", "xxx") == "1"); + BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e"); + BOOST_CHECK(test_args.GetArg("-fff", "xxx") == "0"); + BOOST_CHECK(test_args.GetArg("-ggg", "xxx") == "1"); + BOOST_CHECK(test_args.GetArg("-zzz", "xxx") == "xxx"); + BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0"); + // section-specific setting + BOOST_CHECK(test_args.GetArg("-iii", "xxx") == "2"); + // section takes priority for multiple values + BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend3"); + // check multiple values works + const std::vector<std::string> sec2_ccc_expected = {"extend3","argument","multiple"}; + const auto& sec2_ccc_res = test_args.GetArgs("-ccc"); + BOOST_CHECK_EQUAL_COLLECTIONS(sec2_ccc_res.begin(), sec2_ccc_res.end(), sec2_ccc_expected.begin(), sec2_ccc_expected.end()); + + // Test section only options + + test_args.SetNetworkOnlyArg("-d"); + test_args.SetNetworkOnlyArg("-ccc"); + test_args.SetNetworkOnlyArg("-h"); + + test_args.SelectConfigNetwork(CBaseChainParams::MAIN); + BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e"); + BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2); + BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0"); + + test_args.SelectConfigNetwork("sec1"); + BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee"); + BOOST_CHECK(test_args.GetArgs("-d").size() == 1); + BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2); + BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1"); + + test_args.SelectConfigNetwork("sec2"); + BOOST_CHECK(test_args.GetArg("-d", "xxx") == "xxx"); + BOOST_CHECK(test_args.GetArgs("-d").size() == 0); + BOOST_CHECK(test_args.GetArgs("-ccc").size() == 1); + BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0"); +} + +BOOST_AUTO_TEST_CASE(util_GetArg) +{ + TestArgsManager testArgs; + LOCK(testArgs.cs_args); + testArgs.m_settings.command_line_options.clear(); + testArgs.m_settings.command_line_options["strtest1"] = {"string..."}; + // strtest2 undefined on purpose + testArgs.m_settings.command_line_options["inttest1"] = {"12345"}; + testArgs.m_settings.command_line_options["inttest2"] = {"81985529216486895"}; + // inttest3 undefined on purpose + testArgs.m_settings.command_line_options["booltest1"] = {""}; + // booltest2 undefined on purpose + testArgs.m_settings.command_line_options["booltest3"] = {"0"}; + testArgs.m_settings.command_line_options["booltest4"] = {"1"}; + + // priorities + testArgs.m_settings.command_line_options["pritest1"] = {"a", "b"}; + testArgs.m_settings.ro_config[""]["pritest2"] = {"a", "b"}; + testArgs.m_settings.command_line_options["pritest3"] = {"a"}; + testArgs.m_settings.ro_config[""]["pritest3"] = {"b"}; + testArgs.m_settings.command_line_options["pritest4"] = {"a","b"}; + testArgs.m_settings.ro_config[""]["pritest4"] = {"c","d"}; + + BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string..."); + BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); + BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest1", -1), 12345); + BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest2", -1), 81985529216486895LL); + BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest3", -1), -1); + BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true); + BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest2", false), false); + BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest3", false), false); + BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest4", false), true); + + BOOST_CHECK_EQUAL(testArgs.GetArg("pritest1", "default"), "b"); + BOOST_CHECK_EQUAL(testArgs.GetArg("pritest2", "default"), "a"); + BOOST_CHECK_EQUAL(testArgs.GetArg("pritest3", "default"), "a"); + BOOST_CHECK_EQUAL(testArgs.GetArg("pritest4", "default"), "b"); +} + +BOOST_AUTO_TEST_CASE(util_GetChainName) +{ + TestArgsManager test_args; + const auto testnet = std::make_pair("-testnet", ArgsManager::ALLOW_ANY); + const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_ANY); + test_args.SetupArgs({testnet, regtest}); + + const char* argv_testnet[] = {"cmd", "-testnet"}; + const char* argv_regtest[] = {"cmd", "-regtest"}; + const char* argv_test_no_reg[] = {"cmd", "-testnet", "-noregtest"}; + const char* argv_both[] = {"cmd", "-testnet", "-regtest"}; + + // equivalent to "-testnet" + // regtest in testnet section is ignored + const char* testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1"; + std::string error; + + BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error)); + BOOST_CHECK_EQUAL(test_args.GetChainName(), "main"); + + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error)); + BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); + + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error)); + BOOST_CHECK_EQUAL(test_args.GetChainName(), "regtest"); + + BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_test_no_reg, error)); + BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); + + BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error)); + BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); + + BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error)); + test_args.ReadConfigString(testnetconf); + BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); + + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error)); + test_args.ReadConfigString(testnetconf); + BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); + + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error)); + test_args.ReadConfigString(testnetconf); + BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); + + BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_test_no_reg, error)); + test_args.ReadConfigString(testnetconf); + BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); + + BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error)); + test_args.ReadConfigString(testnetconf); + BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); + + // check setting the network to test (and thus making + // [test] regtest=1 potentially relevant) doesn't break things + test_args.SelectConfigNetwork("test"); + + BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error)); + test_args.ReadConfigString(testnetconf); + BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); + + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error)); + test_args.ReadConfigString(testnetconf); + BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); + + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error)); + test_args.ReadConfigString(testnetconf); + BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); + + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_test_no_reg, error)); + test_args.ReadConfigString(testnetconf); + BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); + + BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error)); + test_args.ReadConfigString(testnetconf); + BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); +} + +// Test different ways settings can be merged, and verify results. This test can +// be used to confirm that updates to settings code don't change behavior +// unintentionally. +// +// The test covers: +// +// - Combining different setting actions. Possible actions are: configuring a +// setting, negating a setting (adding "-no" prefix), and configuring/negating +// settings in a network section (adding "main." or "test." prefixes). +// +// - Combining settings from command line arguments and a config file. +// +// - Combining SoftSet and ForceSet calls. +// +// - Testing "main" and "test" network values to make sure settings from network +// sections are applied and to check for mainnet-specific behaviors like +// inheriting settings from the default section. +// +// - Testing network-specific settings like "-wallet", that may be ignored +// outside a network section, and non-network specific settings like "-server" +// that aren't sensitive to the network. +// +struct ArgsMergeTestingSetup : public BasicTestingSetup { + //! Max number of actions to sequence together. Can decrease this when + //! debugging to make test results easier to understand. + static constexpr int MAX_ACTIONS = 3; + + enum Action { NONE, SET, NEGATE, SECTION_SET, SECTION_NEGATE }; + using ActionList = Action[MAX_ACTIONS]; + + //! Enumerate all possible test configurations. + template <typename Fn> + void ForEachMergeSetup(Fn&& fn) + { + ActionList arg_actions = {}; + // command_line_options do not have sections. Only iterate over SET and NEGATE + ForEachNoDup(arg_actions, SET, NEGATE, [&] { + ActionList conf_actions = {}; + ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] { + for (bool soft_set : {false, true}) { + for (bool force_set : {false, true}) { + for (const std::string& section : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) { + for (const std::string& network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) { + for (bool net_specific : {false, true}) { + fn(arg_actions, conf_actions, soft_set, force_set, section, network, net_specific); + } + } + } + } + } + }); + }); + } + + //! Translate actions into a list of <key>=<value> setting strings. + std::vector<std::string> GetValues(const ActionList& actions, + const std::string& section, + const std::string& name, + const std::string& value_prefix) + { + std::vector<std::string> values; + int suffix = 0; + for (Action action : actions) { + if (action == NONE) break; + std::string prefix; + if (action == SECTION_SET || action == SECTION_NEGATE) prefix = section + "."; + if (action == SET || action == SECTION_SET) { + for (int i = 0; i < 2; ++i) { + values.push_back(prefix + name + "=" + value_prefix + ToString(++suffix)); + } + } + if (action == NEGATE || action == SECTION_NEGATE) { + values.push_back(prefix + "no" + name + "=1"); + } + } + return values; + } +}; + +// Regression test covering different ways config settings can be merged. The +// test parses and merges settings, representing the results as strings that get +// compared against an expected hash. To debug, the result strings can be dumped +// to a file (see comments below). +BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup) +{ + CHash256 out_sha; + FILE* out_file = nullptr; + if (const char* out_path = getenv("ARGS_MERGE_TEST_OUT")) { + out_file = fsbridge::fopen(out_path, "w"); + if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed"); + } + + ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool soft_set, bool force_set, + const std::string& section, const std::string& network, bool net_specific) { + TestArgsManager parser; + LOCK(parser.cs_args); + + std::string desc = "net="; + desc += network; + parser.m_network = network; + + const std::string& name = net_specific ? "wallet" : "server"; + const std::string key = "-" + name; + parser.AddArg(key, name, ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + if (net_specific) parser.SetNetworkOnlyArg(key); + + auto args = GetValues(arg_actions, section, name, "a"); + std::vector<const char*> argv = {"ignored"}; + for (auto& arg : args) { + arg.insert(0, "-"); + desc += " "; + desc += arg; + argv.push_back(arg.c_str()); + } + std::string error; + BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error)); + BOOST_CHECK_EQUAL(error, ""); + + std::string conf; + for (auto& conf_val : GetValues(conf_actions, section, name, "c")) { + desc += " "; + desc += conf_val; + conf += conf_val; + conf += "\n"; + } + std::istringstream conf_stream(conf); + BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error)); + BOOST_CHECK_EQUAL(error, ""); + + if (soft_set) { + desc += " soft"; + parser.SoftSetArg(key, "soft1"); + parser.SoftSetArg(key, "soft2"); + } + + if (force_set) { + desc += " force"; + parser.ForceSetArg(key, "force1"); + parser.ForceSetArg(key, "force2"); + } + + desc += " || "; + + if (!parser.IsArgSet(key)) { + desc += "unset"; + BOOST_CHECK(!parser.IsArgNegated(key)); + BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "default"); + BOOST_CHECK(parser.GetArgs(key).empty()); + } else if (parser.IsArgNegated(key)) { + desc += "negated"; + BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "0"); + BOOST_CHECK(parser.GetArgs(key).empty()); + } else { + desc += parser.GetArg(key, "default"); + desc += " |"; + for (const auto& arg : parser.GetArgs(key)) { + desc += " "; + desc += arg; + } + } + + std::set<std::string> ignored = parser.GetUnsuitableSectionOnlyArgs(); + if (!ignored.empty()) { + desc += " | ignored"; + for (const auto& arg : ignored) { + desc += " "; + desc += arg; + } + } + + desc += "\n"; + + out_sha.Write(MakeUCharSpan(desc)); + if (out_file) { + BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size()); + } + }); + + if (out_file) { + if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed"); + out_file = nullptr; + } + + unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE]; + out_sha.Finalize(out_sha_bytes); + std::string out_sha_hex = HexStr(out_sha_bytes); + + // If check below fails, should manually dump the results with: + // + // ARGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ArgsMerge + // + // And verify diff against previous results to make sure the changes are expected. + // + // Results file is formatted like: + // + // <input> || <IsArgSet/IsArgNegated/GetArg output> | <GetArgs output> | <GetUnsuitable output> + BOOST_CHECK_EQUAL(out_sha_hex, "d1e436c1cd510d0ec44d5205d4b4e3bee6387d316e0075c58206cb16603f3d82"); +} + +// Similar test as above, but for ArgsManager::GetChainName function. +struct ChainMergeTestingSetup : public BasicTestingSetup { + static constexpr int MAX_ACTIONS = 2; + + enum Action { NONE, ENABLE_TEST, DISABLE_TEST, NEGATE_TEST, ENABLE_REG, DISABLE_REG, NEGATE_REG }; + using ActionList = Action[MAX_ACTIONS]; + + //! Enumerate all possible test configurations. + template <typename Fn> + void ForEachMergeSetup(Fn&& fn) + { + ActionList arg_actions = {}; + ForEachNoDup(arg_actions, ENABLE_TEST, NEGATE_REG, [&] { + ActionList conf_actions = {}; + ForEachNoDup(conf_actions, ENABLE_TEST, NEGATE_REG, [&] { fn(arg_actions, conf_actions); }); + }); + } +}; + +BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup) +{ + CHash256 out_sha; + FILE* out_file = nullptr; + if (const char* out_path = getenv("CHAIN_MERGE_TEST_OUT")) { + out_file = fsbridge::fopen(out_path, "w"); + if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed"); + } + + ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions) { + TestArgsManager parser; + LOCK(parser.cs_args); + parser.AddArg("-regtest", "regtest", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + parser.AddArg("-testnet", "testnet", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + + auto arg = [](Action action) { return action == ENABLE_TEST ? "-testnet=1" : + action == DISABLE_TEST ? "-testnet=0" : + action == NEGATE_TEST ? "-notestnet=1" : + action == ENABLE_REG ? "-regtest=1" : + action == DISABLE_REG ? "-regtest=0" : + action == NEGATE_REG ? "-noregtest=1" : nullptr; }; + + std::string desc; + std::vector<const char*> argv = {"ignored"}; + for (Action action : arg_actions) { + const char* argstr = arg(action); + if (!argstr) break; + argv.push_back(argstr); + desc += " "; + desc += argv.back(); + } + std::string error; + BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error)); + BOOST_CHECK_EQUAL(error, ""); + + std::string conf; + for (Action action : conf_actions) { + const char* argstr = arg(action); + if (!argstr) break; + desc += " "; + desc += argstr + 1; + conf += argstr + 1; + conf += "\n"; + } + std::istringstream conf_stream(conf); + BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error)); + BOOST_CHECK_EQUAL(error, ""); + + desc += " || "; + try { + desc += parser.GetChainName(); + } catch (const std::runtime_error& e) { + desc += "error: "; + desc += e.what(); + } + desc += "\n"; + + out_sha.Write(MakeUCharSpan(desc)); + if (out_file) { + BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size()); + } + }); + + if (out_file) { + if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed"); + out_file = nullptr; + } + + unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE]; + out_sha.Finalize(out_sha_bytes); + std::string out_sha_hex = HexStr(out_sha_bytes); + + // If check below fails, should manually dump the results with: + // + // CHAIN_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ChainMerge + // + // And verify diff against previous results to make sure the changes are expected. + // + // Results file is formatted like: + // + // <input> || <output> + BOOST_CHECK_EQUAL(out_sha_hex, "f263493e300023b6509963887444c41386f44b63bc30047eb8402e8c1144854c"); +} + +BOOST_AUTO_TEST_CASE(util_ReadWriteSettings) +{ + // Test writing setting. + TestArgsManager args1; + args1.ForceSetArg("-datadir", fs::PathToString(m_path_root)); + args1.LockSettings([&](util::Settings& settings) { settings.rw_settings["name"] = "value"; }); + args1.WriteSettingsFile(); + + // Test reading setting. + TestArgsManager args2; + args2.ForceSetArg("-datadir", fs::PathToString(m_path_root)); + args2.ReadSettingsFile(); + args2.LockSettings([&](util::Settings& settings) { BOOST_CHECK_EQUAL(settings.rw_settings["name"].get_str(), "value"); }); + + // Test error logging, and remove previously written setting. + { + ASSERT_DEBUG_LOG("Failed renaming settings file"); + fs::remove(args1.GetDataDirBase() / "settings.json"); + fs::create_directory(args1.GetDataDirBase() / "settings.json"); + args2.WriteSettingsFile(); + fs::remove(args1.GetDataDirBase() / "settings.json"); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index 7668940cbc..f3c29cd6b8 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -11,6 +11,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/net.h> #include <test/util/setup_common.h> #include <time.h> #include <util/asmap.h> diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp index b2969ecdc0..d10f4586b4 100644 --- a/src/test/fuzz/banman.cpp +++ b/src/test/fuzz/banman.cpp @@ -8,6 +8,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/net.h> #include <test/util/setup_common.h> #include <util/readwritefile.h> #include <util/system.h> diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp index 4406779015..e8b10a0ad0 100644 --- a/src/test/fuzz/connman.cpp +++ b/src/test/fuzz/connman.cpp @@ -11,6 +11,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/net.h> #include <test/util/setup_common.h> #include <util/system.h> #include <util/translation.h> diff --git a/src/test/fuzz/i2p.cpp b/src/test/fuzz/i2p.cpp index fb6d23aca5..b6e3ca07e2 100644 --- a/src/test/fuzz/i2p.cpp +++ b/src/test/fuzz/i2p.cpp @@ -9,8 +9,8 @@ #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> #include <test/util/setup_common.h> -#include <threadinterrupt.h> #include <util/system.h> +#include <util/threadinterrupt.h> void initialize_i2p() { diff --git a/src/test/fuzz/netaddress.cpp b/src/test/fuzz/netaddress.cpp index 35e6688c61..2022f16a48 100644 --- a/src/test/fuzz/netaddress.cpp +++ b/src/test/fuzz/netaddress.cpp @@ -5,7 +5,7 @@ #include <netaddress.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> -#include <test/fuzz/util.h> +#include <test/fuzz/util/net.h> #include <cassert> #include <cstdint> diff --git a/src/test/fuzz/netbase_dns_lookup.cpp b/src/test/fuzz/netbase_dns_lookup.cpp index 31ea31744a..39d4935126 100644 --- a/src/test/fuzz/netbase_dns_lookup.cpp +++ b/src/test/fuzz/netbase_dns_lookup.cpp @@ -6,7 +6,7 @@ #include <netbase.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> -#include <test/fuzz/util.h> +#include <test/fuzz/util/net.h> #include <cstdint> #include <string> diff --git a/src/test/fuzz/policy_estimator.cpp b/src/test/fuzz/policy_estimator.cpp index a3d57dbdd5..3788c36455 100644 --- a/src/test/fuzz/policy_estimator.cpp +++ b/src/test/fuzz/policy_estimator.cpp @@ -11,6 +11,7 @@ #include <test/fuzz/util/mempool.h> #include <test/util/setup_common.h> #include <txmempool.h> +#include <txmempool_entry.h> #include <cstdint> #include <optional> diff --git a/src/test/fuzz/string.cpp b/src/test/fuzz/string.cpp index 94399faf04..f6373351d8 100644 --- a/src/test/fuzz/string.cpp +++ b/src/test/fuzz/string.cpp @@ -4,6 +4,7 @@ #include <blockfilter.h> #include <clientversion.h> +#include <common/url.h> #include <logging.h> #include <netaddress.h> #include <netbase.h> @@ -27,7 +28,6 @@ #include <util/string.h> #include <util/system.h> #include <util/translation.h> -#include <util/url.h> #include <version.h> #include <cstdint> diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp index d495a6bfe3..73ceb94b14 100644 --- a/src/test/fuzz/util.cpp +++ b/src/test/fuzz/util.cpp @@ -4,6 +4,7 @@ #include <consensus/amount.h> #include <net_processing.h> +#include <netaddress.h> #include <netmessagemaker.h> #include <pubkey.h> #include <test/fuzz/util.h> @@ -507,28 +508,6 @@ bool ContainsSpentInput(const CTransaction& tx, const CCoinsViewCache& inputs) n return false; } -CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept -{ - const Network network = fuzzed_data_provider.PickValueInArray({Network::NET_IPV4, Network::NET_IPV6, Network::NET_INTERNAL, Network::NET_ONION}); - CNetAddr net_addr; - if (network == Network::NET_IPV4) { - in_addr v4_addr = {}; - v4_addr.s_addr = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); - net_addr = CNetAddr{v4_addr}; - } else if (network == Network::NET_IPV6) { - if (fuzzed_data_provider.remaining_bytes() >= 16) { - in6_addr v6_addr = {}; - memcpy(v6_addr.s6_addr, fuzzed_data_provider.ConsumeBytes<uint8_t>(16).data(), 16); - net_addr = CNetAddr{v6_addr, fuzzed_data_provider.ConsumeIntegral<uint32_t>()}; - } - } else if (network == Network::NET_INTERNAL) { - net_addr.SetInternal(fuzzed_data_provider.ConsumeBytesAsString(32)); - } else if (network == Network::NET_ONION) { - net_addr.SetSpecial(fuzzed_data_provider.ConsumeBytesAsString(32)); - } - return net_addr; -} - CAddress ConsumeAddress(FuzzedDataProvider& fuzzed_data_provider) noexcept { return {ConsumeService(fuzzed_data_provider), ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS), NodeSeconds{std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<uint32_t>()}}}; diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index dfe4855326..ecd6eead3f 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -22,6 +22,7 @@ #include <streams.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> +#include <test/fuzz/util/net.h> #include <test/util/net.h> #include <uint256.h> #include <version.h> @@ -283,8 +284,6 @@ inline void SetFuzzedErrNo(FuzzedDataProvider& fuzzed_data_provider) noexcept return result; } -CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept; - inline CSubNet ConsumeSubNet(FuzzedDataProvider& fuzzed_data_provider) noexcept { return {ConsumeNetAddr(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<uint8_t>()}; diff --git a/src/test/fuzz/util/mempool.cpp b/src/test/fuzz/util/mempool.cpp index d0053f77d2..ac83f6ca21 100644 --- a/src/test/fuzz/util/mempool.cpp +++ b/src/test/fuzz/util/mempool.cpp @@ -7,7 +7,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/util.h> #include <test/fuzz/util/mempool.h> -#include <txmempool.h> +#include <txmempool_entry.h> #include <limits> diff --git a/src/test/fuzz/util/net.cpp b/src/test/fuzz/util/net.cpp new file mode 100644 index 0000000000..f8e996cfa5 --- /dev/null +++ b/src/test/fuzz/util/net.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2009-2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <compat/compat.h> +#include <netaddress.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <util/strencodings.h> + +#include <cstdint> +#include <vector> + +CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept +{ + const Network network = fuzzed_data_provider.PickValueInArray({Network::NET_IPV4, Network::NET_IPV6, Network::NET_INTERNAL, Network::NET_ONION}); + CNetAddr net_addr; + if (network == Network::NET_IPV4) { + in_addr v4_addr = {}; + v4_addr.s_addr = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); + net_addr = CNetAddr{v4_addr}; + } else if (network == Network::NET_IPV6) { + if (fuzzed_data_provider.remaining_bytes() >= 16) { + in6_addr v6_addr = {}; + memcpy(v6_addr.s6_addr, fuzzed_data_provider.ConsumeBytes<uint8_t>(16).data(), 16); + net_addr = CNetAddr{v6_addr, fuzzed_data_provider.ConsumeIntegral<uint32_t>()}; + } + } else if (network == Network::NET_INTERNAL) { + net_addr.SetInternal(fuzzed_data_provider.ConsumeBytesAsString(32)); + } else if (network == Network::NET_ONION) { + auto pub_key{fuzzed_data_provider.ConsumeBytes<uint8_t>(ADDR_TORV3_SIZE)}; + pub_key.resize(ADDR_TORV3_SIZE); + const bool ok{net_addr.SetSpecial(OnionToString(pub_key))}; + assert(ok); + } + return net_addr; +} diff --git a/src/test/fuzz/util/net.h b/src/test/fuzz/util/net.h new file mode 100644 index 0000000000..d81adab650 --- /dev/null +++ b/src/test/fuzz/util/net.h @@ -0,0 +1,14 @@ +// Copyright (c) 2009-2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_TEST_FUZZ_UTIL_NET_H +#define BITCOIN_TEST_FUZZ_UTIL_NET_H + +#include <netaddress.h> + +class FuzzedDataProvider; + +CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept; + +#endif // BITCOIN_TEST_FUZZ_UTIL_NET_H diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index 70dd137e22..3643b80d5f 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -429,7 +429,7 @@ BOOST_AUTO_TEST_CASE(logargs) const auto okaylog = std::make_pair("-okaylog", ArgsManager::ALLOW_ANY); const auto dontlog = std::make_pair("-dontlog", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE); SetupArgs(local_args, {okaylog_bool, okaylog_negbool, okaylog, dontlog}); - ResetArgs(local_args, "-okaylog-bool -nookaylog-negbool -okaylog=public -dontlog=private"); + ResetArgs(local_args, "-okaylog-bool -nookaylog-negbool -okaylog=public -dontlog=private42"); // Everything logged to debug.log will also append to str std::string str; @@ -447,7 +447,7 @@ BOOST_AUTO_TEST_CASE(logargs) BOOST_CHECK(str.find("Command-line arg: okaylog-negbool=false") != std::string::npos); BOOST_CHECK(str.find("Command-line arg: okaylog=\"public\"") != std::string::npos); BOOST_CHECK(str.find("dontlog=****") != std::string::npos); - BOOST_CHECK(str.find("private") == std::string::npos); + BOOST_CHECK(str.find("private42") == std::string::npos); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp index 9da1ee11f9..7b1bf11cfb 100644 --- a/src/test/i2p_tests.cpp +++ b/src/test/i2p_tests.cpp @@ -8,8 +8,8 @@ #include <test/util/logging.h> #include <test/util/net.h> #include <test/util/setup_common.h> -#include <threadinterrupt.h> #include <util/system.h> +#include <util/threadinterrupt.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index 393311e4e4..b12aac6299 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -165,7 +165,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) tx5.vout.resize(1); tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx5.vout[0].nValue = 11 * COIN; - entry.nTime = 1; + entry.time = NodeSeconds{1s}; pool.addUnchecked(entry.Fee(10000LL).FromTx(tx5)); BOOST_CHECK_EQUAL(pool.size(), 5U); @@ -225,7 +225,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) tx8.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx8.vout[0].nValue = 10 * COIN; setAncestors.insert(pool.mapTx.find(tx7.GetHash())); - pool.addUnchecked(entry.Fee(0LL).Time(2).FromTx(tx8), setAncestors); + pool.addUnchecked(entry.Fee(0LL).Time(NodeSeconds{2s}).FromTx(tx8), setAncestors); // Now tx8 should be sorted low, but tx6/tx both high sortedOrder.insert(sortedOrder.begin(), tx8.GetHash().ToString()); @@ -239,7 +239,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) tx9.vout.resize(1); tx9.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx9.vout[0].nValue = 1 * COIN; - pool.addUnchecked(entry.Fee(0LL).Time(3).FromTx(tx9), setAncestors); + pool.addUnchecked(entry.Fee(0LL).Time(NodeSeconds{3s}).FromTx(tx9), setAncestors); // tx9 should be sorted low BOOST_CHECK_EQUAL(pool.size(), 9U); @@ -262,7 +262,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) tx10.vout[0].nValue = 10 * COIN; setAncestorsCalculated.clear(); - BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200'000LL).Time(4).FromTx(tx10), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true); + BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200'000LL).Time(NodeSeconds{4s}).FromTx(tx10), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true); BOOST_CHECK(setAncestorsCalculated == setAncestors); pool.addUnchecked(entry.FromTx(tx10), setAncestors); diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 9bc29e3599..ba43f1926b 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -116,19 +116,19 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const tx.vout[0].nValue = 5000000000LL - 1000; // This tx has a low fee: 1000 satoshis uint256 hashParentTx = tx.GetHash(); // save this txid for later use - tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); // This tx has a medium fee: 10000 satoshis tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vout[0].nValue = 5000000000LL - 10000; uint256 hashMediumFeeTx = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); // This tx has a high fee, but depends on the first transaction tx.vin[0].prevout.hash = hashParentTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 50k satoshi fee uint256 hashHighFeeTx = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)); std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 4U); @@ -239,7 +239,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: hash = tx.GetHash(); bool spendsCoinbase = i == 0; // only first tx spends coinbase // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails - tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); tx.vin[0].prevout.hash = hash; } @@ -257,7 +257,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: hash = tx.GetHash(); bool spendsCoinbase = i == 0; // only first tx spends coinbase // If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes - tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx)); tx.vin[0].prevout.hash = hash; } BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); @@ -281,7 +281,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: tx.vout[0].nValue -= LOWFEE; hash = tx.GetHash(); bool spendsCoinbase = i == 0; // only first tx spends coinbase - tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); tx.vin[0].prevout.hash = hash; } BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); @@ -293,7 +293,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: // orphan in tx_mempool, template creation fails hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).FromTx(tx)); BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); } @@ -306,7 +306,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE; hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); tx.vin[0].prevout.hash = hash; tx.vin.resize(2); tx.vin[1].scriptSig = CScript() << OP_1; @@ -314,7 +314,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: tx.vin[1].prevout.n = 0; tx.vout[0].nValue = tx.vout[0].nValue + BLOCKSUBSIDY - HIGHERFEE; // First txn output + fresh coinbase - new txn fee hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(HIGHERFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); } @@ -329,7 +329,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: tx.vout[0].nValue = 0; hash = tx.GetHash(); // give it a fee so it'll get mined - tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)); // Should throw bad-cb-multiple BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple")); } @@ -344,10 +344,10 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE; tx.vout[0].scriptPubKey = CScript() << OP_1; hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); tx.vout[0].scriptPubKey = CScript() << OP_2; hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); } @@ -390,12 +390,12 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: CScript script = CScript() << OP_0; tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script)); hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); tx.vin[0].prevout.hash = hash; tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end()); tx.vout[0].nValue -= LOWFEE; hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)); // Should throw block-validation-failed BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed")); @@ -432,7 +432,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: tx.vout[0].scriptPubKey = CScript() << OP_1; tx.nLockTime = 0; hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail @@ -446,7 +446,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1-m_node.chainman->ActiveChain()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); // txFirst[1] is the 3rd block prevheights[0] = baseheight + 2; hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); + tx_mempool.addUnchecked(entry.Time(Now<NodeSeconds>()).FromTx(tx)); BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail @@ -469,7 +469,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: prevheights[0] = baseheight + 3; tx.nLockTime = m_node.chainman->ActiveChain().Tip()->nHeight + 1; hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); + tx_mempool.addUnchecked(entry.Time(Now<NodeSeconds>()).FromTx(tx)); BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block @@ -480,7 +480,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: prevheights.resize(1); prevheights[0] = baseheight + 4; hash = tx.GetHash(); - tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); + tx_mempool.addUnchecked(entry.Time(Now<NodeSeconds>()).FromTx(tx)); BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later @@ -535,7 +535,7 @@ void MinerTestingSetup::TestPrioritisedMining(const CScript& scriptPubKey, const tx.vout.resize(1); tx.vout[0].nValue = 5000000000LL; // 0 fee uint256 hashFreePrioritisedTx = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(0).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(0).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); tx_mempool.PrioritiseTransaction(hashFreePrioritisedTx, 5 * COIN); tx.vin[0].prevout.hash = txFirst[1]->GetHash(); @@ -543,20 +543,20 @@ void MinerTestingSetup::TestPrioritisedMining(const CScript& scriptPubKey, const tx.vout[0].nValue = 5000000000LL - 1000; // This tx has a low fee: 1000 satoshis uint256 hashParentTx = tx.GetHash(); // save this txid for later use - tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); // This tx has a medium fee: 10000 satoshis tx.vin[0].prevout.hash = txFirst[2]->GetHash(); tx.vout[0].nValue = 5000000000LL - 10000; uint256 hashMediumFeeTx = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)); tx_mempool.PrioritiseTransaction(hashMediumFeeTx, -5 * COIN); // This tx also has a low fee, but is prioritised tx.vin[0].prevout.hash = hashParentTx; tx.vout[0].nValue = 5000000000LL - 1000 - 1000; // 1000 satoshi fee uint256 hashPrioritsedChild = tx.GetHash(); - tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)); tx_mempool.PrioritiseTransaction(hashPrioritsedChild, 2 * COIN); // Test that transaction selection properly updates ancestor fee calculations as prioritised diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp index b652dc44c0..76852d66f7 100644 --- a/src/test/policyestimator_tests.cpp +++ b/src/test/policyestimator_tests.cpp @@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) for (int k = 0; k < 4; k++) { // add 4 fee txs tx.vin[0].prevout.n = 10000*blocknum+100*j+k; // make transaction unique uint256 hash = tx.GetHash(); - mpool.addUnchecked(entry.Fee(feeV[j]).Time(GetTime()).Height(blocknum).FromTx(tx)); + mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx)); txHashes[j].push_back(hash); } } @@ -130,7 +130,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) for (int k = 0; k < 4; k++) { // add 4 fee txs tx.vin[0].prevout.n = 10000*blocknum+100*j+k; uint256 hash = tx.GetHash(); - mpool.addUnchecked(entry.Fee(feeV[j]).Time(GetTime()).Height(blocknum).FromTx(tx)); + mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx)); txHashes[j].push_back(hash); } } @@ -165,7 +165,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) for (int k = 0; k < 4; k++) { // add 4 fee txs tx.vin[0].prevout.n = 10000*blocknum+100*j+k; uint256 hash = tx.GetHash(); - mpool.addUnchecked(entry.Fee(feeV[j]).Time(GetTime()).Height(blocknum).FromTx(tx)); + mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx)); CTransactionRef ptx = mpool.get(hash); if (ptx) block.push_back(ptx); diff --git a/src/test/sock_tests.cpp b/src/test/sock_tests.cpp index 8376ec1a68..5bea08c254 100644 --- a/src/test/sock_tests.cpp +++ b/src/test/sock_tests.cpp @@ -4,9 +4,9 @@ #include <compat/compat.h> #include <test/util/setup_common.h> -#include <threadinterrupt.h> #include <util/sock.h> #include <util/system.h> +#include <util/threadinterrupt.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 0925e2e9ee..b1b262eade 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file) BOOST_CHECK(false); } catch (const std::exception& e) { BOOST_CHECK(strstr(e.what(), - "Read attempted past buffer limit") != nullptr); + "Attempt to position past buffer limit") != nullptr); } // The default argument removes the limit completely. BOOST_CHECK(bf.SetLimit()); @@ -322,7 +322,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file) BOOST_CHECK(!bf.SetPos(0)); // But we should now be positioned at least as far back as allowed // by the rewind window (relative to our farthest read position, 40). - BOOST_CHECK(bf.GetPos() <= 30); + BOOST_CHECK(bf.GetPos() <= 30U); // We can explicitly close the file, or the destructor will do it. bf.fclose(); @@ -330,6 +330,55 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file) fs::remove(streams_test_filename); } +BOOST_AUTO_TEST_CASE(streams_buffered_file_skip) +{ + fs::path streams_test_filename = m_args.GetDataDirBase() / "streams_test_tmp"; + FILE* file = fsbridge::fopen(streams_test_filename, "w+b"); + // The value at each offset is the byte offset (e.g. byte 1 in the file has the value 0x01). + for (uint8_t j = 0; j < 40; ++j) { + fwrite(&j, 1, 1, file); + } + rewind(file); + + // The buffer is 25 bytes, allow rewinding 10 bytes. + CBufferedFile bf(file, 25, 10, 222, 333); + + uint8_t i; + // This is like bf >> (7-byte-variable), in that it will cause data + // to be read from the file into memory, but it's not copied to us. + bf.SkipTo(7); + BOOST_CHECK_EQUAL(bf.GetPos(), 7U); + bf >> i; + BOOST_CHECK_EQUAL(i, 7); + + // The bytes in the buffer up to offset 7 are valid and can be read. + BOOST_CHECK(bf.SetPos(0)); + bf >> i; + BOOST_CHECK_EQUAL(i, 0); + bf >> i; + BOOST_CHECK_EQUAL(i, 1); + + bf.SkipTo(11); + bf >> i; + BOOST_CHECK_EQUAL(i, 11); + + // SkipTo() honors the transfer limit; we can't position beyond the limit. + bf.SetLimit(13); + try { + bf.SkipTo(14); + BOOST_CHECK(false); + } catch (const std::exception& e) { + BOOST_CHECK(strstr(e.what(), "Attempt to position past buffer limit") != nullptr); + } + + // We can position exactly to the transfer limit. + bf.SkipTo(13); + BOOST_CHECK_EQUAL(bf.GetPos(), 13U); + + bf.fclose(); + fs::remove(streams_test_filename); +} + BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) { // Make this test deterministic. @@ -361,7 +410,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) // sizes; the boundaries of the objects can interact arbitrarily // with the CBufferFile's internal buffer. These first three // cases simulate objects of various sizes (1, 2, 5 bytes). - switch (InsecureRandRange(5)) { + switch (InsecureRandRange(6)) { case 0: { uint8_t a[1]; if (currentPos + 1 > fileSize) @@ -399,6 +448,16 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) break; } case 3: { + // SkipTo is similar to the "read" cases above, except + // we don't receive the data. + size_t skip_length{static_cast<size_t>(InsecureRandRange(5))}; + if (currentPos + skip_length > fileSize) continue; + bf.SetLimit(currentPos + skip_length); + bf.SkipTo(currentPos + skip_length); + currentPos += skip_length; + break; + } + case 4: { // Find a byte value (that is at or ahead of the current position). size_t find = currentPos + InsecureRandRange(8); if (find >= fileSize) @@ -415,7 +474,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) currentPos++; break; } - case 4: { + case 5: { size_t requestPos = InsecureRandRange(maxPos + 4); bool okay = bf.SetPos(requestPos); // The new position may differ from the requested position diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp index 11f4be7fef..d5b65b9c08 100644 --- a/src/test/system_tests.cpp +++ b/src/test/system_tests.cpp @@ -51,15 +51,9 @@ BOOST_AUTO_TEST_CASE(run_command) } { // An invalid command is handled by Boost -#ifdef WIN32 - const std::string expected{"The system cannot find the file specified."}; -#else - const std::string expected{"No such file or directory"}; -#endif BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), boost::process::process_error, [&](const boost::process::process_error& e) { - const std::string what(e.what()); - BOOST_CHECK(what.find("RunCommandParseJSON error:") == std::string::npos); - BOOST_CHECK(what.find(expected) != std::string::npos); + BOOST_CHECK(std::string(e.what()).find("RunCommandParseJSON error:") == std::string::npos); + BOOST_CHECK_EQUAL(e.code().value(), 2); return true; }); } diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 9f29342b10..a7ca97222a 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -9,6 +9,7 @@ #include <addrman.h> #include <banman.h> #include <chainparams.h> +#include <common/url.h> #include <consensus/consensus.h> #include <consensus/params.h> #include <consensus/validation.h> @@ -40,13 +41,13 @@ #include <timedata.h> #include <txdb.h> #include <txmempool.h> +#include <txmempool_entry.h> #include <util/strencodings.h> #include <util/string.h> #include <util/thread.h> #include <util/threadnames.h> #include <util/time.h> #include <util/translation.h> -#include <util/url.h> #include <util/vector.h> #include <validation.h> #include <validationinterface.h> @@ -146,7 +147,6 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve Assert(InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes)); m_node.chain = interfaces::MakeChain(m_node); - fCheckBlockIndex = true; static bool noui_connected = false; if (!noui_connected) { noui_connect(); @@ -181,14 +181,13 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve const ChainstateManager::Options chainman_opts{ .chainparams = chainparams, .adjusted_time_callback = GetAdjustedTime, + .check_block_index = true, }; m_node.chainman = std::make_unique<ChainstateManager>(chainman_opts); m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(m_cache_sizes.block_tree_db, true); - // Start script-checking threads. Set g_parallel_script_checks to true so they are used. constexpr int script_check_threads = 2; StartScriptCheckWorkerThreads(script_check_threads); - g_parallel_script_checks = true; } ChainTestingSetup::~ChainTestingSetup() diff --git a/src/test/util/txmempool.cpp b/src/test/util/txmempool.cpp index 12cc1a4a3d..1873cf5ec8 100644 --- a/src/test/util/txmempool.cpp +++ b/src/test/util/txmempool.cpp @@ -34,6 +34,5 @@ CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) co CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef& tx) const { - return CTxMemPoolEntry(tx, nFee, nTime, nHeight, - spendsCoinbase, sigOpCost, lp); + return CTxMemPoolEntry{tx, nFee, TicksSinceEpoch<std::chrono::seconds>(time), nHeight, spendsCoinbase, sigOpCost, lp}; } diff --git a/src/test/util/txmempool.h b/src/test/util/txmempool.h index 70b9ed88db..2fe7d69693 100644 --- a/src/test/util/txmempool.h +++ b/src/test/util/txmempool.h @@ -6,6 +6,7 @@ #define BITCOIN_TEST_UTIL_TXMEMPOOL_H #include <txmempool.h> +#include <util/time.h> namespace node { struct NodeContext; @@ -13,11 +14,10 @@ struct NodeContext; CTxMemPool::Options MemPoolOptionsForTest(const node::NodeContext& node); -struct TestMemPoolEntryHelper -{ +struct TestMemPoolEntryHelper { // Default values CAmount nFee{0}; - int64_t nTime{0}; + NodeSeconds time{}; unsigned int nHeight{1}; bool spendsCoinbase{false}; unsigned int sigOpCost{4}; @@ -27,11 +27,11 @@ struct TestMemPoolEntryHelper CTxMemPoolEntry FromTx(const CTransactionRef& tx) const; // Change the default value - TestMemPoolEntryHelper &Fee(CAmount _fee) { nFee = _fee; return *this; } - TestMemPoolEntryHelper &Time(int64_t _time) { nTime = _time; return *this; } - TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; } - TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; } - TestMemPoolEntryHelper &SigOpsCost(unsigned int _sigopsCost) { sigOpCost = _sigopsCost; return *this; } + TestMemPoolEntryHelper& Fee(CAmount _fee) { nFee = _fee; return *this; } + TestMemPoolEntryHelper& Time(NodeSeconds tp) { time = tp; return *this; } + TestMemPoolEntryHelper& Height(unsigned int _height) { nHeight = _height; return *this; } + TestMemPoolEntryHelper& SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; } + TestMemPoolEntryHelper& SigOpsCost(unsigned int _sigopsCost) { sigOpCost = _sigopsCost; return *this; } }; #endif // BITCOIN_TEST_UTIL_TXMEMPOOL_H diff --git a/src/test/util/wallet.cpp b/src/test/util/wallet.cpp index b54774cbb9..2dadffafb4 100644 --- a/src/test/util/wallet.cpp +++ b/src/test/util/wallet.cpp @@ -21,7 +21,12 @@ const std::string ADDRESS_BCRT1_UNSPENDABLE = "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqq std::string getnewaddress(CWallet& w) { constexpr auto output_type = OutputType::BECH32; - return EncodeDestination(*Assert(w.GetNewDestination(output_type, ""))); + return EncodeDestination(getNewDestination(w, output_type)); +} + +CTxDestination getNewDestination(CWallet& w, OutputType output_type) +{ + return *Assert(w.GetNewDestination(output_type, "")); } #endif // ENABLE_WALLET diff --git a/src/test/util/wallet.h b/src/test/util/wallet.h index 31281bf70e..d8f1db3fd7 100644 --- a/src/test/util/wallet.h +++ b/src/test/util/wallet.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_TEST_UTIL_WALLET_H #define BITCOIN_TEST_UTIL_WALLET_H +#include <outputtype.h> #include <string> namespace wallet { @@ -19,8 +20,10 @@ extern const std::string ADDRESS_BCRT1_UNSPENDABLE; /** Import the address to the wallet */ void importaddress(wallet::CWallet& wallet, const std::string& address); -/** Returns a new address from the wallet */ +/** Returns a new encoded destination from the wallet (hardcoded to BECH32) */ std::string getnewaddress(wallet::CWallet& w); +/** Returns a new destination, of an specific type, from the wallet */ +CTxDestination getNewDestination(wallet::CWallet& w, OutputType output_type); #endif // BITCOIN_TEST_UTIL_WALLET_H diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 009c27927f..602c848c2a 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -9,9 +9,7 @@ #include <hash.h> // For Hash() #include <key.h> // For CKey #include <sync.h> -#include <test/util/logging.h> #include <test/util/setup_common.h> -#include <test/util/str.h> #include <uint256.h> #include <util/getuniquepath.h> #include <util/message.h> // For MessageSign(), MessageVerify(), MESSAGE_MAGIC @@ -55,31 +53,6 @@ namespace BCLog { BOOST_FIXTURE_TEST_SUITE(util_tests, BasicTestingSetup) -BOOST_AUTO_TEST_CASE(util_datadir) -{ - // Use local args variable instead of m_args to avoid making assumptions about test setup - ArgsManager args; - args.ForceSetArg("-datadir", fs::PathToString(m_path_root)); - - const fs::path dd_norm = args.GetDataDirBase(); - - args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/"); - args.ClearPathCache(); - BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase()); - - args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/."); - args.ClearPathCache(); - BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase()); - - args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/./"); - args.ClearPathCache(); - BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase()); - - args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/.//"); - args.ClearPathCache(); - BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase()); -} - namespace { class NoCopyOrMove { @@ -124,6 +97,11 @@ BOOST_AUTO_TEST_CASE(util_check) // Check nested Asserts BOOST_CHECK_EQUAL(Assert((Assert(x).test() ? 3 : 0)), 3); + + // Check -Wdangling-gsl does not trigger when copying the int. (It would + // trigger on "const int&") + const int nine{*Assert(std::optional<int>{9})}; + BOOST_CHECK_EQUAL(9, nine); } BOOST_AUTO_TEST_CASE(util_criticalsection) @@ -294,1000 +272,6 @@ BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) BOOST_CHECK_EQUAL(FormatISO8601Date(1317425777), "2011-09-30"); } -struct TestArgsManager : public ArgsManager -{ - TestArgsManager() { m_network_only_args.clear(); } - void ReadConfigString(const std::string str_config) - { - std::istringstream streamConfig(str_config); - { - LOCK(cs_args); - m_settings.ro_config.clear(); - m_config_sections.clear(); - } - std::string error; - BOOST_REQUIRE(ReadConfigStream(streamConfig, "", error)); - } - void SetNetworkOnlyArg(const std::string arg) - { - LOCK(cs_args); - m_network_only_args.insert(arg); - } - void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args) - { - for (const auto& arg : args) { - AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS); - } - } - using ArgsManager::GetSetting; - using ArgsManager::GetSettingsList; - using ArgsManager::ReadConfigStream; - using ArgsManager::cs_args; - using ArgsManager::m_network; - using ArgsManager::m_settings; -}; - -//! Test GetSetting and GetArg type coercion, negation, and default value handling. -class CheckValueTest : public TestChain100Setup -{ -public: - struct Expect { - util::SettingsValue setting; - bool default_string = false; - bool default_int = false; - bool default_bool = false; - const char* string_value = nullptr; - std::optional<int64_t> int_value; - std::optional<bool> bool_value; - std::optional<std::vector<std::string>> list_value; - const char* error = nullptr; - - explicit Expect(util::SettingsValue s) : setting(std::move(s)) {} - Expect& DefaultString() { default_string = true; return *this; } - Expect& DefaultInt() { default_int = true; return *this; } - Expect& DefaultBool() { default_bool = true; return *this; } - Expect& String(const char* s) { string_value = s; return *this; } - Expect& Int(int64_t i) { int_value = i; return *this; } - Expect& Bool(bool b) { bool_value = b; return *this; } - Expect& List(std::vector<std::string> m) { list_value = std::move(m); return *this; } - Expect& Error(const char* e) { error = e; return *this; } - }; - - void CheckValue(unsigned int flags, const char* arg, const Expect& expect) - { - TestArgsManager test; - test.SetupArgs({{"-value", flags}}); - const char* argv[] = {"ignored", arg}; - std::string error; - bool success = test.ParseParameters(arg ? 2 : 1, (char**)argv, error); - - BOOST_CHECK_EQUAL(test.GetSetting("-value").write(), expect.setting.write()); - auto settings_list = test.GetSettingsList("-value"); - if (expect.setting.isNull() || expect.setting.isFalse()) { - BOOST_CHECK_EQUAL(settings_list.size(), 0U); - } else { - BOOST_CHECK_EQUAL(settings_list.size(), 1U); - BOOST_CHECK_EQUAL(settings_list[0].write(), expect.setting.write()); - } - - if (expect.error) { - BOOST_CHECK(!success); - BOOST_CHECK_NE(error.find(expect.error), std::string::npos); - } else { - BOOST_CHECK(success); - BOOST_CHECK_EQUAL(error, ""); - } - - if (expect.default_string) { - BOOST_CHECK_EQUAL(test.GetArg("-value", "zzzzz"), "zzzzz"); - } else if (expect.string_value) { - BOOST_CHECK_EQUAL(test.GetArg("-value", "zzzzz"), expect.string_value); - } else { - BOOST_CHECK(!success); - } - - if (expect.default_int) { - BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), 99999); - } else if (expect.int_value) { - BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), *expect.int_value); - } else { - BOOST_CHECK(!success); - } - - if (expect.default_bool) { - BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), false); - BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), true); - } else if (expect.bool_value) { - BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), *expect.bool_value); - BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), *expect.bool_value); - } else { - BOOST_CHECK(!success); - } - - if (expect.list_value) { - auto l = test.GetArgs("-value"); - BOOST_CHECK_EQUAL_COLLECTIONS(l.begin(), l.end(), expect.list_value->begin(), expect.list_value->end()); - } else { - BOOST_CHECK(!success); - } - } -}; - -BOOST_FIXTURE_TEST_CASE(util_CheckValue, CheckValueTest) -{ - using M = ArgsManager; - - CheckValue(M::ALLOW_ANY, nullptr, Expect{{}}.DefaultString().DefaultInt().DefaultBool().List({})); - CheckValue(M::ALLOW_ANY, "-novalue", Expect{false}.String("0").Int(0).Bool(false).List({})); - CheckValue(M::ALLOW_ANY, "-novalue=", Expect{false}.String("0").Int(0).Bool(false).List({})); - CheckValue(M::ALLOW_ANY, "-novalue=0", Expect{true}.String("1").Int(1).Bool(true).List({"1"})); - CheckValue(M::ALLOW_ANY, "-novalue=1", Expect{false}.String("0").Int(0).Bool(false).List({})); - CheckValue(M::ALLOW_ANY, "-novalue=2", Expect{false}.String("0").Int(0).Bool(false).List({})); - CheckValue(M::ALLOW_ANY, "-novalue=abc", Expect{true}.String("1").Int(1).Bool(true).List({"1"})); - CheckValue(M::ALLOW_ANY, "-value", Expect{""}.String("").Int(0).Bool(true).List({""})); - CheckValue(M::ALLOW_ANY, "-value=", Expect{""}.String("").Int(0).Bool(true).List({""})); - CheckValue(M::ALLOW_ANY, "-value=0", Expect{"0"}.String("0").Int(0).Bool(false).List({"0"})); - CheckValue(M::ALLOW_ANY, "-value=1", Expect{"1"}.String("1").Int(1).Bool(true).List({"1"})); - CheckValue(M::ALLOW_ANY, "-value=2", Expect{"2"}.String("2").Int(2).Bool(true).List({"2"})); - CheckValue(M::ALLOW_ANY, "-value=abc", Expect{"abc"}.String("abc").Int(0).Bool(false).List({"abc"})); -} - -struct NoIncludeConfTest { - std::string Parse(const char* arg) - { - TestArgsManager test; - test.SetupArgs({{"-includeconf", ArgsManager::ALLOW_ANY}}); - std::array argv{"ignored", arg}; - std::string error; - (void)test.ParseParameters(argv.size(), argv.data(), error); - return error; - } -}; - -BOOST_FIXTURE_TEST_CASE(util_NoIncludeConf, NoIncludeConfTest) -{ - BOOST_CHECK_EQUAL(Parse("-noincludeconf"), ""); - BOOST_CHECK_EQUAL(Parse("-includeconf"), "-includeconf cannot be used from commandline; -includeconf=\"\""); - BOOST_CHECK_EQUAL(Parse("-includeconf=file"), "-includeconf cannot be used from commandline; -includeconf=\"file\""); -} - -BOOST_AUTO_TEST_CASE(util_ParseParameters) -{ - TestArgsManager testArgs; - const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY); - const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY); - const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY); - const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY); - - const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"}; - - std::string error; - LOCK(testArgs.cs_args); - testArgs.SetupArgs({a, b, ccc, d}); - BOOST_CHECK(testArgs.ParseParameters(0, (char**)argv_test, error)); - BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty()); - - BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error)); - BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty()); - - BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error)); - // expectation: -ignored is ignored (program name argument), - // -a, -b and -ccc end up in map, -d ignored because it is after - // a non-option argument (non-GNU option parsing) - BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 3 && testArgs.m_settings.ro_config.empty()); - BOOST_CHECK(testArgs.IsArgSet("-a") && testArgs.IsArgSet("-b") && testArgs.IsArgSet("-ccc") - && !testArgs.IsArgSet("f") && !testArgs.IsArgSet("-d")); - BOOST_CHECK(testArgs.m_settings.command_line_options.count("a") && testArgs.m_settings.command_line_options.count("b") && testArgs.m_settings.command_line_options.count("ccc") - && !testArgs.m_settings.command_line_options.count("f") && !testArgs.m_settings.command_line_options.count("d")); - - BOOST_CHECK(testArgs.m_settings.command_line_options["a"].size() == 1); - BOOST_CHECK(testArgs.m_settings.command_line_options["a"].front().get_str() == ""); - BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].size() == 2); - BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].front().get_str() == "argument"); - BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].back().get_str() == "multiple"); - BOOST_CHECK(testArgs.GetArgs("-ccc").size() == 2); -} - -BOOST_AUTO_TEST_CASE(util_ParseInvalidParameters) -{ - TestArgsManager test; - test.SetupArgs({{"-registered", ArgsManager::ALLOW_ANY}}); - - const char* argv[] = {"ignored", "-registered"}; - std::string error; - BOOST_CHECK(test.ParseParameters(2, (char**)argv, error)); - BOOST_CHECK_EQUAL(error, ""); - - argv[1] = "-unregistered"; - BOOST_CHECK(!test.ParseParameters(2, (char**)argv, error)); - BOOST_CHECK_EQUAL(error, "Invalid parameter -unregistered"); - - // Make sure registered parameters prefixed with a chain name trigger errors. - // (Previously, they were accepted and ignored.) - argv[1] = "-test.registered"; - BOOST_CHECK(!test.ParseParameters(2, (char**)argv, error)); - BOOST_CHECK_EQUAL(error, "Invalid parameter -test.registered"); -} - -static void TestParse(const std::string& str, bool expected_bool, int64_t expected_int) -{ - TestArgsManager test; - test.SetupArgs({{"-value", ArgsManager::ALLOW_ANY}}); - std::string arg = "-value=" + str; - const char* argv[] = {"ignored", arg.c_str()}; - std::string error; - BOOST_CHECK(test.ParseParameters(2, (char**)argv, error)); - BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), expected_bool); - BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), expected_bool); - BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99998), expected_int); - BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), expected_int); -} - -// Test bool and int parsing. -BOOST_AUTO_TEST_CASE(util_ArgParsing) -{ - // Some of these cases could be ambiguous or surprising to users, and might - // be worth triggering errors or warnings in the future. But for now basic - // test coverage is useful to avoid breaking backwards compatibility - // unintentionally. - TestParse("", true, 0); - TestParse(" ", false, 0); - TestParse("0", false, 0); - TestParse("0 ", false, 0); - TestParse(" 0", false, 0); - TestParse("+0", false, 0); - TestParse("-0", false, 0); - TestParse("5", true, 5); - TestParse("5 ", true, 5); - TestParse(" 5", true, 5); - TestParse("+5", true, 5); - TestParse("-5", true, -5); - TestParse("0 5", false, 0); - TestParse("5 0", true, 5); - TestParse("050", true, 50); - TestParse("0.", false, 0); - TestParse("5.", true, 5); - TestParse("0.0", false, 0); - TestParse("0.5", false, 0); - TestParse("5.0", true, 5); - TestParse("5.5", true, 5); - TestParse("x", false, 0); - TestParse("x0", false, 0); - TestParse("x5", false, 0); - TestParse("0x", false, 0); - TestParse("5x", true, 5); - TestParse("0x5", false, 0); - TestParse("false", false, 0); - TestParse("true", false, 0); - TestParse("yes", false, 0); - TestParse("no", false, 0); -} - -BOOST_AUTO_TEST_CASE(util_GetBoolArg) -{ - TestArgsManager testArgs; - const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY); - const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY); - const auto c = std::make_pair("-c", ArgsManager::ALLOW_ANY); - const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY); - const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY); - const auto f = std::make_pair("-f", ArgsManager::ALLOW_ANY); - - const char *argv_test[] = { - "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"}; - std::string error; - LOCK(testArgs.cs_args); - testArgs.SetupArgs({a, b, c, d, e, f}); - BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error)); - - // Each letter should be set. - for (const char opt : "abcdef") - BOOST_CHECK(testArgs.IsArgSet({'-', opt}) || !opt); - - // Nothing else should be in the map - BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 6 && - testArgs.m_settings.ro_config.empty()); - - // The -no prefix should get stripped on the way in. - BOOST_CHECK(!testArgs.IsArgSet("-nob")); - - // The -b option is flagged as negated, and nothing else is - BOOST_CHECK(testArgs.IsArgNegated("-b")); - BOOST_CHECK(!testArgs.IsArgNegated("-a")); - - // Check expected values. - BOOST_CHECK(testArgs.GetBoolArg("-a", false) == true); - BOOST_CHECK(testArgs.GetBoolArg("-b", true) == false); - BOOST_CHECK(testArgs.GetBoolArg("-c", true) == false); - BOOST_CHECK(testArgs.GetBoolArg("-d", false) == true); - BOOST_CHECK(testArgs.GetBoolArg("-e", true) == false); - BOOST_CHECK(testArgs.GetBoolArg("-f", true) == false); -} - -BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) -{ - // Test some awful edge cases that hopefully no user will ever exercise. - TestArgsManager testArgs; - - // Params test - const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_ANY); - const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY); - const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"}; - testArgs.SetupArgs({foo, bar}); - std::string error; - BOOST_CHECK(testArgs.ParseParameters(4, (char**)argv_test, error)); - - // This was passed twice, second one overrides the negative setting. - BOOST_CHECK(!testArgs.IsArgNegated("-foo")); - BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == ""); - - // A double negative is a positive, and not marked as negated. - BOOST_CHECK(!testArgs.IsArgNegated("-bar")); - BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1"); - - // Config test - const char *conf_test = "nofoo=1\nfoo=1\nnobar=0\n"; - BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error)); - testArgs.ReadConfigString(conf_test); - - // This was passed twice, second one overrides the negative setting, - // and the value. - BOOST_CHECK(!testArgs.IsArgNegated("-foo")); - BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "1"); - - // A double negative is a positive, and does not count as negated. - BOOST_CHECK(!testArgs.IsArgNegated("-bar")); - BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1"); - - // Combined test - const char *combo_test_args[] = {"ignored", "-nofoo", "-bar"}; - const char *combo_test_conf = "foo=1\nnobar=1\n"; - BOOST_CHECK(testArgs.ParseParameters(3, (char**)combo_test_args, error)); - testArgs.ReadConfigString(combo_test_conf); - - // Command line overrides, but doesn't erase old setting - BOOST_CHECK(testArgs.IsArgNegated("-foo")); - BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "0"); - BOOST_CHECK(testArgs.GetArgs("-foo").size() == 0); - - // Command line overrides, but doesn't erase old setting - BOOST_CHECK(!testArgs.IsArgNegated("-bar")); - BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == ""); - BOOST_CHECK(testArgs.GetArgs("-bar").size() == 1 - && testArgs.GetArgs("-bar").front() == ""); -} - -BOOST_AUTO_TEST_CASE(util_ReadConfigStream) -{ - const char *str_config = - "a=\n" - "b=1\n" - "ccc=argument\n" - "ccc=multiple\n" - "d=e\n" - "nofff=1\n" - "noggg=0\n" - "h=1\n" - "noh=1\n" - "noi=1\n" - "i=1\n" - "sec1.ccc=extend1\n" - "\n" - "[sec1]\n" - "ccc=extend2\n" - "d=eee\n" - "h=1\n" - "[sec2]\n" - "ccc=extend3\n" - "iii=2\n"; - - TestArgsManager test_args; - LOCK(test_args.cs_args); - const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY); - const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY); - const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY); - const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY); - const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY); - const auto fff = std::make_pair("-fff", ArgsManager::ALLOW_ANY); - const auto ggg = std::make_pair("-ggg", ArgsManager::ALLOW_ANY); - const auto h = std::make_pair("-h", ArgsManager::ALLOW_ANY); - const auto i = std::make_pair("-i", ArgsManager::ALLOW_ANY); - const auto iii = std::make_pair("-iii", ArgsManager::ALLOW_ANY); - test_args.SetupArgs({a, b, ccc, d, e, fff, ggg, h, i, iii}); - - test_args.ReadConfigString(str_config); - // expectation: a, b, ccc, d, fff, ggg, h, i end up in map - // so do sec1.ccc, sec1.d, sec1.h, sec2.ccc, sec2.iii - - BOOST_CHECK(test_args.m_settings.command_line_options.empty()); - BOOST_CHECK(test_args.m_settings.ro_config.size() == 3); - BOOST_CHECK(test_args.m_settings.ro_config[""].size() == 8); - BOOST_CHECK(test_args.m_settings.ro_config["sec1"].size() == 3); - BOOST_CHECK(test_args.m_settings.ro_config["sec2"].size() == 2); - - BOOST_CHECK(test_args.m_settings.ro_config[""].count("a")); - BOOST_CHECK(test_args.m_settings.ro_config[""].count("b")); - BOOST_CHECK(test_args.m_settings.ro_config[""].count("ccc")); - BOOST_CHECK(test_args.m_settings.ro_config[""].count("d")); - BOOST_CHECK(test_args.m_settings.ro_config[""].count("fff")); - BOOST_CHECK(test_args.m_settings.ro_config[""].count("ggg")); - BOOST_CHECK(test_args.m_settings.ro_config[""].count("h")); - BOOST_CHECK(test_args.m_settings.ro_config[""].count("i")); - BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("ccc")); - BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("h")); - BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("ccc")); - BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("iii")); - - BOOST_CHECK(test_args.IsArgSet("-a")); - BOOST_CHECK(test_args.IsArgSet("-b")); - BOOST_CHECK(test_args.IsArgSet("-ccc")); - BOOST_CHECK(test_args.IsArgSet("-d")); - BOOST_CHECK(test_args.IsArgSet("-fff")); - BOOST_CHECK(test_args.IsArgSet("-ggg")); - BOOST_CHECK(test_args.IsArgSet("-h")); - BOOST_CHECK(test_args.IsArgSet("-i")); - BOOST_CHECK(!test_args.IsArgSet("-zzz")); - BOOST_CHECK(!test_args.IsArgSet("-iii")); - - BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), ""); - BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1"); - BOOST_CHECK_EQUAL(test_args.GetArg("-ccc", "xxx"), "argument"); - BOOST_CHECK_EQUAL(test_args.GetArg("-d", "xxx"), "e"); - BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0"); - BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1"); - BOOST_CHECK_EQUAL(test_args.GetArg("-h", "xxx"), "0"); - BOOST_CHECK_EQUAL(test_args.GetArg("-i", "xxx"), "1"); - BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx"); - BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx"); - - for (const bool def : {false, true}) { - BOOST_CHECK(test_args.GetBoolArg("-a", def)); - BOOST_CHECK(test_args.GetBoolArg("-b", def)); - BOOST_CHECK(!test_args.GetBoolArg("-ccc", def)); - BOOST_CHECK(!test_args.GetBoolArg("-d", def)); - BOOST_CHECK(!test_args.GetBoolArg("-fff", def)); - BOOST_CHECK(test_args.GetBoolArg("-ggg", def)); - BOOST_CHECK(!test_args.GetBoolArg("-h", def)); - BOOST_CHECK(test_args.GetBoolArg("-i", def)); - BOOST_CHECK(test_args.GetBoolArg("-zzz", def) == def); - BOOST_CHECK(test_args.GetBoolArg("-iii", def) == def); - } - - BOOST_CHECK(test_args.GetArgs("-a").size() == 1 - && test_args.GetArgs("-a").front() == ""); - BOOST_CHECK(test_args.GetArgs("-b").size() == 1 - && test_args.GetArgs("-b").front() == "1"); - BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2 - && test_args.GetArgs("-ccc").front() == "argument" - && test_args.GetArgs("-ccc").back() == "multiple"); - BOOST_CHECK(test_args.GetArgs("-fff").size() == 0); - BOOST_CHECK(test_args.GetArgs("-nofff").size() == 0); - BOOST_CHECK(test_args.GetArgs("-ggg").size() == 1 - && test_args.GetArgs("-ggg").front() == "1"); - BOOST_CHECK(test_args.GetArgs("-noggg").size() == 0); - BOOST_CHECK(test_args.GetArgs("-h").size() == 0); - BOOST_CHECK(test_args.GetArgs("-noh").size() == 0); - BOOST_CHECK(test_args.GetArgs("-i").size() == 1 - && test_args.GetArgs("-i").front() == "1"); - BOOST_CHECK(test_args.GetArgs("-noi").size() == 0); - BOOST_CHECK(test_args.GetArgs("-zzz").size() == 0); - - BOOST_CHECK(!test_args.IsArgNegated("-a")); - BOOST_CHECK(!test_args.IsArgNegated("-b")); - BOOST_CHECK(!test_args.IsArgNegated("-ccc")); - BOOST_CHECK(!test_args.IsArgNegated("-d")); - BOOST_CHECK(test_args.IsArgNegated("-fff")); - BOOST_CHECK(!test_args.IsArgNegated("-ggg")); - BOOST_CHECK(test_args.IsArgNegated("-h")); // last setting takes precedence - BOOST_CHECK(!test_args.IsArgNegated("-i")); // last setting takes precedence - BOOST_CHECK(!test_args.IsArgNegated("-zzz")); - - // Test sections work - test_args.SelectConfigNetwork("sec1"); - - // same as original - BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), ""); - BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1"); - BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0"); - BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1"); - BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx"); - BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx"); - // d is overridden - BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee"); - // section-specific setting - BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1"); - // section takes priority for multiple values - BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend1"); - // check multiple values works - const std::vector<std::string> sec1_ccc_expected = {"extend1","extend2","argument","multiple"}; - const auto& sec1_ccc_res = test_args.GetArgs("-ccc"); - BOOST_CHECK_EQUAL_COLLECTIONS(sec1_ccc_res.begin(), sec1_ccc_res.end(), sec1_ccc_expected.begin(), sec1_ccc_expected.end()); - - test_args.SelectConfigNetwork("sec2"); - - // same as original - BOOST_CHECK(test_args.GetArg("-a", "xxx") == ""); - BOOST_CHECK(test_args.GetArg("-b", "xxx") == "1"); - BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e"); - BOOST_CHECK(test_args.GetArg("-fff", "xxx") == "0"); - BOOST_CHECK(test_args.GetArg("-ggg", "xxx") == "1"); - BOOST_CHECK(test_args.GetArg("-zzz", "xxx") == "xxx"); - BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0"); - // section-specific setting - BOOST_CHECK(test_args.GetArg("-iii", "xxx") == "2"); - // section takes priority for multiple values - BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend3"); - // check multiple values works - const std::vector<std::string> sec2_ccc_expected = {"extend3","argument","multiple"}; - const auto& sec2_ccc_res = test_args.GetArgs("-ccc"); - BOOST_CHECK_EQUAL_COLLECTIONS(sec2_ccc_res.begin(), sec2_ccc_res.end(), sec2_ccc_expected.begin(), sec2_ccc_expected.end()); - - // Test section only options - - test_args.SetNetworkOnlyArg("-d"); - test_args.SetNetworkOnlyArg("-ccc"); - test_args.SetNetworkOnlyArg("-h"); - - test_args.SelectConfigNetwork(CBaseChainParams::MAIN); - BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e"); - BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2); - BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0"); - - test_args.SelectConfigNetwork("sec1"); - BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee"); - BOOST_CHECK(test_args.GetArgs("-d").size() == 1); - BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2); - BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1"); - - test_args.SelectConfigNetwork("sec2"); - BOOST_CHECK(test_args.GetArg("-d", "xxx") == "xxx"); - BOOST_CHECK(test_args.GetArgs("-d").size() == 0); - BOOST_CHECK(test_args.GetArgs("-ccc").size() == 1); - BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0"); -} - -BOOST_AUTO_TEST_CASE(util_GetArg) -{ - TestArgsManager testArgs; - LOCK(testArgs.cs_args); - testArgs.m_settings.command_line_options.clear(); - testArgs.m_settings.command_line_options["strtest1"] = {"string..."}; - // strtest2 undefined on purpose - testArgs.m_settings.command_line_options["inttest1"] = {"12345"}; - testArgs.m_settings.command_line_options["inttest2"] = {"81985529216486895"}; - // inttest3 undefined on purpose - testArgs.m_settings.command_line_options["booltest1"] = {""}; - // booltest2 undefined on purpose - testArgs.m_settings.command_line_options["booltest3"] = {"0"}; - testArgs.m_settings.command_line_options["booltest4"] = {"1"}; - - // priorities - testArgs.m_settings.command_line_options["pritest1"] = {"a", "b"}; - testArgs.m_settings.ro_config[""]["pritest2"] = {"a", "b"}; - testArgs.m_settings.command_line_options["pritest3"] = {"a"}; - testArgs.m_settings.ro_config[""]["pritest3"] = {"b"}; - testArgs.m_settings.command_line_options["pritest4"] = {"a","b"}; - testArgs.m_settings.ro_config[""]["pritest4"] = {"c","d"}; - - BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string..."); - BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); - BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest1", -1), 12345); - BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest2", -1), 81985529216486895LL); - BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest3", -1), -1); - BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true); - BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest2", false), false); - BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest3", false), false); - BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest4", false), true); - - BOOST_CHECK_EQUAL(testArgs.GetArg("pritest1", "default"), "b"); - BOOST_CHECK_EQUAL(testArgs.GetArg("pritest2", "default"), "a"); - BOOST_CHECK_EQUAL(testArgs.GetArg("pritest3", "default"), "a"); - BOOST_CHECK_EQUAL(testArgs.GetArg("pritest4", "default"), "b"); -} - -BOOST_AUTO_TEST_CASE(util_GetChainName) -{ - TestArgsManager test_args; - const auto testnet = std::make_pair("-testnet", ArgsManager::ALLOW_ANY); - const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_ANY); - test_args.SetupArgs({testnet, regtest}); - - const char* argv_testnet[] = {"cmd", "-testnet"}; - const char* argv_regtest[] = {"cmd", "-regtest"}; - const char* argv_test_no_reg[] = {"cmd", "-testnet", "-noregtest"}; - const char* argv_both[] = {"cmd", "-testnet", "-regtest"}; - - // equivalent to "-testnet" - // regtest in testnet section is ignored - const char* testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1"; - std::string error; - - BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error)); - BOOST_CHECK_EQUAL(test_args.GetChainName(), "main"); - - BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error)); - BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - - BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error)); - BOOST_CHECK_EQUAL(test_args.GetChainName(), "regtest"); - - BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_test_no_reg, error)); - BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - - BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error)); - BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - - BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error)); - test_args.ReadConfigString(testnetconf); - BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - - BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error)); - test_args.ReadConfigString(testnetconf); - BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - - BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error)); - test_args.ReadConfigString(testnetconf); - BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - - BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_test_no_reg, error)); - test_args.ReadConfigString(testnetconf); - BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - - BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error)); - test_args.ReadConfigString(testnetconf); - BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - - // check setting the network to test (and thus making - // [test] regtest=1 potentially relevant) doesn't break things - test_args.SelectConfigNetwork("test"); - - BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error)); - test_args.ReadConfigString(testnetconf); - BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - - BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error)); - test_args.ReadConfigString(testnetconf); - BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - - BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error)); - test_args.ReadConfigString(testnetconf); - BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - - BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_test_no_reg, error)); - test_args.ReadConfigString(testnetconf); - BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - - BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error)); - test_args.ReadConfigString(testnetconf); - BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); -} - -// Test different ways settings can be merged, and verify results. This test can -// be used to confirm that updates to settings code don't change behavior -// unintentionally. -// -// The test covers: -// -// - Combining different setting actions. Possible actions are: configuring a -// setting, negating a setting (adding "-no" prefix), and configuring/negating -// settings in a network section (adding "main." or "test." prefixes). -// -// - Combining settings from command line arguments and a config file. -// -// - Combining SoftSet and ForceSet calls. -// -// - Testing "main" and "test" network values to make sure settings from network -// sections are applied and to check for mainnet-specific behaviors like -// inheriting settings from the default section. -// -// - Testing network-specific settings like "-wallet", that may be ignored -// outside a network section, and non-network specific settings like "-server" -// that aren't sensitive to the network. -// -struct ArgsMergeTestingSetup : public BasicTestingSetup { - //! Max number of actions to sequence together. Can decrease this when - //! debugging to make test results easier to understand. - static constexpr int MAX_ACTIONS = 3; - - enum Action { NONE, SET, NEGATE, SECTION_SET, SECTION_NEGATE }; - using ActionList = Action[MAX_ACTIONS]; - - //! Enumerate all possible test configurations. - template <typename Fn> - void ForEachMergeSetup(Fn&& fn) - { - ActionList arg_actions = {}; - // command_line_options do not have sections. Only iterate over SET and NEGATE - ForEachNoDup(arg_actions, SET, NEGATE, [&] { - ActionList conf_actions = {}; - ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] { - for (bool soft_set : {false, true}) { - for (bool force_set : {false, true}) { - for (const std::string& section : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) { - for (const std::string& network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) { - for (bool net_specific : {false, true}) { - fn(arg_actions, conf_actions, soft_set, force_set, section, network, net_specific); - } - } - } - } - } - }); - }); - } - - //! Translate actions into a list of <key>=<value> setting strings. - std::vector<std::string> GetValues(const ActionList& actions, - const std::string& section, - const std::string& name, - const std::string& value_prefix) - { - std::vector<std::string> values; - int suffix = 0; - for (Action action : actions) { - if (action == NONE) break; - std::string prefix; - if (action == SECTION_SET || action == SECTION_NEGATE) prefix = section + "."; - if (action == SET || action == SECTION_SET) { - for (int i = 0; i < 2; ++i) { - values.push_back(prefix + name + "=" + value_prefix + ToString(++suffix)); - } - } - if (action == NEGATE || action == SECTION_NEGATE) { - values.push_back(prefix + "no" + name + "=1"); - } - } - return values; - } -}; - -// Regression test covering different ways config settings can be merged. The -// test parses and merges settings, representing the results as strings that get -// compared against an expected hash. To debug, the result strings can be dumped -// to a file (see comments below). -BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup) -{ - CHash256 out_sha; - FILE* out_file = nullptr; - if (const char* out_path = getenv("ARGS_MERGE_TEST_OUT")) { - out_file = fsbridge::fopen(out_path, "w"); - if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed"); - } - - ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool soft_set, bool force_set, - const std::string& section, const std::string& network, bool net_specific) { - TestArgsManager parser; - LOCK(parser.cs_args); - - std::string desc = "net="; - desc += network; - parser.m_network = network; - - const std::string& name = net_specific ? "wallet" : "server"; - const std::string key = "-" + name; - parser.AddArg(key, name, ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - if (net_specific) parser.SetNetworkOnlyArg(key); - - auto args = GetValues(arg_actions, section, name, "a"); - std::vector<const char*> argv = {"ignored"}; - for (auto& arg : args) { - arg.insert(0, "-"); - desc += " "; - desc += arg; - argv.push_back(arg.c_str()); - } - std::string error; - BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error)); - BOOST_CHECK_EQUAL(error, ""); - - std::string conf; - for (auto& conf_val : GetValues(conf_actions, section, name, "c")) { - desc += " "; - desc += conf_val; - conf += conf_val; - conf += "\n"; - } - std::istringstream conf_stream(conf); - BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error)); - BOOST_CHECK_EQUAL(error, ""); - - if (soft_set) { - desc += " soft"; - parser.SoftSetArg(key, "soft1"); - parser.SoftSetArg(key, "soft2"); - } - - if (force_set) { - desc += " force"; - parser.ForceSetArg(key, "force1"); - parser.ForceSetArg(key, "force2"); - } - - desc += " || "; - - if (!parser.IsArgSet(key)) { - desc += "unset"; - BOOST_CHECK(!parser.IsArgNegated(key)); - BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "default"); - BOOST_CHECK(parser.GetArgs(key).empty()); - } else if (parser.IsArgNegated(key)) { - desc += "negated"; - BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "0"); - BOOST_CHECK(parser.GetArgs(key).empty()); - } else { - desc += parser.GetArg(key, "default"); - desc += " |"; - for (const auto& arg : parser.GetArgs(key)) { - desc += " "; - desc += arg; - } - } - - std::set<std::string> ignored = parser.GetUnsuitableSectionOnlyArgs(); - if (!ignored.empty()) { - desc += " | ignored"; - for (const auto& arg : ignored) { - desc += " "; - desc += arg; - } - } - - desc += "\n"; - - out_sha.Write(MakeUCharSpan(desc)); - if (out_file) { - BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size()); - } - }); - - if (out_file) { - if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed"); - out_file = nullptr; - } - - unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE]; - out_sha.Finalize(out_sha_bytes); - std::string out_sha_hex = HexStr(out_sha_bytes); - - // If check below fails, should manually dump the results with: - // - // ARGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ArgsMerge - // - // And verify diff against previous results to make sure the changes are expected. - // - // Results file is formatted like: - // - // <input> || <IsArgSet/IsArgNegated/GetArg output> | <GetArgs output> | <GetUnsuitable output> - BOOST_CHECK_EQUAL(out_sha_hex, "d1e436c1cd510d0ec44d5205d4b4e3bee6387d316e0075c58206cb16603f3d82"); -} - -// Similar test as above, but for ArgsManager::GetChainName function. -struct ChainMergeTestingSetup : public BasicTestingSetup { - static constexpr int MAX_ACTIONS = 2; - - enum Action { NONE, ENABLE_TEST, DISABLE_TEST, NEGATE_TEST, ENABLE_REG, DISABLE_REG, NEGATE_REG }; - using ActionList = Action[MAX_ACTIONS]; - - //! Enumerate all possible test configurations. - template <typename Fn> - void ForEachMergeSetup(Fn&& fn) - { - ActionList arg_actions = {}; - ForEachNoDup(arg_actions, ENABLE_TEST, NEGATE_REG, [&] { - ActionList conf_actions = {}; - ForEachNoDup(conf_actions, ENABLE_TEST, NEGATE_REG, [&] { fn(arg_actions, conf_actions); }); - }); - } -}; - -BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup) -{ - CHash256 out_sha; - FILE* out_file = nullptr; - if (const char* out_path = getenv("CHAIN_MERGE_TEST_OUT")) { - out_file = fsbridge::fopen(out_path, "w"); - if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed"); - } - - ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions) { - TestArgsManager parser; - LOCK(parser.cs_args); - parser.AddArg("-regtest", "regtest", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - parser.AddArg("-testnet", "testnet", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - - auto arg = [](Action action) { return action == ENABLE_TEST ? "-testnet=1" : - action == DISABLE_TEST ? "-testnet=0" : - action == NEGATE_TEST ? "-notestnet=1" : - action == ENABLE_REG ? "-regtest=1" : - action == DISABLE_REG ? "-regtest=0" : - action == NEGATE_REG ? "-noregtest=1" : nullptr; }; - - std::string desc; - std::vector<const char*> argv = {"ignored"}; - for (Action action : arg_actions) { - const char* argstr = arg(action); - if (!argstr) break; - argv.push_back(argstr); - desc += " "; - desc += argv.back(); - } - std::string error; - BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error)); - BOOST_CHECK_EQUAL(error, ""); - - std::string conf; - for (Action action : conf_actions) { - const char* argstr = arg(action); - if (!argstr) break; - desc += " "; - desc += argstr + 1; - conf += argstr + 1; - conf += "\n"; - } - std::istringstream conf_stream(conf); - BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error)); - BOOST_CHECK_EQUAL(error, ""); - - desc += " || "; - try { - desc += parser.GetChainName(); - } catch (const std::runtime_error& e) { - desc += "error: "; - desc += e.what(); - } - desc += "\n"; - - out_sha.Write(MakeUCharSpan(desc)); - if (out_file) { - BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size()); - } - }); - - if (out_file) { - if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed"); - out_file = nullptr; - } - - unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE]; - out_sha.Finalize(out_sha_bytes); - std::string out_sha_hex = HexStr(out_sha_bytes); - - // If check below fails, should manually dump the results with: - // - // CHAIN_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ChainMerge - // - // And verify diff against previous results to make sure the changes are expected. - // - // Results file is formatted like: - // - // <input> || <output> - BOOST_CHECK_EQUAL(out_sha_hex, "f263493e300023b6509963887444c41386f44b63bc30047eb8402e8c1144854c"); -} - -BOOST_AUTO_TEST_CASE(util_ReadWriteSettings) -{ - // Test writing setting. - TestArgsManager args1; - args1.ForceSetArg("-datadir", fs::PathToString(m_path_root)); - args1.LockSettings([&](util::Settings& settings) { settings.rw_settings["name"] = "value"; }); - args1.WriteSettingsFile(); - - // Test reading setting. - TestArgsManager args2; - args2.ForceSetArg("-datadir", fs::PathToString(m_path_root)); - args2.ReadSettingsFile(); - args2.LockSettings([&](util::Settings& settings) { BOOST_CHECK_EQUAL(settings.rw_settings["name"].get_str(), "value"); }); - - // Test error logging, and remove previously written setting. - { - ASSERT_DEBUG_LOG("Failed renaming settings file"); - fs::remove(args1.GetDataDirBase() / "settings.json"); - fs::create_directory(args1.GetDataDirBase() / "settings.json"); - args2.WriteSettingsFile(); - fs::remove(args1.GetDataDirBase() / "settings.json"); - } -} - BOOST_AUTO_TEST_CASE(util_FormatMoney) { BOOST_CHECK_EQUAL(FormatMoney(0), "0.00"); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 84ed2e9ef5..12e2d5f224 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -41,42 +41,6 @@ bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp) return true; } -CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee, - int64_t time, unsigned int entry_height, - bool spends_coinbase, int64_t sigops_cost, LockPoints lp) - : tx{tx}, - nFee{fee}, - nTxWeight(GetTransactionWeight(*tx)), - nUsageSize{RecursiveDynamicUsage(tx)}, - nTime{time}, - entryHeight{entry_height}, - spendsCoinbase{spends_coinbase}, - sigOpCost{sigops_cost}, - m_modified_fee{nFee}, - lockPoints{lp}, - nSizeWithDescendants{GetTxSize()}, - nModFeesWithDescendants{nFee}, - nSizeWithAncestors{GetTxSize()}, - nModFeesWithAncestors{nFee}, - nSigOpCostWithAncestors{sigOpCost} {} - -void CTxMemPoolEntry::UpdateModifiedFee(CAmount fee_diff) -{ - nModFeesWithDescendants = SaturatingAdd(nModFeesWithDescendants, fee_diff); - nModFeesWithAncestors = SaturatingAdd(nModFeesWithAncestors, fee_diff); - m_modified_fee = SaturatingAdd(m_modified_fee, fee_diff); -} - -void CTxMemPoolEntry::UpdateLockPoints(const LockPoints& lp) -{ - lockPoints = lp; -} - -size_t CTxMemPoolEntry::GetTxSize() const -{ - return GetVirtualTransactionSize(nTxWeight, sigOpCost, ::nBytesPerSigOp); -} - void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants, const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove) { @@ -1182,3 +1146,17 @@ void CTxMemPool::SetLoadTried(bool load_tried) LOCK(cs); m_load_tried = load_tried; } + + +const std::string RemovalReasonToString(const MemPoolRemovalReason& r) noexcept +{ + switch (r) { + case MemPoolRemovalReason::EXPIRY: return "expiry"; + case MemPoolRemovalReason::SIZELIMIT: return "sizelimit"; + case MemPoolRemovalReason::REORG: return "reorg"; + case MemPoolRemovalReason::BLOCK: return "block"; + case MemPoolRemovalReason::CONFLICT: return "conflict"; + case MemPoolRemovalReason::REPLACED: return "replaced"; + } + assert(false); +} diff --git a/src/txmempool.h b/src/txmempool.h index 4afaac0506..d48327e5dc 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -25,6 +25,7 @@ #include <primitives/transaction.h> #include <random.h> #include <sync.h> +#include <txmempool_entry.h> #include <util/epochguard.h> #include <util/hasher.h> @@ -41,132 +42,11 @@ extern RecursiveMutex cs_main; /** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */ static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF; -struct LockPoints { - // Will be set to the blockchain height and median time past - // values that would be necessary to satisfy all relative locktime - // constraints (BIP68) of this tx given our view of block chain history - int height{0}; - int64_t time{0}; - // As long as the current chain descends from the highest height block - // containing one of the inputs used in the calculation, then the cached - // values are still valid even after a reorg. - CBlockIndex* maxInputBlock{nullptr}; -}; - /** * Test whether the LockPoints height and time are still valid on the current chain */ bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -struct CompareIteratorByHash { - // SFINAE for T where T is either a pointer type (e.g., a txiter) or a reference_wrapper<T> - // (e.g. a wrapped CTxMemPoolEntry&) - template <typename T> - bool operator()(const std::reference_wrapper<T>& a, const std::reference_wrapper<T>& b) const - { - return a.get().GetTx().GetHash() < b.get().GetTx().GetHash(); - } - template <typename T> - bool operator()(const T& a, const T& b) const - { - return a->GetTx().GetHash() < b->GetTx().GetHash(); - } -}; - -/** \class CTxMemPoolEntry - * - * CTxMemPoolEntry stores data about the corresponding transaction, as well - * as data about all in-mempool transactions that depend on the transaction - * ("descendant" transactions). - * - * When a new entry is added to the mempool, we update the descendant state - * (nCountWithDescendants, nSizeWithDescendants, and nModFeesWithDescendants) for - * all ancestors of the newly added transaction. - * - */ - -class CTxMemPoolEntry -{ -public: - typedef std::reference_wrapper<const CTxMemPoolEntry> CTxMemPoolEntryRef; - // two aliases, should the types ever diverge - typedef std::set<CTxMemPoolEntryRef, CompareIteratorByHash> Parents; - typedef std::set<CTxMemPoolEntryRef, CompareIteratorByHash> Children; - -private: - const CTransactionRef tx; - mutable Parents m_parents; - mutable Children m_children; - const CAmount nFee; //!< Cached to avoid expensive parent-transaction lookups - const size_t nTxWeight; //!< ... and avoid recomputing tx weight (also used for GetTxSize()) - const size_t nUsageSize; //!< ... and total memory usage - const int64_t nTime; //!< Local time when entering the mempool - const unsigned int entryHeight; //!< Chain height when entering the mempool - const bool spendsCoinbase; //!< keep track of transactions that spend a coinbase - const int64_t sigOpCost; //!< Total sigop cost - CAmount m_modified_fee; //!< Used for determining the priority of the transaction for mining in a block - LockPoints lockPoints; //!< Track the height and time at which tx was final - - // Information about descendants of this transaction that are in the - // mempool; if we remove this transaction we must remove all of these - // descendants as well. - uint64_t nCountWithDescendants{1}; //!< number of descendant transactions - uint64_t nSizeWithDescendants; //!< ... and size - CAmount nModFeesWithDescendants; //!< ... and total fees (all including us) - - // Analogous statistics for ancestor transactions - uint64_t nCountWithAncestors{1}; - uint64_t nSizeWithAncestors; - CAmount nModFeesWithAncestors; - int64_t nSigOpCostWithAncestors; - -public: - CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee, - int64_t time, unsigned int entry_height, - bool spends_coinbase, - int64_t sigops_cost, LockPoints lp); - - const CTransaction& GetTx() const { return *this->tx; } - CTransactionRef GetSharedTx() const { return this->tx; } - const CAmount& GetFee() const { return nFee; } - size_t GetTxSize() const; - size_t GetTxWeight() const { return nTxWeight; } - std::chrono::seconds GetTime() const { return std::chrono::seconds{nTime}; } - unsigned int GetHeight() const { return entryHeight; } - int64_t GetSigOpCost() const { return sigOpCost; } - CAmount GetModifiedFee() const { return m_modified_fee; } - size_t DynamicMemoryUsage() const { return nUsageSize; } - const LockPoints& GetLockPoints() const { return lockPoints; } - - // Adjusts the descendant state. - void UpdateDescendantState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount); - // Adjusts the ancestor state - void UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps); - // Updates the modified fees with descendants/ancestors. - void UpdateModifiedFee(CAmount fee_diff); - // Update the LockPoints after a reorg - void UpdateLockPoints(const LockPoints& lp); - - uint64_t GetCountWithDescendants() const { return nCountWithDescendants; } - uint64_t GetSizeWithDescendants() const { return nSizeWithDescendants; } - CAmount GetModFeesWithDescendants() const { return nModFeesWithDescendants; } - - bool GetSpendsCoinbase() const { return spendsCoinbase; } - - uint64_t GetCountWithAncestors() const { return nCountWithAncestors; } - uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; } - CAmount GetModFeesWithAncestors() const { return nModFeesWithAncestors; } - int64_t GetSigOpCostWithAncestors() const { return nSigOpCostWithAncestors; } - - const Parents& GetMemPoolParentsConst() const { return m_parents; } - const Children& GetMemPoolChildrenConst() const { return m_children; } - Parents& GetMemPoolParents() const { return m_parents; } - Children& GetMemPoolChildren() const { return m_children; } - - mutable size_t vTxHashesIdx; //!< Index in mempool's vTxHashes - mutable Epoch::Marker m_epoch_marker; //!< epoch when last touched, useful for graph algorithms -}; - // extracts a transaction hash from CTxMemPoolEntry or CTransactionRef struct mempoolentry_txid { @@ -355,6 +235,8 @@ enum class MemPoolRemovalReason { REPLACED, //!< Removed for replacement }; +const std::string RemovalReasonToString(const MemPoolRemovalReason& r) noexcept; + /** * CTxMemPool stores valid-according-to-the-current-best-chain transactions * that may be included in the next block. diff --git a/src/txmempool_entry.h b/src/txmempool_entry.h new file mode 100644 index 0000000000..dd71833200 --- /dev/null +++ b/src/txmempool_entry.h @@ -0,0 +1,174 @@ +// Copyright (c) 2009-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_TXMEMPOOL_ENTRY_H +#define BITCOIN_TXMEMPOOL_ENTRY_H + +#include <consensus/amount.h> +#include <consensus/validation.h> +#include <core_memusage.h> +#include <policy/policy.h> +#include <policy/settings.h> +#include <primitives/transaction.h> +#include <util/epochguard.h> +#include <util/overflow.h> + +#include <chrono> +#include <functional> +#include <memory> +#include <set> +#include <stddef.h> +#include <stdint.h> + +class CBlockIndex; + +struct LockPoints { + // Will be set to the blockchain height and median time past + // values that would be necessary to satisfy all relative locktime + // constraints (BIP68) of this tx given our view of block chain history + int height{0}; + int64_t time{0}; + // As long as the current chain descends from the highest height block + // containing one of the inputs used in the calculation, then the cached + // values are still valid even after a reorg. + CBlockIndex* maxInputBlock{nullptr}; +}; + +struct CompareIteratorByHash { + // SFINAE for T where T is either a pointer type (e.g., a txiter) or a reference_wrapper<T> + // (e.g. a wrapped CTxMemPoolEntry&) + template <typename T> + bool operator()(const std::reference_wrapper<T>& a, const std::reference_wrapper<T>& b) const + { + return a.get().GetTx().GetHash() < b.get().GetTx().GetHash(); + } + template <typename T> + bool operator()(const T& a, const T& b) const + { + return a->GetTx().GetHash() < b->GetTx().GetHash(); + } +}; + +/** \class CTxMemPoolEntry + * + * CTxMemPoolEntry stores data about the corresponding transaction, as well + * as data about all in-mempool transactions that depend on the transaction + * ("descendant" transactions). + * + * When a new entry is added to the mempool, we update the descendant state + * (nCountWithDescendants, nSizeWithDescendants, and nModFeesWithDescendants) for + * all ancestors of the newly added transaction. + * + */ + +class CTxMemPoolEntry +{ +public: + typedef std::reference_wrapper<const CTxMemPoolEntry> CTxMemPoolEntryRef; + // two aliases, should the types ever diverge + typedef std::set<CTxMemPoolEntryRef, CompareIteratorByHash> Parents; + typedef std::set<CTxMemPoolEntryRef, CompareIteratorByHash> Children; + +private: + const CTransactionRef tx; + mutable Parents m_parents; + mutable Children m_children; + const CAmount nFee; //!< Cached to avoid expensive parent-transaction lookups + const size_t nTxWeight; //!< ... and avoid recomputing tx weight (also used for GetTxSize()) + const size_t nUsageSize; //!< ... and total memory usage + const int64_t nTime; //!< Local time when entering the mempool + const unsigned int entryHeight; //!< Chain height when entering the mempool + const bool spendsCoinbase; //!< keep track of transactions that spend a coinbase + const int64_t sigOpCost; //!< Total sigop cost + CAmount m_modified_fee; //!< Used for determining the priority of the transaction for mining in a block + LockPoints lockPoints; //!< Track the height and time at which tx was final + + // Information about descendants of this transaction that are in the + // mempool; if we remove this transaction we must remove all of these + // descendants as well. + uint64_t nCountWithDescendants{1}; //!< number of descendant transactions + uint64_t nSizeWithDescendants; //!< ... and size + CAmount nModFeesWithDescendants; //!< ... and total fees (all including us) + + // Analogous statistics for ancestor transactions + uint64_t nCountWithAncestors{1}; + uint64_t nSizeWithAncestors; + CAmount nModFeesWithAncestors; + int64_t nSigOpCostWithAncestors; + +public: + CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee, + int64_t time, unsigned int entry_height, + bool spends_coinbase, + int64_t sigops_cost, LockPoints lp) + : tx{tx}, + nFee{fee}, + nTxWeight(GetTransactionWeight(*tx)), + nUsageSize{RecursiveDynamicUsage(tx)}, + nTime{time}, + entryHeight{entry_height}, + spendsCoinbase{spends_coinbase}, + sigOpCost{sigops_cost}, + m_modified_fee{nFee}, + lockPoints{lp}, + nSizeWithDescendants{GetTxSize()}, + nModFeesWithDescendants{nFee}, + nSizeWithAncestors{GetTxSize()}, + nModFeesWithAncestors{nFee}, + nSigOpCostWithAncestors{sigOpCost} {} + + const CTransaction& GetTx() const { return *this->tx; } + CTransactionRef GetSharedTx() const { return this->tx; } + const CAmount& GetFee() const { return nFee; } + size_t GetTxSize() const + { + return GetVirtualTransactionSize(nTxWeight, sigOpCost, ::nBytesPerSigOp); + } + size_t GetTxWeight() const { return nTxWeight; } + std::chrono::seconds GetTime() const { return std::chrono::seconds{nTime}; } + unsigned int GetHeight() const { return entryHeight; } + int64_t GetSigOpCost() const { return sigOpCost; } + CAmount GetModifiedFee() const { return m_modified_fee; } + size_t DynamicMemoryUsage() const { return nUsageSize; } + const LockPoints& GetLockPoints() const { return lockPoints; } + + // Adjusts the descendant state. + void UpdateDescendantState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount); + // Adjusts the ancestor state + void UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps); + // Updates the modified fees with descendants/ancestors. + void UpdateModifiedFee(CAmount fee_diff) + { + nModFeesWithDescendants = SaturatingAdd(nModFeesWithDescendants, fee_diff); + nModFeesWithAncestors = SaturatingAdd(nModFeesWithAncestors, fee_diff); + m_modified_fee = SaturatingAdd(m_modified_fee, fee_diff); + } + + // Update the LockPoints after a reorg + void UpdateLockPoints(const LockPoints& lp) + { + lockPoints = lp; + } + + uint64_t GetCountWithDescendants() const { return nCountWithDescendants; } + uint64_t GetSizeWithDescendants() const { return nSizeWithDescendants; } + CAmount GetModFeesWithDescendants() const { return nModFeesWithDescendants; } + + bool GetSpendsCoinbase() const { return spendsCoinbase; } + + uint64_t GetCountWithAncestors() const { return nCountWithAncestors; } + uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; } + CAmount GetModFeesWithAncestors() const { return nModFeesWithAncestors; } + int64_t GetSigOpCostWithAncestors() const { return nSigOpCostWithAncestors; } + + const Parents& GetMemPoolParentsConst() const { return m_parents; } + const Children& GetMemPoolChildrenConst() const { return m_children; } + Parents& GetMemPoolParents() const { return m_parents; } + Children& GetMemPoolChildren() const { return m_children; } + + mutable size_t vTxHashesIdx; //!< Index in mempool's vTxHashes + mutable Epoch::Marker m_epoch_marker; //!< epoch when last touched, useful for graph algorithms +}; + +#endif // BITCOIN_TXMEMPOOL_ENTRY_H diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h index 850d0a1cbc..1af7df079e 100644 --- a/src/univalue/include/univalue.h +++ b/src/univalue/include/univalue.h @@ -25,10 +25,7 @@ public: }; UniValue() { typ = VNULL; } - UniValue(UniValue::VType initialType, const std::string& initialStr = "") { - typ = initialType; - val = initialStr; - } + UniValue(UniValue::VType type, std::string str = {}) : typ{type}, val{std::move(str)} {} template <typename Ref, typename T = std::remove_cv_t<std::remove_reference_t<Ref>>, std::enable_if_t<std::is_floating_point_v<T> || // setFloat std::is_same_v<bool, T> || // setBool @@ -54,12 +51,12 @@ public: void setNull(); void setBool(bool val); - void setNumStr(const std::string& val); + void setNumStr(std::string str); void setInt(uint64_t val); void setInt(int64_t val); void setInt(int val_) { return setInt(int64_t{val_}); } void setFloat(double val); - void setStr(const std::string& val); + void setStr(std::string str); void setArray(); void setObject(); diff --git a/src/univalue/lib/univalue.cpp b/src/univalue/lib/univalue.cpp index 4448981d3e..5aa39edb75 100644 --- a/src/univalue/lib/univalue.cpp +++ b/src/univalue/lib/univalue.cpp @@ -44,15 +44,15 @@ static bool validNumStr(const std::string& s) return (tt == JTOK_NUMBER); } -void UniValue::setNumStr(const std::string& val_) +void UniValue::setNumStr(std::string str) { - if (!validNumStr(val_)) { - throw std::runtime_error{"The string '" + val_ + "' is not a valid JSON number"}; + if (!validNumStr(str)) { + throw std::runtime_error{"The string '" + str + "' is not a valid JSON number"}; } clear(); typ = VNUM; - val = val_; + val = std::move(str); } void UniValue::setInt(uint64_t val_) @@ -82,11 +82,11 @@ void UniValue::setFloat(double val_) return setNumStr(oss.str()); } -void UniValue::setStr(const std::string& val_) +void UniValue::setStr(std::string str) { clear(); typ = VSTR; - val = val_; + val = std::move(str); } void UniValue::setArray() diff --git a/src/univalue/test/object.cpp b/src/univalue/test/object.cpp index 94d7343ff3..65e82543e4 100644 --- a/src/univalue/test/object.cpp +++ b/src/univalue/test/object.cpp @@ -11,6 +11,7 @@ #include <memory> #include <stdexcept> #include <string> +#include <string_view> #include <vector> #define BOOST_CHECK(expr) assert(expr) @@ -160,6 +161,14 @@ void univalue_set() BOOST_CHECK(v.isStr()); BOOST_CHECK_EQUAL(v.getValStr(), "zum"); + { + std::string_view sv{"ab\0c", 4}; + UniValue j{sv}; + BOOST_CHECK(j.isStr()); + BOOST_CHECK_EQUAL(j.getValStr(), sv); + BOOST_CHECK_EQUAL(j.write(), "\"ab\\u0000c\""); + } + v.setFloat(-1.01); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "-1.01"); diff --git a/src/util/check.cpp b/src/util/check.cpp index 2a9f885560..e50d6087d0 100644 --- a/src/util/check.cpp +++ b/src/util/check.cpp @@ -4,8 +4,23 @@ #include <util/check.h> +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <tinyformat.h> +#include <cstdio> +#include <cstdlib> +#include <string> + + +NonFatalCheckError::NonFatalCheckError(const char* msg, const char* file, int line, const char* func) + : std::runtime_error{ + strprintf("Internal bug detected: \"%s\"\n%s:%d (%s)\nPlease report this issue here: %s\n", msg, file, line, func, PACKAGE_BUGREPORT)} +{ +} + void assertion_fail(const char* file, int line, const char* func, const char* assertion) { auto str = strprintf("%s:%s %s: Assertion `%s' failed.\n", file, line, func, assertion); diff --git a/src/util/check.h b/src/util/check.h index aca957925a..b6c03bed2a 100644 --- a/src/util/check.h +++ b/src/util/check.h @@ -5,30 +5,23 @@ #ifndef BITCOIN_UTIL_CHECK_H #define BITCOIN_UTIL_CHECK_H -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif - -#include <tinyformat.h> +#include <attributes.h> #include <stdexcept> +#include <utility> class NonFatalCheckError : public std::runtime_error { - using std::runtime_error::runtime_error; +public: + NonFatalCheckError(const char* msg, const char* file, int line, const char* func); }; -#define format_internal_error(msg, file, line, func, report) \ - strprintf("Internal bug detected: \"%s\"\n%s:%d (%s)\nPlease report this issue here: %s\n", \ - msg, file, line, func, report) - /** Helper for CHECK_NONFATAL() */ template <typename T> -T&& inline_check_non_fatal(T&& val, const char* file, int line, const char* func, const char* assertion) +T&& inline_check_non_fatal(LIFETIMEBOUND T&& val, const char* file, int line, const char* func, const char* assertion) { - if (!(val)) { - throw NonFatalCheckError( - format_internal_error(assertion, file, line, func, PACKAGE_BUGREPORT)); + if (!val) { + throw NonFatalCheckError{assertion, file, line, func}; } return std::forward<T>(val); } @@ -56,7 +49,7 @@ void assertion_fail(const char* file, int line, const char* func, const char* as /** Helper for Assert()/Assume() */ template <bool IS_ASSERT, typename T> -T&& inline_assertion_check(T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion) +T&& inline_assertion_check(LIFETIMEBOUND T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion) { if constexpr (IS_ASSERT #ifdef ABORT_ON_FAILED_ASSUME @@ -87,11 +80,9 @@ T&& inline_assertion_check(T&& val, [[maybe_unused]] const char* file, [[maybe_u /** * NONFATAL_UNREACHABLE() is a macro that is used to mark unreachable code. It throws a NonFatalCheckError. - * This is used to mark code that is not yet implemented or is not yet reachable. */ #define NONFATAL_UNREACHABLE() \ throw NonFatalCheckError( \ - format_internal_error("Unreachable code reached (non-fatal)", \ - __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT)) + "Unreachable code reached (non-fatal)", __FILE__, __LINE__, __func__) #endif // BITCOIN_UTIL_CHECK_H diff --git a/src/util/sock.cpp b/src/util/sock.cpp index 84ac2759fa..e3d30c24b3 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -4,11 +4,11 @@ #include <compat/compat.h> #include <logging.h> -#include <threadinterrupt.h> #include <tinyformat.h> #include <util/sock.h> #include <util/syserror.h> #include <util/system.h> +#include <util/threadinterrupt.h> #include <util/time.h> #include <memory> diff --git a/src/util/sock.h b/src/util/sock.h index 7912284904..a7347df8ee 100644 --- a/src/util/sock.h +++ b/src/util/sock.h @@ -6,7 +6,7 @@ #define BITCOIN_UTIL_SOCK_H #include <compat/compat.h> -#include <threadinterrupt.h> +#include <util/threadinterrupt.h> #include <util/time.h> #include <chrono> diff --git a/src/threadinterrupt.cpp b/src/util/threadinterrupt.cpp index e28b447c1d..70731d6f31 100644 --- a/src/threadinterrupt.cpp +++ b/src/util/threadinterrupt.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <threadinterrupt.h> +#include <util/threadinterrupt.h> #include <sync.h> diff --git a/src/threadinterrupt.h b/src/util/threadinterrupt.h index 979bc2ee3e..d95cbb9aba 100644 --- a/src/threadinterrupt.h +++ b/src/util/threadinterrupt.h @@ -2,8 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_THREADINTERRUPT_H -#define BITCOIN_THREADINTERRUPT_H +#ifndef BITCOIN_UTIL_THREADINTERRUPT_H +#define BITCOIN_UTIL_THREADINTERRUPT_H #include <sync.h> #include <threadsafety.h> @@ -33,4 +33,4 @@ private: std::atomic<bool> flag; }; -#endif // BITCOIN_THREADINTERRUPT_H +#endif // BITCOIN_UTIL_THREADINTERRUPT_H diff --git a/src/validation.cpp b/src/validation.cpp index 37e68cfe4a..1cf6fc0675 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -42,6 +42,7 @@ #include <tinyformat.h> #include <txdb.h> #include <txmempool.h> +#include <txmempool_entry.h> #include <uint256.h> #include <undo.h> #include <util/check.h> // For NDEBUG compile time check @@ -120,13 +121,6 @@ RecursiveMutex cs_main; GlobalMutex g_best_block_mutex; std::condition_variable g_best_block_cv; uint256 g_best_block; -bool g_parallel_script_checks{false}; -bool fCheckBlockIndex = false; -bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED; -int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; - -uint256 hashAssumeValid; -arith_uint256 nMinimumChainWork; const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locator) const { @@ -1545,10 +1539,12 @@ bool Chainstate::IsInitialBlockDownload() const return true; if (m_chain.Tip() == nullptr) return true; - if (m_chain.Tip()->nChainWork < nMinimumChainWork) + if (m_chain.Tip()->nChainWork < m_chainman.MinimumChainWork()) { return true; - if (m_chain.Tip()->GetBlockTime() < (GetTime() - nMaxTipAge)) + } + if (m_chain.Tip()->Time() < NodeClock::now() - m_chainman.m_options.max_tip_age) { return true; + } LogPrintf("Leaving InitialBlockDownload (latching to false)\n"); m_cached_finished_ibd.store(true, std::memory_order_relaxed); return false; @@ -1994,6 +1990,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, uint256 block_hash{block.GetHash()}; assert(*pindex->phashBlock == block_hash); + const bool parallel_script_checks{scriptcheckqueue.HasThreads()}; const auto time_start{SteadyClock::now()}; const CChainParams& params{m_chainman.GetParams()}; @@ -2036,17 +2033,17 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, } bool fScriptChecks = true; - if (!hashAssumeValid.IsNull()) { + if (!m_chainman.AssumedValidBlock().IsNull()) { // We've been configured with the hash of a block which has been externally verified to have a valid history. // A suitable default value is included with the software and updated from time to time. Because validity // relative to a piece of software is an objective fact these defaults can be easily reviewed. // This setting doesn't force the selection of any particular chain but makes validating some faster by // effectively caching the result of part of the verification. - BlockMap::const_iterator it = m_blockman.m_block_index.find(hashAssumeValid); + BlockMap::const_iterator it{m_blockman.m_block_index.find(m_chainman.AssumedValidBlock())}; if (it != m_blockman.m_block_index.end()) { if (it->second.GetAncestor(pindex->nHeight) == pindex && m_chainman.m_best_header->GetAncestor(pindex->nHeight) == pindex && - m_chainman.m_best_header->nChainWork >= nMinimumChainWork) { + m_chainman.m_best_header->nChainWork >= m_chainman.MinimumChainWork()) { // This block is a member of the assumed verified chain and an ancestor of the best header. // Script verification is skipped when connecting blocks under the // assumevalid block. Assuming the assumevalid block is valid this @@ -2059,7 +2056,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, // it hard to hide the implication of the demand. This also avoids having release candidates // that are hardly doing any signature verification at all in testing without having to // artificially set the default assumed verified block further back. - // The test against nMinimumChainWork prevents the skipping when denied access to any chain at + // The test against the minimum chain work prevents the skipping when denied access to any chain at // least as good as the expected chain. fScriptChecks = (GetBlockProofEquivalentTime(*m_chainman.m_best_header, *pindex, *m_chainman.m_best_header, params.GetConsensus()) <= 60 * 60 * 24 * 7 * 2); } @@ -2183,7 +2180,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, // in multiple threads). Preallocate the vector size so a new allocation // doesn't invalidate pointers into the vector, and keep txsdata in scope // for as long as `control`. - CCheckQueueControl<CScriptCheck> control(fScriptChecks && g_parallel_script_checks ? &scriptcheckqueue : nullptr); + CCheckQueueControl<CScriptCheck> control(fScriptChecks && parallel_script_checks ? &scriptcheckqueue : nullptr); std::vector<PrecomputedTransactionData> txsdata(block.vtx.size()); std::vector<int> prevheights; @@ -2242,7 +2239,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, std::vector<CScriptCheck> vChecks; bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ TxValidationState tx_state; - if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], g_parallel_script_checks ? &vChecks : nullptr)) { + if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], parallel_script_checks ? &vChecks : nullptr)) { // Any transaction validation failure in ConnectBlock is a block consensus failure state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), tx_state.GetDebugMessage()); @@ -3532,7 +3529,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", "incorrect proof of work"); // Check against checkpoints - if (fCheckpointsEnabled) { + if (chainman.m_options.checkpoints_enabled) { // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our // BlockIndex(). @@ -3680,12 +3677,12 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida CBlockIndex* pindexPrev = nullptr; BlockMap::iterator mi{m_blockman.m_block_index.find(block.hashPrevBlock)}; if (mi == m_blockman.m_block_index.end()) { - LogPrint(BCLog::VALIDATION, "%s: %s prev block not found\n", __func__, hash.ToString()); + LogPrint(BCLog::VALIDATION, "header %s has prev block not found: %s\n", hash.ToString(), block.hashPrevBlock.ToString()); return state.Invalid(BlockValidationResult::BLOCK_MISSING_PREV, "prev-blk-not-found"); } pindexPrev = &((*mi).second); if (pindexPrev->nStatus & BLOCK_FAILED_MASK) { - LogPrint(BCLog::VALIDATION, "%s: %s prev block invalid\n", __func__, hash.ToString()); + LogPrint(BCLog::VALIDATION, "header %s has prev block invalid: %s\n", hash.ToString(), block.hashPrevBlock.ToString()); return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk"); } if (!ContextualCheckBlockHeader(block, state, m_blockman, *this, pindexPrev, m_options.adjusted_time_callback())) { @@ -3726,7 +3723,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida m_blockman.m_dirty_blockindex.insert(invalid_walk); invalid_walk = invalid_walk->pprev; } - LogPrint(BCLog::VALIDATION, "%s: %s prev block invalid\n", __func__, hash.ToString()); + LogPrint(BCLog::VALIDATION, "header %s has prev block invalid: %s\n", hash.ToString(), block.hashPrevBlock.ToString()); return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk"); } } @@ -3846,7 +3843,7 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV // If our tip is behind, a peer could try to send us // low-work blocks on a fake chain that we would never // request; don't process these. - if (pindex->nChainWork < nMinimumChainWork) return true; + if (pindex->nChainWork < m_chainman.MinimumChainWork()) return true; } const CChainParams& params{m_chainman.GetParams()}; @@ -4393,6 +4390,8 @@ void Chainstate::LoadExternalBlockFile( try { // This takes over fileIn and calls fclose() on it in the CBufferedFile destructor CBufferedFile blkdat(fileIn, 2*MAX_BLOCK_SERIALIZED_SIZE, MAX_BLOCK_SERIALIZED_SIZE+8, SER_DISK, CLIENT_VERSION); + // nRewind indicates where to resume scanning in case something goes wrong, + // such as a block fails to deserialize. uint64_t nRewind = blkdat.GetPos(); while (!blkdat.eof()) { if (ShutdownRequested()) return; @@ -4416,28 +4415,30 @@ void Chainstate::LoadExternalBlockFile( continue; } catch (const std::exception&) { // no valid block header found; don't complain + // (this happens at the end of every blk.dat file) break; } try { - // read block - uint64_t nBlockPos = blkdat.GetPos(); + // read block header + const uint64_t nBlockPos{blkdat.GetPos()}; if (dbp) dbp->nPos = nBlockPos; blkdat.SetLimit(nBlockPos + nSize); - std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(); - CBlock& block = *pblock; - blkdat >> block; - nRewind = blkdat.GetPos(); - - uint256 hash = block.GetHash(); + CBlockHeader header; + blkdat >> header; + const uint256 hash{header.GetHash()}; + // Skip the rest of this block (this may read from disk into memory); position to the marker before the + // next block, but it's still possible to rewind to the start of the current block (without a disk read). + nRewind = nBlockPos + nSize; + blkdat.SkipTo(nRewind); { LOCK(cs_main); // detect out of order blocks, and store them for later - if (hash != params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) { + if (hash != params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(header.hashPrevBlock)) { LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(), - block.hashPrevBlock.ToString()); + header.hashPrevBlock.ToString()); if (dbp && blocks_with_unknown_parent) { - blocks_with_unknown_parent->emplace(block.hashPrevBlock, *dbp); + blocks_with_unknown_parent->emplace(header.hashPrevBlock, *dbp); } continue; } @@ -4445,13 +4446,19 @@ void Chainstate::LoadExternalBlockFile( // process in case the block isn't known yet const CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash); if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) { - BlockValidationState state; - if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr, true)) { - nLoaded++; - } - if (state.IsError()) { - break; - } + // This block can be processed immediately; rewind to its start, read and deserialize it. + blkdat.SetPos(nBlockPos); + std::shared_ptr<CBlock> pblock{std::make_shared<CBlock>()}; + blkdat >> *pblock; + nRewind = blkdat.GetPos(); + + BlockValidationState state; + if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr, true)) { + nLoaded++; + } + if (state.IsError()) { + break; + } } else if (hash != params.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) { LogPrint(BCLog::REINDEX, "Block Import: already had block %s at height %d\n", hash.ToString(), pindex->nHeight); } @@ -4517,7 +4524,7 @@ void Chainstate::LoadExternalBlockFile( void Chainstate::CheckBlockIndex() { - if (!fCheckBlockIndex) { + if (!m_chainman.ShouldCheckBlockIndex()) { return; } @@ -5251,6 +5258,22 @@ void ChainstateManager::ResetChainstates() m_active_chainstate = nullptr; } +/** + * Apply default chain params to nullopt members. + * This helps to avoid coding errors around the accidental use of the compare + * operators that accept nullopt, thus ignoring the intended default value. + */ +static ChainstateManager::Options&& Flatten(ChainstateManager::Options&& opts) +{ + if (!opts.check_block_index.has_value()) opts.check_block_index = opts.chainparams.DefaultConsistencyChecks(); + if (!opts.minimum_chain_work.has_value()) opts.minimum_chain_work = UintToArith256(opts.chainparams.GetConsensus().nMinimumChainWork); + if (!opts.assumed_valid_block.has_value()) opts.assumed_valid_block = opts.chainparams.GetConsensus().defaultAssumeValid; + Assert(opts.adjusted_time_callback); + return std::move(opts); +} + +ChainstateManager::ChainstateManager(Options options) : m_options{Flatten(std::move(options))} {} + ChainstateManager::~ChainstateManager() { LOCK(::cs_main); diff --git a/src/validation.h b/src/validation.h index fb6d59f92e..b8151dc1fc 100644 --- a/src/validation.h +++ b/src/validation.h @@ -63,8 +63,6 @@ struct Params; static const int MAX_SCRIPTCHECK_THREADS = 15; /** -par default (number of script-checking threads, 0 = auto) */ static const int DEFAULT_SCRIPTCHECK_THREADS = 0; -static const int64_t DEFAULT_MAX_TIP_AGE = 24 * 60 * 60; -static const bool DEFAULT_CHECKPOINTS_ENABLED = true; /** Default for -stopatheight */ static const int DEFAULT_STOPATHEIGHT = 0; /** Block files containing a block-height within MIN_BLOCKS_TO_KEEP of ActiveChain().Tip() will not be pruned. */ @@ -93,20 +91,6 @@ extern GlobalMutex g_best_block_mutex; extern std::condition_variable g_best_block_cv; /** Used to notify getblocktemplate RPC of new tips. */ extern uint256 g_best_block; -/** Whether there are dedicated script-checking threads running. - * False indicates all script checking is done on the main threadMessageHandler thread. - */ -extern bool g_parallel_script_checks; -extern bool fCheckBlockIndex; -extern bool fCheckpointsEnabled; -/** If the tip is older than this (in seconds), the node is considered to be in initial block download. */ -extern int64_t nMaxTipAge; - -/** Block hash whose ancestors we will assume to have valid scripts without checking them. */ -extern uint256 hashAssumeValid; - -/** Minimum work we will assume exists on some valid chain. */ -extern arith_uint256 nMinimumChainWork; /** Documentation for argument 'checklevel'. */ extern const std::vector<std::string> CHECKLEVEL_DOC; @@ -698,7 +682,7 @@ public: /** * Make various assertions about the state of the block index. * - * By default this only executes fully when using the Regtest chain; see: fCheckBlockIndex. + * By default this only executes fully when using the Regtest chain; see: m_options.check_block_index. */ void CheckBlockIndex(); @@ -863,13 +847,13 @@ private: public: using Options = kernel::ChainstateManagerOpts; - explicit ChainstateManager(Options options) : m_options{std::move(options)} - { - Assert(m_options.adjusted_time_callback); - } + explicit ChainstateManager(Options options); const CChainParams& GetParams() const { return m_options.chainparams; } const Consensus::Params& GetConsensus() const { return m_options.chainparams.GetConsensus(); } + bool ShouldCheckBlockIndex() const { return *Assert(m_options.check_block_index); } + const arith_uint256& MinimumChainWork() const { return *Assert(m_options.minimum_chain_work); } + const uint256& AssumedValidBlock() const { return *Assert(m_options.assumed_valid_block); } /** * Alias for ::cs_main. diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 613c5b65ef..740c39d99d 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -17,6 +17,8 @@ #include <unordered_map> #include <utility> +const std::string RemovalReasonToString(const MemPoolRemovalReason& r) noexcept; + /** * MainSignalsImpl manages a list of shared_ptr<CValidationInterface> callbacks. * @@ -215,9 +217,10 @@ void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef& tx, MemP auto event = [tx, reason, mempool_sequence, this] { m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(tx, reason, mempool_sequence); }); }; - ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__, + ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s reason=%s", __func__, tx->GetHash().ToString(), - tx->GetWitnessHash().ToString()); + tx->GetWitnessHash().ToString(), + RemovalReasonToString(reason)); } void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex) { diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index d08d3664c4..b56a6d3aee 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -37,7 +37,7 @@ public: bool m_include_unsafe_inputs = false; //! If true, the selection process can add extra unselected inputs from the wallet //! while requires all selected inputs be used - bool m_allow_other_inputs = false; + bool m_allow_other_inputs = true; //! Includes watch only addresses which are solvable bool fAllowWatchOnly = false; //! Override automatic min/max checks on fee, m_feerate must be set if true diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index b568e90998..a8be6cd83a 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -444,6 +444,12 @@ void SelectionResult::AddInput(const OutputGroup& group) m_use_effective = !group.m_subtract_fee_outputs; } +void SelectionResult::AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs) +{ + util::insert(m_selected_inputs, inputs); + m_use_effective = !subtract_fee_outputs; +} + void SelectionResult::Merge(const SelectionResult& other) { m_target += other.m_target; diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index 761c2be0b3..b23dd10867 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -308,6 +308,7 @@ public: void Clear(); void AddInput(const OutputGroup& group); + void AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs); /** Calculates and stores the waste for this selection via GetSelectionWaste */ void ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee); diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 1206a428fc..1d2d7d2a10 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -1589,7 +1589,8 @@ RPCHelpMan importdescriptors() return RPCHelpMan{"importdescriptors", "\nImport descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n" "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n" - "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n", + "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n" + "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n", { {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", { @@ -1887,7 +1888,9 @@ RPCHelpMan restorewallet() { return RPCHelpMan{ "restorewallet", - "\nRestore and loads a wallet from backup.\n", + "\nRestore and loads a wallet from backup.\n" + "\nThe rescan is significantly faster if a descriptor wallet is restored" + "\nand block filters are available (using startup option \"-blockfilterindex=1\").\n", { {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"}, {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."}, diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 9c0c953a7a..6021e4bf4f 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -515,6 +515,7 @@ RPCHelpMan listunspent() {"maximumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Maximum value of each UTXO in " + CURRENCY_UNIT + ""}, {"maximumCount", RPCArg::Type::NUM, RPCArg::DefaultHint{"unlimited"}, "Maximum number of UTXOs"}, {"minimumSumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""}, + {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase UTXOs"} }, RPCArgOptions{.oneline_description="query_options"}}, }, @@ -590,10 +591,8 @@ RPCHelpMan listunspent() include_unsafe = request.params[3].get_bool(); } - CAmount nMinimumAmount = 0; - CAmount nMaximumAmount = MAX_MONEY; - CAmount nMinimumSumAmount = MAX_MONEY; - uint64_t nMaximumCount = 0; + CoinFilterParams filter_coins; + filter_coins.min_amount = 0; if (!request.params[4].isNull()) { const UniValue& options = request.params[4].get_obj(); @@ -604,20 +603,25 @@ RPCHelpMan listunspent() {"maximumAmount", UniValueType()}, {"minimumSumAmount", UniValueType()}, {"maximumCount", UniValueType(UniValue::VNUM)}, + {"include_immature_coinbase", UniValueType(UniValue::VBOOL)} }, true, true); if (options.exists("minimumAmount")) - nMinimumAmount = AmountFromValue(options["minimumAmount"]); + filter_coins.min_amount = AmountFromValue(options["minimumAmount"]); if (options.exists("maximumAmount")) - nMaximumAmount = AmountFromValue(options["maximumAmount"]); + filter_coins.max_amount = AmountFromValue(options["maximumAmount"]); if (options.exists("minimumSumAmount")) - nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]); + filter_coins.min_sum_amount = AmountFromValue(options["minimumSumAmount"]); if (options.exists("maximumCount")) - nMaximumCount = options["maximumCount"].getInt<int64_t>(); + filter_coins.max_count = options["maximumCount"].getInt<int64_t>(); + + if (options.exists("include_immature_coinbase")) { + filter_coins.include_immature_coinbase = options["include_immature_coinbase"].get_bool(); + } } // Make sure the results are valid at least up to the most recent block @@ -633,7 +637,7 @@ RPCHelpMan listunspent() cctl.m_max_depth = nMaxDepth; cctl.m_include_unsafe_inputs = include_unsafe; LOCK(pwallet->cs_wallet); - vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).All(); + vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, filter_coins).All(); } LOCK(pwallet->cs_wallet); diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 6cb33fc11e..0fa693e7e7 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -746,7 +746,7 @@ RPCHelpMan fundrawtransaction() "If that happens, you will need to fund the transaction with different inputs and republish it."}, {"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"}, {"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"}, - {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."}, {"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only.\n" "Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n" "e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."}, @@ -1143,7 +1143,7 @@ RPCHelpMan send() {"add_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "When false, returns a serialized transaction which will not be added to the wallet or broadcast"}, {"change_address", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"}, {"change_position", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"}, - {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\" and \"bech32m\"."}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, {"include_watching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only.\n" "Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n" @@ -1385,7 +1385,9 @@ RPCHelpMan sendall() total_input_value += tx->tx->vout[input.prevout.n].nValue; } } else { - for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, /*nMinimumAmount=*/0).All()) { + CoinFilterParams coins_params; + coins_params.min_amount = 0; + for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, coins_params).All()) { CHECK_NONFATAL(output.input_bytes > 0); if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) { continue; @@ -1596,7 +1598,7 @@ RPCHelpMan walletcreatefundedpsbt() "If that happens, you will need to fund the transaction with different inputs and republish it."}, {"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"}, {"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"}, - {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."}, {"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only"}, {"lockUnspents", RPCArg::Type::BOOL, RPCArg::Default{false}, "Lock selected unspent outputs"}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp index 0e13e4756b..3c10b47082 100644 --- a/src/wallet/rpc/transactions.cpp +++ b/src/wallet/rpc/transactions.cpp @@ -447,7 +447,7 @@ RPCHelpMan listtransactions() {RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>( { {RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction."}, - {RPCResult::Type::STR, "address", "The bitcoin address of the transaction."}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address of the transaction (not returned if the output does not have an address, e.g. OP_RETURN null data)."}, {RPCResult::Type::STR, "category", "The transaction category.\n" "\"send\" Transactions sent.\n" "\"receive\" Non-coinbase transactions received.\n" @@ -561,7 +561,7 @@ RPCHelpMan listsinceblock() {RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>( { {RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction."}, - {RPCResult::Type::STR, "address", "The bitcoin address of the transaction."}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address of the transaction (not returned if the output does not have an address, e.g. OP_RETURN null data)."}, {RPCResult::Type::STR, "category", "The transaction category.\n" "\"send\" Transactions sent.\n" "\"receive\" Non-coinbase transactions received.\n" @@ -839,7 +839,9 @@ RPCHelpMan rescanblockchain() { return RPCHelpMan{"rescanblockchain", "\nRescan the local blockchain for wallet related transactions.\n" - "Note: Use \"getwalletinfo\" to query the scanning progress.\n", + "Note: Use \"getwalletinfo\" to query the scanning progress.\n" + "The rescan is significantly faster when used on a descriptor wallet\n" + "and block filters are available (using startup option \"-blockfilterindex=1\").\n", { {"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"}, {"stop_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "the last block height that should be scanned. If none is provided it will rescan up to the tip at return time of this call."}, diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp index 1aa2a87e99..26270f23ed 100644 --- a/src/wallet/rpc/util.cpp +++ b/src/wallet/rpc/util.cpp @@ -4,9 +4,9 @@ #include <wallet/rpc/util.h> +#include <common/url.h> #include <rpc/util.h> #include <util/translation.h> -#include <util/url.h> #include <wallet/context.h> #include <wallet/wallet.h> diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 39afb79600..896ade77dd 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -2502,14 +2502,23 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& keys->Merge(std::move(*script_keys)); } else { // Maybe there are pubkeys listed that we can sign for - script_keys = std::make_unique<FlatSigningProvider>(); - for (const auto& pk_pair : input.hd_keypaths) { - const CPubKey& pubkey = pk_pair.first; - std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey); - if (pk_keys) { - keys->Merge(std::move(*pk_keys)); - } + std::vector<CPubKey> pubkeys; + + // ECDSA Pubkeys + for (const auto& [pk, _] : input.hd_keypaths) { + pubkeys.push_back(pk); } + + // Taproot output pubkey + std::vector<std::vector<unsigned char>> sols; + if (Solver(script, sols) == TxoutType::WITNESS_V1_TAPROOT) { + sols[0].insert(sols[0].begin(), 0x02); + pubkeys.emplace_back(sols[0]); + sols[0][0] = 0x03; + pubkeys.emplace_back(sols[0]); + } + + // Taproot pubkeys for (const auto& pk_pair : input.m_tap_bip32_paths) { const XOnlyPubKey& pubkey = pk_pair.first; for (unsigned char prefix : {0x02, 0x03}) { @@ -2517,10 +2526,14 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& std::copy(pubkey.begin(), pubkey.end(), b + 1); CPubKey fullpubkey; fullpubkey.Set(b, b + 33); - std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(fullpubkey); - if (pk_keys) { - keys->Merge(std::move(*pk_keys)); - } + pubkeys.push_back(fullpubkey); + } + } + + for (const auto& pubkey : pubkeys) { + std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey); + if (pk_keys) { + keys->Merge(std::move(*pk_keys)); } } } @@ -2645,16 +2658,26 @@ const WalletDescriptor DescriptorScriptPubKeyMan::GetWalletDescriptor() const const std::unordered_set<CScript, SaltedSipHasher> DescriptorScriptPubKeyMan::GetScriptPubKeys() const { + return GetScriptPubKeys(0); +} + +const std::unordered_set<CScript, SaltedSipHasher> DescriptorScriptPubKeyMan::GetScriptPubKeys(int32_t minimum_index) const +{ LOCK(cs_desc_man); std::unordered_set<CScript, SaltedSipHasher> script_pub_keys; script_pub_keys.reserve(m_map_script_pub_keys.size()); - for (auto const& script_pub_key: m_map_script_pub_keys) { - script_pub_keys.insert(script_pub_key.first); + for (auto const& [script_pub_key, index] : m_map_script_pub_keys) { + if (index >= minimum_index) script_pub_keys.insert(script_pub_key); } return script_pub_keys; } +int32_t DescriptorScriptPubKeyMan::GetEndRange() const +{ + return m_max_cached_index + 1; +} + bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool priv) const { LOCK(cs_desc_man); diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 3ab489c374..eb77015956 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -643,6 +643,8 @@ public: const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override; + const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys(int32_t minimum_index) const; + int32_t GetEndRange() const; bool GetDescriptorString(std::string& out, const bool priv) const; diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 6833f9a095..8c0d56a1cb 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -143,14 +143,55 @@ static OutputType GetOutputType(TxoutType type, bool is_from_p2sh) } } +// Fetch and validate the coin control selected inputs. +// Coins could be internal (from the wallet) or external. +util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const CCoinControl& coin_control, + const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) +{ + PreSelectedInputs result; + std::vector<COutPoint> vPresetInputs; + coin_control.ListSelected(vPresetInputs); + for (const COutPoint& outpoint : vPresetInputs) { + int input_bytes = -1; + CTxOut txout; + if (auto ptr_wtx = wallet.GetWalletTx(outpoint.hash)) { + // Clearly invalid input, fail + if (ptr_wtx->tx->vout.size() <= outpoint.n) { + return util::Error{strprintf(_("Invalid pre-selected input %s"), outpoint.ToString())}; + } + txout = ptr_wtx->tx->vout.at(outpoint.n); + input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control); + } else { + // The input is external. We did not find the tx in mapWallet. + if (!coin_control.GetExternalOutput(outpoint, txout)) { + return util::Error{strprintf(_("Not found pre-selected input %s"), outpoint.ToString())}; + } + } + + if (input_bytes == -1) { + input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control); + } + + // If available, override calculated size with coin control specified size + if (coin_control.HasInputWeight(outpoint)) { + input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0); + } + + if (input_bytes == -1) { + return util::Error{strprintf(_("Not solvable pre-selected input %s"), outpoint.ToString())}; // Not solvable, can't estimate size for fee + } + + /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */ + COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate); + result.Insert(output, coin_selection_params.m_subtract_fee_outputs); + } + return result; +} + CoinsResult AvailableCoins(const CWallet& wallet, const CCoinControl* coinControl, std::optional<CFeeRate> feerate, - const CAmount& nMinimumAmount, - const CAmount& nMaximumAmount, - const CAmount& nMinimumSumAmount, - const uint64_t nMaximumCount, - bool only_spendable) + const CoinFilterParams& params) { AssertLockHeld(wallet.cs_wallet); @@ -168,7 +209,7 @@ CoinsResult AvailableCoins(const CWallet& wallet, const uint256& wtxid = entry.first; const CWalletTx& wtx = entry.second; - if (wallet.IsTxImmatureCoinBase(wtx)) + if (wallet.IsTxImmatureCoinBase(wtx) && !params.include_immature_coinbase) continue; int nDepth = wallet.GetTxDepthInMainChain(wtx); @@ -227,10 +268,11 @@ CoinsResult AvailableCoins(const CWallet& wallet, const CTxOut& output = wtx.tx->vout[i]; const COutPoint outpoint(wtxid, i); - if (output.nValue < nMinimumAmount || output.nValue > nMaximumAmount) + if (output.nValue < params.min_amount || output.nValue > params.max_amount) continue; - if (coinControl && coinControl->HasSelected() && !coinControl->m_allow_other_inputs && !coinControl->IsSelected(outpoint)) + // Skip manually selected coins (the caller can fetch them directly) + if (coinControl && coinControl->HasSelected() && coinControl->IsSelected(outpoint)) continue; if (wallet.IsLockedCoin(outpoint)) @@ -258,7 +300,7 @@ CoinsResult AvailableCoins(const CWallet& wallet, bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); // Filter by spendable outputs only - if (!spendable && only_spendable) continue; + if (!spendable && params.only_spendable) continue; // Obtain script type std::vector<std::vector<uint8_t>> script_solutions; @@ -282,14 +324,14 @@ CoinsResult AvailableCoins(const CWallet& wallet, // Cache total amount as we go result.total_amount += output.nValue; // Checks the sum amount of all UTXO's. - if (nMinimumSumAmount != MAX_MONEY) { - if (result.total_amount >= nMinimumSumAmount) { + if (params.min_sum_amount != MAX_MONEY) { + if (result.total_amount >= params.min_sum_amount) { return result; } } // Checks the maximum number of UTXO's. - if (nMaximumCount > 0 && result.Size() >= nMaximumCount) { + if (params.max_count > 0 && result.Size() >= params.max_count) { return result; } } @@ -298,21 +340,16 @@ CoinsResult AvailableCoins(const CWallet& wallet, return result; } -CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) +CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, CoinFilterParams params) { - return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, /*only_spendable=*/false); + params.only_spendable = false; + return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, params); } CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl) { LOCK(wallet.cs_wallet); - return AvailableCoins(wallet, coinControl, - /*feerate=*/ std::nullopt, - /*nMinimumAmount=*/ 1, - /*nMaximumAmount=*/ MAX_MONEY, - /*nMinimumSumAmount=*/ MAX_MONEY, - /*nMaximumCount=*/ 0 - ).total_amount; + return AvailableCoins(wallet, coinControl).total_amount; } const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) @@ -522,82 +559,42 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons return best_result; } -std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params) +std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const PreSelectedInputs& pre_set_inputs, + const CAmount& nTargetValue, const CCoinControl& coin_control, + const CoinSelectionParams& coin_selection_params) { - CAmount value_to_select = nTargetValue; - - OutputGroup preset_inputs(coin_selection_params); + // Deduct preset inputs amount from the search target + CAmount selection_target = nTargetValue - pre_set_inputs.total_amount; - // calculate value from preset inputs and store them - std::set<COutPoint> preset_coins; + // Return if automatic coin selection is disabled, and we don't cover the selection target + if (!coin_control.m_allow_other_inputs && selection_target > 0) return std::nullopt; - std::vector<COutPoint> vPresetInputs; - coin_control.ListSelected(vPresetInputs); - for (const COutPoint& outpoint : vPresetInputs) { - int input_bytes = -1; - CTxOut txout; - auto ptr_wtx = wallet.GetWalletTx(outpoint.hash); - if (ptr_wtx) { - // Clearly invalid input, fail - if (ptr_wtx->tx->vout.size() <= outpoint.n) { - return std::nullopt; - } - txout = ptr_wtx->tx->vout.at(outpoint.n); - input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control); - } else { - // The input is external. We did not find the tx in mapWallet. - if (!coin_control.GetExternalOutput(outpoint, txout)) { - return std::nullopt; - } - } - - if (input_bytes == -1) { - input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control); - } - - // If available, override calculated size with coin control specified size - if (coin_control.HasInputWeight(outpoint)) { - input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0); - } - - if (input_bytes == -1) { - return std::nullopt; // Not solvable, can't estimate size for fee - } - - /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */ - COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate); - if (coin_selection_params.m_subtract_fee_outputs) { - value_to_select -= output.txout.nValue; - } else { - value_to_select -= output.GetEffectiveValue(); - } - preset_coins.insert(outpoint); - /* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done. - * positive_only is set to false because we want to include all preset inputs, even if they are dust. - */ - preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); - } - - // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) - if (coin_control.HasSelected() && !coin_control.m_allow_other_inputs) { + // Return if we can cover the target only with the preset inputs + if (selection_target <= 0) { SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL); - result.AddInput(preset_inputs); - - if (!coin_selection_params.m_subtract_fee_outputs && result.GetSelectedEffectiveValue() < nTargetValue) { - return std::nullopt; - } else if (result.GetSelectedValue() < nTargetValue) { - return std::nullopt; - } - + result.AddInputs(pre_set_inputs.coins, coin_selection_params.m_subtract_fee_outputs); result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); return result; } - // remove preset inputs from coins so that Coin Selection doesn't pick them. - if (coin_control.HasSelected()) { - available_coins.Erase(preset_coins); + // Start wallet Coin Selection procedure + auto op_selection_result = AutomaticCoinSelection(wallet, available_coins, selection_target, coin_control, coin_selection_params); + if (!op_selection_result) return op_selection_result; + + // If needed, add preset inputs to the automatic coin selection result + if (!pre_set_inputs.coins.empty()) { + SelectionResult preselected(pre_set_inputs.total_amount, SelectionAlgorithm::MANUAL); + preselected.AddInputs(pre_set_inputs.coins, coin_selection_params.m_subtract_fee_outputs); + op_selection_result->Merge(preselected); + op_selection_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, + coin_selection_params.m_cost_of_change, + coin_selection_params.m_change_fee); } + return op_selection_result; +} +std::optional<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& value_to_select, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params) +{ unsigned int limit_ancestor_count = 0; unsigned int limit_descendant_count = 0; wallet.chain().getPackageLimits(limit_ancestor_count, limit_descendant_count); @@ -614,16 +611,10 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a available_coins.Shuffle(coin_selection_params.rng_fast); } - SelectionResult preselected(preset_inputs.GetSelectionAmount(), SelectionAlgorithm::MANUAL); - preselected.AddInput(preset_inputs); - // Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the // transaction at a target feerate. If an attempt fails, more attempts may be made using a more // permissive CoinEligibilityFilter. std::optional<SelectionResult> res = [&] { - // Pre-selected inputs already cover the target amount. - if (value_to_select <= 0) return std::make_optional(SelectionResult(value_to_select, SelectionAlgorithm::MANUAL)); - // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six // confirmations on outputs received from other wallets and only spend confirmed change. if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/false)}) return r1; @@ -673,14 +664,6 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a return std::optional<SelectionResult>(); }(); - if (!res) return std::nullopt; - - // Add preset inputs to result - res->Merge(preselected); - if (res->GetAlgo() == SelectionAlgorithm::MANUAL) { - res->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); - } - return res; } @@ -893,17 +876,23 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal( const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size); CAmount selection_target = recipients_sum + not_input_fees; - // Get available coins - auto available_coins = AvailableCoins(wallet, - &coin_control, - coin_selection_params.m_effective_feerate, - 1, /*nMinimumAmount*/ - MAX_MONEY, /*nMaximumAmount*/ - MAX_MONEY, /*nMinimumSumAmount*/ - 0); /*nMaximumCount*/ + // Fetch manually selected coins + PreSelectedInputs preset_inputs; + if (coin_control.HasSelected()) { + auto res_fetch_inputs = FetchSelectedInputs(wallet, coin_control, coin_selection_params); + if (!res_fetch_inputs) return util::Error{util::ErrorString(res_fetch_inputs)}; + preset_inputs = *res_fetch_inputs; + } + + // Fetch wallet available coins if "other inputs" are + // allowed (coins automatically selected by the wallet) + CoinsResult available_coins; + if (coin_control.m_allow_other_inputs) { + available_coins = AvailableCoins(wallet, &coin_control, coin_selection_params.m_effective_feerate); + } // Choose coins to use - std::optional<SelectionResult> result = SelectCoins(wallet, available_coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params); + std::optional<SelectionResult> result = SelectCoins(wallet, available_coins, preset_inputs, /*nTargetValue=*/selection_target, coin_control, coin_selection_params); if (!result) { return util::Error{_("Insufficient funds")}; } diff --git a/src/wallet/spend.h b/src/wallet/spend.h index c29e5be5c7..ba2c6638c8 100644 --- a/src/wallet/spend.h +++ b/src/wallet/spend.h @@ -55,23 +55,34 @@ struct CoinsResult { CAmount total_amount{0}; }; +struct CoinFilterParams { + // Outputs below the minimum amount will not get selected + CAmount min_amount{1}; + // Outputs above the maximum amount will not get selected + CAmount max_amount{MAX_MONEY}; + // Return outputs until the minimum sum amount is covered + CAmount min_sum_amount{MAX_MONEY}; + // Maximum number of outputs that can be returned + uint64_t max_count{0}; + // By default, return only spendable outputs + bool only_spendable{true}; + // By default, do not include immature coinbase outputs + bool include_immature_coinbase{false}; +}; + /** * Populate the CoinsResult struct with vectors of available COutputs, organized by OutputType. */ CoinsResult AvailableCoins(const CWallet& wallet, const CCoinControl* coinControl = nullptr, std::optional<CFeeRate> feerate = std::nullopt, - const CAmount& nMinimumAmount = 1, - const CAmount& nMaximumAmount = MAX_MONEY, - const CAmount& nMinimumSumAmount = MAX_MONEY, - const uint64_t nMaximumCount = 0, - bool only_spendable = true) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + const CoinFilterParams& params = {}) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); /** - * Wrapper function for AvailableCoins which skips the `feerate` parameter. Use this function + * Wrapper function for AvailableCoins which skips the `feerate` and `CoinFilterParams::only_spendable` parameters. Use this function * to list all available coins (e.g. listunspent RPC) while not intending to fund a transaction. */ -CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl = nullptr, CoinFilterParams params = {}) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr); @@ -121,9 +132,35 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins, const CoinSelectionParams& coin_selection_params); +// User manually selected inputs that must be part of the transaction +struct PreSelectedInputs +{ + std::set<COutput> coins; + // If subtract fee from outputs is disabled, the 'total_amount' + // will be the sum of each output effective value + // instead of the sum of the outputs amount + CAmount total_amount{0}; + + void Insert(const COutput& output, bool subtract_fee_outputs) + { + if (subtract_fee_outputs) { + total_amount += output.txout.nValue; + } else { + total_amount += output.GetEffectiveValue(); + } + coins.insert(output); + } +}; + +/** + * Fetch and validate coin control selected inputs. + * Coins could be internal (from the wallet) or external. +*/ +util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const CCoinControl& coin_control, + const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + /** - * Select a set of coins such that nTargetValue is met and at least - * all coins from coin_control are selected; never select unconfirmed coins if they are not ours + * Select a set of coins such that nTargetValue is met; never select unconfirmed coins if they are not ours * param@[in] wallet The wallet which provides data necessary to spend the selected coins * param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering * param@[in] nTargetValue The target value @@ -132,9 +169,17 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons * returns If successful, a SelectionResult containing the selected coins * If failed, a nullopt. */ -std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control, +std::optional<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +/** + * Select all coins from coin_control, and if coin_control 'm_allow_other_inputs=true', call 'AutomaticCoinSelection' to + * select a set of coins such that nTargetValue - pre_set_inputs.total_amount is met. + */ +std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const PreSelectedInputs& pre_set_inputs, + const CAmount& nTargetValue, const CCoinControl& coin_control, + const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + struct CreatedTransactionResult { CTransactionRef tx; diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 23f024247d..f9c8c8ee9d 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -338,9 +338,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) add_coin(available_coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); CCoinControl coin_control; coin_control.m_allow_other_inputs = true; - coin_control.Select(available_coins.All().at(0).outpoint); + COutput select_coin = available_coins.All().at(0); + coin_control.Select(select_coin.outpoint); + PreSelectedInputs selected_input; + selected_input.Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs); + available_coins.coins[OutputType::BECH32].erase(available_coins.coins[OutputType::BECH32].begin()); coin_selection_params_bnb.m_effective_feerate = CFeeRate(0); - const auto result10 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb); + const auto result10 = SelectCoins(*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(result10); } { @@ -363,7 +367,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) expected_result.Clear(); add_coin(10 * CENT, 2, expected_result); CCoinControl coin_control; - const auto result11 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb); + const auto result11 = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(EquivalentResult(expected_result, *result11)); available_coins.Clear(); @@ -378,7 +382,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) expected_result.Clear(); add_coin(9 * CENT, 2, expected_result); add_coin(1 * CENT, 2, expected_result); - const auto result12 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb); + const auto result12 = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(EquivalentResult(expected_result, *result12)); available_coins.Clear(); @@ -394,8 +398,12 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) add_coin(9 * CENT, 2, expected_result); add_coin(1 * CENT, 2, expected_result); coin_control.m_allow_other_inputs = true; - coin_control.Select(available_coins.All().at(1).outpoint); // pre select 9 coin - const auto result13 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb); + COutput select_coin = available_coins.All().at(1); // pre select 9 coin + coin_control.Select(select_coin.outpoint); + PreSelectedInputs selected_input; + selected_input.Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs); + available_coins.coins[OutputType::BECH32].erase(++available_coins.coins[OutputType::BECH32].begin()); + const auto result13 = SelectCoins(*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(EquivalentResult(expected_result, *result13)); } } @@ -783,7 +791,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test) cs_params.m_cost_of_change = 1; cs_params.min_viable_change = 1; CCoinControl cc; - const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params); + const auto result = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, target, cc, cs_params); BOOST_CHECK(result); BOOST_CHECK_GE(result->GetSelectedValue(), target); } @@ -965,7 +973,10 @@ BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test) cc.SetInputWeight(output.outpoint, 148); cc.SelectExternal(output.outpoint, output.txout); - const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params); + const auto preset_inputs = *Assert(FetchSelectedInputs(*wallet, cc, cs_params)); + available_coins.coins[OutputType::BECH32].erase(available_coins.coins[OutputType::BECH32].begin()); + + const auto result = SelectCoins(*wallet, available_coins, preset_inputs, target, cc, cs_params); BOOST_CHECK(!result); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e2c8f6eda3..431e970edc 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5,6 +5,7 @@ #include <wallet/wallet.h> +#include <blockfilter.h> #include <chain.h> #include <consensus/amount.h> #include <consensus/consensus.h> @@ -261,6 +262,64 @@ std::shared_ptr<CWallet> LoadWalletInternal(WalletContext& context, const std::s return nullptr; } } + +class FastWalletRescanFilter +{ +public: + FastWalletRescanFilter(const CWallet& wallet) : m_wallet(wallet) + { + // fast rescanning via block filters is only supported by descriptor wallets right now + assert(!m_wallet.IsLegacy()); + + // create initial filter with scripts from all ScriptPubKeyMans + for (auto spkm : m_wallet.GetAllScriptPubKeyMans()) { + auto desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)}; + assert(desc_spkm != nullptr); + AddScriptPubKeys(desc_spkm); + // save each range descriptor's end for possible future filter updates + if (desc_spkm->IsHDEnabled()) { + m_last_range_ends.emplace(desc_spkm->GetID(), desc_spkm->GetEndRange()); + } + } + } + + void UpdateIfNeeded() + { + // repopulate filter with new scripts if top-up has happened since last iteration + for (const auto& [desc_spkm_id, last_range_end] : m_last_range_ends) { + auto desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(m_wallet.GetScriptPubKeyMan(desc_spkm_id))}; + assert(desc_spkm != nullptr); + int32_t current_range_end{desc_spkm->GetEndRange()}; + if (current_range_end > last_range_end) { + AddScriptPubKeys(desc_spkm, last_range_end); + m_last_range_ends.at(desc_spkm->GetID()) = current_range_end; + } + } + } + + std::optional<bool> MatchesBlock(const uint256& block_hash) const + { + return m_wallet.chain().blockFilterMatchesAny(BlockFilterType::BASIC, block_hash, m_filter_set); + } + +private: + const CWallet& m_wallet; + /** Map for keeping track of each range descriptor's last seen end range. + * This information is used to detect whether new addresses were derived + * (that is, if the current end range is larger than the saved end range) + * after processing a block and hence a filter set update is needed to + * take possible keypool top-ups into account. + */ + std::map<uint256, int32_t> m_last_range_ends; + GCSFilter::ElementSet m_filter_set; + + void AddScriptPubKeys(const DescriptorScriptPubKeyMan* desc_spkm, int32_t last_range_end = 0) + { + for (const auto& script_pub_key : desc_spkm->GetScriptPubKeys(last_range_end)) { + m_filter_set.emplace(script_pub_key.begin(), script_pub_key.end()); + } + } +}; } // namespace std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) @@ -1755,7 +1814,11 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc uint256 block_hash = start_block; ScanResult result; - WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString()); + std::unique_ptr<FastWalletRescanFilter> fast_rescan_filter; + if (!IsLegacy() && chain().hasBlockFilterIndex(BlockFilterType::BASIC)) fast_rescan_filter = std::make_unique<FastWalletRescanFilter>(*this); + + WalletLogPrintf("Rescan started from block %s... (%s)\n", start_block.ToString(), + fast_rescan_filter ? "fast variant using block filters" : "slow variant inspecting all blocks"); fAbortRescan = false; ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if rescan required on startup (e.g. due to corruption) @@ -1782,9 +1845,22 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current); } - // Read block data - CBlock block; - chain().findBlock(block_hash, FoundBlock().data(block)); + bool fetch_block{true}; + if (fast_rescan_filter) { + fast_rescan_filter->UpdateIfNeeded(); + auto matches_block{fast_rescan_filter->MatchesBlock(block_hash)}; + if (matches_block.has_value()) { + if (*matches_block) { + LogPrint(BCLog::SCAN, "Fast rescan: inspect block %d [%s] (filter matched)\n", block_height, block_hash.ToString()); + } else { + result.last_scanned_block = block_hash; + result.last_scanned_height = block_height; + fetch_block = false; + } + } else { + LogPrint(BCLog::SCAN, "Fast rescan: inspect block %d [%s] (WARNING: block filter not found!)\n", block_height, block_hash.ToString()); + } + } // Find next block separately from reading data above, because reading // is slow and there might be a reorg while it is read. @@ -1793,35 +1869,41 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc uint256 next_block_hash; chain().findBlock(block_hash, FoundBlock().inActiveChain(block_still_active).nextBlock(FoundBlock().inActiveChain(next_block).hash(next_block_hash))); - if (!block.IsNull()) { - LOCK(cs_wallet); - if (!block_still_active) { - // Abort scan if current block is no longer active, to prevent - // marking transactions as coming from the wrong block. - result.last_failed_block = block_hash; - result.status = ScanResult::FAILURE; - break; - } - for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - SyncTransaction(block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height, static_cast<int>(posInBlock)}, fUpdate, /*rescanning_old_block=*/true); - } - // scan succeeded, record block as most recent successfully scanned - result.last_scanned_block = block_hash; - result.last_scanned_height = block_height; + if (fetch_block) { + // Read block data + CBlock block; + chain().findBlock(block_hash, FoundBlock().data(block)); + + if (!block.IsNull()) { + LOCK(cs_wallet); + if (!block_still_active) { + // Abort scan if current block is no longer active, to prevent + // marking transactions as coming from the wrong block. + result.last_failed_block = block_hash; + result.status = ScanResult::FAILURE; + break; + } + for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { + SyncTransaction(block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height, static_cast<int>(posInBlock)}, fUpdate, /*rescanning_old_block=*/true); + } + // scan succeeded, record block as most recent successfully scanned + result.last_scanned_block = block_hash; + result.last_scanned_height = block_height; - if (save_progress && next_interval) { - CBlockLocator loc = m_chain->getActiveChainLocator(block_hash); + if (save_progress && next_interval) { + CBlockLocator loc = m_chain->getActiveChainLocator(block_hash); - if (!loc.IsNull()) { - WalletLogPrintf("Saving scan progress %d.\n", block_height); - WalletBatch batch(GetDatabase()); - batch.WriteBestBlock(loc); + if (!loc.IsNull()) { + WalletLogPrintf("Saving scan progress %d.\n", block_height); + WalletBatch batch(GetDatabase()); + batch.WriteBestBlock(loc); + } } + } else { + // could not scan block, keep scanning but record this block as the most recent failure + result.last_failed_block = block_hash; + result.status = ScanResult::FAILURE; } - } else { - // could not scan block, keep scanning but record this block as the most recent failure - result.last_failed_block = block_hash; - result.status = ScanResult::FAILURE; } if (max_height && block_height >= *max_height) { break; diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py index bc85e43a57..4b7a50c1c7 100755 --- a/test/functional/feature_index_prune.py +++ b/test/functional/feature_index_prune.py @@ -138,6 +138,7 @@ class FeatureIndexPruneTest(BitcoinTestFramework): self.connect_nodes(i, 3) self.sync_blocks(timeout=300) + self.sync_index(height=2500) for node in self.nodes[:2]: with node.assert_debug_log(['limited pruning to height 2489']): diff --git a/test/functional/feature_maxtipage.py b/test/functional/feature_maxtipage.py index 87f9d6962d..ddc2102542 100755 --- a/test/functional/feature_maxtipage.py +++ b/test/functional/feature_maxtipage.py @@ -2,10 +2,10 @@ # Copyright (c) 2022 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test logic for setting nMaxTipAge on command line. +"""Test logic for setting -maxtipage on command line. Nodes don't consider themselves out of "initial block download" as long as -their best known block header time is more than nMaxTipAge in the past. +their best known block header time is more than -maxtipage in the past. """ import time diff --git a/test/functional/feature_reindex.py b/test/functional/feature_reindex.py index 44040f426f..0f6a8fd0d2 100755 --- a/test/functional/feature_reindex.py +++ b/test/functional/feature_reindex.py @@ -7,9 +7,12 @@ - Start a single node and generate 3 blocks. - Stop the node and restart it with -reindex. Verify that the node has reindexed up to block 3. - Stop the node and restart it with -reindex-chainstate. Verify that the node has reindexed up to block 3. +- Verify that out-of-order blocks are correctly processed, see LoadExternalBlockFile() """ +import os from test_framework.test_framework import BitcoinTestFramework +from test_framework.p2p import MAGIC_BYTES from test_framework.util import assert_equal @@ -27,11 +30,58 @@ class ReindexTest(BitcoinTestFramework): assert_equal(self.nodes[0].getblockcount(), blockcount) # start_node is blocking on reindex self.log.info("Success") + # Check that blocks can be processed out of order + def out_of_order(self): + # The previous test created 12 blocks + assert_equal(self.nodes[0].getblockcount(), 12) + self.stop_nodes() + + # In this test environment, blocks will always be in order (since + # we're generating them rather than getting them from peers), so to + # test out-of-order handling, swap blocks 1 and 2 on disk. + blk0 = os.path.join(self.nodes[0].datadir, self.nodes[0].chain, 'blocks', 'blk00000.dat') + with open(blk0, 'r+b') as bf: + # Read at least the first few blocks (including genesis) + b = bf.read(2000) + + # Find the offsets of blocks 2, 3, and 4 (the first 3 blocks beyond genesis) + # by searching for the regtest marker bytes (see pchMessageStart). + def find_block(b, start): + return b.find(MAGIC_BYTES["regtest"], start)+4 + + genesis_start = find_block(b, 0) + assert_equal(genesis_start, 4) + b2_start = find_block(b, genesis_start) + b3_start = find_block(b, b2_start) + b4_start = find_block(b, b3_start) + + # Blocks 2 and 3 should be the same size. + assert_equal(b3_start-b2_start, b4_start-b3_start) + + # Swap the second and third blocks (don't disturb the genesis block). + bf.seek(b2_start) + bf.write(b[b3_start:b4_start]) + bf.write(b[b2_start:b3_start]) + + # The reindexing code should detect and accommodate out of order blocks. + with self.nodes[0].assert_debug_log([ + 'LoadExternalBlockFile: Out of order block', + 'LoadExternalBlockFile: Processing out of order child', + ]): + extra_args = [["-reindex"]] + self.start_nodes(extra_args) + + # All blocks should be accepted and processed. + assert_equal(self.nodes[0].getblockcount(), 12) + def run_test(self): self.reindex(False) self.reindex(True) self.reindex(False) self.reindex(True) + self.out_of_order() + + if __name__ == '__main__': ReindexTest().main() diff --git a/test/functional/mocks/signer.py b/test/functional/mocks/signer.py index c5a8f7b1e9..6699914249 100755 --- a/test/functional/mocks/signer.py +++ b/test/functional/mocks/signer.py @@ -27,12 +27,15 @@ def getdescriptors(args): "receive": [ "pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/0/*)#vt6w3l3j", "sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/0/*))#r0grqw5x", - "wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#x30uthjs" + "wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#x30uthjs", + "tr([00000001/86'/1'/" + args.account + "']" + xpub + "/0/*)#sng9rd4t" ], "internal": [ "pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/1/*)#all0v2p2", "sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/1/*))#kwx4c3pe", - "wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#h92akzzg" + "wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#h92akzzg", + "tr([00000001/86'/1'/" + args.account + "']" + xpub + "/1/*)#p8dy7c9n" + ] })) @@ -44,7 +47,8 @@ def displayaddress(args): return sys.stdout.write(json.dumps({"error": "Unexpected fingerprint", "fingerprint": args.fingerprint})) expected_desc = [ - "wpkh([00000001/84'/1'/0'/0/0]02c97dc3f4420402e01a113984311bf4a1b8de376cac0bdcfaf1b3ac81f13433c7)#0yneg42r" + "wpkh([00000001/84'/1'/0'/0/0]02c97dc3f4420402e01a113984311bf4a1b8de376cac0bdcfaf1b3ac81f13433c7)#0yneg42r", + "tr([00000001/86'/1'/0'/0/0]c97dc3f4420402e01a113984311bf4a1b8de376cac0bdcfaf1b3ac81f13433c7)#4vdj9jqk", ] if args.desc not in expected_desc: return sys.stdout.write(json.dumps({"error": "Unexpected descriptor", "desc": args.desc})) diff --git a/test/functional/p2p_dos_header_tree.py b/test/functional/p2p_dos_header_tree.py index 7e26994511..1f904644fc 100755 --- a/test/functional/p2p_dos_header_tree.py +++ b/test/functional/p2p_dos_header_tree.py @@ -22,7 +22,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.setup_clean_chain = True self.chain = 'testnet3' # Use testnet chain because it has an early checkpoint self.num_nodes = 2 - self.extra_args = [["-minimumchainwork=0x0"], ["-minimumchainwork=0x0"]] + self.extra_args = [["-minimumchainwork=0x0", '-prune=550']] * self.num_nodes def add_options(self, parser): parser.add_argument( @@ -63,7 +63,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.log.info("Feed all fork headers (succeeds without checkpoint)") # On node 0 it succeeds because checkpoints are disabled - self.restart_node(0, extra_args=['-nocheckpoints', "-minimumchainwork=0x0"]) + self.restart_node(0, extra_args=['-nocheckpoints', "-minimumchainwork=0x0", '-prune=550']) peer_no_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface()) peer_no_checkpoint.send_and_ping(msg_headers(self.headers_fork)) assert { diff --git a/test/functional/p2p_ping.py b/test/functional/p2p_ping.py index 52dae90d19..2919f7aa7b 100755 --- a/test/functional/p2p_ping.py +++ b/test/functional/p2p_ping.py @@ -12,7 +12,9 @@ from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal + PING_INTERVAL = 2 * 60 +TIMEOUT_INTERVAL = 20 * 60 class msg_pong_corrupt(msg_pong): @@ -20,19 +22,11 @@ class msg_pong_corrupt(msg_pong): return b"" -class NodePongAdd1(P2PInterface): - def on_ping(self, message): - self.send_message(msg_pong(message.nonce + 1)) - - class NodeNoPong(P2PInterface): def on_ping(self, message): pass -TIMEOUT_INTERVAL = 20 * 60 - - class PingPongTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True diff --git a/test/functional/p2p_sendtxrcncl.py b/test/functional/p2p_sendtxrcncl.py index f4c5dd4586..fed9832a7d 100755 --- a/test/functional/p2p_sendtxrcncl.py +++ b/test/functional/p2p_sendtxrcncl.py @@ -39,6 +39,12 @@ class SendTxrcnclReceiver(P2PInterface): def on_sendtxrcncl(self, message): self.sendtxrcncl_msg_received = message + +class P2PFeelerReceiver(SendTxrcnclReceiver): + def on_version(self, message): + pass # feeler connections can not send any message other than their own version + + class PeerTrackMsgOrder(P2PInterface): def __init__(self): super().__init__() @@ -68,7 +74,7 @@ class SendTxRcnclTest(BitcoinTestFramework): assert not peer.sendtxrcncl_msg_received.initiator assert peer.sendtxrcncl_msg_received.responder assert_equal(peer.sendtxrcncl_msg_received.version, 1) - peer.peer_disconnect() + self.nodes[0].disconnect_p2ps() self.log.info('SENDTXRCNCL should be sent before VERACK') peer = self.nodes[0].add_p2p_connection(PeerTrackMsgOrder(), send_version=True, wait_for_verack=True) @@ -76,7 +82,7 @@ class SendTxRcnclTest(BitcoinTestFramework): verack_index = [i for i, msg in enumerate(peer.messages) if msg.msgtype == b'verack'][0] sendtxrcncl_index = [i for i, msg in enumerate(peer.messages) if msg.msgtype == b'sendtxrcncl'][0] assert(sendtxrcncl_index < verack_index) - peer.peer_disconnect() + self.nodes[0].disconnect_p2ps() self.log.info('SENDTXRCNCL on pre-WTXID version should not be sent') peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False) @@ -88,7 +94,7 @@ class SendTxRcnclTest(BitcoinTestFramework): peer.send_message(pre_wtxid_version_msg) peer.wait_for_verack() assert not peer.sendtxrcncl_msg_received - peer.peer_disconnect() + self.nodes[0].disconnect_p2ps() self.log.info('SENDTXRCNCL for fRelay=false should not be sent') peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False) @@ -100,91 +106,100 @@ class SendTxRcnclTest(BitcoinTestFramework): peer.send_message(no_txrelay_version_msg) peer.wait_for_verack() assert not peer.sendtxrcncl_msg_received - peer.peer_disconnect() + self.nodes[0].disconnect_p2ps() self.log.info('valid SENDTXRCNCL received') peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False) peer.send_message(create_sendtxrcncl_msg()) self.wait_until(lambda : "sendtxrcncl" in self.nodes[0].getpeerinfo()[-1]["bytesrecv_per_msg"]) self.log.info('second SENDTXRCNCL triggers a disconnect') - peer.send_message(create_sendtxrcncl_msg()) - peer.wait_for_disconnect() + with self.nodes[0].assert_debug_log(["(sendtxrcncl received from already registered peer); disconnecting"]): + peer.send_message(create_sendtxrcncl_msg()) + peer.wait_for_disconnect() self.log.info('SENDTXRCNCL with initiator=responder=0 triggers a disconnect') sendtxrcncl_no_role = create_sendtxrcncl_msg() sendtxrcncl_no_role.initiator = False sendtxrcncl_no_role.responder = False peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False) - peer.send_message(sendtxrcncl_no_role) - peer.wait_for_disconnect() + with self.nodes[0].assert_debug_log(["txreconciliation protocol violation"]): + peer.send_message(sendtxrcncl_no_role) + peer.wait_for_disconnect() self.log.info('SENDTXRCNCL with initiator=0 and responder=1 from inbound triggers a disconnect') sendtxrcncl_wrong_role = create_sendtxrcncl_msg(initiator=False) peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False) - peer.send_message(sendtxrcncl_wrong_role) - peer.wait_for_disconnect() + with self.nodes[0].assert_debug_log(["txreconciliation protocol violation"]): + peer.send_message(sendtxrcncl_wrong_role) + peer.wait_for_disconnect() self.log.info('SENDTXRCNCL with version=0 triggers a disconnect') sendtxrcncl_low_version = create_sendtxrcncl_msg() sendtxrcncl_low_version.version = 0 peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False) - peer.send_message(sendtxrcncl_low_version) - peer.wait_for_disconnect() + with self.nodes[0].assert_debug_log(["txreconciliation protocol violation"]): + peer.send_message(sendtxrcncl_low_version) + peer.wait_for_disconnect() self.log.info('sending SENDTXRCNCL after sending VERACK triggers a disconnect') - # We use PeerNoVerack even though verack is sent right after, to make sure it was actually - # sent before sendtxrcncl is sent. - peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False) - peer.send_and_ping(msg_verack()) - peer.send_message(create_sendtxrcncl_msg()) - peer.wait_for_disconnect() + peer = self.nodes[0].add_p2p_connection(P2PInterface()) + with self.nodes[0].assert_debug_log(["sendtxrcncl received after verack"]): + peer.send_message(create_sendtxrcncl_msg()) + peer.wait_for_disconnect() self.log.info('SENDTXRCNCL without WTXIDRELAY is ignored (recon state is erased after VERACK)') peer = self.nodes[0].add_p2p_connection(PeerNoVerack(wtxidrelay=False), send_version=True, wait_for_verack=False) with self.nodes[0].assert_debug_log(['Forget txreconciliation state of peer']): peer.send_message(create_sendtxrcncl_msg()) peer.send_message(msg_verack()) - peer.peer_disconnect() + self.nodes[0].disconnect_p2ps() self.log.info('SENDTXRCNCL sent to an outbound') peer = self.nodes[0].add_outbound_p2p_connection( - SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=1, connection_type="outbound-full-relay") + SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=0, connection_type="outbound-full-relay") assert peer.sendtxrcncl_msg_received assert peer.sendtxrcncl_msg_received.initiator assert not peer.sendtxrcncl_msg_received.responder assert_equal(peer.sendtxrcncl_msg_received.version, 1) - peer.peer_disconnect() + self.nodes[0].disconnect_p2ps() self.log.info('SENDTXRCNCL should not be sent if block-relay-only') peer = self.nodes[0].add_outbound_p2p_connection( - SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=2, connection_type="block-relay-only") + SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=0, connection_type="block-relay-only") + assert not peer.sendtxrcncl_msg_received + self.nodes[0].disconnect_p2ps() + + self.log.info("SENDTXRCNCL should not be sent if feeler") + peer = self.nodes[0].add_outbound_p2p_connection(P2PFeelerReceiver(), p2p_idx=0, connection_type="feeler") assert not peer.sendtxrcncl_msg_received - peer.peer_disconnect() + self.nodes[0].disconnect_p2ps() self.log.info('SENDTXRCNCL if block-relay-only triggers a disconnect') peer = self.nodes[0].add_outbound_p2p_connection( - PeerNoVerack(), wait_for_verack=False, p2p_idx=3, connection_type="block-relay-only") - peer.send_message(create_sendtxrcncl_msg(initiator=False)) - peer.wait_for_disconnect() + PeerNoVerack(), wait_for_verack=False, p2p_idx=0, connection_type="block-relay-only") + with self.nodes[0].assert_debug_log(["we indicated no tx relay; disconnecting"]): + peer.send_message(create_sendtxrcncl_msg(initiator=False)) + peer.wait_for_disconnect() self.log.info('SENDTXRCNCL with initiator=1 and responder=0 from outbound triggers a disconnect') sendtxrcncl_wrong_role = create_sendtxrcncl_msg(initiator=True) peer = self.nodes[0].add_outbound_p2p_connection( - P2PInterface(), wait_for_verack=False, p2p_idx=4, connection_type="outbound-full-relay") - peer.send_message(sendtxrcncl_wrong_role) - peer.wait_for_disconnect() + PeerNoVerack(), wait_for_verack=False, p2p_idx=0, connection_type="outbound-full-relay") + with self.nodes[0].assert_debug_log(["txreconciliation protocol violation"]): + peer.send_message(sendtxrcncl_wrong_role) + peer.wait_for_disconnect() self.log.info('SENDTXRCNCL not sent if -txreconciliation flag is not set') self.restart_node(0, []) peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True) assert not peer.sendtxrcncl_msg_received - peer.peer_disconnect() + self.nodes[0].disconnect_p2ps() self.log.info('SENDTXRCNCL not sent if blocksonly is set') self.restart_node(0, ["-txreconciliation", "-blocksonly"]) peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True) assert not peer.sendtxrcncl_msg_received - peer.peer_disconnect() + self.nodes[0].disconnect_p2ps() if __name__ == '__main__': diff --git a/test/functional/rpc_deriveaddresses.py b/test/functional/rpc_deriveaddresses.py index 42d7d59d56..a69326736d 100755 --- a/test/functional/rpc_deriveaddresses.py +++ b/test/functional/rpc_deriveaddresses.py @@ -44,6 +44,13 @@ class DeriveaddressesTest(BitcoinTestFramework): combo_descriptor = descsum_create("combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)") assert_equal(self.nodes[0].deriveaddresses(combo_descriptor), ["mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", "mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", address, "2NDvEwGfpEqJWfybzpKPHF2XH3jwoQV3D7x"]) + # Before #26275, bitcoind would crash when deriveaddresses was + # called with derivation index 2147483647, which is the maximum + # positive value of a signed int32, and - currently - the + # maximum value that the deriveaddresses bitcoin RPC call + # accepts as derivation index. + assert_equal(self.nodes[0].deriveaddresses(descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), [2147483647, 2147483647]), ["bcrt1qtzs23vgzpreks5gtygwxf8tv5rldxvvsyfpdkg"]) + hardened_without_privkey_descriptor = descsum_create("wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1'/1/0)") assert_raises_rpc_error(-5, "Cannot derive script without private keys", self.nodes[0].deriveaddresses, hardened_without_privkey_descriptor) diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 17c6fce9c2..1152995ac9 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -406,7 +406,9 @@ class RawTransactionsTest(BitcoinTestFramework): def test_invalid_input(self): self.log.info("Test fundrawtxn with an invalid vin") - inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin! + txid = "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1" + vout = 0 + inputs = [ {'txid' : txid, 'vout' : vout} ] #invalid vin! outputs = { self.nodes[0].getnewaddress() : 1.0} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) assert_raises_rpc_error(-4, "Unable to find UTXO for external input", self.nodes[2].fundrawtransaction, rawtx) @@ -793,7 +795,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.log.info("Test fundrawtxn with invalid estimate_mode settings") for k, v in {"number": 42, "object": {"foo": "bar"}}.items(): - assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k), + assert_raises_rpc_error(-3, f"JSON value of type {k} for field estimate_mode is not of expected type string", node.fundrawtransaction, rawtx, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True}) for mode in ["", "foo", Decimal("3.141592")]: assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', @@ -803,7 +805,7 @@ class RawTransactionsTest(BitcoinTestFramework): for mode in ["unset", "economical", "conservative"]: self.log.debug("{}".format(mode)) for k, v in {"string": "", "object": {"foo": "bar"}}.items(): - assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k), + assert_raises_rpc_error(-3, f"JSON value of type {k} for field conf_target is not of expected type number", node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": v, "add_inputs": True}) for n in [-1, 0, 1009]: assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h @@ -1011,7 +1013,7 @@ class RawTransactionsTest(BitcoinTestFramework): # An external input without solving data should result in an error raw_tx = wallet.createrawtransaction([ext_utxo], {self.nodes[0].getnewaddress(): ext_utxo["amount"] / 2}) - assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, raw_tx) + assert_raises_rpc_error(-4, "Not solvable pre-selected input COutPoint(%s, %s)" % (ext_utxo["txid"][0:10], ext_utxo["vout"]), wallet.fundrawtransaction, raw_tx) # Error conditions assert_raises_rpc_error(-5, "'not a pubkey' is not hex", wallet.fundrawtransaction, raw_tx, {"solving_data": {"pubkeys":["not a pubkey"]}}) @@ -1095,6 +1097,8 @@ class RawTransactionsTest(BitcoinTestFramework): # Expect: only preset inputs are used. # 5. Explicit add_inputs=true, no preset inputs (same as (1) but with an explicit set): # Expect: include inputs from the wallet. + # 6. Explicit add_inputs=false, no preset inputs: + # Expect: failure as we did not provide inputs and the process cannot automatically select coins. # Case (1), 'send' command # 'add_inputs' value is true unless "inputs" are specified, in such case, add_inputs=false. @@ -1146,6 +1150,10 @@ class RawTransactionsTest(BitcoinTestFramework): tx = wallet.send(outputs=[{addr1: 8}], options=options) assert tx["complete"] + # 6. Explicit add_inputs=false, no preset inputs: + options = {"add_inputs": False} + assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{addr1: 3}], options=options) + ################################################ # Case (1), 'walletcreatefundedpsbt' command @@ -1165,7 +1173,6 @@ class RawTransactionsTest(BitcoinTestFramework): # Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount) options["add_inputs"] = True - options["add_to_wallet"] = False assert "psbt" in wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, options=options) # Case (4), Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount) @@ -1187,6 +1194,10 @@ class RawTransactionsTest(BitcoinTestFramework): } assert "psbt" in wallet.walletcreatefundedpsbt(inputs=[], outputs=outputs, options=options) + # Case (6). Explicit add_inputs=false, no preset inputs: + options = {"add_inputs": False} + assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, inputs=[], outputs=outputs, options=options) + self.nodes[2].unloadwallet("test_preset_inputs") def test_weight_calculation(self): diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py index 278a343b2b..8bd3366e36 100755 --- a/test/functional/rpc_getblockfrompeer.py +++ b/test/functional/rpc_getblockfrompeer.py @@ -5,7 +5,12 @@ """Test the getblockfrompeer RPC.""" from test_framework.authproxy import JSONRPCException -from test_framework.messages import NODE_WITNESS +from test_framework.messages import ( + CBlock, + from_hex, + msg_headers, + NODE_WITNESS, +) from test_framework.p2p import ( P2P_SERVICES, P2PInterface, @@ -16,6 +21,7 @@ from test_framework.util import ( assert_raises_rpc_error, ) + class GetBlockFromPeerTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 @@ -81,6 +87,30 @@ class GetBlockFromPeerTest(BitcoinTestFramework): self.log.info("Don't fetch blocks we already have") assert_raises_rpc_error(-1, "Block already downloaded", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id) + self.log.info("Don't fetch blocks while the node has not synced past it yet") + # For this test we need node 1 in prune mode and as a side effect this also disconnects + # the nodes which is also necessary for the rest of the test. + self.restart_node(1, ["-prune=550"]) + + # Generate a block on the disconnected node that the pruning node is not connected to + blockhash = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[0] + block_hex = self.nodes[0].getblock(blockhash=blockhash, verbosity=0) + block = from_hex(CBlock(), block_hex) + + # Connect a P2PInterface to the pruning node and have it submit only the header of the + # block that the pruning node has not seen + node1_interface = self.nodes[1].add_p2p_connection(P2PInterface()) + node1_interface.send_and_ping(msg_headers([block])) + + # Get the peer id of the P2PInterface from the pruning node + node1_peers = self.nodes[1].getpeerinfo() + assert_equal(len(node1_peers), 1) + node1_interface_id = node1_peers[0]["id"] + + # Trying to fetch this block from the P2PInterface should not be possible + error_msg = "In prune mode, only blocks that the node has already synced previously can be fetched from a peer" + assert_raises_rpc_error(-1, error_msg, self.nodes[1].getblockfrompeer, blockhash, node1_interface_id) + if __name__ == '__main__': GetBlockFromPeerTest().main() diff --git a/test/functional/rpc_mempool_info.py b/test/functional/rpc_mempool_info.py index 9b5a3b9112..ae9c6572cf 100755 --- a/test/functional/rpc_mempool_info.py +++ b/test/functional/rpc_mempool_info.py @@ -84,7 +84,7 @@ class RPCMempoolInfoTest(BitcoinTestFramework): assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", self.nodes[0].gettxspendingprevout, [{'txid' : txidA, 'vout' : -1}]) self.log.info("Invalid txid provided") - assert_raises_rpc_error(-3, "Expected type string for txid, got number", self.nodes[0].gettxspendingprevout, [{'txid' : 42, 'vout' : 0}]) + assert_raises_rpc_error(-3, "JSON value of type number for field txid is not of expected type string", self.nodes[0].gettxspendingprevout, [{'txid' : 42, 'vout' : 0}]) self.log.info("Missing outputs") assert_raises_rpc_error(-8, "Invalid parameter, outputs are missing", self.nodes[0].gettxspendingprevout, []) diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index ad8ba06824..01334e2d57 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -107,6 +107,55 @@ class NetTest(BitcoinTestFramework): # Check dynamically generated networks list in getpeerinfo help output. assert "(ipv4, ipv6, onion, i2p, cjdns, not_publicly_routable)" in self.nodes[0].help("getpeerinfo") + self.log.info("Check getpeerinfo output before a version message was sent") + no_version_peer_id = 2 + no_version_peer_conntime = int(time.time()) + self.nodes[0].setmocktime(no_version_peer_conntime) + with self.nodes[0].assert_debug_log([f"Added connection peer={no_version_peer_id}"]): + no_version_peer = self.nodes[0].add_p2p_connection(P2PInterface(), send_version=False, wait_for_verack=False) + self.nodes[0].setmocktime(0) + peer_info = self.nodes[0].getpeerinfo()[no_version_peer_id] + peer_info.pop("addr") + peer_info.pop("addrbind") + assert_equal( + peer_info, + { + "addr_processed": 0, + "addr_rate_limited": 0, + "addr_relay_enabled": False, + "bip152_hb_from": False, + "bip152_hb_to": False, + "bytesrecv": 0, + "bytesrecv_per_msg": {}, + "bytessent": 0, + "bytessent_per_msg": {}, + "connection_type": "inbound", + "conntime": no_version_peer_conntime, + "id": no_version_peer_id, + "inbound": True, + "inflight": [], + "last_block": 0, + "last_transaction": 0, + "lastrecv": 0, + "lastsend": 0, + "minfeefilter": Decimal("0E-8"), + "network": "not_publicly_routable", + "permissions": [], + "presynced_headers": -1, + "relaytxes": False, + "services": "0000000000000000", + "servicesnames": [], + "startingheight": -1, + "subver": "", + "synced_blocks": -1, + "synced_headers": -1, + "timeoffset": 0, + "version": 0, + }, + ) + no_version_peer.peer_disconnect() + self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 2) + def test_getnettotals(self): self.log.info("Test getnettotals") # Test getnettotals and getpeerinfo by doing a ping. The bytes diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 3b78a7d095..8243f43736 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -275,7 +275,7 @@ class PSBTTest(BitcoinTestFramework): self.log.info("- raises RPC error with invalid estimate_mode settings") for k, v in {"number": 42, "object": {"foo": "bar"}}.items(): - assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k), + assert_raises_rpc_error(-3, f"JSON value of type {k} for field estimate_mode is not of expected type string", self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True}) for mode in ["", "foo", Decimal("3.141592")]: assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', @@ -285,7 +285,7 @@ class PSBTTest(BitcoinTestFramework): for mode in ["unset", "economical", "conservative"]: self.log.debug("{}".format(mode)) for k, v in {"string": "", "object": {"foo": "bar"}}.items(): - assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k), + assert_raises_rpc_error(-3, f"JSON value of type {k} for field conf_target is not of expected type number", self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": v, "add_inputs": True}) for n in [-1, 0, 1009]: assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h @@ -657,7 +657,7 @@ class PSBTTest(BitcoinTestFramework): ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0] # An external input without solving data should result in an error - assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 15}) + assert_raises_rpc_error(-4, "Not solvable pre-selected input COutPoint(%s, %s)" % (ext_utxo["txid"][0:10], ext_utxo["vout"]), wallet.walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 15}) # But funding should work when the solving data is provided psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"], addr_info["embedded"]["embedded"]["scriptPubKey"]]}}) diff --git a/test/functional/rpc_scanblocks.py b/test/functional/rpc_scanblocks.py index 39f091fd1a..743cdf89ed 100755 --- a/test/functional/rpc_scanblocks.py +++ b/test/functional/rpc_scanblocks.py @@ -3,6 +3,10 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the scanblocks RPC call.""" +from test_framework.blockfilter import ( + bip158_basic_element_hash, + bip158_relevant_scriptpubkeys, +) from test_framework.messages import COIN from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -71,6 +75,28 @@ class ScanblocksTest(BitcoinTestFramework): assert(blockhash in node.scanblocks( "start", [{"desc": f"pkh({parent_key}/*)", "range": [0, 100]}], height)['relevant_blocks']) + # check that false-positives are included in the result now; note that + # finding a false-positive at runtime would take too long, hence we simply + # use a pre-calculated one that collides with the regtest genesis block's + # coinbase output and verify that their BIP158 ranged hashes match + genesis_blockhash = node.getblockhash(0) + genesis_spks = bip158_relevant_scriptpubkeys(node, genesis_blockhash) + assert_equal(len(genesis_spks), 1) + genesis_coinbase_spk = list(genesis_spks)[0] + false_positive_spk = bytes.fromhex("001400000000000000000000000000000000000cadcb") + + genesis_coinbase_hash = bip158_basic_element_hash(genesis_coinbase_spk, 1, genesis_blockhash) + false_positive_hash = bip158_basic_element_hash(false_positive_spk, 1, genesis_blockhash) + assert_equal(genesis_coinbase_hash, false_positive_hash) + + assert(genesis_blockhash in node.scanblocks( + "start", [{"desc": f"raw({genesis_coinbase_spk.hex()})"}], 0, 0)['relevant_blocks']) + assert(genesis_blockhash in node.scanblocks( + "start", [{"desc": f"raw({false_positive_spk.hex()})"}], 0, 0)['relevant_blocks']) + + # TODO: after an "accurate" mode for scanblocks is implemented (e.g. PR #26325) + # check here that it filters out the false-positive + # test node with disabled blockfilterindex assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", self.nodes[1].scanblocks, "start", [f"addr({addr_1})"]) @@ -96,7 +122,7 @@ class ScanblocksTest(BitcoinTestFramework): assert_equal(node.scanblocks("abort"), False) # test invalid command - assert_raises_rpc_error(-8, "Invalid command", node.scanblocks, "foobar") + assert_raises_rpc_error(-8, "Invalid action 'foobar'", node.scanblocks, "foobar") if __name__ == '__main__': diff --git a/test/functional/test-shell.md b/test/functional/test-shell.md index 78737509cb..80f4e88109 100644 --- a/test/functional/test-shell.md +++ b/test/functional/test-shell.md @@ -93,8 +93,10 @@ We now let the first node generate 101 regtest blocks, and direct the coinbase rewards to a wallet address owned by the mining node. ``` +>>> test.nodes[0].createwallet('default') +{'name': 'default', 'warning': 'Empty string given as passphrase, wallet will not be encrypted.'} >>> address = test.nodes[0].getnewaddress() ->>> test.self.generatetoaddress(nodes[0], 101, address) +>>> test.generatetoaddress(test.nodes[0], 101, address) ['2b98dd0044aae6f1cca7f88a0acf366a4bfe053c7f7b00da3c0d115f03d67efb', ... ``` Since the two nodes are both initialized by default to establish an outbound diff --git a/test/functional/test_framework/blockfilter.py b/test/functional/test_framework/blockfilter.py new file mode 100644 index 0000000000..a30e37ea5b --- /dev/null +++ b/test/functional/test_framework/blockfilter.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Helper routines relevant for compact block filters (BIP158). +""" +from .siphash import siphash + + +def bip158_basic_element_hash(script_pub_key, N, block_hash): + """ Calculates the ranged hash of a filter element as defined in BIP158: + + 'The first step in the filter construction is hashing the variable-sized + raw items in the set to the range [0, F), where F = N * M.' + + 'The items are first passed through the pseudorandom function SipHash, which takes a + 128-bit key k and a variable-sized byte vector and produces a uniformly random 64-bit + output. Implementations of this BIP MUST use the SipHash parameters c = 2 and d = 4.' + + 'The parameter k MUST be set to the first 16 bytes of the hash (in standard + little-endian representation) of the block for which the filter is constructed. This + ensures the key is deterministic while still varying from block to block.' + """ + M = 784931 + block_hash_bytes = bytes.fromhex(block_hash)[::-1] + k0 = int.from_bytes(block_hash_bytes[0:8], 'little') + k1 = int.from_bytes(block_hash_bytes[8:16], 'little') + return (siphash(k0, k1, script_pub_key) * (N * M)) >> 64 + + +def bip158_relevant_scriptpubkeys(node, block_hash): + """ Determines the basic filter relvant scriptPubKeys as defined in BIP158: + + 'A basic filter MUST contain exactly the following items for each transaction in a block: + - The previous output script (the script being spent) for each input, except for + the coinbase transaction. + - The scriptPubKey of each output, aside from all OP_RETURN output scripts.' + """ + spks = set() + for tx in node.getblock(blockhash=block_hash, verbosity=3)['tx']: + # gather prevout scripts + for i in tx['vin']: + if 'prevout' in i: + spks.add(bytes.fromhex(i['prevout']['scriptPubKey']['hex'])) + # gather output scripts + for o in tx['vout']: + if o['scriptPubKey']['type'] != 'nulldata': + spks.add(bytes.fromhex(o['scriptPubKey']['hex'])) + return spks diff --git a/test/functional/test_framework/siphash.py b/test/functional/test_framework/siphash.py index 85836845d4..5ad245cf1b 100644 --- a/test/functional/test_framework/siphash.py +++ b/test/functional/test_framework/siphash.py @@ -1,15 +1,17 @@ #!/usr/bin/env python3 -# Copyright (c) 2016-2018 The Bitcoin Core developers +# Copyright (c) 2016-2022 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Specialized SipHash-2-4 implementations. +"""SipHash-2-4 implementation. -This implements SipHash-2-4 for 256-bit integers. +This implements SipHash-2-4. For convenience, an interface taking 256-bit +integers is provided in addition to the one accepting generic data. """ def rotl64(n, b): return n >> (64 - b) | (n & ((1 << (64 - b)) - 1)) << b + def siphash_round(v0, v1, v2, v3): v0 = (v0 + v1) & ((1 << 64) - 1) v1 = rotl64(v1, 13) @@ -27,37 +29,37 @@ def siphash_round(v0, v1, v2, v3): v2 = rotl64(v2, 32) return (v0, v1, v2, v3) -def siphash256(k0, k1, h): - n0 = h & ((1 << 64) - 1) - n1 = (h >> 64) & ((1 << 64) - 1) - n2 = (h >> 128) & ((1 << 64) - 1) - n3 = (h >> 192) & ((1 << 64) - 1) + +def siphash(k0, k1, data): + assert(type(data) == bytes) v0 = 0x736f6d6570736575 ^ k0 v1 = 0x646f72616e646f6d ^ k1 v2 = 0x6c7967656e657261 ^ k0 - v3 = 0x7465646279746573 ^ k1 ^ n0 - v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) - v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) - v0 ^= n0 - v3 ^= n1 - v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) - v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) - v0 ^= n1 - v3 ^= n2 + v3 = 0x7465646279746573 ^ k1 + c = 0 + t = 0 + for d in data: + t |= d << (8 * (c % 8)) + c = (c + 1) & 0xff + if (c & 7) == 0: + v3 ^= t + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0 ^= t + t = 0 + t = t | (c << 56) + v3 ^= t v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) - v0 ^= n2 - v3 ^= n3 - v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) - v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) - v0 ^= n3 - v3 ^= 0x2000000000000000 - v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) - v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) - v0 ^= 0x2000000000000000 - v2 ^= 0xFF + v0 ^= t + v2 ^= 0xff v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) return v0 ^ v1 ^ v2 ^ v3 + + +def siphash256(k0, k1, num): + assert(type(num) == int) + return siphash(k0, k1, num.to_bytes(32, 'little')) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 2367a9a8fa..64edff15ae 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -102,6 +102,7 @@ class TestNode(): "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", + "-debugexclude=rand", "-uacomment=testnode%d" % i, ] if use_valgrind: @@ -624,6 +625,10 @@ class TestNode(): This method adds the p2p connection to the self.p2ps list and returns the connection to the caller. + + p2p_idx must be different for simultaneously connected peers. When reusing it for the next peer + after disconnecting the previous one, it is necessary to wait for the disconnect to finish to avoid + a race condition. """ def addconnection_callback(address, port): @@ -651,7 +656,8 @@ class TestNode(): return len([peer for peer in self.getpeerinfo() if peer['subver'] == P2P_SUBVERSION]) def disconnect_p2ps(self): - """Close all p2p connections to the node.""" + """Close all p2p connections to the node. + Use only after each p2p has sent a version message to ensure the wait works.""" for p in self.p2ps: p.peer_disconnect() del self.p2ps[:] diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index e20de8ea8e..e2c13a6705 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -136,6 +136,7 @@ BASE_SCRIPTS = [ # vv Tests less than 30s vv 'wallet_keypool_topup.py --legacy-wallet', 'wallet_keypool_topup.py --descriptors', + 'wallet_fast_rescan.py --descriptors', 'feature_fee_estimation.py', 'interface_zmq.py', 'rpc_invalid_address_message.py', diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index ec58ace4a2..60da22ca26 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -77,8 +77,18 @@ class WalletTest(BitcoinTestFramework): self.log.info("Mining blocks ...") self.generate(self.nodes[0], 1) self.generate(self.nodes[1], 1) + + # Verify listunspent returns immature coinbase if 'include_immature_coinbase' is set + assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': True})), 1) + assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': False})), 0) + self.generatetoaddress(self.nodes[1], COINBASE_MATURITY + 1, ADDRESS_WATCHONLY) + # Verify listunspent returns all immature coinbases if 'include_immature_coinbase' is set + # For now, only the legacy wallet will see the coinbases going to the imported 'ADDRESS_WATCHONLY' + assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': False})), 1 if self.options.descriptors else 2) + assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': True})), 1 if self.options.descriptors else COINBASE_MATURITY + 2) + if not self.options.descriptors: # Tests legacy watchonly behavior which is not present (and does not need to be tested) in descriptor wallets assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50) diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 158ef66110..2ee3e00a7b 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -151,7 +151,7 @@ class BumpFeeTest(BitcoinTestFramework): self.log.info("Test invalid estimate_mode settings") for k, v in {"number": 42, "object": {"foo": "bar"}}.items(): - assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k), + assert_raises_rpc_error(-3, f"JSON value of type {k} for field estimate_mode is not of expected type string", rbf_node.bumpfee, rbfid, {"estimate_mode": v}) for mode in ["foo", Decimal("3.1415"), "sat/B", "BTC/kB"]: assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', diff --git a/test/functional/wallet_crosschain.py b/test/functional/wallet_crosschain.py index b6d0c87985..c0047542ed 100755 --- a/test/functional/wallet_crosschain.py +++ b/test/functional/wallet_crosschain.py @@ -21,7 +21,7 @@ class WalletCrossChain(BitcoinTestFramework): # Switch node 1 to testnet before starting it. self.nodes[1].chain = 'testnet3' - self.nodes[1].extra_args = ['-maxconnections=0'] # disable testnet sync + self.nodes[1].extra_args = ['-maxconnections=0', '-prune=550'] # disable testnet sync with open(self.nodes[1].bitcoinconf, 'r', encoding='utf8') as conf: conf_data = conf.read() with open (self.nodes[1].bitcoinconf, 'w', encoding='utf8') as conf: @@ -51,7 +51,7 @@ class WalletCrossChain(BitcoinTestFramework): if not self.options.descriptors: self.log.info("Override cross-chain wallet load protection") self.stop_nodes() - self.start_nodes([['-walletcrosschain']] * self.num_nodes) + self.start_nodes([['-walletcrosschain', '-prune=550']] * self.num_nodes) self.nodes[0].loadwallet(node1_wallet) self.nodes[1].loadwallet(node0_wallet) diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py index 5dc23ba245..e7cfa56c46 100755 --- a/test/functional/wallet_descriptor.py +++ b/test/functional/wallet_descriptor.py @@ -62,6 +62,11 @@ class WalletDescriptorTest(BitcoinTestFramework): assert addr_info['desc'].startswith('wpkh(') assert_equal(addr_info['hdkeypath'], 'm/84\'/1\'/0\'/0/0') + addr = self.nodes[0].getnewaddress("", "bech32m") + addr_info = self.nodes[0].getaddressinfo(addr) + assert addr_info['desc'].startswith('tr(') + assert_equal(addr_info['hdkeypath'], 'm/86\'/1\'/0\'/0/0') + # Check that getrawchangeaddress works addr = self.nodes[0].getrawchangeaddress("legacy") addr_info = self.nodes[0].getaddressinfo(addr) @@ -78,6 +83,11 @@ class WalletDescriptorTest(BitcoinTestFramework): assert addr_info['desc'].startswith('wpkh(') assert_equal(addr_info['hdkeypath'], 'm/84\'/1\'/0\'/1/0') + addr = self.nodes[0].getrawchangeaddress("bech32m") + addr_info = self.nodes[0].getaddressinfo(addr) + assert addr_info['desc'].startswith('tr(') + assert_equal(addr_info['hdkeypath'], 'm/86\'/1\'/0\'/1/0') + # Make a wallet to receive coins at self.nodes[0].createwallet(wallet_name="desc2", descriptors=True) recv_wrpc = self.nodes[0].get_wallet_rpc("desc2") @@ -161,9 +171,11 @@ class WalletDescriptorTest(BitcoinTestFramework): addr_types = [('legacy', False, 'pkh(', '44\'/1\'/0\'', -13), ('p2sh-segwit', False, 'sh(wpkh(', '49\'/1\'/0\'', -14), ('bech32', False, 'wpkh(', '84\'/1\'/0\'', -13), + ('bech32m', False, 'tr(', '86\'/1\'/0\'', -13), ('legacy', True, 'pkh(', '44\'/1\'/0\'', -13), ('p2sh-segwit', True, 'sh(wpkh(', '49\'/1\'/0\'', -14), - ('bech32', True, 'wpkh(', '84\'/1\'/0\'', -13)] + ('bech32', True, 'wpkh(', '84\'/1\'/0\'', -13), + ('bech32m', True, 'tr(', '86\'/1\'/0\'', -13)] for addr_type, internal, desc_prefix, deriv_path, int_idx in addr_types: int_str = 'internal' if internal else 'external' diff --git a/test/functional/wallet_fast_rescan.py b/test/functional/wallet_fast_rescan.py new file mode 100755 index 0000000000..3b8ae8eb92 --- /dev/null +++ b/test/functional/wallet_fast_rescan.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test that fast rescan using block filters for descriptor wallets detects + top-ups correctly and finds the same transactions than the slow variant.""" +import os +from typing import List + +from test_framework.descriptors import descsum_create +from test_framework.test_framework import BitcoinTestFramework +from test_framework.test_node import TestNode +from test_framework.util import assert_equal +from test_framework.wallet import MiniWallet +from test_framework.wallet_util import get_generate_key + + +KEYPOOL_SIZE = 100 # smaller than default size to speed-up test +NUM_DESCRIPTORS = 9 # number of descriptors (8 default ranged ones + 1 fixed non-ranged one) +NUM_BLOCKS = 6 # number of blocks to mine + + +class WalletFastRescanTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.extra_args = [[f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=1']] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + self.skip_if_no_sqlite() + + def get_wallet_txids(self, node: TestNode, wallet_name: str) -> List[str]: + w = node.get_wallet_rpc(wallet_name) + txs = w.listtransactions('*', 1000000) + return [tx['txid'] for tx in txs] + + def run_test(self): + node = self.nodes[0] + wallet = MiniWallet(node) + wallet.rescan_utxos() + + self.log.info("Create descriptor wallet with backup") + WALLET_BACKUP_FILENAME = os.path.join(node.datadir, 'wallet.bak') + node.createwallet(wallet_name='topup_test', descriptors=True) + w = node.get_wallet_rpc('topup_test') + fixed_key = get_generate_key() + print(w.importdescriptors([{"desc": descsum_create(f"wpkh({fixed_key.privkey})"), "timestamp": "now"}])) + descriptors = w.listdescriptors()['descriptors'] + assert_equal(len(descriptors), NUM_DESCRIPTORS) + w.backupwallet(WALLET_BACKUP_FILENAME) + + self.log.info(f"Create txs sending to end range address of each descriptor, triggering top-ups") + for i in range(NUM_BLOCKS): + self.log.info(f"Block {i+1}/{NUM_BLOCKS}") + for desc_info in w.listdescriptors()['descriptors']: + if 'range' in desc_info: + start_range, end_range = desc_info['range'] + addr = w.deriveaddresses(desc_info['desc'], [end_range, end_range])[0] + spk = bytes.fromhex(w.getaddressinfo(addr)['scriptPubKey']) + self.log.info(f"-> range [{start_range},{end_range}], last address {addr}") + else: + spk = bytes.fromhex(fixed_key.p2wpkh_script) + self.log.info(f"-> fixed non-range descriptor address {fixed_key.p2wpkh_addr}") + wallet.send_to(from_node=node, scriptPubKey=spk, amount=10000) + self.generate(node, 1) + + self.log.info("Import wallet backup with block filter index") + with node.assert_debug_log(['fast variant using block filters']): + node.restorewallet('rescan_fast', WALLET_BACKUP_FILENAME) + txids_fast = self.get_wallet_txids(node, 'rescan_fast') + + self.log.info("Import non-active descriptors with block filter index") + node.createwallet(wallet_name='rescan_fast_nonactive', descriptors=True, disable_private_keys=True, blank=True) + with node.assert_debug_log(['fast variant using block filters']): + w = node.get_wallet_rpc('rescan_fast_nonactive') + w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors]) + txids_fast_nonactive = self.get_wallet_txids(node, 'rescan_fast_nonactive') + + self.restart_node(0, [f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=0']) + self.log.info("Import wallet backup w/o block filter index") + with node.assert_debug_log(['slow variant inspecting all blocks']): + node.restorewallet("rescan_slow", WALLET_BACKUP_FILENAME) + txids_slow = self.get_wallet_txids(node, 'rescan_slow') + + self.log.info("Import non-active descriptors w/o block filter index") + node.createwallet(wallet_name='rescan_slow_nonactive', descriptors=True, disable_private_keys=True, blank=True) + with node.assert_debug_log(['slow variant inspecting all blocks']): + w = node.get_wallet_rpc('rescan_slow_nonactive') + w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors]) + txids_slow_nonactive = self.get_wallet_txids(node, 'rescan_slow_nonactive') + + self.log.info("Verify that all rescans found the same txs in slow and fast variants") + assert_equal(len(txids_slow), NUM_DESCRIPTORS * NUM_BLOCKS) + assert_equal(len(txids_fast), NUM_DESCRIPTORS * NUM_BLOCKS) + assert_equal(len(txids_slow_nonactive), NUM_DESCRIPTORS * NUM_BLOCKS) + assert_equal(len(txids_fast_nonactive), NUM_DESCRIPTORS * NUM_BLOCKS) + assert_equal(sorted(txids_slow), sorted(txids_fast)) + assert_equal(sorted(txids_slow_nonactive), sorted(txids_fast_nonactive)) + + +if __name__ == '__main__': + WalletFastRescanTest().main() diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index f259449bef..aff408ceb1 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -45,6 +45,7 @@ class ListSinceBlockTest(BitcoinTestFramework): if self.options.descriptors: self.test_desc() self.test_send_to_self() + self.test_op_return() def test_no_blockhash(self): self.log.info("Test no blockhash") @@ -448,6 +449,19 @@ class ListSinceBlockTest(BitcoinTestFramework): assert any(c["address"] == addr for c in coins) assert all(self.nodes[2].getaddressinfo(c["address"])["ischange"] for c in coins) + def test_op_return(self): + """Test if OP_RETURN outputs will be displayed correctly.""" + block_hash = self.nodes[2].getbestblockhash() + + raw_tx = self.nodes[2].createrawtransaction([], [{'data': 'aa'}]) + funded_tx = self.nodes[2].fundrawtransaction(raw_tx) + signed_tx = self.nodes[2].signrawtransactionwithwallet(funded_tx['hex']) + tx_id = self.nodes[2].sendrawtransaction(signed_tx['hex']) + + op_ret_tx = [tx for tx in self.nodes[2].listsinceblock(blockhash=block_hash)["transactions"] if tx['txid'] == tx_id][0] + + assert 'address' not in op_ret_tx + if __name__ == '__main__': ListSinceBlockTest().main() diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 7c16b6328d..9bb06774a5 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -109,6 +109,7 @@ class ListTransactionsTest(BitcoinTestFramework): self.run_rbf_opt_in_test() self.run_externally_generated_address_test() self.run_invalid_parameters_test() + self.test_op_return() def run_rbf_opt_in_test(self): """Test the opt-in-rbf flag for sent and received transactions.""" @@ -284,6 +285,17 @@ class ListTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-8, "Negative count", self.nodes[0].listtransactions, count=-1) assert_raises_rpc_error(-8, "Negative from", self.nodes[0].listtransactions, skip=-1) + def test_op_return(self): + """Test if OP_RETURN outputs will be displayed correctly.""" + raw_tx = self.nodes[0].createrawtransaction([], [{'data': 'aa'}]) + funded_tx = self.nodes[0].fundrawtransaction(raw_tx) + signed_tx = self.nodes[0].signrawtransactionwithwallet(funded_tx['hex']) + tx_id = self.nodes[0].sendrawtransaction(signed_tx['hex']) + + op_ret_tx = [tx for tx in self.nodes[0].listtransactions() if tx['txid'] == tx_id][0] + + assert 'address' not in op_ret_tx + if __name__ == '__main__': ListTransactionsTest().main() diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index 07baa0595e..83b8332c4d 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -362,7 +362,7 @@ class WalletSendTest(BitcoinTestFramework): for mode in ["economical", "conservative"]: for k, v in {"string": "true", "bool": True, "object": {"foo": "bar"}}.items(): self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=v, estimate_mode=mode, - expect_error=(-3, f"Expected type number for conf_target, got {k}")) + expect_error=(-3, f"JSON value of type {k} for field conf_target is not of expected type number")) # Test setting explicit fee rate just below the minimum of 1 sat/vB. self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if fee_rate of 0.99999999 is passed") @@ -508,7 +508,7 @@ class WalletSendTest(BitcoinTestFramework): ext_utxo = ext_fund.listunspent(addresses=[addr])[0] # An external input without solving data should result in an error - self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, expect_error=(-4, "Insufficient funds")) + self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, expect_error=(-4, "Not solvable pre-selected input COutPoint(%s, %s)" % (ext_utxo["txid"][0:10], ext_utxo["vout"]))) # But funding should work when the solving data is provided res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, solving_data={"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"], addr_info["embedded"]["embedded"]["scriptPubKey"]]}) diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py index 5609ac9bf5..db3a8a2efa 100755 --- a/test/functional/wallet_signer.py +++ b/test/functional/wallet_signer.py @@ -98,7 +98,7 @@ class WalletSignerTest(BitcoinTestFramework): # ) # self.clear_mock_result(self.nodes[1]) - assert_equal(hww.getwalletinfo()["keypoolsize"], 30) + assert_equal(hww.getwalletinfo()["keypoolsize"], 40) address1 = hww.getnewaddress(address_type="bech32") assert_equal(address1, "bcrt1qm90ugl4d48jv8n6e5t9ln6t9zlpm5th68x4f8g") @@ -121,6 +121,13 @@ class WalletSignerTest(BitcoinTestFramework): assert_equal(address_info['ismine'], True) assert_equal(address_info['hdkeypath'], "m/44'/1'/0'/0/0") + address4 = hww.getnewaddress(address_type="bech32m") + assert_equal(address4, "bcrt1phw4cgpt6cd30kz9k4wkpwm872cdvhss29jga2xpmftelhqll62ms4e9sqj") + address_info = hww.getaddressinfo(address4) + assert_equal(address_info['solvable'], True) + assert_equal(address_info['ismine'], True) + assert_equal(address_info['hdkeypath'], "m/86'/1'/0'/0/0") + self.log.info('Test walletdisplayaddress') result = hww.walletdisplayaddress(address1) assert_equal(result, {"address": address1}) @@ -133,7 +140,7 @@ class WalletSignerTest(BitcoinTestFramework): self.clear_mock_result(self.nodes[1]) self.log.info('Prepare mock PSBT') - self.nodes[0].sendtoaddress(address1, 1) + self.nodes[0].sendtoaddress(address4, 1) self.generate(self.nodes[0], 1) # Load private key into wallet to generate a signed PSBT for the mock @@ -142,14 +149,14 @@ class WalletSignerTest(BitcoinTestFramework): assert mock_wallet.getwalletinfo()['private_keys_enabled'] result = mock_wallet.importdescriptors([{ - "desc": "wpkh([00000001/84'/1'/0']tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0/*)#rweraev0", + "desc": "tr([00000001/86'/1'/0']tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0/*)#0jtt2jc9", "timestamp": 0, "range": [0,1], "internal": False, "active": True }, { - "desc": "wpkh([00000001/84'/1'/0']tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/*)#j6uzqvuh", + "desc": "tr([00000001/86'/1'/0']tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/*)#7xw2h8ga", "timestamp": 0, "range": [0, 0], "internal": True, diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index 3c630ba433..4c28958982 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -5,6 +5,7 @@ """Test generation and spending of P2TR addresses.""" import random +import uuid from decimal import Decimal from test_framework.address import output_key_to_p2tr @@ -156,9 +157,6 @@ KEYS = [ } ] -CHANGE_XPRV = "tprv8ZgxMBicQKsPcyDrWwiecVnTtFmfRwbfFqEfR4ZGWvq5aTTwLBWmAm5zrbMcYtb9gQNFfhRfqhhrBG37U3nhmXxEgeEPBJGHAPrHCrAd1WX" -CHANGE_XPUB = "tpubD6NzVbkrYhZ4WSFeQbPF1uSaTHHbbGnZq8qShabZwCdUQwihxaLMMFhs2kidGF2qrRKiQVqw8VoyuTHj1bZqmMXMeciaU1gBjWA1sim2zUB" - def key(hex_key): """Construct an x-only pubkey from its hex representation.""" @@ -229,17 +227,28 @@ class WalletTaprootTest(BitcoinTestFramework): def do_test_addr(self, comment, pattern, privmap, treefn, keys): self.log.info("Testing %s address derivation" % comment) + + # Create wallets + wallet_uuid = uuid.uuid4().hex + self.nodes[0].createwallet(wallet_name=f"privs_tr_enabled_{wallet_uuid}", descriptors=True, blank=True) + self.nodes[0].createwallet(wallet_name=f"pubs_tr_enabled_{wallet_uuid}", descriptors=True, blank=True, disable_private_keys=True) + self.nodes[0].createwallet(wallet_name=f"addr_gen_{wallet_uuid}", descriptors=True, disable_private_keys=True, blank=True) + privs_tr_enabled = self.nodes[0].get_wallet_rpc(f"privs_tr_enabled_{wallet_uuid}") + pubs_tr_enabled = self.nodes[0].get_wallet_rpc(f"pubs_tr_enabled_{wallet_uuid}") + addr_gen = self.nodes[0].get_wallet_rpc(f"addr_gen_{wallet_uuid}") + desc = self.make_desc(pattern, privmap, keys, False) desc_pub = self.make_desc(pattern, privmap, keys, True) assert_equal(self.nodes[0].getdescriptorinfo(desc)['descriptor'], desc_pub) - result = self.addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}]) + result = addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}]) assert(result[0]['success']) + address_type = "bech32m" if "tr" in pattern else "bech32" for i in range(4): - addr_g = self.addr_gen.getnewaddress(address_type='bech32m') + addr_g = addr_gen.getnewaddress(address_type=address_type) if treefn is not None: addr_r = self.make_addr(treefn, keys, i) assert_equal(addr_g, addr_r) - desc_a = self.addr_gen.getaddressinfo(addr_g)['desc'] + desc_a = addr_gen.getaddressinfo(addr_g)['desc'] if desc.startswith("tr("): assert desc_a.startswith("tr(") rederive = self.nodes[1].deriveaddresses(desc_a) @@ -247,25 +256,37 @@ class WalletTaprootTest(BitcoinTestFramework): assert_equal(rederive[0], addr_g) # tr descriptors can be imported - result = self.privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) + result = privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) assert(result[0]["success"]) - result = self.pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) + result = pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) assert(result[0]["success"]) + # Cleanup + privs_tr_enabled.unloadwallet() + pubs_tr_enabled.unloadwallet() + addr_gen.unloadwallet() + def do_test_sendtoaddress(self, comment, pattern, privmap, treefn, keys_pay, keys_change): self.log.info("Testing %s through sendtoaddress" % comment) + + # Create wallets + wallet_uuid = uuid.uuid4().hex + self.nodes[0].createwallet(wallet_name=f"rpc_online_{wallet_uuid}", descriptors=True, blank=True) + rpc_online = self.nodes[0].get_wallet_rpc(f"rpc_online_{wallet_uuid}") + desc_pay = self.make_desc(pattern, privmap, keys_pay) desc_change = self.make_desc(pattern, privmap, keys_change) desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True) desc_change_pub = self.make_desc(pattern, privmap, keys_change, True) assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub) assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub) - result = self.rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) + result = rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) assert(result[0]['success']) - result = self.rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) + result = rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) assert(result[0]['success']) + address_type = "bech32m" if "tr" in pattern else "bech32" for i in range(4): - addr_g = self.rpc_online.getnewaddress(address_type='bech32m') + addr_g = rpc_online.getnewaddress(address_type=address_type) if treefn is not None: addr_r = self.make_addr(treefn, keys_pay, i) assert_equal(addr_g, addr_r) @@ -273,31 +294,51 @@ class WalletTaprootTest(BitcoinTestFramework): to_amnt = random.randrange(1000000, boring_balance) self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - test_balance = int(self.rpc_online.getbalance() * 100000000) + test_balance = int(rpc_online.getbalance() * 100000000) ret_amnt = random.randrange(100000, test_balance) # Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends. - res = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True, fee_rate=200) + res = rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True, fee_rate=200) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - assert(self.rpc_online.gettransaction(res)["confirmations"] > 0) + assert(rpc_online.gettransaction(res)["confirmations"] > 0) + + # Cleanup + txid = rpc_online.sendall(recipients=[self.boring.getnewaddress()])["txid"] + self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) + assert(rpc_online.gettransaction(txid)["confirmations"] > 0) + rpc_online.unloadwallet() def do_test_psbt(self, comment, pattern, privmap, treefn, keys_pay, keys_change): self.log.info("Testing %s through PSBT" % comment) + + # Create wallets + wallet_uuid = uuid.uuid4().hex + self.nodes[0].createwallet(wallet_name=f"psbt_online_{wallet_uuid}", descriptors=True, disable_private_keys=True, blank=True) + self.nodes[1].createwallet(wallet_name=f"psbt_offline_{wallet_uuid}", descriptors=True, blank=True) + self.nodes[1].createwallet(f"key_only_wallet_{wallet_uuid}", descriptors=True, blank=True) + psbt_online = self.nodes[0].get_wallet_rpc(f"psbt_online_{wallet_uuid}") + psbt_offline = self.nodes[1].get_wallet_rpc(f"psbt_offline_{wallet_uuid}") + key_only_wallet = self.nodes[1].get_wallet_rpc(f"key_only_wallet_{wallet_uuid}") + desc_pay = self.make_desc(pattern, privmap, keys_pay, False) desc_change = self.make_desc(pattern, privmap, keys_change, False) desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True) desc_change_pub = self.make_desc(pattern, privmap, keys_change, True) assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub) assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub) - result = self.psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}]) + result = psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}]) assert(result[0]['success']) - result = self.psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}]) + result = psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}]) assert(result[0]['success']) - result = self.psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) + result = psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) assert(result[0]['success']) - result = self.psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) + result = psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) assert(result[0]['success']) + for key in keys_pay + keys_change: + result = key_only_wallet.importdescriptors([{"desc": descsum_create(f"wpkh({key['xprv']}/*)"), "timestamp":"now"}]) + assert(result[0]["success"]) + address_type = "bech32m" if "tr" in pattern else "bech32" for i in range(4): - addr_g = self.psbt_online.getnewaddress(address_type='bech32m') + addr_g = psbt_online.getnewaddress(address_type=address_type) if treefn is not None: addr_r = self.make_addr(treefn, keys_pay, i) assert_equal(addr_g, addr_r) @@ -305,28 +346,43 @@ class WalletTaprootTest(BitcoinTestFramework): to_amnt = random.randrange(1000000, boring_balance) self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - test_balance = int(self.psbt_online.getbalance() * 100000000) + test_balance = int(psbt_online.getbalance() * 100000000) ret_amnt = random.randrange(100000, test_balance) # Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends. - psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": "bech32m"})['psbt'] - res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) - - decoded = self.psbt_offline.decodepsbt(res["psbt"]) - if pattern.startswith("tr("): - for psbtin in decoded["inputs"]: - assert "non_witness_utxo" not in psbtin - assert "witness_utxo" in psbtin - assert "taproot_internal_key" in psbtin - assert "taproot_bip32_derivs" in psbtin - assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin - if "taproot_script_path_sigs" in psbtin: - assert "taproot_merkle_root" in psbtin - assert "taproot_scripts" in psbtin - - rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] + psbt = psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": address_type})['psbt'] + res = psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) + for wallet in [psbt_offline, key_only_wallet]: + res = wallet.walletprocesspsbt(psbt=psbt, finalize=False) + + decoded = wallet.decodepsbt(res["psbt"]) + if pattern.startswith("tr("): + for psbtin in decoded["inputs"]: + assert "non_witness_utxo" not in psbtin + assert "witness_utxo" in psbtin + assert "taproot_internal_key" in psbtin + assert "taproot_bip32_derivs" in psbtin + assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin + if "taproot_script_path_sigs" in psbtin: + assert "taproot_merkle_root" in psbtin + assert "taproot_scripts" in psbtin + + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] + res = self.nodes[0].testmempoolaccept([rawtx]) + assert res[0]["allowed"] + txid = self.nodes[0].sendrawtransaction(rawtx) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0) + assert(psbt_online.gettransaction(txid)['confirmations'] > 0) + + # Cleanup + psbt = psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"] + res = psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] + txid = self.nodes[0].sendrawtransaction(rawtx) + self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) + assert(psbt_online.gettransaction(txid)['confirmations'] > 0) + psbt_online.unloadwallet() + psbt_offline.unloadwallet() def do_test(self, comment, pattern, privmap, treefn): nkeys = len(privmap) @@ -336,21 +392,8 @@ class WalletTaprootTest(BitcoinTestFramework): self.do_test_psbt(comment, pattern, privmap, treefn, keys[2*nkeys:3*nkeys], keys[3*nkeys:4*nkeys]) def run_test(self): - self.log.info("Creating wallets...") - self.nodes[0].createwallet(wallet_name="privs_tr_enabled", descriptors=True, blank=True) - self.privs_tr_enabled = self.nodes[0].get_wallet_rpc("privs_tr_enabled") - self.nodes[0].createwallet(wallet_name="pubs_tr_enabled", descriptors=True, blank=True, disable_private_keys=True) - self.pubs_tr_enabled = self.nodes[0].get_wallet_rpc("pubs_tr_enabled") self.nodes[0].createwallet(wallet_name="boring") - self.nodes[0].createwallet(wallet_name="addr_gen", descriptors=True, disable_private_keys=True, blank=True) - self.nodes[0].createwallet(wallet_name="rpc_online", descriptors=True, blank=True) - self.nodes[0].createwallet(wallet_name="psbt_online", descriptors=True, disable_private_keys=True, blank=True) - self.nodes[1].createwallet(wallet_name="psbt_offline", descriptors=True, blank=True) self.boring = self.nodes[0].get_wallet_rpc("boring") - self.addr_gen = self.nodes[0].get_wallet_rpc("addr_gen") - self.rpc_online = self.nodes[0].get_wallet_rpc("rpc_online") - self.psbt_online = self.nodes[0].get_wallet_rpc("psbt_online") - self.psbt_offline = self.nodes[1].get_wallet_rpc("psbt_offline") self.log.info("Mining blocks...") gen_addr = self.boring.getnewaddress() @@ -460,18 +503,5 @@ class WalletTaprootTest(BitcoinTestFramework): lambda k1: key(k1) ) - self.log.info("Sending everything back...") - - txid = self.rpc_online.sendall(recipients=[self.boring.getnewaddress()])["txid"] - self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0) - - psbt = self.psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"] - res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) - rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] - txid = self.nodes[0].sendrawtransaction(rawtx) - self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0) - if __name__ == '__main__': WalletTaprootTest().main() diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index abf38ca79b..b69bbe7cd0 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -15,7 +15,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES = ( "chainparamsbase -> util/system -> chainparamsbase", "node/blockstorage -> validation -> node/blockstorage", "node/utxo_snapshot -> validation -> node/utxo_snapshot", - "policy/fees -> txmempool -> policy/fees", "qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel", "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel", "qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog", |