diff options
115 files changed, 2335 insertions, 1019 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 66958b75e7..2aebf1cd54 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,28 +3,21 @@ skip_tags: true image: Visual Studio 2017 configuration: Release platform: x64 +clone_depth: 5 environment: APPVEYOR_SAVE_CACHE_ON_ERROR: true CLCACHE_SERVER: 1 PACKAGES: boost-filesystem boost-signals2 boost-test libevent openssl zeromq berkeleydb secp256k1 leveldb - PYTHONIOENCODING: utf-8 + PATH: 'C:\Python37-x64;C:\Python37-x64\Scripts;%PATH%' + PYTHONUTF8: 1 cache: - C:\tools\vcpkg\installed - C:\Users\appveyor\clcache -init: -- cmd: set PATH=C:\Python36-x64;C:\Python36-x64\Scripts;%PATH% install: -- cmd: pip install git+https://github.com/frerich/clcache.git +- cmd: pip install --quiet git+https://github.com/frerich/clcache.git@v4.2.0 # Disable zmq test for now since python zmq library on Windows would cause Access violation sometimes. # - cmd: pip install zmq -- ps: $packages = $env:PACKAGES -Split ' ' -- ps: for ($i=0; $i -lt $packages.length; $i++) { - $env:ALL_PACKAGES += $packages[$i] + ":" + $env:PLATFORM + "-windows-static " - } -- cmd: git -C C:\Tools\vcpkg pull # This is a temporary fix, can be removed after appveyor update its image to include Microsoft/vcpkg#4046 -- cmd: C:\Tools\vcpkg\bootstrap-vcpkg.bat -- cmd: vcpkg remove --recurse --outdated -- cmd: vcpkg install %ALL_PACKAGES% +- cmd: vcpkg install --triplet %PLATFORM%-windows-static %PACKAGES% > NUL - cmd: del /s /q C:\Tools\vcpkg\installed\%PLATFORM%-windows-static\debug # Remove unused debug library before_build: - ps: clcache -M 536870912 @@ -42,7 +35,7 @@ before_build: build_script: - cmd: msbuild /p:TrackFileAccess=false /p:CLToolExe=clcache.exe build_msvc\bitcoin.sln /m /v:q /nowarn:C4244;C4267;C4715 /nologo after_build: -- ps: fsutil behavior set disablelastaccess 0 # Disable Access time feature on Windows (better performance) +- ps: fsutil behavior set disablelastaccess 1 # Disable Access time feature on Windows (better performance) - ps: clcache -z before_test: - ps: ${conf_ini} = (Get-Content([IO.Path]::Combine(${env:APPVEYOR_BUILD_FOLDER}, "test", "config.ini.in"))) @@ -57,9 +50,9 @@ before_test: - ps: '[IO.File]::WriteAllLines([IO.Path]::Combine(${env:APPVEYOR_BUILD_FOLDER}, "test", "config.ini"), ${conf_ini}, ${utf8})' - ps: move "build_msvc\${env:PLATFORM}\${env:CONFIGURATION}\*.exe" src test_script: -- cmd: src\test_bitcoin.exe -- ps: src\bench_bitcoin.exe -evals=1 -scaling=0 +- cmd: src\test_bitcoin.exe -k stdout -e stdout 2> NUL +- cmd: src\bench_bitcoin.exe -evals=1 -scaling=0 > NUL - ps: python test\util\bitcoin-util-test.py - cmd: python test\util\rpcauth-test.py -- cmd: python test\functional\test_runner.py --ci --force --quiet --combinedlogslen=4000 +- cmd: python test\functional\test_runner.py --ci --force --quiet --combinedlogslen=4000 --failfast deploy: off diff --git a/.travis.yml b/.travis.yml index 0e1f76e56d..1bf560fc4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -103,23 +103,14 @@ jobs: NO_DEPENDS=1 GOAL="install" BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-glibc-back-compat --enable-reduce-exports --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER" -# x86_64 Linux (no depends, only system libs) +# x86_64 Linux (no depends, only system libs, sanitizers: undefined (UBSAN) + integer) - stage: test env: >- HOST=x86_64-unknown-linux-gnu - PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libssl1.0-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" + PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libssl1.0-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" NO_DEPENDS=1 GOAL="install" - BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-glibc-back-compat --enable-reduce-exports --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER" -# x86_64 Linux (sanitizers) - - stage: test - env: >- - HOST=x86_64-unknown-linux-gnu - PACKAGES="clang python3-zmq qtbase5-dev qttools5-dev-tools libssl1.0-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" - NO_DEPENDS=1 - RUN_FUNCTIONAL_TESTS=false # Disabled for now, can be combined with the other x86_64 linux NO_DEPENDS job when functional tests pass the sanitizers - GOAL="install" - BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-glibc-back-compat --enable-reduce-exports --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=undefined CC=clang CXX=clang++" + BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-glibc-back-compat --enable-reduce-exports --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=integer,undefined CC=clang CXX=clang++" # x86_64 Linux, No wallet - stage: test env: >- diff --git a/.travis/lint_04_install.sh b/.travis/lint_04_install.sh index 8c3a9124b9..723e7c56f1 100755 --- a/.travis/lint_04_install.sh +++ b/.travis/lint_04_install.sh @@ -8,3 +8,4 @@ export LC_ALL=C travis_retry pip install codespell==1.13.0 travis_retry pip install flake8==3.5.0 +travis_retry pip install vulture==0.29 diff --git a/.travis/lint_06_script.sh b/.travis/lint_06_script.sh index 6191d82571..701e6d8005 100755 --- a/.travis/lint_06_script.sh +++ b/.travis/lint_06_script.sh @@ -19,6 +19,7 @@ test/lint/check-rpc-mappings.py . test/lint/lint-all.sh if [ "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_EVENT_TYPE" = "cron" ]; then + git log --merges --before="2 days ago" -1 --format='%H' > ./contrib/verify-commits/trusted-sha512-root-commit while read -r LINE; do travis_retry gpg --keyserver hkp://subset.pool.sks-keyservers.net --recv-keys $LINE; done < contrib/verify-commits/trusted-keys && - travis_wait 50 contrib/verify-commits/verify-commits.py; + travis_wait 50 contrib/verify-commits/verify-commits.py --clean-merge=2; fi diff --git a/.travis/test_03_before_install.sh b/.travis/test_03_before_install.sh index d091a67ca9..3c9fcf3f98 100755 --- a/.travis/test_03_before_install.sh +++ b/.travis/test_03_before_install.sh @@ -7,6 +7,8 @@ export LC_ALL=C.UTF-8 PATH=$(echo $PATH | tr ':' "\n" | sed '/\/opt\/python/d' | tr "\n" ":" | sed "s|::|:|g") +# Add llvm-symbolizer directory to PATH. Needed to get symbolized stack traces from the sanitizers. +PATH=$PATH:/usr/lib/llvm-6.0/bin/ export PATH BEGIN_FOLD () { diff --git a/.travis/test_04_install.sh b/.travis/test_04_install.sh index ef595287b7..4cf0ba8984 100755 --- a/.travis/test_04_install.sh +++ b/.travis/test_04_install.sh @@ -7,7 +7,8 @@ export LC_ALL=C.UTF-8 travis_retry docker pull "$DOCKER_NAME_TAG" -env | grep -E '^(CCACHE_|WINEDEBUG|LC_ALL|BOOST_TEST_RANDOM|CONFIG_SHELL)' | tee /tmp/env +export UBSAN_OPTIONS="suppressions=${TRAVIS_BUILD_DIR}/contrib/sanitizers-ubsan.suppressions:print_stacktrace=1:halt_on_error=1" +env | grep -E '^(CCACHE_|WINEDEBUG|LC_ALL|BOOST_TEST_RANDOM|CONFIG_SHELL|UBSAN_OPTIONS)' | tee /tmp/env if [[ $HOST = *-mingw32 ]]; then DOCKER_ADMIN="--cap-add SYS_ADMIN" fi @@ -31,7 +31,8 @@ The `master` branch is regularly built and tested, but is not guaranteed to be completely stable. [Tags](https://github.com/bitcoin/bitcoin/tags) are created regularly to indicate new official, stable release versions of Bitcoin Core. -The contribution workflow is described in [CONTRIBUTING.md](CONTRIBUTING.md). +The contribution workflow is described in [CONTRIBUTING.md](CONTRIBUTING.md) +and useful hints for developers can be found in [doc/developer-notes.md](doc/developer-notes.md). Testing ------- diff --git a/build_msvc/msvc-autogen.py b/build_msvc/msvc-autogen.py index 8888487e75..f351532f9d 100644 --- a/build_msvc/msvc-autogen.py +++ b/build_msvc/msvc-autogen.py @@ -16,10 +16,6 @@ libs = [ ] ignore_list = [ - 'rpc/net.cpp', - 'interfaces/handler.cpp', - 'interfaces/node.cpp', - 'interfaces/wallet.cpp', ] lib_sources = {} @@ -32,7 +28,9 @@ def parse_makefile(makefile): if current_lib: source = line.split()[0] if source.endswith('.cpp') and not source.startswith('$') and source not in ignore_list: - lib_sources[current_lib].append(source.replace('/', '\\')) + source_filename = source.replace('/', '\\') + object_filename = source.replace('/', '_')[:-4] + ".obj" + lib_sources[current_lib].append((source_filename, object_filename)) if not line.endswith('\\'): current_lib = '' continue @@ -51,8 +49,10 @@ def main(): for key, value in lib_sources.items(): vcxproj_filename = os.path.abspath(os.path.join(os.path.dirname(__file__), key, key + '.vcxproj')) content = '' - for source_filename in value: - content += ' <ClCompile Include="..\\..\\src\\' + source_filename + '" />\n' + for source_filename, object_filename in value: + content += ' <ClCompile Include="..\\..\\src\\' + source_filename + '">\n' + content += ' <ObjectFileName>$(IntDir)' + object_filename + '</ObjectFileName>\n' + content += ' </ClCompile>\n' with open(vcxproj_filename + '.in', 'r', encoding='utf-8') as vcxproj_in_file: with open(vcxproj_filename, 'w', encoding='utf-8') as vcxproj_file: vcxproj_file.write(vcxproj_in_file.read().replace( diff --git a/contrib/init/README.md b/contrib/init/README.md index 8d3e57c526..306a37f75a 100644 --- a/contrib/init/README.md +++ b/contrib/init/README.md @@ -5,7 +5,7 @@ Upstart: bitcoind.conf OpenRC: bitcoind.openrc bitcoind.openrcconf CentOS: bitcoind.init -macOS: org.bitcoin.bitcoind.plist +macOS: org.bitcoin.bitcoind.plist ``` have been made available to assist packagers in creating node packages here. diff --git a/contrib/sanitizers-ubsan.suppressions b/contrib/sanitizers-ubsan.suppressions new file mode 100644 index 0000000000..e90d5c2ac0 --- /dev/null +++ b/contrib/sanitizers-ubsan.suppressions @@ -0,0 +1,36 @@ +alignment:move.h +alignment:prevector.h +bool:wallet/wallet.cpp +float-divide-by-zero:policy/fees.cpp +float-divide-by-zero:validation.cpp +float-divide-by-zero:wallet/wallet.cpp +nonnull-attribute:support/cleanse.cpp +unsigned-integer-overflow:arith_uint256.h +unsigned-integer-overflow:basic_string.h +unsigned-integer-overflow:bench/bench.h +unsigned-integer-overflow:bitcoin-tx.cpp +unsigned-integer-overflow:bloom.cpp +unsigned-integer-overflow:chain.cpp +unsigned-integer-overflow:chain.h +unsigned-integer-overflow:coded_stream.h +unsigned-integer-overflow:core_write.cpp +unsigned-integer-overflow:crypto/chacha20.cpp +unsigned-integer-overflow:crypto/ctaes/ctaes.c +unsigned-integer-overflow:crypto/ripemd160.cpp +unsigned-integer-overflow:crypto/sha1.cpp +unsigned-integer-overflow:crypto/sha256.cpp +unsigned-integer-overflow:crypto/sha512.cpp +unsigned-integer-overflow:hash.cpp +unsigned-integer-overflow:leveldb/db/log_reader.cc +unsigned-integer-overflow:leveldb/util/bloom.cc +unsigned-integer-overflow:leveldb/util/crc32c.h +unsigned-integer-overflow:leveldb/util/hash.cc +unsigned-integer-overflow:policy/fees.cpp +unsigned-integer-overflow:prevector.h +unsigned-integer-overflow:script/interpreter.cpp +unsigned-integer-overflow:stl_bvector.h +unsigned-integer-overflow:streams.h +unsigned-integer-overflow:txmempool.cpp +unsigned-integer-overflow:util/strencodings.cpp +unsigned-integer-overflow:validation.cpp +vptr:fs.cpp diff --git a/doc/build-osx.md b/doc/build-osx.md index 7be21f7921..c9a59bab83 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -80,9 +80,9 @@ Running Bitcoin Core is now available at `./src/bitcoind` -Before running, it's recommended that you create an RPC configuration file: +Before running, you may create an empty configuration file: - echo -e "rpcuser=bitcoinrpc\nrpcpassword=$(xxd -l 16 -p /dev/urandom)" > "/Users/${USER}/Library/Application Support/Bitcoin/bitcoin.conf" + touch "/Users/${USER}/Library/Application Support/Bitcoin/bitcoin.conf" chmod 600 "/Users/${USER}/Library/Application Support/Bitcoin/bitcoin.conf" diff --git a/doc/release-notes-pr13381.md b/doc/release-notes-pr13381.md new file mode 100644 index 0000000000..75faad9906 --- /dev/null +++ b/doc/release-notes-pr13381.md @@ -0,0 +1,29 @@ +RPC importprivkey: new label behavior +------------------------------------- + +Previously, `importprivkey` automatically added the default empty label +("") to all addresses associated with the imported private key. Now it +defaults to using any existing label for those addresses. For example: + +- Old behavior: you import a watch-only address with the label "cold + wallet". Later, you import the corresponding private key using the + default settings. The address's label is changed from "cold wallet" + to "". + +- New behavior: you import a watch-only address with the label "cold + wallet". Later, you import the corresponding private key using the + default settings. The address's label remains "cold wallet". + +In both the previous and current case, if you directly specify a label +during the import, that label will override whatever previous label the +addresses may have had. Also in both cases, if none of the addresses +previously had a label, they will still receive the default empty label +(""). Examples: + +- You import a watch-only address with the label "temporary". Later you + import the corresponding private key with the label "final". The + address's label will be changed to "final". + +- You use the default settings to import a private key for an address that + was not previously in the wallet. Its addresses will receive the default + empty label (""). diff --git a/doc/release-notes/release-notes-0.17.0.1.md b/doc/release-notes/release-notes-0.17.0.1.md new file mode 100644 index 0000000000..92db7dac7d --- /dev/null +++ b/doc/release-notes/release-notes-0.17.0.1.md @@ -0,0 +1,41 @@ +Bitcoin Core version 0.17.0.1 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.17.0.1/> + +This release provides a minor bug fix for 0.17.0. + +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/> + +Notable changes +=============== + +An issue was solved with OSX dmg generation, affecting macOS 10.12 to 10.14, +which could cause Finder to crash on install. + +There are no significant changes for other operating systems. + +0.17.0.1 change log +=================== + +### Build system +- #14416 `eb2cc84` Fix OSX dmg issue (10.12 to 10.14) (jonasschnelli) + +### Documentation +- #14509 `1b5af2c` [0.17] doc: use SegWit in getblocktemplate example (Sjors) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- Jonas Schnelli +- Pieter Wuille +- Sjors Provoost +- Wladimir J. van der Laan + diff --git a/share/examples/bitcoin.conf b/share/examples/bitcoin.conf index 4dd73162a2..6062d49c40 100644 --- a/share/examples/bitcoin.conf +++ b/share/examples/bitcoin.conf @@ -71,12 +71,9 @@ # is .cookie and found in the `-datadir` being used for bitcoind. This option is typically used # when the server and client are run as the same user. # -# If not, you must set rpcuser and rpcpassword to secure the JSON-RPC api. The first -# method(DEPRECATED) is to set this pair for the server and client: -#rpcuser=Ulysseys -#rpcpassword=YourSuperGreatPasswordNumber_DO_NOT_USE_THIS_OR_YOU_WILL_GET_ROBBED_385593 +# If not, you must set rpcuser and rpcpassword to secure the JSON-RPC API. # -# The second method `rpcauth` can be added to server startup argument. It is set at initialization time +# The config option `rpcauth` can be added to server startup argument. It is set at initialization time # using the output from the script in share/rpcauth/rpcauth.py after providing a username: # # ./share/rpcauth/rpcauth.py alice @@ -116,21 +113,21 @@ # running on another host using this option: #rpcconnect=127.0.0.1 +# Wallet options + # Create transactions that have enough fees so they are likely to begin confirmation within n blocks (default: 6). # This setting is over-ridden by the -paytxfee option. #txconfirmtarget=n +# Pay a transaction fee every time you send bitcoins. +#paytxfee=0.000x + # Miscellaneous options # Pre-generate this many public/private key pairs, so wallet backups will be valid for # both prior transactions and several dozen future transactions. #keypool=100 -# Pay an optional transaction fee every time you send bitcoins. Transactions with fees -# are more likely than free transactions to be included in generated blocks, so may -# be validated sooner. -#paytxfee=0.00 - # Enable pruning to reduce storage requirements by deleting old blocks. # This mode is incompatible with -txindex and -rescan. # 0 = default (no pruning). diff --git a/share/qt/Info.plist.in b/share/qt/Info.plist.in index 0c0335a1e8..abe605efd0 100644 --- a/share/qt/Info.plist.in +++ b/share/qt/Info.plist.in @@ -97,9 +97,6 @@ <key>NSHighResolutionCapable</key> <string>True</string> - <key>LSAppNapIsDisabled</key> - <string>True</string> - <key>NSRequiresAquaSystemAppearance</key> <string>True</string> diff --git a/src/Makefile.am b/src/Makefile.am index 703304cebd..8dd0d31839 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -125,6 +125,7 @@ BITCOIN_CORE_H = \ index/txindex.h \ indirectmap.h \ init.h \ + interfaces/chain.h \ interfaces/handler.h \ interfaces/node.h \ interfaces/wallet.h \ @@ -183,6 +184,7 @@ BITCOIN_CORE_H = \ txmempool.h \ ui_interface.h \ undo.h \ + util/bytevectorhash.h \ util/system.h \ util/memory.h \ util/moneystr.h \ @@ -232,6 +234,7 @@ libbitcoin_server_a_SOURCES = \ httpserver.cpp \ index/base.cpp \ index/txindex.cpp \ + interfaces/chain.cpp \ interfaces/handler.cpp \ interfaces/node.cpp \ init.cpp \ @@ -321,7 +324,9 @@ crypto_libbitcoin_crypto_base_a_SOURCES = \ crypto/sha256.cpp \ crypto/sha256.h \ crypto/sha512.cpp \ - crypto/sha512.h + crypto/sha512.h \ + crypto/siphash.cpp \ + crypto/siphash.h if USE_ASM crypto_libbitcoin_crypto_base_a_SOURCES += crypto/sha256_sse4.cpp @@ -427,6 +432,7 @@ libbitcoin_util_a_SOURCES = \ support/cleanse.cpp \ sync.cpp \ threadinterrupt.cpp \ + util/bytevectorhash.cpp \ util/system.cpp \ util/moneystr.cpp \ util/strencodings.cpp \ @@ -461,6 +467,7 @@ endif bitcoind_LDADD = \ $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_WALLET) \ + $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_COMMON) \ $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 3ca2a7451d..445849e3d8 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -162,7 +162,8 @@ QT_MOC_CPP = \ BITCOIN_MM = \ qt/macdockiconhandler.mm \ - qt/macnotificationhandler.mm + qt/macnotificationhandler.mm \ + qt/macos_appnap.mm QT_MOC = \ qt/bitcoin.moc \ @@ -205,6 +206,7 @@ BITCOIN_QT_H = \ qt/intro.h \ qt/macdockiconhandler.h \ qt/macnotificationhandler.h \ + qt/macos_appnap.h \ qt/modaloverlay.h \ qt/networkstyle.h \ qt/notificator.h \ @@ -421,6 +423,10 @@ qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) if ENABLE_BIP70 qt_bitcoin_qt_LDADD += $(SSL_LIBS) +else +if TARGET_WINDOWS +qt_bitcoin_qt_LDADD += $(SSL_LIBS) +endif endif qt_bitcoin_qt_LDADD += $(CRYPTO_LIBS) qt_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index ac2299374c..2def0b23e2 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -7,6 +7,7 @@ #include <coins.h> #include <consensus/merkle.h> #include <consensus/validation.h> +#include <crypto/sha256.h> #include <miner.h> #include <policy/policy.h> #include <pow.h> diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index decdadfb26..8552ed34fd 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <bench/bench.h> +#include <interfaces/chain.h> #include <wallet/wallet.h> #include <wallet/coinselection.h> @@ -33,7 +34,8 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<Ou // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) static void CoinSelection(benchmark::State& state) { - const CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy()); + auto chain = interfaces::MakeChain(); + const CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet.cs_wallet); // Add coins. @@ -57,7 +59,8 @@ static void CoinSelection(benchmark::State& state) } typedef std::set<CInputCoin> CoinSet; -static const CWallet testWallet(WalletLocation(), WalletDatabase::CreateDummy()); +static auto testChain = interfaces::MakeChain(); +static const CWallet testWallet(*testChain, WalletLocation(), WalletDatabase::CreateDummy()); std::vector<std::unique_ptr<CWalletTx>> wtxn; // Copied from src/wallet/test/coinselector_tests.cpp diff --git a/src/bench/crypto_hash.cpp b/src/bench/crypto_hash.cpp index d7499a3767..dc0b054420 100644 --- a/src/bench/crypto_hash.cpp +++ b/src/bench/crypto_hash.cpp @@ -14,6 +14,7 @@ #include <crypto/sha1.h> #include <crypto/sha256.h> #include <crypto/sha512.h> +#include <crypto/siphash.h> /* Number of bytes to hash per iteration */ static const uint64_t BUFFER_SIZE = 1000*1000; diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index bdc064b9fb..bc91ca3641 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -255,7 +255,7 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu throw std::runtime_error("invalid TX input vout '" + strVout + "'"); // extract the optional sequence number - uint32_t nSequenceIn=std::numeric_limits<unsigned int>::max(); + uint32_t nSequenceIn = CTxIn::SEQUENCE_FINAL; if (vStrInputParts.size() > 2) nSequenceIn = std::stoul(vStrInputParts[2]); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index c6e2a7c20a..1306ba3821 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -11,6 +11,7 @@ #include <clientversion.h> #include <compat.h> #include <fs.h> +#include <interfaces/chain.h> #include <rpc/server.h> #include <init.h> #include <noui.h> @@ -58,6 +59,9 @@ static void WaitForShutdown() // static bool AppInit(int argc, char* argv[]) { + InitInterfaces interfaces; + interfaces.chain = interfaces::MakeChain(); + bool fRet = false; // @@ -164,7 +168,7 @@ static bool AppInit(int argc, char* argv[]) // If locking the data directory failed, exit immediately return false; } - fRet = AppInitMain(); + fRet = AppInitMain(interfaces); } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInit()"); @@ -178,7 +182,7 @@ static bool AppInit(int argc, char* argv[]) } else { WaitForShutdown(); } - Shutdown(); + Shutdown(interfaces); return fRet; } diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp index a06bced11b..10f51931f0 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -6,7 +6,8 @@ #include <consensus/consensus.h> #include <consensus/validation.h> #include <chainparams.h> -#include <hash.h> +#include <crypto/sha256.h> +#include <crypto/siphash.h> #include <random.h> #include <streams.h> #include <txmempool.h> diff --git a/src/blockfilter.cpp b/src/blockfilter.cpp index 91623fe70a..163e2a52ef 100644 --- a/src/blockfilter.cpp +++ b/src/blockfilter.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <blockfilter.h> +#include <crypto/siphash.h> #include <hash.h> #include <primitives/transaction.h> #include <script/script.h> diff --git a/src/blockfilter.h b/src/blockfilter.h index 46833ac0be..871be11769 100644 --- a/src/blockfilter.h +++ b/src/blockfilter.h @@ -5,14 +5,15 @@ #ifndef BITCOIN_BLOCKFILTER_H #define BITCOIN_BLOCKFILTER_H -#include <set> #include <stdint.h> +#include <unordered_set> #include <vector> #include <primitives/block.h> #include <serialize.h> #include <uint256.h> #include <undo.h> +#include <util/bytevectorhash.h> /** * This implements a Golomb-coded set as defined in BIP 158. It is a @@ -22,7 +23,7 @@ class GCSFilter { public: typedef std::vector<unsigned char> Element; - typedef std::set<Element> ElementSet; + typedef std::unordered_set<Element, ByteVectorHash> ElementSet; private: uint64_t m_siphash_k0; diff --git a/src/coins.cpp b/src/coins.cpp index f125b483bb..3ef9e0463c 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -6,6 +6,7 @@ #include <consensus/consensus.h> #include <random.h> +#include <version.h> bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; } uint256 CCoinsView::GetBestBlock() const { return uint256(); } diff --git a/src/coins.h b/src/coins.h index 3867a37b39..94493453f0 100644 --- a/src/coins.h +++ b/src/coins.h @@ -9,7 +9,7 @@ #include <primitives/transaction.h> #include <compressor.h> #include <core_memusage.h> -#include <hash.h> +#include <crypto/siphash.h> #include <memusage.h> #include <serialize.h> #include <uint256.h> diff --git a/src/crypto/siphash.cpp b/src/crypto/siphash.cpp new file mode 100644 index 0000000000..e81957111a --- /dev/null +++ b/src/crypto/siphash.cpp @@ -0,0 +1,173 @@ +// Copyright (c) 2016-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 <crypto/siphash.h> + +#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) + +#define SIPROUND do { \ + v0 += v1; v1 = ROTL(v1, 13); v1 ^= v0; \ + v0 = ROTL(v0, 32); \ + v2 += v3; v3 = ROTL(v3, 16); v3 ^= v2; \ + v0 += v3; v3 = ROTL(v3, 21); v3 ^= v0; \ + v2 += v1; v1 = ROTL(v1, 17); v1 ^= v2; \ + v2 = ROTL(v2, 32); \ +} while (0) + +CSipHasher::CSipHasher(uint64_t k0, uint64_t k1) +{ + v[0] = 0x736f6d6570736575ULL ^ k0; + v[1] = 0x646f72616e646f6dULL ^ k1; + v[2] = 0x6c7967656e657261ULL ^ k0; + v[3] = 0x7465646279746573ULL ^ k1; + count = 0; + tmp = 0; +} + +CSipHasher& CSipHasher::Write(uint64_t data) +{ + uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + + assert(count % 8 == 0); + + v3 ^= data; + SIPROUND; + SIPROUND; + v0 ^= data; + + v[0] = v0; + v[1] = v1; + v[2] = v2; + v[3] = v3; + + count += 8; + return *this; +} + +CSipHasher& CSipHasher::Write(const unsigned char* data, size_t size) +{ + uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + uint64_t t = tmp; + int c = count; + + while (size--) { + t |= ((uint64_t)(*(data++))) << (8 * (c % 8)); + c++; + if ((c & 7) == 0) { + v3 ^= t; + SIPROUND; + SIPROUND; + v0 ^= t; + t = 0; + } + } + + v[0] = v0; + v[1] = v1; + v[2] = v2; + v[3] = v3; + count = c; + tmp = t; + + return *this; +} + +uint64_t CSipHasher::Finalize() const +{ + uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + + uint64_t t = tmp | (((uint64_t)count) << 56); + + v3 ^= t; + SIPROUND; + SIPROUND; + v0 ^= t; + v2 ^= 0xFF; + SIPROUND; + SIPROUND; + SIPROUND; + SIPROUND; + return v0 ^ v1 ^ v2 ^ v3; +} + +uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val) +{ + /* Specialized implementation for efficiency */ + uint64_t d = val.GetUint64(0); + + uint64_t v0 = 0x736f6d6570736575ULL ^ k0; + uint64_t v1 = 0x646f72616e646f6dULL ^ k1; + uint64_t v2 = 0x6c7967656e657261ULL ^ k0; + uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; + + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(1); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(2); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(3); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + v3 ^= ((uint64_t)4) << 59; + SIPROUND; + SIPROUND; + v0 ^= ((uint64_t)4) << 59; + v2 ^= 0xFF; + SIPROUND; + SIPROUND; + SIPROUND; + SIPROUND; + return v0 ^ v1 ^ v2 ^ v3; +} + +uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra) +{ + /* Specialized implementation for efficiency */ + uint64_t d = val.GetUint64(0); + + uint64_t v0 = 0x736f6d6570736575ULL ^ k0; + uint64_t v1 = 0x646f72616e646f6dULL ^ k1; + uint64_t v2 = 0x6c7967656e657261ULL ^ k0; + uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; + + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(1); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(2); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(3); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = (((uint64_t)36) << 56) | extra; + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + v2 ^= 0xFF; + SIPROUND; + SIPROUND; + SIPROUND; + SIPROUND; + return v0 ^ v1 ^ v2 ^ v3; +} diff --git a/src/crypto/siphash.h b/src/crypto/siphash.h new file mode 100644 index 0000000000..b312f913f9 --- /dev/null +++ b/src/crypto/siphash.h @@ -0,0 +1,47 @@ +// Copyright (c) 2016-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_CRYPTO_SIPHASH_H +#define BITCOIN_CRYPTO_SIPHASH_H + +#include <stdint.h> + +#include <uint256.h> + +/** SipHash-2-4 */ +class CSipHasher +{ +private: + uint64_t v[4]; + uint64_t tmp; + int count; + +public: + /** Construct a SipHash calculator initialized with 128-bit key (k0, k1) */ + CSipHasher(uint64_t k0, uint64_t k1); + /** Hash a 64-bit integer worth of data + * It is treated as if this was the little-endian interpretation of 8 bytes. + * This function can only be used when a multiple of 8 bytes have been written so far. + */ + CSipHasher& Write(uint64_t data); + /** Hash arbitrary bytes. */ + CSipHasher& Write(const unsigned char* data, size_t size); + /** Compute the 64-bit SipHash-2-4 of the data written so far. The object remains untouched. */ + uint64_t Finalize() const; +}; + +/** Optimized SipHash-2-4 implementation for uint256. + * + * It is identical to: + * SipHasher(k0, k1) + * .Write(val.GetUint64(0)) + * .Write(val.GetUint64(1)) + * .Write(val.GetUint64(2)) + * .Write(val.GetUint64(3)) + * .Finalize() + */ +uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val); +uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra); + +#endif // BITCOIN_CRYPTO_SIPHASH_H diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 2a9b297029..9211a7596b 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -14,13 +14,7 @@ public: bool HasWalletSupport() const override {return false;} void AddWalletOptions() const override; bool ParameterInteraction() const override {return true;} - void RegisterRPC(CRPCTable &) const override {} - bool Verify() const override {return true;} - bool Open() const override {LogPrintf("No wallet support compiled in!\n"); return true;} - void Start(CScheduler& scheduler) const override {} - void Flush() const override {} - void Stop() const override {} - void Close() const override {} + void Construct(InitInterfaces& interfaces) const override {LogPrintf("No wallet support compiled in!\n");} }; void DummyWalletInit::AddWalletOptions() const diff --git a/src/hash.cpp b/src/hash.cpp index c049eea716..26150e5ca8 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -77,171 +77,3 @@ void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char he num[3] = (nChild >> 0) & 0xFF; CHMAC_SHA512(chainCode.begin(), chainCode.size()).Write(&header, 1).Write(data, 32).Write(num, 4).Finalize(output); } - -#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) - -#define SIPROUND do { \ - v0 += v1; v1 = ROTL(v1, 13); v1 ^= v0; \ - v0 = ROTL(v0, 32); \ - v2 += v3; v3 = ROTL(v3, 16); v3 ^= v2; \ - v0 += v3; v3 = ROTL(v3, 21); v3 ^= v0; \ - v2 += v1; v1 = ROTL(v1, 17); v1 ^= v2; \ - v2 = ROTL(v2, 32); \ -} while (0) - -CSipHasher::CSipHasher(uint64_t k0, uint64_t k1) -{ - v[0] = 0x736f6d6570736575ULL ^ k0; - v[1] = 0x646f72616e646f6dULL ^ k1; - v[2] = 0x6c7967656e657261ULL ^ k0; - v[3] = 0x7465646279746573ULL ^ k1; - count = 0; - tmp = 0; -} - -CSipHasher& CSipHasher::Write(uint64_t data) -{ - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; - - assert(count % 8 == 0); - - v3 ^= data; - SIPROUND; - SIPROUND; - v0 ^= data; - - v[0] = v0; - v[1] = v1; - v[2] = v2; - v[3] = v3; - - count += 8; - return *this; -} - -CSipHasher& CSipHasher::Write(const unsigned char* data, size_t size) -{ - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; - uint64_t t = tmp; - int c = count; - - while (size--) { - t |= ((uint64_t)(*(data++))) << (8 * (c % 8)); - c++; - if ((c & 7) == 0) { - v3 ^= t; - SIPROUND; - SIPROUND; - v0 ^= t; - t = 0; - } - } - - v[0] = v0; - v[1] = v1; - v[2] = v2; - v[3] = v3; - count = c; - tmp = t; - - return *this; -} - -uint64_t CSipHasher::Finalize() const -{ - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; - - uint64_t t = tmp | (((uint64_t)count) << 56); - - v3 ^= t; - SIPROUND; - SIPROUND; - v0 ^= t; - v2 ^= 0xFF; - SIPROUND; - SIPROUND; - SIPROUND; - SIPROUND; - return v0 ^ v1 ^ v2 ^ v3; -} - -uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val) -{ - /* Specialized implementation for efficiency */ - uint64_t d = val.GetUint64(0); - - uint64_t v0 = 0x736f6d6570736575ULL ^ k0; - uint64_t v1 = 0x646f72616e646f6dULL ^ k1; - uint64_t v2 = 0x6c7967656e657261ULL ^ k0; - uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; - - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(1); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(2); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(3); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - v3 ^= ((uint64_t)4) << 59; - SIPROUND; - SIPROUND; - v0 ^= ((uint64_t)4) << 59; - v2 ^= 0xFF; - SIPROUND; - SIPROUND; - SIPROUND; - SIPROUND; - return v0 ^ v1 ^ v2 ^ v3; -} - -uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra) -{ - /* Specialized implementation for efficiency */ - uint64_t d = val.GetUint64(0); - - uint64_t v0 = 0x736f6d6570736575ULL ^ k0; - uint64_t v1 = 0x646f72616e646f6dULL ^ k1; - uint64_t v2 = 0x6c7967656e657261ULL ^ k0; - uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; - - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(1); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(2); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(3); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = (((uint64_t)36) << 56) | extra; - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - v2 ^= 0xFF; - SIPROUND; - SIPROUND; - SIPROUND; - SIPROUND; - return v0 ^ v1 ^ v2 ^ v3; -} diff --git a/src/hash.h b/src/hash.h index 3534a400b3..6acab0b161 100644 --- a/src/hash.h +++ b/src/hash.h @@ -194,39 +194,4 @@ unsigned int MurmurHash3(unsigned int nHashSeed, const std::vector<unsigned char void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char header, const unsigned char data[32], unsigned char output[64]); -/** SipHash-2-4 */ -class CSipHasher -{ -private: - uint64_t v[4]; - uint64_t tmp; - int count; - -public: - /** Construct a SipHash calculator initialized with 128-bit key (k0, k1) */ - CSipHasher(uint64_t k0, uint64_t k1); - /** Hash a 64-bit integer worth of data - * It is treated as if this was the little-endian interpretation of 8 bytes. - * This function can only be used when a multiple of 8 bytes have been written so far. - */ - CSipHasher& Write(uint64_t data); - /** Hash arbitrary bytes. */ - CSipHasher& Write(const unsigned char* data, size_t size); - /** Compute the 64-bit SipHash-2-4 of the data written so far. The object remains untouched. */ - uint64_t Finalize() const; -}; - -/** Optimized SipHash-2-4 implementation for uint256. - * - * It is identical to: - * SipHasher(k0, k1) - * .Write(val.GetUint64(0)) - * .Write(val.GetUint64(1)) - * .Write(val.GetUint64(2)) - * .Write(val.GetUint64(3)) - * .Finalize() - */ -uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val); -uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra); - #endif // BITCOIN_HASH_H diff --git a/src/init.cpp b/src/init.cpp index d54d4a8782..3ab97be329 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -19,6 +19,7 @@ #include <fs.h> #include <httpserver.h> #include <httprpc.h> +#include <interfaces/chain.h> #include <index/txindex.h> #include <key.h> #include <validation.h> @@ -32,6 +33,7 @@ #include <rpc/server.h> #include <rpc/register.h> #include <rpc/blockchain.h> +#include <rpc/util.h> #include <script/standard.h> #include <script/sigcache.h> #include <scheduler.h> @@ -157,7 +159,7 @@ void Interrupt() } } -void Shutdown() +void Shutdown(InitInterfaces& interfaces) { LogPrintf("%s: In progress...\n", __func__); static CCriticalSection cs_Shutdown; @@ -176,7 +178,9 @@ void Shutdown() StopREST(); StopRPC(); StopHTTPServer(); - g_wallet_init_interface.Flush(); + for (const auto& client : interfaces.chain_clients) { + client->flush(); + } StopMapPort(); // Because these depend on each-other, we make sure that neither can be @@ -239,7 +243,9 @@ void Shutdown() pcoinsdbview.reset(); pblocktree.reset(); } - g_wallet_init_interface.Stop(); + for (const auto& client : interfaces.chain_clients) { + client->stop(); + } #if ENABLE_ZMQ if (g_zmq_notification_interface) { @@ -259,7 +265,7 @@ void Shutdown() UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); GetMainSignals().UnregisterWithMempoolSignals(mempool); - g_wallet_init_interface.Close(); + interfaces.chain_clients.clear(); globalVerifyHandle.reset(); ECC_Stop(); LogPrintf("%s: done\n", __func__); @@ -1158,7 +1164,7 @@ bool AppInitLockDataDirectory() return true; } -bool AppInitMain() +bool AppInitMain(InitInterfaces& interfaces) { const CChainParams& chainparams = Params(); // ********************************************************* Step 4a: application initialization @@ -1221,11 +1227,20 @@ bool AppInitMain() GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); GetMainSignals().RegisterWithMempoolSignals(mempool); + // Create client interfaces for wallets that are supposed to be loaded + // according to -wallet and -disablewallet options. This only constructs + // the interfaces, it doesn't load wallet data. Wallets actually get loaded + // when load() and start() interface methods are called below. + g_wallet_init_interface.Construct(interfaces); + /* Register RPC commands regardless of -server setting so they will be * available in the GUI RPC console even if external calls are disabled. */ RegisterAllCoreRPCCommands(tableRPC); - g_wallet_init_interface.RegisterRPC(tableRPC); + for (const auto& client : interfaces.chain_clients) { + client->registerRpcs(); + } + g_rpc_interfaces = &interfaces; #if ENABLE_ZMQ RegisterZMQRPCCommands(tableRPC); #endif @@ -1243,7 +1258,11 @@ bool AppInitMain() } // ********************************************************* Step 5: verify wallet database integrity - if (!g_wallet_init_interface.Verify()) return false; + for (const auto& client : interfaces.chain_clients) { + if (!client->verify()) { + return false; + } + } // ********************************************************* Step 6: network initialization // Note that we absolutely cannot open any actual connections @@ -1562,7 +1581,11 @@ bool AppInitMain() } // ********************************************************* Step 9: load wallet - if (!g_wallet_init_interface.Open()) return false; + for (const auto& client : interfaces.chain_clients) { + if (!client->load()) { + return false; + } + } // ********************************************************* Step 10: data directory maintenance @@ -1708,7 +1731,9 @@ bool AppInitMain() SetRPCWarmupFinished(); uiInterface.InitMessage(_("Done loading")); - g_wallet_init_interface.Start(scheduler); + for (const auto& client : interfaces.chain_clients) { + client->start(scheduler); + } return true; } diff --git a/src/init.h b/src/init.h index b106353d08..1c59ca069e 100644 --- a/src/init.h +++ b/src/init.h @@ -10,8 +10,17 @@ #include <string> #include <util/system.h> -class CScheduler; -class CWallet; +namespace interfaces { +class Chain; +class ChainClient; +} // namespace interfaces + +//! Pointers to interfaces used during init and destroyed on shutdown. +struct InitInterfaces +{ + std::unique_ptr<interfaces::Chain> chain; + std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients; +}; namespace boost { @@ -20,7 +29,7 @@ class thread_group; /** Interrupt threads */ void Interrupt(); -void Shutdown(); +void Shutdown(InitInterfaces& interfaces); //!Initialize the logging infrastructure void InitLogging(); //!Parameter interaction: change current parameters depending on various rules @@ -54,7 +63,7 @@ bool AppInitLockDataDirectory(); * @note This should only be done after daemonization. Call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitLockDataDirectory should have been called. */ -bool AppInitMain(); +bool AppInitMain(InitInterfaces& interfaces); /** * Setup the arguments for gArgs diff --git a/src/interfaces/README.md b/src/interfaces/README.md index e93b91d23c..57d41df746 100644 --- a/src/interfaces/README.md +++ b/src/interfaces/README.md @@ -4,7 +4,7 @@ The following interfaces are defined here: * [`Chain`](chain.h) — used by wallet to access blockchain and mempool state. Added in [#10973](https://github.com/bitcoin/bitcoin/pull/10973). -* [`Chain::Client`](chain.h) — used by node to start & stop `Chain` clients. Added in [#10973](https://github.com/bitcoin/bitcoin/pull/10973). +* [`ChainClient`](chain.h) — used by node to start & stop `Chain` clients. Added in [#10973](https://github.com/bitcoin/bitcoin/pull/10973). * [`Node`](node.h) — used by GUI to start & stop bitcoin node. Added in [#10244](https://github.com/bitcoin/bitcoin/pull/10244). diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp new file mode 100644 index 0000000000..2571a91031 --- /dev/null +++ b/src/interfaces/chain.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <interfaces/chain.h> + +#include <sync.h> +#include <util/system.h> +#include <validation.h> + +#include <memory> +#include <utility> + +namespace interfaces { +namespace { + +class LockImpl : public Chain::Lock +{ +}; + +class LockingStateImpl : public LockImpl, public UniqueLock<CCriticalSection> +{ + using UniqueLock::UniqueLock; +}; + +class ChainImpl : public Chain +{ +public: + std::unique_ptr<Chain::Lock> lock(bool try_lock) override + { + auto result = MakeUnique<LockingStateImpl>(::cs_main, "cs_main", __FILE__, __LINE__, try_lock); + if (try_lock && result && !*result) return {}; + // std::move necessary on some compilers due to conversion from + // LockingStateImpl to Lock pointer + return std::move(result); + } + std::unique_ptr<Chain::Lock> assumeLocked() override { return MakeUnique<LockImpl>(); } +}; + +} // namespace + +std::unique_ptr<Chain> MakeChain() { return MakeUnique<ChainImpl>(); } + +} // namespace interfaces diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h new file mode 100644 index 0000000000..fe5658de4b --- /dev/null +++ b/src/interfaces/chain.h @@ -0,0 +1,84 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_INTERFACES_CHAIN_H +#define BITCOIN_INTERFACES_CHAIN_H + +#include <memory> +#include <string> +#include <vector> + +class CScheduler; + +namespace interfaces { + +//! Interface for giving wallet processes access to blockchain state. +class Chain +{ +public: + virtual ~Chain() {} + + //! Interface for querying locked chain state, used by legacy code that + //! assumes state won't change between calls. New code should avoid using + //! the Lock interface and instead call higher-level Chain methods + //! that return more information so the chain doesn't need to stay locked + //! between calls. + class Lock + { + public: + virtual ~Lock() {} + }; + + //! Return Lock interface. Chain is locked when this is called, and + //! unlocked when the returned interface is freed. + virtual std::unique_ptr<Lock> lock(bool try_lock = false) = 0; + + //! Return Lock interface assuming chain is already locked. This + //! method is temporary and is only used in a few places to avoid changing + //! behavior while code is transitioned to use the Chain::Lock interface. + virtual std::unique_ptr<Lock> assumeLocked() = 0; +}; + +//! Interface to let node manage chain clients (wallets, or maybe tools for +//! monitoring and analysis in the future). +class ChainClient +{ +public: + virtual ~ChainClient() {} + + //! Register rpcs. + virtual void registerRpcs() = 0; + + //! Check for errors before loading. + virtual bool verify() = 0; + + //! Load saved state. + virtual bool load() = 0; + + //! Start client execution and provide a scheduler. + virtual void start(CScheduler& scheduler) = 0; + + //! Save state to disk. + virtual void flush() = 0; + + //! Shut down client. + virtual void stop() = 0; +}; + +//! Return implementation of Chain interface. +std::unique_ptr<Chain> MakeChain(); + +//! Return implementation of ChainClient interface for a wallet client. This +//! function will be undefined in builds where ENABLE_WALLET is false. +//! +//! Currently, wallets are the only chain clients. But in the future, other +//! types of chain clients could be added, such as tools for monitoring, +//! analysis, or fee estimation. These clients need to expose their own +//! MakeXXXClient functions returning their implementations of the ChainClient +//! interface. +std::unique_ptr<ChainClient> MakeWalletClient(Chain& chain, std::vector<std::string> wallet_filenames); + +} // namespace interfaces + +#endif // BITCOIN_INTERFACES_CHAIN_H diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 490c456e6e..1919e16a66 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -9,6 +9,7 @@ #include <chain.h> #include <chainparams.h> #include <init.h> +#include <interfaces/chain.h> #include <interfaces/handler.h> #include <interfaces/wallet.h> #include <net.h> @@ -50,6 +51,8 @@ namespace { class NodeImpl : public Node { +public: + NodeImpl() { m_interfaces.chain = MakeChain(); } bool parseParameters(int argc, const char* const argv[], std::string& error) override { return gArgs.ParseParameters(argc, argv, error); @@ -68,11 +71,11 @@ class NodeImpl : public Node return AppInitBasicSetup() && AppInitParameterInteraction() && AppInitSanityChecks() && AppInitLockDataDirectory(); } - bool appInitMain() override { return AppInitMain(); } + bool appInitMain() override { return AppInitMain(m_interfaces); } void appShutdown() override { Interrupt(); - Shutdown(); + Shutdown(m_interfaces); } void startShutdown() override { StartShutdown(); } bool shutdownRequested() override { return ShutdownRequested(); } @@ -291,6 +294,7 @@ class NodeImpl : public Node GuessVerificationProgress(Params().TxData(), block)); })); } + InitInterfaces m_interfaces; }; } // namespace diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 14c6bd0330..672a557d41 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -7,12 +7,16 @@ #include <amount.h> #include <chain.h> #include <consensus/validation.h> +#include <init.h> +#include <interfaces/chain.h> #include <interfaces/handler.h> #include <net.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> #include <primitives/transaction.h> +#include <rpc/server.h> +#include <scheduler.h> #include <script/ismine.h> #include <script/standard.h> #include <support/allocators/secure.h> @@ -20,10 +24,18 @@ #include <timedata.h> #include <ui_interface.h> #include <uint256.h> +#include <util/system.h> #include <validation.h> #include <wallet/feebumper.h> #include <wallet/fees.h> +#include <wallet/rpcwallet.h> #include <wallet/wallet.h> +#include <wallet/walletutil.h> + +#include <memory> +#include <string> +#include <utility> +#include <vector> namespace interfaces { namespace { @@ -41,7 +53,8 @@ public: WalletOrderForm order_form, std::string& reject_reason) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); CValidationState state; if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), m_key, g_connman.get(), state)) { reject_reason = state.GetRejectReason(); @@ -56,7 +69,7 @@ public: }; //! Construct wallet tx struct. -static WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, const CWalletTx& wtx) { WalletTx result; result.tx = wtx.tx; @@ -74,7 +87,7 @@ static WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LO IsMine(wallet, result.txout_address.back()) : ISMINE_NO); } - result.credit = wtx.GetCredit(ISMINE_ALL); + result.credit = wtx.GetCredit(locked_chain, ISMINE_ALL); result.debit = wtx.GetDebit(ISMINE_ALL); result.change = wtx.GetChange(); result.time = wtx.GetTxTime(); @@ -84,32 +97,38 @@ static WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LO } //! Construct wallet tx status struct. -static WalletTxStatus MakeWalletTxStatus(const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx) { + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + WalletTxStatus result; auto mi = ::mapBlockIndex.find(wtx.hashBlock); CBlockIndex* block = mi != ::mapBlockIndex.end() ? mi->second : nullptr; result.block_height = (block ? block->nHeight : std::numeric_limits<int>::max()); - result.blocks_to_maturity = wtx.GetBlocksToMaturity(); - result.depth_in_main_chain = wtx.GetDepthInMainChain(); + result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain); + result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain); result.time_received = wtx.nTimeReceived; result.lock_time = wtx.tx->nLockTime; result.is_final = CheckFinalTx(*wtx.tx); - result.is_trusted = wtx.IsTrusted(); + result.is_trusted = wtx.IsTrusted(locked_chain); result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); - result.is_in_main_chain = wtx.IsInMainChain(); + result.is_in_main_chain = wtx.IsInMainChain(locked_chain); return result; } //! Construct wallet TxOut struct. -static WalletTxOut MakeWalletTxOut(CWallet& wallet, const CWalletTx& wtx, int n, int depth) EXCLUSIVE_LOCKS_REQUIRED(cs_main, wallet.cs_wallet) +WalletTxOut MakeWalletTxOut(interfaces::Chain::Lock& locked_chain, + CWallet& wallet, + const CWalletTx& wtx, + int n, + int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { WalletTxOut result; result.txout = wtx.tx->vout[n]; result.time = wtx.GetTxTime(); result.depth_in_main_chain = depth; - result.is_spent = wallet.IsSpent(wtx.GetHash(), n); + result.is_spent = wallet.IsSpent(locked_chain, wtx.GetHash(), n); return result; } @@ -197,22 +216,26 @@ public: } void lockCoin(const COutPoint& output) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.LockCoin(output); } void unlockCoin(const COutPoint& output) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.UnlockCoin(output); } bool isLockedCoin(const COutPoint& output) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.IsLockedCoin(output.hash, output.n); } void listLockedCoins(std::vector<COutPoint>& outputs) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.ListLockedCoins(outputs); } std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient>& recipients, @@ -222,9 +245,10 @@ public: CAmount& fee, std::string& fail_reason) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto pending = MakeUnique<PendingWalletTxImpl>(m_wallet); - if (!m_wallet.CreateTransaction(recipients, pending->m_tx, pending->m_key, fee, change_pos, + if (!m_wallet.CreateTransaction(*locked_chain, recipients, pending->m_tx, pending->m_key, fee, change_pos, fail_reason, coin_control, sign)) { return {}; } @@ -233,8 +257,9 @@ public: bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet.TransactionCanBeAbandoned(txid); } bool abandonTransaction(const uint256& txid) override { - LOCK2(cs_main, m_wallet.cs_wallet); - return m_wallet.AbandonTransaction(txid); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); + return m_wallet.AbandonTransaction(*locked_chain, txid); } bool transactionCanBeBumped(const uint256& txid) override { @@ -262,7 +287,8 @@ public: } CTransactionRef getTx(const uint256& txid) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { return mi->second.tx; @@ -271,20 +297,22 @@ public: } WalletTx getWalletTx(const uint256& txid) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { - return MakeWalletTx(m_wallet, mi->second); + return MakeWalletTx(*locked_chain, m_wallet, mi->second); } return {}; } std::vector<WalletTx> getWalletTxs() override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); std::vector<WalletTx> result; result.reserve(m_wallet.mapWallet.size()); for (const auto& entry : m_wallet.mapWallet) { - result.emplace_back(MakeWalletTx(m_wallet, entry.second)); + result.emplace_back(MakeWalletTx(*locked_chain, m_wallet, entry.second)); } return result; } @@ -292,7 +320,7 @@ public: interfaces::WalletTxStatus& tx_status, int& num_blocks) override { - TRY_LOCK(::cs_main, locked_chain); + auto locked_chain = m_wallet.chain().lock(true /* try_lock */); if (!locked_chain) { return false; } @@ -305,7 +333,7 @@ public: return false; } num_blocks = ::chainActive.Height(); - tx_status = MakeWalletTxStatus(mi->second); + tx_status = MakeWalletTxStatus(*locked_chain, mi->second); return true; } WalletTx getWalletTxDetails(const uint256& txid, @@ -314,14 +342,15 @@ public: bool& in_mempool, int& num_blocks) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { num_blocks = ::chainActive.Height(); in_mempool = mi->second.InMempool(); order_form = mi->second.vOrderForm; - tx_status = MakeWalletTxStatus(mi->second); - return MakeWalletTx(m_wallet, mi->second); + tx_status = MakeWalletTxStatus(*locked_chain, mi->second); + return MakeWalletTx(*locked_chain, m_wallet, mi->second); } return {}; } @@ -341,7 +370,7 @@ public: } bool tryGetBalances(WalletBalances& balances, int& num_blocks) override { - TRY_LOCK(cs_main, locked_chain); + auto locked_chain = m_wallet.chain().lock(true /* try_lock */); if (!locked_chain) return false; TRY_LOCK(m_wallet.cs_wallet, locked_wallet); if (!locked_wallet) { @@ -358,49 +387,55 @@ public: } isminetype txinIsMine(const CTxIn& txin) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.IsMine(txin); } isminetype txoutIsMine(const CTxOut& txout) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.IsMine(txout); } CAmount getDebit(const CTxIn& txin, isminefilter filter) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.GetDebit(txin, filter); } CAmount getCredit(const CTxOut& txout, isminefilter filter) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.GetCredit(txout, filter); } CoinsList listCoins() override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); CoinsList result; - for (const auto& entry : m_wallet.ListCoins()) { + for (const auto& entry : m_wallet.ListCoins(*locked_chain)) { auto& group = result[entry.first]; for (const auto& coin : entry.second) { - group.emplace_back( - COutPoint(coin.tx->GetHash(), coin.i), MakeWalletTxOut(m_wallet, *coin.tx, coin.i, coin.nDepth)); + group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i), + MakeWalletTxOut(*locked_chain, m_wallet, *coin.tx, coin.i, coin.nDepth)); } } return result; } std::vector<WalletTxOut> getCoins(const std::vector<COutPoint>& outputs) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); std::vector<WalletTxOut> result; result.reserve(outputs.size()); for (const auto& output : outputs) { result.emplace_back(); auto it = m_wallet.mapWallet.find(output.hash); if (it != m_wallet.mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + int depth = it->second.GetDepthInMainChain(*locked_chain); if (depth >= 0) { - result.back() = MakeWalletTxOut(m_wallet, it->second, output.n, depth); + result.back() = MakeWalletTxOut(*locked_chain, m_wallet, it->second, output.n, depth); } } } @@ -456,8 +491,32 @@ public: CWallet& m_wallet; }; +class WalletClientImpl : public ChainClient +{ +public: + WalletClientImpl(Chain& chain, std::vector<std::string> wallet_filenames) + : m_chain(chain), m_wallet_filenames(std::move(wallet_filenames)) + { + } + void registerRpcs() override { return RegisterWalletRPCCommands(::tableRPC); } + bool verify() override { return VerifyWallets(m_chain, m_wallet_filenames); } + bool load() override { return LoadWallets(m_chain, m_wallet_filenames); } + void start(CScheduler& scheduler) override { return StartWallets(scheduler); } + void flush() override { return FlushWallets(); } + void stop() override { return StopWallets(); } + ~WalletClientImpl() override { UnloadWallets(); } + + Chain& m_chain; + std::vector<std::string> m_wallet_filenames; +}; + } // namespace std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet) { return MakeUnique<WalletImpl>(wallet); } +std::unique_ptr<ChainClient> MakeWalletClient(Chain& chain, std::vector<std::string> wallet_filenames) +{ + return MakeUnique<WalletClientImpl>(chain, std::move(wallet_filenames)); +} + } // namespace interfaces @@ -11,6 +11,7 @@ #include <amount.h> #include <bloom.h> #include <compat.h> +#include <crypto/siphash.h> #include <hash.h> #include <limitedmap.h> #include <netaddress.h> diff --git a/src/net_processing.cpp b/src/net_processing.cpp index f37312d155..f4ab3aa153 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2357,6 +2357,23 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr for (const CTransactionRef& removedTx : lRemovedTxn) AddToCompactExtraTransactions(removedTx); + // If a tx has been detected by recentRejects, we will have reached + // this point and the tx will have been ignored. Because we haven't run + // the tx through AcceptToMemoryPool, we won't have computed a DoS + // score for it or determined exactly why we consider it invalid. + // + // This means we won't penalize any peer subsequently relaying a DoSy + // tx (even if we penalized the first peer who gave it to us) because + // we have to account for recentRejects showing false positives. In + // other words, we shouldn't penalize a peer if we aren't *sure* they + // submitted a DoSy tx. + // + // Note that recentRejects doesn't just record DoSy or invalid + // transactions, but any tx not accepted by the mempool, which may be + // due to node policy (vs. consensus). So we can't blanket penalize a + // peer simply for relaying a tx that our recentRejects has caught, + // regardless of false positives. + int nDoS = 0; if (state.IsInvalid(nDoS)) { diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp index 18f9c0c2a8..0dc130d104 100644 --- a/src/policy/rbf.cpp +++ b/src/policy/rbf.cpp @@ -7,7 +7,7 @@ bool SignalsOptInRBF(const CTransaction &tx) { for (const CTxIn &txin : tx.vin) { - if (txin.nSequence < std::numeric_limits<unsigned int>::max()-1) { + if (txin.nSequence <= MAX_BIP125_RBF_SEQUENCE) { return true; } } diff --git a/src/prevector.h b/src/prevector.h index 6ddb6f321f..aa77573746 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -10,6 +10,7 @@ #include <stdint.h> #include <string.h> +#include <algorithm> #include <cstddef> #include <iterator> #include <type_traits> @@ -198,22 +199,11 @@ private: const T* item_ptr(difference_type pos) const { return is_direct() ? direct_ptr(pos) : indirect_ptr(pos); } void fill(T* dst, ptrdiff_t count) { - if (IS_TRIVIALLY_CONSTRUCTIBLE<T>::value) { - // The most common use of prevector is where T=unsigned char. For - // trivially constructible types, we can use memset() to avoid - // looping. - ::memset(dst, 0, count * sizeof(T)); - } else { - for (auto i = 0; i < count; ++i) { - new(static_cast<void*>(dst + i)) T(); - } - } + std::fill_n(dst, count, T{}); } void fill(T* dst, ptrdiff_t count, const T& value) { - for (auto i = 0; i < count; ++i) { - new(static_cast<void*>(dst + i)) T(value); - } + std::fill_n(dst, count, value); } template<typename InputIterator> diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index b68f3a439b..558fcf50ba 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -23,9 +23,7 @@ class AmountSpinBox: public QAbstractSpinBox public: explicit AmountSpinBox(QWidget *parent): - QAbstractSpinBox(parent), - currentUnit(BitcoinUnits::BTC), - singleStep(100000) // satoshis + QAbstractSpinBox(parent) { setAlignment(Qt::AlignRight); @@ -44,10 +42,19 @@ public: void fixup(QString &input) const { - bool valid = false; - CAmount val = parse(input, &valid); - if(valid) - { + bool valid; + CAmount val; + + if (input.isEmpty() && !m_allow_empty) { + valid = true; + val = m_min_amount; + } else { + valid = false; + val = parse(input, &valid); + } + + if (valid) { + val = qBound(m_min_amount, val, m_max_amount); input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways); lineEdit()->setText(input); } @@ -64,12 +71,27 @@ public: Q_EMIT valueChanged(); } + void SetAllowEmpty(bool allow) + { + m_allow_empty = allow; + } + + void SetMinValue(const CAmount& value) + { + m_min_amount = value; + } + + void SetMaxValue(const CAmount& value) + { + m_max_amount = value; + } + void stepBy(int steps) { bool valid = false; CAmount val = value(&valid); val = val + steps * singleStep; - val = qMin(qMax(val, CAmount(0)), BitcoinUnits::maxMoney()); + val = qBound(m_min_amount, val, m_max_amount); setValue(val); } @@ -125,9 +147,12 @@ public: } private: - int currentUnit; - CAmount singleStep; + int currentUnit{BitcoinUnits::BTC}; + CAmount singleStep{CAmount(100000)}; // satoshis mutable QSize cachedMinimumSizeHint; + bool m_allow_empty{true}; + CAmount m_min_amount{CAmount(0)}; + CAmount m_max_amount{BitcoinUnits::maxMoney()}; /** * Parse a string into a number of base monetary units and @@ -174,11 +199,10 @@ protected: StepEnabled rv = 0; bool valid = false; CAmount val = value(&valid); - if(valid) - { - if(val > 0) + if (valid) { + if (val > m_min_amount) rv |= StepDownEnabled; - if(val < BitcoinUnits::maxMoney()) + if (val < m_max_amount) rv |= StepUpEnabled; } return rv; @@ -275,6 +299,21 @@ void BitcoinAmountField::setValue(const CAmount& value) amount->setValue(value); } +void BitcoinAmountField::SetAllowEmpty(bool allow) +{ + amount->SetAllowEmpty(allow); +} + +void BitcoinAmountField::SetMinValue(const CAmount& value) +{ + amount->SetMinValue(value); +} + +void BitcoinAmountField::SetMaxValue(const CAmount& value) +{ + amount->SetMaxValue(value); +} + void BitcoinAmountField::setReadOnly(bool fReadOnly) { amount->setReadOnly(fReadOnly); diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h index f93579c492..650481e30d 100644 --- a/src/qt/bitcoinamountfield.h +++ b/src/qt/bitcoinamountfield.h @@ -31,6 +31,15 @@ public: CAmount value(bool *value=0) const; void setValue(const CAmount& value); + /** If allow empty is set to false the field will be set to the minimum allowed value if left empty. **/ + void SetAllowEmpty(bool allow); + + /** Set the minimum value in satoshis **/ + void SetMinValue(const CAmount& value); + + /** Set the maximum value in satoshis **/ + void SetMaxValue(const CAmount& value); + /** Set single step in satoshis **/ void setSingleStep(const CAmount& step); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 072334ebb0..ef82351551 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -92,12 +92,8 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty windowTitle += tr("Node"); } windowTitle += " " + networkStyle->getTitleAddText(); -#ifndef Q_OS_MAC QApplication::setWindowIcon(networkStyle->getTrayAndWindowIcon()); setWindowIcon(networkStyle->getTrayAndWindowIcon()); -#else - MacDockIconHandler::instance()->setIcon(networkStyle->getAppIcon()); -#endif setWindowTitle(windowTitle); rpcConsole = new RPCConsole(node, _platformStyle, 0); @@ -131,7 +127,9 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty createToolBars(); // Create system tray icon and notification - createTrayIcon(networkStyle); + if (QSystemTrayIcon::isSystemTrayAvailable()) { + createTrayIcon(networkStyle); + } // Create status bar statusBar(); @@ -211,6 +209,10 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay); } #endif + +#ifdef Q_OS_MAC + m_app_nap_inhibitor = new CAppNapInhibitor; +#endif } BitcoinGUI::~BitcoinGUI() @@ -223,6 +225,7 @@ BitcoinGUI::~BitcoinGUI() if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) trayIcon->hide(); #ifdef Q_OS_MAC + delete m_app_nap_inhibitor; delete appMenuBar; MacDockIconHandler::cleanup(); #endif @@ -273,17 +276,17 @@ void BitcoinGUI::createActions() #ifdef ENABLE_WALLET // These showNormalIfMinimized are needed because Send Coins and Receive Coins // can be triggered from the tray menu, and need to show the GUI to be useful. - connect(overviewAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(overviewAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(overviewAction, &QAction::triggered, this, &BitcoinGUI::gotoOverviewPage); - connect(sendCoinsAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(sendCoinsAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(sendCoinsAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); }); - connect(sendCoinsMenuAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(sendCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(sendCoinsMenuAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); }); - connect(receiveCoinsAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(receiveCoinsAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(receiveCoinsAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); - connect(receiveCoinsMenuAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(receiveCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(receiveCoinsMenuAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); - connect(historyAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(historyAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage); #endif // ENABLE_WALLET @@ -350,7 +353,9 @@ void BitcoinGUI::createActions() connect(encryptWalletAction, &QAction::triggered, walletFrame, &WalletFrame::encryptWallet); connect(backupWalletAction, &QAction::triggered, walletFrame, &WalletFrame::backupWallet); connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase); + connect(signMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); }); + connect(verifyMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); }); connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses); connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedReceivingAddresses); @@ -585,6 +590,8 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle) { + assert(QSystemTrayIcon::isSystemTrayAvailable()); + #ifndef Q_OS_MAC trayIcon = new QSystemTrayIcon(this); QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + networkStyle->getTitleAddText(); @@ -599,7 +606,7 @@ void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle) void BitcoinGUI::createTrayIconMenu() { #ifndef Q_OS_MAC - // return if trayIcon is unset (only on non-Mac OSes) + // return if trayIcon is unset (only on non-macOSes) if (!trayIcon) return; @@ -608,15 +615,17 @@ void BitcoinGUI::createTrayIconMenu() connect(trayIcon, &QSystemTrayIcon::activated, this, &BitcoinGUI::trayIconActivated); #else - // Note: On Mac, the dock icon is used to provide the tray's functionality. + // Note: On macOS, the Dock icon is used to provide the tray's functionality. MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance(); - dockIconHandler->setMainWindow(static_cast<QMainWindow*>(this)); - trayIconMenu = dockIconHandler->dockMenu(); + connect(dockIconHandler, &MacDockIconHandler::dockIconClicked, this, &BitcoinGUI::macosDockIconActivated); + + trayIconMenu = new QMenu(this); + trayIconMenu->setAsDockMenu(); #endif - // Configuration of the tray icon (or dock icon) icon menu + // Configuration of the tray icon (or Dock icon) menu #ifndef Q_OS_MAC - // Note: On Mac, the dock icon's menu already has show / hide action. + // Note: On macOS, the Dock icon's menu already has Show / Hide action. trayIconMenu->addAction(toggleHideAction); trayIconMenu->addSeparator(); #endif @@ -630,7 +639,7 @@ void BitcoinGUI::createTrayIconMenu() trayIconMenu->addAction(openRPCConsoleAction); } trayIconMenu->addAction(optionsAction); -#ifndef Q_OS_MAC // This is built-in on Mac +#ifndef Q_OS_MAC // This is built-in on macOS trayIconMenu->addSeparator(); trayIconMenu->addAction(quitAction); #endif @@ -645,6 +654,12 @@ void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) toggleHidden(); } } +#else +void BitcoinGUI::macosDockIconActivated() +{ + show(); + activateWindow(); +} #endif void BitcoinGUI::optionsClicked() @@ -663,10 +678,7 @@ void BitcoinGUI::aboutClicked() void BitcoinGUI::showDebugWindow() { - rpcConsole->showNormal(); - rpcConsole->show(); - rpcConsole->raise(); - rpcConsole->activateWindow(); + GUIUtil::bringToFront(rpcConsole); } void BitcoinGUI::showDebugWindowActivateConsole() @@ -786,6 +798,11 @@ void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab) void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool header) { +// Disabling macOS App Nap on initial sync, disk and reindex operations. +#ifdef Q_OS_MAC + (m_node.isInitialBlockDownload() || m_node.getReindex() || m_node.getImporting()) ? m_app_nap_inhibitor->disableAppNap() : m_app_nap_inhibitor->enableAppNap(); +#endif + if (modalOverlay) { if (header) @@ -1148,24 +1165,11 @@ void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden) if(!clientModel) return; - // activateWindow() (sometimes) helps with keyboard focus on Windows - if (isHidden()) - { - show(); - activateWindow(); - } - else if (isMinimized()) - { - showNormal(); - activateWindow(); - } - else if (GUIUtil::isObscured(this)) - { - raise(); - activateWindow(); - } - else if(fToggleHidden) + if (!isHidden() && !isMinimized() && !GUIUtil::isObscured(this) && fToggleHidden) { hide(); + } else { + GUIUtil::bringToFront(this); + } } void BitcoinGUI::toggleHidden() diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index dcaca10557..e8b857c17c 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -20,6 +20,10 @@ #include <QPoint> #include <QSystemTrayIcon> +#ifdef Q_OS_MAC +#include <qt/macos_appnap.h> +#endif + #include <memory> class ClientModel; @@ -143,6 +147,10 @@ private: HelpMessageDialog* helpMessageDialog = nullptr; ModalOverlay* modalOverlay = nullptr; +#ifdef Q_OS_MAC + CAppNapInhibitor* m_app_nap_inhibitor = nullptr; +#endif + /** Keep track of previous number of blocks, to detect progress */ int prevBlocks = 0; int spinnerFrame = 0; @@ -260,6 +268,9 @@ public Q_SLOTS: #ifndef Q_OS_MAC /** Handle tray icon clicked */ void trayIconActivated(QSystemTrayIcon::ActivationReason reason); +#else + /** Handle macOS Dock icon clicked */ + void macosDockIconActivated(); #endif /** Show window if hidden, unminimize when minimized, rise when obscured or show if hidden and fToggleHidden is true */ diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 6b31ddea90..386d559281 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -878,28 +878,15 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <item> <layout class="QHBoxLayout" name="horizontalLayoutFee8"> <item> - <widget class="QCheckBox" name="checkBoxMinimumFee"> - <property name="toolTip"> - <string>Paying only the minimum fee is just fine as long as there is less transaction volume than space in the blocks. But be aware that this can end up in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> - </property> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="labelMinFeeWarning"> + <widget class="QLabel" name="labelCustomFeeWarning"> <property name="enabled"> <bool>true</bool> </property> <property name="toolTip"> - <string>Paying only the minimum fee is just fine as long as there is less transaction volume than space in the blocks. But be aware that this can end up in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> + <string>When there is less transaction volume than space in the blocks, miners as well as relaying nodes may enforce a minimum fee. Paying only this minimum fee is just fine, but be aware that this can result in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> </property> <property name="text"> - <string>(read the tooltip)</string> - </property> - <property name="margin"> - <number>5</number> + <string>A too low fee might result in a never confirming transaction (read the tooltip)</string> </property> </widget> </item> @@ -992,9 +979,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <property name="text"> <string/> </property> - <property name="margin"> - <number>2</number> - </property> </widget> </item> <item> @@ -1009,9 +993,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <property name="text"> <string>(Smart fee not initialized yet. This usually takes a few blocks...)</string> </property> - <property name="margin"> - <number>2</number> - </property> </widget> </item> <item> @@ -1038,24 +1019,8 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <property name="text"> <string>Confirmation time target:</string> </property> - <property name="margin"> - <number>2</number> - </property> </widget> </item> - <item> - <spacer name="verticalSpacer_3"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>1</width> - <height>1</height> - </size> - </property> - </spacer> - </item> </layout> </item> <item> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index a68140ccf9..b1cd2f77d0 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -60,6 +60,14 @@ #include <QFontDatabase> #endif +#if defined(Q_OS_MAC) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +#include <objc/objc-runtime.h> +#include <CoreServices/CoreServices.h> +#endif + namespace GUIUtil { QString dateTimeStr(const QDateTime &date) @@ -353,6 +361,27 @@ bool isObscured(QWidget *w) && checkPoint(QPoint(w->width() / 2, w->height() / 2), w)); } +void bringToFront(QWidget* w) +{ +#ifdef Q_OS_MAC + // Force application activation on macOS. With Qt 5.4 this is required when + // an action in the dock menu is triggered. + id app = objc_msgSend((id) objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + objc_msgSend(app, sel_registerName("activateIgnoringOtherApps:"), YES); +#endif + + if (w) { + // activateWindow() (sometimes) helps with keyboard focus on Windows + if (w->isMinimized()) { + w->showNormal(); + } else { + w->show(); + } + w->activateWindow(); + w->raise(); + } +} + void openDebugLogfile() { fs::path pathDebug = GetDataDir() / "debug.log"; @@ -663,13 +692,8 @@ bool SetStartOnSystemStartup(bool fAutoStart) #elif defined(Q_OS_MAC) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // based on: https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m -#include <CoreFoundation/CoreFoundation.h> -#include <CoreServices/CoreServices.h> - LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl); LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl) { diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 011827e134..f1d0aa48ef 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -115,6 +115,9 @@ namespace GUIUtil // Determine whether a widget is hidden behind other windows bool isObscured(QWidget *w); + // Activate, show and raise the widget + void bringToFront(QWidget* w); + // Open debug.log void openDebugLogfile(); diff --git a/src/qt/macdockiconhandler.h b/src/qt/macdockiconhandler.h index 1c28593d4a..ff867e21a7 100644 --- a/src/qt/macdockiconhandler.h +++ b/src/qt/macdockiconhandler.h @@ -1,44 +1,27 @@ -// Copyright (c) 2011-2015 The Bitcoin Core developers +// 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_MACDOCKICONHANDLER_H #define BITCOIN_QT_MACDOCKICONHANDLER_H -#include <QMainWindow> #include <QObject> -QT_BEGIN_NAMESPACE -class QIcon; -class QMenu; -class QWidget; -QT_END_NAMESPACE - -/** Macintosh-specific dock icon handler. +/** macOS-specific Dock icon handler. */ class MacDockIconHandler : public QObject { Q_OBJECT public: - ~MacDockIconHandler(); - - QMenu *dockMenu(); - void setIcon(const QIcon &icon); - void setMainWindow(QMainWindow *window); static MacDockIconHandler *instance(); static void cleanup(); - void handleDockIconClickEvent(); Q_SIGNALS: void dockIconClicked(); private: MacDockIconHandler(); - - QWidget *m_dummyWidget; - QMenu *m_dockMenu; - QMainWindow *mainWindow; }; #endif // BITCOIN_QT_MACDOCKICONHANDLER_H diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm index b9ad191da7..102adce6c5 100644 --- a/src/qt/macdockiconhandler.mm +++ b/src/qt/macdockiconhandler.mm @@ -1,107 +1,36 @@ -// Copyright (c) 2011-2013 The Bitcoin Core developers +// 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 "macdockiconhandler.h" -#include <QImageWriter> -#include <QMenu> -#include <QBuffer> -#include <QWidget> - #undef slots -#include <Cocoa/Cocoa.h> #include <objc/objc.h> #include <objc/message.h> static MacDockIconHandler *s_instance = nullptr; -bool dockClickHandler(id self,SEL _cmd,...) { +bool dockClickHandler(id self, SEL _cmd, ...) { Q_UNUSED(self) Q_UNUSED(_cmd) - s_instance->handleDockIconClickEvent(); + Q_EMIT s_instance->dockIconClicked(); - // Return NO (false) to suppress the default OS X actions + // Return NO (false) to suppress the default macOS actions return false; } void setupDockClickHandler() { - Class cls = objc_getClass("NSApplication"); - id appInst = objc_msgSend((id)cls, sel_registerName("sharedApplication")); - - if (appInst != nullptr) { - id delegate = objc_msgSend(appInst, sel_registerName("delegate")); - Class delClass = (Class)objc_msgSend(delegate, sel_registerName("class")); - SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); - if (class_getInstanceMethod(delClass, shouldHandle)) - class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"); - else - class_addMethod(delClass, shouldHandle, (IMP)dockClickHandler,"B@:"); - } + id app = objc_msgSend((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + id delegate = objc_msgSend(app, sel_registerName("delegate")); + Class delClass = (Class)objc_msgSend(delegate, sel_registerName("class")); + SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); + class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"); } - MacDockIconHandler::MacDockIconHandler() : QObject() { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - setupDockClickHandler(); - this->m_dummyWidget = new QWidget(); - this->m_dockMenu = new QMenu(this->m_dummyWidget); - this->setMainWindow(nullptr); -#if QT_VERSION >= 0x050200 - this->m_dockMenu->setAsDockMenu(); -#endif - [pool release]; -} - -void MacDockIconHandler::setMainWindow(QMainWindow *window) { - this->mainWindow = window; -} - -MacDockIconHandler::~MacDockIconHandler() -{ - delete this->m_dummyWidget; - this->setMainWindow(nullptr); -} - -QMenu *MacDockIconHandler::dockMenu() -{ - return this->m_dockMenu; -} - -void MacDockIconHandler::setIcon(const QIcon &icon) -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSImage *image = nil; - if (icon.isNull()) - image = [[NSImage imageNamed:@"NSApplicationIcon"] retain]; - else { - // generate NSImage from QIcon and use this as dock icon. - QSize size = icon.actualSize(QSize(128, 128)); - QPixmap pixmap = icon.pixmap(size); - - // Write image into a R/W buffer from raw pixmap, then save the image. - QBuffer notificationBuffer; - if (!pixmap.isNull() && notificationBuffer.open(QIODevice::ReadWrite)) { - QImageWriter writer(¬ificationBuffer, "PNG"); - if (writer.write(pixmap.toImage())) { - NSData* macImgData = [NSData dataWithBytes:notificationBuffer.buffer().data() - length:notificationBuffer.buffer().size()]; - image = [[NSImage alloc] initWithData:macImgData]; - } - } - - if(!image) { - // if testnet image could not be created, load std. app icon - image = [[NSImage imageNamed:@"NSApplicationIcon"] retain]; - } - } - - [NSApp setApplicationIconImage:image]; - [image release]; - [pool release]; } MacDockIconHandler *MacDockIconHandler::instance() @@ -115,14 +44,3 @@ void MacDockIconHandler::cleanup() { delete s_instance; } - -void MacDockIconHandler::handleDockIconClickEvent() -{ - if (this->mainWindow) - { - this->mainWindow->activateWindow(); - this->mainWindow->show(); - } - - Q_EMIT this->dockIconClicked(); -} diff --git a/src/qt/macos_appnap.h b/src/qt/macos_appnap.h new file mode 100644 index 0000000000..8c2cd840b0 --- /dev/null +++ b/src/qt/macos_appnap.h @@ -0,0 +1,24 @@ +// 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_MACOS_APPNAP_H +#define BITCOIN_QT_MACOS_APPNAP_H + +#include <memory> + +class CAppNapInhibitor final +{ +public: + explicit CAppNapInhibitor(); + ~CAppNapInhibitor(); + + void disableAppNap(); + void enableAppNap(); + +private: + class CAppNapImpl; + std::unique_ptr<CAppNapImpl> impl; +}; + +#endif // BITCOIN_QT_MACOS_APPNAP_H diff --git a/src/qt/macos_appnap.mm b/src/qt/macos_appnap.mm new file mode 100644 index 0000000000..22a88782ab --- /dev/null +++ b/src/qt/macos_appnap.mm @@ -0,0 +1,71 @@ +// 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 "macos_appnap.h" + +#include <AvailabilityMacros.h> +#include <Foundation/NSProcessInfo.h> +#include <Foundation/Foundation.h> + +class CAppNapInhibitor::CAppNapImpl +{ +public: + ~CAppNapImpl() + { + if(activityId) + enableAppNap(); + } + + void disableAppNap() + { + if (!activityId) + { + @autoreleasepool { + const NSActivityOptions activityOptions = + NSActivityUserInitiatedAllowingIdleSystemSleep & + ~(NSActivitySuddenTerminationDisabled | + NSActivityAutomaticTerminationDisabled); + + id processInfo = [NSProcessInfo processInfo]; + if ([processInfo respondsToSelector:@selector(beginActivityWithOptions:reason:)]) + { + activityId = [processInfo beginActivityWithOptions: activityOptions reason:@"Temporarily disable App Nap for bitcoin-qt."]; + [activityId retain]; + } + } + } + } + + void enableAppNap() + { + if(activityId) + { + @autoreleasepool { + id processInfo = [NSProcessInfo processInfo]; + if ([processInfo respondsToSelector:@selector(endActivity:)]) + [processInfo endActivity:activityId]; + + [activityId release]; + activityId = nil; + } + } + } + +private: + NSObject* activityId; +}; + +CAppNapInhibitor::CAppNapInhibitor() : impl(new CAppNapImpl()) {} + +CAppNapInhibitor::~CAppNapInhibitor() = default; + +void CAppNapInhibitor::disableAppNap() +{ + impl->disableAppNap(); +} + +void CAppNapInhibitor::enableAppNap() +{ + impl->enableAppNap(); +} diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index b51322394f..c9871f6c66 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -23,6 +23,7 @@ #include <QIntValidator> #include <QLocale> #include <QMessageBox> +#include <QSystemTrayIcon> #include <QTimer> OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : @@ -126,6 +127,13 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : connect(ui->proxyIpTor, &QValidatedLineEdit::validationDidChange, this, &OptionsDialog::updateProxyValidationState); connect(ui->proxyPort, &QLineEdit::textChanged, this, &OptionsDialog::updateProxyValidationState); connect(ui->proxyPortTor, &QLineEdit::textChanged, this, &OptionsDialog::updateProxyValidationState); + + if (!QSystemTrayIcon::isSystemTrayAvailable()) { + ui->hideTrayIcon->setChecked(true); + ui->hideTrayIcon->setEnabled(false); + ui->minimizeToTray->setChecked(false); + ui->minimizeToTray->setEnabled(false); + } } OptionsDialog::~OptionsDialog() @@ -211,8 +219,10 @@ void OptionsDialog::setMapper() /* Window */ #ifndef Q_OS_MAC - mapper->addMapping(ui->hideTrayIcon, OptionsModel::HideTrayIcon); - mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); + if (QSystemTrayIcon::isSystemTrayAvailable()) { + mapper->addMapping(ui->hideTrayIcon, OptionsModel::HideTrayIcon); + mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); + } mapper->addMapping(ui->minimizeOnClose, OptionsModel::MinimizeOnClose); #endif diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 858128f9f9..65db0280b7 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -119,13 +119,11 @@ SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *p settings.setValue("nSmartFeeSliderPosition", 0); if (!settings.contains("nTransactionFee")) settings.setValue("nTransactionFee", (qint64)DEFAULT_PAY_TX_FEE); - if (!settings.contains("fPayOnlyMinFee")) - settings.setValue("fPayOnlyMinFee", false); ui->groupFee->setId(ui->radioSmartFee, 0); ui->groupFee->setId(ui->radioCustomFee, 1); ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true); + ui->customFee->SetAllowEmpty(false); ui->customFee->setValue(settings.value("nTransactionFee").toLongLong()); - ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool()); minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool()); } @@ -174,14 +172,15 @@ void SendCoinsDialog::setModel(WalletModel *_model) connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls); connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels); connect(ui->customFee, &BitcoinAmountField::valueChanged, this, &SendCoinsDialog::coinControlUpdateLabels); - connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::setMinimumFee); - connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateFeeSectionControls); - connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels); connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel); connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels); - ui->customFee->setSingleStep(model->wallet().getRequiredFee(1000)); + CAmount requiredFee = model->wallet().getRequiredFee(1000); + ui->customFee->SetMinValue(requiredFee); + if (ui->customFee->value() < requiredFee) { + ui->customFee->setValue(requiredFee); + } + ui->customFee->setSingleStep(requiredFee); updateFeeSectionControls(); - updateMinFeeLabel(); updateSmartFeeLabel(); // set default rbf checkbox state @@ -210,7 +209,6 @@ SendCoinsDialog::~SendCoinsDialog() settings.setValue("nFeeRadio", ui->groupFee->checkedId()); settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex())); settings.setValue("nTransactionFee", (qint64)ui->customFee->value()); - settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked()); delete ui; } @@ -542,7 +540,6 @@ void SendCoinsDialog::updateDisplayUnit() { setBalance(model->wallet().getBalances()); ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); - updateMinFeeLabel(); updateSmartFeeLabel(); } @@ -642,11 +639,6 @@ void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry) } } -void SendCoinsDialog::setMinimumFee() -{ - ui->customFee->setValue(model->wallet().getRequiredFee(1000)); -} - void SendCoinsDialog::updateFeeSectionControls() { ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked()); @@ -654,10 +646,9 @@ void SendCoinsDialog::updateFeeSectionControls() ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked()); ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked()); ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked()); - ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked()); - ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked()); - ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); - ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); + ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked()); + ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked()); + ui->customFee ->setEnabled(ui->radioCustomFee->isChecked()); } void SendCoinsDialog::updateFeeMinimizedLabel() @@ -672,14 +663,6 @@ void SendCoinsDialog::updateFeeMinimizedLabel() } } -void SendCoinsDialog::updateMinFeeLabel() -{ - if (model && model->getOptionsModel()) - ui->checkBoxMinimumFee->setText(tr("Pay only the required fee of %1").arg( - BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getRequiredFee(1000)) + "/kB") - ); -} - void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl) { if (ui->radioCustomFee->isChecked()) { diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 7009855f17..e1ebc77d59 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -92,9 +92,7 @@ private Q_SLOTS: void coinControlClipboardBytes(); void coinControlClipboardLowOutput(); void coinControlClipboardChange(); - void setMinimumFee(); void updateFeeSectionControls(); - void updateMinFeeLabel(); void updateSmartFeeLabel(); Q_SIGNALS: diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 0cabb5f0be..3e414df1f0 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -2,6 +2,7 @@ #include <qt/test/util.h> #include <test/test_bitcoin.h> +#include <interfaces/chain.h> #include <interfaces/node.h> #include <qt/addressbookpage.h> #include <qt/addresstablemodel.h> @@ -56,7 +57,8 @@ void EditAddressAndSubmit( void TestAddAddressesToSendBook() { TestChain100Setup test; - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateMock()); + auto chain = interfaces::MakeChain(); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); diff --git a/src/qt/test/util.h b/src/qt/test/util.h index 5363c94547..377f07dcba 100644 --- a/src/qt/test/util.h +++ b/src/qt/test/util.h @@ -1,6 +1,8 @@ #ifndef BITCOIN_QT_TEST_UTIL_H #define BITCOIN_QT_TEST_UTIL_H +#include <QString> + /** * Press "Ok" button in message box dialog. * diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 12dbf922f1..f02fd8aea7 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -1,6 +1,7 @@ #include <qt/test/wallettests.h> #include <qt/test/util.h> +#include <interfaces/chain.h> #include <interfaces/node.h> #include <base58.h> #include <qt/bitcoinamountfield.h> @@ -132,7 +133,8 @@ void TestGUI() for (int i = 0; i < 5; ++i) { test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); } - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateMock()); + auto chain = interfaces::MakeChain(); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); { @@ -141,7 +143,7 @@ void TestGUI() wallet->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); } { - LOCK(cs_main); + auto locked_chain = wallet->chain().lock(); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true); diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 053e951921..a619992344 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -292,9 +292,7 @@ void WalletView::usedSendingAddresses() if(!walletModel) return; - usedSendingAddressesPage->show(); - usedSendingAddressesPage->raise(); - usedSendingAddressesPage->activateWindow(); + GUIUtil::bringToFront(usedSendingAddressesPage); } void WalletView::usedReceivingAddresses() @@ -302,9 +300,7 @@ void WalletView::usedReceivingAddresses() if(!walletModel) return; - usedReceivingAddressesPage->show(); - usedReceivingAddressesPage->raise(); - usedReceivingAddressesPage->activateWindow(); + GUIUtil::bringToFront(usedReceivingAddressesPage); } void WalletView::showProgress(const QString &title, int nProgress) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 92f6f0fe11..a61857ca7d 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -12,8 +12,8 @@ #include <checkpoints.h> #include <coins.h> #include <consensus/validation.h> -#include <validation.h> #include <core_io.h> +#include <hash.h> #include <index/txindex.h> #include <key_io.h> #include <policy/feerate.h> @@ -21,14 +21,15 @@ #include <policy/rbf.h> #include <primitives/transaction.h> #include <rpc/server.h> +#include <rpc/util.h> #include <script/descriptor.h> #include <streams.h> #include <sync.h> #include <txdb.h> #include <txmempool.h> -#include <util/system.h> #include <util/strencodings.h> -#include <hash.h> +#include <util/system.h> +#include <validation.h> #include <validationinterface.h> #include <versionbitsinfo.h> #include <warnings.h> diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 58b54c66de..8751991fc4 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -18,10 +18,11 @@ #include <rpc/blockchain.h> #include <rpc/mining.h> #include <rpc/server.h> +#include <rpc/util.h> #include <shutdown.h> #include <txmempool.h> -#include <util/system.h> #include <util/strencodings.h> +#include <util/system.h> #include <validation.h> #include <validationinterface.h> #include <versionbitsinfo.h> diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 4d893bb838..ab049a27f8 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -7,17 +7,18 @@ #include <chainparams.h> #include <clientversion.h> #include <core_io.h> -#include <validation.h> #include <net.h> #include <net_processing.h> #include <netbase.h> #include <policy/policy.h> #include <rpc/protocol.h> +#include <rpc/util.h> #include <sync.h> #include <timedata.h> #include <ui_interface.h> -#include <util/system.h> #include <util/strencodings.h> +#include <util/system.h> +#include <validation.h> #include <version.h> #include <warnings.h> diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 0f87646b08..480cb0b9d2 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -9,10 +9,9 @@ #include <consensus/validation.h> #include <core_io.h> #include <index/txindex.h> -#include <keystore.h> -#include <validation.h> -#include <validationinterface.h> +#include <init.h> #include <key_io.h> +#include <keystore.h> #include <merkleblock.h> #include <net.h> #include <policy/policy.h> @@ -20,6 +19,7 @@ #include <primitives/transaction.h> #include <rpc/rawtransaction.h> #include <rpc/server.h> +#include <rpc/util.h> #include <script/script.h> #include <script/script_error.h> #include <script/sign.h> @@ -27,6 +27,8 @@ #include <txmempool.h> #include <uint256.h> #include <util/strencodings.h> +#include <validation.h> +#include <validationinterface.h> #include <future> #include <stdint.h> @@ -204,7 +206,16 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) { if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2)) throw std::runtime_error( - "gettxoutproof [\"txid\",...] ( blockhash )\n" + RPCHelpMan{"gettxoutproof", + { + {"txids", RPCArg::Type::ARR, + { + {"txid", RPCArg::Type::STR_HEX, false}, + }, + false}, + {"blockhash", RPCArg::Type::STR_HEX, true}, + }} + .ToString() + "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n" "\nNOTE: By default this function only works sometimes. This is when there is an\n" "unspent output in the utxo for this transaction. To make it always work,\n" @@ -346,7 +357,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal if (!locktime.isNull()) { int64_t nLockTime = locktime.get_int64(); - if (nLockTime < 0 || nLockTime > std::numeric_limits<uint32_t>::max()) + if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); rawTx.nLockTime = nLockTime; } @@ -368,18 +379,18 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal uint32_t nSequence; if (rbfOptIn) { - nSequence = MAX_BIP125_RBF_SEQUENCE; + nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */ } else if (rawTx.nLockTime) { - nSequence = std::numeric_limits<uint32_t>::max() - 1; + nSequence = CTxIn::SEQUENCE_FINAL - 1; } else { - nSequence = std::numeric_limits<uint32_t>::max(); + nSequence = CTxIn::SEQUENCE_FINAL; } // set the sequence number if passed in the parameters object const UniValue& sequenceObj = find_value(o, "sequence"); if (sequenceObj.isNum()) { int64_t seqNr64 = sequenceObj.get_int64(); - if (seqNr64 < 0 || seqNr64 > std::numeric_limits<uint32_t>::max()) { + if (seqNr64 < 0 || seqNr64 > CTxIn::SEQUENCE_FINAL) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range"); } else { nSequence = (uint32_t)seqNr64; @@ -671,10 +682,17 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: static UniValue combinerawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "combinerawtransaction [\"hexstring\",...]\n" + RPCHelpMan{"combinerawtransaction", + { + {"txs", RPCArg::Type::ARR, + { + {"hexstring", RPCArg::Type::STR_HEX, false}, + }, + false}, + }} + .ToString() + "\nCombine multiple partially signed transactions into one transaction.\n" "The combined transaction may be another partially signed transaction or a \n" "fully signed transaction." @@ -754,7 +772,7 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) return EncodeHexTx(mergedTx); } -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType) +UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType) { // Fetch previous transactions (inputs): CCoinsView viewDummy; @@ -897,7 +915,30 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) throw std::runtime_error( - "signrawtransactionwithkey \"hexstring\" [\"privatekey1\",...] ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] sighashtype )\n" + RPCHelpMan{"signrawtransactionwithkey", + { + {"hexstring", RPCArg::Type::STR, false}, + {"privkyes", RPCArg::Type::ARR, + { + {"privatekey", RPCArg::Type::STR_HEX, false}, + }, + false}, + {"prevtxs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"txid", RPCArg::Type::STR_HEX, false}, + {"vout", RPCArg::Type::NUM, false}, + {"scriptPubKey", RPCArg::Type::STR_HEX, false}, + {"redeemScript", RPCArg::Type::STR_HEX, false}, + {"amount", RPCArg::Type::AMOUNT, false}, + }, + true}, + }, + true}, + {"sighashtype", RPCArg::Type::STR, true}, + }} + .ToString() + "\nSign inputs for raw transaction (serialized, hex-encoded).\n" "The second argument is an array of base58-encoded private\n" "keys that will be the only keys used to sign the transaction.\n" @@ -969,7 +1010,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) keystore.AddKey(key); } - return SignTransaction(mtx, request.params[2], &keystore, true, request.params[3]); + return SignTransaction(*g_rpc_interfaces->chain, mtx, request.params[2], &keystore, true, request.params[3]); } UniValue signrawtransaction(const JSONRPCRequest& request) @@ -1452,7 +1493,15 @@ UniValue combinepsbt(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "combinepsbt [\"psbt\",...]\n" + RPCHelpMan{"combinepsbt", + { + {"txs", RPCArg::Type::ARR, + { + {"psbt", RPCArg::Type::STR_HEX, false}, + }, + false}, + }} + .ToString() + "\nCombine multiple partially signed Bitcoin transactions into one transaction.\n" "Implements the Combiner role.\n" "\nArguments:\n" @@ -1536,12 +1585,13 @@ UniValue finalizepsbt(const JSONRPCRequest& request) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); } - // Get all of the previous transactions + // Finalize input signatures -- in case we have partial signatures that add up to a complete + // signature, but have not combined them yet (e.g. because the combiner that created this + // PartiallySignedTransaction did not understand them), this will combine them into a final + // script. bool complete = true; for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput& input = psbtx.inputs.at(i); - - complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, *psbtx.tx, input, i, 1); + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL); } UniValue result(UniValue::VOBJ); @@ -1554,10 +1604,10 @@ UniValue finalizepsbt(const JSONRPCRequest& request) mtx.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness; } ssTx << mtx; - result.pushKV("hex", HexStr(ssTx.begin(), ssTx.end())); + result.pushKV("hex", HexStr(ssTx.str())); } else { ssTx << psbtx; - result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("psbt", EncodeBase64(ssTx.str())); } result.pushKV("complete", complete); @@ -1568,7 +1618,37 @@ UniValue createpsbt(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) throw std::runtime_error( - "createpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" + RPCHelpMan{"createpsbt", + { + {"inputs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"txid", RPCArg::Type::STR_HEX, false}, + {"vout", RPCArg::Type::NUM, false}, + {"sequence", RPCArg::Type::NUM, true}, + }, + false}, + }, + false}, + {"outputs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"address", RPCArg::Type::AMOUNT, false}, + }, + true}, + {"", RPCArg::Type::OBJ, + { + {"data", RPCArg::Type::STR_HEX, false}, + }, + true}, + }, + false}, + {"locktime", RPCArg::Type::NUM, true}, + {"replaceable", RPCArg::Type::BOOL, true}, + }} + .ToString() + "\nCreates a transaction in the Partially Signed Transaction format.\n" "Implements the Creator role.\n" "\nArguments:\n" @@ -1669,7 +1749,7 @@ UniValue converttopsbt(const JSONRPCRequest& request) // Remove all scriptSigs and scriptWitnesses from inputs for (CTxIn& input : tx.vin) { - if ((!input.scriptSig.empty() || !input.scriptWitness.IsNull()) && (request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool()))) { + if ((!input.scriptSig.empty() || !input.scriptWitness.IsNull()) && !permitsigdata) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Inputs must not have scriptSigs and scriptWitnesses"); } input.scriptSig.clear(); diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h index 924611ed5a..52d701d1c3 100644 --- a/src/rpc/rawtransaction.h +++ b/src/rpc/rawtransaction.h @@ -9,8 +9,12 @@ class CBasicKeyStore; struct CMutableTransaction; class UniValue; +namespace interfaces { +class Chain; +} // namespace interfaces + /** Sign a transaction with the given keystore and previous transactions */ -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType); +UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType); /** Create a transaction from univalue parameters */ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 78abd7b610..9d2c6ca0a6 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -8,11 +8,12 @@ #include <fs.h> #include <key_io.h> #include <random.h> +#include <rpc/util.h> #include <shutdown.h> #include <sync.h> #include <ui_interface.h> -#include <util/system.h> #include <util/strencodings.h> +#include <util/system.h> #include <boost/bind.hpp> #include <boost/signals2/signal.hpp> diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 8a66191a6a..6f2450708a 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -9,6 +9,8 @@ #include <tinyformat.h> #include <util/strencodings.h> +InitInterfaces* g_rpc_interfaces = nullptr; + // Converts a hex string to a public key if possible CPubKey HexToPubKey(const std::string& hex_in) { @@ -126,3 +128,95 @@ UniValue DescribeAddress(const CTxDestination& dest) { return boost::apply_visitor(DescribeAddressVisitor(), dest); } + +std::string RPCHelpMan::ToString() const +{ + std::string ret; + + ret += m_name; + bool is_optional{false}; + for (const auto& arg : m_args) { + ret += " "; + if (arg.m_optional) { + if (!is_optional) ret += "( "; + is_optional = true; + } else { + // Currently we still support unnamed arguments, so any argument following an optional argument must also be optional + // If support for positional arguments is deprecated in the future, remove this line + assert(!is_optional); + } + ret += arg.ToString(); + } + if (is_optional) ret += " )"; + ret += "\n"; + + return ret; +} + +std::string RPCArg::ToStringObj() const +{ + std::string res = "\"" + m_name + "\":"; + switch (m_type) { + case Type::STR: + return res + "\"str\""; + case Type::STR_HEX: + return res + "\"hex\""; + case Type::NUM: + return res + "n"; + case Type::AMOUNT: + return res + "amount"; + case Type::BOOL: + return res + "bool"; + case Type::ARR: + res += "["; + for (const auto& i : m_inner) { + res += i.ToString() + ","; + } + return res + "...]"; + case Type::OBJ: + case Type::OBJ_USER_KEYS: + // Currently unused, so avoid writing dead code + assert(false); + + // no default case, so the compiler can warn about missing cases + } + assert(false); +} + +std::string RPCArg::ToString() const +{ + switch (m_type) { + case Type::STR_HEX: + case Type::STR: { + return "\"" + m_name + "\""; + } + case Type::NUM: + case Type::AMOUNT: + case Type::BOOL: { + return m_name; + } + case Type::OBJ: + case Type::OBJ_USER_KEYS: { + std::string res; + for (size_t i = 0; i < m_inner.size();) { + res += m_inner[i].ToStringObj(); + if (++i < m_inner.size()) res += ","; + } + if (m_type == Type::OBJ) { + return "{" + res + "}"; + } else { + return "{" + res + ",...}"; + } + } + case Type::ARR: { + std::string res; + for (const auto& i : m_inner) { + res += i.ToString() + ","; + } + return "[" + res + "...]"; + } + + // no default case, so the compiler can warn about missing cases + } + assert(false); +} diff --git a/src/rpc/util.h b/src/rpc/util.h index 0a3a156e45..55ae2fa363 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -17,6 +17,12 @@ class CKeyStore; class CPubKey; class CScript; +struct InitInterfaces; + +//! Pointers to interfaces that need to be accessible from RPC methods. Due to +//! limitations of the RPC framework, there's currently no direct way to pass in +//! state to RPC method implementations. +extern InitInterfaces* g_rpc_interfaces; CPubKey HexToPubKey(const std::string& hex_in); CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in); @@ -24,4 +30,53 @@ CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey UniValue DescribeAddress(const CTxDestination& dest); +struct RPCArg { + enum class Type { + OBJ, + ARR, + STR, + NUM, + BOOL, + OBJ_USER_KEYS, //!< Special type where the user must set the keys e.g. to define multiple addresses; as opposed to e.g. an options object where the keys are predefined + AMOUNT, //!< Special type representing a floating point amount (can be either NUM or STR) + STR_HEX, //!< Special type that is a STR with only hex chars + }; + const std::string m_name; //!< The name of the arg (can be empty for inner args) + const Type m_type; + const std::vector<RPCArg> m_inner; //!< Only used for arrays or dicts + const bool m_optional; + + RPCArg(const std::string& name, const Type& type, const bool optional) + : m_name{name}, m_type{type}, m_optional{optional} + { + assert(type != Type::ARR && type != Type::OBJ); + } + + RPCArg(const std::string& name, const Type& type, const std::vector<RPCArg>& inner, const bool optional) + : m_name{name}, m_type{type}, m_inner{inner}, m_optional{optional} + { + assert(type == Type::ARR || type == Type::OBJ); + } + + std::string ToString() const; + +private: + std::string ToStringObj() const; +}; + +class RPCHelpMan +{ +public: + RPCHelpMan(const std::string& name, const std::vector<RPCArg>& args) + : m_name{name}, m_args{args} + { + } + + std::string ToString() const; + +private: + const std::string m_name; + const std::vector<RPCArg> m_args; +}; + #endif // BITCOIN_RPC_UTIL_H diff --git a/src/script/script.h b/src/script/script.h index 00065a24be..1d8ddba2f2 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -38,6 +38,12 @@ static const int MAX_STACK_SIZE = 1000; // otherwise as UNIX timestamp. static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC +// Maximum nLockTime. Since a lock time indicates the last invalid timestamp, a +// transaction with this lock time will never be valid unless lock time +// checking is disabled (by setting all input sequence numbers to +// SEQUENCE_FINAL). +static const uint32_t LOCKTIME_MAX = 0xFFFFFFFFU; + template <typename T> std::vector<unsigned char> ToByteVector(const T& in) { diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 89cc7c808c..2795dc96d3 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -123,7 +123,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator case TX_PUBKEYHASH: { CKeyID keyID = CKeyID(uint160(vSolutions[0])); CPubKey pubkey; - GetPubKey(provider, sigdata, keyID, pubkey); + if (!GetPubKey(provider, sigdata, keyID, pubkey)) return false; if (!CreateSig(creator, sigdata, provider, sig, pubkey, scriptPubKey, sigversion)) return false; ret.push_back(std::move(sig)); ret.push_back(ToByteVector(pubkey)); @@ -239,10 +239,17 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato return sigdata.complete; } -bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, int index, int sighash) +bool PSBTInputSigned(PSBTInput& input) { - // if this input has a final scriptsig or scriptwitness, don't do anything with it - if (!input.final_script_sig.empty() || !input.final_script_witness.IsNull()) { + return !input.final_script_sig.empty() || !input.final_script_witness.IsNull(); +} + +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash) +{ + PSBTInput& input = psbt.inputs.at(index); + const CMutableTransaction& tx = *psbt.tx; + + if (PSBTInputSigned(input)) { return true; } @@ -253,15 +260,19 @@ bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& t // Get UTXO bool require_witness_sig = false; CTxOut utxo; + + // Verify input sanity, which checks that at most one of witness or non-witness utxos is provided. + if (!input.IsSane()) { + return false; + } + if (input.non_witness_utxo) { // If we're taking our information from a non-witness UTXO, verify that it matches the prevout. - if (input.non_witness_utxo->GetHash() != tx.vin[index].prevout.hash) return false; - // If both witness and non-witness UTXO are provided, verify that they match. This check shouldn't - // matter, as the PSBT deserializer enforces only one of both is provided, and the only way both - // can be present is when they're added simultaneously by FillPSBT (in which case they always match). - // Still, check in order to not rely on callers to enforce this. - if (!input.witness_utxo.IsNull() && input.non_witness_utxo->vout[tx.vin[index].prevout.n] != input.witness_utxo) return false; - utxo = input.non_witness_utxo->vout[tx.vin[index].prevout.n]; + COutPoint prevout = tx.vin[index].prevout; + if (input.non_witness_utxo->GetHash() != prevout.hash) { + return false; + } + utxo = input.non_witness_utxo->vout[prevout.n]; } else if (!input.witness_utxo.IsNull()) { utxo = input.witness_utxo; // When we're taking our information from a witness UTXO, we can't verify it is actually data from @@ -280,18 +291,10 @@ bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& t if (require_witness_sig && !sigdata.witness) return false; input.FromSignatureData(sigdata); + // If we have a witness signature, use the smaller witness UTXO. if (sigdata.witness) { - assert(!utxo.IsNull()); input.witness_utxo = utxo; - } - - // If both UTXO types are present, drop the unnecessary one. - if (input.non_witness_utxo && !input.witness_utxo.IsNull()) { - if (sigdata.witness) { - input.non_witness_utxo = nullptr; - } else { - input.witness_utxo.SetNull(); - } + input.non_witness_utxo = nullptr; } return sig_complete; @@ -513,6 +516,12 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script) return false; } +PartiallySignedTransaction::PartiallySignedTransaction(const CTransaction& tx) : tx(tx) +{ + inputs.resize(tx.vin.size()); + outputs.resize(tx.vout.size()); +} + bool PartiallySignedTransaction::IsNull() const { return !tx && inputs.empty() && outputs.empty() && unknown.empty(); diff --git a/src/script/sign.h b/src/script/sign.h index d47aada17d..a478f49789 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -206,6 +206,9 @@ template<typename Stream> void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, KeyOriginInfo>& hd_keypaths, uint8_t type) { for (auto keypath_pair : hd_keypaths) { + if (!keypath_pair.first.IsValid()) { + throw std::ios_base::failure("Invalid CPubKey being serialized"); + } SerializeToVector(s, type, MakeSpan(keypath_pair.first)); WriteCompactSize(s, (keypath_pair.second.path.size() + 1) * sizeof(uint32_t)); s << keypath_pair.second.fingerprint; @@ -566,6 +569,7 @@ struct PartiallySignedTransaction bool IsSane() const; PartiallySignedTransaction() {} PartiallySignedTransaction(const PartiallySignedTransaction& psbt_in) : tx(psbt_in.tx), inputs(psbt_in.inputs), outputs(psbt_in.outputs), unknown(psbt_in.unknown) {} + explicit PartiallySignedTransaction(const CTransaction& tx); // Only checks if they refer to the same transaction friend bool operator==(const PartiallySignedTransaction& a, const PartiallySignedTransaction &b) @@ -729,8 +733,11 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType); bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType); +/** Checks whether a PSBTInput is already signed. */ +bool PSBTInputSigned(PSBTInput& input); + /** Signs a PSBTInput, verifying that all provided data matches what is being signed. */ -bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, int index, int sighash = SIGHASH_ALL); +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL); /** Extract signature data from a transaction input, and insert it. */ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout); diff --git a/src/test/hash_tests.cpp b/src/test/hash_tests.cpp index fd3a6b0dec..e8e5040855 100644 --- a/src/test/hash_tests.cpp +++ b/src/test/hash_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <crypto/siphash.h> #include <hash.h> #include <util/strencodings.h> #include <test/test_bitcoin.h> diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index f788c34e05..26cdc9bc5c 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2015-2018 The Bitcoin Core developers -// Distributed under the MIT/X11 software license, see the accompanying +// Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index a49796d6f4..121b72a5f7 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -4,8 +4,11 @@ #include <rpc/server.h> #include <rpc/client.h> +#include <rpc/util.h> #include <core_io.h> +#include <init.h> +#include <interfaces/chain.h> #include <key_io.h> #include <netbase.h> @@ -112,10 +115,14 @@ BOOST_AUTO_TEST_CASE(rpc_rawsign) std::string notsigned = r.get_str(); std::string privkey1 = "\"KzsXybp9jX64P5ekX1KUxRQ79Jht9uzW7LorgwE65i5rWACL6LQe\""; std::string privkey2 = "\"Kyhdf5LuKTRx4ge69ybABsiUAWjVRK4XGxAKk2FQLp2HjGMy87Z4\""; + InitInterfaces interfaces; + interfaces.chain = interfaces::MakeChain(); + g_rpc_interfaces = &interfaces; r = CallRPC(std::string("signrawtransactionwithkey ")+notsigned+" [] "+prevout); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == false); r = CallRPC(std::string("signrawtransactionwithkey ")+notsigned+" ["+privkey1+","+privkey2+"] "+prevout); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true); + g_rpc_interfaces = nullptr; } BOOST_AUTO_TEST_CASE(rpc_createraw_op_return) diff --git a/src/test/skiplist_tests.cpp b/src/test/skiplist_tests.cpp index 552bd1ab03..5c46976ace 100644 --- a/src/test/skiplist_tests.cpp +++ b/src/test/skiplist_tests.cpp @@ -170,7 +170,6 @@ BOOST_AUTO_TEST_CASE(findearliestatleast_edge_test) BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(-1)->nHeight, 0); BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(std::numeric_limits<int64_t>::min())->nHeight, 0); - BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(std::numeric_limits<unsigned int>::min())->nHeight, 0); BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(-int64_t(std::numeric_limits<unsigned int>::max()) - 1)->nHeight, 0); BOOST_CHECK(!chain.FindEarliestAtLeast(std::numeric_limits<int64_t>::max())); BOOST_CHECK(!chain.FindEarliestAtLeast(std::numeric_limits<unsigned int>::max())); diff --git a/src/threadsafety.h b/src/threadsafety.h index 47e6b2ea38..33acddc65c 100644 --- a/src/threadsafety.h +++ b/src/threadsafety.h @@ -54,4 +54,15 @@ #define ASSERT_EXCLUSIVE_LOCK(...) #endif // __GNUC__ +// Utility class for indicating to compiler thread analysis that a mutex is +// locked (when it couldn't be determined otherwise). +struct SCOPED_LOCKABLE LockAnnotation +{ + template <typename Mutex> + explicit LockAnnotation(Mutex& mutex) EXCLUSIVE_LOCK_FUNCTION(mutex) + { + } + ~LockAnnotation() UNLOCK_FUNCTION() {} +}; + #endif // BITCOIN_THREADSAFETY_H diff --git a/src/txmempool.h b/src/txmempool.h index cda78ea90c..fadb554723 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -15,6 +15,7 @@ #include <amount.h> #include <coins.h> +#include <crypto/siphash.h> #include <indirectmap.h> #include <policy/feerate.h> #include <primitives/transaction.h> diff --git a/src/undo.h b/src/undo.h index 4ed3dc4ca0..3f50f4caad 100644 --- a/src/undo.h +++ b/src/undo.h @@ -11,6 +11,7 @@ #include <consensus/consensus.h> #include <primitives/transaction.h> #include <serialize.h> +#include <version.h> /** Undo information for a CTxIn * diff --git a/src/util/bytevectorhash.cpp b/src/util/bytevectorhash.cpp new file mode 100644 index 0000000000..f87d0e04b3 --- /dev/null +++ b/src/util/bytevectorhash.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <crypto/siphash.h> +#include <random.h> +#include <util/bytevectorhash.h> + +ByteVectorHash::ByteVectorHash() +{ + GetRandBytes(reinterpret_cast<unsigned char*>(&m_k0), sizeof(m_k0)); + GetRandBytes(reinterpret_cast<unsigned char*>(&m_k1), sizeof(m_k1)); +} + +size_t ByteVectorHash::operator()(const std::vector<unsigned char>& input) const +{ + return CSipHasher(m_k0, m_k1).Write(input.data(), input.size()).Finalize(); +} diff --git a/src/util/bytevectorhash.h b/src/util/bytevectorhash.h new file mode 100644 index 0000000000..b88c17460b --- /dev/null +++ b/src/util/bytevectorhash.h @@ -0,0 +1,26 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_BYTEVECTORHASH_H +#define BITCOIN_UTIL_BYTEVECTORHASH_H + +#include <stdint.h> +#include <vector> + +/** + * Implementation of Hash named requirement for types that internally store a byte array. This may + * be used as the hash function in std::unordered_set or std::unordered_map over such types. + * Internally, this uses a random instance of SipHash-2-4. + */ +class ByteVectorHash final +{ +private: + uint64_t m_k0, m_k1; + +public: + ByteVectorHash(); + size_t operator()(const std::vector<unsigned char>& input) const; +}; + +#endif // BITCOIN_UTIL_BYTEVECTORHASH_H diff --git a/src/util/system.cpp b/src/util/system.cpp index 4f5dd2d6e9..f6f36c2238 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -826,8 +826,10 @@ static bool GetConfigOptions(std::istream& stream, std::string& error, std::vect std::string::size_type pos; int linenr = 1; while (std::getline(stream, str)) { + bool used_hash = false; if ((pos = str.find('#')) != std::string::npos) { str = str.substr(0, pos); + used_hash = true; } const static std::string pattern = " \t\r\n"; str = TrimString(str, pattern); @@ -840,6 +842,10 @@ static bool GetConfigOptions(std::istream& stream, std::string& error, std::vect } else if ((pos = str.find('=')) != std::string::npos) { std::string name = prefix + TrimString(str.substr(0, pos), pattern); std::string value = TrimString(str.substr(pos + 1), pattern); + if (used_hash && name == "rpcpassword") { + error = strprintf("parse error on line %i, using # in rpcpassword can be ambiguous and should be avoided", linenr); + return false; + } options.emplace_back(name, value); } else { error = strprintf("parse error on line %i: %s", linenr, str); diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 5eb70cd7a5..7a71aea715 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <consensus/validation.h> +#include <interfaces/chain.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> #include <wallet/fees.h> @@ -18,7 +19,7 @@ //! Check whether transaction has descendant in wallet or mempool, or has been //! mined, or conflicts with a mined transaction. Return a feebumper::Result. -static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(cs_main, wallet->cs_wallet) +static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chain, const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) { if (wallet->HasWalletSpend(wtx.GetHash())) { errors.push_back("Transaction has descendants in the wallet"); @@ -34,7 +35,7 @@ static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWallet } } - if (wtx.GetDepthInMainChain() != 0) { + if (wtx.GetDepthInMainChain(locked_chain) != 0) { errors.push_back("Transaction has been mined, or is conflicted with a mined transaction"); return feebumper::Result::WALLET_ERROR; } @@ -64,19 +65,21 @@ namespace feebumper { bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); const CWalletTx* wtx = wallet->GetWalletTx(txid); if (wtx == nullptr) return false; std::vector<std::string> errors_dummy; - feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy); + feebumper::Result res = PreconditionChecks(*locked_chain, wallet, *wtx, errors_dummy); return res == feebumper::Result::OK; } Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors, CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); errors.clear(); auto it = wallet->mapWallet.find(txid); if (it == wallet->mapWallet.end()) { @@ -85,7 +88,7 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin } const CWalletTx& wtx = it->second; - Result result = PreconditionChecks(wallet, wtx, errors); + Result result = PreconditionChecks(*locked_chain, wallet, wtx, errors); if (result != Result::OK) { return result; } @@ -212,13 +215,15 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin } bool SignTransaction(CWallet* wallet, CMutableTransaction& mtx) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); return wallet->SignTransaction(mtx); } Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransaction&& mtx, std::vector<std::string>& errors, uint256& bumped_txid) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); if (!errors.empty()) { return Result::MISC_ERROR; } @@ -230,7 +235,7 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti CWalletTx& oldWtx = it->second; // make sure the transaction still has no descendants and hasn't been mined in the meantime - Result result = PreconditionChecks(wallet, oldWtx, errors); + Result result = PreconditionChecks(*locked_chain, wallet, oldWtx, errors); if (result != Result::OK) { return result; } diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 220780c96c..14d811c6cd 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -5,6 +5,7 @@ #include <chainparams.h> #include <init.h> +#include <interfaces/chain.h> #include <net.h> #include <scheduler.h> #include <outputtype.h> @@ -28,28 +29,8 @@ public: //! Wallets parameter interaction bool ParameterInteraction() const override; - //! Register wallet RPCs. - void RegisterRPC(CRPCTable &tableRPC) const override; - - //! Responsible for reading and validating the -wallet arguments and verifying the wallet database. - // This function will perform salvage on the wallet if requested, as long as only one wallet is - // being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). - bool Verify() const override; - - //! Load wallet databases. - bool Open() const override; - - //! Complete startup of wallets. - void Start(CScheduler& scheduler) const override; - - //! Flush all wallets in preparation for shutdown. - void Flush() const override; - - //! Stop all wallets. Wallets will be flushed first. - void Stop() const override; - - //! Close all wallets. - void Close() const override; + //! Add wallets that should be opened to list of init interfaces. + void Construct(InitInterfaces& interfaces) const override; }; const WalletInitInterface& g_wallet_init_interface = WalletInit(); @@ -57,7 +38,7 @@ const WalletInitInterface& g_wallet_init_interface = WalletInit(); void WalletInit::AddWalletOptions() const { gArgs.AddArg("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), false, OptionsCategory::WALLET); - gArgs.AddArg("-avoidpartialspends", strprintf(_("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u)"), DEFAULT_AVOIDPARTIALSPENDS), false, OptionsCategory::WALLET); + gArgs.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u)", DEFAULT_AVOIDPARTIALSPENDS), false, OptionsCategory::WALLET); gArgs.AddArg("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)", false, OptionsCategory::WALLET); gArgs.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", false, OptionsCategory::WALLET); gArgs.AddArg("-discardfee=<amt>", strprintf("The fee rate (in %s/kB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). " @@ -99,7 +80,6 @@ bool WalletInit::ParameterInteraction() const return true; } - gArgs.SoftSetArg("-wallet", ""); const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1; if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) { @@ -165,21 +145,8 @@ bool WalletInit::ParameterInteraction() const return true; } -void WalletInit::RegisterRPC(CRPCTable &t) const -{ - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { - return; - } - - RegisterWalletRPCCommands(t); -} - -bool WalletInit::Verify() const +bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) { - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { - return true; - } - if (gArgs.IsArgSet("-walletdir")) { fs::path wallet_dir = gArgs.GetArg("-walletdir", ""); boost::system::error_code error; @@ -200,8 +167,6 @@ bool WalletInit::Verify() const uiInterface.InitMessage(_("Verifying wallet(s)...")); - std::vector<std::string> wallet_files = gArgs.GetArgs("-wallet"); - // Parameter interaction code should have thrown an error if -salvagewallet // was enabled with more than wallet file, so the wallet_files size check // here should have no effect. @@ -219,7 +184,7 @@ bool WalletInit::Verify() const std::string error_string; std::string warning_string; - bool verify_success = CWallet::Verify(location, salvage_wallet, error_string, warning_string); + bool verify_success = CWallet::Verify(chain, location, salvage_wallet, error_string, warning_string); if (!error_string.empty()) InitError(error_string); if (!warning_string.empty()) InitWarning(warning_string); if (!verify_success) return false; @@ -228,15 +193,20 @@ bool WalletInit::Verify() const return true; } -bool WalletInit::Open() const +void WalletInit::Construct(InitInterfaces& interfaces) const { if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { LogPrintf("Wallet disabled!\n"); - return true; + return; } + gArgs.SoftSetArg("-wallet", ""); + interfaces.chain_clients.emplace_back(interfaces::MakeWalletClient(*interfaces.chain, gArgs.GetArgs("-wallet"))); +} - for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { - std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(WalletLocation(walletFile)); +bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) +{ + for (const std::string& walletFile : wallet_files) { + std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(chain, WalletLocation(walletFile)); if (!pwallet) { return false; } @@ -246,7 +216,7 @@ bool WalletInit::Open() const return true; } -void WalletInit::Start(CScheduler& scheduler) const +void StartWallets(CScheduler& scheduler) { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->postInitProcess(); @@ -256,21 +226,21 @@ void WalletInit::Start(CScheduler& scheduler) const scheduler.scheduleEvery(MaybeCompactWalletDB, 500); } -void WalletInit::Flush() const +void FlushWallets() { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->Flush(false); } } -void WalletInit::Stop() const +void StopWallets() { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->Flush(true); } } -void WalletInit::Close() const +void UnloadWallets() { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { RemoveWallet(pwallet); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index a3bb273230..00a9a6ec8b 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -3,17 +3,19 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> +#include <core_io.h> +#include <interfaces/chain.h> #include <key_io.h> +#include <merkleblock.h> #include <rpc/server.h> -#include <validation.h> +#include <rpc/util.h> #include <script/script.h> #include <script/standard.h> #include <sync.h> #include <util/system.h> #include <util/time.h> +#include <validation.h> #include <wallet/wallet.h> -#include <merkleblock.h> -#include <core_io.h> #include <wallet/rpcwallet.h> @@ -112,7 +114,7 @@ UniValue importprivkey(const JSONRPCRequest& request) "Hint: use importmulti to import more than one private key.\n" "\nArguments:\n" "1. \"privkey\" (string, required) The private key (see dumpprivkey)\n" - "2. \"label\" (string, optional, default=\"\") An optional label\n" + "2. \"label\" (string, optional, default=current label if address exists, otherwise \"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" @@ -133,7 +135,8 @@ UniValue importprivkey(const JSONRPCRequest& request) WalletRescanReserver reserver(pwallet); bool fRescan = true; { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -161,9 +164,14 @@ UniValue importprivkey(const JSONRPCRequest& request) CKeyID vchAddress = pubkey.GetID(); { pwallet->MarkDirty(); - // We don't know which corresponding address will be used; label them all + + // We don't know which corresponding address will be used; + // label all new addresses, and label existing addresses if a + // label was passed. for (const auto& dest : GetAllDestinationsForKey(pubkey)) { - pwallet->SetAddressBook(dest, strLabel, "receive"); + if (!request.params[1].isNull() || pwallet->mapAddressBook.count(dest) == 0) { + pwallet->SetAddressBook(dest, strLabel, "receive"); + } } // Don't throw error in case a key is already there @@ -305,7 +313,8 @@ UniValue importaddress(const JSONRPCRequest& request) fP2SH = request.params[3].get_bool(); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (IsValidDestination(dest)) { @@ -362,7 +371,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request) unsigned int txnIndex = 0; if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) { - LOCK(cs_main); + auto locked_chain = pwallet->chain().lock(); const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash()); if (!pindex || !chainActive.Contains(pindex)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); @@ -382,7 +391,8 @@ UniValue importprunedfunds(const JSONRPCRequest& request) wtx.nIndex = txnIndex; wtx.hashBlock = merkleBlock.header.GetHash(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (pwallet->IsMine(*wtx.tx)) { pwallet->AddToWallet(wtx, false); @@ -412,7 +422,8 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); std::vector<uint256> vHash; @@ -483,7 +494,8 @@ UniValue importpubkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); for (const auto& dest : GetAllDestinationsForKey(pubKey)) { ImportAddress(pwallet, dest, strLabel); @@ -535,7 +547,8 @@ UniValue importwallet(const JSONRPCRequest& request) int64_t nTimeBegin = 0; bool fGood = true; { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -653,7 +666,8 @@ UniValue dumpprivkey(const JSONRPCRequest& request) + HelpExampleRpc("dumpprivkey", "\"myaddress\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -700,7 +714,8 @@ UniValue dumpwallet(const JSONRPCRequest& request) + HelpExampleRpc("dumpwallet", "\"test\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -723,7 +738,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) std::map<CTxDestination, int64_t> mapKeyBirth; const std::map<CKeyID, int64_t>& mapKeyPool = pwallet->GetAllReserveKeys(); - pwallet->GetKeyBirthTimes(mapKeyBirth); + pwallet->GetKeyBirthTimes(*locked_chain, mapKeyBirth); std::set<CScriptID> scripts = pwallet->GetCScripts(); // TODO: include scripts in GetKeyBirthTimes() output instead of separate @@ -1135,7 +1150,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) int64_t nLowestTimestamp = 0; UniValue response(UniValue::VARR); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); // Verify all timestamps are present before importing any keys. diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 45d4b5bceb..71bebb411c 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -8,6 +8,8 @@ #include <consensus/validation.h> #include <core_io.h> #include <httpserver.h> +#include <init.h> +#include <interfaces/chain.h> #include <validation.h> #include <key_io.h> #include <net.h> @@ -89,9 +91,9 @@ void EnsureWalletIsUnlocked(CWallet * const pwallet) } } -static void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx, UniValue& entry) { - int confirms = wtx.GetDepthInMainChain(); + int confirms = wtx.GetDepthInMainChain(locked_chain); entry.pushKV("confirmations", confirms); if (wtx.IsCoinBase()) entry.pushKV("generated", true); @@ -101,7 +103,7 @@ static void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) EXCLUSIVE_LOCK entry.pushKV("blockindex", wtx.nIndex); entry.pushKV("blocktime", LookupBlockIndex(wtx.hashBlock)->GetBlockTime()); } else { - entry.pushKV("trusted", wtx.IsTrusted()); + entry.pushKV("trusted", wtx.IsTrusted(locked_chain)); } uint256 hash = wtx.GetHash(); entry.pushKV("txid", hash.GetHex()); @@ -290,7 +292,7 @@ static UniValue setlabel(const JSONRPCRequest& request) } -static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) +static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) { CAmount curBalance = pwallet->GetBalance(); @@ -317,7 +319,7 @@ static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination & CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); CTransactionRef tx; - if (!pwallet->CreateTransaction(vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { + if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); throw JSONRPCError(RPC_WALLET_ERROR, strError); @@ -373,7 +375,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(dest)) { @@ -415,7 +418,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); - CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue)); + CTransactionRef tx = SendMoney(*locked_chain, pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue)); return tx->GetHash().GetHex(); } @@ -455,10 +458,11 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); - std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(); + std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(*locked_chain); for (const std::set<CTxDestination>& grouping : pwallet->GetAddressGroupings()) { UniValue jsonGrouping(UniValue::VARR); for (const CTxDestination& address : grouping) @@ -508,7 +512,8 @@ static UniValue signmessage(const JSONRPCRequest& request) + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -574,7 +579,9 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // Bitcoin address CTxDestination dest = DecodeDestination(request.params[0].get_str()); @@ -600,7 +607,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) for (const CTxOut& txout : wtx.tx->vout) if (txout.scriptPubKey == scriptPubKey) - if (wtx.GetDepthInMainChain() >= nMinDepth) + if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) nAmount += txout.nValue; } @@ -641,7 +648,9 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // Minimum confirmations int nMinDepth = 1; @@ -663,7 +672,7 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) { CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwallet, address) && setAddress.count(address)) { - if (wtx.GetDepthInMainChain() >= nMinDepth) + if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) nAmount += txout.nValue; } } @@ -707,7 +716,8 @@ static UniValue getbalance(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); const UniValue& dummy_value = request.params[0]; if (!dummy_value.isNull() && dummy_value.get_str() != "*") { @@ -745,7 +755,8 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); return ValueFromAmount(pwallet->GetUnconfirmedBalance()); } @@ -806,7 +817,8 @@ static UniValue sendmany(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (pwallet->GetBroadcastTransactions() && !g_connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -892,7 +904,7 @@ static UniValue sendmany(const JSONRPCRequest& request) int nChangePosRet = -1; std::string strFailReason; CTransactionRef tx; - bool fCreated = pwallet->CreateTransaction(vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); + bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; @@ -945,7 +957,8 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) throw std::runtime_error(msg); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); std::string label; if (!request.params[2].isNull()) @@ -996,8 +1009,10 @@ struct tallyitem } }; -static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwallet->cs_wallet) +static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + // Minimum confirmations int nMinDepth = 1; if (!params[0].isNull()) @@ -1031,7 +1046,7 @@ static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bo if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) continue; - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wtx.GetDepthInMainChain(locked_chain); if (nDepth < nMinDepth) continue; @@ -1185,9 +1200,10 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); - return ListReceived(pwallet, request.params, false); + return ListReceived(*locked_chain, pwallet, request.params, false); } static UniValue listreceivedbylabel(const JSONRPCRequest& request) @@ -1229,9 +1245,10 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); - return ListReceived(pwallet, request.params, true); + return ListReceived(*locked_chain, pwallet, request.params, true); } static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) @@ -1251,7 +1268,7 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) * @param ret The UniValue into which the result is stored. * @param filter The "is mine" filter bool. */ -static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) { CAmount nFee; std::list<COutputEntry> listReceived; @@ -1279,14 +1296,14 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n entry.pushKV("vout", s.vout); entry.pushKV("fee", ValueFromAmount(-nFee)); if (fLong) - WalletTxToJSON(wtx, entry); + WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); } } // Received - if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) + if (listReceived.size() > 0 && wtx.GetDepthInMainChain(locked_chain) >= nMinDepth) { for (const COutputEntry& r : listReceived) { @@ -1301,9 +1318,9 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n MaybePushAddress(entry, r.destination); if (wtx.IsCoinBase()) { - if (wtx.GetDepthInMainChain() < 1) + if (wtx.GetDepthInMainChain(locked_chain) < 1) entry.pushKV("category", "orphan"); - else if (wtx.IsImmatureCoinBase()) + else if (wtx.IsImmatureCoinBase(locked_chain)) entry.pushKV("category", "immature"); else entry.pushKV("category", "generate"); @@ -1318,7 +1335,7 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n } entry.pushKV("vout", r.vout); if (fLong) - WalletTxToJSON(wtx, entry); + WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); ret.push_back(entry); } } @@ -1405,7 +1422,8 @@ UniValue listtransactions(const JSONRPCRequest& request) UniValue ret(UniValue::VARR); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); const CWallet::TxItems & txOrdered = pwallet->wtxOrdered; @@ -1413,7 +1431,7 @@ UniValue listtransactions(const JSONRPCRequest& request) for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = (*it).second; - ListTransactions(pwallet, *pwtx, 0, true, ret, filter); + ListTransactions(*locked_chain, pwallet, *pwtx, 0, true, ret, filter); if ((int)ret.size() >= (nCount+nFrom)) break; } } @@ -1505,7 +1523,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); const CBlockIndex* pindex = nullptr; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain. const CBlockIndex* paltindex = nullptr; // Block index of the specified block, even if it's in a deactivated chain. @@ -1548,8 +1567,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { CWalletTx tx = pairWtx.second; - if (depth == -1 || tx.GetDepthInMainChain() < depth) { - ListTransactions(pwallet, tx, 0, true, transactions, filter); + if (depth == -1 || tx.GetDepthInMainChain(*locked_chain) < depth) { + ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, filter); } } @@ -1566,7 +1585,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) if (it != pwallet->mapWallet.end()) { // We want all transactions regardless of confirmation count to appear here, // even negative confirmation ones, hence the big negative. - ListTransactions(pwallet, it->second, -100000000, true, removed, filter); + ListTransactions(*locked_chain, pwallet, it->second, -100000000, true, removed, filter); } } paltindex = paltindex->pprev; @@ -1640,7 +1659,8 @@ static UniValue gettransaction(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); @@ -1656,7 +1676,7 @@ static UniValue gettransaction(const JSONRPCRequest& request) } const CWalletTx& wtx = it->second; - CAmount nCredit = wtx.GetCredit(filter); + CAmount nCredit = wtx.GetCredit(*locked_chain, filter); CAmount nDebit = wtx.GetDebit(filter); CAmount nNet = nCredit - nDebit; CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); @@ -1665,10 +1685,10 @@ static UniValue gettransaction(const JSONRPCRequest& request) if (wtx.IsFromMe(filter)) entry.pushKV("fee", ValueFromAmount(nFee)); - WalletTxToJSON(wtx, entry); + WalletTxToJSON(pwallet->chain(), *locked_chain, wtx, entry); UniValue details(UniValue::VARR); - ListTransactions(pwallet, wtx, 0, false, details, filter); + ListTransactions(*locked_chain, pwallet, wtx, 0, false, details, filter); entry.pushKV("details", details); std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags()); @@ -1707,14 +1727,15 @@ static UniValue abandontransaction(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); if (!pwallet->mapWallet.count(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); } - if (!pwallet->AbandonTransaction(hash)) { + if (!pwallet->AbandonTransaction(*locked_chain, hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); } @@ -1746,7 +1767,8 @@ static UniValue backupwallet(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); std::string strDest = request.params[0].get_str(); if (!pwallet->BackupWallet(strDest)) { @@ -1782,7 +1804,8 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool unsigned int kpSize = 0; @@ -1833,7 +1856,8 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called."); @@ -1911,7 +1935,8 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); @@ -1967,7 +1992,8 @@ static UniValue walletlock(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); @@ -2013,7 +2039,8 @@ static UniValue encryptwallet(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called."); @@ -2048,7 +2075,21 @@ static UniValue lockunspent(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "lockunspent unlock ([{\"txid\":\"txid\",\"vout\":n},...])\n" + RPCHelpMan{"lockunspent", + { + {"unlock", RPCArg::Type::BOOL, false}, + {"transactions", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"txid", RPCArg::Type::STR_HEX, false}, + {"vout", RPCArg::Type::NUM, false}, + }, + true}, + }, + true}, + }} + .ToString() + "\nUpdates list of temporarily unspendable outputs.\n" "Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n" "If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked.\n" @@ -2087,7 +2128,8 @@ static UniValue lockunspent(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); RPCTypeCheckArgument(request.params[0], UniValue::VBOOL); @@ -2136,7 +2178,7 @@ static UniValue lockunspent(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds"); } - if (pwallet->IsSpent(outpt.hash, outpt.n)) { + if (pwallet->IsSpent(*locked_chain, outpt.hash, outpt.n)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output"); } @@ -2197,7 +2239,8 @@ static UniValue listlockunspent(const JSONRPCRequest& request) + HelpExampleRpc("listlockunspent", "") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); std::vector<COutPoint> vOutpts; pwallet->ListLockedCoins(vOutpts); @@ -2238,7 +2281,8 @@ static UniValue settxfee(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); CAmount nAmount = AmountFromValue(request.params[0]); CFeeRate tx_fee_rate(nAmount, 1000); @@ -2293,7 +2337,8 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); UniValue obj(UniValue::VOBJ); @@ -2420,11 +2465,11 @@ static UniValue loadwallet(const JSONRPCRequest& request) } std::string warning; - if (!CWallet::Verify(location, false, error, warning)) { + if (!CWallet::Verify(*g_rpc_interfaces->chain, location, false, error, warning)) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); } - std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(location); + std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed."); } @@ -2472,11 +2517,11 @@ static UniValue createwallet(const JSONRPCRequest& request) } // Wallet::Verify will check if we're trying to create a wallet with a duplication name. - if (!CWallet::Verify(location, false, error, warning)) { + if (!CWallet::Verify(*g_rpc_interfaces->chain, location, false, error, warning)) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); } - std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(location, (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0)); + std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0)); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); } @@ -2562,13 +2607,14 @@ static UniValue resendwallettransactions(const JSONRPCRequest& request) if (!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->GetBroadcastTransactions()) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast"); } - std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(GetTime(), g_connman.get()); + std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(*locked_chain, GetTime(), g_connman.get()); UniValue result(UniValue::VARR); for (const uint256& txid : txids) { @@ -2588,7 +2634,26 @@ static UniValue listunspent(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 5) throw std::runtime_error( - "listunspent ( minconf maxconf [\"addresses\",...] [include_unsafe] [query_options])\n" + RPCHelpMan{"listunspent", + { + {"minconf", RPCArg::Type::NUM, true}, + {"maxconf", RPCArg::Type::NUM, true}, + {"addresses", RPCArg::Type::ARR, + { + {"address", RPCArg::Type::STR_HEX, true}, + }, + true}, + {"include_unsafe", RPCArg::Type::BOOL, true}, + {"query_options", RPCArg::Type::OBJ, + { + {"minimumAmount", RPCArg::Type::AMOUNT, true}, + {"maximumAmount", RPCArg::Type::AMOUNT, true}, + {"maximumCount", RPCArg::Type::NUM, true}, + {"minimumSumAmount", RPCArg::Type::AMOUNT, true}, + }, + true}, + }} + .ToString() + "\nReturns array of unspent transaction outputs\n" "with between minconf and maxconf (inclusive) confirmations.\n" "Optionally filter to only include txouts paid to specified addresses.\n" @@ -2699,8 +2764,9 @@ static UniValue listunspent(const JSONRPCRequest& request) UniValue results(UniValue::VARR); std::vector<COutput> vecOutputs; { - LOCK2(cs_main, pwallet->cs_wallet); - pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); + pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); } LOCK(pwallet->cs_wallet); @@ -2962,7 +3028,25 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( - "signrawtransactionwithwallet \"hexstring\" ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] sighashtype )\n" + RPCHelpMan{"signrawtransactionwithwallet", + { + {"hexstring", RPCArg::Type::STR, false}, + {"prevtxs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"txid", RPCArg::Type::STR_HEX, false}, + {"vout", RPCArg::Type::NUM, false}, + {"scriptPubKey", RPCArg::Type::STR_HEX, false}, + {"redeemScript", RPCArg::Type::STR_HEX, false}, + {"amount", RPCArg::Type::AMOUNT, false}, + }, + false}, + }, + true}, + {"sighashtype", RPCArg::Type::STR, true}, + }} + .ToString() + "\nSign inputs for raw transaction (serialized, hex-encoded).\n" "The second optional argument (may be null) is an array of previous transaction outputs that\n" "this transaction depends on but may not yet be in the block chain.\n" @@ -3018,10 +3102,11 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) } // Sign the transaction - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); - return SignTransaction(mtx, request.params[1], pwallet, false, request.params[2]); + return SignTransaction(pwallet->chain(), mtx, request.params[1], pwallet, false, request.params[2]); } static UniValue bumpfee(const JSONRPCRequest& request) @@ -3123,7 +3208,8 @@ static UniValue bumpfee(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -3263,7 +3349,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request) CBlockIndex *pindexStop = nullptr; CBlockIndex *pChainTip = nullptr; { - LOCK(cs_main); + auto locked_chain = pwallet->chain().lock(); pindexStart = chainActive.Genesis(); pChainTip = chainActive.Tip(); @@ -3287,7 +3373,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request) // We can't rescan beyond non-pruned blocks, stop and throw an error if (fPruneMode) { - LOCK(cs_main); + auto locked_chain = pwallet->chain().lock(); CBlockIndex *block = pindexStop ? pindexStop : pChainTip; while (block && block->nHeight >= pindexStart->nHeight) { if (!(block->nStatus & BLOCK_HAVE_DATA)) { @@ -3687,7 +3773,8 @@ UniValue sethdseed(const JSONRPCRequest& request) throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download"); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // Do not do anything to non-HD wallets if (!pwallet->IsHDEnabled()) { @@ -3736,24 +3823,34 @@ void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubK hd_keypaths.emplace(vchPubKey, std::move(info)); } -bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type, bool sign, bool bip32derivs) +bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs) { LOCK(pwallet->cs_wallet); // Get all of the previous transactions bool complete = true; - for (unsigned int i = 0; i < txConst->vin.size(); ++i) { - const CTxIn& txin = txConst->vin[i]; + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + const CTxIn& txin = psbtx.tx->vin[i]; PSBTInput& input = psbtx.inputs.at(i); - // If we don't know about this input, skip it and let someone else deal with it - const uint256& txhash = txin.prevout.hash; - const auto it = pwallet->mapWallet.find(txhash); - if (it != pwallet->mapWallet.end()) { - const CWalletTx& wtx = it->second; - CTxOut utxo = wtx.tx->vout[txin.prevout.n]; - // Update both UTXOs from the wallet. - input.non_witness_utxo = wtx.tx; - input.witness_utxo = utxo; + if (PSBTInputSigned(input)) { + continue; + } + + // Verify input looks sane. This will check that we have at most one uxto, witness or non-witness. + if (!input.IsSane()) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "PSBT input is not sane."); + } + + // If we have no utxo, grab it from the wallet. + if (!input.non_witness_utxo && input.witness_utxo.IsNull()) { + const uint256& txhash = txin.prevout.hash; + const auto it = pwallet->mapWallet.find(txhash); + if (it != pwallet->mapWallet.end()) { + const CWalletTx& wtx = it->second; + // We only need the non_witness_utxo, which is a superset of the witness_utxo. + // The signing code will switch to the smaller witness_utxo if this is ok. + input.non_witness_utxo = wtx.tx; + } } // Get the Sighash type @@ -3761,12 +3858,12 @@ bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const C throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match."); } - complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), *psbtx.tx, input, i, sighash_type); + complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), psbtx, i, sighash_type); } // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change - for (unsigned int i = 0; i < txConst->vout.size(); ++i) { - const CTxOut& out = txConst->vout.at(i); + for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { + const CTxOut& out = psbtx.tx->vout.at(i); PSBTOutput& psbt_out = psbtx.outputs.at(i); // Fill a SignatureData with output info @@ -3831,19 +3928,15 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) // Get the sighash type int nHashType = ParseSighashString(request.params[2]); - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(*psbtx.tx); - // Fill transaction with our data and also sign bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool(); - bool complete = FillPSBT(pwallet, psbtx, &txConst, nHashType, sign, bip32derivs); + bool complete = FillPSBT(pwallet, psbtx, nHashType, sign, bip32derivs); UniValue result(UniValue::VOBJ); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; - result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("psbt", EncodeBase64(ssTx.str())); result.pushKV("complete", complete); return result; @@ -3860,7 +3953,55 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) throw std::runtime_error( - "walletcreatefundedpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( options bip32derivs )\n" + RPCHelpMan{"walletcreatefundedpsbt", + { + {"inputs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"txid", RPCArg::Type::STR_HEX, false}, + {"vout", RPCArg::Type::NUM, false}, + {"sequence", RPCArg::Type::NUM, false}, + }, + false}, + }, + false}, + {"outputs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"address", RPCArg::Type::AMOUNT, true}, + }, + true}, + {"", RPCArg::Type::OBJ, + { + {"data", RPCArg::Type::STR_HEX, true}, + }, + true}, + }, + false}, + {"locktime", RPCArg::Type::NUM, true}, + {"options", RPCArg::Type::OBJ, + { + {"changeAddress", RPCArg::Type::STR_HEX, true}, + {"changePosition", RPCArg::Type::NUM, true}, + {"change_type", RPCArg::Type::STR, true}, + {"includeWatching", RPCArg::Type::BOOL, true}, + {"lockUnspents", RPCArg::Type::BOOL, true}, + {"feeRate", RPCArg::Type::NUM, true}, + {"subtractFeeFromOutputs", RPCArg::Type::ARR, + { + {"int", RPCArg::Type::NUM, true}, + }, + true}, + {"replaceable", RPCArg::Type::BOOL, true}, + {"conf_target", RPCArg::Type::NUM, true}, + {"estimate_mode", RPCArg::Type::STR, true}, + }, + true}, + {"bip32derivs", RPCArg::Type::BOOL, true}, + }} + .ToString() + "\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n" "Implements the Creator and Updater roles.\n" "\nArguments:\n" @@ -3935,29 +4076,18 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]); // Make a blank psbt - PartiallySignedTransaction psbtx; - psbtx.tx = rawTx; - for (unsigned int i = 0; i < rawTx.vin.size(); ++i) { - psbtx.inputs.push_back(PSBTInput()); - } - for (unsigned int i = 0; i < rawTx.vout.size(); ++i) { - psbtx.outputs.push_back(PSBTOutput()); - } - - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(*psbtx.tx); + PartiallySignedTransaction psbtx(rawTx); // Fill transaction with out data but don't sign bool bip32derivs = request.params[4].isNull() ? false : request.params[4].get_bool(); - FillPSBT(pwallet, psbtx, &txConst, 1, false, bip32derivs); + FillPSBT(pwallet, psbtx, 1, false, bip32derivs); // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; UniValue result(UniValue::VOBJ); - result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("psbt", EncodeBase64(ssTx.str())); result.pushKV("fee", ValueFromAmount(fee)); result.pushKV("changepos", change_position); return result; diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 9b9a159b86..abd7750874 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -30,5 +30,5 @@ bool EnsureWalletIsAvailable(CWallet *, bool avoidException); UniValue getaddressinfo(const JSONRPCRequest& request); UniValue signrawtransactionwithwallet(const JSONRPCRequest& request); -bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type = 1, bool sign = true, bool bip32derivs = false); +bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false); #endif //BITCOIN_WALLET_RPCWALLET_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index a9464870ea..5c65acf601 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -28,7 +28,8 @@ std::vector<std::unique_ptr<CWalletTx>> wtxn; typedef std::set<CInputCoin> CoinSet; static std::vector<COutput> vCoins; -static CWallet testWallet(WalletLocation(), WalletDatabase::CreateDummy()); +static auto testChain = interfaces::MakeChain(); +static CWallet testWallet(*testChain, WalletLocation(), WalletDatabase::CreateDummy()); static CAmount balance = 0; CoinEligibilityFilter filter_standard(1, 6, 0); diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp index 1453029c9c..3b828d57f9 100644 --- a/src/wallet/test/init_test_fixture.cpp +++ b/src/wallet/test/init_test_fixture.cpp @@ -8,6 +8,8 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName): BasicTestingSetup(chainName) { + m_chain_client = MakeWalletClient(*m_chain, {}); + std::string sep; sep += fs::path::preferred_separator; diff --git a/src/wallet/test/init_test_fixture.h b/src/wallet/test/init_test_fixture.h index 5684adbece..cd47b31da1 100644 --- a/src/wallet/test/init_test_fixture.h +++ b/src/wallet/test/init_test_fixture.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H #define BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H +#include <interfaces/chain.h> #include <test/test_bitcoin.h> @@ -16,6 +17,8 @@ struct InitWalletDirTestingSetup: public BasicTestingSetup { fs::path m_datadir; fs::path m_cwd; std::map<std::string, fs::path> m_walletdir_path_cases; + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); + std::unique_ptr<interfaces::ChainClient> m_chain_client; }; #endif // BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp index 7048547b2b..5852d3ef84 100644 --- a/src/wallet/test/init_tests.cpp +++ b/src/wallet/test/init_tests.cpp @@ -17,7 +17,7 @@ BOOST_FIXTURE_TEST_SUITE(init_tests, InitWalletDirTestingSetup) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_default) { SetWalletDir(m_walletdir_path_cases["default"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == true); fs::path walletdir = gArgs.GetArg("-walletdir", ""); fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); @@ -27,7 +27,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_default) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom) { SetWalletDir(m_walletdir_path_cases["custom"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == true); fs::path walletdir = gArgs.GetArg("-walletdir", ""); fs::path expected_path = fs::canonical(m_walletdir_path_cases["custom"]); @@ -37,28 +37,28 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_does_not_exist) { SetWalletDir(m_walletdir_path_cases["nonexistent"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == false); } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_directory) { SetWalletDir(m_walletdir_path_cases["file"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == false); } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_relative) { SetWalletDir(m_walletdir_path_cases["relative"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == false); } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing) { SetWalletDir(m_walletdir_path_cases["trailing"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == true); fs::path walletdir = gArgs.GetArg("-walletdir", ""); fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); @@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing2) { SetWalletDir(m_walletdir_path_cases["trailing2"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == true); fs::path walletdir = gArgs.GetArg("-walletdir", ""); fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index cb1ad25461..9918eeb89f 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -59,12 +59,8 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) CDataStream ssData(ParseHex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"), SER_NETWORK, PROTOCOL_VERSION); ssData >> psbtx; - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(*psbtx.tx); - // Fill transaction with our data - FillPSBT(&m_wallet, psbtx, &txConst, 1, false, true); + FillPSBT(&m_wallet, psbtx, SIGHASH_ALL, false, true); // Get the final tx CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index d42209ab15..a5fb1db86c 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -9,7 +9,7 @@ #include <wallet/rpcwallet.h> WalletTestingSetup::WalletTestingSetup(const std::string& chainName): - TestingSetup(chainName), m_wallet(WalletLocation(), WalletDatabase::CreateMock()) + TestingSetup(chainName), m_wallet(*m_chain, WalletLocation(), WalletDatabase::CreateMock()) { bool fFirstRun; m_wallet.LoadWallet(fFirstRun); diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index ff8ec32b03..e6fe8c9473 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -7,6 +7,8 @@ #include <test/test_bitcoin.h> +#include <interfaces/chain.h> +#include <interfaces/wallet.h> #include <wallet/wallet.h> #include <memory> @@ -17,6 +19,7 @@ struct WalletTestingSetup: public TestingSetup { explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~WalletTestingSetup(); + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); CWallet m_wallet; }; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 269a916829..c6aac8aad5 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -11,6 +11,7 @@ #include <vector> #include <consensus/validation.h> +#include <interfaces/chain.h> #include <rpc/server.h> #include <test/test_bitcoin.h> #include <validation.h> @@ -34,6 +35,8 @@ static void AddKey(CWallet& wallet, const CKey& key) BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { + auto chain = interfaces::MakeChain(); + // Cap last block file size, and mine new block in a new block file. CBlockIndex* const nullBlock = nullptr; CBlockIndex* oldTip = chainActive.Tip(); @@ -41,12 +44,12 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = chainActive.Tip(); - LOCK(cs_main); + auto locked_chain = chain->lock(); // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { - CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -61,7 +64,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) // Verify ScanForWalletTransactions only picks transactions in the new block // file. { - CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -73,7 +76,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) // before the missing block, and success for a key whose creation time is // after. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddWallet(wallet); UniValue keys; keys.setArray(); @@ -115,6 +118,8 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) // than or equal to key birthday. BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { + auto chain = interfaces::MakeChain(); + // Create two blocks with same timestamp to verify that importwallet rescan // will pick up both blocks, not just the first. const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5; @@ -128,13 +133,13 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) SetMockTime(KEY_TIME); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); - LOCK(cs_main); + auto locked_chain = chain->lock(); std::string backup_file = (SetDataDir("importwallet_rescan") / "wallet.backup").string(); // Import key into wallet and call dumpwallet to create backup file. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet->cs_wallet); wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); @@ -150,7 +155,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); JSONRPCRequest request; request.params.setArray(); @@ -180,21 +185,23 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // debit functions. BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { - CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy()); + auto chain = interfaces::MakeChain(); + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); CWalletTx wtx(&wallet, m_coinbase_txns.back()); - LOCK2(cs_main, wallet.cs_wallet); + auto locked_chain = chain->lock(); + LOCK(wallet.cs_wallet); wtx.hashBlock = chainActive.Tip()->GetBlockHash(); wtx.nIndex = 0; // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 0); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 0); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50*COIN); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50*COIN); } static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) @@ -204,7 +211,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 SetMockTime(mockTime); CBlockIndex* block = nullptr; if (blockTime > 0) { - LOCK(cs_main); + auto locked_chain = wallet.chain().lock(); auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex); assert(inserted.second); const uint256& hash = inserted.first->first; @@ -273,7 +280,7 @@ public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - wallet = MakeUnique<CWallet>(WalletLocation(), WalletDatabase::CreateMock()); + wallet = MakeUnique<CWallet>(*m_chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); @@ -295,7 +302,7 @@ public: int changePos = -1; std::string error; CCoinControl dummy; - BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, reservekey, fee, changePos, error, dummy)); + BOOST_CHECK(wallet->CreateTransaction(*m_locked_chain, {recipient}, tx, reservekey, fee, changePos, error, dummy)); CValidationState state; BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, nullptr, state)); CMutableTransaction blocktx; @@ -311,6 +318,8 @@ public: return it->second; } + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); + std::unique_ptr<interfaces::Chain::Lock> m_locked_chain = m_chain->assumeLocked(); // Temporary. Removed in upcoming lock cleanup std::unique_ptr<CWallet> wallet; }; @@ -323,7 +332,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) std::map<CTxDestination, std::vector<COutput>> list; { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); @@ -339,7 +348,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); @@ -349,7 +358,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { LOCK2(cs_main, wallet->cs_wallet); std::vector<COutput> available; - wallet->AvailableCoins(available); + wallet->AvailableCoins(*m_locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto& group : list) { @@ -361,14 +370,14 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { LOCK2(cs_main, wallet->cs_wallet); std::vector<COutput> available; - wallet->AvailableCoins(available); + wallet->AvailableCoins(*m_locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); @@ -377,7 +386,8 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy()); + auto chain = interfaces::MakeChain(); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); CPubKey pubkey; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 2ea9f45462..8ea4c5c495 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -11,6 +11,7 @@ #include <consensus/consensus.h> #include <consensus/validation.h> #include <fs.h> +#include <interfaces/chain.h> #include <key.h> #include <key_io.h> #include <keystore.h> @@ -593,7 +594,7 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran * Outpoint is spent if any non-conflicted transaction * spends it: */ -bool CWallet::IsSpent(const uint256& hash, unsigned int n) const +bool CWallet::IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const { const COutPoint outpoint(hash, n); std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; @@ -604,7 +605,7 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const const uint256& wtxid = it->second; std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); if (mit != mapWallet.end()) { - int depth = mit->second.GetDepthInMainChain(); + int depth = mit->second.GetDepthInMainChain(locked_chain); if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) return true; // Spent } @@ -1005,9 +1006,10 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); const CWalletTx* wtx = GetWalletTx(hashTx); - return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); + return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain(*locked_chain) == 0 && !wtx->InMempool(); } void CWallet::MarkInputsDirty(const CTransactionRef& tx) @@ -1020,9 +1022,10 @@ void CWallet::MarkInputsDirty(const CTransactionRef& tx) } } -bool CWallet::AbandonTransaction(const uint256& hashTx) +bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx) { - LOCK2(cs_main, cs_wallet); + auto locked_chain_recursive = chain().lock(); // Temporary. Removed in upcoming lock cleanup + LOCK(cs_wallet); WalletBatch batch(*database, "r+"); @@ -1033,7 +1036,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(hashTx); assert(it != mapWallet.end()); CWalletTx& origtx = it->second; - if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) { + if (origtx.GetDepthInMainChain(locked_chain) != 0 || origtx.InMempool()) { return false; } @@ -1046,7 +1049,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(); + int currentconfirm = wtx.GetDepthInMainChain(locked_chain); // If the orig tx was not in block, none of its spends can be assert(currentconfirm <= 0); // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon} @@ -1077,7 +1080,8 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); int conflictconfirms = 0; CBlockIndex* pindex = LookupBlockIndex(hashBlock); @@ -1106,7 +1110,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(); + int currentconfirm = wtx.GetDepthInMainChain(*locked_chain); if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. @@ -1140,7 +1144,8 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin } void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); SyncTransaction(ptx); auto it = mapWallet.find(ptx->GetHash()); @@ -1158,7 +1163,8 @@ void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) { } void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); // TODO: Temporarily ensure that mempool removals are notified before // connected transactions. This shouldn't matter, but the abandoned // state of transactions in our wallet is currently cleared when we @@ -1180,7 +1186,8 @@ void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const } void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const CTransactionRef& ptx : pblock->vtx) { SyncTransaction(ptx); @@ -1199,7 +1206,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() { // 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. - LOCK(cs_main); + auto locked_chain = chain().lock(); const CBlockIndex* initialChainTip = chainActive.Tip(); if (m_last_block_processed && m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { @@ -1600,7 +1607,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r // to be scanned. CBlockIndex* startBlock = nullptr; { - LOCK(cs_main); + auto locked_chain = chain().lock(); startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); } @@ -1652,7 +1659,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock double progress_begin; double progress_end; { - LOCK(cs_main); + auto locked_chain = chain().lock(); progress_begin = GuessVerificationProgress(chainParams.TxData(), pindex); if (pindexStop == nullptr) { tip = chainActive.Tip(); @@ -1674,7 +1681,8 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock CBlock block; if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); if (pindex && !chainActive.Contains(pindex)) { // Abort scan if current block is no longer active, to prevent // marking transactions as coming from the wrong block. @@ -1691,7 +1699,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock break; } { - LOCK(cs_main); + auto locked_chain = chain().lock(); pindex = chainActive.Next(pindex); progress_current = GuessVerificationProgress(chainParams.TxData(), pindex); if (pindexStop == nullptr && tip != chainActive.Tip()) { @@ -1716,7 +1724,8 @@ void CWallet::ReacceptWalletTransactions() // If transactions aren't being broadcasted, don't let them into local mempool either if (!fBroadcastTransactions) return; - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); std::map<int64_t, CWalletTx*> mapSorted; // Sort pending wallet transactions based on their initial wallet insertion order @@ -1726,7 +1735,7 @@ void CWallet::ReacceptWalletTransactions() CWalletTx& wtx = item.second; assert(wtx.GetHash() == wtxid); - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wtx.GetDepthInMainChain(*locked_chain); if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); @@ -1737,18 +1746,18 @@ void CWallet::ReacceptWalletTransactions() for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *(item.second); CValidationState state; - wtx.AcceptToMemoryPool(maxTxFee, state); + wtx.AcceptToMemoryPool(*locked_chain, maxTxFee, state); } } -bool CWalletTx::RelayWalletTransaction(CConnman* connman) +bool CWalletTx::RelayWalletTransaction(interfaces::Chain::Lock& locked_chain, CConnman* connman) { assert(pwallet->GetBroadcastTransactions()); - if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain() == 0) + if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain(locked_chain) == 0) { CValidationState state; /* GetDepthInMainChain already catches known conflicts. */ - if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) { + if (InMempool() || AcceptToMemoryPool(locked_chain, maxTxFee, state)) { pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); if (connman) { CInv inv(MSG_TX, GetHash()); @@ -1806,10 +1815,10 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const return debit; } -CAmount CWalletTx::GetCredit(const isminefilter& filter) const +CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const { // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) + if (IsImmatureCoinBase(locked_chain)) return 0; CAmount credit = 0; @@ -1839,9 +1848,9 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const return credit; } -CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const +CAmount CWalletTx::GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache) const { - if (IsImmatureCoinBase() && IsInMainChain()) { + if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { if (fUseCache && fImmatureCreditCached) return nImmatureCreditCached; nImmatureCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE); @@ -1852,13 +1861,13 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const return 0; } -CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const +CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache, const isminefilter& filter) const { if (pwallet == nullptr) return 0; // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) + if (IsImmatureCoinBase(locked_chain)) return 0; CAmount* cache = nullptr; @@ -1880,7 +1889,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter uint256 hashTx = GetHash(); for (unsigned int i = 0; i < tx->vout.size(); i++) { - if (!pwallet->IsSpent(hashTx, i)) + if (!pwallet->IsSpent(locked_chain, hashTx, i)) { const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, filter); @@ -1897,9 +1906,9 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter return nCredit; } -CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const +CAmount CWalletTx::GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache) const { - if (IsImmatureCoinBase() && IsInMainChain()) { + if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { if (fUseCache && fImmatureWatchCreditCached) return nImmatureWatchCreditCached; nImmatureWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY); @@ -1924,12 +1933,14 @@ bool CWalletTx::InMempool() const return fInMempool; } -bool CWalletTx::IsTrusted() const +bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain) const { + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + // Quick answer in most cases if (!CheckFinalTx(*tx)) return false; - int nDepth = GetDepthInMainChain(); + int nDepth = GetDepthInMainChain(locked_chain); if (nDepth >= 1) return true; if (nDepth < 0) @@ -1964,7 +1975,7 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const return CTransaction(tx1) == CTransaction(tx2); } -std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman) +std::vector<uint256> CWallet::ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime, CConnman* connman) { std::vector<uint256> result; @@ -1983,7 +1994,7 @@ std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime, CCon for (const std::pair<const unsigned int, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *item.second; - if (wtx.RelayWalletTransaction(connman)) + if (wtx.RelayWalletTransaction(locked_chain, connman)) result.push_back(wtx.GetHash()); } return result; @@ -2007,7 +2018,8 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman // Rebroadcast unconfirmed txes older than 5 minutes before the last // block was found: - std::vector<uint256> relayed = ResendWalletTransactionsBefore(nBestBlockTime-5*60, connman); + auto locked_chain = chain().assumeLocked(); // Temporary. Removed in upcoming lock cleanup + std::vector<uint256> relayed = ResendWalletTransactionsBefore(*locked_chain, nBestBlockTime-5*60, connman); if (!relayed.empty()) WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); } @@ -2027,12 +2039,13 @@ CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) con { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (pcoin->IsTrusted() && pcoin->GetDepthInMainChain() >= min_depth) { - nTotal += pcoin->GetAvailableCredit(true, filter); + if (pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) >= min_depth) { + nTotal += pcoin->GetAvailableCredit(*locked_chain, true, filter); } } } @@ -2044,12 +2057,13 @@ CAmount CWallet::GetUnconfirmedBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableCredit(); + if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool()) + nTotal += pcoin->GetAvailableCredit(*locked_chain); } } return nTotal; @@ -2059,11 +2073,12 @@ CAmount CWallet::GetImmatureBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - nTotal += pcoin->GetImmatureCredit(); + nTotal += pcoin->GetImmatureCredit(*locked_chain); } } return nTotal; @@ -2073,12 +2088,13 @@ CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableCredit(true, ISMINE_WATCH_ONLY); + if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool()) + nTotal += pcoin->GetAvailableCredit(*locked_chain, true, ISMINE_WATCH_ONLY); } } return nTotal; @@ -2088,11 +2104,12 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - nTotal += pcoin->GetImmatureWatchOnlyCredit(); + nTotal += pcoin->GetImmatureWatchOnlyCredit(*locked_chain); } } return nTotal; @@ -2106,13 +2123,15 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const // trusted. CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) const { - LOCK2(cs_main, cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CAmount balance = 0; for (const auto& entry : mapWallet) { const CWalletTx& wtx = entry.second; - const int depth = wtx.GetDepthInMainChain(); - if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase()) { + const int depth = wtx.GetDepthInMainChain(*locked_chain); + if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase(*locked_chain)) { continue; } @@ -2139,11 +2158,12 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) cons CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CAmount balance = 0; std::vector<COutput> vCoins; - AvailableCoins(vCoins, true, coinControl); + AvailableCoins(*locked_chain, vCoins, true, coinControl); for (const COutput& out : vCoins) { if (out.fSpendable) { balance += out.tx->tx->vout[out.i].nValue; @@ -2152,7 +2172,7 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const return balance; } -void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const +void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const { AssertLockHeld(cs_main); AssertLockHeld(cs_wallet); @@ -2168,10 +2188,10 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const if (!CheckFinalTx(*pcoin->tx)) continue; - if (pcoin->IsImmatureCoinBase()) + if (pcoin->IsImmatureCoinBase(locked_chain)) continue; - int nDepth = pcoin->GetDepthInMainChain(); + int nDepth = pcoin->GetDepthInMainChain(locked_chain); if (nDepth < 0) continue; @@ -2180,7 +2200,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const if (nDepth == 0 && !pcoin->InMempool()) continue; - bool safeTx = pcoin->IsTrusted(); + bool safeTx = pcoin->IsTrusted(locked_chain); // We should not consider coins from transactions that are replacing // other transactions. @@ -2230,7 +2250,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const if (IsLockedCoin(entry.first, i)) continue; - if (IsSpent(wtxid, i)) + if (IsSpent(locked_chain, wtxid, i)) continue; isminetype mine = IsMine(pcoin->tx->vout[i]); @@ -2261,7 +2281,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const } } -std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const +std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Chain::Lock& locked_chain) const { AssertLockHeld(cs_main); AssertLockHeld(cs_wallet); @@ -2269,7 +2289,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const std::map<CTxDestination, std::vector<COutput>> result; std::vector<COutput> availableCoins; - AvailableCoins(availableCoins); + AvailableCoins(locked_chain, availableCoins); for (const COutput& coin : availableCoins) { CTxDestination address; @@ -2284,7 +2304,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const for (const COutPoint& output : lockedCoins) { auto it = mapWallet.find(output.hash); if (it != mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + int depth = it->second.GetDepthInMainChain(locked_chain); if (depth >= 0 && output.n < it->second.tx->vout.size() && IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { CTxDestination address; @@ -2499,11 +2519,12 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC // Acquire the locks to prevent races to the new locked unspents between the // CreateTransaction call and LockCoin calls (when lockUnspents is true). - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CReserveKey reservekey(this); CTransactionRef tx_new; - if (!CreateTransaction(vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { + if (!CreateTransaction(*locked_chain, vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { return false; } @@ -2562,7 +2583,7 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec return m_default_address_type; } -bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, +bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; @@ -2624,10 +2645,11 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac int nBytes; { std::set<CInputCoin> setCoins; - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); { std::vector<COutput> vAvailableCoins; - AvailableCoins(vAvailableCoins, true, &coin_control); + AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control); CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy // Create change script that will be used if we need change @@ -2962,7 +2984,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CConnman* connman, CValidationState& state) { { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CWalletTx wtxNew(this, std::move(tx)); wtxNew.mapValue = std::move(mapValue); @@ -2995,11 +3018,11 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve if (fBroadcastTransactions) { // Broadcast - if (!wtx.AcceptToMemoryPool(maxTxFee, state)) { + if (!wtx.AcceptToMemoryPool(*locked_chain, maxTxFee, state)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } else { - wtx.RelayWalletTransaction(connman); + wtx.RelayWalletTransaction(*locked_chain, connman); } } } @@ -3008,7 +3031,8 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); fFirstRunRet = false; DBErrors nLoadWalletRet = WalletBatch(*database,"cr+").LoadWallet(this); @@ -3392,7 +3416,7 @@ int64_t CWallet::GetOldestKeyPoolTime() return oldestKey; } -std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() +std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain::Lock& locked_chain) { std::map<CTxDestination, CAmount> balances; @@ -3402,13 +3426,13 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() { const CWalletTx *pcoin = &walletEntry.second; - if (!pcoin->IsTrusted()) + if (!pcoin->IsTrusted(locked_chain)) continue; - if (pcoin->IsImmatureCoinBase()) + if (pcoin->IsImmatureCoinBase(locked_chain)) continue; - int nDepth = pcoin->GetDepthInMainChain(); + int nDepth = pcoin->GetDepthInMainChain(locked_chain); if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1)) continue; @@ -3420,7 +3444,7 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() if(!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr)) continue; - CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue; + CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue; if (!balances.count(addr)) balances[addr] = 0; @@ -3645,7 +3669,7 @@ void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const /** @} */ // end of Actions -void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const { +void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<CTxDestination, int64_t> &mapKeyBirth) const { AssertLockHeld(cs_wallet); // mapKeyMetadata mapKeyBirth.clear(); @@ -3825,7 +3849,7 @@ void CWallet::MarkPreSplitKeys() } } -bool CWallet::Verify(const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string) +bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string) { // Do some checking on wallet path. It should be either a: // @@ -3866,7 +3890,7 @@ bool CWallet::Verify(const WalletLocation& location, bool salvage_wallet, std::s if (salvage_wallet) { // Recover readable keypairs: - CWallet dummyWallet(WalletLocation(), WalletDatabase::CreateDummy()); + CWallet dummyWallet(chain, WalletLocation(), WalletDatabase::CreateDummy()); std::string backup_filename; if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { return false; @@ -3876,7 +3900,7 @@ bool CWallet::Verify(const WalletLocation& location, bool salvage_wallet, std::s return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string); } -std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& location, uint64_t wallet_creation_flags) +std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, uint64_t wallet_creation_flags) { const std::string& walletFile = location.GetName(); @@ -3886,7 +3910,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& loc if (gArgs.GetBoolArg("-zapwallettxes", false)) { uiInterface.InitMessage(_("Zapping all transactions from wallet...")); - std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(location, WalletDatabase::Create(location.GetPath())); + std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(chain, location, WalletDatabase::Create(location.GetPath())); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DBErrors::LOAD_OK) { InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); @@ -3900,7 +3924,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& loc bool fFirstRun = true; // TODO: Can't use std::make_shared because we need a custom deleter but // should be possible to use std::allocate_shared. - std::shared_ptr<CWallet> walletInstance(new CWallet(location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); + std::shared_ptr<CWallet> walletInstance(new CWallet(chain, location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { @@ -4010,6 +4034,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& loc return nullptr; } + auto locked_chain = chain.assumeLocked(); // Temporary. Removed in upcoming lock cleanup walletInstance->ChainStateFlushed(chainActive.GetLocator()); } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation @@ -4097,7 +4122,9 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& loc // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); - LOCK2(cs_main, walletInstance->cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for FindForkInGlobalIndex below. Removed in upcoming commit. + auto locked_chain = chain.lock(); + LOCK(walletInstance->cs_wallet); CBlockIndex *pindexRescan = chainActive.Genesis(); if (!gArgs.GetBoolArg("-rescan", false)) @@ -4232,7 +4259,7 @@ void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock) nIndex = posInBlock; } -int CMerkleTx::GetDepthInMainChain() const +int CMerkleTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const { if (hashUnset()) return 0; @@ -4247,23 +4274,25 @@ int CMerkleTx::GetDepthInMainChain() const return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1); } -int CMerkleTx::GetBlocksToMaturity() const +int CMerkleTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const { if (!IsCoinBase()) return 0; - int chain_depth = GetDepthInMainChain(); + int chain_depth = GetDepthInMainChain(locked_chain); assert(chain_depth >= 0); // coinbase tx should not be conflicted return std::max(0, (COINBASE_MATURITY+1) - chain_depth); } -bool CMerkleTx::IsImmatureCoinBase() const +bool CMerkleTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const { // note GetBlocksToMaturity is 0 for non-coinbase tx - return GetBlocksToMaturity() > 0; + return GetBlocksToMaturity(locked_chain) > 0; } -bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) +bool CWalletTx::AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, const CAmount& nAbsurdFee, CValidationState& state) { + LockAnnotation lock(::cs_main); // Temporary, for AcceptToMemoryPool below. Removed in upcoming commit. + // We must set fInMempool here - while it will be re-set to true by the // entered-mempool callback, if we did not there would be a race where a // user could call sendmoney in a loop and hit spurious out of funds errors diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 74eaa09506..f96798201f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -7,6 +7,7 @@ #define BITCOIN_WALLET_WALLET_H #include <amount.h> +#include <interfaces/chain.h> #include <outputtype.h> #include <policy/feerate.h> #include <streams.h> @@ -33,6 +34,26 @@ #include <utility> #include <vector> +//! Responsible for reading and validating the -wallet arguments and verifying the wallet database. +// This function will perform salvage on the wallet if requested, as long as only one wallet is +// being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). +bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); + +//! Load wallet databases. +bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); + +//! Complete startup of wallets. +void StartWallets(CScheduler& scheduler); + +//! Flush all wallets in preparation for shutdown. +void FlushWallets(); + +//! Stop all wallets. Wallets will be flushed first. +void StopWallets(); + +//! Close all wallets. +void UnloadWallets(); + bool AddWallet(const std::shared_ptr<CWallet>& wallet); bool RemoveWallet(const std::shared_ptr<CWallet>& wallet); bool HasWallets(); @@ -264,22 +285,22 @@ public: * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ - int GetDepthInMainChain() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool IsInMainChain() const EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return GetDepthInMainChain() > 0; } + int GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const; + bool IsInMainChain(interfaces::Chain::Lock& locked_chain) const { return GetDepthInMainChain(locked_chain) > 0; } /** * @return number of blocks to maturity for this transaction: * 0 : is not a coinbase transaction, or is a mature coinbase transaction * >0 : is a coinbase transaction which matures in this many blocks */ - int GetBlocksToMaturity() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const; bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } void setAbandoned() { hashBlock = ABANDON_HASH; } const uint256& GetHash() const { return tx->GetHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } - bool IsImmatureCoinBase() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const; }; //Get the marginal bytes of spending the specified output @@ -458,14 +479,14 @@ public: //! filter decides which addresses will count towards the debit CAmount GetDebit(const isminefilter& filter) const; - CAmount GetCredit(const isminefilter& filter) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - CAmount GetImmatureCredit(bool fUseCache=true) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CAmount GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const; + CAmount GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true) const; // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwallet->cs_wallet)". The // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid // having to resolve the issue of member access into incomplete type CWallet. - CAmount GetAvailableCredit(bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; - CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CAmount GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; + CAmount GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache=true) const; CAmount GetChange() const; // Get the marginal bytes if spending the specified output from this transaction @@ -486,15 +507,15 @@ public: bool IsEquivalentTo(const CWalletTx& tx) const; bool InMempool() const; - bool IsTrusted() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool IsTrusted(interfaces::Chain::Lock& locked_chain) const; int64_t GetTxTime() const; // RelayWalletTransaction may only be called if fBroadcastTransactions! - bool RelayWalletTransaction(CConnman* connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain, CConnman* connman); /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ - bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, const CAmount& nAbsurdFee, CValidationState& state); // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation @@ -676,6 +697,9 @@ private: */ bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + /** Interface for accessing chain state. */ + interfaces::Chain& m_chain; + /** Wallet location which includes wallet name (see WalletLocation). */ WalletLocation m_location; @@ -737,7 +761,7 @@ public: unsigned int nMasterKeyMaxID = 0; /** Construct wallet with specified name and database implementation. */ - CWallet(const WalletLocation& location, std::unique_ptr<WalletDatabase> database) : m_location(location), database(std::move(database)) + CWallet(interfaces::Chain& chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) : m_chain(chain), m_location(location), database(std::move(database)) { } @@ -759,6 +783,9 @@ public: std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet); + /** Interface for accessing chain state. */ + interfaces::Chain& chain() const { return m_chain; } + const CWalletTx* GetWalletTx(const uint256& hash) const; //! check whether we are allowed to upgrade (or already support) to the named feature @@ -767,12 +794,12 @@ public: /** * populate vCoins with vector of available COutputs. */ - void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, const int nMinDepth = 0, const int nMaxDepth = 9999999) const EXCLUSIVE_LOCKS_REQUIRED(cs_main, cs_wallet); + void AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, const int nMinDepth = 0, const int nMaxDepth = 9999999) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Return list of available coins and locked coins grouped by non-change output address. */ - std::map<CTxDestination, std::vector<COutput>> ListCoins() const EXCLUSIVE_LOCKS_REQUIRED(cs_main, cs_wallet); + std::map<CTxDestination, std::vector<COutput>> ListCoins(interfaces::Chain::Lock& locked_chain) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Find non-change parent output. @@ -788,7 +815,7 @@ public: bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const; - bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_main, cs_wallet); + bool IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const; bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -852,7 +879,7 @@ public: bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); - void GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<CTxDestination, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); unsigned int ComputeTimeSmart(const CWalletTx& wtx) const; /** @@ -874,7 +901,7 @@ public: void ReacceptWalletTransactions(); void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override EXCLUSIVE_LOCKS_REQUIRED(cs_main); // ResendWalletTransactionsBefore may only be called if fBroadcastTransactions! - std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + std::vector<uint256> ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime, CConnman* connman); CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const; CAmount GetUnconfirmedBalance() const; CAmount GetImmatureBalance() const; @@ -897,7 +924,7 @@ public: * selected by SelectCoins(); Also create the change output, when needed * @note passing nChangePosInOut as -1 will result in setting a random position */ - bool CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, + bool CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CConnman* connman, CValidationState& state); @@ -956,7 +983,7 @@ public: const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; } std::set<std::set<CTxDestination>> GetAddressGroupings() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - std::map<CTxDestination, CAmount> GetAddressBalances() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + std::map<CTxDestination, CAmount> GetAddressBalances(interfaces::Chain::Lock& locked_chain); std::set<CTxDestination> GetLabelAddresses(const std::string& label) const; @@ -1051,16 +1078,16 @@ public: bool TransactionCanBeAbandoned(const uint256& hashTx) const; /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ - bool AbandonTransaction(const uint256& hashTx); + bool AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx); /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */ bool MarkReplaced(const uint256& originalHash, const uint256& newHash); //! Verify wallet naming and perform salvage on the wallet if required - static bool Verify(const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string); + static bool Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string); /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */ - static std::shared_ptr<CWallet> CreateWalletFromFile(const WalletLocation& location, uint64_t wallet_creation_flags = 0); + static std::shared_ptr<CWallet> CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, uint64_t wallet_creation_flags = 0); /** * Wallet post-init setup diff --git a/src/walletinitinterface.h b/src/walletinitinterface.h index 6f12551273..22aca65990 100644 --- a/src/walletinitinterface.h +++ b/src/walletinitinterface.h @@ -9,6 +9,7 @@ class CScheduler; class CRPCTable; +struct InitInterfaces; class WalletInitInterface { public: @@ -18,20 +19,8 @@ public: virtual void AddWalletOptions() const = 0; /** Check wallet parameter interaction */ virtual bool ParameterInteraction() const = 0; - /** Register wallet RPC*/ - virtual void RegisterRPC(CRPCTable &) const = 0; - /** Verify wallets */ - virtual bool Verify() const = 0; - /** Open wallets*/ - virtual bool Open() const = 0; - /** Start wallets*/ - virtual void Start(CScheduler& scheduler) const = 0; - /** Flush Wallets*/ - virtual void Flush() const = 0; - /** Stop Wallets*/ - virtual void Stop() const = 0; - /** Close wallets */ - virtual void Close() const = 0; + /** Add wallets that should be opened to list of init interfaces. */ + virtual void Construct(InitInterfaces& interfaces) const = 0; virtual ~WalletInitInterface() {} }; diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp index 0e0b745375..66b491427d 100644 --- a/src/zmq/zmqrpc.cpp +++ b/src/zmq/zmqrpc.cpp @@ -5,6 +5,7 @@ #include <zmq/zmqrpc.h> #include <rpc/server.h> +#include <rpc/util.h> #include <zmq/zmqabstractnotifier.h> #include <zmq/zmqnotificationinterface.h> diff --git a/test/functional/example_test.py b/test/functional/example_test.py index 3f15367a75..be3544ee74 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -164,13 +164,13 @@ class ExampleTest(BitcoinTestFramework): self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1 - height = 1 + height = self.nodes[0].getblockcount() for i in range(10): # Use the mininode and blocktools functionality to manually build a block # Calling the generate() rpc is easier, but this allows us to exactly # control the blocks and transactions. - block = create_block(self.tip, create_coinbase(height), self.block_time) + block = create_block(self.tip, create_coinbase(height+1), self.block_time) block.solve() block_message = msg_block(block) # Send message is used to send a P2P message to the node over our P2PInterface diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 628cefb76d..e386915ada 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -824,7 +824,7 @@ class FullBlockTest(BitcoinTestFramework): tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0))) b64a = self.update_block("64a", [tx]) assert_equal(len(b64a.serialize()), MAX_BLOCK_BASE_SIZE + 8) - self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize():') + self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize()') # bitcoind doesn't disconnect us for sending a bloated block, but if we subsequently # resend the header message, it won't send us the getdata message again. Just diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index f84b08a199..302a5ec1cb 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -25,7 +25,6 @@ CLTV_HEIGHT = 1351 # Reject codes that we might receive in this test REJECT_INVALID = 16 -REJECT_OBSOLETE = 17 REJECT_NONSTANDARD = 64 def cltv_invalidate(tx): diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index 492772d5e3..88a9aadc7b 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -30,6 +30,10 @@ class ConfArgsTest(BitcoinTestFramework): self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: + conf.write('server=1\nrpcuser=someuser\nrpcpassword=some#pass') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') + + with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('') # clear def run_test(self): diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index 16272877e7..9cbc1b39bd 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -22,7 +22,6 @@ DERSIG_HEIGHT = 1251 # Reject codes that we might receive in this test REJECT_INVALID = 16 -REJECT_OBSOLETE = 17 REJECT_NONSTANDARD = 64 # A canonical signature consists of: diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py index 0678b1a651..1e0b876593 100755 --- a/test/functional/p2p_invalid_block.py +++ b/test/functional/p2p_invalid_block.py @@ -77,9 +77,9 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block2.vtx.append(tx2) assert_equal(block2.hashMerkleRoot, block2.calc_merkle_root()) assert_equal(orig_hash, block2.rehash()) - assert(block2_orig.vtx != block2.vtx) + assert block2_orig.vtx != block2.vtx - node.p2p.send_blocks_and_test([block2], node, success=False, request_block=False, reject_reason='bad-txns-duplicate') + node.p2p.send_blocks_and_test([block2], node, success=False, reject_reason='bad-txns-duplicate') # Check transactions for duplicate inputs self.log.info("Test duplicate input block.") @@ -89,7 +89,7 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block2_orig.hashMerkleRoot = block2_orig.calc_merkle_root() block2_orig.rehash() block2_orig.solve() - node.p2p.send_blocks_and_test([block2_orig], node, success=False, request_block=False, reject_reason='bad-txns-inputs-duplicate') + node.p2p.send_blocks_and_test([block2_orig], node, success=False, reject_reason='bad-txns-inputs-duplicate') self.log.info("Test very broken block.") @@ -102,7 +102,8 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block3.rehash() block3.solve() - node.p2p.send_blocks_and_test([block3], node, success=False, request_block=False, reject_reason='bad-cb-amount') + node.p2p.send_blocks_and_test([block3], node, success=False, reject_reason='bad-cb-amount') + if __name__ == '__main__': InvalidBlockRequestTest().main() diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py new file mode 100755 index 0000000000..a2d40fab1a --- /dev/null +++ b/test/functional/p2p_invalid_messages.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-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. +"""Test node responses to invalid network messages.""" +import struct + +from test_framework import messages +from test_framework.mininode import P2PDataStore +from test_framework.test_framework import BitcoinTestFramework + + +class msg_unrecognized: + """Nonsensical message. Modeled after similar types in test_framework.messages.""" + + command = b'badmsg' + + def __init__(self, str_data): + self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data + + def serialize(self): + return messages.ser_string(self.str_data) + + def __repr__(self): + return "{}(data={})".format(self.command, self.str_data) + + +class msg_nametoolong(msg_unrecognized): + + command = b'thisnameiswayyyyyyyyytoolong' + + +class InvalidMessagesTest(BitcoinTestFramework): + + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def run_test(self): + """ + 0. Send a bunch of large (4MB) messages of an unrecognized type. Check to see + that it isn't an effective DoS against the node. + + 1. Send an oversized (4MB+) message and check that we're disconnected. + + 2. Send a few messages with an incorrect data size in the header, ensure the + messages are ignored. + + 3. Send an unrecognized message with a command name longer than 12 characters. + + """ + node = self.nodes[0] + self.node = node + node.add_p2p_connection(P2PDataStore()) + conn2 = node.add_p2p_connection(P2PDataStore()) + + msg_limit = 4 * 1000 * 1000 # 4MB, per MAX_PROTOCOL_MESSAGE_LENGTH + valid_data_limit = msg_limit - 5 # Account for the 4-byte length prefix + + # + # 0. + # + # Send as large a message as is valid, ensure we aren't disconnected but + # also can't exhaust resources. + # + msg_at_size = msg_unrecognized("b" * valid_data_limit) + assert len(msg_at_size.serialize()) == msg_limit + + with node.assert_memory_usage_stable(perc_increase_allowed=0.03): + self.log.info( + "Sending a bunch of large, junk messages to test " + "memory exhaustion. May take a bit...") + + # Run a bunch of times to test for memory exhaustion. + for _ in range(80): + node.p2p.send_message(msg_at_size) + + # Check that, even though the node is being hammered by nonsense from one + # connection, it can still service other peers in a timely way. + for _ in range(20): + conn2.sync_with_ping(timeout=2) + + # Peer 1, despite serving up a bunch of nonsense, should still be connected. + self.log.info("Waiting for node to drop junk messages.") + node.p2p.sync_with_ping(timeout=30) + assert node.p2p.is_connected + + # + # 1. + # + # Send an oversized message, ensure we're disconnected. + # + msg_over_size = msg_unrecognized("b" * (valid_data_limit + 1)) + assert len(msg_over_size.serialize()) == (msg_limit + 1) + + with node.assert_debug_log(["Oversized message from peer=0, disconnecting"]): + # An unknown message type (or *any* message type) over + # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. + node.p2p.send_message(msg_over_size) + node.p2p.wait_for_disconnect(timeout=4) + + node.disconnect_p2ps() + conn = node.add_p2p_connection(P2PDataStore()) + conn.wait_for_verack() + + # + # 2. + # + # Send messages with an incorrect data size in the header. + # + actual_size = 100 + msg = msg_unrecognized("b" * actual_size) + + # TODO: handle larger-than cases. I haven't been able to pin down what behavior to expect. + for wrong_size in (2, 77, 78, 79): + self.log.info("Sending a message with incorrect size of {}".format(wrong_size)) + + # Unmodified message should submit okay. + node.p2p.send_and_ping(msg) + + # A message lying about its data size results in a disconnect when the incorrect + # data size is less than the actual size. + # + # TODO: why does behavior change at 78 bytes? + # + node.p2p.send_raw_message(self._tweak_msg_data_size(msg, wrong_size)) + + # For some reason unknown to me, we sometimes have to push additional data to the + # peer in order for it to realize a disconnect. + try: + node.p2p.send_message(messages.msg_ping(nonce=123123)) + except IOError: + pass + + node.p2p.wait_for_disconnect(timeout=10) + node.disconnect_p2ps() + node.add_p2p_connection(P2PDataStore()) + + # + # 3. + # + # Send a message with a too-long command name. + # + node.p2p.send_message(msg_nametoolong("foobar")) + node.p2p.wait_for_disconnect(timeout=4) + + # Node is still up. + conn = node.add_p2p_connection(P2PDataStore()) + conn.sync_with_ping() + + + def _tweak_msg_data_size(self, message, wrong_size): + """ + Return a raw message based on another message but with an incorrect data size in + the message header. + """ + raw_msg = self.node.p2p.build_message(message) + + bad_size_bytes = struct.pack("<I", wrong_size) + num_header_bytes_before_size = 4 + 12 + + # Replace the correct data size in the message with an incorrect one. + raw_msg_with_wrong_size = ( + raw_msg[:num_header_bytes_before_size] + + bad_size_bytes + + raw_msg[(num_header_bytes_before_size + len(bad_size_bytes)):] + ) + assert len(raw_msg) == len(raw_msg_with_wrong_size) + + return raw_msg_with_wrong_size + + + +if __name__ == '__main__': + InvalidMessagesTest().main() diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index be096af892..78d6e78aed 100755 --- a/test/functional/rpc_help.py +++ b/test/functional/rpc_help.py @@ -7,12 +7,18 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error +import os + class HelpRpcTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def run_test(self): + self.test_categories() + self.dump_help() + + def test_categories(self): node = self.nodes[0] # wrong argument count @@ -37,6 +43,15 @@ class HelpRpcTest(BitcoinTestFramework): assert_equal(titles, components) + def dump_help(self): + dump_dir = os.path.join(self.options.tmpdir, 'rpc_help_dump') + os.mkdir(dump_dir) + calls = [line.split(' ', 1)[0] for line in self.nodes[0].help().splitlines() if line and not line.startswith('==')] + for call in calls: + with open(os.path.join(dump_dir, call), 'w', encoding='utf-8') as f: + # Make sure the node can generate the help at runtime without crashing + f.write(self.nodes[0].help(call)) + if __name__ == '__main__': HelpRpcTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index fca910bf64..04d9bb65a6 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -146,6 +146,9 @@ class PSBTTest(BitcoinTestFramework): # Make sure that a psbt with signatures cannot be converted signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex']) assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex']) + assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'], False) + # Unless we allow it to convert and strip signatures + self.nodes[0].converttopsbt(signedtx['hex'], True) # Explicitly allow converting non-empty txs new_psbt = self.nodes[0].converttopsbt(rawtx['hex']) @@ -207,6 +210,13 @@ class PSBTTest(BitcoinTestFramework): assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE assert_equal(decoded_psbt["tx"]["locktime"], 0) + # Regression test for 14473 (mishandling of already-signed witness transaction): + psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}]) + complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"]) + double_processed_psbt = self.nodes[0].walletprocesspsbt(complete_psbt["psbt"]) + assert_equal(complete_psbt, double_processed_psbt) + # We don't care about the decode result, but decoding must succeed. + self.nodes[0].decodepsbt(double_processed_psbt["psbt"]) # BIP 174 Test Vectors @@ -269,6 +279,10 @@ class PSBTTest(BitcoinTestFramework): self.test_utxo_conversion() + # Test that psbts with p2pkh outputs are created properly + p2pkh = self.nodes[0].getnewaddress(address_type='legacy') + psbt = self.nodes[1].walletcreatefundedpsbt([], [{p2pkh : 1}], 0, {"includeWatching" : True}, True) + self.nodes[0].decodepsbt(psbt['psbt']) if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 92acbb9a09..c72cb8835c 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -35,7 +35,6 @@ MY_VERSION = 70014 # past bip-31 for ping/pong MY_SUBVERSION = b"/python-mininode-tester:0.0.3/" MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37) -MAX_INV_SZ = 50000 MAX_LOCATOR_SZ = 101 MAX_BLOCK_BASE_SIZE = 1000000 @@ -58,9 +57,6 @@ MSG_TYPE_MASK = 0xffffffff >> 2 def sha256(s): return hashlib.new('sha256', s).digest() -def ripemd160(s): - return hashlib.new('ripemd160', s).digest() - def hash256(s): return sha256(sha256(s)) @@ -887,13 +883,12 @@ class BlockTransactions: class CPartialMerkleTree: - __slots__ = ("fBad", "nTransactions", "vBits", "vHash") + __slots__ = ("nTransactions", "vBits", "vHash") def __init__(self): self.nTransactions = 0 self.vHash = [] self.vBits = [] - self.fBad = False def deserialize(self, f): self.nTransactions = struct.unpack("<i", f.read(4))[0] diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index 91fde136de..388c123055 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -207,10 +207,13 @@ class P2PConnection(asyncio.Protocol): This method takes a P2P payload, builds the P2P header and adds the message to the send buffer to be sent over the socket.""" + tmsg = self.build_message(message) + self._log_message("send", message) + return self.send_raw_message(tmsg) + + def send_raw_message(self, raw_message_bytes): if not self.is_connected: raise IOError('Not connected') - self._log_message("send", message) - tmsg = self._build_message(message) def maybe_write(): if not self._transport: @@ -220,12 +223,12 @@ class P2PConnection(asyncio.Protocol): # Python 3.4 versions. if hasattr(self._transport, 'is_closing') and self._transport.is_closing(): return - self._transport.write(tmsg) + self._transport.write(raw_message_bytes) NetworkThread.network_event_loop.call_soon_threadsafe(maybe_write) # Class utility methods - def _build_message(self, message): + def build_message(self, message): """Build a serialized P2P message""" command = message.command data = message.serialize() @@ -346,7 +349,7 @@ class P2PInterface(P2PConnection): self.send_message(msg_pong(message.nonce)) def on_verack(self, message): - self.verack_received = True + pass def on_version(self, message): assert message.nVersion >= MIN_VERSION_SUPPORTED, "Version {} received. Test framework only supports versions greater than {}".format(message.nVersion, MIN_VERSION_SUPPORTED) @@ -409,9 +412,9 @@ class P2PInterface(P2PConnection): # Message sending helper functions - def send_and_ping(self, message): + def send_and_ping(self, message, timeout=60): self.send_message(message) - self.sync_with_ping() + self.sync_with_ping(timeout=timeout) # Sync up with the node def sync_with_ping(self, timeout=60): diff --git a/test/functional/test_framework/socks5.py b/test/functional/test_framework/socks5.py index dd0f209268..a21c864e75 100644 --- a/test/functional/test_framework/socks5.py +++ b/test/functional/test_framework/socks5.py @@ -54,10 +54,9 @@ class Socks5Command(): return 'Socks5Command(%s,%s,%s,%s,%s,%s)' % (self.cmd, self.atyp, self.addr, self.port, self.username, self.password) class Socks5Connection(): - def __init__(self, serv, conn, peer): + def __init__(self, serv, conn): self.serv = serv self.conn = conn - self.peer = peer def handle(self): """Handle socks5 request according to RFC192.""" @@ -137,9 +136,9 @@ class Socks5Server(): def run(self): while self.running: - (sockconn, peer) = self.s.accept() + (sockconn, _) = self.s.accept() if self.running: - conn = Socks5Connection(self, sockconn, peer) + conn = Socks5Connection(self, sockconn) thread = threading.Thread(None, conn.handle) thread.daemon = True thread.start() diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 3a6107bb37..9dcc0e6d0e 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -115,6 +115,25 @@ class TestNode(): ] return PRIV_KEYS[self.index] + def get_mem_rss(self): + """Get the memory usage (RSS) per `ps`. + + Returns None if `ps` is unavailable. + """ + assert self.running + + try: + return int(subprocess.check_output( + ["ps", "h", "-o", "rss", "{}".format(self.process.pid)], + stderr=subprocess.DEVNULL).split()[-1]) + + # Avoid failing on platforms where ps isn't installed. + # + # We could later use something like `psutils` to work across platforms. + except (FileNotFoundError, subprocess.SubprocessError): + self.log.exception("Unable to get memory usage") + return None + def _node_msg(self, msg: str) -> str: """Return a modified msg that identifies this node by its index as a debugging aid.""" return "[node %d] %s" % (self.index, msg) @@ -271,6 +290,29 @@ class TestNode(): if re.search(re.escape(expected_msg), log, flags=re.MULTILINE) is None: self._raise_assertion_error('Expected message "{}" does not partially match log:\n\n{}\n\n'.format(expected_msg, print_log)) + @contextlib.contextmanager + def assert_memory_usage_stable(self, perc_increase_allowed=0.03): + """Context manager that allows the user to assert that a node's memory usage (RSS) + hasn't increased beyond some threshold percentage. + """ + before_memory_usage = self.get_mem_rss() + + yield + + after_memory_usage = self.get_mem_rss() + + if not (before_memory_usage and after_memory_usage): + self.log.warning("Unable to detect memory usage (RSS) - skipping memory check.") + return + + perc_increase_memory_usage = (after_memory_usage / before_memory_usage) - 1 + + if perc_increase_memory_usage > perc_increase_allowed: + self._raise_assertion_error( + "Memory usage increased over threshold of {:.3f}% from {} to {} ({:.3f}%)".format( + perc_increase_allowed * 100, before_memory_usage, after_memory_usage, + perc_increase_memory_usage * 100)) + def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, match=ErrorMatch.FULL_TEXT, *args, **kwargs): """Attempt to start the node and expect it to raise an error. diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 8cbc9655c6..5541b44690 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -136,6 +136,7 @@ BASE_SCRIPTS = [ 'mining_prioritisetransaction.py', 'p2p_invalid_locator.py', 'p2p_invalid_block.py', + 'p2p_invalid_messages.py', 'p2p_invalid_tx.py', 'feature_assumevalid.py', 'example_test.py', @@ -154,6 +155,7 @@ BASE_SCRIPTS = [ 'feature_nulldummy.py', 'mempool_accept.py', 'wallet_import_rescan.py', + 'wallet_import_with_label.py', 'rpc_bind.py --ipv4', 'rpc_bind.py --ipv6', 'rpc_bind.py --nonloopback', @@ -211,7 +213,7 @@ def main(): epilog=''' Help text and arguments for individual test script:''', formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('--combinedlogslen', '-c', type=int, default=0, help='print a combined log (of length n lines) from all test nodes and test framework to the console on failure.') + parser.add_argument('--combinedlogslen', '-c', type=int, default=0, metavar='n', help='On failure, print a log (of length n lines) to the console, combined from the test framework and all test nodes.') parser.add_argument('--coverage', action='store_true', help='generate a basic coverage report for the RPC interface') parser.add_argument('--ci', action='store_true', help='Run checks and code that are usually only enabled in a continuous integration environment') parser.add_argument('--exclude', '-x', help='specify a comma-separated-list of scripts to exclude.') @@ -364,7 +366,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= tmpdir=tmpdir, test_list=test_list, flags=flags, - timeout_duration=20 * 60 if runs_ci else float('inf'), # in seconds + timeout_duration=40 * 60 if runs_ci else float('inf'), # in seconds ) start_time = time.time() test_results = [] @@ -634,7 +636,7 @@ class RPCCoverage(): with open(coverage_ref_filename, 'r', encoding="utf8") as coverage_ref_file: all_cmds.update([line.strip() for line in coverage_ref_file.readlines()]) - for root, dirs, files in os.walk(self.dir): + for root, _, files in os.walk(self.dir): for filename in files: if filename.startswith(coverage_file_prefix): coverage_filenames.add(os.path.join(root, filename)) diff --git a/test/functional/wallet_import_with_label.py b/test/functional/wallet_import_with_label.py new file mode 100755 index 0000000000..95acaa752e --- /dev/null +++ b/test/functional/wallet_import_with_label.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the behavior of RPC importprivkey on set and unset labels of +addresses. + +It tests different cases in which an address is imported with importaddress +with or without a label and then its private key is imported with importprivkey +with and without a label. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +class ImportWithLabel(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + """Main test logic""" + + self.log.info( + "Test importaddress with label and importprivkey without label." + ) + self.log.info("Import a watch-only address with a label.") + address = self.nodes[0].getnewaddress() + label = "Test Label" + self.nodes[1].importaddress(address, label) + address_assert = self.nodes[1].getaddressinfo(address) + + assert_equal(address_assert["iswatchonly"], True) + assert_equal(address_assert["ismine"], False) + assert_equal(address_assert["label"], label) + + self.log.info( + "Import the watch-only address's private key without a " + "label and the address should keep its label." + ) + priv_key = self.nodes[0].dumpprivkey(address) + self.nodes[1].importprivkey(priv_key) + + assert_equal(label, self.nodes[1].getaddressinfo(address)["label"]) + + self.log.info( + "Test importaddress without label and importprivkey with label." + ) + self.log.info("Import a watch-only address without a label.") + address2 = self.nodes[0].getnewaddress() + self.nodes[1].importaddress(address2) + address_assert2 = self.nodes[1].getaddressinfo(address2) + + assert_equal(address_assert2["iswatchonly"], True) + assert_equal(address_assert2["ismine"], False) + assert_equal(address_assert2["label"], "") + + self.log.info( + "Import the watch-only address's private key with a " + "label and the address should have its label updated." + ) + priv_key2 = self.nodes[0].dumpprivkey(address2) + label2 = "Test Label 2" + self.nodes[1].importprivkey(priv_key2, label2) + + assert_equal(label2, self.nodes[1].getaddressinfo(address2)["label"]) + + self.log.info("Test importaddress with label and importprivkey with label.") + self.log.info("Import a watch-only address with a label.") + address3 = self.nodes[0].getnewaddress() + label3_addr = "Test Label 3 for importaddress" + self.nodes[1].importaddress(address3, label3_addr) + address_assert3 = self.nodes[1].getaddressinfo(address3) + + assert_equal(address_assert3["iswatchonly"], True) + assert_equal(address_assert3["ismine"], False) + assert_equal(address_assert3["label"], label3_addr) + + self.log.info( + "Import the watch-only address's private key with a " + "label and the address should have its label updated." + ) + priv_key3 = self.nodes[0].dumpprivkey(address3) + label3_priv = "Test Label 3 for importprivkey" + self.nodes[1].importprivkey(priv_key3, label3_priv) + + assert_equal(label3_priv, self.nodes[1].getaddressinfo(address3)["label"]) + + self.log.info( + "Test importprivkey won't label new dests with the same " + "label as others labeled dests for the same key." + ) + self.log.info("Import a watch-only legacy address with a label.") + address4 = self.nodes[0].getnewaddress() + label4_addr = "Test Label 4 for importaddress" + self.nodes[1].importaddress(address4, label4_addr) + address_assert4 = self.nodes[1].getaddressinfo(address4) + + assert_equal(address_assert4["iswatchonly"], True) + assert_equal(address_assert4["ismine"], False) + assert_equal(address_assert4["label"], label4_addr) + + self.log.info("Asserts address has no embedded field with dests.") + + assert_equal(address_assert4.get("embedded"), None) + + self.log.info( + "Import the watch-only address's private key without a " + "label and new destinations for the key should have an " + "empty label while the 'old' destination should keep " + "its label." + ) + priv_key4 = self.nodes[0].dumpprivkey(address4) + self.nodes[1].importprivkey(priv_key4) + address_assert4 = self.nodes[1].getaddressinfo(address4) + + assert address_assert4.get("embedded") + + bcaddress_assert = self.nodes[1].getaddressinfo( + address_assert4["embedded"]["address"] + ) + + assert_equal(address_assert4["label"], label4_addr) + assert_equal(bcaddress_assert["label"], "") + + self.stop_nodes() + + +if __name__ == "__main__": + ImportWithLabel().main() diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh new file mode 100755 index 0000000000..3341f794f9 --- /dev/null +++ b/test/lint/lint-python-dead-code.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Find dead Python code. + +export LC_ALL=C + +if ! command -v vulture > /dev/null; then + echo "Skipping Python dead code linting since vulture is not installed. Install by running \"pip3 install vulture\"" + exit 0 +fi + +vulture \ + --min-confidence 60 \ + --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,get_ecdh_key,get_privkey,is_compressed,is_fullyvalid,msg_generic,on_*,optionxform,restype,set_privkey" \ + $(git ls-files -- "*.py" ":(exclude)contrib/") |