diff options
32 files changed, 619 insertions, 360 deletions
diff --git a/.travis.yml b/.travis.yml index 7abc6e8ffd..258fd1b1c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -dist: trusty +dist: xenial os: linux language: minimal cache: diff --git a/.travis/test_04_install.sh b/.travis/test_04_install.sh index 3f74158117..a111387f10 100755 --- a/.travis/test_04_install.sh +++ b/.travis/test_04_install.sh @@ -15,6 +15,8 @@ export UBSAN_OPTIONS="suppressions=${TRAVIS_BUILD_DIR}/test/sanitizer_suppressio env | grep -E '^(BITCOIN_CONFIG|CCACHE_|WINEDEBUG|LC_ALL|BOOST_TEST_RANDOM|CONFIG_SHELL|(ASAN|LSAN|TSAN|UBSAN)_OPTIONS)' | tee /tmp/env if [[ $HOST = *-mingw32 ]]; then DOCKER_ADMIN="--cap-add SYS_ADMIN" +elif [[ $BITCOIN_CONFIG = *--with-sanitizers=*address* ]]; then # If ran with (ASan + LSan), Docker needs access to ptrace (https://github.com/google/sanitizers/issues/764) + DOCKER_ADMIN="--cap-add SYS_PTRACE" fi DOCKER_ID=$(docker run $DOCKER_ADMIN -idt --mount type=bind,src=$TRAVIS_BUILD_DIR,dst=$TRAVIS_BUILD_DIR --mount type=bind,src=$CCACHE_DIR,dst=$CCACHE_DIR -w $TRAVIS_BUILD_DIR --env-file /tmp/env $DOCKER_NAME_TAG) diff --git a/build_msvc/README.md b/build_msvc/README.md index 5fb08df8d7..63c5babf35 100644 --- a/build_msvc/README.md +++ b/build_msvc/README.md @@ -31,20 +31,13 @@ Additional dependencies required from the [bitcoin-core](https://github.com/bitc Building --------------------- -The instructions below use vcpkg to install the dependencies. +The instructions below use `vcpkg` to install the dependencies. -- Clone and vcpkg from the [github repository](https://github.com/Microsoft/vcpkg) and install as per the instructions in the main README.md. +- Clone `vcpkg` from the [github repository](https://github.com/Microsoft/vcpkg) and install as per the instructions in the main README.md. - Install the required packages (replace x64 with x86 as required): -- Install the required dependencies with vcpkg: ``` - PS >.\vcpkg install boost:x64-windows-static ` - libevent:x64-windows-static ` - openssl:x64-windows-static ` - zeromq:x64-windows-static ` - berkeleydb:x64-windows-static ` - secp256k1:x64-windows-static ` - leveldb:x64-windows-static + PS >.\vcpkg install --triplet x64-windows-static boost-filesystem boost-signals2 boost-test libevent openssl zeromq berkeleydb secp256k1 leveldb ``` - Use Python to generate *.vcxproj from Makefile @@ -53,4 +46,4 @@ The instructions below use vcpkg to install the dependencies. PS >python msvc-autogen.py ``` -- Build in Visual Studio.
\ No newline at end of file +- Build in Visual Studio. diff --git a/doc/build-netbsd.md b/doc/build-netbsd.md index 5bf2d6b59b..ab422f6aa7 100644 --- a/doc/build-netbsd.md +++ b/doc/build-netbsd.md @@ -1,6 +1,6 @@ NetBSD build guide ====================== -(updated for NetBSD 7.0) +(updated for NetBSD 8.0) This guide describes how to build bitcoind and command-line utilities on NetBSD. @@ -15,21 +15,38 @@ You will need the following modules, which can be installed via pkgsrc or pkgin: autoconf automake boost -db4 git gmake libevent libtool -python27 -``` +pkg-config +python37 -Download the source code: -``` -git clone https://github.com/bitcoin/bitcoin +git clone https://github.com/bitcoin/bitcoin.git ``` See [dependencies.md](dependencies.md) for a complete overview. +### Building BerkeleyDB + +BerkeleyDB is only necessary for the wallet functionality. To skip this, pass +`--disable-wallet` to `./configure` and skip to the next section. + +It is recommended to use Berkeley DB 4.8. You cannot use the BerkeleyDB library +from ports, for the same reason as boost above (g++/libstd++ incompatibility). +If you have to build it yourself, you can use [the installation script included +in contrib/](/contrib/install_db4.sh) like so: + +```shell +./contrib/install_db4.sh `pwd` +``` + +from the root of the repository. Then set `BDB_PREFIX` for the next section: + +```shell +export BDB_PREFIX="$PWD/db4" +``` + ### Building Bitcoin Core **Important**: Use `gmake` (the non-GNU `make` will exit with an error). @@ -37,13 +54,26 @@ See [dependencies.md](dependencies.md) for a complete overview. With wallet: ``` ./autogen.sh -./configure CPPFLAGS="-I/usr/pkg/include" LDFLAGS="-L/usr/pkg/lib" BOOST_CPPFLAGS="-I/usr/pkg/include" BOOST_LDFLAGS="-L/usr/pkg/lib" -gmake +./configure --with-gui=no CPPFLAGS="-I/usr/pkg/include" \ + LDFLAGS="-L/usr/pkg/lib" \ + BOOST_CPPFLAGS="-I/usr/pkg/include" \ + BOOST_LDFLAGS="-L/usr/pkg/lib" \ + BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \ + BDB_CFLAGS="-I${BDB_PREFIX}/include" ``` Without wallet: ``` ./autogen.sh -./configure --disable-wallet CPPFLAGS="-I/usr/pkg/include" LDFLAGS="-L/usr/pkg/lib" BOOST_CPPFLAGS="-I/usr/pkg/include" BOOST_LDFLAGS="-L/usr/pkg/lib" -gmake +./configure --with-gui=no --disable-wallet \ + CPPFLAGS="-I/usr/pkg/include" \ + LDFLAGS="-L/usr/pkg/lib" \ + BOOST_CPPFLAGS="-I/usr/pkg/include" \ + BOOST_LDFLAGS="-L/usr/pkg/lib" +``` + +Build and run the tests: +```bash +gmake # use -jX here for parallelism +gmake check ``` diff --git a/doc/psbt.md b/doc/psbt.md index 7e6a93714d..560b45ef31 100644 --- a/doc/psbt.md +++ b/doc/psbt.md @@ -124,7 +124,7 @@ does not need to be involved. - Finally anyone can broadcast the transaction using `sendrawtransaction "T"`. In case there are more signers, it may be advantageous to let them all sign in -parallel, rather passing the PSBT from one signer to the next one. In the +parallel, rather than passing the PSBT from one signer to the next one. In the above example this would translate to Carol handing a copy of *P* to each signer separately. They can then all invoke `walletprocesspsbt "P"`, and end up with their individually-signed PSBT structures. They then all send those back to diff --git a/doc/release-notes-14565.md b/doc/release-notes-14565.md new file mode 100644 index 0000000000..38d76fee46 --- /dev/null +++ b/doc/release-notes-14565.md @@ -0,0 +1,5 @@ +Low-level RPC changes +--------------------- + +The `importmulti` RPC will now contain a new per-request `warnings` field with strings +that explain when fields are being ignored or inconsistant, if any. diff --git a/doc/release-notes/release-notes-0.17.1.md b/doc/release-notes/release-notes-0.17.1.md new file mode 100644 index 0000000000..b1e50e0391 --- /dev/null +++ b/doc/release-notes/release-notes-0.17.1.md @@ -0,0 +1,168 @@ +Bitcoin Core version 0.17.1 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.17.1/> + +or through BitTorrent: + + magnet:?xt=urn:btih:c56c87ccfaa8e6fbccc90d549121e61efd97cb6f&dn=bitcoin-core-0.17.1&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Fzer0day.ch%3A1337&tr=udp%3A%2F%2Fexplodie.org%3A6969 + +This is a new minor version release, with various bugfixes +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). + +If your node has a txindex, the txindex db will be migrated the first time you run 0.17.0 or newer, which may take up to a few hours. Your node will not be functional until this migration completes. + +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. Upgrading +directly from 0.7.x and earlier without redownloading the blockchain is not supported. +However, as usual, old wallet versions are still supported. + +Downgrading warning +------------------- + +The chainstate database for this release is not compatible with previous +releases, so if you run 0.15 and then decide to switch back to any +older version, you will need to run the old release with the `-reindex-chainstate` +option to rebuild the chainstate data structures in the old format. + +If your node has pruning enabled, this will entail re-downloading and +processing the entire blockchain. + +Compatibility +============== + +Bitcoin Core is extensively tested on multiple operating systems using +the Linux kernel, macOS 10.10+, and Windows 7 and newer (Windows XP is not supported). + +Bitcoin Core should also work on most other Unix-like systems but is not +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. + +Notable changes +=============== + +`listtransactions` label support +-------------------------------- + +The `listtransactions` RPC `account` parameter which was deprecated in 0.17.0 +and renamed to `dummy` has been un-deprecated and renamed again to `label`. + +When bitcoin is configured with the `-deprecatedrpc=accounts` setting, specifying +a label/account/dummy argument will return both outgoing and incoming +transactions. Without the `-deprecatedrpc=accounts` setting, it will only return +incoming transactions (because it used to be possible to create transactions +spending from specific accounts, but this is no longer possible with labels). + +When `-deprecatedrpc=accounts` is set, it's possible to pass the empty string "" +to list transactions that don't have any label. Without +`-deprecatedrpc=accounts`, passing the empty string is an error because returning +only non-labeled transactions is not generally useful behavior and can cause +confusion. + +0.17.1 change log +================= + +### P2P protocol and network code +- #14685 `9406502` Fix a deserialization overflow edge case (kazcw) +- #14728 `b901578` Fix uninitialized read when stringifying an addrLocal (kazcw) + +### Wallet +- #14441 `5150acc` Restore ability to list incoming transactions by label (jnewbery) +- #13546 `91fa15a` Fix use of uninitialized value `bnb_used` in CWallet::CreateTransaction(…) (practicalswift) +- #14310 `bb90695` Ensure wallet is unlocked before signing (gustavonalle) +- #14690 `5782fdc` Throw error if CPubKey is invalid during PSBT keypath serialization (instagibbs) +- #14852 `2528443` backport: [tests] Add `wallet_balance.py` (MarcoFalke) +- #14196 `3362a95` psbt: always drop the unnecessary utxo and convert non-witness utxo to witness when necessary (achow101) +- #14588 `70ee1f8` Refactor PSBT signing logic to enforce invariant and fix signing bug (gwillen) +- #14424 `89a9a9d` Stop requiring imported pubkey to sign non-PKH schemes (sipa, MeshCollider) + +### RPC and other APIs +- #14417 `fb9ad04` Fix listreceivedbyaddress not taking address as a string (etscrivner) +- #14596 `de5e48a` Bugfix: RPC: Add `address_type` named param for createmultisig (luke-jr) +- #14618 `9666dba` Make HTTP RPC debug logging more informative (practicalswift) +- #14197 `7bee414` [psbt] Convert non-witness UTXOs to witness if witness sig created (achow101) +- #14377 `a3fe125` Check that a separator is found for psbt inputs, outputs, and global map (achow101) +- #14356 `7a590d8` Fix converttopsbt permitsigdata arg, add basic test (instagibbs) +- #14453 `75b5d8c` Fix wallet unload during walletpassphrase timeout (promag) + +### GUI +- #14403 `0242b5a` Revert "Force TLS1.0+ for SSL connections" (real-or-random) +- #14593 `df5131b` Explicitly disable "Dark Mode" appearance on macOS (fanquake) + +### Build system +- #14647 `7edebed` Remove illegal spacing in darwin.mk (ch4ot1c) +- #14698 `ec71f06` Add bitcoin-tx.exe into Windows installer (ken2812221) + +### Tests and QA +- #13965 `29899ec` Fix extended functional tests fail (ken2812221) +- #14011 `9461f98` Disable wallet and address book Qt tests on macOS minimal platform (ryanofsky) +- #14180 `86fadee` Run all tests even if wallet is not compiled (MarcoFalke) +- #14122 `8bc1bad` Test `rpc_help.py` failed: Check whether ZMQ is enabled or not (Kvaciral) +- #14101 `96dc936` Use named args in validation acceptance tests (MarcoFalke) +- #14020 `24d796a` Add tests for RPC help (promag) +- #14052 `7ff32a6` Add some actual witness in `rpc_rawtransaction` (MarcoFalke) +- #14215 `b72fbab` Use correct python index slices in example test (sdaftuar) +- #14024 `06544fa` Add `TestNode::assert_debug_log` (MarcoFalke) +- #14658 `60f7a97` Add test to ensure node can generate all rpc help texts at runtime (MarcoFalke) +- #14632 `96f15e8` Fix a comment (fridokus) +- #14700 `f9db08e` Avoid race in `p2p_invalid_block` by waiting for the block request (MarcoFalke) +- #14845 `67225e2` Add `wallet_balance.py` (jnewbery) + +### Documentation +- #14161 `5f51fd6` doc/descriptors.md tweaks (ryanofsky) +- #14276 `85aacc4` Add autogen.sh in ARM Cross-compilation (walterwhite81) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- Andrew Chow +- Chun Kuan Lee +- David A. Harding +- Eric Scrivner +- fanquake +- fridokus +- Glenn Willen +- Gregory Sanders +- gustavonalle +- John Newbery +- Jon Layton +- Jonas Schnelli +- João Barbosa +- Kaz Wesley +- Kvaciral +- Luke Dashjr +- MarcoFalke +- MeshCollider +- Pieter Wuille +- practicalswift +- Russell Yanofsky +- Sjors Provoost +- Suhas Daftuar +- Tim Ruffing +- Walter +- Wladimir J. van der Laan + +As well as everyone that helped translating on [Transifex](https://www.transifex.com/projects/p/bitcoin/). diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index 7cf27f9f5b..8133fd9d65 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -78,7 +78,7 @@ static void AssembleBlock(benchmark::State& state) ::pcoinsTip.reset(new CCoinsViewCache(pcoinsdbview.get())); const CChainParams& chainparams = Params(); - thread_group.create_thread(boost::bind(&CScheduler::serviceQueue, &scheduler)); + thread_group.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler)); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); LoadGenesisBlock(chainparams); CValidationState state; diff --git a/src/init.cpp b/src/init.cpp index 8ecd79197f..a3a7c5a3bb 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -59,7 +59,6 @@ #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/split.hpp> -#include <boost/bind.hpp> #include <boost/thread.hpp> #include <openssl/crypto.h> @@ -1237,8 +1236,8 @@ bool AppInitMain(InitInterfaces& interfaces) } // Start the lightweight task scheduler thread - CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler); - threadGroup.create_thread(boost::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop)); + CScheduler::Function serviceLoop = std::bind(&CScheduler::serviceQueue, &scheduler); + threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop)); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); GetMainSignals().RegisterWithMempoolSignals(mempool); @@ -1646,7 +1645,7 @@ bool AppInitMain(InitInterfaces& interfaces) vImportFiles.push_back(strFile); } - threadGroup.create_thread(boost::bind(&ThreadImport, vImportFiles)); + threadGroup.create_thread(std::bind(&ThreadImport, vImportFiles)); // Wait for genesis block to be processed { diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index d7056ddd89..15e1b98fb4 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -58,7 +58,6 @@ #include <QVBoxLayout> #include <QWindow> -#include <boost/bind.hpp> const std::string BitcoinGUI::DEFAULT_UIPLATFORM = #if defined(Q_OS_MAC) @@ -1294,8 +1293,8 @@ static bool ThreadSafeMessageBox(BitcoinGUI* gui, const std::string& message, co void BitcoinGUI::subscribeToCoreSignals() { // Connect signals to client - m_handler_message_box = m_node.handleMessageBox(boost::bind(ThreadSafeMessageBox, this, _1, _2, _3)); - m_handler_question = m_node.handleQuestion(boost::bind(ThreadSafeMessageBox, this, _1, _3, _4)); + m_handler_message_box = m_node.handleMessageBox(std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + m_handler_question = m_node.handleQuestion(std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_3, std::placeholders::_4)); } void BitcoinGUI::unsubscribeFromCoreSignals() diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 75012b279c..217326f818 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -252,13 +252,13 @@ static void BlockTipChanged(ClientModel *clientmodel, bool initialSync, int heig void ClientModel::subscribeToCoreSignals() { // Connect signals to client - m_handler_show_progress = m_node.handleShowProgress(boost::bind(ShowProgress, this, _1, _2)); - m_handler_notify_num_connections_changed = m_node.handleNotifyNumConnectionsChanged(boost::bind(NotifyNumConnectionsChanged, this, _1)); - m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged(boost::bind(NotifyNetworkActiveChanged, this, _1)); - m_handler_notify_alert_changed = m_node.handleNotifyAlertChanged(boost::bind(NotifyAlertChanged, this)); - m_handler_banned_list_changed = m_node.handleBannedListChanged(boost::bind(BannedListChanged, this)); - m_handler_notify_block_tip = m_node.handleNotifyBlockTip(boost::bind(BlockTipChanged, this, _1, _2, _3, _4, false)); - m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(boost::bind(BlockTipChanged, this, _1, _2, _3, _4, true)); + m_handler_show_progress = m_node.handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); + m_handler_notify_num_connections_changed = m_node.handleNotifyNumConnectionsChanged(std::bind(NotifyNumConnectionsChanged, this, std::placeholders::_1)); + m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged(std::bind(NotifyNetworkActiveChanged, this, std::placeholders::_1)); + m_handler_notify_alert_changed = m_node.handleNotifyAlertChanged(std::bind(NotifyAlertChanged, this)); + m_handler_banned_list_changed = m_node.handleBannedListChanged(std::bind(BannedListChanged, this)); + m_handler_notify_block_tip = m_node.handleNotifyBlockTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, false)); + m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, true)); } void ClientModel::unsubscribeFromCoreSignals() diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 7b952f9fd7..b109a08b1c 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -24,7 +24,6 @@ #include <QPainter> #include <QRadialGradient> -#include <boost/bind.hpp> SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const NetworkStyle *networkStyle) : QWidget(0, f), curAlignment(0), m_node(node) @@ -174,7 +173,7 @@ static void ShowProgress(SplashScreen *splash, const std::string &title, int nPr #ifdef ENABLE_WALLET void SplashScreen::ConnectWallet(std::unique_ptr<interfaces::Wallet> wallet) { - m_connected_wallet_handlers.emplace_back(wallet->handleShowProgress(boost::bind(ShowProgress, this, _1, _2, false))); + m_connected_wallet_handlers.emplace_back(wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2, false))); m_connected_wallets.emplace_back(std::move(wallet)); } #endif @@ -182,8 +181,8 @@ void SplashScreen::ConnectWallet(std::unique_ptr<interfaces::Wallet> wallet) void SplashScreen::subscribeToCoreSignals() { // Connect signals to client - m_handler_init_message = m_node.handleInitMessage(boost::bind(InitMessage, this, _1)); - m_handler_show_progress = m_node.handleShowProgress(boost::bind(ShowProgress, this, _1, _2, _3)); + m_handler_init_message = m_node.handleInitMessage(std::bind(InitMessage, this, std::placeholders::_1)); + m_handler_show_progress = m_node.handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); #ifdef ENABLE_WALLET m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) { ConnectWallet(std::move(wallet)); }); #endif diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 1983c3bc92..01722146c5 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -27,7 +27,6 @@ #include <QIcon> #include <QList> -#include <boost/bind.hpp> // Amount column is right-aligned it contains numbers static int column_alignments[] = { @@ -745,8 +744,8 @@ static void ShowProgress(TransactionTableModel *ttm, const std::string &title, i void TransactionTableModel::subscribeToCoreSignals() { // Connect signals to wallet - m_handler_transaction_changed = walletModel->wallet().handleTransactionChanged(boost::bind(NotifyTransactionChanged, this, _1, _2)); - m_handler_show_progress = walletModel->wallet().handleShowProgress(boost::bind(ShowProgress, this, _1, _2)); + m_handler_transaction_changed = walletModel->wallet().handleTransactionChanged(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2)); + m_handler_show_progress = walletModel->wallet().handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); } void TransactionTableModel::unsubscribeFromCoreSignals() diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 353da0c9b4..ba16fdd5e1 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -428,12 +428,12 @@ static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly void WalletModel::subscribeToCoreSignals() { // Connect signals to wallet - m_handler_unload = m_wallet->handleUnload(boost::bind(&NotifyUnload, this)); - m_handler_status_changed = m_wallet->handleStatusChanged(boost::bind(&NotifyKeyStoreStatusChanged, this)); - m_handler_address_book_changed = m_wallet->handleAddressBookChanged(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); - m_handler_transaction_changed = m_wallet->handleTransactionChanged(boost::bind(NotifyTransactionChanged, this, _1, _2)); - m_handler_show_progress = m_wallet->handleShowProgress(boost::bind(ShowProgress, this, _1, _2)); - m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged(boost::bind(NotifyWatchonlyChanged, this, _1)); + m_handler_unload = m_wallet->handleUnload(std::bind(&NotifyUnload, this)); + m_handler_status_changed = m_wallet->handleStatusChanged(std::bind(&NotifyKeyStoreStatusChanged, this)); + m_handler_address_book_changed = m_wallet->handleAddressBookChanged(std::bind(NotifyAddressBookChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + m_handler_transaction_changed = m_wallet->handleTransactionChanged(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2)); + m_handler_show_progress = m_wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); + m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged(std::bind(NotifyWatchonlyChanged, this, std::placeholders::_1)); } void WalletModel::unsubscribeFromCoreSignals() diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 370ae8e4d7..bf00870107 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -130,8 +130,8 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) " ],\n" " \"blockhash\" : \"hash\", (string) the block hash\n" " \"confirmations\" : n, (numeric) The confirmations\n" - " \"time\" : ttt, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT)\n" " \"blocktime\" : ttt (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"time\" : ttt, (numeric) Same as \"blocktime\"\n" "}\n" "\nExamples:\n" @@ -1135,7 +1135,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) "Sign the transaction, and get back the hex\n" + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + "\nTest acceptance of the transaction (signed hex)\n" - + HelpExampleCli("testmempoolaccept", "\"signedhex\"") + + + HelpExampleCli("testmempoolaccept", "[\"signedhex\"]") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]") ); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 03e1c81132..e7e047334e 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -15,7 +15,6 @@ #include <util/strencodings.h> #include <util/system.h> -#include <boost/bind.hpp> #include <boost/signals2/signal.hpp> #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/split.hpp> @@ -504,11 +503,7 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const std::vector<std::string> CRPCTable::listCommands() const { std::vector<std::string> commandList; - typedef std::map<std::string, const CRPCCommand*> commandMap; - - std::transform( mapCommands.begin(), mapCommands.end(), - std::back_inserter(commandList), - boost::bind(&commandMap::value_type::first,_1) ); + for (const auto& i : mapCommands) commandList.emplace_back(i.first); return commandList; } diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 552391d7d0..b2da62fc75 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -8,7 +8,6 @@ #include <reverselock.h> #include <assert.h> -#include <boost/bind.hpp> #include <utility> CScheduler::CScheduler() : nThreadsServicingQueue(0), stopRequested(false), stopWhenEmpty(false) @@ -120,12 +119,12 @@ void CScheduler::scheduleFromNow(CScheduler::Function f, int64_t deltaMilliSecon static void Repeat(CScheduler* s, CScheduler::Function f, int64_t deltaMilliSeconds) { f(); - s->scheduleFromNow(boost::bind(&Repeat, s, f, deltaMilliSeconds), deltaMilliSeconds); + s->scheduleFromNow(std::bind(&Repeat, s, f, deltaMilliSeconds), deltaMilliSeconds); } void CScheduler::scheduleEvery(CScheduler::Function f, int64_t deltaMilliSeconds) { - scheduleFromNow(boost::bind(&Repeat, this, f, deltaMilliSeconds), deltaMilliSeconds); + scheduleFromNow(std::bind(&Repeat, this, f, deltaMilliSeconds), deltaMilliSeconds); } size_t CScheduler::getQueueInfo(boost::chrono::system_clock::time_point &first, diff --git a/src/scheduler.h b/src/scheduler.h index 6c45f508ec..6d7f42cf9f 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -25,7 +25,7 @@ // CScheduler* s = new CScheduler(); // s->scheduleFromNow(doSomething, 11); // Assuming a: void doSomething() { } // s->scheduleFromNow(std::bind(Class::func, this, argument), 3); -// boost::thread* t = new boost::thread(boost::bind(CScheduler::serviceQueue, s)); +// boost::thread* t = new boost::thread(std::bind(CScheduler::serviceQueue, s)); // // ... then at program shutdown, clean up the thread running serviceQueue: // t->interrupt(); diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp index 12ec00ce02..100d65b779 100644 --- a/src/test/scheduler_tests.cpp +++ b/src/test/scheduler_tests.cpp @@ -7,7 +7,6 @@ #include <test/test_bitcoin.h> -#include <boost/bind.hpp> #include <boost/thread.hpp> #include <boost/test/unit_test.hpp> @@ -21,7 +20,7 @@ static void microTask(CScheduler& s, boost::mutex& mutex, int& counter, int delt } boost::chrono::system_clock::time_point noTime = boost::chrono::system_clock::time_point::min(); if (rescheduleTime != noTime) { - CScheduler::Function f = boost::bind(µTask, boost::ref(s), boost::ref(mutex), boost::ref(counter), -delta + 1, noTime); + CScheduler::Function f = std::bind(µTask, std::ref(s), std::ref(mutex), std::ref(counter), -delta + 1, noTime); s.schedule(f, rescheduleTime); } } @@ -69,8 +68,8 @@ BOOST_AUTO_TEST_CASE(manythreads) boost::chrono::system_clock::time_point t = now + boost::chrono::microseconds(randomMsec(rng)); boost::chrono::system_clock::time_point tReschedule = now + boost::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); - CScheduler::Function f = boost::bind(µTask, boost::ref(microTasks), - boost::ref(counterMutex[whichCounter]), boost::ref(counter[whichCounter]), + CScheduler::Function f = std::bind(µTask, std::ref(microTasks), + std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), randomDelta(rng), tReschedule); microTasks.schedule(f, t); } @@ -82,20 +81,20 @@ BOOST_AUTO_TEST_CASE(manythreads) // As soon as these are created they will start running and servicing the queue boost::thread_group microThreads; for (int i = 0; i < 5; i++) - microThreads.create_thread(boost::bind(&CScheduler::serviceQueue, µTasks)); + microThreads.create_thread(std::bind(&CScheduler::serviceQueue, µTasks)); MicroSleep(600); now = boost::chrono::system_clock::now(); // More threads and more tasks: for (int i = 0; i < 5; i++) - microThreads.create_thread(boost::bind(&CScheduler::serviceQueue, µTasks)); + microThreads.create_thread(std::bind(&CScheduler::serviceQueue, µTasks)); for (int i = 0; i < 100; i++) { boost::chrono::system_clock::time_point t = now + boost::chrono::microseconds(randomMsec(rng)); boost::chrono::system_clock::time_point tReschedule = now + boost::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); - CScheduler::Function f = boost::bind(µTask, boost::ref(microTasks), - boost::ref(counterMutex[whichCounter]), boost::ref(counter[whichCounter]), + CScheduler::Function f = std::bind(µTask, std::ref(microTasks), + std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), randomDelta(rng), tReschedule); microTasks.schedule(f, t); } @@ -126,7 +125,7 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered) // if they don't we'll get out of order behaviour boost::thread_group threads; for (int i = 0; i < 5; ++i) { - threads.create_thread(boost::bind(&CScheduler::serviceQueue, &scheduler)); + threads.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler)); } // these are not atomic, if SinglethreadedSchedulerClient prevents diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index a3201de385..858bb512fc 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -89,7 +89,7 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha // We have to run a scheduler thread to prevent ActivateBestChain // from blocking due to queue overrun. - threadGroup.create_thread(boost::bind(&CScheduler::serviceQueue, &scheduler)); + threadGroup.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler)); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); mempool.setSanityCheck(1.0); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index c68b6bbb4d..d952054483 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -467,7 +467,7 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) { CCheckQueueControl<CScriptCheck> control(&scriptcheckqueue); for (int i=0; i<20; i++) - threadGroup.create_thread(boost::bind(&CCheckQueue<CScriptCheck>::Thread, boost::ref(scriptcheckqueue))); + threadGroup.create_thread(std::bind(&CCheckQueue<CScriptCheck>::Thread, std::ref(scriptcheckqueue))); std::vector<Coin> coins; for(uint32_t i = 0; i < mtx.vin.size(); i++) { diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 229cc7d553..c9ee6f9f81 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -15,7 +15,6 @@ #include <set> #include <stdlib.h> -#include <boost/bind.hpp> #include <boost/signals2/signal.hpp> #include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/classification.hpp> @@ -459,8 +458,8 @@ TorController::TorController(struct event_base* _base, const std::string& _targe if (!reconnect_ev) LogPrintf("tor: Failed to create event for reconnection: out of memory?\n"); // Start connection attempts immediately - if (!conn.Connect(_target, boost::bind(&TorController::connected_cb, this, _1), - boost::bind(&TorController::disconnected_cb, this, _1) )) { + if (!conn.Connect(_target, std::bind(&TorController::connected_cb, this, std::placeholders::_1), + std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) { LogPrintf("tor: Initiating connection to Tor control port %s failed\n", _target); } // Read service private key if cached @@ -538,7 +537,7 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& // Note that the 'virtual' port doesn't have to be the same as our internal port, but this is just a convenient // choice. TODO; refactor the shutdown sequence some day. _conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", private_key, GetListenPort(), GetListenPort()), - boost::bind(&TorController::add_onion_cb, this, _1, _2)); + std::bind(&TorController::add_onion_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Authentication failed\n"); } @@ -597,7 +596,7 @@ void TorController::authchallenge_cb(TorControlConnection& _conn, const TorContr } std::vector<uint8_t> computedClientHash = ComputeResponse(TOR_SAFE_CLIENTKEY, cookie, clientNonce, serverNonce); - _conn.Command("AUTHENTICATE " + HexStr(computedClientHash), boost::bind(&TorController::auth_cb, this, _1, _2)); + _conn.Command("AUTHENTICATE " + HexStr(computedClientHash), std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Invalid reply to AUTHCHALLENGE\n"); } @@ -646,23 +645,23 @@ void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorContro if (methods.count("HASHEDPASSWORD")) { LogPrint(BCLog::TOR, "tor: Using HASHEDPASSWORD authentication\n"); boost::replace_all(torpassword, "\"", "\\\""); - _conn.Command("AUTHENTICATE \"" + torpassword + "\"", boost::bind(&TorController::auth_cb, this, _1, _2)); + _conn.Command("AUTHENTICATE \"" + torpassword + "\"", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available\n"); } } else if (methods.count("NULL")) { LogPrint(BCLog::TOR, "tor: Using NULL authentication\n"); - _conn.Command("AUTHENTICATE", boost::bind(&TorController::auth_cb, this, _1, _2)); + _conn.Command("AUTHENTICATE", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else if (methods.count("SAFECOOKIE")) { // Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie LogPrint(BCLog::TOR, "tor: Using SAFECOOKIE authentication, reading cookie authentication from %s\n", cookiefile); std::pair<bool,std::string> status_cookie = ReadBinaryFile(cookiefile, TOR_COOKIE_SIZE); if (status_cookie.first && status_cookie.second.size() == TOR_COOKIE_SIZE) { - // _conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), boost::bind(&TorController::auth_cb, this, _1, _2)); + // _conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); cookie = std::vector<uint8_t>(status_cookie.second.begin(), status_cookie.second.end()); clientNonce = std::vector<uint8_t>(TOR_NONCE_SIZE, 0); GetRandBytes(clientNonce.data(), TOR_NONCE_SIZE); - _conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), boost::bind(&TorController::authchallenge_cb, this, _1, _2)); + _conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), std::bind(&TorController::authchallenge_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { if (status_cookie.first) { LogPrintf("tor: Authentication cookie %s is not exactly %i bytes, as is required by the spec\n", cookiefile, TOR_COOKIE_SIZE); @@ -684,7 +683,7 @@ void TorController::connected_cb(TorControlConnection& _conn) { reconnect_timeout = RECONNECT_TIMEOUT_START; // First send a PROTOCOLINFO command to figure out what authentication is expected - if (!_conn.Command("PROTOCOLINFO 1", boost::bind(&TorController::protocolinfo_cb, this, _1, _2))) + if (!_conn.Command("PROTOCOLINFO 1", std::bind(&TorController::protocolinfo_cb, this, std::placeholders::_1, std::placeholders::_2))) LogPrintf("tor: Error sending initial protocolinfo command\n"); } @@ -711,8 +710,8 @@ void TorController::Reconnect() /* Try to reconnect and reestablish if we get booted - for example, Tor * may be restarting. */ - if (!conn.Connect(target, boost::bind(&TorController::connected_cb, this, _1), - boost::bind(&TorController::disconnected_cb, this, _1) )) { + if (!conn.Connect(target, std::bind(&TorController::connected_cb, this, std::placeholders::_1), + std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) { LogPrintf("tor: Re-initiating connection to Tor control port %s failed\n", target); } } diff --git a/src/validation.cpp b/src/validation.cpp index 5696684ed6..00d7a60cb7 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2366,14 +2366,11 @@ class ConnectTrace { private: std::vector<PerBlockConnectTrace> blocksConnected; CTxMemPool &pool; + boost::signals2::scoped_connection m_connNotifyEntryRemoved; public: explicit ConnectTrace(CTxMemPool &_pool) : blocksConnected(1), pool(_pool) { - pool.NotifyEntryRemoved.connect(boost::bind(&ConnectTrace::NotifyEntryRemoved, this, _1, _2)); - } - - ~ConnectTrace() { - pool.NotifyEntryRemoved.disconnect(boost::bind(&ConnectTrace::NotifyEntryRemoved, this, _1, _2)); + m_connNotifyEntryRemoved = pool.NotifyEntryRemoved.connect(std::bind(&ConnectTrace::NotifyEntryRemoved, this, std::placeholders::_1, std::placeholders::_2)); } void BlockConnected(CBlockIndex* pindex, std::shared_ptr<const CBlock> pblock) { @@ -3374,10 +3371,30 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); - // If the previous block index isn't valid, determine if it descends from any block which - // has been found invalid (m_failed_blocks), then mark pindexPrev and any blocks - // between them as failed. + /* Determine if this block descends from any block which has been found + * invalid (m_failed_blocks), then mark pindexPrev and any blocks between + * them as failed. For example: + * + * D3 + * / + * B2 - C2 + * / \ + * A D2 - E2 - F2 + * \ + * B1 - C1 - D1 - E1 + * + * In the case that we attempted to reorg from E1 to F2, only to find + * C2 to be invalid, we would mark D2, E2, and F2 as BLOCK_FAILED_CHILD + * but NOT D3 (it was not in any of our candidate sets at the time). + * + * In any case D3 will also be marked as BLOCK_FAILED_CHILD at restart + * in LoadBlockIndex. + */ if (!pindexPrev->IsValid(BLOCK_VALID_SCRIPTS)) { + // The above does not mean "invalid": it checks if the previous block + // hasn't been validated up to BLOCK_VALID_SCRIPTS. This is a performance + // optimization, in the common case of adding a new block to the tip, + // we don't need to iterate over the failed blocks list. for (const CBlockIndex* failedit : m_failed_blocks) { if (pindexPrev->GetAncestor(failedit->nHeight) == failedit) { assert(failedit->nStatus & BLOCK_FAILED_VALID); diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 214a9ffba9..533d412888 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -17,6 +17,18 @@ #include <boost/signals2/signal.hpp> +struct ValidationInterfaceConnections { + boost::signals2::scoped_connection UpdatedBlockTip; + boost::signals2::scoped_connection TransactionAddedToMempool; + boost::signals2::scoped_connection BlockConnected; + boost::signals2::scoped_connection BlockDisconnected; + boost::signals2::scoped_connection TransactionRemovedFromMempool; + boost::signals2::scoped_connection ChainStateFlushed; + boost::signals2::scoped_connection Broadcast; + boost::signals2::scoped_connection BlockChecked; + boost::signals2::scoped_connection NewPoWValidBlock; +}; + struct MainSignalsInstance { boost::signals2::signal<void (const CBlockIndex *, const CBlockIndex *, bool fInitialDownload)> UpdatedBlockTip; boost::signals2::signal<void (const CTransactionRef &)> TransactionAddedToMempool; @@ -32,12 +44,18 @@ struct MainSignalsInstance { // but must ensure all callbacks happen in-order, so we end up creating // our own queue here :( SingleThreadedSchedulerClient m_schedulerClient; + std::unordered_map<CValidationInterface*, ValidationInterfaceConnections> m_connMainSignals; explicit MainSignalsInstance(CScheduler *pscheduler) : m_schedulerClient(pscheduler) {} }; static CMainSignals g_signals; +// This map has to a separate global instead of a member of MainSignalsInstance, +// because RegisterWithMempoolSignals is currently called before RegisterBackgroundSignalScheduler, +// so MainSignalsInstance hasn't been created yet. +static std::unordered_map<CTxMemPool*, boost::signals2::scoped_connection> g_connNotifyEntryRemoved; + void CMainSignals::RegisterBackgroundSignalScheduler(CScheduler& scheduler) { assert(!m_internals); m_internals.reset(new MainSignalsInstance(&scheduler)); @@ -59,11 +77,11 @@ size_t CMainSignals::CallbacksPending() { } void CMainSignals::RegisterWithMempoolSignals(CTxMemPool& pool) { - pool.NotifyEntryRemoved.connect(boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2)); + g_connNotifyEntryRemoved.emplace(&pool, pool.NotifyEntryRemoved.connect(std::bind(&CMainSignals::MempoolEntryRemoved, this, std::placeholders::_1, std::placeholders::_2))); } void CMainSignals::UnregisterWithMempoolSignals(CTxMemPool& pool) { - pool.NotifyEntryRemoved.disconnect(boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2)); + g_connNotifyEntryRemoved.erase(&pool); } CMainSignals& GetMainSignals() @@ -72,42 +90,27 @@ CMainSignals& GetMainSignals() } void RegisterValidationInterface(CValidationInterface* pwalletIn) { - g_signals.m_internals->UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3)); - g_signals.m_internals->TransactionAddedToMempool.connect(boost::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, _1)); - g_signals.m_internals->BlockConnected.connect(boost::bind(&CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3)); - g_signals.m_internals->BlockDisconnected.connect(boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1)); - g_signals.m_internals->TransactionRemovedFromMempool.connect(boost::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1)); - g_signals.m_internals->ChainStateFlushed.connect(boost::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, _1)); - g_signals.m_internals->Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2)); - g_signals.m_internals->BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); - g_signals.m_internals->NewPoWValidBlock.connect(boost::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, _1, _2)); + ValidationInterfaceConnections& conns = g_signals.m_internals->m_connMainSignals[pwalletIn]; + conns.UpdatedBlockTip = g_signals.m_internals->UpdatedBlockTip.connect(std::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + conns.TransactionAddedToMempool = g_signals.m_internals->TransactionAddedToMempool.connect(std::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, std::placeholders::_1)); + conns.BlockConnected = g_signals.m_internals->BlockConnected.connect(std::bind(&CValidationInterface::BlockConnected, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + conns.BlockDisconnected = g_signals.m_internals->BlockDisconnected.connect(std::bind(&CValidationInterface::BlockDisconnected, pwalletIn, std::placeholders::_1)); + conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect(std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1)); + conns.ChainStateFlushed = g_signals.m_internals->ChainStateFlushed.connect(std::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, std::placeholders::_1)); + conns.Broadcast = g_signals.m_internals->Broadcast.connect(std::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, std::placeholders::_1, std::placeholders::_2)); + conns.BlockChecked = g_signals.m_internals->BlockChecked.connect(std::bind(&CValidationInterface::BlockChecked, pwalletIn, std::placeholders::_1, std::placeholders::_2)); + conns.NewPoWValidBlock = g_signals.m_internals->NewPoWValidBlock.connect(std::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, std::placeholders::_1, std::placeholders::_2)); } void UnregisterValidationInterface(CValidationInterface* pwalletIn) { - g_signals.m_internals->BlockChecked.disconnect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); - g_signals.m_internals->Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2)); - g_signals.m_internals->ChainStateFlushed.disconnect(boost::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, _1)); - g_signals.m_internals->TransactionAddedToMempool.disconnect(boost::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, _1)); - g_signals.m_internals->BlockConnected.disconnect(boost::bind(&CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3)); - g_signals.m_internals->BlockDisconnected.disconnect(boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1)); - g_signals.m_internals->TransactionRemovedFromMempool.disconnect(boost::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1)); - g_signals.m_internals->UpdatedBlockTip.disconnect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3)); - g_signals.m_internals->NewPoWValidBlock.disconnect(boost::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, _1, _2)); + g_signals.m_internals->m_connMainSignals.erase(pwalletIn); } void UnregisterAllValidationInterfaces() { if (!g_signals.m_internals) { return; } - g_signals.m_internals->BlockChecked.disconnect_all_slots(); - g_signals.m_internals->Broadcast.disconnect_all_slots(); - g_signals.m_internals->ChainStateFlushed.disconnect_all_slots(); - g_signals.m_internals->TransactionAddedToMempool.disconnect_all_slots(); - g_signals.m_internals->BlockConnected.disconnect_all_slots(); - g_signals.m_internals->BlockDisconnected.disconnect_all_slots(); - g_signals.m_internals->TransactionRemovedFromMempool.disconnect_all_slots(); - g_signals.m_internals->UpdatedBlockTip.disconnect_all_slots(); - g_signals.m_internals->NewPoWValidBlock.disconnect_all_slots(); + g_signals.m_internals->m_connMainSignals.clear(); } void CallFunctionInValidationInterfaceQueue(std::function<void ()> func) { diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 647af3eb86..ed1e2d3940 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -836,9 +836,98 @@ UniValue dumpwallet(const JSONRPCRequest& request) return reply; } +struct ImportData +{ + // Input data + std::unique_ptr<CScript> redeemscript; //!< Provided redeemScript; will be moved to `import_scripts` if relevant. + std::unique_ptr<CScript> witnessscript; //!< Provided witnessScript; will be moved to `import_scripts` if relevant. + + // Output data + std::set<CScript> import_scripts; + std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability) +}; + +enum class ScriptContext +{ + TOP, //! Top-level scriptPubKey + P2SH, //! P2SH redeemScript + WITNESS_V0, //! P2WSH witnessScript +}; + +// Analyse the provided scriptPubKey, determining which keys and which redeem scripts from the ImportData struct are needed to spend it, and mark them as used. +// Returns an error string, or the empty string for success. +static std::string RecurseImportData(const CScript& script, ImportData& import_data, const ScriptContext script_ctx) +{ + // Use Solver to obtain script type and parsed pubkeys or hashes: + std::vector<std::vector<unsigned char>> solverdata; + txnouttype script_type = Solver(script, solverdata); + + switch (script_type) { + case TX_PUBKEY: { + CPubKey pubkey(solverdata[0].begin(), solverdata[0].end()); + import_data.used_keys.emplace(pubkey.GetID(), false); + return ""; + } + case TX_PUBKEYHASH: { + CKeyID id = CKeyID(uint160(solverdata[0])); + import_data.used_keys[id] = true; + return ""; + } + case TX_SCRIPTHASH: { + if (script_ctx == ScriptContext::P2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH"); + if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside a P2WSH"); + assert(script_ctx == ScriptContext::TOP); + CScriptID id = CScriptID(uint160(solverdata[0])); + auto subscript = std::move(import_data.redeemscript); // Remove redeemscript from import_data to check for superfluous script later. + if (!subscript) return "missing redeemscript"; + if (CScriptID(*subscript) != id) return "redeemScript does not match the scriptPubKey"; + import_data.import_scripts.emplace(*subscript); + return RecurseImportData(*subscript, import_data, ScriptContext::P2SH); + } + case TX_MULTISIG: { + for (size_t i = 1; i + 1< solverdata.size(); ++i) { + CPubKey pubkey(solverdata[i].begin(), solverdata[i].end()); + import_data.used_keys.emplace(pubkey.GetID(), false); + } + return ""; + } + case TX_WITNESS_V0_SCRIPTHASH: { + if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WSH inside another P2WSH"); + uint256 fullid(solverdata[0]); + CScriptID id; + CRIPEMD160().Write(fullid.begin(), fullid.size()).Finalize(id.begin()); + auto subscript = std::move(import_data.witnessscript); // Remove redeemscript from import_data to check for superfluous script later. + if (!subscript) return "missing witnessscript"; + if (CScriptID(*subscript) != id) return "witnessScript does not match the scriptPubKey or redeemScript"; + if (script_ctx == ScriptContext::TOP) { + import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WSH requires the TOP script imported (see script/ismine.cpp) + } + import_data.import_scripts.emplace(*subscript); + return RecurseImportData(*subscript, import_data, ScriptContext::WITNESS_V0); + } + case TX_WITNESS_V0_KEYHASH: { + if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WPKH inside P2WSH"); + CKeyID id = CKeyID(uint160(solverdata[0])); + import_data.used_keys[id] = true; + if (script_ctx == ScriptContext::TOP) { + import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WPKH requires the TOP script imported (see script/ismine.cpp) + } + return ""; + } + case TX_NULL_DATA: + return "unspendable script"; + case TX_NONSTANDARD: + case TX_WITNESS_UNKNOWN: + default: + return "unrecognized script"; + } +} static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { + UniValue warnings(UniValue::VARR); + UniValue result(UniValue::VOBJ); + try { // First ensure scriptPubKey has either a script or JSON with "address" string const UniValue& scriptPubKey = data["scriptPubKey"]; @@ -860,18 +949,16 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con // Generate the script and destination for the scriptPubKey provided CScript script; CTxDestination dest; - if (!isScript) { dest = DecodeDestination(output); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\""); } script = GetScriptForDestination(dest); } else { if (!IsHex(output)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\""); } - std::vector<unsigned char> vData(ParseHex(output)); script = CScript(vData.begin(), vData.end()); if (!ExtractDestination(script, dest) && !internal) { @@ -879,203 +966,161 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con } } - // Watchonly and private keys - if (watchOnly && keys.size()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Watch-only addresses should not include private keys"); - } - - // Internal addresses should not have a label - if (internal && data.exists("label")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); - } - - // Force users to provide the witness script in its field rather than redeemscript - if (!strRedeemScript.empty() && script.IsPayToWitnessScriptHash()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WSH addresses have an empty redeemscript. Please provide the witnessscript instead."); - } - - CScript scriptpubkey_script = script; - CTxDestination scriptpubkey_dest = dest; - bool allow_p2wpkh = true; - - // P2SH - if (!strRedeemScript.empty() && script.IsPayToScriptHash()) { - // Check the redeemScript is valid + // Parse all arguments + ImportData import_data; + if (strRedeemScript.size()) { if (!IsHex(strRedeemScript)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script: must be hex string"); - } - - // Import redeem script. - std::vector<unsigned char> vData(ParseHex(strRedeemScript)); - CScript redeemScript = CScript(vData.begin(), vData.end()); - CScriptID redeem_id(redeemScript); - - // Check that the redeemScript and scriptPubKey match - if (GetScriptForDestination(redeem_id) != script) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The redeemScript does not match the scriptPubKey"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string"); } - - pwallet->MarkDirty(); - - if (!pwallet->AddWatchOnly(redeemScript, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } - - if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); - } - - // Now set script to the redeemScript so we parse the inner script as P2WSH or P2WPKH below - script = redeemScript; - ExtractDestination(script, dest); + auto parsed_redeemscript = ParseHex(strRedeemScript); + import_data.redeemscript = MakeUnique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end()); } - - // (P2SH-)P2WSH - if (!witness_script_hex.empty() && script.IsPayToWitnessScriptHash()) { + if (witness_script_hex.size()) { if (!IsHex(witness_script_hex)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script: must be hex string"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string"); } - - // Generate the scripts - std::vector<unsigned char> witness_script_parsed(ParseHex(witness_script_hex)); - CScript witness_script = CScript(witness_script_parsed.begin(), witness_script_parsed.end()); - CScriptID witness_id(witness_script); - - // Check that the witnessScript and scriptPubKey match - if (GetScriptForDestination(WitnessV0ScriptHash(witness_script)) != script) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The witnessScript does not match the scriptPubKey or redeemScript"); + auto parsed_witnessscript = ParseHex(witness_script_hex); + import_data.witnessscript = MakeUnique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end()); + } + std::map<CKeyID, CPubKey> pubkey_map; + for (size_t i = 0; i < pubKeys.size(); ++i) { + const auto& str = pubKeys[i].get_str(); + if (!IsHex(str)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string"); } - - // Add the witness script as watch only only if it is not for P2SH-P2WSH - if (!scriptpubkey_script.IsPayToScriptHash() && !pwallet->AddWatchOnly(witness_script, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + auto parsed_pubkey = ParseHex(str); + CPubKey pubkey(parsed_pubkey.begin(), parsed_pubkey.end()); + if (!pubkey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key"); } - - if (!pwallet->HaveCScript(witness_id) && !pwallet->AddCScript(witness_script)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2wsh witnessScript to wallet"); + pubkey_map.emplace(pubkey.GetID(), pubkey); + } + std::map<CKeyID, CKey> privkey_map; + for (size_t i = 0; i < keys.size(); ++i) { + const auto& str = keys[i].get_str(); + CKey key = DecodeSecret(str); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } + CPubKey pubkey = key.GetPubKey(); + CKeyID id = pubkey.GetID(); + if (pubkey_map.count(id)) { + pubkey_map.erase(id); + } + privkey_map.emplace(id, key); + } - // Now set script to the witnessScript so we parse the inner script as P2PK or P2PKH below - script = witness_script; - ExtractDestination(script, dest); - allow_p2wpkh = false; // P2WPKH cannot be embedded in P2WSH + // Internal addresses should not have a label + if (internal && data.exists("label")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); } - // (P2SH-)P2PK/P2PKH/P2WPKH - if (dest.type() == typeid(CKeyID) || dest.type() == typeid(WitnessV0KeyHash)) { - if (!allow_p2wpkh && dest.type() == typeid(WitnessV0KeyHash)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WPKH cannot be embedded in P2WSH"); - } - if (keys.size() > 1 || pubKeys.size() > 1) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "More than one key given for one single-key address"); - } - CPubKey pubkey; - if (keys.size()) { - pubkey = DecodeSecret(keys[0].get_str()).GetPubKey(); + // Verify and process input data + bool have_solving_data = import_data.redeemscript || import_data.witnessscript || pubkey_map.size() || privkey_map.size(); + if (have_solving_data) { + // Match up data in import_data with the scriptPubKey in script. + auto error = RecurseImportData(script, import_data, ScriptContext::TOP); + + // Verify whether the watchonly option corresponds to the availability of private keys. + bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool>& used_key){ return privkey_map.count(used_key.first) > 0; }); + if (!watchOnly && !spendable) { + warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); } - if (pubKeys.size()) { - const std::string& strPubKey = pubKeys[0].get_str(); - if (!IsHex(strPubKey)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); - } - std::vector<unsigned char> vData(ParseHex(pubKeys[0].get_str())); - CPubKey pubkey_temp(vData.begin(), vData.end()); - if (pubkey.size() && pubkey_temp != pubkey) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key does not match public key for address"); - } - pubkey = pubkey_temp; + if (watchOnly && spendable) { + warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."); } - if (pubkey.size() > 0) { - if (!pubkey.IsFullyValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); - } - // Check the key corresponds to the destination given - std::vector<CTxDestination> destinations = GetAllDestinationsForKey(pubkey); - if (std::find(destinations.begin(), destinations.end(), dest) == destinations.end()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Key does not match address destination"); + // Check that all required keys for solvability are provided. + if (error.empty()) { + for (const auto& require_key : import_data.used_keys) { + if (!require_key.second) continue; // Not a required key + if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) { + error = "some required keys are missing"; + } } + } - // This is necessary to force the wallet to import the pubKey - CScript scriptRawPubKey = GetScriptForRawPubKey(pubkey); - - if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) { - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + if (!error.empty()) { + warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript."); + import_data = ImportData(); + pubkey_map.clear(); + privkey_map.clear(); + have_solving_data = false; + } else { + // RecurseImportData() removes any relevant redeemscript/witnessscript from import_data, so we can use that to discover if a superfluous one was provided. + if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script."); + if (import_data.witnessscript) warnings.push_back("Ignoring witnessscript as this is not a (P2SH-)P2WSH script."); + for (auto it = privkey_map.begin(); it != privkey_map.end(); ) { + auto oldit = it++; + if (import_data.used_keys.count(oldit->first) == 0) { + warnings.push_back("Ignoring irrelevant private key."); + privkey_map.erase(oldit); + } } - - pwallet->MarkDirty(); - - if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) { + auto oldit = it++; + auto key_data_it = import_data.used_keys.find(oldit->first); + if (key_data_it == import_data.used_keys.end() || !key_data_it->second) { + warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH or P2WPKH."); + pubkey_map.erase(oldit); + } } } } - // Import the address - if (::IsMine(*pwallet, scriptpubkey_script) == ISMINE_SPENDABLE) { + // Check whether we have any work to do + if (::IsMine(*pwallet, script) & ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); } + // All good, time to import pwallet->MarkDirty(); - - if (!pwallet->AddWatchOnly(scriptpubkey_script, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } - - if (!watchOnly && !pwallet->HaveCScript(CScriptID(scriptpubkey_script)) && !pwallet->AddCScript(scriptpubkey_script)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding scriptPubKey script to wallet"); - } - - // if not internal add to address book or update label - if (!internal) { - assert(IsValidDestination(scriptpubkey_dest)); - pwallet->SetAddressBook(scriptpubkey_dest, label, "receive"); - } - - // Import private keys. - for (size_t i = 0; i < keys.size(); i++) { - const std::string& strPrivkey = keys[i].get_str(); - - // Checks. - CKey key = DecodeSecret(strPrivkey); - - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); + for (const auto& entry : import_data.import_scripts) { + if (!pwallet->HaveCScript(CScriptID(entry)) && !pwallet->AddCScript(entry)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); } - - CPubKey pubKey = key.GetPubKey(); - assert(key.VerifyPubKey(pubKey)); - - CKeyID vchAddress = pubKey.GetID(); - pwallet->MarkDirty(); - - if (pwallet->HaveKey(vchAddress)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key"); - } - - pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp; - - if (!pwallet->AddKeyPubKey(key, pubKey)) { + } + for (const auto& entry : privkey_map) { + const CKey& key = entry.second; + CPubKey pubkey = key.GetPubKey(); + const CKeyID& id = entry.first; + assert(key.VerifyPubKey(pubkey)); + pwallet->mapKeyMetadata[id].nCreateTime = timestamp; + // If the private key is not present in the wallet, insert it. + if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } - pwallet->UpdateTimeFirstKey(timestamp); } + for (const auto& entry : pubkey_map) { + const CPubKey& pubkey = entry.second; + const CKeyID& id = entry.first; + CPubKey temp; + if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + } + if (!have_solving_data || !::IsMine(*pwallet, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated + if (!pwallet->AddWatchOnly(script, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + } + if (!internal) { + assert(IsValidDestination(dest)); + pwallet->SetAddressBook(dest, label, "receive"); + } - UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(true)); - return result; } catch (const UniValue& e) { - UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV("error", e); - return result; } catch (...) { - UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); + result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); - return result; } + if (warnings.size()) result.pushKV("warnings", warnings); + return result; } static int64_t GetImportTimestamp(const UniValue& data, int64_t now) @@ -1122,18 +1167,18 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) }, {"redeemscript", RPCArg::Type::STR, /* opt */ true, /* default_val */ "omitted", "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"}, {"witnessscript", RPCArg::Type::STR, /* opt */ true, /* default_val */ "omitted", "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"}, - {"pubkeys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving pubkeys that must occur in the output or redeemscript", + {"pubkeys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving pubkeys to import. They must occur in P2PKH or P2WPKH scripts. They are not required when the private key is also provided (see the \"keys\" argument).", { {"pubKey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", ""}, } }, - {"keys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript", + {"keys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving private keys to import. The corresponding public keys must occur in the output or redeemscript.", { {"key", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", ""}, } }, - {"internal", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be treated as not incoming payments aka change"}, - {"watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty"}, + {"internal", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be treated as not incoming payments (also known as change)"}, + {"watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be considered watched even when not all private keys are provided."}, {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "''", "Label to assign to the address, only allowed with internal=false"}, }, }, @@ -1154,7 +1199,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") + "\nResponse is an array with the same size as the input that has the execution result :\n" - " [{ \"success\": true } , { \"success\": false, \"error\": { \"code\": -1, \"message\": \"Internal Server Error\"} }, ... ]\n"); + " [{\"success\": true}, {\"success\": true, \"warnings\": [\"Ignoring irrelevant private key\"]}, {\"success\": false, \"error\": {\"code\": -1, \"message\": \"Internal Server Error\"}}, ...]\n"); RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 3d0467038d..12a4ce9aff 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -180,7 +180,7 @@ class AssumeValidTest(BitcoinTestFramework): for i in range(2202): p2p1.send_message(msg_block(self.blocks[i])) # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync. - p2p1.sync_with_ping(120) + p2p1.sync_with_ping(150) assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202) # Send blocks to node2. Block 102 will be rejected. diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 537f80a243..9a3f4fae45 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -191,6 +191,8 @@ class PruneTest(BitcoinTestFramework): def reorg_back(self): # Verify that a block on the old main chain fork has been pruned away assert_raises_rpc_error(-1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash) + with self.nodes[2].assert_debug_log(expected_msgs=['block verification stopping at height', '(pruning, no data)']): + self.nodes[2].verifychain(checklevel=4, nblocks=0) self.log.info("Will need to redownload block %d" % self.forkheight) # Verify that we have enough history to reorg back to the fork point diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 570f821b2f..352fa32b5b 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -388,21 +388,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): sync_blocks(group) sync_mempools(group) - def enable_mocktime(self): - """Enable mocktime for the script. - - mocktime may be needed for scripts that use the cached version of the - blockchain. If the cached version of the blockchain is used without - mocktime then the mempools will not sync due to IBD. - - For backward compatibility of the python scripts with previous - versions of the cache, this helper function sets mocktime to Jan 1, - 2014 + (201 * 10 * 60)""" - self.mocktime = 1388534400 + (201 * 10 * 60) - - def disable_mocktime(self): - self.mocktime = 0 - # Private helper methods. These should not be accessed by the subclass test scripts. def _start_logging(self): @@ -479,6 +464,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): for node in self.nodes: node.wait_for_rpc_connection() + # For backward compatibility of the python scripts with previous + # versions of the cache, set mocktime to Jan 1, + # 2014 + (201 * 10 * 60)""" + self.mocktime = 1388534400 + (201 * 10 * 60) + # Create a 200-block-long chain; each of the 4 first nodes # gets 25 mature blocks and 25 immature. # Note: To preserve compatibility with older versions of @@ -486,7 +476,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # # blocks are created with timestamps 10 minutes apart # starting from 2010 minutes in the past - self.enable_mocktime() block_time = self.mocktime - (201 * 10 * 60) for i in range(2): for peer in range(4): @@ -500,7 +489,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # Shut them down, and clean up cache directories: self.stop_nodes() self.nodes = [] - self.disable_mocktime() + self.mocktime = 0 def cache_path(n, *paths): return os.path.join(get_datadir_path(self.options.cachedir, n), "regtest", *paths) diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index a163f7018c..3492075694 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -119,9 +119,13 @@ class ImportMultiTest(BitcoinTestFramework): CScript([OP_HASH160, witness_script, OP_EQUAL]).hex(), # p2sh-p2wsh script_to_p2sh_p2wsh(script_code)) # p2sh-p2wsh addr - def test_importmulti(self, req, success, error_code=None, error_message=None): + def test_importmulti(self, req, success, error_code=None, error_message=None, warnings=[]): """Run importmulti and assert success""" result = self.nodes[1].importmulti([req]) + observed_warnings = [] + if 'warnings' in result[0]: + observed_warnings = result[0]['warnings'] + assert_equal("\n".join(sorted(warnings)), "\n".join(sorted(observed_warnings))) assert_equal(result[0]['success'], success) if error_code is not None: assert_equal(result[0]['error']['code'], error_code) @@ -178,7 +182,7 @@ class ImportMultiTest(BitcoinTestFramework): "timestamp": "now"}, False, error_code=-5, - error_message='Invalid address') + error_message='Invalid address \"not valid address\"') # ScriptPubKey + internal self.log.info("Should import a scriptPubKey with internal flag") @@ -227,7 +231,8 @@ class ImportMultiTest(BitcoinTestFramework): "timestamp": "now", "pubkeys": [key.pubkey], "internal": False}, - True) + True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) self.test_address(address, iswatchonly=True, ismine=False, @@ -241,7 +246,8 @@ class ImportMultiTest(BitcoinTestFramework): "timestamp": "now", "pubkeys": [key.pubkey], "internal": True}, - True) + True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) self.test_address(address, iswatchonly=True, ismine=False, @@ -284,20 +290,19 @@ class ImportMultiTest(BitcoinTestFramework): error_message='The wallet already contains the private key for this address or script') # Address + Private key + watchonly - self.log.info("Should not import an address with private key and with watchonly") + self.log.info("Should import an address with private key and with watchonly") key = self.get_key() address = key.p2pkh_addr self.test_importmulti({"scriptPubKey": {"address": address}, "timestamp": "now", "keys": [key.privkey], "watchonly": True}, - False, - error_code=-8, - error_message='Watch-only addresses should not include private keys') + True, + warnings=["All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."]) self.test_address(address, iswatchonly=False, - ismine=False, - timestamp=None) + ismine=True, + timestamp=timestamp) # ScriptPubKey + Private key + internal self.log.info("Should import a scriptPubKey with internal and with private key") @@ -358,8 +363,9 @@ class ImportMultiTest(BitcoinTestFramework): self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_addr}, "timestamp": "now", "redeemscript": multisig.redeem_script}, - True) - self.test_address(multisig.p2sh_addr, timestamp=timestamp) + True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + self.test_address(multisig.p2sh_addr, timestamp=timestamp, iswatchonly=True, ismine=False, solvable=True) p2shunspent = self.nodes[1].listunspent(0, 999999, [multisig.p2sh_addr])[0] assert_equal(p2shunspent['spendable'], False) @@ -377,9 +383,13 @@ class ImportMultiTest(BitcoinTestFramework): "timestamp": "now", "redeemscript": multisig.redeem_script, "keys": multisig.privkeys[0:2]}, - True) + True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) self.test_address(multisig.p2sh_addr, - timestamp=timestamp) + timestamp=timestamp, + ismine=False, + iswatchonly=True, + solvable=True) p2shunspent = self.nodes[1].listunspent(0, 999999, [multisig.p2sh_addr])[0] assert_equal(p2shunspent['spendable'], False) @@ -398,28 +408,31 @@ class ImportMultiTest(BitcoinTestFramework): "redeemscript": multisig.redeem_script, "keys": multisig.privkeys[0:2], "watchonly": True}, - False, - error_code=-8, - error_message='Watch-only addresses should not include private keys') + True) + self.test_address(multisig.p2sh_addr, + iswatchonly=True, + ismine=False, + solvable=True, + timestamp=timestamp) # Address + Public key + !Internal + Wrong pubkey - self.log.info("Should not import an address with a wrong public key") + self.log.info("Should not import an address with the wrong public key as non-solvable") key = self.get_key() address = key.p2pkh_addr wrong_key = self.get_key().pubkey self.test_importmulti({"scriptPubKey": {"address": address}, "timestamp": "now", "pubkeys": [wrong_key]}, - False, - error_code=-5, - error_message='Key does not match address destination') + True, + warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) self.test_address(address, - iswatchonly=False, + iswatchonly=True, ismine=False, - timestamp=None) + solvable=False, + timestamp=timestamp) # ScriptPubKey + Public key + internal + Wrong pubkey - self.log.info("Should not import a scriptPubKey with internal and with a wrong public key") + self.log.info("Should import a scriptPubKey with internal and with a wrong public key as non-solvable") key = self.get_key() address = key.p2pkh_addr wrong_key = self.get_key().pubkey @@ -427,32 +440,32 @@ class ImportMultiTest(BitcoinTestFramework): "timestamp": "now", "pubkeys": [wrong_key], "internal": True}, - False, - error_code=-5, - error_message='Key does not match address destination') + True, + warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) self.test_address(address, - iswatchonly=False, + iswatchonly=True, ismine=False, - timestamp=None) + solvable=False, + timestamp=timestamp) # Address + Private key + !watchonly + Wrong private key - self.log.info("Should not import an address with a wrong private key") + self.log.info("Should import an address with a wrong private key as non-solvable") key = self.get_key() address = key.p2pkh_addr wrong_privkey = self.get_key().privkey self.test_importmulti({"scriptPubKey": {"address": address}, "timestamp": "now", "keys": [wrong_privkey]}, - False, - error_code=-5, - error_message='Key does not match address destination') + True, + warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) self.test_address(address, - iswatchonly=False, + iswatchonly=True, ismine=False, - timestamp=None) + solvable=False, + timestamp=timestamp) # ScriptPubKey + Private key + internal + Wrong private key - self.log.info("Should not import a scriptPubKey with internal and with a wrong private key") + self.log.info("Should import a scriptPubKey with internal and with a wrong private key as non-solvable") key = self.get_key() address = key.p2pkh_addr wrong_privkey = self.get_key().privkey @@ -460,13 +473,13 @@ class ImportMultiTest(BitcoinTestFramework): "timestamp": "now", "keys": [wrong_privkey], "internal": True}, - False, - error_code=-5, - error_message='Key does not match address destination') + True, + warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) self.test_address(address, - iswatchonly=False, + iswatchonly=True, ismine=False, - timestamp=None) + solvable=False, + timestamp=timestamp) # Importing existing watch only address with new timestamp should replace saved timestamp. assert_greater_than(timestamp, watchonly_timestamp) @@ -516,7 +529,8 @@ class ImportMultiTest(BitcoinTestFramework): self.test_importmulti({"scriptPubKey": {"address": address}, "timestamp": "now", "pubkeys": [key.pubkey]}, - True) + True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) self.test_address(address, ismine=False, solvable=True) @@ -571,7 +585,8 @@ class ImportMultiTest(BitcoinTestFramework): "timestamp": "now", "redeemscript": key.p2sh_p2wpkh_redeem_script, "pubkeys": [key.pubkey]}, - True) + True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) self.test_address(address, solvable=True, ismine=False) @@ -591,14 +606,17 @@ class ImportMultiTest(BitcoinTestFramework): # P2SH-P2WSH multisig + redeemscript with no private key multisig = self.get_multisig() + address = multisig.p2sh_p2wsh_addr self.log.info("Should import a p2sh-p2wsh with respective redeem script but no private key") - self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_p2wsh_addr}, + self.test_importmulti({"scriptPubKey": {"address": address}, "timestamp": "now", "redeemscript": multisig.p2wsh_script, "witnessscript": multisig.redeem_script}, - True) + True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) self.test_address(address, - solvable=True) + solvable=True, + ismine=False) if __name__ == '__main__': ImportMultiTest().main() diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 8ca0387268..17f044bf65 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -25,12 +25,13 @@ def tx_from_hex(hexstring): class ListTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.enable_mocktime() def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): + self.nodes[0].generate(1) # Get out of IBD + self.sync_all() # Simple send, 0 to 1: txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) self.sync_all() diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index 5caebf3739..f5d4780b6d 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -241,12 +241,11 @@ def count_format_specifiers(format_string): 4 """ assert(type(format_string) is str) + format_string = format_string.replace('%%', 'X') n = 0 in_specifier = False for i, char in enumerate(format_string): - if format_string[i - 1:i + 1] == "%%" or format_string[i:i + 2] == "%%": - pass - elif char == "%": + if char == "%": in_specifier = True n += 1 elif char in "aAcdeEfFgGinopsuxX": diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh index f6d0fd382b..4b9e2615b6 100755 --- a/test/lint/lint-includes.sh +++ b/test/lint/lint-includes.sh @@ -50,7 +50,6 @@ EXPECTED_BOOST_INCLUDES=( boost/algorithm/string/classification.hpp boost/algorithm/string/replace.hpp boost/algorithm/string/split.hpp - boost/bind.hpp boost/chrono/chrono.hpp boost/date_time/posix_time/posix_time.hpp boost/filesystem.hpp |