diff options
74 files changed, 1151 insertions, 715 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index ff84c01a39..8dad9c4f9d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -61,5 +61,5 @@ test_script: - ps: src\bench_bitcoin.exe -evals=1 -scaling=0 - ps: python test\util\bitcoin-util-test.py - cmd: python test\util\rpcauth-test.py -- cmd: python test\functional\test_runner.py --force --quiet --combinedlogslen=4000 --exclude wallet_multiwallet +- cmd: python test\functional\test_runner.py --force --quiet --combinedlogslen=4000 deploy: off diff --git a/configure.ac b/configure.ac index 72bd785e2e..7141a9a03a 100644 --- a/configure.ac +++ b/configure.ac @@ -209,6 +209,11 @@ AC_ARG_ENABLE([zmq], [disable ZMQ notifications])], [use_zmq=$enableval], [use_zmq=yes]) +AC_ARG_ENABLE([bip70], + [AS_HELP_STRING([--disable-bip70], + [disable BIP70 (payment protocol) support in GUI (enabled by default)])], + [enable_bip70=$enableval], + [enable_bip70=yes]) AC_ARG_WITH([protoc-bindir],[AS_HELP_STRING([--with-protoc-bindir=BIN_DIR],[specify protoc bin path])], [protoc_bin_path=$withval], []) @@ -1082,7 +1087,9 @@ if test x$use_pkgconfig = xyes; then [ PKG_CHECK_MODULES([SSL], [libssl],, [AC_MSG_ERROR(openssl not found.)]) PKG_CHECK_MODULES([CRYPTO], [libcrypto],,[AC_MSG_ERROR(libcrypto not found.)]) - BITCOIN_QT_CHECK([PKG_CHECK_MODULES([PROTOBUF], [protobuf], [have_protobuf=yes], [BITCOIN_QT_FAIL(libprotobuf not found)])]) + if test x$enable_bip70 != xno; then + BITCOIN_QT_CHECK([PKG_CHECK_MODULES([PROTOBUF], [protobuf], [have_protobuf=yes], [BITCOIN_QT_FAIL(libprotobuf not found)])]) + fi if test x$use_qr != xno; then BITCOIN_QT_CHECK([PKG_CHECK_MODULES([QR], [libqrencode], [have_qrencode=yes], [have_qrencode=no])]) fi @@ -1142,7 +1149,9 @@ else esac fi - BITCOIN_QT_CHECK(AC_CHECK_LIB([protobuf] ,[main],[PROTOBUF_LIBS=-lprotobuf], BITCOIN_QT_FAIL(libprotobuf not found))) + if test x$enable_bip70 != xno; then + BITCOIN_QT_CHECK(AC_CHECK_LIB([protobuf] ,[main],[PROTOBUF_LIBS=-lprotobuf], BITCOIN_QT_FAIL(libprotobuf not found))) + fi if test x$use_qr != xno; then BITCOIN_QT_CHECK([AC_CHECK_LIB([qrencode], [main],[QR_LIBS=-lqrencode], [have_qrencode=no])]) BITCOIN_QT_CHECK([AC_CHECK_HEADER([qrencode.h],, have_qrencode=no)]) @@ -1220,7 +1229,9 @@ AM_CONDITIONAL([EMBEDDED_UNIVALUE],[test x$need_bundled_univalue = xyes]) AC_SUBST(UNIVALUE_CFLAGS) AC_SUBST(UNIVALUE_LIBS) +if test x$enable_bip70 != xno; then BITCOIN_QT_PATH_PROGS([PROTOC], [protoc],$protoc_bin_path) +fi AC_MSG_CHECKING([whether to build bitcoind]) AM_CONDITIONAL([BUILD_BITCOIND], [test x$build_bitcoind = xyes]) @@ -1338,6 +1349,15 @@ if test x$bitcoin_enable_qt != xno; then else AC_MSG_RESULT([no]) fi + + AC_MSG_CHECKING([whether to build BIP70 support]) + if test x$enable_bip70 != xno; then + AC_DEFINE([ENABLE_BIP70],[1],[Define if BIP70 support should be compiled in]) + enable_bip70=yes + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi fi AM_CONDITIONAL([ENABLE_ZMQ], [test "x$use_zmq" = "xyes"]) @@ -1369,6 +1389,7 @@ AM_CONDITIONAL([ENABLE_WALLET],[test x$enable_wallet = xyes]) AM_CONDITIONAL([ENABLE_TESTS],[test x$BUILD_TEST = xyes]) AM_CONDITIONAL([ENABLE_QT],[test x$bitcoin_enable_qt = xyes]) AM_CONDITIONAL([ENABLE_QT_TESTS],[test x$BUILD_TEST_QT = xyes]) +AM_CONDITIONAL([ENABLE_BIP70],[test x$enable_bip70 = xyes]) AM_CONDITIONAL([ENABLE_BENCH],[test x$use_bench = xyes]) AM_CONDITIONAL([USE_QRCODE], [test x$use_qr = xyes]) AM_CONDITIONAL([USE_LCOV],[test x$use_lcov = xyes]) @@ -1503,6 +1524,7 @@ echo "Options used to compile and link:" echo " with wallet = $enable_wallet" echo " with gui / qt = $bitcoin_enable_qt" if test x$bitcoin_enable_qt != xno; then + echo " with bip70 = $enable_bip70" echo " with qr = $use_qr" fi echo " with zmq = $use_zmq" diff --git a/doc/README.md b/doc/README.md index b3f875c4a4..acee6d0c80 100644 --- a/doc/README.md +++ b/doc/README.md @@ -3,7 +3,7 @@ Bitcoin Core Setup --------------------- -Bitcoin Core is the original Bitcoin client and it builds the backbone of the network. It downloads and, by default, stores the entire history of Bitcoin transactions (which is currently more than 100 GBs); depending on the speed of your computer and network connection, the synchronization process can take anywhere from a few hours to a day or more. +Bitcoin Core is the original Bitcoin client and it builds the backbone of the network. It downloads and, by default, stores the entire history of Bitcoin transactions, which requires a few hundred gigabytes of disk space. Depending on the speed of your computer and network connection, the synchronization process can take anywhere from a few hours to a day or more. To download Bitcoin Core, visit [bitcoincore.org](https://bitcoincore.org/en/releases/). @@ -30,7 +30,7 @@ Drag Bitcoin Core to your applications folder, and then run Bitcoin Core. * See the documentation at the [Bitcoin Wiki](https://en.bitcoin.it/wiki/Main_Page) for help and more information. -* Ask for help on [#bitcoin](http://webchat.freenode.net?channels=bitcoin) on Freenode. If you don't have an IRC client use [webchat here](http://webchat.freenode.net?channels=bitcoin). +* Ask for help on [#bitcoin](http://webchat.freenode.net?channels=bitcoin) on Freenode. If you don't have an IRC client, use [webchat here](http://webchat.freenode.net?channels=bitcoin). * Ask for help on the [BitcoinTalk](https://bitcointalk.org/) forums, in the [Technical Support board](https://bitcointalk.org/index.php?board=4.0). Building @@ -64,11 +64,12 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th ### Resources * Discuss on the [BitcoinTalk](https://bitcointalk.org/) forums, in the [Development & Technical Discussion board](https://bitcointalk.org/index.php?board=6.0). -* Discuss project-specific development on #bitcoin-core-dev on Freenode. If you don't have an IRC client use [webchat here](http://webchat.freenode.net/?channels=bitcoin-core-dev). -* Discuss general Bitcoin development on #bitcoin-dev on Freenode. If you don't have an IRC client use [webchat here](http://webchat.freenode.net/?channels=bitcoin-dev). +* Discuss project-specific development on #bitcoin-core-dev on Freenode. If you don't have an IRC client, use [webchat here](http://webchat.freenode.net/?channels=bitcoin-core-dev). +* Discuss general Bitcoin development on #bitcoin-dev on Freenode. If you don't have an IRC client, use [webchat here](http://webchat.freenode.net/?channels=bitcoin-dev). ### Miscellaneous - [Assets Attribution](assets-attribution.md) +- [bitcoin.conf Configuration File](bitcoin-conf.md) - [Files](files.md) - [Fuzz-testing](fuzzing.md) - [Reduce Traffic](reduce-traffic.md) diff --git a/doc/bitcoin-conf.md b/doc/bitcoin-conf.md new file mode 100644 index 0000000000..88ecb8fe65 --- /dev/null +++ b/doc/bitcoin-conf.md @@ -0,0 +1,37 @@ +# `bitcoin.conf` Configuration File + +The configuration file is used by `bitcoind`, `bitcoin-qt` and `bitcoin-cli`. + +All command-line options (except for `-?`, `-help`, `-version` and `-conf`) may be specified in a configuration file, and all configuration file options (except for `includeconf`) may also be specified on the command line. Command-line options override values set in the configuration file and configuration file options override values set in the GUI. + +## Configuration File Format + +The configuration file is a plain text file and consists of `option=value` entries, one per line. Leading and trailing whitespaces are removed. + +In contrast to the command-line usage: +- an option must be specified without leading `-`; +- a value of the given option is mandatory; e.g., `testnet=1` (for chain selection options), `noconnect=1` (for negated options). + +### Blank lines + +Blank lines are allowed and ignored by the parser. + +### Comments + +A comment starts with a number sign (`#`) and extends to the end of the line. All comments are ignored by the parser. + +Comments may appear in two ways: +- on their own on an otherwise empty line (_preferable_); +- after an `option=value` entry. + +### Network specific options + +Network specific options can be: +- placed into sections with headers `[main]` (not `[mainnet]`), `[test]` (not `[testnet]`) or `[regtest]`; +- prefixed with a chain name; e.g., `regtest.maxmempool=100`. + +## Configuration File Path + +The configuration file is not automatically created; you can create it using your favorite text editor. By default, the configuration file name is `bitcoin.conf` and it is located in the Bitcoin data directory, but both the Bitcoin data directory and the configuration file path may be changed using the `-datadir` and `-conf` command-line options. + +The `includeconf=<file>` option in the `bitcoin.conf` file can be used to include additional configuration files. diff --git a/doc/descriptors.md b/doc/descriptors.md index c23ac06e8f..b25678e06a 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -22,19 +22,20 @@ Output descriptors currently support: ## Examples -- `pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)` represents a P2PK output. -- `pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)` represents a P2PKH output. -- `wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)` represents a P2WPKH output. -- `sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))` represents a P2SH-P2WPKH output. -- `combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)` represents a P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH output. -- `sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))` represents a (overly complicated) P2SH-P2WSH-P2PKH output. -- `multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)` represents a bare *1-of-2* multisig. -- `sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))` represents a P2SH *2-of-2* multisig. -- `wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))` represents a P2WSH *2-of-3* multisig. -- `sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))` represents a P2SH-P2WSH *1-of-3* multisig. -- `pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)` refers to a single P2PK output, using the public key part from the specified xpub. -- `pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2)` refers to a single P2PKH output, using child key *1'/2* of the specified xpub. -- `wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/0/*))` refers to a chain of *1-of-2* P2WSH multisig outputs, using public keys taken from two HD chains with corresponding derivation paths. +- `pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)` describes a P2PK output with the specified public key. +- `pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)` describes a P2PKH output with the specified public key. +- `wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)` describes a P2WPKH output with the specified public key. +- `sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))` describes a P2SH-P2WPKH output with the specified public key. +- `combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)` describes any P2PK, P2PKH, P2WPKH, or P2SH-P2WPKH output with the specified public key. +- `sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))` describes an (overly complicated) P2SH-P2WSH-P2PKH output with the specified public key. +- `multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)` describes a bare *1-of-2* multisig output with keys in the specified order. +- `sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))` describes a P2SH *2-of-2* multisig output with keys in the specified order. +- `wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))` describes a P2WSH *2-of-3* multisig output with keys in the specified order. +- `sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))` describes a P2SH-P2WSH *1-of-3* multisig output with keys in the specified order. +- `pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)` describes a P2PK output with the public key of the specified xpub. +- `pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2)` describes a P2PKH output with child key *1'/2* of the specified xpub. +- `pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)` describes a set of P2PKH outputs, but additionally specifies that the specified xpub is a child of a master with fingerprint `d34db33f`, and derived using path `44'/0'/0'`. +- `wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))` describes a set of *1-of-2* P2WSH multisig outputs where the first multisig key is the *1/0/`i`* child of the first specified xpub and the second multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default). ## Reference @@ -52,14 +53,20 @@ Descriptors consist of several types of expressions. The top level expression is - `raw(HEX)` (top level only): the script whose hex encoding is HEX. `KEY` expressions: -- Hex encoded public keys (66 characters starting with `02` or `03`, or 130 characters starting with `04`). - - Inside `wpkh` and `wsh`, only compressed public keys are permitted. -- [WIF](https://en.bitcoin.it/wiki/Wallet_import_format) encoded private keys may be specified instead of the corresponding public key, with the same meaning. --`xpub` encoded extended public key or `xprv` encoded private key (as defined in [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). - - Followed by zero or more `/NUM` unhardened and `/NUM'` hardened BIP32 derivation steps. - - Optionally followed by a single `/*` or `/*'` final step to denote all (direct) unhardened or hardened children. - - The usage of hardened derivation steps requires providing the private key. - - Instead of a `'`, the suffix `h` can be used to denote hardened derivation. +- Optionally, key origin information, consisting of: + - An open bracket `[` + - Exactly 8 hex characters for the fingerprint of the key where the derivation starts (see BIP32 for details) + - Followed by zero or more `/NUM` or `/NUM'` path elements to indicate unhardened or hardened derivation steps between the fingerprint and the key or xpub/xprv root that follows + - A closing bracket `]` +- Followed by the actual key, which is either: + - Hex encoded public keys (66 characters starting with `02` or `03`, or 130 characters starting with `04`). + - Inside `wpkh` and `wsh`, only compressed public keys are permitted. + - [WIF](https://en.bitcoin.it/wiki/Wallet_import_format) encoded private keys may be specified instead of the corresponding public key, with the same meaning. + -`xpub` encoded extended public key or `xprv` encoded private key (as defined in [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). + - Followed by zero or more `/NUM` unhardened and `/NUM'` hardened BIP32 derivation steps. + - Optionally followed by a single `/*` or `/*'` final step to denote all (direct) unhardened or hardened children. + - The usage of hardened derivation steps requires providing the private key. +- Anywhere a `'` suffix is permitted to denote hardened derivation, the suffix `h` can be used instead. `ADDR` expressions are any type of supported address: - P2PKH addresses (base58, of the form `1...`). Note that P2PKH addresses in descriptors cannot be used for P2PK outputs (use the `pk` function instead). @@ -91,6 +98,15 @@ on Bitcoin's OP_CHECKMULTISIG opcode. To support these, we introduce the multisig policy, where any *k* out of the *n* provided public keys must sign. +Key order is significant. A `multi()` expression describes a multisig script +with keys in the specified order, and in a search for TXOs, it will not match +outputs with multisig scriptPubKeys that have the same keys in a different +order. Also, to prevent a combinatorial explosion of the search space, if more +than one of the `multi()` key arguments is a BIP32 wildcard path ending in `/*` +or `*'`, the `multi()` expression only matches multisig scripts with the `i`th +child key from each wildcard path in lockstep, rather than scripts with any +combination of child keys from each wildcard path. + ### BIP32 derived keys and chains Most modern wallet software and hardware uses keys that are derived using @@ -101,12 +117,39 @@ path consists of a sequence of 0 or more integers (in the range *0..2<sup>31</sup>-1*) each optionally followed by `'` or `h`, and separated by `/` characters. The string may optionally end with the literal `/*` or `/*'` (or `/*h`) to refer to all unhardened or hardened -child keys instead. +child keys in a configurable range (by default `0-1000`, inclusive). Whenever a public key is described using a hardened derivation step, the script cannot be computed without access to the corresponding private key. +### Key origin identification + +In order to describe scripts whose signing keys reside on another device, +it may be necessary to identify the master key and derivation path an +xpub was derived with. + +For example, when following BIP44, it would be useful to describe a +change chain directly as `xpub.../44'/0'/0'/1/*` where `xpub...` +corresponds with the master key `m`. Unfortunately, since there are +hardened derivation steps that follow the xpub, this descriptor does not +let you compute scripts without access to the corresponding private keys. +Instead, it should be written as `xpub.../1/*`, where xpub corresponds to +`m/44'/0'/0'`. + +When interacting with a hardware device, it may be necessary to include +the entire path from the master down. BIP174 standardizes this by +providing the master key *fingerprint* (first 32 bit of the Hash160 of +the master pubkey), plus all derivation steps. To support constructing +these, we permit providing this key origin information inside the +descriptor language, even though it does not affect the actual +scriptPubKeys it refers to. + +Every public key can be prefixed by an 8-character hexadecimal +fingerprint plus optional derivation steps (hardened and unhardened) +surrounded by brackets, identifying the master and derivation path the key or xpub +that follows was derived with. + ### Including private keys Often it is useful to communicate a description of scripts along with the @@ -119,6 +162,6 @@ steps, or for dumping wallet descriptors including private key material. In order to easily represent the sets of scripts currently supported by existing Bitcoin Core wallets, a convenience function `combo` is -provided, which takes as input a public key, and constructs the P2PK, +provided, which takes as input a public key, and describes a set of P2PK, P2PKH, P2WPKH, and P2SH-P2WPH scripts for that key. In case the key is -uncompressed, it only constructs P2PK and P2PKH. +uncompressed, the set only includes P2PK and P2PKH scripts. diff --git a/doc/release-notes-13152.md b/doc/release-notes-13152.md index 72526f5355..ace56f4d1b 100644 --- a/doc/release-notes-13152.md +++ b/doc/release-notes-13152.md @@ -1,4 +1,5 @@ New RPC methods ------------ -- `getnodeaddresses` returns peer addresses known to this node. It may be used to connect to nodes over TCP without using the DNS seeds.
\ No newline at end of file +- `getnodeaddresses` returns peer addresses known to this node. It may be used to connect to nodes over TCP without using the DNS seeds. +- `listwalletdir` returns a list of wallets in the wallet directory which is configured with `-walletdir` parameter. diff --git a/doc/release-notes-14296.md b/doc/release-notes-14296.md new file mode 100644 index 0000000000..f7c52baace --- /dev/null +++ b/doc/release-notes-14296.md @@ -0,0 +1,5 @@ +addwitnessaddress RPC method removed +------------------------------------ + +The `addwitnessaddress` RPC was added for segwit testing in version 0.13.0. It +was deprecated in version 0.16.0. This version fully removes the RPC method. diff --git a/doc/release-notes-14468.md b/doc/release-notes-14468.md new file mode 100644 index 0000000000..fb0243aba8 --- /dev/null +++ b/doc/release-notes-14468.md @@ -0,0 +1,15 @@ +Wallet `generate` RPC method deprecated +--------------------------------------- + +The wallet's `generate` RPC method has been deprecated and will be fully +removed in v0.19. + +`generate` is only used for testing. The RPC call reaches across multiple +subsystems (wallet and mining), so is deprecated to simplify the wallet-node +interface. Projects that are using `generate` for testing purposes should +transition to using the `generatetoaddress` call, which does not require or use +the wallet component. Calling `generatetoaddress` with an address returned by +`getnewaddress` gives the same functionality as the old `generate` method. + +To continue using `generate` in v0.18, restart bitcoind with the +`-deprecatedrpc=generate` configuration. diff --git a/src/Makefile.am b/src/Makefile.am index 7a2e9fa5e8..6141919007 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -587,9 +587,11 @@ if HARDEN $(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) $(top_srcdir)/contrib/devtools/security-check.py < $(bin_PROGRAMS) endif +if ENABLE_BIP70 %.pb.cc %.pb.h: %.proto @test -f $(PROTOC) $(AM_V_GEN) $(PROTOC) --cpp_out=$(@D) --proto_path=$(<D) $< +endif if EMBEDDED_LEVELDB include Makefile.leveldb.include diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index c7a1963135..8d25e83529 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -122,7 +122,6 @@ QT_MOC_CPP = \ qt/moc_bitcoinamountfield.cpp \ qt/moc_bitcoingui.cpp \ qt/moc_bitcoinunits.cpp \ - qt/moc_callback.cpp \ qt/moc_clientmodel.cpp \ qt/moc_coincontroldialog.cpp \ qt/moc_coincontroltreewidget.cpp \ @@ -168,7 +167,6 @@ BITCOIN_MM = \ QT_MOC = \ qt/bitcoin.moc \ qt/bitcoinamountfield.moc \ - qt/callback.moc \ qt/intro.moc \ qt/overviewpage.moc \ qt/rpcconsole.moc @@ -178,9 +176,15 @@ QT_QRC = qt/bitcoin.qrc QT_QRC_LOCALE_CPP = qt/qrc_bitcoin_locale.cpp QT_QRC_LOCALE = qt/bitcoin_locale.qrc +if ENABLE_BIP70 PROTOBUF_CC = qt/paymentrequest.pb.cc PROTOBUF_H = qt/paymentrequest.pb.h PROTOBUF_PROTO = qt/paymentrequest.proto +else +PROTOBUF_CC = +PROTOBUF_H = +PROTOBUF_PROTO = +endif BITCOIN_QT_H = \ qt/addressbookpage.h \ @@ -191,7 +195,6 @@ BITCOIN_QT_H = \ qt/bitcoinamountfield.h \ qt/bitcoingui.h \ qt/bitcoinunits.h \ - qt/callback.h \ qt/clientmodel.h \ qt/coincontroldialog.h \ qt/coincontroltreewidget.h \ @@ -330,7 +333,6 @@ BITCOIN_QT_WALLET_CPP = \ qt/editaddressdialog.cpp \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ - qt/paymentrequestplus.cpp \ qt/paymentserver.cpp \ qt/receivecoinsdialog.cpp \ qt/receiverequestdialog.cpp \ @@ -349,13 +351,19 @@ BITCOIN_QT_WALLET_CPP = \ qt/walletmodeltransaction.cpp \ qt/walletview.cpp +BITCOIN_QT_WALLET_BIP70_CPP = \ + qt/paymentrequestplus.cpp + BITCOIN_QT_CPP = $(BITCOIN_QT_BASE_CPP) if TARGET_WINDOWS BITCOIN_QT_CPP += $(BITCOIN_QT_WINDOWS_CPP) endif if ENABLE_WALLET BITCOIN_QT_CPP += $(BITCOIN_QT_WALLET_CPP) -endif +if ENABLE_BIP70 +BITCOIN_QT_CPP += $(BITCOIN_QT_WALLET_BIP70_CPP) +endif # ENABLE_BIP70 +endif # ENABLE_WALLET RES_IMAGES = @@ -408,8 +416,11 @@ endif if ENABLE_ZMQ qt_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif +if ENABLE_BIP70 +qt_bitcoin_qt_LDADD += $(SSL_LIBS) +endif qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) \ - $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ + $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) qt_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) qt_bitcoin_qt_LIBTOOLFLAGS = $(AM_LIBTOOLFLAGS) --tag CXX diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 4b14212b2e..db7873e8b7 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -13,9 +13,12 @@ TEST_QT_MOC_CPP = \ if ENABLE_WALLET TEST_QT_MOC_CPP += \ qt/test/moc_addressbooktests.cpp \ - qt/test/moc_paymentservertests.cpp \ qt/test/moc_wallettests.cpp -endif +if ENABLE_BIP70 +TEST_QT_MOC_CPP += \ + qt/test/moc_paymentservertests.cpp +endif # ENABLE_BIP70 +endif # ENABLE_WALLET TEST_QT_H = \ qt/test/addressbooktests.h \ @@ -48,10 +51,13 @@ qt_test_test_bitcoin_qt_SOURCES = \ if ENABLE_WALLET qt_test_test_bitcoin_qt_SOURCES += \ qt/test/addressbooktests.cpp \ - qt/test/paymentservertests.cpp \ qt/test/wallettests.cpp \ wallet/test/wallet_test_fixture.cpp -endif +if ENABLE_BIP70 +qt_test_test_bitcoin_qt_SOURCES += \ + qt/test/paymentservertests.cpp +endif # ENABLE_BIP70 +endif # ENABLE_WALLET nodist_qt_test_test_bitcoin_qt_SOURCES = $(TEST_QT_MOC_CPP) diff --git a/src/arith_uint256.cpp b/src/arith_uint256.cpp index 13fa176f8b..791dad7a60 100644 --- a/src/arith_uint256.cpp +++ b/src/arith_uint256.cpp @@ -176,7 +176,7 @@ unsigned int base_uint<BITS>::bits() const for (int pos = WIDTH - 1; pos >= 0; pos--) { if (pn[pos]) { for (int nbits = 31; nbits > 0; nbits--) { - if (pn[pos] & 1 << nbits) + if (pn[pos] & 1U << nbits) return 32 * pos + nbits + 1; } return 32 * pos + 1; diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index a3fcb87675..6d86581ac6 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -356,7 +356,7 @@ static void MutateTxAddOutMultiSig(CMutableTransaction& tx, const std::string& s if (vStrInputParts.size() < numkeys + 3) throw std::runtime_error("incorrect number of multisig pubkeys"); - if (required < 1 || required > 20 || numkeys < 1 || numkeys > 20 || numkeys < required) + if (required < 1 || required > MAX_PUBKEYS_PER_MULTISIG || numkeys < 1 || numkeys > MAX_PUBKEYS_PER_MULTISIG || numkeys < required) throw std::runtime_error("multisig parameter mismatch. Required " \ + std::to_string(required) + " of " + std::to_string(numkeys) + "signatures."); diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 3714187a96..3eb77354c1 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -34,6 +34,16 @@ void DummyWalletInit::AddWalletOptions() const const WalletInitInterface& g_wallet_init_interface = DummyWalletInit(); +fs::path GetWalletDir() +{ + throw std::logic_error("Wallet function called in non-wallet build."); +} + +std::vector<fs::path> ListWalletDir() +{ + throw std::logic_error("Wallet function called in non-wallet build."); +} + std::vector<std::shared_ptr<CWallet>> GetWallets() { throw std::logic_error("Wallet function called in non-wallet build."); diff --git a/src/fs.cpp b/src/fs.cpp index a146107c4c..3c8f4c0247 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -3,6 +3,7 @@ #ifndef WIN32 #include <fcntl.h> #else +#define NOMINMAX #include <codecvt> #include <windows.h> #endif @@ -89,7 +90,7 @@ bool FileLock::TryLock() return false; } _OVERLAPPED overlapped = {0}; - if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 0, 0, &overlapped)) { + if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, std::numeric_limits<DWORD>::max(), std::numeric_limits<DWORD>::max(), &overlapped)) { reason = GetErrorReason(); return false; } diff --git a/src/index/base.cpp b/src/index/base.cpp index 788f7adccd..42c6b0373d 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -65,7 +65,7 @@ bool BaseIndex::Init() return true; } -static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev) +static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index d95b41657c..2b19e11f08 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -38,6 +38,8 @@ #include <univalue.h> class CWallet; +fs::path GetWalletDir(); +std::vector<fs::path> ListWalletDir(); std::vector<std::shared_ptr<CWallet>> GetWallets(); namespace interfaces { @@ -218,6 +220,18 @@ class NodeImpl : public Node LOCK(::cs_main); return ::pcoinsTip->GetCoin(output, coin); } + std::string getWalletDir() override + { + return GetWalletDir().string(); + } + std::vector<std::string> listWalletDir() override + { + std::vector<std::string> paths; + for (auto& path : ListWalletDir()) { + paths.push_back(path.string()); + } + return paths; + } std::vector<std::unique_ptr<Wallet>> getWallets() override { std::vector<std::unique_ptr<Wallet>> wallets; diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 8185c015a9..1f8bbbff7a 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -173,6 +173,12 @@ public: //! Get unspent outputs associated with a transaction. virtual bool getUnspentOutput(const COutPoint& output, Coin& coin) = 0; + //! Return default wallet directory. + virtual std::string getWalletDir() = 0; + + //! Return available wallets in wallet directory. + virtual std::vector<std::string> listWalletDir() = 0; + //! Return interfaces for accessing wallets (if any). virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 566ae37509..84079fdc3c 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -103,7 +103,7 @@ static WalletTxStatus MakeWalletTxStatus(const CWalletTx& wtx) EXCLUSIVE_LOCKS_R } //! Construct wallet TxOut struct. -static WalletTxOut MakeWalletTxOut(CWallet& wallet, const CWalletTx& wtx, int n, int depth) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static WalletTxOut MakeWalletTxOut(CWallet& wallet, const CWalletTx& wtx, int n, int depth) EXCLUSIVE_LOCKS_REQUIRED(cs_main, wallet.cs_wallet) { WalletTxOut result; result.txout = wtx.tx->vout[n]; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 6d8b530f69..0f834eb8c1 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -73,7 +73,7 @@ public: /* Below flags apply in the context of BIP 68*/ /* If this flag set, CTxIn::nSequence is NOT interpreted as a * relative lock-time. */ - static const uint32_t SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31); + static const uint32_t SEQUENCE_LOCKTIME_DISABLE_FLAG = (1U << 31); /* If CTxIn::nSequence encodes a relative lock-time and this flag * is set, the relative lock-time has units of 512 seconds, diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index a014ad4b28..c0f9b3d5d3 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -439,8 +439,10 @@ void BitcoinApplication::addWallet(WalletModel* walletModel) window->setCurrentWallet(walletModel->getWalletName()); } +#ifdef ENABLE_BIP70 connect(walletModel, &WalletModel::coinsSent, paymentServer, &PaymentServer::fetchPaymentACK); +#endif connect(walletModel, &WalletModel::unload, this, &BitcoinApplication::removeWallet); m_wallet_models.push_back(walletModel); @@ -467,7 +469,9 @@ void BitcoinApplication::initializeResult(bool success) // Log this only after AppInitMain finishes, as then logging setup is guaranteed complete qWarning() << "Platform customization:" << platformStyle->getName(); #ifdef ENABLE_WALLET +#ifdef ENABLE_BIP70 PaymentServer::LoadRootCAs(); +#endif paymentServer->setOptionsModel(optionsModel); #endif @@ -536,7 +540,7 @@ WId BitcoinApplication::getMainWinId() const static void SetupUIArgs() { -#ifdef ENABLE_WALLET +#if defined(ENABLE_WALLET) && defined(ENABLE_BIP70) gArgs.AddArg("-allowselfsignedrootcertificates", strprintf("Allow self signed root certificates (default: %u)", DEFAULT_SELFSIGNED_ROOTCERTS), true, OptionsCategory::GUI); #endif gArgs.AddArg("-choosedatadir", strprintf("Choose data directory on startup (default: %u)", DEFAULT_CHOOSE_DATADIR), false, OptionsCategory::GUI); diff --git a/src/qt/callback.h b/src/qt/callback.h deleted file mode 100644 index da6b0c4c2e..0000000000 --- a/src/qt/callback.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef BITCOIN_QT_CALLBACK_H -#define BITCOIN_QT_CALLBACK_H - -#include <QObject> - -class Callback : public QObject -{ - Q_OBJECT -public Q_SLOTS: - virtual void call() = 0; -}; - -template <typename F> -class FunctionCallback : public Callback -{ - F f; - -public: - explicit FunctionCallback(F f_) : f(std::move(f_)) {} - ~FunctionCallback() override {} - void call() override { f(this); } -}; - -template <typename F> -FunctionCallback<F>* makeCallback(F f) -{ - return new FunctionCallback<F>(std::move(f)); -} - -#endif // BITCOIN_QT_CALLBACK_H diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 68330c51fa..ea970c0bc9 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -2,10 +2,15 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/coincontroldialog.h> #include <qt/forms/ui_coincontroldialog.h> #include <qt/addresstablemodel.h> +#include <base58.h> #include <qt/bitcoinunits.h> #include <qt/guiutil.h> #include <qt/optionsmodel.h> diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index bcafc8f859..3f118e37f1 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/paymentserver.h> #include <qt/bitcoinunits.h> @@ -45,6 +49,7 @@ const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds const QString BITCOIN_IPC_PREFIX("bitcoin:"); +#ifdef ENABLE_BIP70 // BIP70 payment protocol messages const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK"; const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; @@ -52,21 +57,7 @@ const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; const char* BIP71_MIMETYPE_PAYMENT = "application/bitcoin-payment"; const char* BIP71_MIMETYPE_PAYMENTACK = "application/bitcoin-paymentack"; const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/bitcoin-paymentrequest"; - -struct X509StoreDeleter { - void operator()(X509_STORE* b) { - X509_STORE_free(b); - } -}; - -struct X509Deleter { - void operator()(X509* b) { X509_free(b); } -}; - -namespace // Anon namespace -{ - std::unique_ptr<X509_STORE, X509StoreDeleter> certStore; -} +#endif // // Create a name that is unique for: @@ -93,94 +84,6 @@ static QString ipcServerName() static QList<QString> savedPaymentRequests; -static void ReportInvalidCertificate(const QSslCertificate& cert) -{ - qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName); -} - -// -// Load OpenSSL's list of root certificate authorities -// -void PaymentServer::LoadRootCAs(X509_STORE* _store) -{ - // Unit tests mostly use this, to pass in fake root CAs: - if (_store) - { - certStore.reset(_store); - return; - } - - // Normal execution, use either -rootcertificates or system certs: - certStore.reset(X509_STORE_new()); - - // Note: use "-system-" default here so that users can pass -rootcertificates="" - // and get 'I don't like X.509 certificates, don't trust anybody' behavior: - QString certFile = QString::fromStdString(gArgs.GetArg("-rootcertificates", "-system-")); - - // Empty store - if (certFile.isEmpty()) { - qDebug() << QString("PaymentServer::%1: Payment request authentication via X.509 certificates disabled.").arg(__func__); - return; - } - - QList<QSslCertificate> certList; - - if (certFile != "-system-") { - qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root certificate.").arg(__func__).arg(certFile); - - certList = QSslCertificate::fromPath(certFile); - // Use those certificates when fetching payment requests, too: - QSslSocket::setDefaultCaCertificates(certList); - } else - certList = QSslSocket::systemCaCertificates(); - - int nRootCerts = 0; - const QDateTime currentTime = QDateTime::currentDateTime(); - - for (const QSslCertificate& cert : certList) { - // Don't log NULL certificates - if (cert.isNull()) - continue; - - // Not yet active/valid, or expired certificate - if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) { - ReportInvalidCertificate(cert); - continue; - } - - // Blacklisted certificate - if (cert.isBlacklisted()) { - ReportInvalidCertificate(cert); - continue; - } - QByteArray certData = cert.toDer(); - const unsigned char *data = (const unsigned char *)certData.data(); - - std::unique_ptr<X509, X509Deleter> x509(d2i_X509(0, &data, certData.size())); - if (x509 && X509_STORE_add_cert(certStore.get(), x509.get())) - { - // Note: X509_STORE increases the reference count to the X509 object, - // we still have to release our reference to it. - ++nRootCerts; - } - else - { - ReportInvalidCertificate(cert); - continue; - } - } - qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts << " root certificates"; - - // Project for another day: - // Fetch certificate revocation lists, and add them to certStore. - // Issues to consider: - // performance (start a thread to fetch in background?) - // privacy (fetch through tor/proxy so IP address isn't revealed) - // would it be easier to just use a compiled-in blacklist? - // or use Qt's blacklist? - // "certificate stapling" with server-side caching is more efficient -} - // // Sending to the server is done synchronously, at startup. // If the server isn't already running, startup continues, @@ -221,6 +124,7 @@ void PaymentServer::ipcParseCommandLine(interfaces::Node& node, int argc, char* } } } +#ifdef ENABLE_BIP70 else if (QFile::exists(arg)) // Filename { savedPaymentRequests.append(arg); @@ -244,6 +148,7 @@ void PaymentServer::ipcParseCommandLine(interfaces::Node& node, int argc, char* // GUI hasn't started yet so we can't pop up a message box. qWarning() << "PaymentServer::ipcSendCommandLine: Payment request file does not exist: " << arg; } +#endif } } @@ -290,12 +195,16 @@ PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : QObject(parent), saveURIs(true), uriServer(0), - netManager(0), optionsModel(0) +#ifdef ENABLE_BIP70 + ,netManager(0) +#endif { +#ifdef ENABLE_BIP70 // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; +#endif // Install global event filter to catch QFileOpenEvents // on Mac: sent when you click bitcoin: links @@ -319,14 +228,18 @@ PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : } else { connect(uriServer, &QLocalServer::newConnection, this, &PaymentServer::handleURIConnection); +#ifdef ENABLE_BIP70 connect(this, &PaymentServer::receivedPaymentACK, this, &PaymentServer::handlePaymentACK); +#endif } } } PaymentServer::~PaymentServer() { +#ifdef ENABLE_BIP70 google::protobuf::ShutdownProtobufLibrary(); +#endif } // @@ -349,33 +262,11 @@ bool PaymentServer::eventFilter(QObject *object, QEvent *event) return QObject::eventFilter(object, event); } -void PaymentServer::initNetManager() -{ - if (!optionsModel) - return; - delete netManager; - - // netManager is used to fetch paymentrequests given in bitcoin: URIs - netManager = new QNetworkAccessManager(this); - - QNetworkProxy proxy; - - // Query active SOCKS5 proxy - if (optionsModel->getProxySettings(proxy)) { - netManager->setProxy(proxy); - - qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port(); - } - else - qDebug() << "PaymentServer::initNetManager: No active proxy server found."; - - connect(netManager, &QNetworkAccessManager::finished, this, &PaymentServer::netRequestFinished); - connect(netManager, &QNetworkAccessManager::sslErrors, this, &PaymentServer::reportSslErrors); -} - void PaymentServer::uiReady() { +#ifdef ENABLE_BIP70 initNetManager(); +#endif saveURIs = false; for (const QString& s : savedPaymentRequests) @@ -403,6 +294,10 @@ void PaymentServer::handleURIOrFile(const QString& s) QUrlQuery uri((QUrl(s))); if (uri.hasQueryItem("r")) // payment request URI { +#ifdef ENABLE_BIP70 + Q_EMIT message(tr("URI handling"), + tr("You are using a BIP70 URL which will be unsupported in the future."), + CClientUIInterface::ICON_WARNING); QByteArray temp; temp.append(uri.queryItemValue("r")); QString decoded = QUrl::fromPercentEncoding(temp); @@ -420,7 +315,11 @@ void PaymentServer::handleURIOrFile(const QString& s) tr("Payment request fetch URL is invalid: %1").arg(fetchUrl.toString()), CClientUIInterface::ICON_WARNING); } - +#else + Q_EMIT message(tr("URI handling"), + tr("Cannot process payment request because BIP70 support was not compiled in."), + CClientUIInterface::ICON_WARNING); +#endif return; } else // normal URI @@ -444,6 +343,7 @@ void PaymentServer::handleURIOrFile(const QString& s) } } +#ifdef ENABLE_BIP70 if (QFile::exists(s)) // payment request file { PaymentRequestPlus request; @@ -459,6 +359,7 @@ void PaymentServer::handleURIOrFile(const QString& s) return; } +#endif } void PaymentServer::handleURIConnection() @@ -481,6 +382,140 @@ void PaymentServer::handleURIConnection() handleURIOrFile(msg); } +void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) +{ + this->optionsModel = _optionsModel; +} + +#ifdef ENABLE_BIP70 +struct X509StoreDeleter { + void operator()(X509_STORE* b) { + X509_STORE_free(b); + } +}; + +struct X509Deleter { + void operator()(X509* b) { X509_free(b); } +}; + +namespace // Anon namespace +{ + std::unique_ptr<X509_STORE, X509StoreDeleter> certStore; +} + +static void ReportInvalidCertificate(const QSslCertificate& cert) +{ + qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName); +} + +// +// Load OpenSSL's list of root certificate authorities +// +void PaymentServer::LoadRootCAs(X509_STORE* _store) +{ + // Unit tests mostly use this, to pass in fake root CAs: + if (_store) + { + certStore.reset(_store); + return; + } + + // Normal execution, use either -rootcertificates or system certs: + certStore.reset(X509_STORE_new()); + + // Note: use "-system-" default here so that users can pass -rootcertificates="" + // and get 'I don't like X.509 certificates, don't trust anybody' behavior: + QString certFile = QString::fromStdString(gArgs.GetArg("-rootcertificates", "-system-")); + + // Empty store + if (certFile.isEmpty()) { + qDebug() << QString("PaymentServer::%1: Payment request authentication via X.509 certificates disabled.").arg(__func__); + return; + } + + QList<QSslCertificate> certList; + + if (certFile != "-system-") { + qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root certificate.").arg(__func__).arg(certFile); + + certList = QSslCertificate::fromPath(certFile); + // Use those certificates when fetching payment requests, too: + QSslSocket::setDefaultCaCertificates(certList); + } else + certList = QSslSocket::systemCaCertificates(); + + int nRootCerts = 0; + const QDateTime currentTime = QDateTime::currentDateTime(); + + for (const QSslCertificate& cert : certList) { + // Don't log NULL certificates + if (cert.isNull()) + continue; + + // Not yet active/valid, or expired certificate + if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) { + ReportInvalidCertificate(cert); + continue; + } + + // Blacklisted certificate + if (cert.isBlacklisted()) { + ReportInvalidCertificate(cert); + continue; + } + + QByteArray certData = cert.toDer(); + const unsigned char *data = (const unsigned char *)certData.data(); + + std::unique_ptr<X509, X509Deleter> x509(d2i_X509(0, &data, certData.size())); + if (x509 && X509_STORE_add_cert(certStore.get(), x509.get())) + { + // Note: X509_STORE increases the reference count to the X509 object, + // we still have to release our reference to it. + ++nRootCerts; + } + else + { + ReportInvalidCertificate(cert); + continue; + } + } + qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts << " root certificates"; + + // Project for another day: + // Fetch certificate revocation lists, and add them to certStore. + // Issues to consider: + // performance (start a thread to fetch in background?) + // privacy (fetch through tor/proxy so IP address isn't revealed) + // would it be easier to just use a compiled-in blacklist? + // or use Qt's blacklist? + // "certificate stapling" with server-side caching is more efficient +} + +void PaymentServer::initNetManager() +{ + if (!optionsModel) + return; + delete netManager; + + // netManager is used to fetch paymentrequests given in bitcoin: URIs + netManager = new QNetworkAccessManager(this); + + QNetworkProxy proxy; + + // Query active SOCKS5 proxy + if (optionsModel->getProxySettings(proxy)) { + netManager->setProxy(proxy); + + qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port(); + } + else + qDebug() << "PaymentServer::initNetManager: No active proxy server found."; + + connect(netManager, &QNetworkAccessManager::finished, this, &PaymentServer::netRequestFinished); + connect(netManager, &QNetworkAccessManager::sslErrors, this, &PaymentServer::reportSslErrors); +} + // // Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine() // so don't use "Q_EMIT message()", but "QMessageBox::"! @@ -731,11 +766,6 @@ void PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> Q_EMIT message(tr("Network request error"), errString, CClientUIInterface::MSG_ERROR); } -void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) -{ - this->optionsModel = _optionsModel; -} - void PaymentServer::handlePaymentACK(const QString& paymentACKMsg) { // currently we don't further process or store the paymentACK message @@ -794,3 +824,4 @@ X509_STORE* PaymentServer::getCertStore() { return certStore.get(); } +#endif diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h index d335db9c85..30b5bc3b6d 100644 --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -32,7 +32,13 @@ // sends them to the server. // +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#ifdef ENABLE_BIP70 #include <qt/paymentrequestplus.h> +#endif #include <qt/walletmodel.h> #include <QObject> @@ -73,6 +79,10 @@ public: explicit PaymentServer(QObject* parent, bool startLocalServer = true); ~PaymentServer(); + // OptionsModel is used for getting proxy settings and display unit + void setOptionsModel(OptionsModel *optionsModel); + +#ifdef ENABLE_BIP70 // Load root certificate authorities. Pass nullptr (default) // to read from the file specified in the -rootcertificates setting, // or, if that's not set, to use the system default root certificates. @@ -83,9 +93,6 @@ public: // Return certificate store static X509_STORE* getCertStore(); - // OptionsModel is used for getting proxy settings and display unit - void setOptionsModel(OptionsModel *optionsModel); - // Verify that the payment request network matches the client network static bool verifyNetwork(interfaces::Node& node, const payments::PaymentDetails& requestDetails); // Verify if the payment request is expired @@ -94,33 +101,40 @@ public: static bool verifySize(qint64 requestSize); // Verify the payment request amount is valid static bool verifyAmount(const CAmount& requestAmount); +#endif Q_SIGNALS: // Fired when a valid payment request is received void receivedPaymentRequest(SendCoinsRecipient); - // Fired when a valid PaymentACK is received - void receivedPaymentACK(const QString &paymentACKMsg); - // Fired when a message should be reported to the user void message(const QString &title, const QString &message, unsigned int style); +#ifdef ENABLE_BIP70 + // Fired when a valid PaymentACK is received + void receivedPaymentACK(const QString &paymentACKMsg); +#endif + public Q_SLOTS: // Signal this when the main window's UI is ready // to display payment requests to the user void uiReady(); - // Submit Payment message to a merchant, get back PaymentACK: - void fetchPaymentACK(WalletModel* walletModel, const SendCoinsRecipient& recipient, QByteArray transaction); - // Handle an incoming URI, URI with local file scheme or file void handleURIOrFile(const QString& s); +#ifdef ENABLE_BIP70 + // Submit Payment message to a merchant, get back PaymentACK: + void fetchPaymentACK(WalletModel* walletModel, const SendCoinsRecipient& recipient, QByteArray transaction); +#endif + private Q_SLOTS: void handleURIConnection(); +#ifdef ENABLE_BIP70 void netRequestFinished(QNetworkReply*); void reportSslErrors(QNetworkReply*, const QList<QSslError> &); void handlePaymentACK(const QString& paymentACKMsg); +#endif protected: // Constructor registers this on the parent QApplication to @@ -128,19 +142,19 @@ protected: bool eventFilter(QObject *object, QEvent *event); private: + bool saveURIs; // true during startup + QLocalServer* uriServer; + OptionsModel *optionsModel; + +#ifdef ENABLE_BIP70 static bool readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request); bool processPaymentRequest(const PaymentRequestPlus& request, SendCoinsRecipient& recipient); void fetchRequest(const QUrl& url); // Setup networking void initNetManager(); - - bool saveURIs; // true during startup - QLocalServer* uriServer; - QNetworkAccessManager* netManager; // Used to fetch payment requests - - OptionsModel *optionsModel; +#endif }; #endif // BITCOIN_QT_PAYMENTSERVER_H diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 6f66bc19e1..858128f9f9 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/sendcoinsdialog.h> #include <qt/forms/ui_sendcoinsdialog.h> @@ -290,7 +294,9 @@ void SendCoinsDialog::on_sendButton_clicked() QString recipientElement; recipientElement = "<br />"; +#ifdef ENABLE_BIP70 if (!rcp.paymentRequest.IsInitialized()) // normal payment +#endif { if(rcp.label.length() > 0) // label with address { @@ -302,6 +308,7 @@ void SendCoinsDialog::on_sendButton_clicked() recipientElement.append(tr("%1 to %2").arg(amount, address)); } } +#ifdef ENABLE_BIP70 else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request { recipientElement.append(tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant))); @@ -310,6 +317,7 @@ void SendCoinsDialog::on_sendButton_clicked() { recipientElement.append(tr("%1 to %2").arg(amount, address)); } +#endif formatted.append(recipientElement); } diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index b394ff3150..76c942c8b9 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/sendcoinsentry.h> #include <qt/forms/ui_sendcoinsentry.h> @@ -133,9 +137,11 @@ bool SendCoinsEntry::validate(interfaces::Node& node) // Check input validity bool retval = true; +#ifdef ENABLE_BIP70 // Skip checks for payment request if (recipient.paymentRequest.IsInitialized()) return retval; +#endif if (!model->validateAddress(ui->payTo->text())) { @@ -166,9 +172,11 @@ bool SendCoinsEntry::validate(interfaces::Node& node) SendCoinsRecipient SendCoinsEntry::getValue() { +#ifdef ENABLE_BIP70 // Payment request if (recipient.paymentRequest.IsInitialized()) return recipient; +#endif // Normal payment recipient.address = ui->payTo->text(); @@ -196,6 +204,7 @@ void SendCoinsEntry::setValue(const SendCoinsRecipient &value) { recipient = value; +#ifdef ENABLE_BIP70 if (recipient.paymentRequest.IsInitialized()) // payment request { if (recipient.authenticatedMerchant.isEmpty()) // unauthenticated @@ -216,6 +225,7 @@ void SendCoinsEntry::setValue(const SendCoinsRecipient &value) } } else // normal payment +#endif { // message ui->messageTextLabel->setText(recipient.message); diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index c3d33c76d4..e6777c068d 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -6,17 +6,17 @@ #include <qt/addressbookpage.h> #include <qt/addresstablemodel.h> #include <qt/editaddressdialog.h> -#include <qt/callback.h> #include <qt/optionsmodel.h> #include <qt/platformstyle.h> #include <qt/qvalidatedlineedit.h> #include <qt/walletmodel.h> #include <key.h> -#include <pubkey.h> #include <key_io.h> +#include <pubkey.h> #include <wallet/wallet.h> +#include <QApplication> #include <QTimer> #include <QMessageBox> @@ -139,5 +139,16 @@ void TestAddAddressesToSendBook() void AddressBookTests::addressBookTests() { +#ifdef Q_OS_MAC + if (QApplication::platformName() == "minimal") { + // Disable for mac on "minimal" platform to avoid crashes inside the Qt + // framework when it tries to look up unimplemented cocoa functions, + // and fails to handle returned nulls + // (https://bugreports.qt.io/browse/QTBUG-49686). + QWARN("Skipping AddressBookTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke " + "with 'test_bitcoin-qt -platform cocoa' on mac, or else use a linux or windows build."); + return; + } +#endif TestAddAddressesToSendBook(); } diff --git a/src/qt/test/compattests.cpp b/src/qt/test/compattests.cpp index af5c69ea9a..6750c543da 100644 --- a/src/qt/test/compattests.cpp +++ b/src/qt/test/compattests.cpp @@ -2,7 +2,13 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#if defined(ENABLE_WALLET) && defined(ENABLE_BIP70) #include <qt/paymentrequestplus.h> // this includes protobuf's port.h which defines its own bswap macos +#endif #include <qt/test/compattests.h> diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index df65a85fb5..28df4ebf26 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -14,9 +14,11 @@ #ifdef ENABLE_WALLET #include <qt/test/addressbooktests.h> +#ifdef ENABLE_BIP70 #include <qt/test/paymentservertests.h> +#endif // ENABLE_BIP70 #include <qt/test/wallettests.h> -#endif +#endif // ENABLE_WALLET #include <QApplication> #include <QObject> @@ -74,7 +76,7 @@ int main(int argc, char *argv[]) if (QTest::qExec(&test1) != 0) { fInvalid = true; } -#ifdef ENABLE_WALLET +#if defined(ENABLE_WALLET) && defined(ENABLE_BIP70) PaymentServerTests test2; if (QTest::qExec(&test2) != 0) { fInvalid = true; diff --git a/src/qt/test/util.cpp b/src/qt/test/util.cpp index 0bd719326e..ae2fb93bf7 100644 --- a/src/qt/test/util.cpp +++ b/src/qt/test/util.cpp @@ -1,15 +1,13 @@ -#include <qt/callback.h> - #include <QApplication> #include <QMessageBox> -#include <QTimer> -#include <QString> #include <QPushButton> +#include <QString> +#include <QTimer> #include <QWidget> void ConfirmMessage(QString* text, int msec) { - QTimer::singleShot(msec, makeCallback([text](Callback* callback) { + QTimer::singleShot(msec, [text]() { for (QWidget* widget : QApplication::topLevelWidgets()) { if (widget->inherits("QMessageBox")) { QMessageBox* messageBox = qobject_cast<QMessageBox*>(widget); @@ -17,6 +15,5 @@ void ConfirmMessage(QString* text, int msec) messageBox->defaultButton()->click(); } } - delete callback; - }), &Callback::call); + }); } diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index a0cfe8ae87..fcc5806b81 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -2,8 +2,8 @@ #include <qt/test/util.h> #include <interfaces/node.h> +#include <base58.h> #include <qt/bitcoinamountfield.h> -#include <qt/callback.h> #include <qt/optionsmodel.h> #include <qt/platformstyle.h> #include <qt/qvalidatedlineedit.h> @@ -39,7 +39,7 @@ namespace //! Press "Yes" or "Cancel" buttons in modal send confirmation dialog. void ConfirmSend(QString* text = nullptr, bool cancel = false) { - QTimer::singleShot(0, makeCallback([text, cancel](Callback* callback) { + QTimer::singleShot(0, [text, cancel]() { for (QWidget* widget : QApplication::topLevelWidgets()) { if (widget->inherits("SendConfirmationDialog")) { SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget); @@ -49,8 +49,7 @@ void ConfirmSend(QString* text = nullptr, bool cancel = false) button->click(); } } - delete callback; - }), &Callback::call); + }); } //! Send coins to address and return txid. @@ -243,5 +242,16 @@ void TestGUI() void WalletTests::walletTests() { +#ifdef Q_OS_MAC + if (QApplication::platformName() == "minimal") { + // Disable for mac on "minimal" platform to avoid crashes inside the Qt + // framework when it tries to look up unimplemented cocoa functions, + // and fails to handle returned nulls + // (https://bugreports.qt.io/browse/QTBUG-49686). + QWARN("Skipping WalletTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke " + "with 'test_bitcoin-qt -platform cocoa' on mac, or else use a linux or windows build."); + return; + } +#endif TestGUI(); } diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 59332be754..3c5617bfa8 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifdef HAVE_CONFIG_H +#include <config/bitcoin-config.h> +#endif + #include <qt/transactiondesc.h> #include <qt/bitcoinunits.h> @@ -257,6 +261,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall if (r.first == "Message") strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(r.second, true) + "<br>"; +#ifdef ENABLE_BIP70 // // PaymentRequest info: // @@ -271,6 +276,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>"; } } +#endif if (wtx.is_coinbase) { diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index 7d903b3b1c..faeed87ec4 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -14,7 +14,9 @@ #include <qt/clientmodel.h> #include <qt/guiconstants.h> #include <qt/intro.h> +#ifdef ENABLE_BIP70 #include <qt/paymentrequestplus.h> +#endif #include <qt/guiutil.h> #include <clientversion.h> diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f7cc94ae32..71b2d321e2 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/walletmodel.h> #include <qt/addresstablemodel.h> @@ -142,6 +146,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact if (rcp.fSubtractFeeFromAmount) fSubtractFeeFromAmount = true; +#ifdef ENABLE_BIP70 if (rcp.paymentRequest.IsInitialized()) { // PaymentRequest... CAmount subtotal = 0; @@ -164,6 +169,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact total += subtotal; } else +#endif { // User-entered bitcoin address / amount: if(!validateAddress(rcp.address)) { @@ -235,6 +241,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran std::vector<std::pair<std::string, std::string>> vOrderForm; for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { +#ifdef ENABLE_BIP70 if (rcp.paymentRequest.IsInitialized()) { // Make sure any payment requests involved are still valid. @@ -247,7 +254,9 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran rcp.paymentRequest.SerializeToString(&value); vOrderForm.emplace_back("PaymentRequest", std::move(value)); } - else if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example) + else +#endif + if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example) vOrderForm.emplace_back("Message", rcp.message.toStdString()); } @@ -266,7 +275,9 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { // Don't touch the address book when we have a payment request +#ifdef ENABLE_BIP70 if (!rcp.paymentRequest.IsInitialized()) +#endif { std::string strAddress = rcp.address.toStdString(); CTxDestination dest = DecodeDestination(strAddress); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index b22728c69b..ec4c5a2a6c 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -10,7 +10,13 @@ #include <serialize.h> #include <script/standard.h> +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#ifdef ENABLE_BIP70 #include <qt/paymentrequestplus.h> +#endif #include <qt/walletmodeltransaction.h> #include <interfaces/wallet.h> @@ -63,8 +69,14 @@ public: // If from a payment request, this is used for storing the memo QString message; +#ifdef ENABLE_BIP70 // If from a payment request, paymentRequest.IsInitialized() will be true PaymentRequestPlus paymentRequest; +#else + // If building with BIP70 is disabled, keep the payment request around as + // serialized string to ensure load/store is lossless + std::string sPaymentRequest; +#endif // Empty if no authentication or invalid signature/cert/etc. QString authenticatedMerchant; @@ -80,9 +92,11 @@ public: std::string sAddress = address.toStdString(); std::string sLabel = label.toStdString(); std::string sMessage = message.toStdString(); +#ifdef ENABLE_BIP70 std::string sPaymentRequest; if (!ser_action.ForRead() && paymentRequest.IsInitialized()) paymentRequest.SerializeToString(&sPaymentRequest); +#endif std::string sAuthenticatedMerchant = authenticatedMerchant.toStdString(); READWRITE(this->nVersion); @@ -98,8 +112,10 @@ public: address = QString::fromStdString(sAddress); label = QString::fromStdString(sLabel); message = QString::fromStdString(sMessage); +#ifdef ENABLE_BIP70 if (!sPaymentRequest.empty()) paymentRequest.parse(QByteArray::fromRawData(sPaymentRequest.data(), sPaymentRequest.size())); +#endif authenticatedMerchant = QString::fromStdString(sAuthenticatedMerchant); } } diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index de50616499..eb3b0baf08 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifdef HAVE_CONFIG_H +#include <config/bitcoin-config.h> +#endif + #include <qt/walletmodeltransaction.h> #include <interfaces/node.h> @@ -46,6 +50,7 @@ void WalletModelTransaction::reassignAmounts(int nChangePosRet) { SendCoinsRecipient& rcp = (*it); +#ifdef ENABLE_BIP70 if (rcp.paymentRequest.IsInitialized()) { CAmount subtotal = 0; @@ -62,6 +67,7 @@ void WalletModelTransaction::reassignAmounts(int nChangePosRet) rcp.amount = subtotal; } else // normal recipient (no payment request) +#endif { if (i == nChangePosRet) i++; diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index 75ede2e2a1..289aee847b 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -8,6 +8,7 @@ #include <qt/walletmodel.h> #include <memory> +#include <amount.h> #include <QObject> diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 649e222c39..a550f693e6 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -45,7 +45,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "listreceivedbyaddress", 0, "minconf" }, { "listreceivedbyaddress", 1, "include_empty" }, { "listreceivedbyaddress", 2, "include_watchonly" }, - { "listreceivedbyaddress", 3, "address_filter" }, { "listreceivedbylabel", 0, "minconf" }, { "listreceivedbylabel", 1, "include_empty" }, { "listreceivedbylabel", 2, "include_watchonly" }, @@ -148,7 +147,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "logging", 0, "include" }, { "logging", 1, "exclude" }, { "disconnectnode", 1, "nodeid" }, - { "addwitnessaddress", 1, "p2sh" }, // Echo with conversion (For testing only) { "echojson", 0, "arg0" }, { "echojson", 1, "arg1" }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index c565b9b4f9..25cfc69da9 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -167,6 +167,8 @@ static UniValue generatetoaddress(const JSONRPCRequest& request) "\nExamples:\n" "\nGenerate 11 blocks to myaddress\n" + HelpExampleCli("generatetoaddress", "11 \"myaddress\"") + + "If you are running the bitcoin core wallet, you can get a new address to send the newly generated bitcoin to with:\n" + + HelpExampleCli("getnewaddress", "") ); int nGenerate = request.params[0].get_int(); diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 45b097dde6..478797e958 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -41,7 +41,7 @@ struct PubkeyProvider virtual ~PubkeyProvider() = default; /** Derive a public key. */ - virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const = 0; + virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info) const = 0; /** Whether this represent multiple public keys at different positions. */ virtual bool IsRange() const = 0; @@ -56,6 +56,37 @@ struct PubkeyProvider virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0; }; +class OriginPubkeyProvider final : public PubkeyProvider +{ + KeyOriginInfo m_origin; + std::unique_ptr<PubkeyProvider> m_provider; + + std::string OriginString() const + { + return HexStr(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint)) + FormatKeyPath(m_origin.path); + } + +public: + OriginPubkeyProvider(KeyOriginInfo info, std::unique_ptr<PubkeyProvider> provider) : m_origin(std::move(info)), m_provider(std::move(provider)) {} + bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info) const override + { + if (!m_provider->GetPubKey(pos, arg, key, info)) return false; + std::copy(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint), info.fingerprint); + info.path.insert(info.path.begin(), m_origin.path.begin(), m_origin.path.end()); + return true; + } + bool IsRange() const override { return m_provider->IsRange(); } + size_t GetSize() const override { return m_provider->GetSize(); } + std::string ToString() const override { return "[" + OriginString() + "]" + m_provider->ToString(); } + bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override + { + std::string sub; + if (!m_provider->ToPrivateString(arg, sub)) return false; + ret = "[" + OriginString() + "]" + std::move(sub); + return true; + } +}; + /** An object representing a parsed constant public key in a descriptor. */ class ConstPubkeyProvider final : public PubkeyProvider { @@ -63,9 +94,12 @@ class ConstPubkeyProvider final : public PubkeyProvider public: ConstPubkeyProvider(const CPubKey& pubkey) : m_pubkey(pubkey) {} - bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const override + bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info) const override { - out = m_pubkey; + key = m_pubkey; + info.path.clear(); + CKeyID keyid = m_pubkey.GetID(); + std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint); return true; } bool IsRange() const override { return false; } @@ -98,7 +132,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider CKey key; if (!arg.GetKey(m_extkey.pubkey.GetID(), key)) return false; ret.nDepth = m_extkey.nDepth; - std::copy(m_extkey.vchFingerprint, m_extkey.vchFingerprint + 4, ret.vchFingerprint); + std::copy(m_extkey.vchFingerprint, m_extkey.vchFingerprint + sizeof(ret.vchFingerprint), ret.vchFingerprint); ret.nChild = m_extkey.nChild; ret.chaincode = m_extkey.chaincode; ret.key = key; @@ -118,27 +152,32 @@ public: BIP32PubkeyProvider(const CExtPubKey& extkey, KeyPath path, DeriveType derive) : m_extkey(extkey), m_path(std::move(path)), m_derive(derive) {} bool IsRange() const override { return m_derive != DeriveType::NO; } size_t GetSize() const override { return 33; } - bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const override + bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info) const override { if (IsHardened()) { - CExtKey key; - if (!GetExtKey(arg, key)) return false; + CExtKey extkey; + if (!GetExtKey(arg, extkey)) return false; for (auto entry : m_path) { - key.Derive(key, entry); + extkey.Derive(extkey, entry); } - if (m_derive == DeriveType::UNHARDENED) key.Derive(key, pos); - if (m_derive == DeriveType::HARDENED) key.Derive(key, pos | 0x80000000UL); - out = key.Neuter().pubkey; + if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos); + if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL); + key = extkey.Neuter().pubkey; } else { // TODO: optimize by caching - CExtPubKey key = m_extkey; + CExtPubKey extkey = m_extkey; for (auto entry : m_path) { - key.Derive(key, entry); + extkey.Derive(extkey, entry); } - if (m_derive == DeriveType::UNHARDENED) key.Derive(key, pos); + if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos); assert(m_derive != DeriveType::HARDENED); - out = key.pubkey; + key = extkey.pubkey; } + CKeyID keyid = m_extkey.pubkey.GetID(); + std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint); + info.path = m_path; + if (m_derive == DeriveType::UNHARDENED) info.path.push_back((uint32_t)pos); + if (m_derive == DeriveType::HARDENED) info.path.push_back(((uint32_t)pos) | 0x80000000L); return true; } std::string ToString() const override @@ -221,9 +260,11 @@ public: bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override { CPubKey key; - if (!m_provider->GetPubKey(pos, arg, key)) return false; + KeyOriginInfo info; + if (!m_provider->GetPubKey(pos, arg, key, info)) return false; output_scripts = std::vector<CScript>{m_script_fn(key)}; - out.pubkeys.emplace(key.GetID(), std::move(key)); + out.origins.emplace(key.GetID(), std::move(info)); + out.pubkeys.emplace(key.GetID(), key); return true; } }; @@ -272,15 +313,19 @@ public: bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override { - std::vector<CPubKey> pubkeys; - pubkeys.reserve(m_providers.size()); + std::vector<std::pair<CPubKey, KeyOriginInfo>> entries; + entries.reserve(m_providers.size()); + // Construct temporary data in `entries`, to avoid producing output in case of failure. for (const auto& p : m_providers) { - CPubKey key; - if (!p->GetPubKey(pos, arg, key)) return false; - pubkeys.push_back(key); + entries.emplace_back(); + if (!p->GetPubKey(pos, arg, entries.back().first, entries.back().second)) return false; } - for (const CPubKey& key : pubkeys) { - out.pubkeys.emplace(key.GetID(), std::move(key)); + std::vector<CPubKey> pubkeys; + pubkeys.reserve(entries.size()); + for (auto& entry : entries) { + pubkeys.push_back(entry.first); + out.origins.emplace(entry.first.GetID(), std::move(entry.second)); + out.pubkeys.emplace(entry.first.GetID(), entry.first); } output_scripts = std::vector<CScript>{GetScriptForMultisig(m_threshold, pubkeys)}; return true; @@ -343,13 +388,15 @@ public: bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override { CPubKey key; - if (!m_provider->GetPubKey(pos, arg, key)) return false; + KeyOriginInfo info; + if (!m_provider->GetPubKey(pos, arg, key, info)) return false; CKeyID keyid = key.GetID(); { CScript p2pk = GetScriptForRawPubKey(key); CScript p2pkh = GetScriptForDestination(keyid); output_scripts = std::vector<CScript>{std::move(p2pk), std::move(p2pkh)}; out.pubkeys.emplace(keyid, key); + out.origins.emplace(keyid, std::move(info)); } if (key.IsCompressed()) { CScript p2wpkh = GetScriptForDestination(WitnessV0KeyHash(keyid)); @@ -447,7 +494,8 @@ bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out) return true; } -std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out) +/** Parse a public key that excludes origin information. */ +std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out) { auto split = Split(sp, '/'); std::string str(split[0].begin(), split[0].end()); @@ -484,6 +532,28 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool per return MakeUnique<BIP32PubkeyProvider>(extpubkey, std::move(path), type); } +/** Parse a public key including origin information (if enabled). */ +std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out) +{ + auto origin_split = Split(sp, ']'); + if (origin_split.size() > 2) return nullptr; + if (origin_split.size() == 1) return ParsePubkeyInner(origin_split[0], permit_uncompressed, out); + if (origin_split[0].size() < 1 || origin_split[0][0] != '[') return nullptr; + auto slash_split = Split(origin_split[0].subspan(1), '/'); + if (slash_split[0].size() != 8) return nullptr; + std::string fpr_hex = std::string(slash_split[0].begin(), slash_split[0].end()); + if (!IsHex(fpr_hex)) return nullptr; + auto fpr_bytes = ParseHex(fpr_hex); + KeyOriginInfo info; + static_assert(sizeof(info.fingerprint) == 4, "Fingerprint must be 4 bytes"); + assert(fpr_bytes.size() == 4); + std::copy(fpr_bytes.begin(), fpr_bytes.end(), info.fingerprint); + if (!ParseKeyPath(slash_split, info.path)) return nullptr; + auto provider = ParsePubkeyInner(origin_split[1], permit_uncompressed, out); + if (!provider) return nullptr; + return MakeUnique<OriginPubkeyProvider>(std::move(info), std::move(provider)); +} + /** Parse a script in a particular context. */ std::unique_ptr<Descriptor> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out) { diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 0042f35e2e..c721b20ced 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -686,6 +686,7 @@ bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& inf bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } +bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return LookupHelper(origins, keyid, info); } bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) diff --git a/src/script/sign.h b/src/script/sign.h index 2fc4575e59..689501269d 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -34,7 +34,7 @@ public: virtual bool GetCScript(const CScriptID &scriptid, CScript& script) const { return false; } virtual bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const { return false; } virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; } - virtual bool GetKeyOrigin(const CKeyID& id, KeyOriginInfo& info) const { return false; } + virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } }; extern const SigningProvider& DUMMY_SIGNING_PROVIDER; @@ -58,10 +58,12 @@ struct FlatSigningProvider final : public SigningProvider { std::map<CScriptID, CScript> scripts; std::map<CKeyID, CPubKey> pubkeys; + std::map<CKeyID, KeyOriginInfo> origins; std::map<CKeyID, CKey> keys; bool GetCScript(const CScriptID& scriptid, CScript& script) const override; bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; + bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; bool GetKey(const CKeyID& keyid, CKey& key) const override; }; diff --git a/src/test/amount_tests.cpp b/src/test/amount_tests.cpp index adffcfeef5..1ff040b077 100644 --- a/src/test/amount_tests.cpp +++ b/src/test/amount_tests.cpp @@ -13,8 +13,10 @@ BOOST_FIXTURE_TEST_SUITE(amount_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(MoneyRangeTest) { BOOST_CHECK_EQUAL(MoneyRange(CAmount(-1)), false); - BOOST_CHECK_EQUAL(MoneyRange(MAX_MONEY + CAmount(1)), false); + BOOST_CHECK_EQUAL(MoneyRange(CAmount(0)), true); BOOST_CHECK_EQUAL(MoneyRange(CAmount(1)), true); + BOOST_CHECK_EQUAL(MoneyRange(MAX_MONEY), true); + BOOST_CHECK_EQUAL(MoneyRange(MAX_MONEY + CAmount(1)), false); } BOOST_AUTO_TEST_CASE(GetFeeTest) @@ -23,43 +25,43 @@ BOOST_AUTO_TEST_CASE(GetFeeTest) feeRate = CFeeRate(0); // Must always return 0 - BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0); - BOOST_CHECK_EQUAL(feeRate.GetFee(1e5), 0); + BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1e5), CAmount(0)); feeRate = CFeeRate(1000); // Must always just return the arg - BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0); - BOOST_CHECK_EQUAL(feeRate.GetFee(1), 1); - BOOST_CHECK_EQUAL(feeRate.GetFee(121), 121); - BOOST_CHECK_EQUAL(feeRate.GetFee(999), 999); - BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), 1e3); - BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), 9e3); + BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1), CAmount(1)); + BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(121)); + BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(999)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), CAmount(1e3)); + BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(9e3)); feeRate = CFeeRate(-1000); // Must always just return -1 * arg - BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0); - BOOST_CHECK_EQUAL(feeRate.GetFee(1), -1); - BOOST_CHECK_EQUAL(feeRate.GetFee(121), -121); - BOOST_CHECK_EQUAL(feeRate.GetFee(999), -999); - BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), -1e3); - BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), -9e3); + BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1), CAmount(-1)); + BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(-121)); + BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(-999)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), CAmount(-1e3)); + BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(-9e3)); feeRate = CFeeRate(123); // Truncates the result, if not integer - BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0); - BOOST_CHECK_EQUAL(feeRate.GetFee(8), 1); // Special case: returns 1 instead of 0 - BOOST_CHECK_EQUAL(feeRate.GetFee(9), 1); - BOOST_CHECK_EQUAL(feeRate.GetFee(121), 14); - BOOST_CHECK_EQUAL(feeRate.GetFee(122), 15); - BOOST_CHECK_EQUAL(feeRate.GetFee(999), 122); - BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), 123); - BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), 1107); + BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0)); + BOOST_CHECK_EQUAL(feeRate.GetFee(8), CAmount(1)); // Special case: returns 1 instead of 0 + BOOST_CHECK_EQUAL(feeRate.GetFee(9), CAmount(1)); + BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(14)); + BOOST_CHECK_EQUAL(feeRate.GetFee(122), CAmount(15)); + BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(122)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), CAmount(123)); + BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(1107)); feeRate = CFeeRate(-123); // Truncates the result, if not integer - BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0); - BOOST_CHECK_EQUAL(feeRate.GetFee(8), -1); // Special case: returns -1 instead of 0 - BOOST_CHECK_EQUAL(feeRate.GetFee(9), -1); + BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0)); + BOOST_CHECK_EQUAL(feeRate.GetFee(8), CAmount(-1)); // Special case: returns -1 instead of 0 + BOOST_CHECK_EQUAL(feeRate.GetFee(9), CAmount(-1)); // check alternate constructor feeRate = CFeeRate(1000); @@ -67,6 +69,9 @@ BOOST_AUTO_TEST_CASE(GetFeeTest) BOOST_CHECK_EQUAL(feeRate.GetFee(100), altFeeRate.GetFee(100)); // Check full constructor + BOOST_CHECK(CFeeRate(CAmount(-1), 0) == CFeeRate(0)); + BOOST_CHECK(CFeeRate(CAmount(0), 0) == CFeeRate(0)); + BOOST_CHECK(CFeeRate(CAmount(1), 0) == CFeeRate(0)); // default value BOOST_CHECK(CFeeRate(CAmount(-1), 1000) == CFeeRate(-1)); BOOST_CHECK(CFeeRate(CAmount(0), 1000) == CFeeRate(0)); diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index e739b84a48..f3083bab4a 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -43,9 +43,12 @@ std::string MaybeUseHInsteadOfApostrophy(std::string ret) return ret; } -void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts) +const std::set<std::vector<uint32_t>> ONLY_EMPTY{{}}; + +void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY) { FlatSigningProvider keys_priv, keys_pub; + std::set<std::vector<uint32_t>> left_paths = paths; // Check that parsing succeeds. auto parse_priv = Parse(MaybeUseHInsteadOfApostrophy(prv), keys_priv); @@ -84,7 +87,7 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std: for (size_t i = 0; i < max; ++i) { const auto& ref = scripts[(flags & RANGE) ? i : 0]; for (int t = 0; t < 2; ++t) { - FlatSigningProvider key_provider = (flags & HARDENED) ? keys_priv : keys_pub; + const FlatSigningProvider& key_provider = (flags & HARDENED) ? keys_priv : keys_pub; FlatSigningProvider script_provider; std::vector<CScript> spks; BOOST_CHECK((t ? parse_priv : parse_pub)->Expand(i, key_provider, spks, script_provider)); @@ -100,9 +103,16 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std: BOOST_CHECK_MESSAGE(SignSignature(Merge(keys_priv, script_provider), spks[n], spend, 0, 1, SIGHASH_ALL), prv); } } - + // Test whether the observed key path is present in the 'paths' variable (which contains expected, unobserved paths), + // and then remove it from that set. + for (const auto& origin : script_provider.origins) { + BOOST_CHECK_MESSAGE(paths.count(origin.second.path), "Unexpected key path: " + prv); + left_paths.erase(origin.second.path); + } } } + // Verify no expected paths remain that were not observed. + BOOST_CHECK_MESSAGE(left_paths.empty(), "Not all expected key paths found: " + prv); } } @@ -114,7 +124,7 @@ BOOST_AUTO_TEST_CASE(descriptor_test) // Basic single-key compressed Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}); Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}); - Check("pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}); + Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, {{1,0x80000002UL,3,0x80000004UL}}); Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}); Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}); @@ -135,20 +145,26 @@ BOOST_AUTO_TEST_CASE(descriptor_test) Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}); // Versions with BIP32 derivations - Check("combo(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}); - Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}); - Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}); - Check("wpkh(xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh(xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}); - Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}); - Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}); + Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}); + Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, {{0}}); + Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, {{0xFFFFFFFFUL,0}}); + Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, {{0x8000000DUL, 1, 2, 0}, {0x8000000DUL, 1, 2, 1}, {0x8000000DUL, 1, 2, 2}}); + Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); + Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, {{0}, {1}}); + CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)"); // Too long key fingerprint CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)"); // BIP 32 path element overflow // Multisig constructions Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}); - Check("sh(multi(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}); - Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}); + Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}}); + Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, {{0xFFFFFFFFUL,0}, {1,2,0}, {1,2,1}, {1,2,2}, {10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}); CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))"); // P2SH does not fit 16 compressed pubkeys in a redeemscript + CheckUnparsable("wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Double key origin descriptor + CheckUnparsable("wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Non hex fingerprint + CheckUnparsable("wsh(multi(2,[aaaaaaaa],xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa],xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // No public key with origin + CheckUnparsable("wsh(multi(2,[aaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Too short fingerprint + CheckUnparsable("wsh(multi(2,[aaaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Too long fingerprint // Check for invalid nesting of structures CheckUnparsable("sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)"); // P2SH needs a script, not a key diff --git a/src/validation.cpp b/src/validation.cpp index 458458d85d..29804c5e10 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -918,7 +918,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // There is a similar check in CreateNewBlock() to prevent creating // invalid blocks (using TestBlockValidity), however allowing such // transactions into the mempool can be exploited as a DoS attack. - unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(chainActive.Tip(), Params().GetConsensus()); + unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(chainActive.Tip(), chainparams.GetConsensus()); if (!CheckInputsFromMempoolAndCache(tx, state, view, pool, currentBlockScriptVerifyFlags, true, txdata)) { return error("%s: BUG! PLEASE REPORT THIS! CheckInputs failed against latest-block but not STANDARD flags %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); @@ -4766,6 +4766,9 @@ bool DumpMempool() std::map<uint256, CAmount> mapDeltas; std::vector<TxMempoolInfo> vinfo; + static Mutex dump_mutex; + LOCK(dump_mutex); + { LOCK(mempool.cs); for (const auto &i : mempool.mapDeltas) { diff --git a/src/validation.h b/src/validation.h index 1034ba4665..dbd827633b 100644 --- a/src/validation.h +++ b/src/validation.h @@ -257,7 +257,7 @@ bool LoadGenesisBlock(const CChainParams& chainparams); * initializing state if we're running with -reindex. */ bool LoadBlockIndex(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Update the chain tip based on database information. */ -bool LoadChainTip(const CChainParams& chainparams); +bool LoadChainTip(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Unload database information */ void UnloadBlockIndex(); /** Run an instance of the script checking thread */ @@ -436,7 +436,7 @@ inline CBlockIndex* LookupBlockIndex(const uint256& hash) } /** Find the last common block between the parameter chain and a locator. */ -CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator); +CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Mark a block as precious and reorganize. * diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index a7bf89c572..25880f3529 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -20,6 +20,7 @@ #include <boost/thread.hpp> namespace { + //! Make sure database has a unique fileid within the environment. If it //! doesn't, throw an error. BDB caches do not work properly when more than one //! open database has the same fileid (values written to one database may show @@ -29,25 +30,19 @@ namespace { //! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html), //! so bitcoin should never create different databases with the same fileid, but //! this error can be triggered if users manually copy database files. -void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db) +void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db, WalletDatabaseFileId& fileid) { if (env.IsMock()) return; - u_int8_t fileid[DB_FILE_ID_LEN]; - int ret = db.get_mpf()->get_fileid(fileid); + int ret = db.get_mpf()->get_fileid(fileid.value); if (ret != 0) { throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret)); } - for (const auto& item : env.mapDb) { - u_int8_t item_fileid[DB_FILE_ID_LEN]; - if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 && - memcmp(fileid, item_fileid, sizeof(fileid)) == 0) { - const char* item_filename = nullptr; - item.second->get_dbname(&item_filename, nullptr); + for (const auto& item : env.m_fileids) { + if (fileid == item.second && &fileid != &item.second) { throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename, - HexStr(std::begin(item_fileid), std::end(item_fileid)), - item_filename ? item_filename : "(unknown database)")); + HexStr(std::begin(item.second.value), std::end(item.second.value)), item.first)); } } } @@ -56,6 +51,11 @@ CCriticalSection cs_db; std::map<std::string, BerkeleyEnvironment> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to open db environment. } // namespace +bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const +{ + return memcmp(value, &rhs.value, sizeof(value)) == 0; +} + BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename) { fs::path env_directory; @@ -504,7 +504,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo // versions of BDB have an set_lk_exclusive method for this // purpose, but the older version we use does not.) for (const auto& env : g_dbenvs) { - CheckUniqueFileid(env.second, strFilename, *pdb_temp); + CheckUniqueFileid(env.second, strFilename, *pdb_temp, this->env->m_fileids[strFilename]); } pdb = pdb_temp.release(); @@ -826,6 +826,13 @@ void BerkeleyDatabase::Flush(bool shutdown) LOCK(cs_db); g_dbenvs.erase(env->Directory().string()); env = nullptr; + } else { + // TODO: To avoid g_dbenvs.erase erasing the environment prematurely after the + // first database shutdown when multiple databases are open in the same + // environment, should replace raw database `env` pointers with shared or weak + // pointers, or else separate the database and environment shutdowns so + // environments can be shut down after databases. + env->m_fileids.erase(strFile); } } } diff --git a/src/wallet/db.h b/src/wallet/db.h index 467ed13b45..68a59607ae 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -18,6 +18,7 @@ #include <map> #include <memory> #include <string> +#include <unordered_map> #include <vector> #include <db_cxx.h> @@ -25,6 +26,11 @@ static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100; static const bool DEFAULT_WALLET_PRIVDB = true; +struct WalletDatabaseFileId { + u_int8_t value[DB_FILE_ID_LEN]; + bool operator==(const WalletDatabaseFileId& rhs) const; +}; + class BerkeleyEnvironment { private: @@ -38,6 +44,7 @@ public: std::unique_ptr<DbEnv> dbenv; std::map<std::string, int> mapFileUseCount; std::map<std::string, Db*> mapDb; + std::unordered_map<std::string, WalletDatabaseFileId> m_fileids; std::condition_variable_any m_db_in_use; BerkeleyEnvironment(const fs::path& env_directory); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index df10fb48cc..dc39ba3001 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -982,131 +982,6 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) return result; } -class Witnessifier : public boost::static_visitor<bool> -{ -public: - CWallet * const pwallet; - CTxDestination result; - bool already_witness; - - explicit Witnessifier(CWallet *_pwallet) : pwallet(_pwallet), already_witness(false) {} - - bool operator()(const CKeyID &keyID) { - if (pwallet) { - CScript basescript = GetScriptForDestination(keyID); - CScript witscript = GetScriptForWitness(basescript); - if (!IsSolvable(*pwallet, witscript)) { - return false; - } - return ExtractDestination(witscript, result); - } - return false; - } - - bool operator()(const CScriptID &scriptID) { - CScript subscript; - if (pwallet && pwallet->GetCScript(scriptID, subscript)) { - int witnessversion; - std::vector<unsigned char> witprog; - if (subscript.IsWitnessProgram(witnessversion, witprog)) { - ExtractDestination(subscript, result); - already_witness = true; - return true; - } - CScript witscript = GetScriptForWitness(subscript); - if (!IsSolvable(*pwallet, witscript)) { - return false; - } - return ExtractDestination(witscript, result); - } - return false; - } - - bool operator()(const WitnessV0KeyHash& id) - { - already_witness = true; - result = id; - return true; - } - - bool operator()(const WitnessV0ScriptHash& id) - { - already_witness = true; - result = id; - return true; - } - - template<typename T> - bool operator()(const T& dest) { return false; } -}; - -static UniValue addwitnessaddress(const JSONRPCRequest& request) -{ - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - CWallet* const pwallet = wallet.get(); - - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - { - std::string msg = "addwitnessaddress \"address\" ( p2sh )\n" - "\nDEPRECATED: set the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n" - "Add a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n" - "It returns the witness script.\n" - - "\nArguments:\n" - "1. \"address\" (string, required) An address known to the wallet\n" - "2. p2sh (bool, optional, default=true) Embed inside P2SH\n" - - "\nResult:\n" - "\"witnessaddress\", (string) The value of the new address (P2SH or BIP173).\n" - "}\n" - ; - throw std::runtime_error(msg); - } - - if (!IsDeprecatedRPCEnabled("addwitnessaddress")) { - throw JSONRPCError(RPC_METHOD_DEPRECATED, "addwitnessaddress is deprecated and will be fully removed in v0.17. " - "To use addwitnessaddress in v0.16, restart bitcoind with -deprecatedrpc=addwitnessaddress.\n" - "Projects should transition to using the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n"); - } - - CTxDestination dest = DecodeDestination(request.params[0].get_str()); - if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); - } - - bool p2sh = true; - if (!request.params[1].isNull()) { - p2sh = request.params[1].get_bool(); - } - - Witnessifier w(pwallet); - bool ret = boost::apply_visitor(w, dest); - if (!ret) { - throw JSONRPCError(RPC_WALLET_ERROR, "Public key or redeemscript not known to wallet, or the key is uncompressed"); - } - - CScript witprogram = GetScriptForDestination(w.result); - - if (p2sh) { - w.result = CScriptID(witprogram); - } - - if (w.already_witness) { - if (!(dest == w.result)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Cannot convert between witness address types"); - } - } else { - pwallet->AddCScript(witprogram); // Implicit for single-key now, but necessary for multisig and for compatibility with older software - pwallet->SetAddressBook(w.result, "", "receive"); - } - - return EncodeDestination(w.result); -} - struct tallyitem { CAmount nAmount; @@ -1121,7 +996,7 @@ struct tallyitem } }; -static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwallet->cs_wallet) { // Minimum confirmations int nMinDepth = 1; @@ -1928,13 +1803,6 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) } -static void LockWallet(CWallet* pWallet) -{ - LOCK(pWallet->cs_wallet); - pWallet->nRelockTime = 0; - pWallet->Lock(); -} - static UniValue walletpassphrase(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -2004,7 +1872,18 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) pwallet->TopUpKeyPool(); pwallet->nRelockTime = GetTime() + nSleepTime; - RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), std::bind(LockWallet, pwallet), nSleepTime); + + // Keep a weak pointer to the wallet so that it is possible to unload the + // wallet before the following callback is called. If a valid shared pointer + // is acquired in the callback then the wallet is still loaded. + std::weak_ptr<CWallet> weak_wallet = wallet; + RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet] { + if (auto shared_wallet = weak_wallet.lock()) { + LOCK(shared_wallet->cs_wallet); + shared_wallet->Lock(); + shared_wallet->nRelockTime = 0; + } + }, nSleepTime); return NullUniValue; } @@ -2443,6 +2322,38 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) return obj; } +static UniValue listwalletdir(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 0) { + throw std::runtime_error( + "listwalletdir\n" + "Returns a list of wallets in the wallet directory.\n" + "{\n" + " \"wallets\" : [ (json array of objects)\n" + " {\n" + " \"name\" : \"name\" (string) The wallet name\n" + " }\n" + " ,...\n" + " ]\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("listwalletdir", "") + + HelpExampleRpc("listwalletdir", "") + ); + } + + UniValue wallets(UniValue::VARR); + for (const auto& path : ListWalletDir()) { + UniValue wallet(UniValue::VOBJ); + wallet.pushKV("name", path.string()); + wallets.push_back(wallet); + } + + UniValue result(UniValue::VOBJ); + result.pushKV("wallets", wallets); + return result; +} + static UniValue listwallets(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) @@ -3289,6 +3200,12 @@ UniValue generate(const JSONRPCRequest& request) ); } + if (!IsDeprecatedRPCEnabled("generate")) { + throw JSONRPCError(RPC_METHOD_DEPRECATED, "The wallet generate rpc method is deprecated and will be fully removed in v0.19. " + "To use generate in v0.18, restart bitcoind with -deprecatedrpc=generate.\n" + "Clients should transition to using the node rpc method generatetoaddress\n"); + } + int num_generate = request.params[0].get_int(); uint64_t max_tries = 1000000; if (!request.params[1].isNull()) { @@ -4059,7 +3976,6 @@ static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- { "generating", "generate", &generate, {"nblocks","maxtries"} }, - { "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} }, { "hidden", "resendwallettransactions", &resendwallettransactions, {} }, { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} }, { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, @@ -4096,6 +4012,7 @@ static const CRPCCommand commands[] = { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, { "wallet", "listtransactions", &listtransactions, {"dummy","count","skip","include_watchonly"} }, { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, + { "wallet", "listwalletdir", &listwalletdir, {} }, { "wallet", "listwallets", &listwallets, {} }, { "wallet", "loadwallet", &loadwallet, {"filename"} }, { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} }, diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index 526f2d983f..34d6e1b87c 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -17,6 +17,8 @@ BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup) BOOST_AUTO_TEST_CASE(psbt_updater_test) { + LOCK(m_wallet.cs_wallet); + // Create prevtxs and add to wallet CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION); CTransactionRef prev_tx1; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index afe47d986e..29014790e9 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4093,7 +4093,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); - LOCK(cs_main); + LOCK2(cs_main, walletInstance->cs_wallet); CBlockIndex *pindexRescan = chainActive.Genesis(); if (!gArgs.GetBoolArg("-rescan", false)) @@ -4178,7 +4178,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); { - LOCK(walletInstance->cs_wallet); walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size()); walletInstance->WalletLogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size()); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index da326517c0..e6e23ab247 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -460,7 +460,11 @@ public: CAmount GetDebit(const isminefilter& filter) const; CAmount GetCredit(const isminefilter& filter) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); CAmount GetImmatureCredit(bool fUseCache=true) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - CAmount GetAvailableCredit(bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwallet->cs_wallet)". The + // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid + // having to resolve the issue of member access into incomplete type CWallet. + CAmount GetAvailableCredit(bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); CAmount GetChange() const; @@ -492,7 +496,13 @@ public: /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - std::set<uint256> GetConflicts() const; + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; }; class COutput @@ -591,13 +601,13 @@ private: std::mutex mutexScanning; friend class WalletRescanReserver; - WalletBatch *encrypted_batch = nullptr; + WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr; //! the current wallet version: clients below this version are not able to load the wallet int nWalletVersion = FEATURE_BASE; //! the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded - int nWalletMaxVersion = FEATURE_BASE; + int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE; int64_t nNextResend = 0; int64_t nLastResend = 0; @@ -609,9 +619,9 @@ private: * mutated transactions where the mutant gets mined). */ typedef std::multimap<COutPoint, uint256> TxSpends; - TxSpends mapTxSpends; - void AddToSpends(const COutPoint& outpoint, const uint256& wtxid); - void AddToSpends(const uint256& wtxid); + TxSpends mapTxSpends GUARDED_BY(cs_wallet); + void AddToSpends(const COutPoint& outpoint, const uint256& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void AddToSpends(const uint256& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Add a transaction to the wallet, or update it. pIndex and posInBlock should @@ -632,9 +642,9 @@ private: void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); /* Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */ - void MarkInputsDirty(const CTransactionRef& tx); + void MarkInputsDirty(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>); + void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. * Should be called with pindexBlock and posInBlock if this is for a transaction that is included in a block. */ @@ -647,13 +657,13 @@ private: void DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::set<int64_t> setInternalKeyPool; - std::set<int64_t> setExternalKeyPool; + std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet); std::set<int64_t> set_pre_split_keypool; - int64_t m_max_keypool_index = 0; + int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0; std::map<CKeyID, int64_t> m_pool_key_to_index; std::atomic<uint64_t> m_wallet_flags{0}; - int64_t nTimeFirstKey = 0; + int64_t nTimeFirstKey GUARDED_BY(cs_wallet) = 0; /** * Private version of AddWatchOnly method which does not accept a @@ -709,20 +719,20 @@ public: * if they are not ours */ bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, - const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const; + const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** Get a name for this wallet for logging/debugging purposes. */ const std::string& GetName() const { return m_name; } void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void MarkPreSplitKeys(); + void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); // Map from Key ID to key metadata. - std::map<CKeyID, CKeyMetadata> mapKeyMetadata; + std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_wallet); // Map from Script ID to key metadata (for watch-only keys). - std::map<CScriptID, CKeyMetadata> m_script_metadata; + std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet); typedef std::map<unsigned int, CMasterKey> MasterKeyMap; MasterKeyMap mapMasterKeys; @@ -739,17 +749,17 @@ public: encrypted_batch = nullptr; } - std::map<uint256, CWalletTx> mapWallet; + std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet); typedef std::multimap<int64_t, CWalletTx*> TxItems; TxItems wtxOrdered; - int64_t nOrderPosNext = 0; + int64_t nOrderPosNext GUARDED_BY(cs_wallet) = 0; uint64_t nAccountingEntryNumber = 0; std::map<CTxDestination, CAddressBookData> mapAddressBook; - std::set<COutPoint> setLockedCoins; + std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet); const CWalletTx* GetWalletTx(const uint256& hash) const; @@ -769,7 +779,7 @@ public: /** * Find non-change parent output. */ - const CTxOut& FindNonChangeParentOutput(const CTransaction& tx, int output) const; + const CTxOut& FindNonChangeParentOutput(const CTransaction& tx, int output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Shuffle and select coins until nTargetValue is reached while avoiding @@ -780,7 +790,7 @@ public: bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const; - bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_main, cs_wallet); std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const; bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -856,7 +866,7 @@ public: void MarkDirty(); bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); - void LoadToWallet(const CWalletTx& wtxIn); + void LoadToWallet(const CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void TransactionAddedToMempool(const CTransactionRef& tx) override; void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) override; void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override; @@ -1000,7 +1010,7 @@ public: int GetVersion() { LOCK(cs_wallet); return nWalletVersion; } //! Get wallet transactions that conflict with given transaction (spend same outputs) - std::set<uint256> GetConflicts(const uint256& txid) const; + std::set<uint256> GetConflicts(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Check if a given transaction has any of its outputs spent by another transaction in the wallet bool HasWalletSpend(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -1198,6 +1208,6 @@ public: // Use DummySignatureCreator, which inserts 71 byte signatures everywhere. // NOTE: this requires that all inputs must be in mapWallet (eg the tx should // be IsAllFromMe). -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false); +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false); #endif // BITCOIN_WALLET_WALLET_H diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp index 34c76ec4c4..c0c9afe13e 100644 --- a/src/wallet/walletutil.cpp +++ b/src/wallet/walletutil.cpp @@ -4,6 +4,8 @@ #include <wallet/walletutil.h> +#include <util.h> + fs::path GetWalletDir() { fs::path path; @@ -25,3 +27,51 @@ fs::path GetWalletDir() return path; } + +static bool IsBerkeleyBtree(const fs::path& path) +{ + // A Berkeley DB Btree file has at least 4K. + // This check also prevents opening lock files. + boost::system::error_code ec; + if (fs::file_size(path, ec) < 4096) return false; + + fs::ifstream file(path.string(), std::ios::binary); + if (!file.is_open()) return false; + + file.seekg(12, std::ios::beg); // Magic bytes start at offset 12 + uint32_t data = 0; + file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic + + // Berkeley DB Btree magic bytes, from: + // https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75 + // - big endian systems - 00 05 31 62 + // - little endian systems - 62 31 05 00 + return data == 0x00053162 || data == 0x62310500; +} + +std::vector<fs::path> ListWalletDir() +{ + const fs::path wallet_dir = GetWalletDir(); + std::vector<fs::path> paths; + + for (auto it = fs::recursive_directory_iterator(wallet_dir); it != end(it); ++it) { + if (it->status().type() == fs::directory_file && IsBerkeleyBtree(it->path() / "wallet.dat")) { + // Found a directory which contains wallet.dat btree file, add it as a wallet. + paths.emplace_back(fs::relative(it->path(), wallet_dir)); + } else if (it.level() == 0 && it->symlink_status().type() == fs::regular_file && IsBerkeleyBtree(it->path())) { + if (it->path().filename() == "wallet.dat") { + // Found top-level wallet.dat btree file, add top level directory "" + // as a wallet. + paths.emplace_back(); + } else { + // Found top-level btree file not called wallet.dat. Current bitcoin + // software will never create these files but will allow them to be + // opened in a shared database environment for backwards compatibility. + // Add it to the list of available wallets. + paths.emplace_back(fs::relative(it->path(), wallet_dir)); + } + } + } + + return paths; +} diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h index 7c42954616..77f5ca428d 100644 --- a/src/wallet/walletutil.h +++ b/src/wallet/walletutil.h @@ -5,10 +5,14 @@ #ifndef BITCOIN_WALLET_WALLETUTIL_H #define BITCOIN_WALLET_WALLETUTIL_H -#include <chainparamsbase.h> -#include <util.h> +#include <fs.h> + +#include <vector> //! Get the path of the wallet directory. fs::path GetWalletDir(); +//! Get wallets in wallet directory. +std::vector<fs::path> ListWalletDir(); + #endif // BITCOIN_WALLET_WALLETUTIL_H diff --git a/test/README.md b/test/README.md index a5ba732bc0..680f9bf9a6 100644 --- a/test/README.md +++ b/test/README.md @@ -3,17 +3,18 @@ utilities in their entirety. It does not contain unit tests, which can be found in [/src/test](/src/test), [/src/wallet/test](/src/wallet/test), etc. -There are currently two sets of tests in this directory: +This directory contains the following sets of tests: - [functional](/test/functional) which test the functionality of bitcoind and bitcoin-qt by interacting with them through the RPC and P2P interfaces. - [util](/test/util) which tests the bitcoin utilities, currently only bitcoin-tx. +- [lint](/test/lint/) which perform various static analysis checks. The util tests are run as part of `make check` target. The functional -tests are run by the travis continuous build process whenever a pull -request is opened. Both sets of tests can also be run locally. +tests and lint scripts are run by the travis continuous build process whenever a pull +request is opened. All sets of tests can also be run locally. # Running tests locally @@ -30,7 +31,7 @@ The ZMQ functional test requires a python ZMQ library. To install it: #### Running the tests -Individual tests can be run by directly calling the test script, eg: +Individual tests can be run by directly calling the test script, e.g.: ``` test/functional/feature_rbf.py @@ -180,6 +181,26 @@ Note: gdb attach step may require `sudo` Util tests can be run locally by running `test/util/bitcoin-util-test.py`. Use the `-v` option for verbose output. +### Lint tests + +#### Dependencies + +The lint tests require codespell and flake8. To install: `pip3 install codespell flake8`. + +#### Running the tests + +Individual tests can be run by directly calling the test script, e.g.: + +``` +test/lint/lint-filenames.sh +``` + +You can run all the shell-based lint tests by running: + +``` +test/lint/lint-all.sh +``` + # Writing functional tests You are encouraged to write functional tests for new or existing features. diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py new file mode 100755 index 0000000000..9fb0d35a68 --- /dev/null +++ b/test/functional/feature_filelock.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Check that it's not possible to start a second bitcoind instance using the same datadir or wallet.""" +import os + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.test_node import ErrorMatch + +class FilelockTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + def setup_network(self): + self.add_nodes(self.num_nodes, extra_args=None) + self.nodes[0].start([]) + self.nodes[0].wait_for_rpc_connection() + + def run_test(self): + datadir = os.path.join(self.nodes[0].datadir, 'regtest') + self.log.info("Using datadir {}".format(datadir)) + + self.log.info("Check that we can't start a second bitcoind instance using the same datadir") + expected_msg = "Error: Cannot obtain a lock on data directory {}. Bitcoin Core is probably already running.".format(datadir) + self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg) + + if self.is_wallet_compiled(): + wallet_dir = os.path.join(datadir, 'wallets') + self.log.info("Check that we can't start a second bitcoind instance using the same wallet") + expected_msg = "Error: Error initializing wallet database environment" + self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX) + +if __name__ == '__main__': + FilelockTest().main() diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py index 90dc4c8e2b..d8083b2840 100755 --- a/test/functional/feature_notifications.py +++ b/test/functional/feature_notifications.py @@ -51,12 +51,13 @@ class NotificationsTest(BitcoinTestFramework): # directory content should equal the generated transaction hashes txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count))) assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir))) + self.stop_node(1) for tx_file in os.listdir(self.walletnotify_dir): os.remove(os.path.join(self.walletnotify_dir, tx_file)) self.log.info("test -walletnotify after rescan") # restart node to rescan to force wallet notifications - self.restart_node(1) + self.start_node(1) connect_nodes_bi(self.nodes, 0, 1) wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10) diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py index a79cc3d34b..eb76089d9c 100755 --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -12,6 +12,7 @@ Generate 427 more blocks. [Consensus] Check that the new NULLDUMMY rules are not enforced on the 431st block. [Policy/Consensus] Check that the new NULLDUMMY rules are enforced on the 432nd block. """ +import time from test_framework.blocktools import create_coinbase, create_block, create_transaction, add_witness_commitment from test_framework.messages import CTransaction @@ -19,8 +20,6 @@ from test_framework.script import CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, bytes_to_hex_str -import time - NULLDUMMY_ERROR = "non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero) (code 64)" def trueDummy(tx): @@ -42,7 +41,7 @@ class NULLDUMMYTest(BitcoinTestFramework): self.setup_clean_chain = True # This script tests NULLDUMMY activation, which is part of the 'segwit' deployment, so we go through # normal segwit activation here (and don't use the default always-on behaviour). - self.extra_args = [['-whitelist=127.0.0.1', '-vbparams=segwit:0:999999999999', '-addresstype=legacy', "-deprecatedrpc=addwitnessaddress"]] + self.extra_args = [['-whitelist=127.0.0.1', '-vbparams=segwit:0:999999999999', '-addresstype=legacy']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -50,14 +49,14 @@ class NULLDUMMYTest(BitcoinTestFramework): def run_test(self): self.address = self.nodes[0].getnewaddress() self.ms_address = self.nodes[0].addmultisigaddress(1, [self.address])['address'] - self.wit_address = self.nodes[0].addwitnessaddress(self.address) + self.wit_address = self.nodes[0].getnewaddress(address_type='p2sh-segwit') self.wit_ms_address = self.nodes[0].addmultisigaddress(1, [self.address], '', 'p2sh-segwit')['address'] - self.coinbase_blocks = self.nodes[0].generate(2) # Block 2 + self.coinbase_blocks = self.nodes[0].generate(2) # Block 2 coinbase_txid = [] for i in self.coinbase_blocks: coinbase_txid.append(self.nodes[0].getblock(i)['tx'][0]) - self.nodes[0].generate(427) # Block 429 + self.nodes[0].generate(427) # Block 429 self.lastblockhash = self.nodes[0].getbestblockhash() self.tip = int("0x" + self.lastblockhash, 0) self.lastblockheight = 429 @@ -82,7 +81,7 @@ class NULLDUMMYTest(BitcoinTestFramework): self.log.info("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation") test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, amount=46) - test6txs=[CTransaction(test4tx)] + test6txs = [CTransaction(test4tx)] trueDummy(test4tx) assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, bytes_to_hex_str(test4tx.serialize_with_witness()), True) self.block_submit(self.nodes[0], [test4tx]) @@ -99,8 +98,7 @@ class NULLDUMMYTest(BitcoinTestFramework): self.nodes[0].sendrawtransaction(bytes_to_hex_str(i.serialize_with_witness()), True) self.block_submit(self.nodes[0], test6txs, True, True) - - def block_submit(self, node, txs, witness = False, accept = False): + def block_submit(self, node, txs, witness=False, accept=False): block = create_block(self.tip, create_coinbase(self.lastblockheight + 1), self.lastblocktime + 1) block.nVersion = 4 for tx in txs: diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index 2cbfc26e89..7098a03f1e 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -5,11 +5,10 @@ """Test the SegWit changeover logic.""" from decimal import Decimal +from io import BytesIO from test_framework.address import ( key_to_p2pkh, - key_to_p2sh_p2wpkh, - key_to_p2wpkh, program_to_witness, script_to_p2sh, script_to_p2sh_p2wsh, @@ -21,8 +20,6 @@ from test_framework.script import CScript, OP_HASH160, OP_CHECKSIG, OP_0, hash16 from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, bytes_to_hex_str, connect_nodes, hex_str_to_bytes, sync_blocks, try_rpc -from io import BytesIO - NODE_0 = 0 NODE_2 = 2 WIT_V0 = 0 @@ -51,20 +48,17 @@ class SegWitTest(BitcoinTestFramework): "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", - "-deprecatedrpc=addwitnessaddress", ], [ "-blockversion=4", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", - "-deprecatedrpc=addwitnessaddress", ], [ "-blockversion=536870915", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", - "-deprecatedrpc=addwitnessaddress", ], ] @@ -91,9 +85,8 @@ class SegWitTest(BitcoinTestFramework): def fail_accept(self, node, error_msg, txid, sign, redeem_script=""): assert_raises_rpc_error(-26, error_msg, send_to_witness, use_p2wsh=1, node=node, utxo=getutxo(txid), pubkey=self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=sign, insert_redeem_script=redeem_script) - def run_test(self): - self.nodes[0].generate(161) #block 161 + self.nodes[0].generate(161) # block 161 self.log.info("Verify sigops are counted in GBT with pre-BIP141 rules before the fork") txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) @@ -103,28 +96,24 @@ class SegWitTest(BitcoinTestFramework): assert(tmpl['sigoplimit'] == 20000) assert(tmpl['transactions'][0]['hash'] == txid) assert(tmpl['transactions'][0]['sigops'] == 2) - tmpl = self.nodes[0].getblocktemplate({'rules':['segwit']}) + tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']}) assert(tmpl['sizelimit'] == 1000000) assert('weightlimit' not in tmpl) assert(tmpl['sigoplimit'] == 20000) assert(tmpl['transactions'][0]['hash'] == txid) assert(tmpl['transactions'][0]['sigops'] == 2) - self.nodes[0].generate(1) #block 162 + self.nodes[0].generate(1) # block 162 balance_presetup = self.nodes[0].getbalance() self.pubkey = [] - p2sh_ids = [] # p2sh_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE embedded in p2sh - wit_ids = [] # wit_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE via bare witness + p2sh_ids = [] # p2sh_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE embedded in p2sh + wit_ids = [] # wit_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE via bare witness for i in range(3): newaddress = self.nodes[i].getnewaddress() self.pubkey.append(self.nodes[i].getaddressinfo(newaddress)["pubkey"]) multiscript = CScript([OP_1, hex_str_to_bytes(self.pubkey[-1]), OP_1, OP_CHECKMULTISIG]) - p2sh_addr = self.nodes[i].addwitnessaddress(newaddress) - bip173_addr = self.nodes[i].addwitnessaddress(newaddress, False) p2sh_ms_addr = self.nodes[i].addmultisigaddress(1, [self.pubkey[-1]], '', 'p2sh-segwit')['address'] bip173_ms_addr = self.nodes[i].addmultisigaddress(1, [self.pubkey[-1]], '', 'bech32')['address'] - assert_equal(p2sh_addr, key_to_p2sh_p2wpkh(self.pubkey[-1])) - assert_equal(bip173_addr, key_to_p2wpkh(self.pubkey[-1])) assert_equal(p2sh_ms_addr, script_to_p2sh_p2wsh(multiscript)) assert_equal(bip173_ms_addr, script_to_p2wsh(multiscript)) p2sh_ids.append([]) @@ -139,32 +128,32 @@ class SegWitTest(BitcoinTestFramework): wit_ids[n][v].append(send_to_witness(v, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[n], False, Decimal("49.999"))) p2sh_ids[n][v].append(send_to_witness(v, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[n], True, Decimal("49.999"))) - self.nodes[0].generate(1) #block 163 + self.nodes[0].generate(1) # block 163 sync_blocks(self.nodes) # Make sure all nodes recognize the transactions as theirs - assert_equal(self.nodes[0].getbalance(), balance_presetup - 60*50 + 20*Decimal("49.999") + 50) - assert_equal(self.nodes[1].getbalance(), 20*Decimal("49.999")) - assert_equal(self.nodes[2].getbalance(), 20*Decimal("49.999")) + assert_equal(self.nodes[0].getbalance(), balance_presetup - 60 * 50 + 20 * Decimal("49.999") + 50) + assert_equal(self.nodes[1].getbalance(), 20 * Decimal("49.999")) + assert_equal(self.nodes[2].getbalance(), 20 * Decimal("49.999")) - self.nodes[0].generate(260) #block 423 + self.nodes[0].generate(260) # block 423 sync_blocks(self.nodes) self.log.info("Verify witness txs are skipped for mining before the fork") - self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][0], True) #block 424 - self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][0], True) #block 425 - self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][0], True) #block 426 - self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][0], True) #block 427 + self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][0], True) # block 424 + self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][0], True) # block 425 + self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][0], True) # block 426 + self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][0], True) # block 427 self.log.info("Verify unsigned p2sh witness txs without a redeem script are invalid") self.fail_accept(self.nodes[2], "mandatory-script-verify-flag", p2sh_ids[NODE_2][WIT_V0][1], False) self.fail_accept(self.nodes[2], "mandatory-script-verify-flag", p2sh_ids[NODE_2][WIT_V1][1], False) - self.nodes[2].generate(4) # blocks 428-431 + self.nodes[2].generate(4) # blocks 428-431 self.log.info("Verify previous witness txs skipped for mining can now be mined") assert_equal(len(self.nodes[2].getrawmempool()), 4) - block = self.nodes[2].generate(1) #block 432 (first block with new rules; 432 = 144 * 3) + block = self.nodes[2].generate(1) # block 432 (first block with new rules; 432 = 144 * 3) sync_blocks(self.nodes) assert_equal(len(self.nodes[2].getrawmempool()), 0) segwit_tx_list = self.nodes[2].getblock(block[0])["tx"] @@ -181,8 +170,8 @@ class SegWitTest(BitcoinTestFramework): self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", p2sh_ids[NODE_0][WIT_V1][0], False, witness_script(True, self.pubkey[0])) self.log.info("Verify block and transaction serialization rpcs return differing serializations depending on rpc serialization flag") - assert(self.nodes[2].getblock(block[0], False) != self.nodes[0].getblock(block[0], False)) - assert(self.nodes[1].getblock(block[0], False) == self.nodes[2].getblock(block[0], False)) + assert(self.nodes[2].getblock(block[0], False) != self.nodes[0].getblock(block[0], False)) + assert(self.nodes[1].getblock(block[0], False) == self.nodes[2].getblock(block[0], False)) for i in range(len(segwit_tx_list)): tx = FromHex(CTransaction(), self.nodes[2].gettransaction(segwit_tx_list[i])["hex"]) assert(self.nodes[2].getrawtransaction(segwit_tx_list[i]) != self.nodes[0].getrawtransaction(segwit_tx_list[i])) @@ -198,21 +187,21 @@ class SegWitTest(BitcoinTestFramework): self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program was passed an empty witness) (code 64)', p2sh_ids[NODE_2][WIT_V1][2], sign=False, redeem_script=witness_script(True, self.pubkey[2])) self.log.info("Verify default node can now use witness txs") - self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], True) #block 432 - self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], True) #block 433 - self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], True) #block 434 - self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], True) #block 435 + self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], True) # block 432 + self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], True) # block 433 + self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], True) # block 434 + self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], True) # block 435 self.log.info("Verify sigops are counted in GBT with BIP141 rules after the fork") txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) - tmpl = self.nodes[0].getblocktemplate({'rules':['segwit']}) + tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']}) assert(tmpl['sizelimit'] >= 3999577) # actual maximum size is lower due to minimum mandatory non-witness data assert(tmpl['weightlimit'] == 4000000) assert(tmpl['sigoplimit'] == 80000) assert(tmpl['transactions'][0]['txid'] == txid) assert(tmpl['transactions'][0]['sigops'] == 8) - self.nodes[0].generate(1) # Mine a block to clear the gbt cache + self.nodes[0].generate(1) # Mine a block to clear the gbt cache self.log.info("Non-segwit miners are able to use GBT response after activation.") # Create a 3-tx chain: tx1 (non-segwit input, paying to a segwit output) -> @@ -222,7 +211,7 @@ class SegWitTest(BitcoinTestFramework): txid1 = send_to_witness(1, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[0], False, Decimal("49.996")) hex_tx = self.nodes[0].gettransaction(txid)['hex'] tx = FromHex(CTransaction(), hex_tx) - assert(tx.wit.is_null()) # This should not be a segwit input + assert(tx.wit.is_null()) # This should not be a segwit input assert(txid1 in self.nodes[0].getrawmempool()) # Now create tx2, which will spend from txid1. @@ -247,13 +236,13 @@ class SegWitTest(BitcoinTestFramework): template = self.nodes[0].getblocktemplate() # Check that tx1 is the only transaction of the 3 in the template. - template_txids = [ t['txid'] for t in template['transactions'] ] + template_txids = [t['txid'] for t in template['transactions']] assert(txid2 not in template_txids and txid3 not in template_txids) assert(txid1 in template_txids) # Check that running with segwit support results in all 3 being included. template = self.nodes[0].getblocktemplate({"rules": ["segwit"]}) - template_txids = [ t['txid'] for t in template['transactions'] ] + template_txids = [t['txid'] for t in template['transactions']] assert(txid1 in template_txids) assert(txid2 in template_txids) assert(txid3 in template_txids) @@ -264,17 +253,17 @@ class SegWitTest(BitcoinTestFramework): # Mine a block to clear the gbt cache again. self.nodes[0].generate(1) - self.log.info("Verify behaviour of importaddress, addwitnessaddress and listunspent") + self.log.info("Verify behaviour of importaddress and listunspent") # Some public keys to be used later pubkeys = [ - "0363D44AABD0F1699138239DF2F042C3282C0671CC7A76826A55C8203D90E39242", # cPiM8Ub4heR9NBYmgVzJQiUH1if44GSBGiqaeJySuL2BKxubvgwb - "02D3E626B3E616FC8662B489C123349FECBFC611E778E5BE739B257EAE4721E5BF", # cPpAdHaD6VoYbW78kveN2bsvb45Q7G5PhaPApVUGwvF8VQ9brD97 - "04A47F2CBCEFFA7B9BCDA184E7D5668D3DA6F9079AD41E422FA5FD7B2D458F2538A62F5BD8EC85C2477F39650BD391EA6250207065B2A81DA8B009FC891E898F0E", # 91zqCU5B9sdWxzMt1ca3VzbtVm2YM6Hi5Rxn4UDtxEaN9C9nzXV - "02A47F2CBCEFFA7B9BCDA184E7D5668D3DA6F9079AD41E422FA5FD7B2D458F2538", # cPQFjcVRpAUBG8BA9hzr2yEzHwKoMgLkJZBBtK9vJnvGJgMjzTbd - "036722F784214129FEB9E8129D626324F3F6716555B603FFE8300BBCB882151228", # cQGtcm34xiLjB1v7bkRa4V3aAc9tS2UTuBZ1UnZGeSeNy627fN66 - "0266A8396EE936BF6D99D17920DB21C6C7B1AB14C639D5CD72B300297E416FD2EC", # cTW5mR5M45vHxXkeChZdtSPozrFwFgmEvTNnanCW6wrqwaCZ1X7K - "0450A38BD7F0AC212FEBA77354A9B036A32E0F7C81FC4E0C5ADCA7C549C4505D2522458C2D9AE3CEFD684E039194B72C8A10F9CB9D4764AB26FCC2718D421D3B84", # 92h2XPssjBpsJN5CqSP7v9a7cf2kgDunBC6PDFwJHMACM1rrVBJ + "0363D44AABD0F1699138239DF2F042C3282C0671CC7A76826A55C8203D90E39242", # cPiM8Ub4heR9NBYmgVzJQiUH1if44GSBGiqaeJySuL2BKxubvgwb + "02D3E626B3E616FC8662B489C123349FECBFC611E778E5BE739B257EAE4721E5BF", # cPpAdHaD6VoYbW78kveN2bsvb45Q7G5PhaPApVUGwvF8VQ9brD97 + "04A47F2CBCEFFA7B9BCDA184E7D5668D3DA6F9079AD41E422FA5FD7B2D458F2538A62F5BD8EC85C2477F39650BD391EA6250207065B2A81DA8B009FC891E898F0E", # 91zqCU5B9sdWxzMt1ca3VzbtVm2YM6Hi5Rxn4UDtxEaN9C9nzXV + "02A47F2CBCEFFA7B9BCDA184E7D5668D3DA6F9079AD41E422FA5FD7B2D458F2538", # cPQFjcVRpAUBG8BA9hzr2yEzHwKoMgLkJZBBtK9vJnvGJgMjzTbd + "036722F784214129FEB9E8129D626324F3F6716555B603FFE8300BBCB882151228", # cQGtcm34xiLjB1v7bkRa4V3aAc9tS2UTuBZ1UnZGeSeNy627fN66 + "0266A8396EE936BF6D99D17920DB21C6C7B1AB14C639D5CD72B300297E416FD2EC", # cTW5mR5M45vHxXkeChZdtSPozrFwFgmEvTNnanCW6wrqwaCZ1X7K + "0450A38BD7F0AC212FEBA77354A9B036A32E0F7C81FC4E0C5ADCA7C549C4505D2522458C2D9AE3CEFD684E039194B72C8A10F9CB9D4764AB26FCC2718D421D3B84", # 92h2XPssjBpsJN5CqSP7v9a7cf2kgDunBC6PDFwJHMACM1rrVBJ ] # Import a compressed key and an uncompressed key, generate some multisig addresses @@ -282,8 +271,8 @@ class SegWitTest(BitcoinTestFramework): uncompressed_spendable_address = ["mvozP4UwyGD2mGZU4D2eMvMLPB9WkMmMQu"] self.nodes[0].importprivkey("cNC8eQ5dg3mFAVePDX4ddmPYpPbw41r9bm2jd1nLJT77e6RrzTRR") compressed_spendable_address = ["mmWQubrDomqpgSYekvsU7HWEVjLFHAakLe"] - assert ((self.nodes[0].getaddressinfo(uncompressed_spendable_address[0])['iscompressed'] == False)) - assert ((self.nodes[0].getaddressinfo(compressed_spendable_address[0])['iscompressed'] == True)) + assert not self.nodes[0].getaddressinfo(uncompressed_spendable_address[0])['iscompressed'] + assert self.nodes[0].getaddressinfo(compressed_spendable_address[0])['iscompressed'] self.nodes[0].importpubkey(pubkeys[0]) compressed_solvable_address = [key_to_p2pkh(pubkeys[0])] @@ -305,7 +294,6 @@ class SegWitTest(BitcoinTestFramework): uncompressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], uncompressed_solvable_address[0]])['address']) compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_solvable_address[0]])['address']) compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_solvable_address[0], compressed_solvable_address[1]])['address']) - unknown_address = ["mtKKyoHabkk6e4ppT7NaM7THqPUt7AzPrT", "2NDP3jLWAFT8NDAiUa9qiE6oBt2awmMq7Dx"] # Test multisig_without_privkey # We have 2 public keys without private keys, use addmultisigaddress to add to wallet. @@ -386,7 +374,6 @@ class SegWitTest(BitcoinTestFramework): op1 = CScript([OP_1]) op0 = CScript([OP_0]) # 2N7MGY19ti4KDMSzRfPAssP6Pxyuxoi6jLe is the P2SH(P2PKH) version of mjoE3sSrb8ByYEvgnC3Aox86u1CHnfJA4V - unsolvable_address = ["mjoE3sSrb8ByYEvgnC3Aox86u1CHnfJA4V", "2N7MGY19ti4KDMSzRfPAssP6Pxyuxoi6jLe", script_to_p2sh(op1), script_to_p2sh(op0)] unsolvable_address_key = hex_str_to_bytes("02341AEC7587A51CDE5279E0630A531AEA2615A9F80B17E8D9376327BAEAA59E3D") unsolvablep2pkh = CScript([OP_DUP, OP_HASH160, hash160(unsolvable_address_key), OP_EQUALVERIFY, OP_CHECKSIG]) unsolvablep2wshp2pkh = CScript([OP_0, sha256(unsolvablep2pkh)]) @@ -394,9 +381,9 @@ class SegWitTest(BitcoinTestFramework): p2wshop1 = CScript([OP_0, sha256(op1)]) unsolvable_after_importaddress.append(unsolvablep2pkh) unsolvable_after_importaddress.append(unsolvablep2wshp2pkh) - unsolvable_after_importaddress.append(op1) # OP_1 will be imported as script + unsolvable_after_importaddress.append(op1) # OP_1 will be imported as script unsolvable_after_importaddress.append(p2wshop1) - unseen_anytime.append(op0) # OP_0 will be imported as P2SH address with no script provided + unseen_anytime.append(op0) # OP_0 will be imported as P2SH address with no script provided unsolvable_after_importaddress.append(p2shop0) spendable_txid = [] @@ -432,27 +419,14 @@ class SegWitTest(BitcoinTestFramework): # exceptions and continue. try_rpc(-4, "The wallet already contains the private key for this address or script", self.nodes[0].importaddress, i, "", False, True) - self.nodes[0].importaddress(script_to_p2sh(op0)) # import OP_0 as address only - self.nodes[0].importaddress(multisig_without_privkey_address) # Test multisig_without_privkey + self.nodes[0].importaddress(script_to_p2sh(op0)) # import OP_0 as address only + self.nodes[0].importaddress(multisig_without_privkey_address) # Test multisig_without_privkey spendable_txid.append(self.mine_and_test_listunspent(spendable_anytime + spendable_after_importaddress, 2)) solvable_txid.append(self.mine_and_test_listunspent(solvable_anytime + solvable_after_importaddress, 1)) self.mine_and_test_listunspent(unsolvable_after_importaddress, 1) self.mine_and_test_listunspent(unseen_anytime, 0) - # addwitnessaddress should refuse to return a witness address if an uncompressed key is used - # note that no witness address should be returned by unsolvable addresses - for i in uncompressed_spendable_address + uncompressed_solvable_address + unknown_address + unsolvable_address: - assert_raises_rpc_error(-4, "Public key or redeemscript not known to wallet, or the key is uncompressed", self.nodes[0].addwitnessaddress, i) - - # addwitnessaddress should return a witness addresses even if keys are not in the wallet - self.nodes[0].addwitnessaddress(multisig_without_privkey_address) - - for i in compressed_spendable_address + compressed_solvable_address: - witaddress = self.nodes[0].addwitnessaddress(i) - # addwitnessaddress should return the same address if it is a known P2SH-witness address - assert_equal(witaddress, self.nodes[0].addwitnessaddress(witaddress)) - spendable_txid.append(self.mine_and_test_listunspent(spendable_anytime + spendable_after_importaddress, 2)) solvable_txid.append(self.mine_and_test_listunspent(solvable_anytime + solvable_after_importaddress, 1)) self.mine_and_test_listunspent(unsolvable_after_importaddress, 1) @@ -470,8 +444,6 @@ class SegWitTest(BitcoinTestFramework): self.nodes[0].importpubkey(pubkeys[6]) uncompressed_solvable_address = [key_to_p2pkh(pubkeys[6])] - spendable_after_addwitnessaddress = [] # These outputs should be seen after importaddress - solvable_after_addwitnessaddress=[] # These outputs should be seen after importaddress but not spendable unseen_anytime = [] # These outputs should never be seen solvable_anytime = [] # These outputs should be solvable after importpubkey unseen_anytime = [] # These outputs should never be seen @@ -488,8 +460,6 @@ class SegWitTest(BitcoinTestFramework): v = self.nodes[0].getaddressinfo(i) if (v['isscript']): [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) - # P2WSH and P2SH(P2WSH) multisig with compressed keys are spendable after addwitnessaddress - spendable_after_addwitnessaddress.extend([p2wsh, p2sh_p2wsh]) premature_witaddress.append(script_to_p2sh(p2wsh)) else: [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) @@ -510,9 +480,7 @@ class SegWitTest(BitcoinTestFramework): for i in compressed_solvable_address: v = self.nodes[0].getaddressinfo(i) if (v['isscript']): - # P2WSH multisig without private key are seen after addwitnessaddress [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) - solvable_after_addwitnessaddress.extend([p2wsh, p2sh_p2wsh]) premature_witaddress.append(script_to_p2sh(p2wsh)) else: [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) @@ -521,29 +489,11 @@ class SegWitTest(BitcoinTestFramework): self.mine_and_test_listunspent(spendable_anytime, 2) self.mine_and_test_listunspent(solvable_anytime, 1) - self.mine_and_test_listunspent(spendable_after_addwitnessaddress + solvable_after_addwitnessaddress + unseen_anytime, 0) - - # addwitnessaddress should refuse to return a witness address if an uncompressed key is used - # note that a multisig address returned by addmultisigaddress is not solvable until it is added with importaddress - # premature_witaddress are not accepted until the script is added with addwitnessaddress first - for i in uncompressed_spendable_address + uncompressed_solvable_address + premature_witaddress: - # This will raise an exception - assert_raises_rpc_error(-4, "Public key or redeemscript not known to wallet, or the key is uncompressed", self.nodes[0].addwitnessaddress, i) - - # after importaddress it should pass addwitnessaddress - v = self.nodes[0].getaddressinfo(compressed_solvable_address[1]) - self.nodes[0].importaddress(v['hex'],"",False,True) - for i in compressed_spendable_address + compressed_solvable_address + premature_witaddress: - witaddress = self.nodes[0].addwitnessaddress(i) - assert_equal(witaddress, self.nodes[0].addwitnessaddress(witaddress)) - - spendable_txid.append(self.mine_and_test_listunspent(spendable_after_addwitnessaddress + spendable_anytime, 2)) - solvable_txid.append(self.mine_and_test_listunspent(solvable_after_addwitnessaddress + solvable_anytime, 1)) self.mine_and_test_listunspent(unseen_anytime, 0) # Check that createrawtransaction/decoderawtransaction with non-v0 Bech32 works - v1_addr = program_to_witness(1, [3,5]) - v1_tx = self.nodes[0].createrawtransaction([getutxo(spendable_txid[0])],{v1_addr: 1}) + v1_addr = program_to_witness(1, [3, 5]) + v1_tx = self.nodes[0].createrawtransaction([getutxo(spendable_txid[0])], {v1_addr: 1}) v1_decoded = self.nodes[1].decoderawtransaction(v1_tx) assert_equal(v1_decoded['vout'][0]['scriptPubKey']['addresses'][0], v1_addr) assert_equal(v1_decoded['vout'][0]['scriptPubKey']['hex'], "51020305") @@ -586,7 +536,7 @@ class SegWitTest(BitcoinTestFramework): def mine_and_test_listunspent(self, script_list, ismine): utxo = find_spendable_utxo(self.nodes[0], 50) tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(int('0x'+utxo['txid'],0), utxo['vout']))) + tx.vin.append(CTxIn(COutPoint(int('0x' + utxo['txid'], 0), utxo['vout']))) for i in script_list: tx.vout.append(CTxOut(10000000, i)) tx.rehash() @@ -599,7 +549,7 @@ class SegWitTest(BitcoinTestFramework): for i in self.nodes[0].listunspent(): if (i['txid'] == txid): watchcount += 1 - if (i['spendable'] == True): + if i['spendable']: spendcount += 1 if (ismine == 2): assert_equal(spendcount, len(script_list)) @@ -610,14 +560,14 @@ class SegWitTest(BitcoinTestFramework): assert_equal(watchcount, 0) return txid - def p2sh_address_to_script(self,v): + def p2sh_address_to_script(self, v): bare = CScript(hex_str_to_bytes(v['hex'])) p2sh = CScript(hex_str_to_bytes(v['scriptPubKey'])) p2wsh = CScript([OP_0, sha256(bare)]) p2sh_p2wsh = CScript([OP_HASH160, hash160(p2wsh), OP_EQUAL]) return([bare, p2sh, p2wsh, p2sh_p2wsh]) - def p2pkh_address_to_script(self,v): + def p2pkh_address_to_script(self, v): pubkey = hex_str_to_bytes(v['pubkey']) p2wpkh = CScript([OP_0, hash160(pubkey)]) p2sh_p2wpkh = CScript([OP_HASH160, hash160(p2wpkh), OP_EQUAL]) @@ -631,7 +581,7 @@ class SegWitTest(BitcoinTestFramework): p2sh_p2wsh_p2pkh = CScript([OP_HASH160, hash160(p2wsh_p2pkh), OP_EQUAL]) return [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] - def create_and_mine_tx_from_txids(self, txids, success = True): + def create_and_mine_tx_from_txids(self, txids, success=True): tx = CTransaction() for i in txids: txtmp = CTransaction() @@ -639,7 +589,7 @@ class SegWitTest(BitcoinTestFramework): f = BytesIO(hex_str_to_bytes(txraw)) txtmp.deserialize(f) for j in range(len(txtmp.vout)): - tx.vin.append(CTxIn(COutPoint(int('0x'+i,0), j))) + tx.vin.append(CTxIn(COutPoint(int('0x' + i, 0), j))) tx.vout.append(CTxOut(0, CScript())) tx.rehash() signresults = self.nodes[0].signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize_without_witness()))['hex'] diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 58cdaf861f..aed439339f 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -18,7 +18,7 @@ class TestBitcoinCli(BitcoinTestFramework): cli_response = self.nodes[0].cli("-version").send_cli() assert("Bitcoin Core RPC client version" in cli_response) - self.log.info("Compare responses from gewalletinfo RPC and `bitcoin-cli getwalletinfo`") + self.log.info("Compare responses from getwalletinfo RPC and `bitcoin-cli getwalletinfo`") if self.is_wallet_compiled(): cli_response = self.nodes[0].cli.getwalletinfo() rpc_response = self.nodes[0].getwalletinfo() diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index fafc27d16a..f0dee59b5c 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -7,7 +7,6 @@ Version 1 compact blocks are pre-segwit (txids) Version 2 compact blocks are post-segwit (wtxids) """ - from decimal import Decimal import random @@ -99,7 +98,7 @@ class CompactBlocksTest(BitcoinTestFramework): self.num_nodes = 2 # This test was written assuming SegWit is activated using BIP9 at height 432 (3x confirmation window). # TODO: Rewrite this test to support SegWit being always active. - self.extra_args = [["-vbparams=segwit:0:0"], ["-vbparams=segwit:0:999999999999", "-txindex", "-deprecatedrpc=addwitnessaddress"]] + self.extra_args = [["-vbparams=segwit:0:0"], ["-vbparams=segwit:0:999999999999", "-txindex"]] self.utxos = [] def skip_test_if_missing_module(self): @@ -189,7 +188,7 @@ class CompactBlocksTest(BitcoinTestFramework): # Now try a SENDCMPCT message with too-high version sendcmpct = msg_sendcmpct() - sendcmpct.version = preferred_version+1 + sendcmpct.version = preferred_version + 1 sendcmpct.announce = True test_node.send_and_ping(sendcmpct) check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message) @@ -220,7 +219,7 @@ class CompactBlocksTest(BitcoinTestFramework): check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message) # Try one more time, after sending a version-1, announce=false message. - sendcmpct.version = preferred_version-1 + sendcmpct.version = preferred_version - 1 sendcmpct.announce = False test_node.send_and_ping(sendcmpct) check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message) @@ -234,7 +233,7 @@ class CompactBlocksTest(BitcoinTestFramework): if old_node is not None: # Verify that a peer using an older protocol version can receive # announcements from this node. - sendcmpct.version = preferred_version-1 + sendcmpct.version = preferred_version - 1 sendcmpct.announce = True old_node.send_and_ping(sendcmpct) # Header sync @@ -265,9 +264,9 @@ class CompactBlocksTest(BitcoinTestFramework): if use_witness_address: # Want at least one segwit spend, so move all funds to # a witness address. - address = node.addwitnessaddress(address) + address = node.getnewaddress(address_type='bech32') value_to_send = node.getbalance() - node.sendtoaddress(address, satoshi_round(value_to_send-Decimal(0.1))) + node.sendtoaddress(address, satoshi_round(value_to_send - Decimal(0.1))) node.generate(1) segwit_tx_generated = False @@ -279,7 +278,7 @@ class CompactBlocksTest(BitcoinTestFramework): segwit_tx_generated = True if use_witness_address: - assert(segwit_tx_generated) # check that our test is not broken + assert segwit_tx_generated # check that our test is not broken # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) @@ -403,8 +402,7 @@ class CompactBlocksTest(BitcoinTestFramework): coinbase_hash = block.vtx[0].sha256 if version == 2: coinbase_hash = block.vtx[0].calc_sha256(True) - comp_block.shortids = [ - calculate_shortid(k0, k1, coinbase_hash) ] + comp_block.shortids = [calculate_shortid(k0, k1, coinbase_hash)] test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) # Expect a getblocktxn message. @@ -443,7 +441,7 @@ class CompactBlocksTest(BitcoinTestFramework): # node needs, and that responding to them causes the block to be # reconstructed. def test_getblocktxn_requests(self, node, test_node, version): - with_witness = (version==2) + with_witness = (version == 2) def test_getblocktxn_response(compact_block, peer, expected_result): msg = msg_cmpctblock(compact_block.to_p2p()) @@ -470,7 +468,7 @@ class CompactBlocksTest(BitcoinTestFramework): msg_bt = msg_blocktxn() if with_witness: - msg_bt = msg_witness_blocktxn() # serialize with witnesses + msg_bt = msg_witness_blocktxn() # serialize with witnesses msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[1:]) test_tip_after_message(node, test_node, msg_bt, block.sha256) @@ -560,7 +558,7 @@ class CompactBlocksTest(BitcoinTestFramework): # verifying that the block isn't marked bad permanently. This is good # enough for now. msg = msg_blocktxn() - if version==2: + if version == 2: msg = msg_witness_blocktxn() msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]] + block.vtx[7:]) test_node.send_and_ping(msg) @@ -571,11 +569,11 @@ class CompactBlocksTest(BitcoinTestFramework): # We should receive a getdata request wait_until(lambda: "getdata" in test_node.last_message, timeout=10, lock=mininode_lock) assert_equal(len(test_node.last_message["getdata"].inv), 1) - assert(test_node.last_message["getdata"].inv[0].type == 2 or test_node.last_message["getdata"].inv[0].type == 2|MSG_WITNESS_FLAG) + assert(test_node.last_message["getdata"].inv[0].type == 2 or test_node.last_message["getdata"].inv[0].type == 2 | MSG_WITNESS_FLAG) assert_equal(test_node.last_message["getdata"].inv[0].hash, block.sha256) # Deliver the block - if version==2: + if version == 2: test_node.send_and_ping(msg_witness_block(block)) else: test_node.send_and_ping(msg_block(block)) @@ -655,7 +653,7 @@ class CompactBlocksTest(BitcoinTestFramework): # Generate an old compactblock, and verify that it's not accepted. cur_height = node.getblockcount() - hashPrevBlock = int(node.getblockhash(cur_height-5), 16) + hashPrevBlock = int(node.getblockhash(cur_height - 5), 16) block = self.build_block_on_tip(node) block.hashPrevBlock = hashPrevBlock block.solve() @@ -684,7 +682,7 @@ class CompactBlocksTest(BitcoinTestFramework): assert "blocktxn" not in test_node.last_message def activate_segwit(self, node): - node.generate(144*3) + node.generate(144 * 3) assert_equal(get_bip9_status(node, "segwit")["status"], 'active') def test_end_to_end_block_relay(self, node, listeners): @@ -780,7 +778,7 @@ class CompactBlocksTest(BitcoinTestFramework): delivery_peer.send_message(msg_tx(tx)) delivery_peer.sync_with_ping() - cmpct_block.prefilled_txn[0].tx.wit.vtxinwit = [ CTxInWitness() ] + cmpct_block.prefilled_txn[0].tx.wit.vtxinwit = [CTxInWitness()] cmpct_block.prefilled_txn[0].tx.wit.vtxinwit[0].scriptWitness.stack = [ser_uint256(0)] cmpct_block.use_witness = True @@ -886,7 +884,7 @@ class CompactBlocksTest(BitcoinTestFramework): self.log.info("Syncing nodes...") assert(self.nodes[0].getbestblockhash() != self.nodes[1].getbestblockhash()) while (self.nodes[0].getblockcount() > self.nodes[1].getblockcount()): - block_hash = self.nodes[0].getblockhash(self.nodes[1].getblockcount()+1) + block_hash = self.nodes[0].getblockhash(self.nodes[1].getblockcount() + 1) self.nodes[1].submitblock(self.nodes[0].getblock(block_hash, False)) assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash()) diff --git a/test/functional/rpc_deprecated.py b/test/functional/rpc_deprecated.py index 58074803cc..588bfbe083 100755 --- a/test/functional/rpc_deprecated.py +++ b/test/functional/rpc_deprecated.py @@ -4,12 +4,17 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test deprecation of RPC calls.""" from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_raises_rpc_error class DeprecatedRpcTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True - self.extra_args = [[], ["-deprecatedrpc=validateaddress"]] + self.extra_args = [[], ["-deprecatedrpc=generate"]] + + def skip_test_if_missing_module(self): + # The generate RPC method requires the wallet to be compiled + self.skip_if_no_wallet() def run_test(self): # This test should be used to verify correct behaviour of deprecated @@ -18,7 +23,10 @@ class DeprecatedRpcTest(BitcoinTestFramework): # self.log.info("Make sure that -deprecatedrpc=createmultisig allows it to take addresses") # assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, [self.nodes[0].getnewaddress()]) # self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()]) - pass + + self.log.info("Test generate RPC") + assert_raises_rpc_error(-32, 'The wallet generate rpc method is deprecated', self.nodes[0].rpc.generate, 1) + self.nodes[1].generate(1) if __name__ == '__main__': DeprecatedRpcTest().main() diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index 96f9ccdbdb..881b839a4e 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -65,6 +65,8 @@ class ScantxoutsetTest(BitcoinTestFramework): assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007")) self.log.info("Test extended key derivation.") + # Run various scans, and verify that the sum of the amounts of the matches corresponds to the expected subset. + # Note that all amounts in the UTXO set are powers of 2 multiplied by 0.001 BTC, so each amounts uniquely identifies a subset. assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/0h)"])['total_amount'], Decimal("0.008")) assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/1h)"])['total_amount'], Decimal("0.016")) assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500')"])['total_amount'], Decimal("0.032")) @@ -82,7 +84,7 @@ class ScantxoutsetTest(BitcoinTestFramework): assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1)"])['total_amount'], Decimal("8.192")) assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500)"])['total_amount'], Decimal("16.384")) assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"])['total_amount'], Decimal("4.096")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"])['total_amount'], Decimal("8.192")) + assert_equal(self.nodes[0].scantxoutset("start", [ "combo([abcdef88/1/2'/3/4h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"])['total_amount'], Decimal("8.192")) assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1500)"])['total_amount'], Decimal("16.384")) assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1499}])['total_amount'], Decimal("1.536")) assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1500}])['total_amount'], Decimal("3.584")) diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index f8caa57250..6864e8a838 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -500,9 +500,9 @@ class P2PDataStore(P2PInterface): wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock) if expect_disconnect: - self.wait_for_disconnect() + self.wait_for_disconnect(timeout=timeout) else: - self.sync_with_ping() + self.sync_with_ping(timeout=timeout) if success: wait_until(lambda: node.getbestblockhash() == blocks[-1].hash, timeout=timeout) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index c05988c661..cc1bfabbfa 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -197,6 +197,25 @@ class TestNode(): time.sleep(1.0 / poll_per_s) self._raise_assertion_error("Unable to connect to bitcoind") + def generate(self, nblocks, maxtries=1000000): + self.log.debug("TestNode.generate() dispatches `generate` call to `generatetoaddress`") + # Try to import the node's deterministic private key. This is a no-op if the private key + # has already been imported. + try: + self.rpc.importprivkey(privkey=self.get_deterministic_priv_key().key, label='coinbase', rescan=False) + except JSONRPCException as e: + # This may fail if: + # - wallet is disabled ('Method not found') + # - there are multiple wallets to import to ('Wallet file not specified') + # - wallet is locked ('Error: Please enter the wallet passphrase with walletpassphrase first') + # Just ignore those errors. We can make this tidier by importing the privkey during TestFramework.setup_nodes + # TODO: tidy up deterministic privkey import. + assert str(e).startswith('Method not found') or \ + str(e).startswith('Wallet file not specified') or \ + str(e).startswith('Error: Please enter the wallet passphrase with walletpassphrase first') + + return self.generatetoaddress(nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries) + def get_wallet_rpc(self, wallet_name): if self.use_cli: return self.cli("-rpcwallet={}".format(wallet_name)) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index c6d1574201..c1dcc46e23 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -174,6 +174,7 @@ BASE_SCRIPTS = [ 'rpc_getblockstats.py', 'p2p_fingerprint.py', 'feature_uacomment.py', + 'feature_filelock.py', 'p2p_unrequested_blocks.py', 'feature_includeconf.py', 'rpc_scantxoutset.py', @@ -320,9 +321,10 @@ def main(): args=passon_args, combined_logs_len=args.combinedlogslen, failfast=args.failfast, + level=logging_level ) -def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False): +def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, level=logging.DEBUG): args = args or [] # Warn if bitcoind is already running (unix only) @@ -357,22 +359,22 @@ def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=Fal raise #Run Tests - job_queue = TestHandler(jobs, tests_dir, tmpdir, test_list, flags) + job_queue = TestHandler(jobs, tests_dir, tmpdir, test_list, flags, level) start_time = time.time() test_results = [] max_len_name = len(max(test_list, key=len)) - - for _ in range(len(test_list)): + test_count = len(test_list) + for i in range(test_count): test_result, testdir, stdout, stderr = job_queue.get_next() test_results.append(test_result) - + done_str = "{}/{} - {}{}{}".format(i + 1, test_count, BOLD[1], test_result.name, BOLD[0]) if test_result.status == "Passed": - logging.debug("\n%s%s%s passed, Duration: %s s" % (BOLD[1], test_result.name, BOLD[0], test_result.time)) + logging.debug("%s passed, Duration: %s s" % (done_str, test_result.time)) elif test_result.status == "Skipped": - logging.debug("\n%s%s%s skipped" % (BOLD[1], test_result.name, BOLD[0])) + logging.debug("%s skipped" % (done_str)) else: - print("\n%s%s%s failed, Duration: %s s\n" % (BOLD[1], test_result.name, BOLD[0], test_result.time)) + print("%s failed, Duration: %s s\n" % (done_str, test_result.time)) print(BOLD[1] + 'stdout:\n' + BOLD[0] + stdout + '\n') print(BOLD[1] + 'stderr:\n' + BOLD[0] + stderr + '\n') if combined_logs_len and os.path.isdir(testdir): @@ -438,13 +440,14 @@ class TestHandler: Trigger the test scripts passed in via the list. """ - def __init__(self, num_tests_parallel, tests_dir, tmpdir, test_list=None, flags=None): + def __init__(self, num_tests_parallel, tests_dir, tmpdir, test_list=None, flags=None, logging_level=logging.DEBUG): assert(num_tests_parallel >= 1) self.num_jobs = num_tests_parallel self.tests_dir = tests_dir self.tmpdir = tmpdir self.test_list = test_list self.flags = flags + self.logging_level = logging_level self.num_running = 0 self.jobs = [] @@ -471,6 +474,7 @@ class TestHandler: log_stderr)) if not self.jobs: raise IndexError('pop from empty list') + dot_count = 0 while True: # Return first proc that finishes time.sleep(.5) @@ -491,9 +495,14 @@ class TestHandler: status = "Failed" self.num_running -= 1 self.jobs.remove(job) - + if self.logging_level == logging.DEBUG: + clearline = '\r' + (' ' * dot_count) + '\r' + print(clearline, end='', flush=True) + dot_count = 0 return TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr - print('.', end='', flush=True) + if self.logging_level == logging.DEBUG: + print('.', end='', flush=True) + dot_count += 1 def kill_and_join(self): """Send SIGKILL to all jobs and block until all have ended.""" diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 8bbff7f7ef..4de3356d79 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -11,6 +11,7 @@ from test_framework.util import ( assert_array_result, assert_equal, assert_fee_amount, + assert_greater_than, assert_raises_rpc_error, connect_nodes_bi, sync_blocks, @@ -92,13 +93,13 @@ class WalletTest(BitcoinTestFramework): assert_equal(txout['value'], 50) # Send 21 BTC from 0 to 2 using sendtoaddress call. - # Locked memory should use at least 32 bytes to sign each transaction + # Locked memory should increase to sign transactions self.log.info("test getmemoryinfo") memory_before = self.nodes[0].getmemoryinfo() self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11) mempool_txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10) memory_after = self.nodes[0].getmemoryinfo() - assert(memory_before['locked']['used'] + 64 <= memory_after['locked']['used']) + assert_greater_than(memory_after['locked']['used'], memory_before['locked']['used']) self.log.info("test gettxout (second part)") # utxo spent in mempool should be visible if you exclude mempool diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 67ee00871d..7d3d9b61e2 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -13,26 +13,22 @@ can be disabled or reordered if needed for debugging. If new test cases are added in the future, they should try to follow the same convention and not make assumptions about execution order. """ - from decimal import Decimal +import io from test_framework.blocktools import add_witness_commitment, create_block, create_coinbase, send_to_witness from test_framework.messages import BIP125_SEQUENCE_NUMBER, CTransaction from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_greater_than, assert_raises_rpc_error, bytes_to_hex_str, connect_nodes_bi, hex_str_to_bytes, sync_mempools -import io - WALLET_PASSPHRASE = "test" WALLET_PASSPHRASE_TIMEOUT = 3600 - class BumpFeeTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [[ - "-deprecatedrpc=addwitnessaddress", "-walletrbf={}".format(i), "-mintxfee=0.00002", ] for i in range(self.num_nodes)] @@ -107,8 +103,7 @@ def test_segwit_bumpfee_succeeds(rbf_node, dest_address): # which spends it, and make sure bumpfee can be called on it. segwit_in = next(u for u in rbf_node.listunspent() if u["amount"] == Decimal("0.001")) - segwit_out = rbf_node.getaddressinfo(rbf_node.getnewaddress()) - rbf_node.addwitnessaddress(segwit_out["address"]) + segwit_out = rbf_node.getaddressinfo(rbf_node.getnewaddress(address_type='p2sh-segwit')) segwitid = send_to_witness( use_p2wsh=False, node=rbf_node, @@ -157,7 +152,7 @@ def test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address): signedtx = peer_node.signrawtransactionwithwallet(signedtx["hex"]) rbfid = rbf_node.sendrawtransaction(signedtx["hex"]) assert_raises_rpc_error(-4, "Transaction contains inputs that don't belong to this wallet", - rbf_node.bumpfee, rbfid) + rbf_node.bumpfee, rbfid) def test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address): @@ -187,11 +182,11 @@ def test_dust_to_fee(rbf_node, dest_address): # (32-byte p2sh-pwpkh output size + 148 p2pkh spend estimate) * 10k(discard_rate) / 1000 = 1800 # P2SH outputs are slightly "over-discarding" due to the IsDust calculation assuming it will # be spent as a P2PKH. - bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 50000-1800}) + bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 50000 - 1800}) full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1) assert_equal(bumped_tx["fee"], Decimal("0.00050000")) assert_equal(len(fulltx["vout"]), 2) - assert_equal(len(full_bumped_tx["vout"]), 1) #change output is eliminated + assert_equal(len(full_bumped_tx["vout"]), 1) # change output is eliminated def test_settxfee(rbf_node, dest_address): @@ -222,7 +217,7 @@ def test_rebumping_not_replaceable(rbf_node, dest_address): rbfid = spend_one_input(rbf_node, dest_address) bumped = rbf_node.bumpfee(rbfid, {"totalFee": 10000, "replaceable": False}) assert_raises_rpc_error(-4, "Transaction is not BIP 125 replaceable", rbf_node.bumpfee, bumped["txid"], - {"totalFee": 20000}) + {"totalFee": 20000}) def test_unconfirmed_not_spendable(rbf_node, rbf_node_address): @@ -276,7 +271,7 @@ def test_locked_wallet_fails(rbf_node, dest_address): rbfid = spend_one_input(rbf_node, dest_address) rbf_node.walletlock() assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first.", - rbf_node.bumpfee, rbfid) + rbf_node.bumpfee, rbfid) def spend_one_input(node, dest_address): diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py index b1db1e4ab9..20cb816ee8 100755 --- a/test/functional/wallet_dump.py +++ b/test/functional/wallet_dump.py @@ -3,7 +3,6 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the dumpwallet RPC.""" - import os from test_framework.test_framework import BitcoinTestFramework @@ -19,11 +18,12 @@ def read_dump(file_name, addrs, script_addrs, hd_master_addr_old): Also check that the old hd_master is inactive """ with open(file_name, encoding='utf8') as inputfile: - found_addr = 0 + found_legacy_addr = 0 + found_p2sh_segwit_addr = 0 + found_bech32_addr = 0 found_script_addr = 0 found_addr_chg = 0 found_addr_rsv = 0 - witness_addr_ret = None hd_master_addr_ret = None for line in inputfile: # only read non comment lines @@ -60,14 +60,14 @@ def read_dump(file_name, addrs, script_addrs, hd_master_addr_old): # count key types for addrObj in addrs: if addrObj['address'] == addr.split(",")[0] and addrObj['hdkeypath'] == keypath and keytype == "label=": - # a labeled entry in the wallet should contain both a native address - # and the p2sh-p2wpkh address that was added at wallet setup - if len(addr.split(",")) == 2: - addr_list = addr.split(",") - # the entry should be of the first key in the wallet - assert_equal(addrs[0]['address'], addr_list[0]) - witness_addr_ret = addr_list[1] - found_addr += 1 + if addr.startswith('m') or addr.startswith('n'): + # P2PKH address + found_legacy_addr += 1 + elif addr.startswith('2'): + # P2SH-segwit address + found_p2sh_segwit_addr += 1 + elif addr.startswith('bcrt1'): + found_bech32_addr += 1 break elif keytype == "change=1": found_addr_chg += 1 @@ -82,13 +82,13 @@ def read_dump(file_name, addrs, script_addrs, hd_master_addr_old): found_script_addr += 1 break - return found_addr, found_script_addr, found_addr_chg, found_addr_rsv, hd_master_addr_ret, witness_addr_ret + return found_legacy_addr, found_p2sh_segwit_addr, found_bech32_addr, found_script_addr, found_addr_chg, found_addr_rsv, hd_master_addr_ret class WalletDumpTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [["-keypool=90", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"]] + self.extra_args = [["-keypool=90", "-addresstype=legacy"]] self.rpc_timeout = 120 def skip_test_if_missing_module(self): @@ -102,49 +102,54 @@ class WalletDumpTest(BitcoinTestFramework): wallet_unenc_dump = os.path.join(self.nodes[0].datadir, "wallet.unencrypted.dump") wallet_enc_dump = os.path.join(self.nodes[0].datadir, "wallet.encrypted.dump") - # generate 20 addresses to compare against the dump - # but since we add a p2sh-p2wpkh address for the first pubkey in the - # wallet, we will expect 21 addresses in the dump - test_addr_count = 20 + # generate 30 addresses to compare against the dump + # - 10 legacy P2PKH + # - 10 P2SH-segwit + # - 10 bech32 + test_addr_count = 10 addrs = [] - for i in range(0,test_addr_count): - addr = self.nodes[0].getnewaddress() - vaddr= self.nodes[0].getaddressinfo(addr) #required to get hd keypath - addrs.append(vaddr) - # Should be a no-op: - self.nodes[0].keypoolrefill() + for address_type in ['legacy', 'p2sh-segwit', 'bech32']: + for i in range(0, test_addr_count): + addr = self.nodes[0].getnewaddress(address_type=address_type) + vaddr = self.nodes[0].getaddressinfo(addr) # required to get hd keypath + addrs.append(vaddr) - # Test scripts dump by adding a P2SH witness and a 1-of-1 multisig address - witness_addr = self.nodes[0].addwitnessaddress(addrs[0]["address"], True) + # Test scripts dump by adding a 1-of-1 multisig address multisig_addr = self.nodes[0].addmultisigaddress(1, [addrs[1]["address"]])["address"] - script_addrs = [witness_addr, multisig_addr] + + # Refill the keypool. getnewaddress() refills the keypool *before* taking a key from + # the keypool, so the final call to getnewaddress leaves the keypool with one key below + # its capacity + self.nodes[0].keypoolrefill() # dump unencrypted wallet result = self.nodes[0].dumpwallet(wallet_unenc_dump) assert_equal(result['filename'], wallet_unenc_dump) - found_addr, found_script_addr, found_addr_chg, found_addr_rsv, hd_master_addr_unenc, witness_addr_ret = \ - read_dump(wallet_unenc_dump, addrs, script_addrs, None) - assert_equal(found_addr, test_addr_count) # all keys must be in the dump - assert_equal(found_script_addr, 2) # all scripts must be in the dump + found_legacy_addr, found_p2sh_segwit_addr, found_bech32_addr, found_script_addr, found_addr_chg, found_addr_rsv, hd_master_addr_unenc = \ + read_dump(wallet_unenc_dump, addrs, [multisig_addr], None) + assert_equal(found_legacy_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_p2sh_segwit_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_bech32_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_script_addr, 1) # all scripts must be in the dump assert_equal(found_addr_chg, 0) # 0 blocks where mined assert_equal(found_addr_rsv, 90 * 2) # 90 keys plus 100% internal keys - assert_equal(witness_addr_ret, witness_addr) # p2sh-p2wsh address added to the first key - #encrypt wallet, restart, unlock and dump + # encrypt wallet, restart, unlock and dump self.nodes[0].encryptwallet('test') self.nodes[0].walletpassphrase('test', 10) # Should be a no-op: self.nodes[0].keypoolrefill() self.nodes[0].dumpwallet(wallet_enc_dump) - found_addr, found_script_addr, found_addr_chg, found_addr_rsv, _, witness_addr_ret = \ - read_dump(wallet_enc_dump, addrs, script_addrs, hd_master_addr_unenc) - assert_equal(found_addr, test_addr_count) - assert_equal(found_script_addr, 2) + found_legacy_addr, found_p2sh_segwit_addr, found_bech32_addr, found_script_addr, found_addr_chg, found_addr_rsv, _ = \ + read_dump(wallet_enc_dump, addrs, [multisig_addr], hd_master_addr_unenc) + assert_equal(found_legacy_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_p2sh_segwit_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_bech32_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_script_addr, 1) assert_equal(found_addr_chg, 90 * 2) # old reserve keys are marked as change now assert_equal(found_addr_rsv, 90 * 2) - assert_equal(witness_addr_ret, witness_addr) # Overwriting should fail assert_raises_rpc_error(-8, "already exists", lambda: self.nodes[0].dumpwallet(wallet_enc_dump)) @@ -155,13 +160,13 @@ class WalletDumpTest(BitcoinTestFramework): # Make sure the address is not IsMine before import result = self.nodes[0].getaddressinfo(multisig_addr) - assert(result['ismine'] == False) + assert not result['ismine'] self.nodes[0].importwallet(wallet_unenc_dump) # Now check IsMine is true result = self.nodes[0].getaddressinfo(multisig_addr) - assert(result['ismine'] == True) + assert result['ismine'] if __name__ == '__main__': WalletDumpTest().main() diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py index 51afa0cb1a..ceb9709712 100755 --- a/test/functional/wallet_keypool.py +++ b/test/functional/wallet_keypool.py @@ -73,11 +73,10 @@ class KeyPoolTest(BitcoinTestFramework): time.sleep(1.1) assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0) - # drain them by mining - nodes[0].generate(1) - nodes[0].generate(1) - nodes[0].generate(1) - assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].generate, 1) + # drain the keypool + for _ in range(3): + nodes[0].getnewaddress() + assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getnewaddress) nodes[0].walletpassphrase('test', 100) nodes[0].keypoolrefill(100) diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py index 8d7c77bb96..b71dae9f40 100755 --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -29,8 +29,8 @@ class WalletLabelsTest(BitcoinTestFramework): # Note each time we call generate, all generated coins go into # the same address, so we call twice to get two addresses w/50 each - node.generate(1) - node.generate(101) + node.generatetoaddress(nblocks=1, address=node.getnewaddress(label='coinbase')) + node.generatetoaddress(nblocks=101, address=node.getnewaddress(label='coinbase')) assert_equal(node.getbalance(), 100) # there should be 2 address groups @@ -42,8 +42,9 @@ class WalletLabelsTest(BitcoinTestFramework): linked_addresses = set() for address_group in address_groups: assert_equal(len(address_group), 1) - assert_equal(len(address_group[0]), 2) + assert_equal(len(address_group[0]), 3) assert_equal(address_group[0][1], 50) + assert_equal(address_group[0][2], 'coinbase') linked_addresses.add(address_group[0][0]) # send 50 from each address to a third address not in this wallet @@ -77,7 +78,7 @@ class WalletLabelsTest(BitcoinTestFramework): label.verify(node) # Check all labels are returned by listlabels. - assert_equal(node.listlabels(), [label.name for label in labels]) + assert_equal(node.listlabels(), sorted(['coinbase'] + [label.name for label in labels])) # Send a transaction to each label. for label in labels: diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py index 3485c4470f..9e8667c600 100755 --- a/test/functional/wallet_listreceivedby.py +++ b/test/functional/wallet_listreceivedby.py @@ -68,6 +68,10 @@ class ReceivedByTest(BitcoinTestFramework): res = self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True, address_filter=addr) assert_array_result(res, {"address": addr}, expected) assert_equal(len(res), 1) + # Test for regression on CLI calls with address string (#14173) + cli_res = self.nodes[1].cli.listreceivedbyaddress(0, True, True, addr) + assert_array_result(cli_res, {"address": addr}, expected) + assert_equal(len(cli_res), 1) # Error on invalid address assert_raises_rpc_error(-4, "address_filter parameter was invalid", self.nodes[1].listreceivedbyaddress, minconf=0, include_empty=True, include_watchonly=True, address_filter="bamboozling") # Another address receive money diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 189bc2d50e..6a95ca4321 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -8,6 +8,7 @@ Verify that a bitcoind node can load multiple wallet files """ import os import shutil +import time from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch @@ -38,15 +39,16 @@ class MultiWalletTest(BitcoinTestFramework): return wallet_dir(name, "wallet.dat") return wallet_dir(name) + assert_equal(self.nodes[0].listwalletdir(), { 'wallets': [{ 'name': '' }] }) + # check wallet.dat is created self.stop_nodes() assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True) # create symlink to verify wallet directory path can be referenced # through symlink - if os.name != 'nt': - os.mkdir(wallet_dir('w7')) - os.symlink('w7', wallet_dir('w7_symlink')) + os.mkdir(wallet_dir('w7')) + os.symlink('w7', wallet_dir('w7_symlink')) # rename wallet.dat to make sure plain wallet file paths (as opposed to # directory paths) can be loaded @@ -67,10 +69,10 @@ class MultiWalletTest(BitcoinTestFramework): # w8 - to verify existing wallet file is loaded correctly # '' - to verify default wallet file is created correctly wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', ''] - if os.name == 'nt': - wallet_names.remove('w7_symlink') extra_args = ['-wallet={}'.format(n) for n in wallet_names] self.start_node(0, extra_args) + assert_equal(set(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), set(['', 'w3', 'w2', os.path.join('sub', 'w5'), 'w7', 'w7', 'w1', 'w8', 'w'])) + assert_equal(set(node.listwallets()), set(wallet_names)) # check that all requested wallets were created @@ -95,9 +97,8 @@ class MultiWalletTest(BitcoinTestFramework): self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) # should not initialize if wallet file is a symlink - if os.name != 'nt': - os.symlink('w8', wallet_dir('w8_symlink')) - self.nodes[0].assert_start_raises_init_error(['-wallet=w8_symlink'], 'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX) + os.symlink('w8', wallet_dir('w8_symlink')) + self.nodes[0].assert_start_raises_init_error(['-wallet=w8_symlink'], 'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX) # should not initialize if the specified walletdir does not exist self.nodes[0].assert_start_raises_init_error(['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist') @@ -125,7 +126,7 @@ class MultiWalletTest(BitcoinTestFramework): self.start_node(0, ['-wallet=w4', '-wallet=w5']) assert_equal(set(node.listwallets()), {"w4", "w5"}) w5 = wallet("w5") - w5.generate(1) + node.generatetoaddress(nblocks=1, address=w5.getnewaddress()) # now if wallets/ exists again, but the rootdir is specified as the walletdir, w4 and w5 should still be loaded os.rename(wallet_dir2, wallet_dir()) @@ -143,11 +144,13 @@ class MultiWalletTest(BitcoinTestFramework): self.restart_node(0, extra_args) + assert_equal(set(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), set(['', 'w3', 'w2', os.path.join('sub', 'w5'), 'w7', 'w7', 'w8_copy', 'w1', 'w8', 'w'])) + wallets = [wallet(w) for w in wallet_names] wallet_bad = wallet("bad") # check wallet names and balances - wallets[0].generate(1) + node.generatetoaddress(nblocks=1, address=wallets[0].getnewaddress()) for wallet_name, wallet in zip(wallet_names, wallets): info = wallet.getwalletinfo() assert_equal(info['immature_balance'], 50 if wallet is wallets[0] else 0) @@ -160,7 +163,7 @@ class MultiWalletTest(BitcoinTestFramework): assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo) w1, w2, w3, w4, *_ = wallets - w1.generate(101) + node.generatetoaddress(nblocks=101, address=w1.getnewaddress()) assert_equal(w1.getbalance(), 100) assert_equal(w2.getbalance(), 0) assert_equal(w3.getbalance(), 0) @@ -169,7 +172,7 @@ class MultiWalletTest(BitcoinTestFramework): w1.sendtoaddress(w2.getnewaddress(), 1) w1.sendtoaddress(w3.getnewaddress(), 2) w1.sendtoaddress(w4.getnewaddress(), 3) - w1.generate(1) + node.generatetoaddress(nblocks=1, address=w1.getnewaddress()) assert_equal(w2.getbalance(), 1) assert_equal(w3.getbalance(), 2) assert_equal(w4.getbalance(), 3) @@ -223,9 +226,12 @@ class MultiWalletTest(BitcoinTestFramework): # Fail to load if one wallet is a copy of another assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') + # Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304 + assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') + + # Fail to load if wallet file is a symlink - if os.name != 'nt': - assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink') + assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink') # Fail to load if a directory is specified that doesn't contain a wallet os.mkdir(wallet_dir('empty_wallet_dir')) @@ -267,7 +273,11 @@ class MultiWalletTest(BitcoinTestFramework): assert 'w1' not in self.nodes[0].listwallets() # Successfully unload the wallet referenced by the request endpoint + # Also ensure unload works during walletpassphrase timeout + w2.encryptwallet('test') + w2.walletpassphrase('test', 1) w2.unloadwallet() + time.sleep(1.1) assert 'w2' not in self.nodes[0].listwallets() # Successfully unload all wallets @@ -281,6 +291,8 @@ class MultiWalletTest(BitcoinTestFramework): assert_equal(self.nodes[0].listwallets(), ['w1']) assert_equal(w1.getwalletinfo()['walletname'], 'w1') + assert_equal(set(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), set(['', 'w3', 'w2', os.path.join('sub', 'w5'), 'w7', 'w9', 'w7', 'w8_copy', 'w1', 'w8', 'w'])) + # Test backing up and restoring wallets self.log.info("Test wallet backup") self.restart_node(0, ['-nowallet']) |