diff options
40 files changed, 2128 insertions, 523 deletions
diff --git a/doc/REST-interface.md b/doc/REST-interface.md index 02a665008b..c96871ab5f 100644 --- a/doc/REST-interface.md +++ b/doc/REST-interface.md @@ -101,6 +101,7 @@ $ curl localhost:18332/rest/getutxos/checkmempool/b2cdfd7b89def827ff8af7cd9bff76 Returns various information about the TX mempool. Only supports JSON as output format. +* loaded : (boolean) if the mempool is fully loaded * size : (numeric) the number of transactions in the TX mempool * bytes : (numeric) size of the TX mempool in bytes * usage : (numeric) total TX mempool memory usage diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am index 9b36319e64..edbc0911a1 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -15,3 +15,9 @@ endif if BUILD_BITCOIN_TX dist_man1_MANS+=bitcoin-tx.1 endif + +if ENABLE_WALLET +if BUILD_BITCOIN_WALLET + dist_man1_MANS+=bitcoin-wallet.1 +endif +endif diff --git a/doc/release-notes-15730.md b/doc/release-notes-15730.md new file mode 100644 index 0000000000..7a4a60b1ee --- /dev/null +++ b/doc/release-notes-15730.md @@ -0,0 +1,5 @@ +RPC changes +----------- +The RPC `getwalletinfo` response now includes the `scanning` key with an object +if there is a scanning in progress or `false` otherwise. Currently the object +has the scanning duration and progress. diff --git a/doc/release-notes.md b/doc/release-notes.md index 0de0f563b1..834c9b36dc 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -61,6 +61,15 @@ platform. Notable changes =============== +New RPCs +-------- + +- `getbalances` returns an object with all balances (`mine`, + `untrusted_pending` and `immature`). Please refer to the RPC help of + `getbalances` for details. The new RPC is intended to replace + `getunconfirmedbalance` and the balance fields in `getwalletinfo`, as well as + `getbalance`. The old calls may be removed in a future version. + Updated RPCs ------------ diff --git a/doc/release-notes/release-notes-0.18.0.md b/doc/release-notes/release-notes-0.18.0.md new file mode 100644 index 0000000000..3ca7d52243 --- /dev/null +++ b/doc/release-notes/release-notes-0.18.0.md @@ -0,0 +1,1224 @@ +Bitcoin Core version 0.18.0 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.18.0/> + +This is a new major version release, including new features, various bug +fixes and performance improvements, as well as updated translations. + +Please report bugs using the issue tracker at GitHub: + + <https://github.com/bitcoin/bitcoin/issues> + +To receive security and update notifications, please subscribe to: + + <https://bitcoincore.org/en/list/announcements/join/> + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has +completely shut down (which might take a few minutes for older +versions), then run the installer (on Windows) or just copy over +`/Applications/Bitcoin-Qt` (on Mac) or `bitcoind`/`bitcoin-qt` (on +Linux). + +The first time you run version 0.15.0 or newer, your chainstate database +will be converted to a new format, which will take anywhere from a few +minutes to half an hour, depending on the speed of your machine. + +Note that the block database format also changed in version 0.8.0 and +there is no automatic upgrade code from before version 0.8 to version +0.15.0 or later. Upgrading directly from 0.7.x and earlier without +redownloading the blockchain is not supported. However, as usual, old +wallet versions are still supported. + +Compatibility +============== + +Bitcoin Core is supported and extensively tested on operating systems +using the Linux kernel, macOS 10.10+, and Windows 7 and newer. It is not +recommended to use Bitcoin Core on unsupported systems. + +Bitcoin Core should also work on most other Unix-like systems but is not +as frequently tested on them. + +From 0.17.0 onwards, macOS <10.10 is no longer supported. 0.17.0 is +built using Qt 5.9.x, which doesn't support versions of macOS older than +10.10. Additionally, Bitcoin Core does not yet change appearance when +macOS "dark mode" is activated. + +In addition to previously-supported CPU platforms, this release's +pre-compiled distribution also provides binaries for the RISC-V +platform. + +If you are using the `systemd` unit configuration file located at +`contrib/init/bitcoind.service`, it has been changed to use +`/var/lib/bitcoind` as the data directory instead of +`~bitcoin/.bitcoin`. When switching over to the new configuration file, +please make sure that the filesystem on which `/var/lib/bitcoind` will +exist has enough space (check using `df -h /var/lib/bitcoind`), and +optionally copy over your existing data directory. See the [systemd init +file section](#systemd-init-file) for more details. + +Known issues +============ + +Wallet GUI +---------- + +For advanced users who have both (1) enabled coin control features, and +(2) are using multiple wallets loaded at the same time: The coin control +input selection dialog can erroneously retain wrong-wallet state when +switching wallets using the dropdown menu. For now, it is recommended +not to use coin control features with multiple wallets loaded. + +Notable changes +=============== + +Mining +------ + +- Calls to `getblocktemplate` will fail if the segwit rule is not + specified. Calling `getblocktemplate` without segwit specified is + almost certainly a misconfiguration since doing so results in lower + rewards for the miner. Failed calls will produce an error message + describing how to enable the segwit rule. + +Configuration option changes +---------------------------- + +- A warning is printed if an unrecognized section name is used in the + configuration file. Recognized sections are `[test]`, `[main]`, and + `[regtest]`. + +- Four new options are available for configuring the maximum number of + messages that ZMQ will queue in memory (the "high water mark") before + dropping additional messages. The default value is 1,000, the same as + was used for previous releases. See the [ZMQ + documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/zmq.md#usage) + for details. + +- The `rpcallowip` option can no longer be used to automatically listen + on all network interfaces. Instead, the `rpcbind` parameter must be + used to specify the IP addresses to listen on. Listening for RPC + commands over a public network connection is insecure and should be + disabled, so a warning is now printed if a user selects such a + configuration. If you need to expose RPC in order to use a tool like + Docker, ensure you only bind RPC to your localhost, e.g. `docker run + [...] -p 127.0.0.1:8332:8332` (this is an extra `:8332` over the + normal Docker port specification). + +- The `rpcpassword` option now causes a startup error if the password + set in the configuration file contains a hash character (#), as it's + ambiguous whether the hash character is meant for the password or as a + comment. + +- The `whitelistforcerelay` option is used to relay transactions from + whitelisted peers even when not accepted to the mempool. This option + now defaults to being off, so that changes in policy and + disconnect/ban behavior will not cause a node that is whitelisting + another to be dropped by peers. Users can still explicitly enable + this behavior with the command line option (and may want to consider + [contacting](https://bitcoincore.org/en/contact/) the Bitcoin Core + project to let us know about their use-case, as this feature could be + deprecated in the future). + +systemd init file +----------------- + +The systemd init file (`contrib/init/bitcoind.service`) has been changed +to use `/var/lib/bitcoind` as the data directory instead of +`~bitcoin/.bitcoin`. This change makes Bitcoin Core more consistent with +other services, and makes the systemd init config more consistent with +existing Upstart and OpenRC configs. + +The configuration, PID, and data directories are now completely managed +by systemd, which will take care of their creation, permissions, etc. +See [`systemd.exec(5)`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RuntimeDirectory=) +for more details. + +When using the provided init files under `contrib/init`, overriding the +`datadir` option in `/etc/bitcoin/bitcoin.conf` will have no effect. +This is because the command line arguments specified in the init files +take precedence over the options specified in +`/etc/bitcoin/bitcoin.conf`. + + +Documentation +------------- + +- A new short [document](https://github.com/bitcoin/bitcoin/blob/master/doc/JSON-RPC-interface.md) + about the JSON-RPC interface describes cases where the results of an + RPC might contain inconsistencies between data sourced from different + subsystems, such as wallet state and mempool state. A note is added + to the [REST interface documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md) + indicating that the same rules apply. + +- Further information is added to the [JSON-RPC + documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/JSON-RPC-interface.md) + about how to secure this interface. + +- A new [document](https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md) + about the `bitcoin.conf` file describes how to use it to configure + Bitcoin Core. + +- A new document introduces Bitcoin Core's BIP174 [Partially-Signed + Bitcoin Transactions + (PSBT)](https://github.com/bitcoin/bitcoin/blob/master/doc/psbt.md) + interface, which is used to allow multiple programs to collaboratively + work to create, sign, and broadcast new transactions. This is useful + for offline (cold storage) wallets, multisig wallets, coinjoin + implementations, and many other cases where two or more programs need + to interact to generate a complete transaction. + +- The [output script + descriptor](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) + documentation has been updated with information about new features in + this still-developing language for describing the output scripts that + a wallet or other program wants to receive notifications for, such as + which addresses it wants to know received payments. The language is + currently used in multiple new and updated RPCs described in these + release notes and is expected to be adapted to other RPCs and to the + underlying wallet structure. + +Build system changes +-------------------- + +- A new `--disable-bip70` option may be passed to `./configure` to + prevent Bitcoin-Qt from being built with support for the BIP70 payment + protocol or from linking libssl. As the payment protocol has exposed + Bitcoin Core to libssl vulnerabilities in the past, builders who don't + need BIP70 support are encouraged to use this option to reduce their + exposure to future vulnerabilities. + +- The minimum required version of Qt (when building the GUI) has been + increased from 5.2 to 5.5.1 (the [depends + system](https://github.com/bitcoin/bitcoin/blob/master/depends/README.md) + provides 5.9.7) + +New RPCs +-------- + +- `getnodeaddresses` returns peer addresses known to this node. It may + be used to find nodes to connect to without using a DNS seeder. + +- `listwalletdir` returns a list of wallets in the wallet directory + (either the default wallet directory or the directory configured by + the `-walletdir` parameter). + +- `getrpcinfo` returns runtime details of the RPC server. At the moment, + it returns an array of the currently active commands and how long + they've been running. + +- `deriveaddresses` returns one or more addresses corresponding to an + [output descriptor](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md). + +- `getdescriptorinfo` accepts a descriptor and returns information about + it, including its computed checksum. + +- `joinpsbts` merges multiple distinct PSBTs into a single PSBT. The + multiple PSBTs must have different inputs. The resulting PSBT will + contain every input and output from all of the PSBTs. Any signatures + provided in any of the PSBTs will be dropped. + +- `analyzepsbt` examines a PSBT and provides information about what + the PSBT contains and the next steps that need to be taken in order + to complete the transaction. For each input of a PSBT, `analyzepsbt` + provides information about what information is missing for that + input, including whether a UTXO needs to be provided, what pubkeys + still need to be provided, which scripts need to be provided, and + what signatures are still needed. Every input will also list which + role is needed to complete that input, and `analyzepsbt` will also + list the next role in general needed to complete the PSBT. + `analyzepsbt` will also provide the estimated fee rate and estimated + virtual size of the completed transaction if it has enough + information to do so. + +- `utxoupdatepsbt` searches the set of Unspent Transaction Outputs + (UTXOs) to find the outputs being spent by the partial transaction. + PSBTs need to have the UTXOs being spent to be provided because + the signing algorithm requires information from the UTXO being spent. + For segwit inputs, only the UTXO itself is necessary. For + non-segwit outputs, the entire previous transaction is needed so + that signers can be sure that they are signing the correct thing. + Unfortunately, because the UTXO set only contains UTXOs and not full + transactions, `utxoupdatepsbt` will only add the UTXO for segwit + inputs. + +Updated RPCs +------------ + +Note: some low-level RPC changes mainly useful for testing are described +in the Low-level Changes section below. + +- `getpeerinfo` now returns an additional `minfeefilter` field set to + the peer's BIP133 fee filter. You can use this to detect that you + have peers that are willing to accept transactions below the default + minimum relay fee. + +- The mempool RPCs, such as `getrawmempool` with `verbose=true`, now + return an additional "bip125-replaceable" value indicating whether the + transaction (or its unconfirmed ancestors) opts-in to asking nodes and + miners to replace it with a higher-feerate transaction spending any of + the same inputs. + +- `settxfee` previously silently ignored attempts to set the fee below + the allowed minimums. It now prints a warning. The special value of + "0" may still be used to request the minimum value. + +- `getaddressinfo` now provides an `ischange` field indicating whether + the wallet used the address in a change output. + +- `importmulti` has been updated to support P2WSH, P2WPKH, P2SH-P2WPKH, + and P2SH-P2WSH. Requests for P2WSH and P2SH-P2WSH accept an additional + `witnessscript` parameter. + +- `importmulti` now returns an additional `warnings` field for each + request with an array of strings explaining when fields are being + ignored or are inconsistent, if there are any. + +- `getaddressinfo` now returns an additional `solvable` boolean field + when Bitcoin Core knows enough about the address's scriptPubKey, + optional redeemScript, and optional witnessScript in order for the + wallet to be able to generate an unsigned input spending funds sent to + that address. + +- The `getaddressinfo`, `listunspent`, and `scantxoutset` RPCs now + return an additional `desc` field that contains an output descriptor + containing all key paths and signing information for the address + (except for the private key). The `desc` field is only returned for + `getaddressinfo` and `listunspent` when the address is solvable. + +- `importprivkey` will preserve previously-set labels for addresses or + public keys corresponding to the private key being imported. For + example, if you imported a watch-only address with the label "cold + wallet" in earlier releases of Bitcoin Core, subsequently importing + the private key would default to resetting the address's label to the + default empty-string label (""). In this release, the previous label + of "cold wallet" will be retained. If you optionally specify any + label besides the default when calling `importprivkey`, the new label + will be applied to the address. + +- See the [Mining](#mining) section for changes to `getblocktemplate`. + +- `getmininginfo` now omits `currentblockweight` and `currentblocktx` + when a block was never assembled via RPC on this node. + +- The `getrawtransaction` RPC & REST endpoints no longer check the + unspent UTXO set for a transaction. The remaining behaviors are as + follows: 1. If a blockhash is provided, check the corresponding block. + 2. If no blockhash is provided, check the mempool. 3. If no blockhash + is provided but txindex is enabled, also check txindex. + +- `unloadwallet` is now synchronous, meaning it will not return until + the wallet is fully unloaded. + +- `importmulti` now supports importing of addresses from descriptors. A + "desc" parameter can be provided instead of the "scriptPubKey" in a + request, as well as an optional range for ranged descriptors to + specify the start and end of the range to import. Descriptors with key + origin information imported through `importmulti` will have their key + origin information stored in the wallet for use with creating PSBTs. + More information about descriptors can be found + [here](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md). + +- `listunspent` has been modified so that it also returns + `witnessScript`, the witness script in the case of a P2WSH or + P2SH-P2WSH output. + +- `createwallet` now has an optional `blank` argument that can be used + to create a blank wallet. Blank wallets do not have any keys or HD + seed. They cannot be opened in software older than 0.18. Once a blank + wallet has a HD seed set (by using `sethdseed`) or private keys, + scripts, addresses, and other watch only things have been imported, + the wallet is no longer blank and can be opened in 0.17.x. Encrypting + a blank wallet will also set a HD seed for it. + +Deprecated or removed RPCs +-------------------------- + +- `signrawtransaction` is removed after being deprecated and hidden + behind a special configuration option in version 0.17.0. + +- The 'account' API is removed after being deprecated in v0.17. The + 'label' API was introduced in v0.17 as a replacement for accounts. + See the [release notes from + v0.17](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.17.0.md#label-and-account-apis-for-wallet) + for a full description of the changes from the 'account' API to the + 'label' API. + +- `addwitnessaddress` is removed after being deprecated in version + 0.16.0. + +- `generate` is deprecated and will be fully removed in a subsequent + major version. This RPC is only used for testing, but its + implementation reached across multiple subsystems (wallet and mining), + so it is being deprecated to simplify the wallet-node interface. + Projects that are using `generate` for testing purposes should + transition to using the `generatetoaddress` RPC, which does not + require or use the wallet component. Calling `generatetoaddress` with + an address returned by the `getnewaddress` RPC gives the same + functionality as the old `generate` RPC. To continue using `generate` + in this version, restart bitcoind with the `-deprecatedrpc=generate` + configuration option. + +- Be reminded that parts of the `validateaddress` command have been + deprecated and moved to `getaddressinfo`. The following deprecated + fields have moved to `getaddressinfo`: `ismine`, `iswatchonly`, + `script`, `hex`, `pubkeys`, `sigsrequired`, `pubkey`, `embedded`, + `iscompressed`, `label`, `timestamp`, `hdkeypath`, `hdmasterkeyid`. + +- The `addresses` field has been removed from the `validateaddress` + and `getaddressinfo` RPC methods. This field was confusing since + it referred to public keys using their P2PKH address. Clients + should use the `embedded.address` field for P2SH or P2WSH wrapped + addresses, and `pubkeys` for inspecting multisig participants. + +REST changes +------------ + +- A new `/rest/blockhashbyheight/` endpoint is added for fetching the + hash of the block in the current best blockchain based on its height + (how many blocks it is after the Genesis Block). + +Graphical User Interface (GUI) +------------------------------ + +- A new Window menu is added alongside the existing File, Settings, and + Help menus. Several items from the other menus that opened new + windows have been moved to this new Window menu. + +- In the Send tab, the checkbox for "pay only the required fee" has been + removed. Instead, the user can simply decrease the value in the + Custom Feerate field all the way down to the node's configured minimum + relay fee. + +- In the Overview tab, the watch-only balance will be the only balance + shown if the wallet was created using the `createwallet` RPC and the + `disable_private_keys` parameter was set to true. + +- The launch-on-startup option is no longer available on macOS if + compiled with macosx min version greater than 10.11 (use + CXXFLAGS="-mmacosx-version-min=10.11" + CFLAGS="-mmacosx-version-min=10.11" for setting the deployment sdk + version) + +Tools +----- + +- A new `bitcoin-wallet` tool is now distributed alongside Bitcoin + Core's other executables. Without needing to use any RPCs, this tool + can currently create a new wallet file or display some basic + information about an existing wallet, such as whether the wallet is + encrypted, whether it uses an HD seed, how many transactions it + contains, and how many address book entries it has. + +Planned changes +=============== + +This section describes planned changes to Bitcoin Core that may affect +other Bitcoin software and services. + +- Since version 0.16.0, Bitcoin Core’s built-in wallet has defaulted to + generating P2SH-wrapped segwit addresses when users want to receive + payments. These addresses are backwards compatible with all + widely-used software. Starting with Bitcoin Core 0.20 (expected about + a year after 0.18), Bitcoin Core will default to native segwit + addresses (bech32) that provide additional fee savings and other + benefits. Currently, many wallets and services already support sending + to bech32 addresses, and if the Bitcoin Core project sees enough + additional adoption, it will instead default to bech32 receiving + addresses in Bitcoin Core 0.19 (approximately November 2019). + P2SH-wrapped segwit addresses will continue to be provided if the user + requests them in the GUI or by RPC, and anyone who doesn’t want the + update will be able to configure their default address type. + (Similarly, pioneering users who want to change their default now may + set the `addresstype=bech32` configuration option in any Bitcoin Core + release from 0.16.0 up.) + +Deprecated P2P messages +----------------------- + +- BIP 61 reject messages are now deprecated. Reject messages have no use + case on the P2P network and are only logged for debugging by most + network nodes. Furthermore, they increase bandwidth and can be harmful + for privacy and security. It has been possible to disable BIP 61 + messages since v0.17 with the `-enablebip61=0` option. BIP 61 messages + will be disabled by default in a future version, before being removed + entirely. + +Low-level changes +================= + +This section describes RPC changes mainly useful for testing, mostly not +relevant in production. The changes are mentioned for completeness. + +RPC +--- + +- The `submitblock` RPC previously returned the reason a rejected block + was invalid the first time it processed that block, but returned a + generic "duplicate" rejection message on subsequent occasions it + processed the same block. It now always returns the fundamental + reason for rejecting an invalid block and only returns "duplicate" for + valid blocks it has already accepted. + +- A new `submitheader` RPC allows submitting block headers independently + from their block. This is likely only useful for testing. + +- The `signrawtransactionwithkey` and `signrawtransactionwithwallet` + RPCs have been modified so that they also optionally accept a + `witnessScript`, the witness script in the case of a P2WSH or + P2SH-P2WSH output. This is compatible with the change to + `listunspent`. + +- For the `walletprocesspsbt` and `walletcreatefundedpsbt` RPCs, if the + `bip32derivs` parameter is set to true but the key metadata for a + public key has not been updated yet, then that key will have a + derivation path as if it were just an independent key (i.e. no + derivation path and its master fingerprint is itself). + +Configuration +------------- + +- The `-usehd` configuration option was removed in version 0.16. From + that version onwards, all new wallets created are hierarchical + deterministic wallets. This release makes specifying `-usehd` an + invalid configuration option. + +Network +------- + +- This release allows peers that your node automatically disconnected + for misbehavior (e.g. sending invalid data) to reconnect to your node + if you have unused incoming connection slots. If your slots fill up, + a misbehaving node will be disconnected to make room for nodes without + a history of problems (unless the misbehaving node helps your node in + some other way, such as by connecting to a part of the Internet from + which you don't have many other peers). Previously, Bitcoin Core + banned the IP addresses of misbehaving peers for a period of time + (default of 1 day); this was easily circumvented by attackers with + multiple IP addresses. If you manually ban a peer, such as by using + the `setban` RPC, all connections from that peer will still be + rejected. + +Wallet +------- + +- The key metadata will need to be upgraded the first time that the HD + seed is available. For unencrypted wallets this will occur on wallet + loading. For encrypted wallets this will occur the first time the + wallet is unlocked. + +- Newly encrypted wallets will no longer require restarting the + software. Instead such wallets will be completely unloaded and + reloaded to achieve the same effect. + +- A sub-project of Bitcoin Core now provides Hardware Wallet Interaction + (HWI) scripts that allow command-line users to use several popular + hardware key management devices with Bitcoin Core. See their [project + page](https://github.com/bitcoin-core/HWI#readme) for details. + +Security +-------- + +- This release changes the Random Number Generator (RNG) used from + OpenSSL to Bitcoin Core's own implementation, although entropy + gathered by Bitcoin Core is fed out to OpenSSL and then read back in + when the program needs strong randomness. This moves Bitcoin Core a + little closer to no longer needing to depend on OpenSSL, a dependency + that has caused security issues in the past. The new implementation + gathers entropy from multiple sources, including from hardware + supporting the rdseed CPU instruction. + +Changes for particular platforms +-------------------------------- + +- On macOS, Bitcoin Core now opts out of application CPU throttling + ("app nap") during initial blockchain download, when catching up from + over 100 blocks behind the current chain tip, or when reindexing chain + data. This helps prevent these operations from taking an excessively + long time because the operating system is attempting to conserve + power. + +0.18.0 change log +================= + +### Consensus +- #14247 Fix crash bug with duplicate inputs within a transaction (TheBlueMatt) + +### Mining +- #14811 Mining: Enforce that segwit option must be set in GBT (jnewbery) + +### Block and transaction handling +- #13310 Report progress in ReplayBlocks while rolling forward (promag) +- #13783 validation: Pass tx pool reference into CheckSequenceLocks (MarcoFalke) +- #14834 validation: Assert that pindexPrev is non-null when required (kallewoof) +- #14085 index: Fix for indexers skipping genesis block (jimpo) +- #14963 mempool, validation: Explain `cs_main` locking semantics (MarcoFalke) +- #15193 Default `-whitelistforcerelay` to off (sdaftuar) +- #15429 Update `assumevalid`, `minimumchainwork`, and `getchaintxstats` to height 563378 (gmaxwell) +- #15552 Granular invalidateblock and RewindBlockIndex (MarcoFalke) +- #14841 Move CheckBlock() call to critical section (hebasto) + +### P2P protocol and network code +- #14025 Remove dead code for nVersion=10300 (MarcoFalke) +- #12254 BIP 158: Compact Block Filters for Light Clients (jimpo) +- #14073 blockfilter: Avoid out-of-bounds script access (jimpo) +- #14140 Switch nPrevNodeCount to vNodesSize (pstratem) +- #14027 Skip stale tip checking if outbound connections are off or if reindexing (gmaxwell) +- #14532 Never bind `INADDR_ANY` by default, and warn when doing so explicitly (luke-jr) +- #14733 Make peer timeout configurable, speed up very slow test and ensure correct code path tested (zallarak) +- #14336 Implement poll (pstratem) +- #15051 IsReachable is the inverse of IsLimited (DRY). Includes unit tests (mmachicao) +- #15138 Drop IsLimited in favor of IsReachable (Empact) +- #14605 Return of the Banman (dongcarl) +- #14970 Add dnsseed.emzy.de to DNS seeds (Emzy) +- #14929 Allow connections from misbehavior banned peers (gmaxwell) +- #15345 Correct comparison of addr count (dongcarl) +- #15201 Add missing locking annotation for vNodes. vNodes is guarded by cs_vNodes (practicalswift) +- #14626 Select orphan transaction uniformly for eviction (sipa) +- #15486 Ensure tried collisions resolve, and allow feeler connections to existing outbound netgroups (sdaftuar) + +### Wallet +- #13962 Remove unused `dummy_tx` variable from FillPSBT (dongcarl) +- #13967 Don't report `minversion` wallet entry as unknown (instagibbs) +- #13988 Add checks for settxfee reasonableness (ajtowns) +- #12559 Avoid locking `cs_main` in some wallet RPC (promag) +- #13631 Add CMerkleTx::IsImmatureCoinBase method (Empact) +- #14023 Remove accounts RPCs (jnewbery) +- #13825 Kill accounts (jnewbery) +- #10605 Add AssertLockHeld assertions in CWallet::ListCoins (ryanofsky) +- #12490 Remove deprecated wallet rpc features from `bitcoin_server` (jnewbery) +- #14138 Set `encrypted_batch` to nullptr after delete. Avoid double free in the case of NDEBUG (practicalswift) +- #14168 Remove `ENABLE_WALLET` from `libbitcoin_server.a` (jnewbery) +- #12493 Reopen CDBEnv after encryption instead of shutting down (achow101) +- #14282 Remove `-usehd` option (jnewbery) +- #14146 Remove trailing separators from `-walletdir` arg (PierreRochard) +- #14291 Add ListWalletDir utility function (promag) +- #14468 Deprecate `generate` RPC method (jnewbery) +- #11634 Add missing `cs_wallet`/`cs_KeyStore` locks to wallet (practicalswift) +- #14296 Remove `addwitnessaddress` (jnewbery) +- #14451 Add BIP70 deprecation warning and allow building GUI without BIP70 support (jameshilliard) +- #14320 Fix duplicate fileid detection (ken2812221) +- #14561 Remove `fs::relative` call and fix listwalletdir tests (promag) +- #14454 Add SegWit support to importmulti (MeshCollider) +- #14410 rpcwallet: `ischange` field for `getaddressinfo` RPC (mrwhythat) +- #14350 Add WalletLocation class (promag) +- #14689 Require a public key to be retrieved when signing a P2PKH input (achow101) +- #14478 Show error to user when corrupt wallet unlock fails (MeshCollider) +- #14411 Restore ability to list incoming transactions by label (ryanofsky) +- #14552 Detect duplicate wallet by comparing the db filename (ken2812221) +- #14678 Remove redundant KeyOriginInfo access, already done in CreateSig (instagibbs) +- #14477 Add ability to convert solvability info to descriptor (sipa) +- #14380 Fix assert crash when specified change output spend size is unknown (instagibbs) +- #14760 Log env path in `BerkeleyEnvironment::Flush` (promag) +- #14646 Add expansion cache functions to descriptors (unused for now) (sipa) +- #13076 Fix ScanForWalletTransactions to return an enum indicating scan result: `success` / `failure` / `user_abort` (Empact) +- #14821 Replace CAffectedKeysVisitor with descriptor based logic (sipa) +- #14957 Initialize `stop_block` in CWallet::ScanForWalletTransactions (Empact) +- #14565 Overhaul `importmulti` logic (sipa) +- #15039 Avoid leaking nLockTime fingerprint when anti-fee-sniping (MarcoFalke) +- #14268 Introduce SafeDbt to handle Dbt with free or `memory_cleanse` raii-style (Empact) +- #14711 Remove uses of chainActive and mapBlockIndex in wallet code (ryanofsky) +- #15279 Clarify rescanblockchain doc (MarcoFalke) +- #15292 Remove `boost::optional`-related false positive -Wmaybe-uninitialized warnings on GCC compiler (hebasto) +- #13926 [Tools] bitcoin-wallet - a tool for creating and managing wallets offline (jnewbery) +- #11911 Free BerkeleyEnvironment instances when not in use (ryanofsky) +- #15235 Do not import private keys to wallets with private keys disabled (achow101) +- #15263 Descriptor expansions only need pubkey entries for PKH/WPKH (sipa) +- #15322 Add missing `cs_db` lock (promag) +- #15297 Releases dangling files on `BerkeleyEnvironment::Close` (promag) +- #14491 Allow descriptor imports with importmulti (MeshCollider) +- #15365 Add lock annotation for mapAddressBook (MarcoFalke) +- #15226 Allow creating blank (empty) wallets (alternative) (achow101) +- #15390 [wallet-tool] Close bdb when flushing wallet (jnewbery) +- #15334 Log absolute paths for the wallets (hebasto) +- #14978 Factor out PSBT utilities from RPCs for use in GUI code; related refactoring (gwillen) +- #14481 Add P2SH-P2WSH support to listunspent RPC (MeshCollider) +- #14021 Import key origin data through descriptors in importmulti (achow101) +- #14075 Import watch only pubkeys to the keypool if private keys are disabled (achow101) +- #15368 Descriptor checksums (sipa) +- #15433 Use a single wallet batch for `UpgradeKeyMetadata` (jonasschnelli) +- #15408 Remove unused `TransactionError` constants (MarcoFalke) +- #15583 Log and ignore errors in ListWalletDir and IsBerkeleyBtree (promag) +- #14195 Pass privkey export DER compression flag correctly (fingera) +- #15299 Fix assertion in `CKey::SignCompact` (promag) +- #14437 Start to separate wallet from node (ryanofsky) +- #15749 Fix: importmulti only imports origin info for PKH outputs (sipa) + +### RPC and other APIs +- #12842 Prevent concurrent `savemempool` (promag) +- #13987 Report `minfeefilter` value in `getpeerinfo` RPC (ajtowns) +- #13891 Remove getinfo deprecation warning (jnewbery) +- #13399 Add `submitheader` (MarcoFalke) +- #12676 Show `bip125-replaceable` flag, when retrieving mempool entries (dexX7) +- #13723 PSBT key path cleanups (sipa) +- #14008 Preserve a format of RPC command definitions (kostyantyn) +- #9332 Let wallet `importmulti` RPC accept labels for standard scriptPubKeys (ryanofsky) +- #13983 Return more specific reject reason for submitblock (MarcoFalke) +- #13152 Add getnodeaddresses RPC command (chris-belcher) +- #14298 rest: Improve performance for JSON calls (alecalve) +- #14297 Remove warning for removed estimatefee RPC (jnewbery) +- #14373 Consistency fixes for RPC descriptions (ch4ot1c) +- #14150 Add key origin support to descriptors (sipa) +- #14518 Always throw in getblockstats if `-txindex` is required (promag) +- #14060 ZMQ: add options to configure outbound message high water mark, aka SNDHWM (mruddy) +- #13381 Add possibility to preserve labels on importprivkey (marcoagner) +- #14530 Use `RPCHelpMan` to generate RPC doc strings (MarcoFalke) +- #14720 Correctly name RPC arguments (MarcoFalke) +- #14726 Use `RPCHelpMan` for all RPCs (MarcoFalke) +- #14796 Pass argument descriptions to `RPCHelpMan` (MarcoFalke) +- #14670 http: Fix HTTP server shutdown (promag) +- #14885 Assert that named arguments are unique in `RPCHelpMan` (promag) +- #14877 Document default values for optional arguments (MarcoFalke) +- #14875 RPCHelpMan: Support required arguments after optional ones (MarcoFalke) +- #14993 Fix data race (UB) in InterruptRPC() (practicalswift) +- #14653 rpcwallet: Add missing transaction categories to RPC helptexts (andrewtoth) +- #14981 Clarify RPC `getrawtransaction`'s time help text (benthecarman) +- #12151 Remove `cs_main` lock from blockToJSON and blockheaderToJSON (promag) +- #15078 Document `bytessent_per_msg` and `bytesrecv_per_msg` (MarcoFalke) +- #15057 Correct `reconsiderblock `help text, add test (MarcoFalke) +- #12153 Avoid permanent `cs_main` lock in `getblockheader` (promag) +- #14982 Add `getrpcinfo` command (promag) +- #15122 Expand help text for `importmulti` changes (jnewbery) +- #15186 remove duplicate solvable field from `getaddressinfo` (fanquake) +- #15209 zmq: log outbound message high water mark when reusing socket (fanquake) +- #15177 rest: Improve tests and documention of /headers and /block (promag) +- #14353 rest: Add blockhash call, fetch blockhash by height (jonasschnelli) +- #15248 Compile on GCC4.8 (MarcoFalke) +- #14987 RPCHelpMan: Pass through Result and Examples (MarcoFalke) +- #15159 Remove lookup to UTXO set from GetTransaction (amitiuttarwar) +- #15245 remove deprecated mentions of signrawtransaction from fundraw help (instagibbs) +- #14667 Add `deriveaddresses` RPC util method (Sjors) +- #15357 Don't ignore `-maxtxfee` when wallet is disabled (JBaczuk) +- #15337 Fix for segfault if combinepsbt called with empty inputs (benthecarman) +- #14918 RPCHelpMan: Check default values are given at compile-time (MarcoFalke) +- #15383 mining: Omit uninitialized currentblockweight, currentblocktx (MarcoFalke) +- #13932 Additional utility RPCs for PSBT (achow101) +- #15401 Actually throw help when passed invalid number of params (MarcoFalke) +- #15471 rpc/gui: Remove 'Unknown block versions being mined' warning (laanwj) +- #15497 Consistent range arguments in scantxoutset/importmulti/deriveaddresses (sipa) +- #15510 deriveaddresses: add range to CRPCConvertParam (Sjors) +- #15582 Fix overflow bug in analyzepsbt fee: CAmount instead of int (sipa) +- #13424 Consistently validate txid / blockhash length and encoding in rpc calls (Empact) +- #15750 Remove the addresses field from the getaddressinfo return object (jnewbery) + +### GUI +- #13634 Compile `boost::signals2` only once (MarcoFalke) +- #13248 Make proxy icon from statusbar clickable (mess110) +- #12818 TransactionView: highlight replacement tx after fee bump (Sjors) +- #13529 Use new Qt5 connect syntax (promag) +- #14162 Also log and print messages or questions like bitcoind (MarcoFalke) +- #14385 Avoid system harfbuzz and bz2 (theuni) +- #14450 Fix QCompleter popup regression (hebasto) +- #14177 Set C locale for amountWidget (hebasto) +- #14374 Add `Blocksdir` to Debug window (hebasto) +- #14554 Remove unused `adjustedTime` parameter (hebasto) +- #14228 Enable system tray icon by default if available (hebasto) +- #14608 Remove the "Pay only required fee…" checkbox (hebasto) +- #14521 qt, docs: Fix `bitcoin-qt -version` output formatting (hebasto) +- #13966 When private key is disabled, only show watch-only balance (ken2812221) +- #14828 Remove hidden columns in coin control dialog (promag) +- #14783 Fix `boost::signals2::no_slots_error` in early calls to InitWarning (promag) +- #14854 Cleanup SplashScreen class (hebasto) +- #14801 Use window() instead of obsolete topLevelWidget() (hebasto) +- #14573 Add Window menu (promag) +- #14979 Restore < Qt5.6 compatibility for addAction (jonasschnelli) +- #14975 Refactoring with QString::toNSString() (hebasto) +- #15000 Fix broken notificator on GNOME (hebasto) +- #14375 Correct misleading "overridden options" label (hebasto) +- #15007 Notificator class refactoring (hebasto) +- #14784 Use `WalletModel*` instead of the wallet name as map key (promag) +- #11625 Add BitcoinApplication & RPCConsole tests (ryanofsky) +- #14517 Fix start with the `-min` option (hebasto) +- #13216 implements concept for different disk sizes on intro (marcoagner) +- #15114 Replace remaining 0 with nullptr (Empact) +- #14594 Fix minimized window bug on Linux (hebasto) +- #14556 Fix confirmed transaction labeled "open" (#13299) (hebasto) +- #15149 Show current wallet name in window title (promag) +- #15136 "Peers" tab overhaul (hebasto) +- #14250 Remove redundant stopThread() and stopExecutor() signals (hebasto) +- #15040 Add workaround for QProgressDialog bug on macOS (hebasto) +- #15101 Add WalletController (promag) +- #15178 Improve "help-console" message (hebasto) +- #15210 Fix window title update (promag) +- #15167 Fix wallet selector size adjustment (hebasto) +- #15208 Remove macOS launch-at-startup when compiled with > macOS 10.11, fix memory mismanagement (jonasschnelli) +- #15163 Correct units for "-dbcache" and "-prune" (hebasto) +- #15225 Change the receive button to respond to keypool state changing (achow101) +- #15280 Fix shutdown order (promag) +- #15203 Fix issue #9683 "gui, wallet: random abort (segmentation fault) (dooglus) +- #15091 Fix model overlay header sync (jonasschnelli) +- #15153 Add Open Wallet menu (promag) +- #15183 Fix `m_assumed_blockchain_size` variable value (marcoagner) +- #15063 If BIP70 is disabled, attempt to fall back to BIP21 parsing (luke-jr) +- #15195 Add Close Wallet action (promag) +- #15462 Fix async open wallet call order (promag) +- #15801 Bugfix: GUI: Options: Initialise prune setting range before loading current value, and remove upper bound limit (luke-jr) + +### Build system +- #13955 gitian: Bump descriptors for (0.)18 (fanquake) +- #13899 Enable -Wredundant-decls where available. Remove redundant redeclarations (practicalswift) +- #13665 Add RISC-V support to gitian (ken2812221) +- #14062 Generate MSVC project files via python script (ken2812221) +- #14037 Add README.md to linux release tarballs (hebasto) +- #14183 Remove unused Qt 4 dependencies (ken2812221) +- #14127 Avoid getifaddrs when unavailable (greenaddress) +- #14184 Scripts and tools: increased timeout downloading (cisba) +- #14204 Move `interfaces/*` to `libbitcoin_server` (laanwj) +- #14208 Actually remove `ENABLE_WALLET` (jnewbery) +- #14212 Remove libssl from LDADD unless GUI (MarcoFalke) +- #13578 Upgrade zeromq to 4.2.5 and avoid deprecated zeromq API functions (mruddy) +- #14281 lcov: filter /usr/lib/ from coverage reports (MarcoFalke) +- #14325 gitian: Use versioned unsigned tarballs instead of generically named ones (achow101) +- #14253 During 'make clean', remove some files that are currently missed (murrayn) +- #14455 Unbreak `make clean` (jamesob) +- #14495 Warn (don't fail!) on spelling errors (practicalswift) +- #14496 Pin to specific versions of Python packages we install from PyPI in Travis (practicalswift) +- #14568 Fix Qt link order for Windows build (ken2812221) +- #14252 Run functional tests and benchmarks under the undefined behaviour sanitizer (UBSan) (practicalswift) +- #14612 Include full version number in released file names (achow101) +- #14840 Remove duplicate libconsensus linking in test make (AmirAbrams) +- #14564 Adjust configure so that only BIP70 is disabled when protobuf is missing instead of the GUI (jameshilliard) +- #14883 Add `--retry 5` to curl opts in `install_db4.sh` (qubenix) +- #14701 Add `CLIENT_VERSION_BUILD` to CFBundleGetInfoString (fanquake) +- #14849 Qt 5.9.7 (fanquake) +- #15020 Add names to Travis jobs (gkrizek) +- #15047 Allow to configure --with-sanitizers=fuzzer (MarcoFalke) +- #15154 Configure: bitcoin-tx doesn't need libevent, so don't pull it in (luke-jr) +- #15175 Drop macports support (Empact) +- #15308 Restore compatibility with older boost (Empact) +- #15407 msvc: Fix silent merge conflict between #13926 and #14372 part II (ken2812221) +- #15388 Makefile.am: add rule for src/bitcoin-wallet (Sjors) +- #15393 Bump minimum Qt version to 5.5.1 (Sjors) +- #15285 Prefer Python 3.4 even if newer versions are present on the system (Sjors) +- #15398 msvc: Add rapidcheck property tests (ken2812221) +- #15431 msvc: scripted-diff: Remove NDEBUG pre-define in project file (ken2812221) +- #15549 gitian: Improve error handling (laanwj) +- #15548 use full version string in setup.exe (MarcoFalke) +- #11526 Visual Studio build configuration for Bitcoin Core (sipsorcery) +- #15110 build\_msvc: Fix the build problem in `libbitcoin_server` (Mr-Leshiy) +- #14372 msvc: build secp256k1 and leveldb locally (ken2812221) +- #15325 msvc: Fix silent merge conflict between #13926 and #14372 (ken2812221) +- #15391 Add compile time verification of assumptions we're currently making implicitly/tacitly (practicalswift) +- #15503 msvc: Use a single file to specify the include path (ken2812221) +- #13765 contrib: Add gitian build support for github pull request (ken2812221) +- #15809 gitignore: plist and dat (jamesob) + +### Tests and QA +- #15405 appveyor: Clean cache when build configuration changes (Sjors) +- #13953 Fix deprecation in bitcoin-util-test.py (isghe) +- #13963 Replace usage of tostring() with tobytes() (dongcarl) +- #13964 ci: Add appveyor ci (ken2812221) +- #13997 appveyor: fetch the latest port data (ken2812221) +- #13707 Add usage note to check-rpc-mappings.py (masonicboom) +- #14036 travis: Run unit tests --with-sanitizers=undefined (MarcoFalke) +- #13861 Add testing of `value_ret` for SelectCoinsBnB (Empact) +- #13863 travis: Move script sections to files in `.travis/` subject to shellcheck (scravy) +- #14081 travis: Fix missing differentiation between unit and functional tests (scravy) +- #14042 travis: Add cxxflags=-wno-psabi at arm job (ken2812221) +- #14051 Make `combine_logs.py` handle multi-line logs (jnewbery) +- #14093 Fix accidental trunction from int to bool (practicalswift) +- #14108 Add missing locking annotations and locks (`g_cs_orphans`) (practicalswift) +- #14088 Don't assert(…) with side effects (practicalswift) +- #14086 appveyor: Use clcache to speed up build (ken2812221) +- #13954 Warn (don't fail!) on spelling errors. Fix typos reported by codespell (practicalswift) +- #12775 Integration of property based testing into Bitcoin Core (Christewart) +- #14119 Read reject reasons from debug log, not P2P messages (MarcoFalke) +- #14189 Fix silent merge conflict in `wallet_importmulti` (MarcoFalke) +- #13419 Speed up `knapsack_solver_test` by not recreating wallet 100 times (lucash-dev) +- #14199 Remove redundant BIP174 test from `rpc_psbt.json` (araspitzu) +- #14179 Fixups to "Run all tests even if wallet is not compiled" (MarcoFalke) +- #14225 Reorder tests and move most of extended tests up to normal tests (ken2812221) +- #14236 `generate` --> `generatetoaddress` change to allow tests run without wallet (sanket1729) +- #14287 Use MakeUnique to construct objects owned by `unique_ptrs` (practicalswift) +- #14007 Run functional test on Windows and enable it on Appveyor (ken2812221) +- #14275 Write the notification message to different files to avoid race condition in `feature_notifications.py` (ken2812221) +- #14306 appveyor: Move AppVeyor YAML to dot-file-style YAML (MitchellCash) +- #14305 Enforce critical class instance attributes in functional tests, fix segwit test specificity (JustinTArthur) +- #12246 Bugfix: Only run bitcoin-tx tests when bitcoin-tx is enabled (luke-jr) +- #14316 Exclude all tests with difference parameters in `--exclude` list (ken2812221) +- #14381 Add missing call to `skip_if_no_cli()` (practicalswift) +- #14389 travis: Set codespell version to avoid breakage (MarcoFalke) +- #14398 Don't access out of bounds array index: array[sizeof(array)] (Empact) +- #14419 Remove `rpc_zmq.py` (jnewbery) +- #14241 appveyor: Script improvement (ken2812221) +- #14413 Allow closed RPC handler in `assert_start_raises_init_error` (ken2812221) +- #14324 Run more tests with wallet disabled (MarcoFalke) +- #13649 Allow arguments to be forwarded to flake8 in lint-python.sh (jamesob) +- #14465 Stop node before removing the notification file (ken2812221) +- #14460 Improve 'CAmount' tests (hebasto) +- #14456 forward timeouts properly in `send_blocks_and_test` (jamesob) +- #14527 Revert "Make qt wallet test compatible with qt4" (MarcoFalke) +- #14504 Show the progress of functional tests (isghe) +- #14559 appveyor: Enable multiwallet tests (ken2812221) +- #13515 travis: Enable qt for all jobs (ken2812221) +- #14571 Test that nodes respond to `getdata` with `notfound` (MarcoFalke) +- #14569 Print dots by default in functional tests (ken2812221) +- #14631 Move deterministic address import to `setup_nodes` (jnewbery) +- #14630 test: Remove travis specific code (MarcoFalke) +- #14528 travis: Compile once on xenial (MarcoFalke) +- #14092 Dry run `bench_bitcoin` as part `make check` to allow for quick identification of assertion/sanitizer failures in benchmarking code (practicalswift) +- #14664 `example_test.py`: fixup coinbase height argument, derive number clearly (instagibbs) +- #14522 Add invalid P2P message tests (jamesob) +- #14619 Fix value display name in `test_runner` help text (merland) +- #14672 Send fewer spam messages in `p2p_invalid_messages` (jamesob) +- #14673 travis: Fail the ubsan travis build in case of newly introduced ubsan errors (practicalswift) +- #14665 appveyor: Script improvement part II (ken2812221) +- #14365 Add Python dead code linter (vulture) to Travis (practicalswift) +- #14693 `test_node`: `get_mem_rss` fixups (MarcoFalke) +- #14714 util.h: explicitly include required QString header (1Il1) +- #14705 travis: Avoid timeout on verify-commits check (MarcoFalke) +- #14770 travis: Do not specify sudo in `.travis` (scravy) +- #14719 Check specific reject reasons in `feature_block` (MarcoFalke) +- #14771 Add `BOOST_REQUIRE` to getters returning optional (MarcoFalke) +- #14777 Add regtest for JSON-RPC batch calls (domob1812) +- #14764 travis: Run thread sanitizer on unit tests (MarcoFalke) +- #14400 Add Benchmark to test input de-duplication worst case (JeremyRubin) +- #14812 Fix `p2p_invalid_messages` on macOS (jamesob) +- #14813 Add `wallet_encryption` error tests (MarcoFalke) +- #14820 Fix `descriptor_tests` not checking ToString output of public descriptors (ryanofsky) +- #14794 Add AddressSanitizer (ASan) Travis build (practicalswift) +- #14819 Bugfix: `test/functional/mempool_accept`: Ensure oversize transaction is actually oversize (luke-jr) +- #14822 bench: Destroy wallet txs instead of leaking their memory (MarcoFalke) +- #14683 Better `combine_logs.py` behavior (jamesob) +- #14231 travis: Save cache even when build or test fail (ken2812221) +- #14816 Add CScriptNum decode python implementation in functional suite (instagibbs) +- #14861 Modify `rpc_bind` to conform to #14532 behaviour (dongcarl) +- #14864 Run scripted-diff in subshell (dongcarl) +- #14795 Allow `test_runner` command line to receive parameters for each test (marcoagner) +- #14788 Possible fix the permission error when the tests open the cookie file (ken2812221) +- #14857 `wallet_keypool_topup.py`: Test for all keypool address types (instagibbs) +- #14886 Refactor importmulti tests (jnewbery) +- #14908 Removed implicit CTransaction constructor calls from tests and benchmarks (lucash-dev) +- #14903 Handle ImportError explicitly, improve comparisons against None (daniel-s-ingram) +- #14884 travis: Enforce python 3.4 support through linter (Sjors) +- #14940 Add test for truncated pushdata script (MarcoFalke) +- #14926 consensus: Check that final transactions are valid (MarcoFalke) +- #14937 travis: Fix travis would always be green even if it fail (ken2812221) +- #14953 Make `g_insecure_rand_ctx` `thread_local` (MarcoFalke) +- #14931 mempool: Verify prioritization is dumped correctly (MarcoFalke) +- #14935 Test for expected return values when calling functions returning a success code (practicalswift) +- #14969 Fix `cuckoocache_tests` TSAN failure introduced in 14935 (practicalswift) +- #14964 Fix race in `mempool_accept` (MarcoFalke) +- #14829 travis: Enable functional tests in the threadsanitizer (tsan) build job (practicalswift) +- #14985 Remove `thread_local` from `test_bitcoin` (MarcoFalke) +- #15005 Bump timeout to run tests in travis thread sanitizer (MarcoFalke) +- #15013 Avoid race in `p2p_timeouts` (MarcoFalke) +- #14960 lint/format-strings: Correctly exclude escaped percent symbols (luke-jr) +- #14930 pruning: Check that verifychain can be called when pruned (MarcoFalke) +- #15022 Upgrade Travis OS to Xenial (gkrizek) +- #14738 Fix running `wallet_listtransactions.py` individually through `test_runner.py` (kristapsk) +- #15026 Rename `rpc_timewait` to `rpc_timeout` (MarcoFalke) +- #15069 Fix `rpc_net.py` `pong` race condition (Empact) +- #14790 Allow running `rpc_bind.py` --nonloopback test without IPv6 (kristapsk) +- #14457 add invalid tx templates for use in functional tests (jamesob) +- #14855 Correct ineffectual WithOrVersion from `transactions_tests` (Empact) +- #15099 Use `std::vector` API for construction of test data (domob1812) +- #15102 Run `invalid_txs.InputMissing` test in `feature_block` (MarcoFalke) +- #15059 Add basic test for BIP34 (MarcoFalke) +- #15108 Tidy up `wallet_importmulti.py` (amitiuttarwar) +- #15164 Ignore shellcheck warning SC2236 (promag) +- #15170 refactor/lint: Add ignored shellcheck suggestions to an array (koalaman) +- #14958 Remove race between connecting and shutdown on separate connections (promag) +- #15166 Pin shellcheck version (practicalswift) +- #15196 Update all `subprocess.check_output` functions to be Python 3.4 compatible (gkrizek) +- #15043 Build fuzz targets into seperate executables (MarcoFalke) +- #15276 travis: Compile once on trusty (MarcoFalke) +- #15246 Add tests for invalid message headers (MarcoFalke) +- #15301 When testing with --usecli, unify RPC arg to cli arg conversion and handle dicts and lists (achow101) +- #15247 Use wallet to retrieve raw transactions (MarcoFalke) +- #15303 travis: Remove unused `functional_tests_config` (MarcoFalke) +- #15330 Fix race in `p2p_invalid_messages` (MarcoFalke) +- #15324 Make bloom tests deterministic (MarcoFalke) +- #15328 travis: Revert "run extended tests once daily" (MarcoFalke) +- #15327 Make test `updatecoins_simulation_test` deterministic (practicalswift) +- #14519 add utility to easily profile node performance with perf (jamesob) +- #15349 travis: Only exit early if compilation took longer than 30 min (MarcoFalke) +- #15350 Drop RPC connection if --usecli (promag) +- #15370 test: Remove unused --force option (MarcoFalke) +- #14543 minor `p2p_sendheaders` fix of height in coinbase (instagibbs) +- #13787 Test for Windows encoding issue (ken2812221) +- #15378 Added missing tests for RPC wallet errors (benthecarman) +- #15238 remove some magic mining constants in functional tests (instagibbs) +- #15411 travis: Combine --disable-bip70 into existing job (MarcoFalke) +- #15295 fuzz: Add `test/fuzz/test_runner.py` and run it in travis (MarcoFalke) +- #15413 Add missing `cs_main` locks required when accessing pcoinsdbview, pcoinsTip or pblocktree (practicalswift) +- #15399 fuzz: Script validation flags (MarcoFalke) +- #15410 txindex: interrupt threadGroup before calling destructor (MarcoFalke) +- #15397 Remove manual byte editing in `wallet_tx_clone` func test (instagibbs) +- #15415 functional: allow custom cwd, use tmpdir as default (Sjors) +- #15404 Remove `-txindex` to start nodes (amitiuttarwar) +- #15439 remove `byte.hex()` to keep compatibility (AkioNak) +- #15419 Always refresh cache to be out of ibd (MarcoFalke) +- #15507 Bump timeout on tests that timeout on windows (MarcoFalke) +- #15506 appveyor: fix cache issue and reduce dependencies build time (ken2812221) +- #15485 add `rpc_misc.py`, mv test getmemoryinfo, add test mallocinfo (adamjonas) +- #15321 Add `cs_main` lock annotations for mapBlockIndex (MarcoFalke) +- #14128 lint: Make sure we read the command line inputs using UTF-8 decoding in python (ken2812221) +- #14115 lint: Make all linters work under the default macos dev environment (build-osx.md) (practicalswift) +- #15219 lint: Enable python linters via an array (Empact) + +### Platform support +- #13866 utils: Use `_wfopen` and `_wfreopen` on windows (ken2812221) +- #13886 utils: Run commands using UTF-8 string on windows (ken2812221) +- #14192 utils: Convert `fs::filesystem_error` messages from local multibyte to UTF-8 on windows (ken2812221) +- #13877 utils: Make fs::path::string() always return UTF-8 string on windows (ken2812221) +- #13883 utils: Convert windows args to UTF-8 string (ken2812221) +- #13878 utils: Add fstream wrapper to allow to pass unicode filename on windows (ken2812221) +- #14426 utils: Fix broken windows filelock (ken2812221) +- #14686 Fix windows build error if `--disable-bip70` (ken2812221) +- #14922 windows: Set `_WIN32_WINNT` to 0x0601 (Windows 7) (ken2812221) +- #13888 Call unicode API on Windows (ken2812221) +- #15468 Use `fsbridge::ifstream` to fix Windows path issue (ken2812221) +- #13734 Drop `boost::scoped_array` and use `wchar_t` API explicitly on Windows (ken2812221) +- #13884 Enable bdb unicode support for Windows (ken2812221) + +### Miscellaneous +- #13935 contrib: Adjust output to current test format (AkioNak) +- #14097 validation: Log FormatStateMessage on ConnectBlock error in ConnectTip (MarcoFalke) +- #13724 contrib: Support ARM and RISC-V symbol check (ken2812221) +- #13159 Don't close old debug log file handle prematurely when trying to re-open (on SIGHUP) (practicalswift) +- #14186 bitcoin-cli: don't translate command line options (HashUnlimited) +- #14057 logging: Only log `using config file path_to_bitcoin.conf` message on startup if conf file exists (leishman) +- #14164 Update univalue subtree (MarcoFalke) +- #14272 init: Remove deprecated args from hidden args (MarcoFalke) +- #14494 Error if # is used in rpcpassword in conf (MeshCollider) +- #14742 Properly generate salt in rpcauth.py (dongcarl) +- #14708 Warn unrecognised sections in the config file (AkioNak) +- #14756 Improve rpcauth.py by using argparse and getpass modules (promag) +- #14785 scripts: Fix detection of copyright holders (cornelius) +- #14831 scripts: Use `#!/usr/bin/env bash` instead of `#!/bin/bash` (vim88) +- #14869 Scripts: Add trusted key for samuel dobson (laanwj) +- #14809 Tools: improve verify-commits.py script (jlopp) +- #14624 Some simple improvements to the RNG code (sipa) +- #14947 scripts: Remove python 2 import workarounds (practicalswift) +- #15087 Error if rpcpassword contains hash in conf sections (MeshCollider) +- #14433 Add checksum in gitian build scripts for ossl (TheCharlatan) +- #15165 contrib: Allow use of github api authentication in github-merge (laanwj) +- #14409 utils and libraries: Make 'blocksdir' always net specific (hebasto) +- #14839 threads: Fix unitialized members in `sched_param` (fanquake) +- #14955 Switch all RNG code to the built-in PRNG (sipa) +- #15258 Scripts and tools: Fix `devtools/copyright_header.py` to always honor exclusions (Empact) +- #12255 Update bitcoin.service to conform to init.md (dongcarl) +- #15266 memory: Construct globals on first use (MarcoFalke) +- #15347 Fix build after pr 15266 merged (hebasto) +- #15351 Update linearize-hashes.py (OverlordQ) +- #15358 util: Add setuphelpoptions() (MarcoFalke) +- #15216 Scripts and tools: Replace script name with a special parameter (hebasto) +- #15250 Use RdSeed when available, and reduce RdRand load (sipa) +- #15278 Improve PID file error handling (hebasto) +- #15270 Pull leveldb subtree (MarcoFalke) +- #15456 Enable PID file creation on WIN (riordant) +- #12783 macOS: disable AppNap during sync (krab) +- #13910 Log progress while verifying blocks at level 4 (domob1812) +- #15124 Fail AppInitMain if either disk space check fails (Empact) +- #15117 Fix invalid memory write in case of failing mmap(…) in PosixLockedPageAllocator::AllocateLocked (practicalswift) +- #14357 streams: Fix broken `streams_vector_reader` test. Remove unused `seek(size_t)` +- #11640 Make `LOCK`, `LOCK2`, `TRY_LOCK` work with CWaitableCriticalSection (ryanofsky) +- #14074 Use `std::unordered_set` instead of `set` in blockfilter interface (jimpo) +- #15275 Add gitian PGP key for hebasto (hebasto) + +### Documentation +- #14120 Notes about control port and read access to cookie (JBaczuk) +- #14135 correct GetDifficulty doc after #13288 (fanquake) +- #14013 Add new regtest ports in man following #10825 ports reattributions (ariard) +- #14149 Remove misleading checkpoints comment in CMainParams (MarcoFalke) +- #14153 Add disable-wallet section to OSX build instructions, update line in Unix instructions (bitstein) +- #13662 Explain when reindex-chainstate can be used instead of reindex (Sjors) +- #14207 `-help-debug` implies `-help` (laanwj) +- #14213 Fix reference to lint-locale-dependence.sh (hebasto) +- #14206 Document `-checklevel` levels (laanwj) +- #14217 Add GitHub PR template (MarcoFalke) +- #14331 doxygen: Fix member comments (MarcoFalke) +- #14264 Split depends installation instructions per arch (MarcoFalke) +- #14393 Add missing apt-get install (poiuty) +- #14428 Fix macOS files description in qt/README.md (hebasto) +- #14390 release process: RPC documentation (karel-3d) +- #14472 getblocktemplate: use SegWit in example (Sjors) +- #14497 Add doc/bitcoin-conf.md (hebasto) +- #14526 Document lint tests (fanquake) +- #14511 Remove explicit storage requirement from README.md (merland) +- #14600 Clarify commit message guidelines (merland) +- #14617 FreeBSD: Document Python 3 requirement for 'gmake check' (murrayn) +- #14592 Add external interface consistency guarantees (MarcoFalke) +- #14625 Make clear function argument case in dev notes (dongcarl) +- #14515 Update OpenBSD build guide for 6.4 (fanquake) +- #14436 Add comment explaining recentRejects-DoS behavior (jamesob) +- #14684 conf: Remove deprecated options from docs, Other cleanup (MarcoFalke) +- #14731 Improve scripted-diff developer docs (dongcarl) +- #14778 A few minor formatting fixes and clarifications to descriptors.md (jnewbery) +- #14448 Clarify rpcwallet flag url change (JBaczuk) +- #14808 Clarify RPC rawtransaction documentation (jlopp) +- #14804 Less confusing documentation for `torpassword` (fanquake) +- #14848 Fix broken Gmane URL in security-check.py (cyounkins-bot) +- #14882 developer-notes.md: Point out that UniValue deviates from upstream (Sjors) +- #14909 Update minimum required Qt (fanquake) +- #14914 Add nice table to files.md (emilengler) +- #14741 Indicate `-rpcauth` option password hashing alg (dongcarl) +- #14950 Add NSIS setup/install steps to windows docs (fanquake) +- #13930 Better explain GetAncestor check for `m_failed_blocks` in AcceptBlockHeader (Sjors) +- #14973 Improve Windows native build instructions (murrayn) +- #15073 Botbot.me (IRC logs) not available anymore (anduck) +- #15038 Get more info about GUI-related issue on Linux (hebasto) +- #14832 Add more Doxygen information to Developer Notes (ch4ot1c) +- #15128 Fix download link in doc/README.md (merland) +- #15127 Clarifying testing instructions (benthecarman) +- #15132 Add FreeBSD build notes link to doc/README.md (fanquake) +- #15173 Explain what .python-version does (Sjors) +- #15223 Add information about security to the JSON-RPC doc (harding) +- #15249 Update python docs to reflect that wildcard imports are disallowed (Empact) +- #15176 Get rid of badly named `doc/README_osx.md` (merland) +- #15272 Correct logging return type and RPC example (fanquake) +- #15244 Gdb attaching to process during tests has non-sudo solution (instagibbs) +- #15332 Small updates to `getrawtransaction` description (amitiuttarwar) +- #15354 Add missing `bitcoin-wallet` tool manpages (MarcoFalke) +- #15343 netaddress: Make IPv4 loopback comment more descriptive (dongcarl) +- #15353 Minor textual improvements in `translation_strings_policy.md` (merland) +- #15426 importmulti: add missing description of keypool option (harding) +- #15425 Add missing newline to listunspent help for witnessScript (harding) +- #15348 Add separate productivity notes document (dongcarl) +- #15416 Update FreeBSD build guide for 12.0 (fanquake) +- #15222 Add info about factors that affect dependency list (merland) +- #13676 Explain that mempool memory is added to `-dbcache` (Sjors) +- #15273 Slight tweak to the verify-commits script directions (droark) +- #15477 Remove misleading hint in getrawtransaction (MarcoFalke) +- #15489 Update release process for snap package (MarcoFalke) +- #15524 doc: Remove berkeleydb PPA from linux build instructions (MarcoFalke) +- #15559 Correct `analyzepsbt` rpc doc (fanquake) +- #15194 Add comment describing `fDisconnect` behavior (dongcarl) +- #15754 getrpcinfo docs (benthecarman) +- #15763 Update bips.md for 0.18.0 (sipa) +- #15757 List new RPCs in psbt.md and descriptors.md (sipa) +- #15765 correct bitcoinconsensus_version in shared-libraries.md (fanquake) +- #15792 describe onlynet option in doc/tor.md (jonatack) +- #15802 mention creating application support bitcoin folder on OSX (JimmyMow) +- #15799 Clarify RPC versioning (MarcoFalke) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- 1Il1 +- 251 +- Aaron Clauson +- Adam Jonas +- Akio Nakamura +- Alexander Leishman +- Alexey Ivanov +- Alexey Poghilenkov +- Amir Abrams +- Amiti Uttarwar +- Andrew Chow +- andrewtoth +- Anthony Towns +- Antoine Le Calvez +- Antoine Riard +- Antti Majakivi +- araspitzu +- Arvid Norberg +- Ben Carman +- Ben Woosley +- benthecarman +- bitcoinhodler +- Carl Dong +- Chakib Benziane +- Chris Moore +- Chris Stewart +- chris-belcher +- Chun Kuan Lee +- Cornelius Schumacher +- Cory Fields +- Craig Younkins +- Cristian Mircea Messel +- Damian Mee +- Daniel Ingram +- Daniel Kraft +- David A. Harding +- DesWurstes +- dexX7 +- Dimitri Deijs +- Dimitris Apostolou +- Douglas Roark +- DrahtBot +- Emanuele Cisbani +- Emil Engler +- Eric Scrivner +- fridokus +- Gal Buki +- Gleb Naumenko +- Glenn Willen +- Graham Krizek +- Gregory Maxwell +- Gregory Sanders +- gustavonalle +- Harry Moreno +- Hennadii Stepanov +- Isidoro Ghezzi +- Jack Mallers +- James Hilliard +- James O'Beirne +- Jameson Lopp +- Jeremy Rubin +- Jesse Cohen +- Jim Posen +- John Newbery +- Jon Layton +- Jonas Schnelli +- João Barbosa +- Jordan Baczuk +- Jorge Timón +- Julian Fleischer +- Justin Turner Arthur +- Karel Bílek +- Karl-Johan Alm +- Kaz Wesley +- ken2812221 +- Kostiantyn Stepaniuk +- Kristaps Kaupe +- Lawrence Nahum +- Lenny Maiorani +- liuyujun +- lucash-dev +- luciana +- Luke Dashjr +- marcaiaf +- marcoagner +- MarcoFalke +- Martin Erlandsson +- Marty Jones +- Mason Simon +- Michael Ford +- Michael Goldstein +- Michael Polzer +- Mitchell Cash +- mruddy +- Murray Nesbitt +- OverlordQ +- Patrick Strateman +- Pierre Rochard +- Pieter Wuille +- poiuty +- practicalswift +- priscoan +- qubenix +- riordant +- Russell Yanofsky +- Samuel Dobson +- sanket1729 +- Sjors Provoost +- Stephan Oeste +- Steven Roose +- Suhas Daftuar +- TheCharlatan +- Tim Ruffing +- Vidar Holen +- vim88 +- Walter +- whythat +- Wladimir J. van der Laan +- Zain Iqbal Allarakhia + +As well as everyone that helped translating on [Transifex](https://www.transifex.com/projects/p/bitcoin/). diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index ba6523d7c2..c4c08487f3 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -140,6 +140,7 @@ QT_MOC_CPP = \ qt/moc_overviewpage.cpp \ qt/moc_peertablemodel.cpp \ qt/moc_paymentserver.cpp \ + qt/moc_qrimagewidget.cpp \ qt/moc_qvalidatedlineedit.cpp \ qt/moc_qvaluecombobox.cpp \ qt/moc_receivecoinsdialog.cpp \ @@ -220,6 +221,7 @@ BITCOIN_QT_H = \ qt/paymentserver.h \ qt/peertablemodel.h \ qt/platformstyle.h \ + qt/qrimagewidget.h \ qt/qvalidatedlineedit.h \ qt/qvaluecombobox.h \ qt/receivecoinsdialog.h \ @@ -340,6 +342,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ qt/paymentserver.cpp \ + qt/qrimagewidget.cpp \ qt/receivecoinsdialog.cpp \ qt/receiverequestdialog.cpp \ qt/recentrequeststablemodel.cpp \ diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp index 10f51931f0..f0fcf675eb 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -203,7 +203,7 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector< // but that is expensive, and CheckBlock caches a block's // "checked-status" (in the CBlock?). CBlock should be able to // check its own merkle root and cache that check. - if (state.CorruptionPossible()) + if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED) return READ_STATUS_FAILED; // Possible Short ID collision return READ_STATUS_CHECKBLOCK_FAILED; } diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp index 61a607ef7f..23ed3ecb53 100644 --- a/src/consensus/tx_check.cpp +++ b/src/consensus/tx_check.cpp @@ -11,24 +11,24 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe { // Basic checks that don't depend on any context if (tx.vin.empty()) - return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vin-empty"); if (tx.vout.empty()) - return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-empty"); // Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability) if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-oversize"); // Check for negative or overflow output values CAmount nValueOut = 0; for (const auto& txout : tx.vout) { if (txout.nValue < 0) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-negative"); if (txout.nValue > MAX_MONEY) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-toolarge"); nValueOut += txout.nValue; if (!MoneyRange(nValueOut)) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge"); } // Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock @@ -37,20 +37,20 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe for (const auto& txin : tx.vin) { if (!vInOutPoints.insert(txin.prevout).second) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-duplicate"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputs-duplicate"); } } if (tx.IsCoinBase()) { if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100) - return state.DoS(100, false, REJECT_INVALID, "bad-cb-length"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-length"); } else { for (const auto& txin : tx.vin) if (txin.prevout.IsNull()) - return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-prevout-null"); } return true; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index fbbbcfd040..4b93cae848 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -160,7 +160,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c { // are the actual inputs available? if (!inputs.HaveInputs(tx)) { - return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-missingorspent", false, + return state.Invalid(ValidationInvalidReason::TX_MISSING_INPUTS, false, REJECT_INVALID, "bad-txns-inputs-missingorspent", strprintf("%s: inputs missing/spent", __func__)); } @@ -172,28 +172,27 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c // If prev is coinbase, check that it's matured if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) { - return state.Invalid(false, - REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", + return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); } // Check for negative or overflow input values nValueIn += coin.out.nValue; if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) { - return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); } } const CAmount value_out = tx.GetValueOut(); if (nValueIn < value_out) { - return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false, + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-in-belowout", strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out))); } // Tally transaction fees const CAmount txfee_aux = nValueIn - value_out; if (!MoneyRange(txfee_aux)) { - return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-fee-outofrange"); } txfee = txfee_aux; diff --git a/src/consensus/validation.h b/src/consensus/validation.h index f2e2c3585a..2e23f4b3a4 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -22,6 +22,78 @@ static const unsigned char REJECT_NONSTANDARD = 0x40; static const unsigned char REJECT_INSUFFICIENTFEE = 0x42; static const unsigned char REJECT_CHECKPOINT = 0x43; +/** A "reason" why something was invalid, suitable for determining whether the + * provider of the object should be banned/ignored/disconnected/etc. + * These are much more granular than the rejection codes, which may be more + * useful for some other use-cases. + */ +enum class ValidationInvalidReason { + // txn and blocks: + NONE, //!< not actually invalid + CONSENSUS, //!< invalid by consensus rules (excluding any below reasons) + /** + * Invalid by a change to consensus rules more recent than SegWit. + * Currently unused as there are no such consensus rule changes, and any download + * sources realistically need to support SegWit in order to provide useful data, + * so differentiating between always-invalid and invalid-by-pre-SegWit-soft-fork + * is uninteresting. + */ + RECENT_CONSENSUS_CHANGE, + // Only blocks (or headers): + CACHED_INVALID, //!< this object was cached as being invalid, but we don't know why + BLOCK_INVALID_HEADER, //!< invalid proof of work or time too old + BLOCK_MUTATED, //!< the block's data didn't match the data committed to by the PoW + BLOCK_MISSING_PREV, //!< We don't have the previous block the checked one is built on + BLOCK_INVALID_PREV, //!< A block this one builds on is invalid + BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad) + BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints + // Only loose txn: + TX_NOT_STANDARD, //!< didn't meet our local policy rules + TX_MISSING_INPUTS, //!< a transaction was missing some of its inputs + TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks + /** + * Transaction might be missing a witness, have a witness prior to SegWit + * activation, or witness may have been malleated (which includes + * non-standard witnesses). + */ + TX_WITNESS_MUTATED, + /** + * Tx already in mempool or conflicts with a tx in the chain + * (if it conflicts with another tx in mempool, we use MEMPOOL_POLICY as it failed to reach the RBF threshold) + * TODO: Currently this is only used if the transaction already exists in the mempool or on chain, + * TODO: ATMP's fMissingInputs and a valid CValidationState being used to indicate missing inputs + */ + TX_CONFLICT, + TX_MEMPOOL_POLICY, //!< violated mempool's fee/size/descendant/RBF/etc limits +}; + +inline bool IsTransactionReason(ValidationInvalidReason r) +{ + return r == ValidationInvalidReason::NONE || + r == ValidationInvalidReason::CONSENSUS || + r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE || + r == ValidationInvalidReason::TX_NOT_STANDARD || + r == ValidationInvalidReason::TX_PREMATURE_SPEND || + r == ValidationInvalidReason::TX_MISSING_INPUTS || + r == ValidationInvalidReason::TX_WITNESS_MUTATED || + r == ValidationInvalidReason::TX_CONFLICT || + r == ValidationInvalidReason::TX_MEMPOOL_POLICY; +} + +inline bool IsBlockReason(ValidationInvalidReason r) +{ + return r == ValidationInvalidReason::NONE || + r == ValidationInvalidReason::CONSENSUS || + r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE || + r == ValidationInvalidReason::CACHED_INVALID || + r == ValidationInvalidReason::BLOCK_INVALID_HEADER || + r == ValidationInvalidReason::BLOCK_MUTATED || + r == ValidationInvalidReason::BLOCK_MISSING_PREV || + r == ValidationInvalidReason::BLOCK_INVALID_PREV || + r == ValidationInvalidReason::BLOCK_TIME_FUTURE || + r == ValidationInvalidReason::BLOCK_CHECKPOINT; +} + /** Capture information about block/transaction validation */ class CValidationState { private: @@ -30,32 +102,24 @@ private: MODE_INVALID, //!< network rule violation (DoS value may be set) MODE_ERROR, //!< run-time error } mode; - int nDoS; + ValidationInvalidReason m_reason; std::string strRejectReason; unsigned int chRejectCode; - bool corruptionPossible; std::string strDebugMessage; public: - CValidationState() : mode(MODE_VALID), nDoS(0), chRejectCode(0), corruptionPossible(false) {} - bool DoS(int level, bool ret = false, - unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="", - bool corruptionIn=false, - const std::string &strDebugMessageIn="") { + CValidationState() : mode(MODE_VALID), m_reason(ValidationInvalidReason::NONE), chRejectCode(0) {} + bool Invalid(ValidationInvalidReason reasonIn, bool ret = false, + unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="", + const std::string &strDebugMessageIn="") { + m_reason = reasonIn; chRejectCode = chRejectCodeIn; strRejectReason = strRejectReasonIn; - corruptionPossible = corruptionIn; strDebugMessage = strDebugMessageIn; if (mode == MODE_ERROR) return ret; - nDoS += level; mode = MODE_INVALID; return ret; } - bool Invalid(bool ret = false, - unsigned int _chRejectCode=0, const std::string &_strRejectReason="", - const std::string &_strDebugMessage="") { - return DoS(0, ret, _chRejectCode, _strRejectReason, false, _strDebugMessage); - } bool Error(const std::string& strRejectReasonIn) { if (mode == MODE_VALID) strRejectReason = strRejectReasonIn; @@ -71,19 +135,7 @@ public: bool IsError() const { return mode == MODE_ERROR; } - bool IsInvalid(int &nDoSOut) const { - if (IsInvalid()) { - nDoSOut = nDoS; - return true; - } - return false; - } - bool CorruptionPossible() const { - return corruptionPossible; - } - void SetCorruptionPossible() { - corruptionPossible = true; - } + ValidationInvalidReason GetReason() const { return m_reason; } unsigned int GetRejectCode() const { return chRejectCode; } std::string GetRejectReason() const { return strRejectReason; } std::string GetDebugMessage() const { return strDebugMessage; } diff --git a/src/init.cpp b/src/init.cpp index 2272624f8a..92b3c9510a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -241,8 +241,8 @@ void Shutdown(InitInterfaces& interfaces) g_txindex.reset(); DestroyAllBlockFilterIndexes(); - if (g_is_mempool_loaded && gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { - DumpMempool(); + if (::mempool.IsLoaded() && gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { + DumpMempool(::mempool); } if (fFeeEstimatesInitialized) @@ -735,9 +735,9 @@ static void ThreadImport(std::vector<fs::path> vImportFiles) } } // End scope of CImportingNow if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { - LoadMempool(); + LoadMempool(::mempool); } - g_is_mempool_loaded = !ShutdownRequested(); + ::mempool.SetIsLoaded(!ShutdownRequested()); } /** Sanity checks diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 839af650bb..617be3ca71 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -122,12 +122,6 @@ class LockImpl : public Chain::Lock } return nullopt; } - bool isPotentialTip(const uint256& hash) override - { - if (::chainActive.Tip()->GetBlockHash() == hash) return true; - CBlockIndex* block = LookupBlockIndex(hash); - return block && block->GetAncestor(::chainActive.Height()) == ::chainActive.Tip(); - } CBlockLocator getTipLocator() override { return ::chainActive.GetLocator(); } Optional<int> findLocatorFork(const CBlockLocator& locator) override { @@ -343,7 +337,16 @@ public: { return MakeUnique<NotificationsHandlerImpl>(*this, notifications); } - void waitForNotifications() override { SyncWithValidationInterfaceQueue(); } + void waitForNotificationsIfNewBlocksConnected(const uint256& old_tip) override + { + if (!old_tip.IsNull()) { + LOCK(::cs_main); + if (old_tip == ::chainActive.Tip()->GetBlockHash()) return; + CBlockIndex* block = LookupBlockIndex(old_tip); + if (block && block->GetAncestor(::chainActive.Height()) == ::chainActive.Tip()) return; + } + SyncWithValidationInterfaceQueue(); + } std::unique_ptr<Handler> handleRpc(const CRPCCommand& command) override { return MakeUnique<RpcHandlerImpl>(command); diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 7564ad26ac..0b7249a5ab 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -43,12 +43,6 @@ class Wallet; //! asynchronously //! (https://github.com/bitcoin/bitcoin/pull/10973#issuecomment-380101269). //! -//! * The isPotentialTip() and waitForNotifications() methods are too low-level -//! and should be replaced with a higher level -//! waitForNotificationsUpTo(block_hash) method that the wallet can call -//! instead -//! (https://github.com/bitcoin/bitcoin/pull/10973#discussion_r266995234). -//! //! * The relayTransactions() and submitToMemoryPool() methods could be replaced //! with a higher-level broadcastTransaction method //! (https://github.com/bitcoin/bitcoin/pull/14978#issuecomment-459373984). @@ -123,11 +117,6 @@ public: //! information is desired). virtual Optional<int> findFork(const uint256& hash, Optional<int>* height) = 0; - //! Return true if block hash points to the current chain tip, or to a - //! possible descendant of the current chain tip that isn't currently - //! connected. - virtual bool isPotentialTip(const uint256& hash) = 0; - //! Get locator for the current chain tip. virtual CBlockLocator getTipLocator() = 0; @@ -256,8 +245,10 @@ public: //! Register handler for notifications. virtual std::unique_ptr<Handler> handleNotifications(Notifications& notifications) = 0; - //! Wait for pending notifications to be handled. - virtual void waitForNotifications() = 0; + //! Wait for pending notifications to be processed unless block hash points to the current + //! chain tip, or to a possible descendant of the current chain tip that isn't currently + //! connected. + virtual void waitForNotificationsIfNewBlocksConnected(const uint256& old_tip) = 0; //! Register handler for RPC. Command is not copied, so reference //! needs to remain valid until Handler is disconnected. diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 74e33189dc..71ebd72b83 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -351,7 +351,16 @@ struct CNodeState { TxDownloadState m_tx_download; - CNodeState(CAddress addrIn, std::string addrNameIn) : address(addrIn), name(addrNameIn) { + //! Whether this peer is an inbound connection + bool m_is_inbound; + + //! Whether this peer is a manual connection + bool m_is_manual_connection; + + CNodeState(CAddress addrIn, std::string addrNameIn, bool is_inbound, bool is_manual) : + address(addrIn), name(std::move(addrNameIn)), m_is_inbound(is_inbound), + m_is_manual_connection (is_manual) + { fCurrentlyConnected = false; nMisbehavior = 0; fShouldBan = false; @@ -747,7 +756,7 @@ void PeerLogicValidation::InitializeNode(CNode *pnode) { NodeId nodeid = pnode->GetId(); { LOCK(cs_main); - mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName))); + mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName), pnode->fInbound, pnode->m_manual_connection)); } if(!pnode->fInbound) PushNodeVersion(pnode, connman, GetTime()); @@ -959,6 +968,90 @@ void Misbehaving(NodeId pnode, int howmuch, const std::string& message) EXCLUSIV LogPrint(BCLog::NET, "%s: %s peer=%d (%d -> %d)%s\n", __func__, state->name, pnode, state->nMisbehavior-howmuch, state->nMisbehavior, message_prefixed); } +/** + * Returns true if the given validation state result may result in a peer + * banning/disconnecting us. We use this to determine which unaccepted + * transactions from a whitelisted peer that we can safely relay. + */ +static bool TxRelayMayResultInDisconnect(const CValidationState& state) +{ + assert(IsTransactionReason(state.GetReason())); + return state.GetReason() == ValidationInvalidReason::CONSENSUS; +} + +/** + * Potentially ban a node based on the contents of a CValidationState object + * + * @param[in] via_compact_block: this bool is passed in because net_processing should + * punish peers differently depending on whether the data was provided in a compact + * block message or not. If the compact block had a valid header, but contained invalid + * txs, the peer should not be punished. See BIP 152. + * + * @return Returns true if the peer was punished (probably disconnected) + * + * Changes here may need to be reflected in TxRelayMayResultInDisconnect(). + */ +static bool MaybePunishNode(NodeId nodeid, const CValidationState& state, bool via_compact_block, const std::string& message = "") { + switch (state.GetReason()) { + case ValidationInvalidReason::NONE: + break; + // The node is providing invalid data: + case ValidationInvalidReason::CONSENSUS: + case ValidationInvalidReason::BLOCK_MUTATED: + if (!via_compact_block) { + LOCK(cs_main); + Misbehaving(nodeid, 100, message); + return true; + } + break; + case ValidationInvalidReason::CACHED_INVALID: + { + LOCK(cs_main); + CNodeState *node_state = State(nodeid); + if (node_state == nullptr) { + break; + } + + // Ban outbound (but not inbound) peers if on an invalid chain. + // Exempt HB compact block peers and manual connections. + if (!via_compact_block && !node_state->m_is_inbound && !node_state->m_is_manual_connection) { + Misbehaving(nodeid, 100, message); + return true; + } + break; + } + case ValidationInvalidReason::BLOCK_INVALID_HEADER: + case ValidationInvalidReason::BLOCK_CHECKPOINT: + case ValidationInvalidReason::BLOCK_INVALID_PREV: + { + LOCK(cs_main); + Misbehaving(nodeid, 100, message); + } + return true; + // Conflicting (but not necessarily invalid) data or different policy: + case ValidationInvalidReason::BLOCK_MISSING_PREV: + { + // TODO: Handle this much more gracefully (10 DoS points is super arbitrary) + LOCK(cs_main); + Misbehaving(nodeid, 10, message); + } + return true; + case ValidationInvalidReason::RECENT_CONSENSUS_CHANGE: + case ValidationInvalidReason::BLOCK_TIME_FUTURE: + case ValidationInvalidReason::TX_NOT_STANDARD: + case ValidationInvalidReason::TX_MISSING_INPUTS: + case ValidationInvalidReason::TX_PREMATURE_SPEND: + case ValidationInvalidReason::TX_WITNESS_MUTATED: + case ValidationInvalidReason::TX_CONFLICT: + case ValidationInvalidReason::TX_MEMPOOL_POLICY: + break; + } + if (message != "") { + LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message); + } + return false; +} + @@ -1132,14 +1225,12 @@ void PeerLogicValidation::BlockChecked(const CBlock& block, const CValidationSta const uint256 hash(block.GetHash()); std::map<uint256, std::pair<NodeId, bool>>::iterator it = mapBlockSource.find(hash); - int nDoS = 0; - if (state.IsInvalid(nDoS)) { + if (state.IsInvalid()) { // Don't send reject message with code 0 or an internal reject code. if (it != mapBlockSource.end() && State(it->second.first) && state.GetRejectCode() > 0 && state.GetRejectCode() < REJECT_INTERNAL) { CBlockReject reject = {(unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), hash}; State(it->second.first)->rejects.push_back(reject); - if (nDoS > 0 && it->second.second) - Misbehaving(it->second.first, nDoS); + MaybePunishNode(/*nodeid=*/ it->second.first, state, /*via_compact_block=*/ !it->second.second); } } // Check that: @@ -1489,7 +1580,7 @@ inline void static SendBlockTransactions(const CBlock& block, const BlockTransac connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); } -bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool punish_duplicate_invalid) +bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool via_compact_block) { const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); size_t nCount = headers.size(); @@ -1551,48 +1642,8 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve CValidationState state; CBlockHeader first_invalid_header; if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast, &first_invalid_header)) { - int nDoS; - if (state.IsInvalid(nDoS)) { - LOCK(cs_main); - if (nDoS > 0) { - Misbehaving(pfrom->GetId(), nDoS, "invalid header received"); - } else { - LogPrint(BCLog::NET, "peer=%d: invalid header received\n", pfrom->GetId()); - } - if (punish_duplicate_invalid && LookupBlockIndex(first_invalid_header.GetHash())) { - // Goal: don't allow outbound peers to use up our outbound - // connection slots if they are on incompatible chains. - // - // We ask the caller to set punish_invalid appropriately based - // on the peer and the method of header delivery (compact - // blocks are allowed to be invalid in some circumstances, - // under BIP 152). - // Here, we try to detect the narrow situation that we have a - // valid block header (ie it was valid at the time the header - // was received, and hence stored in mapBlockIndex) but know the - // block is invalid, and that a peer has announced that same - // block as being on its active chain. - // Disconnect the peer in such a situation. - // - // Note: if the header that is invalid was not accepted to our - // mapBlockIndex at all, that may also be grounds for - // disconnecting the peer, as the chain they are on is likely - // to be incompatible. However, there is a circumstance where - // that does not hold: if the header's timestamp is more than - // 2 hours ahead of our current time. In that case, the header - // may become valid in the future, and we don't want to - // disconnect a peer merely for serving us one too-far-ahead - // block header, to prevent an attacker from splitting the - // network by mining a block right at the 2 hour boundary. - // - // TODO: update the DoS logic (or, rather, rewrite the - // DoS-interface between validation and net_processing) so that - // the interface is cleaner, and so that we disconnect on all the - // reasons that a peer's headers chain is incompatible - // with ours (eg block->nVersion softforks, MTP violations, - // etc), and not just the duplicate-invalid case. - pfrom->fDisconnect = true; - } + if (state.IsInvalid()) { + MaybePunishNode(pfrom->GetId(), state, via_compact_block, "invalid header received"); return false; } } @@ -1727,13 +1778,13 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se const CTransaction& orphanTx = *porphanTx; NodeId fromPeer = orphan_it->second.fromPeer; bool fMissingInputs2 = false; - // Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan - // resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get - // anyone relaying LegitTxX banned) - CValidationState stateDummy; + // Use a new CValidationState because orphans come from different peers (and we call + // MaybePunishNode based on the source peer from the orphan map, not based on the peer + // that relayed the previous transaction). + CValidationState orphan_state; if (setMisbehaving.count(fromPeer)) continue; - if (AcceptToMemoryPool(mempool, stateDummy, porphanTx, &fMissingInputs2, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { + if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &fMissingInputs2, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); RelayTransaction(orphanTx, connman); for (unsigned int i = 0; i < orphanTx.vout.size(); i++) { @@ -1747,17 +1798,18 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se EraseOrphanTx(orphanHash); done = true; } else if (!fMissingInputs2) { - int nDos = 0; - if (stateDummy.IsInvalid(nDos) && nDos > 0) { + if (orphan_state.IsInvalid()) { // Punish peer that gave us an invalid orphan tx - Misbehaving(fromPeer, nDos); - setMisbehaving.insert(fromPeer); + if (MaybePunishNode(fromPeer, orphan_state, /*via_compact_block*/ false)) { + setMisbehaving.insert(fromPeer); + } LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n", orphanHash.ToString()); } // Has inputs but not accepted to mempool // Probably non-standard or insufficient fee LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", orphanHash.ToString()); - if (!orphanTx.HasWitness() && !stateDummy.CorruptionPossible()) { + assert(IsTransactionReason(orphan_state.GetReason())); + if (!orphanTx.HasWitness() && orphan_state.GetReason() != ValidationInvalidReason::TX_WITNESS_MUTATED) { // Do not use rejection cache for witness transactions or // witness-stripped transactions, as they can have been malleated. // See https://github.com/bitcoin/bitcoin/issues/8279 for details. @@ -2474,7 +2526,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr recentRejects->insert(tx.GetHash()); } } else { - if (!tx.HasWitness() && !state.CorruptionPossible()) { + assert(IsTransactionReason(state.GetReason())); + if (!tx.HasWitness() && state.GetReason() != ValidationInvalidReason::TX_WITNESS_MUTATED) { // Do not use rejection cache for witness transactions or // witness-stripped transactions, as they can have been malleated. // See https://github.com/bitcoin/bitcoin/issues/8279 for details. @@ -2493,15 +2546,13 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // to policy, allowing the node to function as a gateway for // nodes hidden behind it. // - // Never relay transactions that we would assign a non-zero DoS - // score for, as we expect peers to do the same with us in that - // case. - int nDoS = 0; - if (!state.IsInvalid(nDoS) || nDoS == 0) { + // Never relay transactions that might result in being + // disconnected (or banned). + if (state.IsInvalid() && TxRelayMayResultInDisconnect(state)) { + LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); + } else { LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId()); RelayTransaction(tx, connman); - } else { - LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); } } } @@ -2526,8 +2577,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // peer simply for relaying a tx that our recentRejects has caught, // regardless of false positives. - int nDoS = 0; - if (state.IsInvalid(nDoS)) + if (state.IsInvalid()) { LogPrint(BCLog::MEMPOOLREJ, "%s from peer=%d was not accepted: %s\n", tx.GetHash().ToString(), pfrom->GetId(), @@ -2536,9 +2586,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::REJECT, strCommand, (unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash)); } - if (nDoS > 0) { - Misbehaving(pfrom->GetId(), nDoS); - } + MaybePunishNode(pfrom->GetId(), state, /*via_compact_block*/ false); } return true; } @@ -2574,14 +2622,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr const CBlockIndex *pindex = nullptr; CValidationState state; if (!ProcessNewBlockHeaders({cmpctblock.header}, state, chainparams, &pindex)) { - int nDoS; - if (state.IsInvalid(nDoS)) { - if (nDoS > 0) { - LOCK(cs_main); - Misbehaving(pfrom->GetId(), nDoS, strprintf("Peer %d sent us invalid header via cmpctblock\n", pfrom->GetId())); - } else { - LogPrint(BCLog::NET, "Peer %d sent us invalid header via cmpctblock\n", pfrom->GetId()); - } + if (state.IsInvalid()) { + MaybePunishNode(pfrom->GetId(), state, /*via_compact_block*/ true, "invalid header via cmpctblock"); return true; } } @@ -2731,7 +2773,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // the peer if the header turns out to be for an invalid block. // Note that if a peer tries to build on an invalid chain, that // will be detected and the peer will be banned. - return ProcessHeadersMessage(pfrom, connman, {cmpctblock.header}, chainparams, /*punish_duplicate_invalid=*/false); + return ProcessHeadersMessage(pfrom, connman, {cmpctblock.header}, chainparams, /*via_compact_block=*/true); } if (fBlockReconstructed) { @@ -2874,12 +2916,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr ReadCompactSize(vRecv); // ignore tx count; assume it is 0. } - // Headers received via a HEADERS message should be valid, and reflect - // the chain the peer is on. If we receive a known-invalid header, - // disconnect the peer if it is using one of our outbound connection - // slots. - bool should_punish = !pfrom->fInbound && !pfrom->m_manual_connection; - return ProcessHeadersMessage(pfrom, connman, headers, chainparams, should_punish); + return ProcessHeadersMessage(pfrom, connman, headers, chainparams, /*via_compact_block=*/false); } if (strCommand == NetMsgType::BLOCK) diff --git a/src/psbt.cpp b/src/psbt.cpp index f31f2af0d1..97bda51a63 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -337,7 +337,9 @@ std::string PSBTRoleName(PSBTRole role) { case PSBTRole::SIGNER: return "signer"; case PSBTRole::FINALIZER: return "finalizer"; case PSBTRole::EXTRACTOR: return "extractor"; + // no default case, so the compiler can warn about missing cases } + assert(false); } bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error) diff --git a/src/qt/forms/receiverequestdialog.ui b/src/qt/forms/receiverequestdialog.ui index dbe966b241..9f896ee3b1 100644 --- a/src/qt/forms/receiverequestdialog.ui +++ b/src/qt/forms/receiverequestdialog.ui @@ -127,7 +127,7 @@ <customwidget> <class>QRImageWidget</class> <extends>QLabel</extends> - <header>qt/receiverequestdialog.h</header> + <header>qt/qrimagewidget.h</header> </customwidget> </customwidgets> <resources/> diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 736ff13a4a..d8f5594983 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -37,12 +37,6 @@ static const bool DEFAULT_SPLASHSCREEN = true; */ static const int TOOLTIP_WRAP_THRESHOLD = 80; -/* Maximum allowed URI length */ -static const int MAX_URI_LENGTH = 255; - -/* QRCodeDialog -- size of exported QR Code image */ -#define QR_IMAGE_SIZE 300 - /* Number of frames in spinner animation */ #define SPINNER_FRAMES 36 diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp new file mode 100644 index 0000000000..bf1baf5470 --- /dev/null +++ b/src/qt/qrimagewidget.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2011-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <qt/qrimagewidget.h> + +#include <qt/guiutil.h> + +#include <QApplication> +#include <QClipboard> +#include <QDrag> +#include <QMenu> +#include <QMimeData> +#include <QMouseEvent> +#include <QPainter> + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> /* for USE_QRCODE */ +#endif + +#ifdef USE_QRCODE +#include <qrencode.h> +#endif + +QRImageWidget::QRImageWidget(QWidget *parent): + QLabel(parent), contextMenu(nullptr) +{ + contextMenu = new QMenu(this); + QAction *saveImageAction = new QAction(tr("&Save Image..."), this); + connect(saveImageAction, &QAction::triggered, this, &QRImageWidget::saveImage); + contextMenu->addAction(saveImageAction); + QAction *copyImageAction = new QAction(tr("&Copy Image"), this); + connect(copyImageAction, &QAction::triggered, this, &QRImageWidget::copyImage); + contextMenu->addAction(copyImageAction); +} + +bool QRImageWidget::setQR(const QString& data, const QString& text) +{ +#ifdef USE_QRCODE + setText(""); + if (data.isEmpty()) return false; + + // limit length + if (data.length() > MAX_URI_LENGTH) { + setText(tr("Resulting URI too long, try to reduce the text for label / message.")); + return false; + } + + QRcode *code = QRcode_encodeString(data.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); + + if (!code) { + setText(tr("Error encoding URI into QR Code.")); + return false; + } + + QImage qrImage = QImage(code->width + 8, code->width + 8, QImage::Format_RGB32); + qrImage.fill(0xffffff); + unsigned char *p = code->data; + for (int y = 0; y < code->width; ++y) { + for (int x = 0; x < code->width; ++x) { + qrImage.setPixel(x + 4, y + 4, ((*p & 1) ? 0x0 : 0xffffff)); + ++p; + } + } + QRcode_free(code); + + QImage qrAddrImage = QImage(QR_IMAGE_SIZE, QR_IMAGE_SIZE + (text.isEmpty() ? 0 : 20), QImage::Format_RGB32); + qrAddrImage.fill(0xffffff); + QPainter painter(&qrAddrImage); + painter.drawImage(0, 0, qrImage.scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE)); + + if (!text.isEmpty()) { + QFont font = GUIUtil::fixedPitchFont(); + QRect paddedRect = qrAddrImage.rect(); + + // calculate ideal font size + qreal font_size = GUIUtil::calculateIdealFontSize(paddedRect.width() - 20, text, font); + font.setPointSizeF(font_size); + + painter.setFont(font); + paddedRect.setHeight(QR_IMAGE_SIZE+12); + painter.drawText(paddedRect, Qt::AlignBottom|Qt::AlignCenter, text); + } + + painter.end(); + setPixmap(QPixmap::fromImage(qrAddrImage)); + + return true; +#else + setText(tr("QR code support not available.")); + return false; +#endif +} + +QImage QRImageWidget::exportImage() +{ + if(!pixmap()) + return QImage(); + return pixmap()->toImage(); +} + +void QRImageWidget::mousePressEvent(QMouseEvent *event) +{ + if(event->button() == Qt::LeftButton && pixmap()) + { + event->accept(); + QMimeData *mimeData = new QMimeData; + mimeData->setImageData(exportImage()); + + QDrag *drag = new QDrag(this); + drag->setMimeData(mimeData); + drag->exec(); + } else { + QLabel::mousePressEvent(event); + } +} + +void QRImageWidget::saveImage() +{ + if(!pixmap()) + return; + QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), nullptr); + if (!fn.isEmpty()) + { + exportImage().save(fn); + } +} + +void QRImageWidget::copyImage() +{ + if(!pixmap()) + return; + QApplication::clipboard()->setImage(exportImage()); +} + +void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) +{ + if(!pixmap()) + return; + contextMenu->exec(event->globalPos()); +} diff --git a/src/qt/qrimagewidget.h b/src/qt/qrimagewidget.h new file mode 100644 index 0000000000..2a219ac101 --- /dev/null +++ b/src/qt/qrimagewidget.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011-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. + +#ifndef BITCOIN_QT_QRIMAGEWIDGET_H +#define BITCOIN_QT_QRIMAGEWIDGET_H + +#include <QImage> +#include <QLabel> + +/* Maximum allowed URI length */ +static const int MAX_URI_LENGTH = 255; + +/* Size of exported QR Code image */ +static const int QR_IMAGE_SIZE = 300; + +QT_BEGIN_NAMESPACE +class QMenu; +QT_END_NAMESPACE + +/* Label widget for QR code. This image can be dragged, dropped, copied and saved + * to disk. + */ +class QRImageWidget : public QLabel +{ + Q_OBJECT + +public: + explicit QRImageWidget(QWidget *parent = nullptr); + bool setQR(const QString& data, const QString& text = ""); + QImage exportImage(); + +public Q_SLOTS: + void saveImage(); + void copyImage(); + +protected: + virtual void mousePressEvent(QMouseEvent *event); + virtual void contextMenuEvent(QContextMenuEvent *event); + +private: + QMenu *contextMenu; +}; + +#endif // BITCOIN_QT_QRIMAGEWIDGET_H diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index f5b30cf6d2..20b29145a0 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -6,85 +6,17 @@ #include <qt/forms/ui_receiverequestdialog.h> #include <qt/bitcoinunits.h> -#include <qt/guiconstants.h> #include <qt/guiutil.h> #include <qt/optionsmodel.h> +#include <qt/qrimagewidget.h> #include <QClipboard> -#include <QDrag> -#include <QMenu> -#include <QMimeData> -#include <QMouseEvent> #include <QPixmap> #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> /* for USE_QRCODE */ #endif -#ifdef USE_QRCODE -#include <qrencode.h> -#endif - -QRImageWidget::QRImageWidget(QWidget *parent): - QLabel(parent), contextMenu(nullptr) -{ - contextMenu = new QMenu(this); - QAction *saveImageAction = new QAction(tr("&Save Image..."), this); - connect(saveImageAction, &QAction::triggered, this, &QRImageWidget::saveImage); - contextMenu->addAction(saveImageAction); - QAction *copyImageAction = new QAction(tr("&Copy Image"), this); - connect(copyImageAction, &QAction::triggered, this, &QRImageWidget::copyImage); - contextMenu->addAction(copyImageAction); -} - -QImage QRImageWidget::exportImage() -{ - if(!pixmap()) - return QImage(); - return pixmap()->toImage(); -} - -void QRImageWidget::mousePressEvent(QMouseEvent *event) -{ - if(event->button() == Qt::LeftButton && pixmap()) - { - event->accept(); - QMimeData *mimeData = new QMimeData; - mimeData->setImageData(exportImage()); - - QDrag *drag = new QDrag(this); - drag->setMimeData(mimeData); - drag->exec(); - } else { - QLabel::mousePressEvent(event); - } -} - -void QRImageWidget::saveImage() -{ - if(!pixmap()) - return; - QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), nullptr); - if (!fn.isEmpty()) - { - exportImage().save(fn); - } -} - -void QRImageWidget::copyImage() -{ - if(!pixmap()) - return; - QApplication::clipboard()->setImage(exportImage()); -} - -void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) -{ - if(!pixmap()) - return; - contextMenu->exec(event->globalPos()); -} - ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveRequestDialog), @@ -150,55 +82,9 @@ void ReceiveRequestDialog::update() } ui->outUri->setText(html); -#ifdef USE_QRCODE - ui->lblQRCode->setText(""); - if(!uri.isEmpty()) - { - // limit URI length - if (uri.length() > MAX_URI_LENGTH) - { - ui->lblQRCode->setText(tr("Resulting URI too long, try to reduce the text for label / message.")); - } else { - QRcode *code = QRcode_encodeString(uri.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); - if (!code) - { - ui->lblQRCode->setText(tr("Error encoding URI into QR Code.")); - return; - } - QImage qrImage = QImage(code->width + 8, code->width + 8, QImage::Format_RGB32); - qrImage.fill(0xffffff); - unsigned char *p = code->data; - for (int y = 0; y < code->width; y++) - { - for (int x = 0; x < code->width; x++) - { - qrImage.setPixel(x + 4, y + 4, ((*p & 1) ? 0x0 : 0xffffff)); - p++; - } - } - QRcode_free(code); - - QImage qrAddrImage = QImage(QR_IMAGE_SIZE, QR_IMAGE_SIZE+20, QImage::Format_RGB32); - qrAddrImage.fill(0xffffff); - QPainter painter(&qrAddrImage); - painter.drawImage(0, 0, qrImage.scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE)); - QFont font = GUIUtil::fixedPitchFont(); - QRect paddedRect = qrAddrImage.rect(); - - // calculate ideal font size - qreal font_size = GUIUtil::calculateIdealFontSize(paddedRect.width() - 20, info.address, font); - font.setPointSizeF(font_size); - - painter.setFont(font); - paddedRect.setHeight(QR_IMAGE_SIZE+12); - painter.drawText(paddedRect, Qt::AlignBottom|Qt::AlignCenter, info.address); - painter.end(); - - ui->lblQRCode->setPixmap(QPixmap::fromImage(qrAddrImage)); - ui->btnSaveAs->setEnabled(true); - } + if (ui->lblQRCode->setQR(uri, info.address)) { + ui->btnSaveAs->setEnabled(true); } -#endif } void ReceiveRequestDialog::on_btnCopyURI_clicked() diff --git a/src/qt/receiverequestdialog.h b/src/qt/receiverequestdialog.h index dd28fd73c8..a6e1a2af16 100644 --- a/src/qt/receiverequestdialog.h +++ b/src/qt/receiverequestdialog.h @@ -8,41 +8,11 @@ #include <qt/walletmodel.h> #include <QDialog> -#include <QImage> -#include <QLabel> -#include <QPainter> namespace Ui { class ReceiveRequestDialog; } -QT_BEGIN_NAMESPACE -class QMenu; -QT_END_NAMESPACE - -/* Label widget for QR code. This image can be dragged, dropped, copied and saved - * to disk. - */ -class QRImageWidget : public QLabel -{ - Q_OBJECT - -public: - explicit QRImageWidget(QWidget *parent = nullptr); - QImage exportImage(); - -public Q_SLOTS: - void saveImage(); - void copyImage(); - -protected: - virtual void mousePressEvent(QMouseEvent *event); - virtual void contextMenuEvent(QContextMenuEvent *event); - -private: - QMenu *contextMenu; -}; - class ReceiveRequestDialog : public QDialog { Q_OBJECT diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 672fc69673..f140999622 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -93,6 +93,9 @@ static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* b UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) { + // Serialize passed information without accessing chain state of the active chain! + AssertLockNotHeld(cs_main); // For performance reasons + UniValue result(UniValue::VOBJ); result.pushKV("hash", blockindex->GetBlockHash().GetHex()); const CBlockIndex* pnext; @@ -119,6 +122,9 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails) { + // Serialize passed information without accessing chain state of the active chain! + AssertLockNotHeld(cs_main); // For performance reasons + UniValue result(UniValue::VOBJ); result.pushKV("hash", blockindex->GetBlockHash().GetHex()); const CBlockIndex* pnext; @@ -824,9 +830,7 @@ static CBlock GetBlockChecked(const CBlockIndex* pblockindex) static UniValue getblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( - RPCHelpMan{"getblock", + const RPCHelpMan help{"getblock", "\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.\n" "If verbosity is 1, returns an Object with information about block <hash>.\n" "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction. \n", @@ -878,9 +882,11 @@ static UniValue getblock(const JSONRPCRequest& request) HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") }, - }.ToString()); + }; - LOCK(cs_main); + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } uint256 hash(ParseHashV(request.params[0], "blockhash")); @@ -892,12 +898,20 @@ static UniValue getblock(const JSONRPCRequest& request) verbosity = request.params[1].get_bool() ? 1 : 0; } - const CBlockIndex* pblockindex = LookupBlockIndex(hash); - if (!pblockindex) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); - } + CBlock block; + const CBlockIndex* pblockindex; + const CBlockIndex* tip; + { + LOCK(cs_main); + pblockindex = LookupBlockIndex(hash); + tip = chainActive.Tip(); + + if (!pblockindex) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } - const CBlock block = GetBlockChecked(pblockindex); + block = GetBlockChecked(pblockindex); + } if (verbosity <= 0) { @@ -907,7 +921,7 @@ static UniValue getblock(const JSONRPCRequest& request) return strHex; } - return blockToJSON(block, chainActive.Tip(), pblockindex, verbosity >= 2); + return blockToJSON(block, tip, pblockindex, verbosity >= 2); } struct CCoinsStats @@ -1491,6 +1505,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool) // Make sure this call is atomic in the pool. LOCK(pool.cs); UniValue ret(UniValue::VOBJ); + ret.pushKV("loaded", pool.IsLoaded()); ret.pushKV("size", (int64_t)pool.size()); ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize()); ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage()); @@ -1511,6 +1526,7 @@ static UniValue getmempoolinfo(const JSONRPCRequest& request) {}, RPCResult{ "{\n" + " \"loaded\": true|false (boolean) True if the mempool is fully loaded\n" " \"size\": xxxxx, (numeric) Current tx count\n" " \"bytes\": xxxxx, (numeric) Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted\n" " \"usage\": xxxxx, (numeric) Total memory usage for the mempool\n" @@ -2061,11 +2077,11 @@ static UniValue savemempool(const JSONRPCRequest& request) }.ToString()); } - if (!g_is_mempool_loaded) { + if (!::mempool.IsLoaded()) { throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); } - if (!DumpMempool()) { + if (!DumpMempool(::mempool)) { throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); } diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 55d1de453f..ff461fbcbc 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -5,9 +5,13 @@ #ifndef BITCOIN_RPC_BLOCKCHAIN_H #define BITCOIN_RPC_BLOCKCHAIN_H -#include <vector> -#include <stdint.h> #include <amount.h> +#include <sync.h> + +#include <stdint.h> +#include <vector> + +extern RecursiveMutex cs_main; class CBlock; class CBlockIndex; @@ -28,7 +32,7 @@ double GetDifficulty(const CBlockIndex* blockindex); void RPCNotifyBlockChange(bool ibd, const CBlockIndex *); /** Block description to JSON */ -UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails = false); +UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails = false) LOCKS_EXCLUDED(cs_main); /** Mempool information to JSON */ UniValue MempoolInfoToJSON(const CTxMemPool& pool); @@ -37,7 +41,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool); UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false); /** Block header to JSON */ -UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex); +UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) LOCKS_EXCLUDED(cs_main); /** Used by getblockstats to get feerates at different percentiles by weight */ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight); diff --git a/src/sync.h b/src/sync.h index 3857eda56b..2667fb52f9 100644 --- a/src/sync.h +++ b/src/sync.h @@ -198,6 +198,16 @@ using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove LeaveCritical(); \ } +//! Run code while locking a mutex. +//! +//! Examples: +//! +//! WITH_LOCK(cs, shared_val = shared_val + 1); +//! +//! int val = WITH_LOCK(cs, return shared_val); +//! +#define WITH_LOCK(cs, code) [&] { LOCK(cs); code; }() + class CSemaphore { private: diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp index 331c340b74..26ae7be202 100644 --- a/src/test/txvalidation_tests.cpp +++ b/src/test/txvalidation_tests.cpp @@ -52,10 +52,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup) // Check that the validation state reflects the unsuccessful attempt. BOOST_CHECK(state.IsInvalid()); BOOST_CHECK_EQUAL(state.GetRejectReason(), "coinbase"); - - int nDoS; - BOOST_CHECK_EQUAL(state.IsInvalid(nDoS), true); - BOOST_CHECK_EQUAL(nDoS, 100); + BOOST_CHECK(state.GetReason() == ValidationInvalidReason::CONSENSUS); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/txmempool.cpp b/src/txmempool.cpp index daac24cc40..90b28227a0 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1091,4 +1091,16 @@ void CTxMemPool::GetTransactionAncestry(const uint256& txid, size_t& ancestors, } } +bool CTxMemPool::IsLoaded() const +{ + LOCK(cs); + return m_is_loaded; +} + +void CTxMemPool::SetIsLoaded(bool loaded) +{ + LOCK(cs); + m_is_loaded = loaded; +} + SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} diff --git a/src/txmempool.h b/src/txmempool.h index a8a0f7fa45..3ada47a28e 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -455,6 +455,8 @@ private: void trackPackageRemoved(const CFeeRate& rate) EXCLUSIVE_LOCKS_REQUIRED(cs); + bool m_is_loaded GUARDED_BY(cs){false}; + public: static const int ROLLING_FEE_HALFLIFE = 60 * 60 * 12; // public only for testing @@ -672,6 +674,12 @@ public: */ void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) const; + /** @returns true if the mempool is fully loaded */ + bool IsLoaded() const; + + /** Sets the current loaded state */ + void SetIsLoaded(bool loaded); + unsigned long size() const { LOCK(cs); diff --git a/src/util/system.cpp b/src/util/system.cpp index efd35bed55..6925bda4ef 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1085,11 +1085,12 @@ void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) { fcntl(fileno(file), F_PREALLOCATE, &fst); } ftruncate(fileno(file), fst.fst_length); -#elif defined(__linux__) +#else + #if defined(__linux__) // Version using posix_fallocate off_t nEndPos = (off_t)offset + length; - posix_fallocate(fileno(file), 0, nEndPos); -#else + if (0 == posix_fallocate(fileno(file), 0, nEndPos)) return; + #endif // Fallback version // TODO: just write one byte per block static const char buf[65536] = {}; diff --git a/src/validation.cpp b/src/validation.cpp index d8c8e638ce..ba9d6184ee 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -256,7 +256,6 @@ CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE); CBlockPolicyEstimator feeEstimator; CTxMemPool mempool(&feeEstimator); -std::atomic_bool g_is_mempool_loaded{false}; /** Constant stuff for coinbase transactions we create: */ CScript COINBASE_FLAGS; @@ -586,28 +585,28 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // Coinbase is only valid in a block, not as a loose transaction if (tx.IsCoinBase()) - return state.DoS(100, false, REJECT_INVALID, "coinbase"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "coinbase"); // Rather not work on nonstandard transactions (unless -testnet/-regtest) std::string reason; if (fRequireStandard && !IsStandardTx(tx, reason)) - return state.DoS(0, false, REJECT_NONSTANDARD, reason); + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, reason); // Do not work on transactions that are too small. // A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes. // Transactions smaller than this are not relayed to reduce unnecessary malloc overhead. if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) < MIN_STANDARD_TX_NONWITNESS_SIZE) - return state.DoS(0, false, REJECT_NONSTANDARD, "tx-size-small"); + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "tx-size-small"); // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. if (!CheckFinalTx(tx, STANDARD_LOCKTIME_VERIFY_FLAGS)) - return state.DoS(0, false, REJECT_NONSTANDARD, "non-final"); + return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-final"); // is it already in the memory pool? if (pool.exists(hash)) { - return state.Invalid(false, REJECT_DUPLICATE, "txn-already-in-mempool"); + return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-in-mempool"); } // Check for conflicts with in-memory transactions @@ -643,7 +642,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } } if (fReplacementOptOut) { - return state.Invalid(false, REJECT_DUPLICATE, "txn-mempool-conflict"); + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_DUPLICATE, "txn-mempool-conflict"); } setConflicts.insert(ptxConflicting->GetHash()); @@ -673,7 +672,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool for (size_t out = 0; out < tx.vout.size(); out++) { // Optimistically just do efficient check of cache for outputs if (pcoinsTip->HaveCoinInCache(COutPoint(hash, out))) { - return state.Invalid(false, REJECT_DUPLICATE, "txn-already-known"); + return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-known"); } } // Otherwise assume this might be an orphan tx for which we just haven't seen parents yet @@ -696,7 +695,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // Must keep pool.cs for this unless we change CheckSequenceLocks to take a // CoinsViewCache instead of create its own if (!CheckSequenceLocks(pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) - return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final"); + return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-BIP68-final"); CAmount nFees = 0; if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees)) { @@ -705,11 +704,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // Check for non-standard pay-to-script-hash in inputs if (fRequireStandard && !AreInputsStandard(tx, view)) - return state.Invalid(false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs"); + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs"); // Check for non-standard witness in P2WSH if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, view)) - return state.DoS(0, false, REJECT_NONSTANDARD, "bad-witness-nonstandard", true); + return state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, REJECT_NONSTANDARD, "bad-witness-nonstandard"); int64_t nSigOpsCost = GetTransactionSigOpCost(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS); @@ -732,27 +731,22 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool fSpendsCoinbase, nSigOpsCost, lp); unsigned int nSize = entry.GetTxSize(); - // Check that the transaction doesn't have an excessive number of - // sigops, making it impossible to mine. Since the coinbase transaction - // itself can contain sigops MAX_STANDARD_TX_SIGOPS is less than - // MAX_BLOCK_SIGOPS; we still consider this an invalid rather than - // merely non-standard transaction. if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST) - return state.DoS(0, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", false, + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", strprintf("%d", nSigOpsCost)); CAmount mempoolRejectFee = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize); if (!bypass_limits && mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) { - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", false, strprintf("%d < %d", nModifiedFees, mempoolRejectFee)); + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", strprintf("%d < %d", nModifiedFees, mempoolRejectFee)); } // No transactions are allowed below minRelayTxFee except from disconnected blocks if (!bypass_limits && nModifiedFees < ::minRelayTxFee.GetFee(nSize)) { - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", false, strprintf("%d < %d", nModifiedFees, ::minRelayTxFee.GetFee(nSize))); + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", strprintf("%d < %d", nModifiedFees, ::minRelayTxFee.GetFee(nSize))); } if (nAbsurdFee && nFees > nAbsurdFee) - return state.Invalid(false, + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_HIGHFEE, "absurdly-high-fee", strprintf("%d > %d", nFees, nAbsurdFee)); @@ -764,7 +758,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; std::string errString; if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { - return state.DoS(0, false, REJECT_NONSTANDARD, "too-long-mempool-chain", false, errString); + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString); } // A transaction that spends outputs that would be replaced by it is invalid. Now @@ -776,8 +770,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool const uint256 &hashAncestor = ancestorIt->GetTx().GetHash(); if (setConflicts.count(hashAncestor)) { - return state.DoS(10, false, - REJECT_INVALID, "bad-txns-spends-conflicting-tx", false, + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-spends-conflicting-tx", strprintf("%s spends conflicting transaction %s", hash.ToString(), hashAncestor.ToString())); @@ -819,8 +812,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize()); if (newFeeRate <= oldFeeRate) { - return state.DoS(0, false, - REJECT_INSUFFICIENTFEE, "insufficient fee", false, + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee", strprintf("rejecting replacement %s; new feerate %s <= old feerate %s", hash.ToString(), newFeeRate.ToString(), @@ -848,8 +840,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool nConflictingSize += it->GetTxSize(); } } else { - return state.DoS(0, false, - REJECT_NONSTANDARD, "too many potential replacements", false, + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too many potential replacements", strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n", hash.ToString(), nConflictingCount, @@ -868,8 +859,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // it's cheaper to just check if the new input refers to a // tx that's in the mempool. if (pool.exists(tx.vin[j].prevout.hash)) { - return state.DoS(0, false, - REJECT_NONSTANDARD, "replacement-adds-unconfirmed", false, + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "replacement-adds-unconfirmed", strprintf("replacement %s adds unconfirmed input, idx %d", hash.ToString(), j)); } @@ -881,8 +871,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // transactions would not be paid for. if (nModifiedFees < nConflictingFees) { - return state.DoS(0, false, - REJECT_INSUFFICIENTFEE, "insufficient fee", false, + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee", strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s", hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees))); } @@ -892,8 +881,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool CAmount nDeltaFees = nModifiedFees - nConflictingFees; if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize)) { - return state.DoS(0, false, - REJECT_INSUFFICIENTFEE, "insufficient fee", false, + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee", strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s", hash.ToString(), FormatMoney(nDeltaFees), @@ -914,8 +902,10 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) && !CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) { // Only the witness is missing, so the transaction itself may be fine. - state.SetCorruptionPossible(); + state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, + state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage()); } + assert(IsTransactionReason(state.GetReason())); return false; // state filled in by CheckInputs } @@ -972,7 +962,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool if (!bypass_limits) { LimitMempoolSize(pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60); if (!pool.exists(hash)) - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool full"); + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool full"); } } @@ -1310,7 +1300,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(c } void CChainState::InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) { - if (!state.CorruptionPossible()) { + if (state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; m_failed_blocks.insert(pindex); setDirtyBlockIndex.insert(pindex); @@ -1378,6 +1368,9 @@ void InitScriptExecutionCache() { * which are matched. This is useful for checking blocks where we will likely never need the cache * entry again. * + * Note that we may set state.reason to NOT_STANDARD for extra soft-fork flags in flags, block-checking + * callers should probably reset it to CONSENSUS in such cases. + * * Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp */ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -1433,22 +1426,26 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi // Check whether the failure was caused by a // non-mandatory script verification check, such as // non-standard DER encodings or non-null dummy - // arguments; if so, don't trigger DoS protection to - // avoid splitting the network between upgraded and - // non-upgraded nodes. + // arguments; if so, ensure we return NOT_STANDARD + // instead of CONSENSUS to avoid downstream users + // splitting the network between upgraded and + // non-upgraded nodes by banning CONSENSUS-failing + // data providers. CScriptCheck check2(coin.out, tx, i, flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata); if (check2()) - return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); } - // Failures of other flags indicate a transaction that is - // invalid in new blocks, e.g. an invalid P2SH. We DoS ban - // such nodes as they are not following the protocol. That - // said during an upgrade careful thought should be taken - // as to the correct behavior - we may want to continue - // peering with non-upgraded nodes even after soft-fork - // super-majority signaling has occurred. - return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); + // MANDATORY flag failures correspond to + // ValidationInvalidReason::CONSENSUS. Because CONSENSUS + // failures are the most serious case of validation + // failures, we may need to consider using + // RECENT_CONSENSUS_CHANGE for any script failure that + // could be due to non-upgraded nodes which we may want to + // support, to avoid splitting the network (but this + // depends on the details of how net_processing handles + // such errors). + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); } } @@ -1808,7 +1805,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // re-enforce that rule here (at least until we make it impossible for // GetAdjustedTime() to go backward). if (!CheckBlock(block, state, chainparams.GetConsensus(), !fJustCheck, !fJustCheck)) { - if (state.CorruptionPossible()) { + if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED) { // We don't write down blocks to disk if they may have been // corrupted, so this should be impossible unless we're having hardware // problems. @@ -1943,7 +1940,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl for (const auto& tx : block.vtx) { for (size_t o = 0; o < tx->vout.size(); o++) { if (view.HaveCoin(COutPoint(tx->GetHash(), o))) { - return state.DoS(100, error("ConnectBlock(): tried to overwrite transaction"), + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): tried to overwrite transaction"), REJECT_INVALID, "bad-txns-BIP30"); } } @@ -1983,11 +1980,19 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl { CAmount txfee = 0; if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, txfee)) { + if (!IsBlockReason(state.GetReason())) { + // CheckTxInputs may return MISSING_INPUTS or + // PREMATURE_SPEND but we can't return that, as it's not + // defined for a block, so we reset the reason flag to + // CONSENSUS here. + state.Invalid(ValidationInvalidReason::CONSENSUS, false, + state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage()); + } return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state)); } nFees += txfee; if (!MoneyRange(nFees)) { - return state.DoS(100, error("%s: accumulated fee in the block out of range.", __func__), + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: accumulated fee in the block out of range.", __func__), REJECT_INVALID, "bad-txns-accumulated-fee-outofrange"); } @@ -2000,7 +2005,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl } if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) { - return state.DoS(100, error("%s: contains a non-BIP68-final transaction", __func__), + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: contains a non-BIP68-final transaction", __func__), REJECT_INVALID, "bad-txns-nonfinal"); } } @@ -2011,7 +2016,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // * witness (when witness enabled in flags and excludes coinbase) nSigOpsCost += GetTransactionSigOpCost(tx, view, flags); if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) - return state.DoS(100, error("ConnectBlock(): too many sigops"), + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): too many sigops"), REJECT_INVALID, "bad-blk-sigops"); txdata.emplace_back(tx); @@ -2019,9 +2024,20 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl { std::vector<CScriptCheck> vChecks; bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ - if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) + if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) { + if (state.GetReason() == ValidationInvalidReason::TX_NOT_STANDARD) { + // CheckInputs may return NOT_STANDARD for extra flags we passed, + // but we can't return that, as it's not defined for a block, so + // we reset the reason flag to CONSENSUS here. + // In the event of a future soft-fork, we may need to + // consider whether rewriting to CONSENSUS or + // RECENT_CONSENSUS_CHANGE would be more appropriate. + state.Invalid(ValidationInvalidReason::CONSENSUS, false, + state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage()); + } return error("ConnectBlock(): CheckInputs on %s failed with %s", tx.GetHash().ToString(), FormatStateMessage(state)); + } control.Add(vChecks); } @@ -2036,13 +2052,13 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus()); if (block.vtx[0]->GetValueOut() > blockReward) - return state.DoS(100, + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)", block.vtx[0]->GetValueOut(), blockReward), REJECT_INVALID, "bad-cb-amount"); if (!control.Wait()) - return state.DoS(100, error("%s: CheckQueue failed", __func__), REJECT_INVALID, "block-validation-failed"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: CheckQueue failed", __func__), REJECT_INVALID, "block-validation-failed"); int64_t nTime4 = GetTimeMicros(); nTimeVerify += nTime4 - nTime2; LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1, MILLI * (nTime4 - nTime2), nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal); @@ -2570,7 +2586,7 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) { if (state.IsInvalid()) { // The block violates a consensus rule. - if (!state.CorruptionPossible()) { + if (state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { InvalidChainFound(vpindexToConnect.front()); } state = CValidationState(); @@ -3068,7 +3084,7 @@ static bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, { // Check proof of work matches claimed amount if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) - return state.DoS(50, false, REJECT_INVALID, "high-hash", false, "proof of work failed"); + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "high-hash", "proof of work failed"); return true; } @@ -3090,13 +3106,13 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P bool mutated; uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated); if (block.hashMerkleRoot != hashMerkleRoot2) - return state.DoS(100, false, REJECT_INVALID, "bad-txnmrklroot", true, "hashMerkleRoot mismatch"); + return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-txnmrklroot", "hashMerkleRoot mismatch"); // Check for merkle tree malleability (CVE-2012-2459): repeating sequences // of transactions in a block without affecting the merkle root of a block, // while still invalidating it. if (mutated) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-duplicate", true, "duplicate transaction"); + return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-txns-duplicate", "duplicate transaction"); } // All potential-corruption validation must be done before we do any @@ -3107,19 +3123,19 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P // Size limits if (block.vtx.empty() || block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT || ::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) - return state.DoS(100, false, REJECT_INVALID, "bad-blk-length", false, "size limits failed"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-length", "size limits failed"); // First transaction must be coinbase, the rest must not be if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) - return state.DoS(100, false, REJECT_INVALID, "bad-cb-missing", false, "first tx is not coinbase"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-missing", "first tx is not coinbase"); for (unsigned int i = 1; i < block.vtx.size(); i++) if (block.vtx[i]->IsCoinBase()) - return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-multiple", "more than one coinbase"); // Check transactions for (const auto& tx : block.vtx) if (!CheckTransaction(*tx, state, true)) - return state.Invalid(false, state.GetRejectCode(), state.GetRejectReason(), + return state.Invalid(state.GetReason(), false, state.GetRejectCode(), state.GetRejectReason(), strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), state.GetDebugMessage())); unsigned int nSigOps = 0; @@ -3128,7 +3144,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P nSigOps += GetLegacySigOpCount(*tx); } if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST) - return state.DoS(100, false, REJECT_INVALID, "bad-blk-sigops", false, "out-of-bounds SigOpCount"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-sigops", "out-of-bounds SigOpCount"); if (fCheckPOW && fCheckMerkleRoot) block.fChecked = true; @@ -3237,7 +3253,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta // Check proof of work const Consensus::Params& consensusParams = params.GetConsensus(); if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) - return state.DoS(100, false, REJECT_INVALID, "bad-diffbits", false, "incorrect proof of work"); + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "bad-diffbits", "incorrect proof of work"); // Check against checkpoints if (fCheckpointsEnabled) { @@ -3246,23 +3262,23 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta // MapBlockIndex. CBlockIndex* pcheckpoint = GetLastCheckpoint(params.Checkpoints()); if (pcheckpoint && nHeight < pcheckpoint->nHeight) - return state.DoS(100, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint"); + return state.Invalid(ValidationInvalidReason::BLOCK_CHECKPOINT, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint"); } // Check timestamp against prev if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) - return state.Invalid(false, REJECT_INVALID, "time-too-old", "block's timestamp is too early"); + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "time-too-old", "block's timestamp is too early"); // Check timestamp if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME) - return state.Invalid(false, REJECT_INVALID, "time-too-new", "block timestamp too far in the future"); + return state.Invalid(ValidationInvalidReason::BLOCK_TIME_FUTURE, false, REJECT_INVALID, "time-too-new", "block timestamp too far in the future"); // Reject outdated version blocks when 95% (75% on testnet) of the network has upgraded: // check for version 2, 3 and 4 upgrades if((block.nVersion < 2 && nHeight >= consensusParams.BIP34Height) || (block.nVersion < 3 && nHeight >= consensusParams.BIP66Height) || (block.nVersion < 4 && nHeight >= consensusParams.BIP65Height)) - return state.Invalid(false, REJECT_OBSOLETE, strprintf("bad-version(0x%08x)", block.nVersion), + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_OBSOLETE, strprintf("bad-version(0x%08x)", block.nVersion), strprintf("rejected nVersion=0x%08x block", block.nVersion)); return true; @@ -3292,7 +3308,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // Check that all transactions are finalized for (const auto& tx : block.vtx) { if (!IsFinalTx(*tx, nHeight, nLockTimeCutoff)) { - return state.DoS(10, false, REJECT_INVALID, "bad-txns-nonfinal", false, "non-final transaction"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-nonfinal", "non-final transaction"); } } @@ -3302,7 +3318,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c CScript expect = CScript() << nHeight; if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() || !std::equal(expect.begin(), expect.end(), block.vtx[0]->vin[0].scriptSig.begin())) { - return state.DoS(100, false, REJECT_INVALID, "bad-cb-height", false, "block height mismatch in coinbase"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-height", "block height mismatch in coinbase"); } } @@ -3324,11 +3340,11 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // already does not permit it, it is impossible to trigger in the // witness tree. if (block.vtx[0]->vin[0].scriptWitness.stack.size() != 1 || block.vtx[0]->vin[0].scriptWitness.stack[0].size() != 32) { - return state.DoS(100, false, REJECT_INVALID, "bad-witness-nonce-size", true, strprintf("%s : invalid witness reserved value size", __func__)); + return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-witness-nonce-size", strprintf("%s : invalid witness reserved value size", __func__)); } CHash256().Write(hashWitness.begin(), 32).Write(&block.vtx[0]->vin[0].scriptWitness.stack[0][0], 32).Finalize(hashWitness.begin()); if (memcmp(hashWitness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) { - return state.DoS(100, false, REJECT_INVALID, "bad-witness-merkle-match", true, strprintf("%s : witness merkle commitment mismatch", __func__)); + return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-witness-merkle-match", strprintf("%s : witness merkle commitment mismatch", __func__)); } fHaveWitness = true; } @@ -3338,7 +3354,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c if (!fHaveWitness) { for (const auto& tx : block.vtx) { if (tx->HasWitness()) { - return state.DoS(100, false, REJECT_INVALID, "unexpected-witness", true, strprintf("%s : unexpected witness data found", __func__)); + return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "unexpected-witness", strprintf("%s : unexpected witness data found", __func__)); } } } @@ -3350,7 +3366,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // the block hash, so we couldn't mark the block as permanently // failed). if (GetBlockWeight(block) > MAX_BLOCK_WEIGHT) { - return state.DoS(100, false, REJECT_INVALID, "bad-blk-weight", false, strprintf("%s : weight limit failed", __func__)); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-weight", strprintf("%s : weight limit failed", __func__)); } return true; @@ -3370,7 +3386,7 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& if (ppindex) *ppindex = pindex; if (pindex->nStatus & BLOCK_FAILED_MASK) - return state.Invalid(error("%s: block %s is marked invalid", __func__, hash.ToString()), 0, "duplicate"); + return state.Invalid(ValidationInvalidReason::CACHED_INVALID, error("%s: block %s is marked invalid", __func__, hash.ToString()), 0, "duplicate"); return true; } @@ -3381,10 +3397,10 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& CBlockIndex* pindexPrev = nullptr; BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); if (mi == mapBlockIndex.end()) - return state.DoS(10, error("%s: prev block not found", __func__), 0, "prev-blk-not-found"); + return state.Invalid(ValidationInvalidReason::BLOCK_MISSING_PREV, error("%s: prev block not found", __func__), 0, "prev-blk-not-found"); pindexPrev = (*mi).second; if (pindexPrev->nStatus & BLOCK_FAILED_MASK) - return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_PREV, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); @@ -3421,7 +3437,7 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& setDirtyBlockIndex.insert(invalid_walk); invalid_walk = invalid_walk->pprev; } - return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_PREV, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); } } } @@ -3525,7 +3541,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CVali if (!CheckBlock(block, state, chainparams.GetConsensus()) || !ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) { - if (state.IsInvalid() && !state.CorruptionPossible()) { + assert(IsBlockReason(state.GetReason())); + if (state.IsInvalid() && state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; setDirtyBlockIndex.insert(pindex); } @@ -4735,7 +4752,7 @@ int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::D static const uint64_t MEMPOOL_DUMP_VERSION = 1; -bool LoadMempool() +bool LoadMempool(CTxMemPool& pool) { const CChainParams& chainparams = Params(); int64_t nExpiryTimeout = gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60; @@ -4770,12 +4787,12 @@ bool LoadMempool() CAmount amountdelta = nFeeDelta; if (amountdelta) { - mempool.PrioritiseTransaction(tx->GetHash(), amountdelta); + pool.PrioritiseTransaction(tx->GetHash(), amountdelta); } CValidationState state; if (nTime + nExpiryTimeout > nNow) { LOCK(cs_main); - AcceptToMemoryPoolWithTime(chainparams, mempool, state, tx, nullptr /* pfMissingInputs */, nTime, + AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, nullptr /* pfMissingInputs */, nTime, nullptr /* plTxnReplaced */, false /* bypass_limits */, 0 /* nAbsurdFee */, false /* test_accept */); if (state.IsValid()) { @@ -4785,7 +4802,7 @@ bool LoadMempool() // wallet(s) having loaded it while we were processing // mempool transactions; consider these as valid, instead of // failed, but mark them as 'already there' - if (mempool.exists(tx->GetHash())) { + if (pool.exists(tx->GetHash())) { ++already_there; } else { ++failed; @@ -4801,7 +4818,7 @@ bool LoadMempool() file >> mapDeltas; for (const auto& i : mapDeltas) { - mempool.PrioritiseTransaction(i.first, i.second); + pool.PrioritiseTransaction(i.first, i.second); } } catch (const std::exception& e) { LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing anyway.\n", e.what()); @@ -4812,7 +4829,7 @@ bool LoadMempool() return true; } -bool DumpMempool() +bool DumpMempool(const CTxMemPool& pool) { int64_t start = GetTimeMicros(); @@ -4823,11 +4840,11 @@ bool DumpMempool() LOCK(dump_mutex); { - LOCK(mempool.cs); - for (const auto &i : mempool.mapDeltas) { + LOCK(pool.cs); + for (const auto &i : pool.mapDeltas) { mapDeltas[i.first] = i.second; } - vinfo = mempool.infoAll(); + vinfo = pool.infoAll(); } int64_t mid = GetTimeMicros(); diff --git a/src/validation.h b/src/validation.h index 61a7a270fc..7ab6adaf33 100644 --- a/src/validation.h +++ b/src/validation.h @@ -142,7 +142,6 @@ extern CScript COINBASE_FLAGS; extern CCriticalSection cs_main; extern CBlockPolicyEstimator feeEstimator; extern CTxMemPool mempool; -extern std::atomic_bool g_is_mempool_loaded; typedef std::unordered_map<uint256, CBlockIndex*, BlockHasher> BlockMap; extern BlockMap& mapBlockIndex GUARDED_BY(cs_main); extern Mutex g_best_block_mutex; @@ -474,10 +473,10 @@ static const unsigned int REJECT_HIGHFEE = 0x100; CBlockFileInfo* GetBlockFileInfo(size_t n); /** Dump the mempool to disk. */ -bool DumpMempool(); +bool DumpMempool(const CTxMemPool& pool); /** Load the mempool from disk. */ -bool LoadMempool(); +bool LoadMempool(CTxMemPool& pool); //! Check whether the block associated with this index entry is pruned or not. inline bool IsBlockPruned(const CBlockIndex* pblockindex) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 5a22508c4b..626cdccfee 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -9,7 +9,6 @@ #include <core_io.h> #include <init.h> #include <interfaces/chain.h> -#include <validation.h> #include <key_io.h> #include <net.h> #include <node/transaction.h> @@ -27,10 +26,11 @@ #include <timedata.h> #include <util/bip32.h> #include <util/fees.h> -#include <util/system.h> #include <util/moneystr.h> +#include <util/system.h> #include <util/url.h> #include <util/validation.h> +#include <validation.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> #include <wallet/psbtwallet.h> @@ -70,14 +70,14 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques return wallets.size() == 1 || (request.fHelp && wallets.size() > 0) ? wallets[0] : nullptr; } -std::string HelpRequiringPassphrase(CWallet * const pwallet) +std::string HelpRequiringPassphrase(const CWallet* pwallet) { return pwallet && pwallet->IsCrypted() ? "\nRequires wallet passphrase to be set with walletpassphrase call." : ""; } -bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException) +bool EnsureWalletIsAvailable(const CWallet* pwallet, bool avoidException) { if (pwallet) return true; if (avoidException) return false; @@ -89,7 +89,7 @@ bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException) "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path)."); } -void EnsureWalletIsUnlocked(CWallet * const pwallet) +void EnsureWalletIsUnlocked(const CWallet* pwallet) { if (pwallet->IsLocked()) { throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); @@ -785,7 +785,7 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) if (request.fHelp || request.params.size() > 0) throw std::runtime_error( RPCHelpMan{"getunconfirmedbalance", - "Returns the server's total unconfirmed balance\n", + "DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n", {}, RPCResults{}, RPCExamples{""}, @@ -2373,6 +2373,68 @@ static UniValue settxfee(const JSONRPCRequest& request) return true; } +static UniValue getbalances(const JSONRPCRequest& request) +{ + std::shared_ptr<CWallet> const rpc_wallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(rpc_wallet.get(), request.fHelp)) { + return NullUniValue; + } + CWallet& wallet = *rpc_wallet; + + const RPCHelpMan help{ + "getbalances", + "Returns an object with all balances in " + CURRENCY_UNIT + ".\n", + {}, + RPCResult{ + "{\n" + " \"mine\": { (object) balances from outputs that the wallet can sign\n" + " \"trusted\": xxx (numeric) trusted balance (outputs created by the wallet or confirmed outputs)\n" + " \"untrusted_pending\": xxx (numeric) untrusted pending balance (outputs created by others that are in the mempool)\n" + " \"immature\": xxx (numeric) balance from immature coinbase outputs\n" + " },\n" + " \"watchonly\": { (object) watchonly balances (not present if wallet does not watch anything)\n" + " \"trusted\": xxx (numeric) trusted balance (outputs created by the wallet or confirmed outputs)\n" + " \"untrusted_pending\": xxx (numeric) untrusted pending balance (outputs created by others that are in the mempool)\n" + " \"immature\": xxx (numeric) balance from immature coinbase outputs\n" + " },\n" + "}\n"}, + RPCExamples{ + HelpExampleCli("getbalances", "") + + HelpExampleRpc("getbalances", "")}, + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + wallet.BlockUntilSyncedToCurrentChain(); + + auto locked_chain = wallet.chain().lock(); + LOCK(wallet.cs_wallet); + + UniValue obj(UniValue::VOBJ); + + const auto bal = wallet.GetBalance(); + UniValue balances{UniValue::VOBJ}; + { + UniValue balances_mine{UniValue::VOBJ}; + balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted)); + balances_mine.pushKV("untrusted_pending", ValueFromAmount(bal.m_mine_untrusted_pending)); + balances_mine.pushKV("immature", ValueFromAmount(bal.m_mine_immature)); + balances.pushKV("mine", balances_mine); + } + if (wallet.HaveWatchOnly()) { + UniValue balances_watchonly{UniValue::VOBJ}; + balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted)); + balances_watchonly.pushKV("untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending)); + balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature)); + balances.pushKV("watchonly", balances_watchonly); + } + return balances; +} + static UniValue getwalletinfo(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -2382,18 +2444,16 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( - RPCHelpMan{"getwalletinfo", + const RPCHelpMan help{"getwalletinfo", "Returns an object containing various wallet state info.\n", {}, RPCResult{ "{\n" " \"walletname\": xxxxx, (string) the wallet name\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n" - " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n" - " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n" - " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n" + " \"balance\": xxxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.trusted\n" + " \"unconfirmed_balance\": xxx, (numeric) DEPRECATED. Identical to getbalances().mine.untrusted_pending\n" + " \"immature_balance\": xxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.immature\n" " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n" " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n" @@ -2402,13 +2462,22 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n" " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n" + " \"scanning\": (json object) current scanning details, or false if no scan is in progress\n" + " {\n" + " \"duration\" : xxxx (numeric) elapsed seconds since scan start\n" + " \"progress\" : x.xxxx, (numeric) scanning progress percentage [0.0, 1.0]\n" + " }\n" "}\n" }, RPCExamples{ HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "") }, - }.ToString()); + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2441,6 +2510,14 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) obj.pushKV("hdseedid", seed_id.GetHex()); } obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + if (pwallet->IsScanning()) { + UniValue scanning(UniValue::VOBJ); + scanning.pushKV("duration", pwallet->ScanningDuration() / 1000); + scanning.pushKV("progress", pwallet->ScanningProgress()); + obj.pushKV("scanning", scanning); + } else { + obj.pushKV("scanning", false); + } return obj; } @@ -4073,6 +4150,7 @@ static const CRPCCommand commands[] = { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} }, { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} }, + { "wallet", "getbalances", &getbalances, {} }, { "wallet", "getwalletinfo", &getwalletinfo, {} }, { "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} }, { "wallet", "importmulti", &importmulti, {"requests","options"} }, diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 7cf607ccc7..90617472cc 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -31,9 +31,9 @@ void RegisterWalletRPCCommands(interfaces::Chain& chain, std::vector<std::unique */ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request); -std::string HelpRequiringPassphrase(CWallet *); -void EnsureWalletIsUnlocked(CWallet *); -bool EnsureWalletIsAvailable(CWallet *, bool avoidException); +std::string HelpRequiringPassphrase(const CWallet*); +void EnsureWalletIsUnlocked(const CWallet*); +bool EnsureWalletIsAvailable(const CWallet*, bool avoidException); UniValue getaddressinfo(const JSONRPCRequest& request); UniValue signrawtransactionwithwallet(const JSONRPCRequest& request); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4ab94f0c2c..1a9e640a8a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1287,24 +1287,12 @@ void CWallet::UpdatedBlockTip() void CWallet::BlockUntilSyncedToCurrentChain() { AssertLockNotHeld(cs_wallet); - - { - // Skip the queue-draining stuff if we know we're caught up with - // chainActive.Tip()... - // We could also take cs_wallet here, and call m_last_block_processed - // protected by cs_wallet instead of cs_main, but as long as we need - // cs_main here anyway, it's easier to just call it cs_main-protected. - auto locked_chain = chain().lock(); - - if (!m_last_block_processed.IsNull() && locked_chain->isPotentialTip(m_last_block_processed)) { - return; - } - } - - // ...otherwise put a callback in the validation interface queue and wait + // Skip the queue-draining stuff if we know we're caught up with + // chainActive.Tip(), otherwise put a callback in the validation interface queue and wait // for the queue to drain enough to execute it (indicating we are caught up // at least with the time we entered this function). - chain().waitForNotifications(); + uint256 last_block_hash = WITH_LOCK(cs_wallet, return m_last_block_processed); + chain().waitForNotificationsIfNewBlocksConnected(last_block_hash); } @@ -1798,8 +1786,9 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc } double progress_current = progress_begin; while (block_height && !fAbortRescan && !chain().shutdownRequested()) { + m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin); if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) { - ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100)))); + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 900af75f4f..30ea51c8a2 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -596,6 +596,8 @@ class CWallet final : public CCryptoKeyStore, private interfaces::Chain::Notific private: std::atomic<bool> fAbortRescan{false}; std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver + std::atomic<int64_t> m_scanning_start{0}; + std::atomic<double> m_scanning_progress{0}; std::mutex mutexScanning; friend class WalletRescanReserver; @@ -693,7 +695,7 @@ private: * to have seen all transactions in the chain, but is only used to track * live BlockConnected callbacks. */ - uint256 m_last_block_processed; + uint256 m_last_block_processed GUARDED_BY(cs_wallet); public: /* @@ -820,6 +822,8 @@ public: void AbortRescan() { fAbortRescan = true; } bool IsAbortingRescan() { return fAbortRescan; } bool IsScanning() { return fScanningWallet; } + int64_t ScanningDuration() const { return fScanningWallet ? GetTimeMillis() - m_scanning_start : 0; } + double ScanningProgress() const { return fScanningWallet ? (double) m_scanning_progress : 0; } /** * keystore implementation @@ -1241,6 +1245,8 @@ public: if (m_wallet->fScanningWallet) { return false; } + m_wallet->m_scanning_start = GetTimeMillis(); + m_wallet->m_scanning_progress = 0; m_wallet->fScanningWallet = true; m_could_reserve = true; return true; diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index 5bb3b5c094..45ecaabe14 100755 --- a/test/functional/combine_logs.py +++ b/test/functional/combine_logs.py @@ -11,6 +11,7 @@ from collections import defaultdict, namedtuple import heapq import itertools import os +import pathlib import re import sys import tempfile @@ -51,9 +52,23 @@ def main(): if not args.testdir: print("Opening latest test directory: {}".format(testdir), file=sys.stderr) + colors = defaultdict(lambda: '') + if args.color: + colors["test"] = "\033[0;36m" # CYAN + colors["node0"] = "\033[0;34m" # BLUE + colors["node1"] = "\033[0;32m" # GREEN + colors["node2"] = "\033[0;31m" # RED + colors["node3"] = "\033[0;33m" # YELLOW + colors["reset"] = "\033[0m" # Reset font color + log_events = read_logs(testdir) - print_logs(log_events, color=args.color, html=args.html) + if args.html: + print_logs_html(log_events) + else: + print_logs_plain(log_events, colors) + print_node_warnings(testdir, colors) + def read_logs(tmp_dir): """Reads log files. @@ -71,6 +86,26 @@ def read_logs(tmp_dir): return heapq.merge(*[get_log_events(source, f) for source, f in files]) +def print_node_warnings(tmp_dir, colors): + """Print nodes' errors and warnings""" + + warnings = [] + for stream in ['stdout', 'stderr']: + for i in itertools.count(): + folder = "{}/node{}/{}".format(tmp_dir, i, stream) + if not os.path.isdir(folder): + break + for (_, _, fns) in os.walk(folder): + for fn in fns: + warning = pathlib.Path('{}/{}'.format(folder, fn)).read_text().strip() + if warning: + warnings.append(("node{} {}".format(i, stream), warning)) + + print() + for w in warnings: + print("{} {} {} {}".format(colors[w[0].split()[0]], w[0], w[1], colors["reset"])) + + def find_latest_test_dir(): """Returns the latest tmpfile test directory prefix.""" tmpdir = tempfile.gettempdir() @@ -127,18 +162,9 @@ def get_log_events(source, logfile): except FileNotFoundError: print("File %s could not be opened. Continuing without it." % logfile, file=sys.stderr) -def print_logs(log_events, color=False, html=False): - """Renders the iterator of log events into text or html.""" - if not html: - colors = defaultdict(lambda: '') - if color: - colors["test"] = "\033[0;36m" # CYAN - colors["node0"] = "\033[0;34m" # BLUE - colors["node1"] = "\033[0;32m" # GREEN - colors["node2"] = "\033[0;31m" # RED - colors["node3"] = "\033[0;33m" # YELLOW - colors["reset"] = "\033[0m" # Reset font color +def print_logs_plain(log_events, colors): + """Renders the iterator of log events into text.""" for event in log_events: lines = event.event.splitlines() print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, lines[0], colors["reset"])) @@ -146,7 +172,9 @@ def print_logs(log_events, color=False, html=False): for line in lines[1:]: print("{0}{1}{2}".format(colors[event.source.rstrip()], line, colors["reset"])) - else: + +def print_logs_html(log_events): + """Renders the iterator of log events into html.""" try: import jinja2 except ImportError: diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index d262dae5aa..454eb583f7 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -58,7 +58,7 @@ class BadTxTemplate: class OutputMissing(BadTxTemplate): reject_reason = "bad-txns-vout-empty" - expect_disconnect = False + expect_disconnect = True def get_tx(self): tx = CTransaction() @@ -69,7 +69,7 @@ class OutputMissing(BadTxTemplate): class InputMissing(BadTxTemplate): reject_reason = "bad-txns-vin-empty" - expect_disconnect = False + expect_disconnect = True # We use a blank transaction here to make sure # it is interpreted as a non-witness transaction. diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 72eb4f804f..3ad83cd2b3 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -281,7 +281,7 @@ class FullBlockTest(BitcoinTestFramework): self.log.info("Reject a block spending an immature coinbase.") self.move_tip(15) b20 = self.next_block(20, spend=out[7]) - self.send_blocks([b20], success=False, reject_reason='bad-txns-premature-spend-of-coinbase') + self.send_blocks([b20], success=False, reject_reason='bad-txns-premature-spend-of-coinbase', reconnect=True) # Attempt to spend a coinbase at depth too low (on a fork this time) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) @@ -294,7 +294,7 @@ class FullBlockTest(BitcoinTestFramework): self.send_blocks([b21], False) b22 = self.next_block(22, spend=out[5]) - self.send_blocks([b22], success=False, reject_reason='bad-txns-premature-spend-of-coinbase') + self.send_blocks([b22], success=False, reject_reason='bad-txns-premature-spend-of-coinbase', reconnect=True) # Create a block on either side of MAX_BLOCK_BASE_SIZE and make sure its accepted/rejected # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) @@ -616,7 +616,7 @@ class FullBlockTest(BitcoinTestFramework): while b47.sha256 < target: b47.nNonce += 1 b47.rehash() - self.send_blocks([b47], False, force_send=True, reject_reason='high-hash') + self.send_blocks([b47], False, force_send=True, reject_reason='high-hash', reconnect=True) self.log.info("Reject a block with a timestamp >2 hours in the future") self.move_tip(44) @@ -667,7 +667,7 @@ class FullBlockTest(BitcoinTestFramework): b54 = self.next_block(54, spend=out[15]) b54.nTime = b35.nTime - 1 b54.solve() - self.send_blocks([b54], False, force_send=True, reject_reason='time-too-old') + self.send_blocks([b54], False, force_send=True, reject_reason='time-too-old', reconnect=True) # valid timestamp self.move_tip(53) @@ -813,7 +813,7 @@ class FullBlockTest(BitcoinTestFramework): assert tx.vin[0].nSequence < 0xffffffff tx.calc_sha256() b62 = self.update_block(62, [tx]) - self.send_blocks([b62], success=False, reject_reason='bad-txns-nonfinal') + self.send_blocks([b62], success=False, reject_reason='bad-txns-nonfinal', reconnect=True) # Test a non-final coinbase is also rejected # @@ -827,7 +827,7 @@ class FullBlockTest(BitcoinTestFramework): b63.vtx[0].vin[0].nSequence = 0xDEADBEEF b63.vtx[0].rehash() b63 = self.update_block(63, []) - self.send_blocks([b63], success=False, reject_reason='bad-txns-nonfinal') + self.send_blocks([b63], success=False, reject_reason='bad-txns-nonfinal', reconnect=True) # This checks that a block with a bloated VARINT between the block_header and the array of tx such that # the block is > MAX_BLOCK_BASE_SIZE with the bloated varint, but <= MAX_BLOCK_BASE_SIZE without the bloated varint, @@ -1241,7 +1241,7 @@ class FullBlockTest(BitcoinTestFramework): self.log.info("Reject a block with an invalid block header version") b_v1 = self.next_block('b_v1', version=1) - self.send_blocks([b_v1], success=False, force_send=True, reject_reason='bad-version(0x00000001)') + self.send_blocks([b_v1], success=False, force_send=True, reject_reason='bad-version(0x00000001)', reconnect=True) self.move_tip(chain1_tip + 2) b_cb34 = self.next_block('b_cb34', version=4) diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py index d74d4eaaf1..bb0169ee52 100755 --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -37,7 +37,6 @@ Test is as follows: """ from decimal import Decimal import os -import time from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, wait_until @@ -83,9 +82,10 @@ class MempoolPersistTest(BitcoinTestFramework): self.start_node(1, extra_args=["-persistmempool=0"]) self.start_node(0) self.start_node(2) - # Give bitcoind a second to reload the mempool - wait_until(lambda: len(self.nodes[0].getrawmempool()) == 5, timeout=1) - wait_until(lambda: len(self.nodes[2].getrawmempool()) == 5, timeout=1) + wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"], timeout=1) + wait_until(lambda: self.nodes[2].getmempoolinfo()["loaded"], timeout=1) + assert_equal(len(self.nodes[0].getrawmempool()), 5) + assert_equal(len(self.nodes[2].getrawmempool()), 5) # The others have loaded their mempool. If node_1 loaded anything, we'd probably notice by now: assert_equal(len(self.nodes[1].getrawmempool()), 0) @@ -100,14 +100,14 @@ class MempoolPersistTest(BitcoinTestFramework): self.log.debug("Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.") self.stop_nodes() self.start_node(0, extra_args=["-persistmempool=0"]) - # Give bitcoind a second to reload the mempool - time.sleep(1) + wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"]) assert_equal(len(self.nodes[0].getrawmempool()), 0) self.log.debug("Stop-start node0. Verify that it has the transactions in its mempool.") self.stop_nodes() self.start_node(0) - wait_until(lambda: len(self.nodes[0].getrawmempool()) == 5) + wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"]) + assert_equal(len(self.nodes[0].getrawmempool()), 5) mempooldat0 = os.path.join(self.nodes[0].datadir, 'regtest', 'mempool.dat') mempooldat1 = os.path.join(self.nodes[1].datadir, 'regtest', 'mempool.dat') @@ -120,7 +120,8 @@ class MempoolPersistTest(BitcoinTestFramework): os.rename(mempooldat0, mempooldat1) self.stop_nodes() self.start_node(1, extra_args=[]) - wait_until(lambda: len(self.nodes[1].getrawmempool()) == 5) + wait_until(lambda: self.nodes[1].getmempoolinfo()["loaded"]) + assert_equal(len(self.nodes[1].getrawmempool()), 5) self.log.debug("Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails") # to test the exception we are creating a tmp folder called mempool.dat.new diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index d406ee3229..ec5d6f1715 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -66,10 +66,16 @@ if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 +EXTENDED_SCRIPTS = [ + # These tests are not run by the travis build process. + # Longest test should go first, to favor running tests in parallel + 'feature_pruning.py', + 'feature_dbcrash.py', +] + BASE_SCRIPTS = [ # Scripts that are run by the travis build process. # Longest test should go first, to favor running tests in parallel - 'feature_pruning.py', 'feature_fee_estimation.py', 'wallet_hd.py', 'wallet_backup.py', @@ -197,12 +203,6 @@ BASE_SCRIPTS = [ # Put them in a random line within the section that fits their approximate run-time ] -EXTENDED_SCRIPTS = [ - # These tests are not run by the travis build process. - # Longest test should go first, to favor running tests in parallel - 'feature_dbcrash.py', -] - # Place EXTENDED_SCRIPTS first since it has the 3 longest running tests ALL_SCRIPTS = EXTENDED_SCRIPTS + BASE_SCRIPTS diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index e2a20beec5..4d1f1ccdc1 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -4,20 +4,23 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet balance RPC methods.""" from decimal import Decimal +import struct +from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE as ADDRESS_WATCHONLY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, + connect_nodes_bi, + sync_blocks, ) -RANDOM_COINBASE_ADDRESS = 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ' def create_transactions(node, address, amt, fees): # Create and sign raw transactions from node to address for amt. # Creates a transaction for each fee and returns an array # of the raw transactions. - utxos = node.listunspent(0) + utxos = [u for u in node.listunspent(0) if u['spendable']] # Create transactions inputs = [] @@ -25,7 +28,7 @@ def create_transactions(node, address, amt, fees): for utxo in utxos: inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]}) ins_total += utxo['amount'] - if ins_total > amt: + if ins_total + max(fees) > amt: break txs = [] @@ -33,6 +36,7 @@ def create_transactions(node, address, amt, fees): outputs = {address: amt, node.getrawchangeaddress(): ins_total - amt - fee} raw_tx = node.createrawtransaction(inputs, outputs, 0, True) raw_tx = node.signrawtransactionwithwallet(raw_tx) + assert_equal(raw_tx['complete'], True) txs.append(raw_tx) return txs @@ -41,31 +45,48 @@ class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True + self.extra_args = [ + ['-limitdescendantcount=3'], # Limit mempool descendants as a hack to have wallet txs rejected from the mempool + [], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): + self.nodes[0].importaddress(ADDRESS_WATCHONLY) # Check that nodes don't own any UTXOs assert_equal(len(self.nodes[0].listunspent()), 0) assert_equal(len(self.nodes[1].listunspent()), 0) - self.log.info("Mining one block for each node") + self.log.info("Check that only node 0 is watching an address") + assert 'watchonly' in self.nodes[0].getbalances() + assert 'watchonly' not in self.nodes[1].getbalances() + self.log.info("Mining blocks ...") self.nodes[0].generate(1) self.sync_all() self.nodes[1].generate(1) - self.nodes[1].generatetoaddress(100, RANDOM_COINBASE_ADDRESS) + self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY) self.sync_all() + assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50) + assert_equal(self.nodes[0].getwalletinfo()['balance'], 50) + assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50) + + assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 5000) + assert 'watchonly' not in self.nodes[1].getbalances() + assert_equal(self.nodes[0].getbalance(), 50) assert_equal(self.nodes[1].getbalance(), 50) self.log.info("Test getbalance with different arguments") assert_equal(self.nodes[0].getbalance("*"), 50) assert_equal(self.nodes[0].getbalance("*", 1), 50) - assert_equal(self.nodes[0].getbalance("*", 1, True), 50) + assert_equal(self.nodes[0].getbalance("*", 1, True), 100) assert_equal(self.nodes[0].getbalance(minconf=1), 50) + assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 100) + assert_equal(self.nodes[1].getbalance(minconf=0, include_watchonly=True), 50) # Send 40 BTC from 0 to 1 and 60 BTC from 1 to 0. txs = create_transactions(self.nodes[0], self.nodes[1].getnewaddress(), 40, [Decimal('0.01')]) @@ -83,32 +104,37 @@ class WalletTest(BitcoinTestFramework): self.log.info("Test getbalance and getunconfirmedbalance with unconfirmed inputs") - # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions - assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send - assert_equal(self.nodes[1].getbalance(), Decimal('29.99')) # change from node 1's send - # Same with minconf=0 - assert_equal(self.nodes[0].getbalance(minconf=0), Decimal('9.99')) - assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('29.99')) - # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago - # TODO: fix getbalance tracking of coin spentness depth - assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0')) - assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0')) - # getunconfirmedbalance - assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend - assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent + def test_balances(*, fee_node_1=0): + # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions + assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send + assert_equal(self.nodes[1].getbalance(), Decimal('30') - fee_node_1) # change from node 1's send + # Same with minconf=0 + assert_equal(self.nodes[0].getbalance(minconf=0), Decimal('9.99')) + assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('30') - fee_node_1) + # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago + # TODO: fix getbalance tracking of coin spentness depth + assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0')) + assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0')) + # getunconfirmedbalance + assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend + assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('60')) + assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60')) + + assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent + assert_equal(self.nodes[1].getbalances()['mine']['untrusted_pending'], Decimal('0')) + assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0')) + + test_balances(fee_node_1=Decimal('0.01')) # Node 1 bumps the transaction fee and resends self.nodes[1].sendrawtransaction(txs[1]['hex']) + self.nodes[0].sendrawtransaction(txs[1]['hex']) # sending on both nodes is faster than waiting for propagation self.sync_all() self.log.info("Test getbalance and getunconfirmedbalance with conflicted unconfirmed inputs") + test_balances(fee_node_1=Decimal('0.02')) - assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60')) # output of node 1's send - assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) - assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0')) # Doesn't include output of node 0's send since it was spent - assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) - - self.nodes[1].generatetoaddress(1, RANDOM_COINBASE_ADDRESS) + self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY) self.sync_all() # balances are correct after the transactions are confirmed @@ -118,7 +144,7 @@ class WalletTest(BitcoinTestFramework): # Send total balance away from node 1 txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), Decimal('29.97'), [Decimal('0.01')]) self.nodes[1].sendrawtransaction(txs[0]['hex']) - self.nodes[1].generatetoaddress(2, RANDOM_COINBASE_ADDRESS) + self.nodes[1].generatetoaddress(2, ADDRESS_WATCHONLY) self.sync_all() # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago @@ -140,6 +166,51 @@ class WalletTest(BitcoinTestFramework): after = self.nodes[1].getunconfirmedbalance() assert_equal(before + Decimal('0.1'), after) + # Create 3 more wallet txs, where the last is not accepted to the + # mempool because it is the third descendant of the tx above + for _ in range(3): + # Set amount high enough such that all coins are spent by each tx + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 99) + + self.log.info('Check that wallet txs not in the mempool are untrusted') + assert txid not in self.nodes[0].getrawmempool() + assert_equal(self.nodes[0].gettransaction(txid)['trusted'], False) + assert_equal(self.nodes[0].getbalance(minconf=0), 0) + + self.log.info("Test replacement and reorg of non-mempool tx") + tx_orig = self.nodes[0].gettransaction(txid)['hex'] + # Increase fee by 1 coin + tx_replace = tx_orig.replace( + struct.pack("<q", 99 * 10**8).hex(), + struct.pack("<q", 98 * 10**8).hex(), + ) + tx_replace = self.nodes[0].signrawtransactionwithwallet(tx_replace)['hex'] + # Total balance is given by the sum of outputs of the tx + total_amount = sum([o['value'] for o in self.nodes[0].decoderawtransaction(tx_replace)['vout']]) + self.sync_all() + self.nodes[1].sendrawtransaction(hexstring=tx_replace, maxfeerate=0) + + # Now confirm tx_replace + block_reorg = self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY)[0] + self.sync_all() + assert_equal(self.nodes[0].getbalance(minconf=0), total_amount) + + self.log.info('Put txs back into mempool of node 1 (not node 0)') + self.nodes[0].invalidateblock(block_reorg) + self.nodes[1].invalidateblock(block_reorg) + assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted + self.nodes[0].generatetoaddress(1, ADDRESS_WATCHONLY) + assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted + + # Now confirm tx_orig + self.restart_node(1, ['-persistmempool=0']) + connect_nodes_bi(self.nodes, 0, 1) + sync_blocks(self.nodes) + self.nodes[1].sendrawtransaction(tx_orig) + self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY) + self.sync_all() + assert_equal(self.nodes[0].getbalance(minconf=0), total_amount + 1) # The reorg recovered our fee of 1 coin + if __name__ == '__main__': WalletTest().main() |