diff options
129 files changed, 3389 insertions, 2618 deletions
diff --git a/build_msvc/README.md b/build_msvc/README.md index 88b1f514bd..0c668b2838 100644 --- a/build_msvc/README.md +++ b/build_msvc/README.md @@ -12,7 +12,7 @@ Quick Start The minimal steps required to build Bitcoin Core with the msbuild toolchain are below. More detailed instructions are contained in the following sections. ``` -vcpkg install --triplet x64-windows-static boost-filesystem boost-signals2 boost-test libevent openssl zeromq berkeleydb rapidcheck double-conversion +vcpkg install --triplet x64-windows-static boost-filesystem boost-multi-index boost-signals2 boost-test boost-thread libevent openssl zeromq berkeleydb rapidcheck double-conversion py -3 build_msvc\msvc-autogen.py msbuild /m build_msvc\bitcoin.sln /p:Platform=x64 /p:Configuration=Release /t:build ``` @@ -40,33 +40,27 @@ The [external dependencies](https://github.com/bitcoin/bitcoin/blob/master/doc/d Qt --------------------- -All the Bitcoin Core applications are configured to build with static linking. In order to build the Bitcoin Core Qt applications a static build of Qt is required. +In order to build the Bitcoin Core a static build of Qt is required. The runtime library version (e.g. v141, v142) and platform type (x86 or x64) must also match. -The runtime library version (e.g. v141, v142) and platform type (x86 or x64) must also match. OpenSSL must also be linked into the Qt binaries in order to provide full functionality of the Bitcoin Core Qt programs. An example of the configure command to build Qtv5.9.7 locally to link with Bitcoin Core is shown below (adjust paths accordingly), note it can be expected that the configure and subsequent build will fail numerous times until dependency issues are resolved. +A prebuilt version of Qt can be downloaded from [here](https://github.com/sipsorcery/qt_win_binary/releases). Please be aware this download is NOT an officially sanctioned Bitcoin Core distribution and is provided for developer convenience. It should NOT be used for builds that will be used in a production environment or with real funds. -```` -..\Qtv5.9.7_src\configure -developer-build -confirm-license -debug-and-release -opensource -platform win32-msvc -opengl desktop -no-shared -static -no-static-runtime -mp -qt-zlib -qt-pcre -qt-libpng -ltcg -make libs -make tools -no-libjpeg -nomake examples -no-compile-examples -no-dbus -no-libudev -no-qml-debug -no-icu -no-gtk -no-opengles3 -no-angle -no-sql-sqlite -no-sql-odbc -no-sqlite -no-libudev -skip qt3d -skip qtactiveqt -skip qtandroidextras -skip qtcanvas3d -skip qtcharts -skip qtconnectivity -skip qtdatavis3d -skip qtdeclarative -skip qtdoc -skip qtgamepad -skip qtgraphicaleffects -skip qtimageformats -skip qtlocation -skip qtmacextras -skip qtmultimedia -skip qtnetworkauth -skip qtpurchasing -skip qtquickcontrols -skip qtquickcontrols2 -skip qtscript -skip qtscxml -skip qtsensors -skip qtserialbus -skip qtserialport -skip qtspeech -skip qtvirtualkeyboard -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtx11extras -skip qtxmlpatterns -nomake tests -openssl-linked -IC:\Dev\github\vcpkg\installed\x64-windows-static\include -LC:\Dev\github\vcpkg\installed\x64-windows-static\lib OPENSSL_LIBS="-llibeay32 -lssleay32 -lgdi32 -luser32 -lwsock32 -ladvapi32" -prefix C:\Qt5.9.7_ssl_x64_static_vs2017 -```` - -A prebuilt version for x64 and Visual C++ runtime v141 (Visual Studio 2017) can be downloaded from [here](https://github.com/sipsorcery/qt_win_binary/releases). Please be aware this download is NOT an officially sanctioned Bitcoin Core distribution and is provided for developer convenience. It should NOT be used for builds that will be used in a production environment or with real funds. - -To build Bitcoin Core without Qt unload or disable the bitcoin-qt, libbitcoin_qt and test_bitcoin-qt projects. +To build Bitcoin Core without Qt unload or disable the `bitcoin-qt`, `libbitcoin_qt` and `test_bitcoin-qt` projects. Building --------------------- The instructions below use `vcpkg` to install the dependencies. -- Clone `vcpkg` from the [github repository](https://github.com/Microsoft/vcpkg) and install as per the instructions in the main README.md. +- Install [`vcpkg`](https://github.com/Microsoft/vcpkg). - Install the required packages (replace x64 with x86 as required): ``` - PS >.\vcpkg install --triplet x64-windows-static boost-filesystem boost-signals2 boost-test libevent openssl zeromq berkeleydb rapidcheck double-conversion +PS >.\vcpkg install --triplet x64-windows-static boost-filesystem boost-multi-index boost-signals2 boost-test boost-thread libevent openssl zeromq berkeleydb rapidcheck double-conversion ``` -- Use Python to generate *.vcxproj from Makefile +- Use Python to generate `*.vcxproj` from Makefile ``` - PS >py -3 msvc-autogen.py +PS >py -3 msvc-autogen.py ``` - An optional step is to adjust the settings in the build_msvc directory and the common.init.vcxproj file. This project file contains settings that are common to all projects such as the runtime library version and target Windows SDK version. The Qt directories can also be set. diff --git a/ci/retry/README.md b/ci/retry/README.md index 983a498070..1b03c652db 100644 --- a/ci/retry/README.md +++ b/ci/retry/README.md @@ -32,8 +32,8 @@ Help: -v, --verbose Verbose output -t, --tries=# Set max retries: Default 10 -s, --sleep=secs Constant sleep amount (seconds) - -m, --min=secs Exponenetial Backoff: minimum sleep amount (seconds): Default 0.3 - -x, --max=secs Exponenetial Backoff: maximum sleep amount (seconds): Default 60 + -m, --min=secs Exponential Backoff: minimum sleep amount (seconds): Default 0.3 + -x, --max=secs Exponential Backoff: maximum sleep amount (seconds): Default 60 -f, --fail="script +cmds" Fail Script: run in case of final failure ### Examples diff --git a/ci/retry/retry b/ci/retry/retry index 0e5f6e9701..3c06519dbd 100755 --- a/ci/retry/retry +++ b/ci/retry/retry @@ -17,7 +17,7 @@ __log_out() { echo "$1" 1>&2 } -# Paramters: max_tries min_sleep max_sleep constant_sleep fail_script EXECUTION_COMMAND +# Parameters: max_tries min_sleep max_sleep constant_sleep fail_script EXECUTION_COMMAND retry() { local max_tries="$1"; shift @@ -83,8 +83,8 @@ Usage: $retry [options] -- execute command -v, --verbose Verbose output -t, --tries=# Set max retries: Default 10 -s, --sleep=secs Constant sleep amount (seconds) - -m, --min=secs Exponenetial Backoff: minimum sleep amount (seconds): Default 0.3 - -x, --max=secs Exponenetial Backoff: maximum sleep amount (seconds): Default 60 + -m, --min=secs Exponential Backoff: minimum sleep amount (seconds): Default 0.3 + -x, --max=secs Exponential Backoff: maximum sleep amount (seconds): Default 60 -f, --fail="script +cmds" Fail Script: run in case of final failure EOF } diff --git a/contrib/bitcoin-cli.bash-completion b/contrib/bitcoin-cli.bash-completion index f4cac84182..f7f12a2773 100644 --- a/contrib/bitcoin-cli.bash-completion +++ b/contrib/bitcoin-cli.bash-completion @@ -17,13 +17,6 @@ _bitcoin_rpc() { $bitcoin_cli "${rpcargs[@]}" "$@" } -# Add wallet accounts to COMPREPLY -_bitcoin_accounts() { - local accounts - accounts=$(_bitcoin_rpc listaccounts | awk -F '"' '{ print $2 }') - COMPREPLY=( "${COMPREPLY[@]}" $( compgen -W "$accounts" -- "$cur" ) ) -} - _bitcoin_cli() { local cur prev words=() cword local bitcoin_cli @@ -60,10 +53,9 @@ _bitcoin_cli() { if ((cword > 3)); then case ${words[cword-3]} in addmultisigaddress) - _bitcoin_accounts return 0 ;; - getbalance|gettxout|importaddress|importpubkey|importprivkey|listreceivedbyaccount|listreceivedbyaddress|listsinceblock) + getbalance|gettxout|importaddress|importpubkey|importprivkey|listreceivedbyaddress|listsinceblock) COMPREPLY=( $( compgen -W "true false" -- "$cur" ) ) return 0 ;; @@ -80,14 +72,10 @@ _bitcoin_cli() { COMPREPLY=( $( compgen -W "add remove" -- "$cur" ) ) return 0 ;; - fundrawtransaction|getblock|getblockheader|getmempoolancestors|getmempooldescendants|getrawtransaction|gettransaction|listaccounts|listreceivedbyaccount|listreceivedbyaddress|sendrawtransaction) + fundrawtransaction|getblock|getblockheader|getmempoolancestors|getmempooldescendants|getrawtransaction|gettransaction|listreceivedbyaddress|sendrawtransaction) COMPREPLY=( $( compgen -W "true false" -- "$cur" ) ) return 0 ;; - move|setaccount) - _bitcoin_accounts - return 0 - ;; esac fi @@ -96,12 +84,11 @@ _bitcoin_cli() { _filedir return 0 ;; - getaddednodeinfo|getrawmempool|lockunspent|setgenerate) + getaddednodeinfo|getrawmempool|lockunspent) COMPREPLY=( $( compgen -W "true false" -- "$cur" ) ) return 0 ;; - getaccountaddress|getaddressesbyaccount|getbalance|getnewaddress|getreceivedbyaccount|listtransactions|move|sendfrom|sendmany) - _bitcoin_accounts + getbalance|getnewaddress|listtransactions|sendmany) return 0 ;; esac diff --git a/contrib/gitian-descriptors/gitian-win.yml b/contrib/gitian-descriptors/gitian-win.yml index d600af84fb..1c3d6a8dc8 100644 --- a/contrib/gitian-descriptors/gitian-win.yml +++ b/contrib/gitian-descriptors/gitian-win.yml @@ -145,10 +145,7 @@ script: | make ${MAKEOPTS} -C src check-security make deploy make install DESTDIR=${INSTALLPATH} - ( - SETUP_EXE="$(basename "$(echo ./*-setup.exe)")" - cp -f "$SETUP_EXE" "${OUTDIR}/${SETUP_EXE/%-setup.exe/-setup-unsigned.exe}" - ) + cp -f --target-directory="${OUTDIR}" ./bitcoin-*-setup-unsigned.exe cd installed mv ${DISTNAME}/bin/*.dll ${DISTNAME}/lib/ find . -name "lib*.la" -delete diff --git a/doc/build-windows.md b/doc/build-windows.md index f8095f6a65..bbff638b90 100644 --- a/doc/build-windows.md +++ b/doc/build-windows.md @@ -8,18 +8,18 @@ The options known to work for building Bitcoin Core on Windows are: * On Linux, using the [Mingw-w64](https://mingw-w64.org/doku.php) cross compiler tool chain. Ubuntu Bionic 18.04 is required and is the platform used to build the Bitcoin Core Windows release binaries. * On Windows, using [Windows -Subsystem for Linux (WSL)](https://msdn.microsoft.com/commandline/wsl/about) and the Mingw-w64 cross compiler tool chain. +Subsystem for Linux (WSL)](https://docs.microsoft.com/windows/wsl/about) and the Mingw-w64 cross compiler tool chain. +* On Windows, using a native compiler tool chain such as [Visual Studio](https://www.visualstudio.com). Other options which may work, but which have not been extensively tested are (please contribute instructions): -* On Windows, using a POSIX compatibility layer application such as [cygwin](http://www.cygwin.com/) or [msys2](http://www.msys2.org/). -* On Windows, using a native compiler tool chain such as [Visual Studio](https://www.visualstudio.com). +* On Windows, using a POSIX compatibility layer application such as [cygwin](https://www.cygwin.com/) or [msys2](https://www.msys2.org/). Installing Windows Subsystem for Linux --------------------------------------- With Windows 10, Microsoft has released a new feature named the [Windows -Subsystem for Linux (WSL)](https://msdn.microsoft.com/commandline/wsl/about). This +Subsystem for Linux (WSL)](https://docs.microsoft.com/windows/wsl/about). This feature allows you to run a bash shell directly on Windows in an Ubuntu-based environment. Within this environment you can cross compile for Windows without the need for a separate Linux VM or server. Note that while WSL can be installed with @@ -28,7 +28,7 @@ tested with Ubuntu. This feature is not supported in versions of Windows prior to Windows 10 or on Windows Server SKUs. In addition, it is available [only for 64-bit versions of -Windows](https://msdn.microsoft.com/en-us/commandline/wsl/install_guide). +Windows](https://docs.microsoft.com/windows/wsl/install-win10). Full instructions to install WSL are available on the above link. To install WSL on Windows 10 with Fall Creators Update installed (version >= 16215.0) do the following: diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 9cf4b4b075..1a7ce91ca6 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -640,6 +640,28 @@ Strings and formatting - *Rationale*: Bitcoin Core uses tinyformat, which is type safe. Leave them out to avoid confusion. +- Use `.c_str()` sparingly. Its only valid use is to pass C++ strings to C functions that take NULL-terminated + strings. + + - Do not use it when passing a sized array (so along with `.size()`). Use `.data()` instead to get a pointer + to the raw data. + + - *Rationale*: Although this is guaranteed to be safe starting with C++11, `.data()` communicates the intent better. + + - Do not use it when passing strings to `tfm::format`, `strprintf`, `LogPrint[f]`. + + - *Rationale*: This is redundant. Tinyformat handles strings. + + - Do not use it to convert to `QString`. Use `QString::fromStdString()`. + + - *Rationale*: Qt has build-in functionality for converting their string + type from/to C++. No need to roll your own. + + - In cases where do you call `.c_str()`, you might want to additionally check that the string does not contain embedded '\0' characters, because + it will (necessarily) truncate the string. This might be used to hide parts of the string from logging or to circumvent + checks. If a use of strings is sensitive to this, take care to check the string for embedded NULL characters first + and reject it if there are any (see `ParsePrechecks` in `strencodings.cpp` for an example). + Shadowing -------------- diff --git a/doc/fuzzing.md b/doc/fuzzing.md index 3dc6be8b86..50e9251b8d 100644 --- a/doc/fuzzing.md +++ b/doc/fuzzing.md @@ -77,13 +77,13 @@ will print an error and suggestion if so. ## libFuzzer -A recent version of `clang`, the address sanitizer and libFuzzer is needed (all +A recent version of `clang`, the address/undefined sanitizers (ASan/UBSan) and libFuzzer is needed (all found in the `compiler-rt` runtime libraries package). To build all fuzz targets with libFuzzer, run ``` -./configure --disable-ccache --enable-fuzz --with-sanitizers=fuzzer,address CC=clang CXX=clang++ +./configure --disable-ccache --enable-fuzz --with-sanitizers=fuzzer,address,undefined CC=clang CXX=clang++ make ``` diff --git a/share/setup.nsi.in b/share/setup.nsi.in index e9aa1f2b73..649483c732 100644 --- a/share/setup.nsi.in +++ b/share/setup.nsi.in @@ -48,7 +48,7 @@ Var StartMenuGroup !insertmacro MUI_LANGUAGE English # Installer attributes -OutFile @abs_top_srcdir@/@PACKAGE_TARNAME@-@PACKAGE_VERSION@-win@WINDOWS_BITS@-setup.exe +OutFile @abs_top_srcdir@/@PACKAGE_TARNAME@-@PACKAGE_VERSION@-win@WINDOWS_BITS@-setup-unsigned.exe !if "@WINDOWS_BITS@" == "64" InstallDir $PROGRAMFILES64\Bitcoin !else diff --git a/src/Makefile.am b/src/Makefile.am index 82cc19d57c..619f968bc9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -157,6 +157,7 @@ BITCOIN_CORE_H = \ netmessagemaker.h \ node/coin.h \ node/coinstats.h \ + node/context.h \ node/psbt.h \ node/transaction.h \ noui.h \ @@ -236,6 +237,7 @@ BITCOIN_CORE_H = \ wallet/load.h \ wallet/psbtwallet.h \ wallet/rpcwallet.h \ + wallet/scriptpubkeyman.h \ wallet/wallet.h \ wallet/walletdb.h \ wallet/wallettool.h \ @@ -284,6 +286,7 @@ libbitcoin_server_a_SOURCES = \ net_processing.cpp \ node/coin.cpp \ node/coinstats.cpp \ + node/context.cpp \ node/psbt.cpp \ node/transaction.cpp \ noui.cpp \ @@ -339,11 +342,11 @@ libbitcoin_wallet_a_SOURCES = \ wallet/db.cpp \ wallet/feebumper.cpp \ wallet/fees.cpp \ - wallet/ismine.cpp \ wallet/load.cpp \ wallet/psbtwallet.cpp \ wallet/rpcdump.cpp \ wallet/rpcwallet.cpp \ + wallet/scriptpubkeyman.cpp \ wallet/wallet.cpp \ wallet/walletdb.cpp \ wallet/walletutil.cpp \ diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 38143e32b9..fbcab86d8f 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -30,6 +30,7 @@ bench_bench_bitcoin_SOURCES = \ bench/gcs_filter.cpp \ bench/merkle_root.cpp \ bench/mempool_eviction.cpp \ + bench/mempool_stress.cpp \ bench/rpc_blockchain.cpp \ bench/rpc_mempool.cpp \ bench/util_time.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 019e832cc6..c3f0120005 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -22,6 +22,7 @@ FUZZ_TARGETS = \ test/fuzz/inv_deserialize \ test/fuzz/messageheader_deserialize \ test/fuzz/netaddr_deserialize \ + test/fuzz/parse_iso8601 \ test/fuzz/script \ test/fuzz/script_flags \ test/fuzz/service_deserialize \ @@ -269,6 +270,12 @@ test_fuzz_netaddr_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_netaddr_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_netaddr_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) +test_fuzz_parse_iso8601_SOURCES = $(FUZZ_SUITE) test/fuzz/parse_iso8601.cpp +test_fuzz_parse_iso8601_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_parse_iso8601_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_parse_iso8601_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_parse_iso8601_LDADD = $(FUZZ_SUITE_LD_COMMON) + test_fuzz_script_SOURCES = $(FUZZ_SUITE) test/fuzz/script.cpp test_fuzz_script_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) test_fuzz_script_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) diff --git a/src/banman.h b/src/banman.h index a1a00309dd..9d45bf0559 100644 --- a/src/banman.h +++ b/src/banman.h @@ -66,5 +66,4 @@ private: const int64_t m_default_ban_time; }; -extern std::unique_ptr<BanMan> g_banman; #endif diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index 157f936a95..2f47398d99 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -38,8 +38,8 @@ static void AssembleBlock(benchmark::State& state) LOCK(::cs_main); // Required for ::AcceptToMemoryPool. for (const auto& txr : txs) { - CValidationState state; - bool ret{::AcceptToMemoryPool(::mempool, state, txr, nullptr /* pfMissingInputs */, nullptr /* plTxnReplaced */, false /* bypass_limits */, /* nAbsurdFee */ 0)}; + TxValidationState state; + bool ret{::AcceptToMemoryPool(::mempool, state, txr, nullptr /* plTxnReplaced */, false /* bypass_limits */, /* nAbsurdFee */ 0)}; assert(ret); } } diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index 4b13381e16..edf43bd4dc 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -42,7 +42,7 @@ static void DeserializeAndCheckBlockTest(benchmark::State& state) bool rewound = stream.Rewind(benchmark::data::block413567.size()); assert(rewound); - CValidationState validationState; + BlockValidationState validationState; bool checked = CheckBlock(block, validationState, chainParams->GetConsensus()); assert(checked); } diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index f2ab03e20e..29a145bfe6 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -4,6 +4,7 @@ #include <bench/bench.h> #include <interfaces/chain.h> +#include <node/context.h> #include <wallet/coinselection.h> #include <wallet/wallet.h> @@ -28,7 +29,8 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<st // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) static void CoinSelection(benchmark::State& state) { - auto chain = interfaces::MakeChain(); + NodeContext node; + auto chain = interfaces::MakeChain(node); const CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); std::vector<std::unique_ptr<CWalletTx>> wtxs; LOCK(wallet.cs_wallet); @@ -60,7 +62,8 @@ static void CoinSelection(benchmark::State& state) } typedef std::set<CInputCoin> CoinSet; -static auto testChain = interfaces::MakeChain(); +static NodeContext testNode; +static auto testChain = interfaces::MakeChain(testNode); static const CWallet testWallet(testChain.get(), WalletLocation(), WalletDatabase::CreateDummy()); std::vector<std::unique_ptr<CWalletTx>> wtxn; diff --git a/src/bench/duplicate_inputs.cpp b/src/bench/duplicate_inputs.cpp index 6cfa3750d6..a783370b4e 100644 --- a/src/bench/duplicate_inputs.cpp +++ b/src/bench/duplicate_inputs.cpp @@ -54,7 +54,7 @@ static void DuplicateInputs(benchmark::State& state) block.hashMerkleRoot = BlockMerkleRoot(block); while (state.KeepRunning()) { - CValidationState cvstate{}; + BlockValidationState cvstate{}; assert(!CheckBlock(block, cvstate, chainparams.GetConsensus(), false, false)); assert(cvstate.GetRejectReason() == "bad-txns-inputs-duplicate"); } diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp new file mode 100644 index 0000000000..a42ffaae62 --- /dev/null +++ b/src/bench/mempool_stress.cpp @@ -0,0 +1,93 @@ +// Copyright (c) 2011-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <bench/bench.h> +#include <policy/policy.h> +#include <txmempool.h> + +#include <vector> + +static void AddTx(const CTransactionRef& tx, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) +{ + int64_t nTime = 0; + unsigned int nHeight = 1; + bool spendsCoinbase = false; + unsigned int sigOpCost = 4; + LockPoints lp; + pool.addUnchecked(CTxMemPoolEntry(tx, 1000, nTime, nHeight, spendsCoinbase, sigOpCost, lp)); +} + +struct Available { + CTransactionRef ref; + size_t vin_left{0}; + size_t tx_count; + Available(CTransactionRef& ref, size_t tx_count) : ref(ref), tx_count(tx_count){} + Available& operator=(Available other) { + ref = other.ref; + vin_left = other.vin_left; + tx_count = other.tx_count; + return *this; + } +}; + +static void ComplexMemPool(benchmark::State& state) +{ + FastRandomContext det_rand{true}; + std::vector<Available> available_coins; + std::vector<CTransactionRef> ordered_coins; + // Create some base transactions + size_t tx_counter = 1; + for (auto x = 0; x < 100; ++x) { + CMutableTransaction tx = CMutableTransaction(); + tx.vin.resize(1); + tx.vin[0].scriptSig = CScript() << CScriptNum(tx_counter); + tx.vin[0].scriptWitness.stack.push_back(CScriptNum(x).getvch()); + tx.vout.resize(det_rand.randrange(10)+2); + for (auto& out : tx.vout) { + out.scriptPubKey = CScript() << CScriptNum(tx_counter) << OP_EQUAL; + out.nValue = 10 * COIN; + } + ordered_coins.emplace_back(MakeTransactionRef(tx)); + available_coins.emplace_back(ordered_coins.back(), tx_counter++); + } + for (auto x = 0; x < 800 && !available_coins.empty(); ++x) { + CMutableTransaction tx = CMutableTransaction(); + size_t n_ancestors = det_rand.randrange(10)+1; + for (size_t ancestor = 0; ancestor < n_ancestors && !available_coins.empty(); ++ancestor){ + size_t idx = det_rand.randrange(available_coins.size()); + Available coin = available_coins[idx]; + uint256 hash = coin.ref->GetHash(); + // biased towards taking just one ancestor, but maybe more + size_t n_to_take = det_rand.randrange(2) == 0 ? 1 : 1+det_rand.randrange(coin.ref->vout.size() - coin.vin_left); + for (size_t i = 0; i < n_to_take; ++i) { + tx.vin.emplace_back(); + tx.vin.back().prevout = COutPoint(hash, coin.vin_left++); + tx.vin.back().scriptSig = CScript() << coin.tx_count; + tx.vin.back().scriptWitness.stack.push_back(CScriptNum(coin.tx_count).getvch()); + } + if (coin.vin_left == coin.ref->vin.size()) { + coin = available_coins.back(); + available_coins.pop_back(); + } + tx.vout.resize(det_rand.randrange(10)+2); + for (auto& out : tx.vout) { + out.scriptPubKey = CScript() << CScriptNum(tx_counter) << OP_EQUAL; + out.nValue = 10 * COIN; + } + } + ordered_coins.emplace_back(MakeTransactionRef(tx)); + available_coins.emplace_back(ordered_coins.back(), tx_counter++); + } + CTxMemPool pool; + LOCK2(cs_main, pool.cs); + while (state.KeepRunning()) { + for (auto& tx : ordered_coins) { + AddTx(tx, pool); + } + pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); + pool.TrimToSize(GetVirtualTransactionSize(*ordered_coins.front())); + } +} + +BENCHMARK(ComplexMemPool, 1); diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index 313b5a3ba0..0e660d6bcd 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -4,6 +4,7 @@ #include <bench/bench.h> #include <interfaces/chain.h> +#include <node/context.h> #include <optional.h> #include <test/util.h> #include <validationinterface.h> @@ -13,7 +14,8 @@ static void WalletBalance(benchmark::State& state, const bool set_dirty, const b { const auto& ADDRESS_WATCHONLY = ADDRESS_BCRT1_UNSPENDABLE; - std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(); + NodeContext node; + std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node); CWallet wallet{chain.get(), WalletLocation(), WalletDatabase::CreateMock()}; { bool first_run; diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 85c5cb06e9..d7b6891503 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -258,6 +258,8 @@ public: result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]); result.pushKV("protocolversion", batch[ID_NETWORKINFO]["result"]["protocolversion"]); result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]); + result.pushKV("headers", batch[ID_BLOCKCHAININFO]["result"]["headers"]); + result.pushKV("verificationprogress", batch[ID_BLOCKCHAININFO]["result"]["verificationprogress"]); result.pushKV("timeoffset", batch[ID_NETWORKINFO]["result"]["timeoffset"]); result.pushKV("connections", batch[ID_NETWORKINFO]["result"]["connections"]); result.pushKV("proxy", batch[ID_NETWORKINFO]["result"]["networks"][0]["proxy"]); @@ -366,7 +368,7 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co std::string endpoint = "/"; if (!gArgs.GetArgs("-rpcwallet").empty()) { std::string walletName = gArgs.GetArg("-rpcwallet", ""); - char *encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false); + char *encodedURI = evhttp_uriencode(walletName.data(), walletName.size(), false); if (encodedURI) { endpoint = "/wallet/"+ std::string(encodedURI); free(encodedURI); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 70c308960c..4b5cea4849 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -12,6 +12,7 @@ #include <compat.h> #include <init.h> #include <interfaces/chain.h> +#include <node/context.h> #include <noui.h> #include <shutdown.h> #include <ui_interface.h> @@ -24,13 +25,13 @@ const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; -static void WaitForShutdown() +static void WaitForShutdown(NodeContext& node) { while (!ShutdownRequested()) { MilliSleep(200); } - Interrupt(); + Interrupt(node); } ////////////////////////////////////////////////////////////////////////////// @@ -39,8 +40,8 @@ static void WaitForShutdown() // static bool AppInit(int argc, char* argv[]) { - InitInterfaces interfaces; - interfaces.chain = interfaces::MakeChain(); + NodeContext node; + node.chain = interfaces::MakeChain(node); bool fRet = false; @@ -142,7 +143,7 @@ static bool AppInit(int argc, char* argv[]) // If locking the data directory failed, exit immediately return false; } - fRet = AppInitMain(interfaces); + fRet = AppInitMain(node); } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInit()"); @@ -152,11 +153,11 @@ static bool AppInit(int argc, char* argv[]) if (!fRet) { - Interrupt(); + Interrupt(node); } else { - WaitForShutdown(); + WaitForShutdown(node); } - Shutdown(interfaces); + Shutdown(node); return fRet; } diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp index f0fcf675eb..bf13297582 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -197,13 +197,13 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector< if (vtx_missing.size() != tx_missing_offset) return READ_STATUS_INVALID; - CValidationState state; + BlockValidationState state; if (!CheckBlock(block, state, Params().GetConsensus())) { // TODO: We really want to just check merkle tree manually here, // but that is expensive, and CheckBlock caches a block's // "checked-status" (in the CBlock?). CBlock should be able to // check its own merkle root and cache that check. - if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED) + if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) return READ_STATUS_FAILED; // Possible Short ID collision return READ_STATUS_CHECKBLOCK_FAILED; } diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 7ff30ac1cd..dd4d3e97ac 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -62,7 +62,7 @@ static CBlock CreateGenesisBlock(uint32_t nTime, uint32_t nNonce, uint32_t nBits class CMainParams : public CChainParams { public: CMainParams() { - strNetworkID = "main"; + strNetworkID = CBaseChainParams::MAIN; consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22"); consensus.BIP34Height = 227931; @@ -169,7 +169,7 @@ public: class CTestNetParams : public CChainParams { public: CTestNetParams() { - strNetworkID = "test"; + strNetworkID = CBaseChainParams::TESTNET; consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105"); consensus.BIP34Height = 21111; @@ -254,7 +254,7 @@ public: class CRegTestParams : public CChainParams { public: explicit CRegTestParams(const ArgsManager& args) { - strNetworkID = "regtest"; + strNetworkID = CBaseChainParams::REGTEST; consensus.nSubsidyHalvingInterval = 150; consensus.BIP16Exception = uint256(); consensus.BIP34Height = 500; // BIP34 activated on regtest (Used in functional tests) diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp index 6793f871cf..88bb12c713 100644 --- a/src/consensus/tx_check.cpp +++ b/src/consensus/tx_check.cpp @@ -7,28 +7,28 @@ #include <primitives/transaction.h> #include <consensus/validation.h> -bool CheckTransaction(const CTransaction& tx, CValidationState& state) +bool CheckTransaction(const CTransaction& tx, TxValidationState& state) { // Basic checks that don't depend on any context if (tx.vin.empty()) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vin-empty"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vin-empty"); if (tx.vout.empty()) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vout-empty"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-empty"); // Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability) if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-oversize"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-oversize"); // Check for negative or overflow output values (see CVE-2010-5139) CAmount nValueOut = 0; for (const auto& txout : tx.vout) { if (txout.nValue < 0) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vout-negative"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-negative"); if (txout.nValue > MAX_MONEY) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vout-toolarge"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-toolarge"); nValueOut += txout.nValue; if (!MoneyRange(nValueOut)) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-txouttotal-toolarge"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-txouttotal-toolarge"); } // Check for duplicate inputs (see CVE-2018-17144) @@ -39,19 +39,19 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state) std::set<COutPoint> vInOutPoints; for (const auto& txin : tx.vin) { if (!vInOutPoints.insert(txin.prevout).second) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-inputs-duplicate"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-inputs-duplicate"); } if (tx.IsCoinBase()) { if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-cb-length"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cb-length"); } else { for (const auto& txin : tx.vin) if (txin.prevout.IsNull()) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-prevout-null"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-prevout-null"); } return true; diff --git a/src/consensus/tx_check.h b/src/consensus/tx_check.h index 6f3f8fe969..b818a284f1 100644 --- a/src/consensus/tx_check.h +++ b/src/consensus/tx_check.h @@ -13,8 +13,8 @@ */ class CTransaction; -class CValidationState; +class TxValidationState; -bool CheckTransaction(const CTransaction& tx, CValidationState& state); +bool CheckTransaction(const CTransaction& tx, TxValidationState& state); #endif // BITCOIN_CONSENSUS_TX_CHECK_H diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index ceeddc3f6d..31bdabea28 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -156,11 +156,11 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i return nSigOps; } -bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee) +bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee) { // are the actual inputs available? if (!inputs.HaveInputs(tx)) { - return state.Invalid(ValidationInvalidReason::TX_MISSING_INPUTS, false, "bad-txns-inputs-missingorspent", + return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent", strprintf("%s: inputs missing/spent", __func__)); } @@ -172,27 +172,27 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c // If prev is coinbase, check that it's matured if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) { - return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, "bad-txns-premature-spend-of-coinbase", + return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "bad-txns-premature-spend-of-coinbase", strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); } // Check for negative or overflow input values nValueIn += coin.out.nValue; if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-inputvalues-outofrange"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-inputvalues-outofrange"); } } const CAmount value_out = tx.GetValueOut(); if (nValueIn < value_out) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-in-belowout", + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-in-belowout", strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out))); } // Tally transaction fees const CAmount txfee_aux = nValueIn - value_out; if (!MoneyRange(txfee_aux)) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-fee-outofrange"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-fee-outofrange"); } txfee = txfee_aux; diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h index 3519fc555d..b6599f2878 100644 --- a/src/consensus/tx_verify.h +++ b/src/consensus/tx_verify.h @@ -13,7 +13,7 @@ class CBlockIndex; class CCoinsViewCache; class CTransaction; -class CValidationState; +class TxValidationState; /** Transaction validation functions */ @@ -24,7 +24,7 @@ namespace Consensus { * @param[out] txfee Set to the transaction fee if successful. * Preconditions: tx.IsCoinBase() is false. */ -bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee); +bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee); } // namespace Consensus /** Auxiliary functions for transaction validation (ideally should not be exposed) */ diff --git a/src/consensus/validation.h b/src/consensus/validation.h index 4920cdf881..e602b9d5f3 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -12,13 +12,12 @@ #include <primitives/transaction.h> #include <primitives/block.h> -/** A "reason" why something was invalid, suitable for determining whether the - * provider of the object should be banned/ignored/disconnected/etc. +/** A "reason" why a transaction was invalid, suitable for determining whether the + * provider of the transaction should be banned/ignored/disconnected/etc. */ -enum class ValidationInvalidReason { - // txn and blocks: - NONE, //!< not actually invalid - CONSENSUS, //!< invalid by consensus rules (excluding any below reasons) +enum class TxValidationResult { + TX_RESULT_UNSET, //!< initial value. Tx has not yet been rejected + TX_CONSENSUS, //!< invalid by consensus rules /** * Invalid by a change to consensus rules more recent than SegWit. * Currently unused as there are no such consensus rule changes, and any download @@ -26,18 +25,9 @@ enum class ValidationInvalidReason { * so differentiating between always-invalid and invalid-by-pre-SegWit-soft-fork * is uninteresting. */ - RECENT_CONSENSUS_CHANGE, - // Only blocks (or headers): - CACHED_INVALID, //!< this object was cached as being invalid, but we don't know why - BLOCK_INVALID_HEADER, //!< invalid proof of work or time too old - BLOCK_MUTATED, //!< the block's data didn't match the data committed to by the PoW - BLOCK_MISSING_PREV, //!< We don't have the previous block the checked one is built on - BLOCK_INVALID_PREV, //!< A block this one builds on is invalid - BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad) - BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints - // Only loose txn: + TX_RECENT_CONSENSUS_CHANGE, TX_NOT_STANDARD, //!< didn't meet our local policy rules - TX_MISSING_INPUTS, //!< a transaction was missing some of its inputs + TX_MISSING_INPUTS, //!< transaction was missing some of its inputs TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks /** * Transaction might be missing a witness, have a witness prior to SegWit @@ -48,82 +38,107 @@ enum class ValidationInvalidReason { /** * Tx already in mempool or conflicts with a tx in the chain * (if it conflicts with another tx in mempool, we use MEMPOOL_POLICY as it failed to reach the RBF threshold) - * TODO: Currently this is only used if the transaction already exists in the mempool or on chain, - * TODO: ATMP's fMissingInputs and a valid CValidationState being used to indicate missing inputs + * Currently this is only used if the transaction already exists in the mempool or on chain. */ TX_CONFLICT, TX_MEMPOOL_POLICY, //!< violated mempool's fee/size/descendant/RBF/etc limits }; -inline bool IsTransactionReason(ValidationInvalidReason r) -{ - return r == ValidationInvalidReason::NONE || - r == ValidationInvalidReason::CONSENSUS || - r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE || - r == ValidationInvalidReason::TX_NOT_STANDARD || - r == ValidationInvalidReason::TX_PREMATURE_SPEND || - r == ValidationInvalidReason::TX_MISSING_INPUTS || - r == ValidationInvalidReason::TX_WITNESS_MUTATED || - r == ValidationInvalidReason::TX_CONFLICT || - r == ValidationInvalidReason::TX_MEMPOOL_POLICY; -} +/** A "reason" why a block was invalid, suitable for determining whether the + * provider of the block should be banned/ignored/disconnected/etc. + * These are much more granular than the rejection codes, which may be more + * useful for some other use-cases. + */ +enum class BlockValidationResult { + BLOCK_RESULT_UNSET, //!< initial value. Block has not yet been rejected + BLOCK_CONSENSUS, //!< invalid by consensus rules (excluding any below reasons) + /** + * Invalid by a change to consensus rules more recent than SegWit. + * Currently unused as there are no such consensus rule changes, and any download + * sources realistically need to support SegWit in order to provide useful data, + * so differentiating between always-invalid and invalid-by-pre-SegWit-soft-fork + * is uninteresting. + */ + BLOCK_RECENT_CONSENSUS_CHANGE, + BLOCK_CACHED_INVALID, //!< this block was cached as being invalid and we didn't store the reason why + BLOCK_INVALID_HEADER, //!< invalid proof of work or time too old + BLOCK_MUTATED, //!< the block's data didn't match the data committed to by the PoW + BLOCK_MISSING_PREV, //!< We don't have the previous block the checked one is built on + BLOCK_INVALID_PREV, //!< A block this one builds on is invalid + BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad) + BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints +}; -inline bool IsBlockReason(ValidationInvalidReason r) -{ - return r == ValidationInvalidReason::NONE || - r == ValidationInvalidReason::CONSENSUS || - r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE || - r == ValidationInvalidReason::CACHED_INVALID || - r == ValidationInvalidReason::BLOCK_INVALID_HEADER || - r == ValidationInvalidReason::BLOCK_MUTATED || - r == ValidationInvalidReason::BLOCK_MISSING_PREV || - r == ValidationInvalidReason::BLOCK_INVALID_PREV || - r == ValidationInvalidReason::BLOCK_TIME_FUTURE || - r == ValidationInvalidReason::BLOCK_CHECKPOINT; -} -/** Capture information about block/transaction validation */ -class CValidationState { + +/** Base class for capturing information about block/transaction validation. This is subclassed + * by TxValidationState and BlockValidationState for validation information on transactions + * and blocks respectively. */ +class ValidationState { private: enum mode_state { MODE_VALID, //!< everything ok MODE_INVALID, //!< network rule violation (DoS value may be set) MODE_ERROR, //!< run-time error - } mode; - ValidationInvalidReason m_reason; - std::string strRejectReason; - std::string strDebugMessage; -public: - CValidationState() : mode(MODE_VALID), m_reason(ValidationInvalidReason::NONE) {} - bool Invalid(ValidationInvalidReason reasonIn, bool ret = false, - const std::string &strRejectReasonIn="", - const std::string &strDebugMessageIn="") { - m_reason = reasonIn; - strRejectReason = strRejectReasonIn; - strDebugMessage = strDebugMessageIn; - if (mode == MODE_ERROR) - return ret; - mode = MODE_INVALID; - return ret; + } m_mode; + std::string m_reject_reason; + std::string m_debug_message; +protected: + void Invalid(const std::string &reject_reason="", + const std::string &debug_message="") + { + m_reject_reason = reject_reason; + m_debug_message = debug_message; + if (m_mode != MODE_ERROR) m_mode = MODE_INVALID; } - bool Error(const std::string& strRejectReasonIn) { - if (mode == MODE_VALID) - strRejectReason = strRejectReasonIn; - mode = MODE_ERROR; +public: + // ValidationState is abstract. Have a pure virtual destructor. + virtual ~ValidationState() = 0; + + ValidationState() : m_mode(MODE_VALID) {} + bool Error(const std::string& reject_reason) + { + if (m_mode == MODE_VALID) + m_reject_reason = reject_reason; + m_mode = MODE_ERROR; return false; } - bool IsValid() const { - return mode == MODE_VALID; - } - bool IsInvalid() const { - return mode == MODE_INVALID; + bool IsValid() const { return m_mode == MODE_VALID; } + bool IsInvalid() const { return m_mode == MODE_INVALID; } + bool IsError() const { return m_mode == MODE_ERROR; } + std::string GetRejectReason() const { return m_reject_reason; } + std::string GetDebugMessage() const { return m_debug_message; } +}; + +inline ValidationState::~ValidationState() {}; + +class TxValidationState : public ValidationState { +private: + TxValidationResult m_result; +public: + bool Invalid(TxValidationResult result, + const std::string &reject_reason="", + const std::string &debug_message="") + { + m_result = result; + ValidationState::Invalid(reject_reason, debug_message); + return false; } - bool IsError() const { - return mode == MODE_ERROR; + TxValidationResult GetResult() const { return m_result; } +}; + +class BlockValidationState : public ValidationState { +private: + BlockValidationResult m_result; +public: + bool Invalid(BlockValidationResult result, + const std::string &reject_reason="", + const std::string &debug_message="") { + m_result = result; + ValidationState::Invalid(reject_reason, debug_message); + return false; } - ValidationInvalidReason GetReason() const { return m_reason; } - std::string GetRejectReason() const { return strRejectReason; } - std::string GetDebugMessage() const { return strDebugMessage; } + BlockValidationResult GetResult() const { return m_result; } }; // These implement the weight = (stripped_size * 4) + witness_size formula, diff --git a/src/crypto/hkdf_sha256_32.cpp b/src/crypto/hkdf_sha256_32.cpp index 9cea5995ec..e684eced37 100644 --- a/src/crypto/hkdf_sha256_32.cpp +++ b/src/crypto/hkdf_sha256_32.cpp @@ -9,7 +9,7 @@ CHKDF_HMAC_SHA256_L32::CHKDF_HMAC_SHA256_L32(const unsigned char* ikm, size_t ikmlen, const std::string& salt) { - CHMAC_SHA256((const unsigned char*)salt.c_str(), salt.size()).Write(ikm, ikmlen).Finalize(m_prk); + CHMAC_SHA256((const unsigned char*)salt.data(), salt.size()).Write(ikm, ikmlen).Finalize(m_prk); } void CHKDF_HMAC_SHA256_L32::Expand32(const std::string& info, unsigned char hash[OUTPUT_SIZE]) diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 0edcb0286d..38b5b0efc4 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -19,7 +19,7 @@ public: bool HasWalletSupport() const override {return false;} void AddWalletOptions() const override; bool ParameterInteraction() const override {return true;} - void Construct(InitInterfaces& interfaces) const override {LogPrintf("No wallet support compiled in!\n");} + void Construct(NodeContext& node) const override {LogPrintf("No wallet support compiled in!\n");} }; void DummyWalletInit::AddWalletOptions() const diff --git a/src/fs.cpp b/src/fs.cpp index 7b422b8d70..73fb3b606e 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -107,10 +107,10 @@ std::string get_filesystem_error_message(const fs::filesystem_error& e) #else // Convert from Multi Byte to utf-16 std::string mb_string(e.what()); - int size = MultiByteToWideChar(CP_ACP, 0, mb_string.c_str(), mb_string.size(), nullptr, 0); + int size = MultiByteToWideChar(CP_ACP, 0, mb_string.data(), mb_string.size(), nullptr, 0); std::wstring utf16_string(size, L'\0'); - MultiByteToWideChar(CP_ACP, 0, mb_string.c_str(), mb_string.size(), &*utf16_string.begin(), size); + MultiByteToWideChar(CP_ACP, 0, mb_string.data(), mb_string.size(), &*utf16_string.begin(), size); // Convert from utf-16 to utf-8 return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t>().to_bytes(utf16_string); #endif diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 2c2f67b169..0437f0c7de 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -112,7 +112,7 @@ static bool multiUserAuthorized(std::string strUserPass) static const unsigned int KEY_SIZE = 32; unsigned char out[KEY_SIZE]; - CHMAC_SHA256(reinterpret_cast<const unsigned char*>(strSalt.c_str()), strSalt.size()).Write(reinterpret_cast<const unsigned char*>(strPass.c_str()), strPass.size()).Finalize(out); + CHMAC_SHA256(reinterpret_cast<const unsigned char*>(strSalt.data()), strSalt.size()).Write(reinterpret_cast<const unsigned char*>(strPass.data()), strPass.size()).Finalize(out); std::vector<unsigned char> hexvec(out, out+KEY_SIZE); std::string strHashFromPass = HexStr(hexvec); diff --git a/src/init.cpp b/src/init.cpp index 93221efe9b..1a99ca9abc 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -29,6 +29,7 @@ #include <net_permissions.h> #include <net_processing.h> #include <netbase.h> +#include <node/context.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> @@ -84,9 +85,6 @@ static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false; // Dump addresses to banlist.dat every 15 minutes (900s) static constexpr int DUMP_BANS_INTERVAL = 60 * 15; -std::unique_ptr<CConnman> g_connman; -std::unique_ptr<PeerLogicValidation> peerLogic; -std::unique_ptr<BanMan> g_banman; #ifdef WIN32 // Win32 LevelDB doesn't use filedescriptors, and the ones used for @@ -154,7 +152,7 @@ static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle; static boost::thread_group threadGroup; static CScheduler scheduler; -void Interrupt() +void Interrupt(NodeContext& node) { InterruptHTTPServer(); InterruptHTTPRPC(); @@ -162,15 +160,15 @@ void Interrupt() InterruptREST(); InterruptTorControl(); InterruptMapPort(); - if (g_connman) - g_connman->Interrupt(); + if (node.connman) + node.connman->Interrupt(); if (g_txindex) { g_txindex->Interrupt(); } ForEachBlockFilterIndex([](BlockFilterIndex& index) { index.Interrupt(); }); } -void Shutdown(InitInterfaces& interfaces) +void Shutdown(NodeContext& node) { LogPrintf("%s: In progress...\n", __func__); static CCriticalSection cs_Shutdown; @@ -189,15 +187,15 @@ void Shutdown(InitInterfaces& interfaces) StopREST(); StopRPC(); StopHTTPServer(); - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { client->flush(); } StopMapPort(); // Because these depend on each-other, we make sure that neither can be // using the other before destroying them. - if (peerLogic) UnregisterValidationInterface(peerLogic.get()); - if (g_connman) g_connman->Stop(); + if (node.peer_logic) UnregisterValidationInterface(node.peer_logic.get()); + if (node.connman) node.connman->Stop(); if (g_txindex) g_txindex->Stop(); ForEachBlockFilterIndex([](BlockFilterIndex& index) { index.Stop(); }); @@ -210,9 +208,9 @@ void Shutdown(InitInterfaces& interfaces) // After the threads that potentially access these pointers have been stopped, // destruct and reset all to nullptr. - peerLogic.reset(); - g_connman.reset(); - g_banman.reset(); + node.peer_logic.reset(); + node.connman.reset(); + node.banman.reset(); g_txindex.reset(); DestroyAllBlockFilterIndexes(); @@ -261,7 +259,7 @@ void Shutdown(InitInterfaces& interfaces) } pblocktree.reset(); } - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { client->stop(); } @@ -280,7 +278,7 @@ void Shutdown(InitInterfaces& interfaces) } catch (const fs::filesystem_error& e) { LogPrintf("%s: Unable to remove PID file: %s\n", __func__, fsbridge::get_filesystem_error_message(e)); } - interfaces.chain_clients.clear(); + node.chain_clients.clear(); UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); GetMainSignals().UnregisterWithMempoolSignals(mempool); @@ -482,7 +480,7 @@ void SetupServerArgs() "(0-4, default: %u)", DEFAULT_CHECKLEVEL), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-checkblockindex", strprintf("Do a consistency check for the block tree, chainstate, and other validation data structures occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkpoints", strprintf("Enable rejection of any forks from the known historical chain until block 295000 (default: %u)", DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-dropmessagestest=<n>", "Randomly drop 1 of every <n> network messages", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); @@ -712,7 +710,7 @@ static void ThreadImport(std::vector<fs::path> vImportFiles) } // scan for better chains in the block chain database, that are not yet connected in the active best chain - CValidationState state; + BlockValidationState state; if (!ActivateBestChain(state, chainparams)) { LogPrintf("Failed to connect best block (%s)\n", FormatStateMessage(state)); StartShutdown(); @@ -1207,7 +1205,7 @@ bool AppInitLockDataDirectory() return true; } -bool AppInitMain(InitInterfaces& interfaces) +bool AppInitMain(NodeContext& node) { const CChainParams& chainparams = Params(); // ********************************************************* Step 4a: application initialization @@ -1258,7 +1256,7 @@ bool AppInitMain(InitInterfaces& interfaces) InitSignatureCache(); InitScriptExecutionCache(); - LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads); + LogPrintf("Script verification uses %d additional threads\n", std::max(nScriptCheckThreads - 1, 0)); if (nScriptCheckThreads) { for (int i=0; i<nScriptCheckThreads-1; i++) threadGroup.create_thread([i]() { return ThreadScriptCheck(i); }); @@ -1275,16 +1273,16 @@ bool AppInitMain(InitInterfaces& interfaces) // 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); + g_wallet_init_interface.Construct(node); /* 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); - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { client->registerRpcs(); } - g_rpc_interfaces = &interfaces; + g_rpc_node = &node; #if ENABLE_ZMQ RegisterZMQRPCCommands(tableRPC); #endif @@ -1302,7 +1300,7 @@ bool AppInitMain(InitInterfaces& interfaces) } // ********************************************************* Step 5: verify wallet database integrity - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { if (!client->verify()) { return false; } @@ -1314,13 +1312,13 @@ bool AppInitMain(InitInterfaces& interfaces) // is not yet setup and may end up being set up twice if we // need to reindex later. - assert(!g_banman); - g_banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); - assert(!g_connman); - g_connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()))); + assert(!node.banman); + node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); + assert(!node.connman); + node.connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()))); - peerLogic.reset(new PeerLogicValidation(g_connman.get(), g_banman.get(), scheduler)); - RegisterValidationInterface(peerLogic.get()); + node.peer_logic.reset(new PeerLogicValidation(node.connman.get(), node.banman.get(), scheduler)); + RegisterValidationInterface(node.peer_logic.get()); // sanitize comments per BIP-0014, format user agent and check total size std::vector<std::string> uacomments; @@ -1661,7 +1659,7 @@ bool AppInitMain(InitInterfaces& interfaces) } // ********************************************************* Step 9: load wallet - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { if (!client->load()) { return false; } @@ -1765,8 +1763,8 @@ bool AppInitMain(InitInterfaces& interfaces) connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chain_active_height; connOptions.uiInterface = &uiInterface; - connOptions.m_banman = g_banman.get(); - connOptions.m_msgproc = peerLogic.get(); + connOptions.m_banman = node.banman.get(); + connOptions.m_msgproc = node.peer_logic.get(); connOptions.nSendBufferMaxSize = 1000*gArgs.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); connOptions.nReceiveFloodSize = 1000*gArgs.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); connOptions.m_added_nodes = gArgs.GetArgs("-addnode"); @@ -1806,7 +1804,7 @@ bool AppInitMain(InitInterfaces& interfaces) connOptions.m_specified_outgoing = connect; } } - if (!g_connman->Start(scheduler, connOptions)) { + if (!node.connman->Start(scheduler, connOptions)) { return false; } @@ -1815,12 +1813,13 @@ bool AppInitMain(InitInterfaces& interfaces) SetRPCWarmupFinished(); uiInterface.InitMessage(_("Done loading").translated); - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { client->start(scheduler); } - scheduler.scheduleEvery([]{ - g_banman->DumpBanlist(); + BanMan* banman = node.banman.get(); + scheduler.scheduleEvery([banman]{ + banman->DumpBanlist(); }, DUMP_BANS_INTERVAL * 1000); return true; diff --git a/src/init.h b/src/init.h index 1c59ca069e..ca52dadf08 100644 --- a/src/init.h +++ b/src/init.h @@ -10,26 +10,14 @@ #include <string> #include <util/system.h> -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 -{ +struct NodeContext; +namespace boost { class thread_group; } // namespace boost /** Interrupt threads */ -void Interrupt(); -void Shutdown(InitInterfaces& interfaces); +void Interrupt(NodeContext& node); +void Shutdown(NodeContext& node); //!Initialize the logging infrastructure void InitLogging(); //!Parameter interaction: change current parameters depending on various rules @@ -63,7 +51,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(InitInterfaces& interfaces); +bool AppInitMain(NodeContext& node); /** * Setup the arguments for gArgs diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index b2c20573fb..23099a7799 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -11,6 +11,7 @@ #include <net.h> #include <net_processing.h> #include <node/coin.h> +#include <node/context.h> #include <node/transaction.h> #include <policy/fees.h> #include <policy/policy.h> @@ -238,6 +239,7 @@ public: class ChainImpl : public Chain { public: + explicit ChainImpl(NodeContext& node) : m_node(node) {} std::unique_ptr<Chain::Lock> lock(bool try_lock) override { auto result = MakeUnique<LockImpl>(::cs_main, "cs_main", __FILE__, __LINE__, try_lock); @@ -286,7 +288,7 @@ public: } bool broadcastTransaction(const CTransactionRef& tx, std::string& err_string, const CAmount& max_tx_fee, bool relay) override { - const TransactionError err = BroadcastTransaction(tx, err_string, max_tx_fee, relay, /*wait_callback*/ false); + const TransactionError err = BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay, /*wait_callback*/ false); // Chain clients only care about failures to accept the tx to the mempool. Disregard non-mempool related failures. // Note: this will need to be updated if BroadcastTransactions() is updated to return other non-mempool failures // that Chain clients do not need to know about. @@ -378,9 +380,10 @@ public: notifications.TransactionAddedToMempool(entry.GetSharedTx()); } } + NodeContext& m_node; }; } // namespace -std::unique_ptr<Chain> MakeChain() { return MakeUnique<ChainImpl>(); } +std::unique_ptr<Chain> MakeChain(NodeContext& node) { return MakeUnique<ChainImpl>(node); } } // namespace interfaces diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 73a78e21fb..82eeba1160 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -18,12 +18,12 @@ class CBlock; class CFeeRate; class CRPCCommand; class CScheduler; -class CValidationState; class Coin; class uint256; enum class RBFTransactionState; struct CBlockLocator; struct FeeCalculation; +struct NodeContext; namespace interfaces { @@ -291,7 +291,7 @@ public: }; //! Return implementation of Chain interface. -std::unique_ptr<Chain> MakeChain(); +std::unique_ptr<Chain> MakeChain(NodeContext& node); //! Return implementation of ChainClient interface for a wallet client. This //! function will be undefined in builds where ENABLE_WALLET is false. diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 6577895d50..1877c92178 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -16,6 +16,7 @@ #include <net_processing.h> #include <netaddress.h> #include <netbase.h> +#include <node/context.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/settings.h> @@ -52,7 +53,6 @@ namespace { class NodeImpl : public Node { public: - NodeImpl() { m_interfaces.chain = MakeChain(); } void initError(const std::string& message) override { InitError(message); } bool parseParameters(int argc, const char* const argv[], std::string& error) override { @@ -75,11 +75,15 @@ public: return AppInitBasicSetup() && AppInitParameterInteraction() && AppInitSanityChecks() && AppInitLockDataDirectory(); } - bool appInitMain() override { return AppInitMain(m_interfaces); } + bool appInitMain() override + { + m_context.chain = MakeChain(m_context); + return AppInitMain(m_context); + } void appShutdown() override { - Interrupt(); - Shutdown(m_interfaces); + Interrupt(m_context); + Shutdown(m_context); } void startShutdown() override { StartShutdown(); } bool shutdownRequested() override { return ShutdownRequested(); } @@ -96,15 +100,15 @@ public: bool getProxy(Network net, proxyType& proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(CConnman::NumConnections flags) override { - return g_connman ? g_connman->GetNodeCount(flags) : 0; + return m_context.connman ? m_context.connman->GetNodeCount(flags) : 0; } bool getNodesStats(NodesStats& stats) override { stats.clear(); - if (g_connman) { + if (m_context.connman) { std::vector<CNodeStats> stats_temp; - g_connman->GetNodeStats(stats_temp); + m_context.connman->GetNodeStats(stats_temp); stats.reserve(stats_temp.size()); for (auto& node_stats_temp : stats_temp) { @@ -125,44 +129,44 @@ public: } bool getBanned(banmap_t& banmap) override { - if (g_banman) { - g_banman->GetBanned(banmap); + if (m_context.banman) { + m_context.banman->GetBanned(banmap); return true; } return false; } bool ban(const CNetAddr& net_addr, BanReason reason, int64_t ban_time_offset) override { - if (g_banman) { - g_banman->Ban(net_addr, reason, ban_time_offset); + if (m_context.banman) { + m_context.banman->Ban(net_addr, reason, ban_time_offset); return true; } return false; } bool unban(const CSubNet& ip) override { - if (g_banman) { - g_banman->Unban(ip); + if (m_context.banman) { + m_context.banman->Unban(ip); return true; } return false; } bool disconnect(const CNetAddr& net_addr) override { - if (g_connman) { - return g_connman->DisconnectNode(net_addr); + if (m_context.connman) { + return m_context.connman->DisconnectNode(net_addr); } return false; } bool disconnect(NodeId id) override { - if (g_connman) { - return g_connman->DisconnectNode(id); + if (m_context.connman) { + return m_context.connman->DisconnectNode(id); } return false; } - int64_t getTotalBytesRecv() override { return g_connman ? g_connman->GetTotalBytesRecv() : 0; } - int64_t getTotalBytesSent() override { return g_connman ? g_connman->GetTotalBytesSent() : 0; } + int64_t getTotalBytesRecv() override { return m_context.connman ? m_context.connman->GetTotalBytesRecv() : 0; } + int64_t getTotalBytesSent() override { return m_context.connman ? m_context.connman->GetTotalBytesSent() : 0; } size_t getMempoolSize() override { return ::mempool.size(); } size_t getMempoolDynamicUsage() override { return ::mempool.DynamicMemoryUsage(); } bool getHeaderTip(int& height, int64_t& block_time) override @@ -202,11 +206,11 @@ public: bool getImporting() override { return ::fImporting; } void setNetworkActive(bool active) override { - if (g_connman) { - g_connman->SetNetworkActive(active); + if (m_context.connman) { + m_context.connman->SetNetworkActive(active); } } - bool getNetworkActive() override { return g_connman && g_connman->GetNetworkActive(); } + bool getNetworkActive() override { return m_context.connman && m_context.connman->GetNetworkActive(); } CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) override { FeeCalculation fee_calc; @@ -255,12 +259,12 @@ public: } std::unique_ptr<Wallet> loadWallet(const std::string& name, std::string& error, std::vector<std::string>& warnings) override { - return MakeWallet(LoadWallet(*m_interfaces.chain, name, error, warnings)); + return MakeWallet(LoadWallet(*m_context.chain, name, error, warnings)); } WalletCreationStatus createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::vector<std::string>& warnings, std::unique_ptr<Wallet>& result) override { std::shared_ptr<CWallet> wallet; - WalletCreationStatus status = CreateWallet(*m_interfaces.chain, passphrase, wallet_creation_flags, name, error, warnings, wallet); + WalletCreationStatus status = CreateWallet(*m_context.chain, passphrase, wallet_creation_flags, name, error, warnings, wallet); result = MakeWallet(wallet); return status; } @@ -315,7 +319,8 @@ public: /* verification progress is unused when a header was received */ 0); })); } - InitInterfaces m_interfaces; + NodeContext* context() override { return &m_context; } + NodeContext m_context; }; } // namespace diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 4ee467014c..c29037f2e3 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -28,6 +28,7 @@ class RPCTimerInterface; class UniValue; class proxyType; struct CNodeStateStats; +struct NodeContext; enum class WalletCreationStatus; namespace interfaces { @@ -254,6 +255,9 @@ public: using NotifyHeaderTipFn = std::function<void(bool initial_download, int height, int64_t block_time, double verification_progress)>; virtual std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0; + + //! Return pointer to internal chain interface, useful for testing. + virtual NodeContext* context() { return nullptr; } }; //! Return implementation of Node interface. diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 9b0a8b64c9..b6ede08b14 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -46,7 +46,7 @@ WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, co result.txout_is_mine.emplace_back(wallet.IsMine(txout)); result.txout_address.emplace_back(); result.txout_address_is_mine.emplace_back(ExtractDestination(txout.scriptPubKey, result.txout_address.back()) ? - IsMine(wallet, result.txout_address.back()) : + wallet.IsMine(result.txout_address.back()) : ISMINE_NO); } result.credit = wtx.GetCredit(locked_chain, ISMINE_ALL); @@ -117,10 +117,17 @@ public: std::string error; return m_wallet->GetNewDestination(type, label, dest, error); } - bool getPubKey(const CKeyID& address, CPubKey& pub_key) override { return m_wallet->GetPubKey(address, pub_key); } - bool getPrivKey(const CKeyID& address, CKey& key) override { return m_wallet->GetKey(address, key); } - bool isSpendable(const CTxDestination& dest) override { return IsMine(*m_wallet, dest) & ISMINE_SPENDABLE; } - bool haveWatchOnly() override { return m_wallet->HaveWatchOnly(); }; + bool getPubKey(const CKeyID& address, CPubKey& pub_key) override { return m_wallet->GetLegacyScriptPubKeyMan()->GetPubKey(address, pub_key); } + bool getPrivKey(const CKeyID& address, CKey& key) override { return m_wallet->GetLegacyScriptPubKeyMan()->GetKey(address, key); } + bool isSpendable(const CTxDestination& dest) override { return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; } + bool haveWatchOnly() override + { + auto spk_man = m_wallet->GetLegacyScriptPubKeyMan(); + if (spk_man) { + return spk_man->HaveWatchOnly(); + } + return false; + }; bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::string& purpose) override { return m_wallet->SetAddressBook(dest, name, purpose); @@ -143,7 +150,7 @@ public: *name = it->second.name; } if (is_mine) { - *is_mine = IsMine(*m_wallet, dest); + *is_mine = m_wallet->IsMine(dest); } if (purpose) { *purpose = it->second.purpose; @@ -155,11 +162,11 @@ public: LOCK(m_wallet->cs_wallet); std::vector<WalletAddress> result; for (const auto& item : m_wallet->mapAddressBook) { - result.emplace_back(item.first, IsMine(*m_wallet, item.first), item.second.name, item.second.purpose); + result.emplace_back(item.first, m_wallet->IsMine(item.first), item.second.name, item.second.purpose); } return result; } - void learnRelatedScripts(const CPubKey& key, OutputType type) override { m_wallet->LearnRelatedScripts(key, type); } + void learnRelatedScripts(const CPubKey& key, OutputType type) override { m_wallet->GetLegacyScriptPubKeyMan()->LearnRelatedScripts(key, type); } bool addDestData(const CTxDestination& dest, const std::string& key, const std::string& value) override { LOCK(m_wallet->cs_wallet); @@ -342,7 +349,7 @@ public: result.balance = bal.m_mine_trusted; result.unconfirmed_balance = bal.m_mine_untrusted_pending; result.immature_balance = bal.m_mine_immature; - result.have_watch_only = m_wallet->HaveWatchOnly(); + result.have_watch_only = haveWatchOnly(); if (result.have_watch_only) { result.watch_only_balance = bal.m_watchonly_trusted; result.unconfirmed_watch_only_balance = bal.m_watchonly_untrusted_pending; @@ -489,7 +496,11 @@ public: : m_chain(chain), m_wallet_filenames(std::move(wallet_filenames)) { } - void registerRpcs() override { return RegisterWalletRPCCommands(m_chain, m_rpc_handlers); } + void registerRpcs() override + { + g_rpc_chain = &m_chain; + return RegisterWalletRPCCommands(m_chain, m_rpc_handlers); + } 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); } diff --git a/src/logging.h b/src/logging.h index 75cd5353c0..e37c0c823b 100644 --- a/src/logging.h +++ b/src/logging.h @@ -155,12 +155,13 @@ static inline void LogPrintf(const char* fmt, const Args&... args) } } -template <typename... Args> -static inline void LogPrint(const BCLog::LogFlags& category, const Args&... args) -{ - if (LogAcceptCategory((category))) { - LogPrintf(args...); - } -} +// Use a macro instead of a function for conditional logging to prevent +// evaluating arguments when logging for the category is not enabled. +#define LogPrint(category, ...) \ + do { \ + if (LogAcceptCategory((category))) { \ + LogPrintf(__VA_ARGS__); \ + } \ + } while (0) #endif // BITCOIN_LOGGING_H diff --git a/src/miner.cpp b/src/miner.cpp index 4f51be8a08..1c9174ee07 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -162,7 +162,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblock->nNonce = 0; pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); - CValidationState state; + BlockValidationState state; if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, FormatStateMessage(state))); } @@ -480,8 +480,6 @@ private: friend struct CConnmanTest; }; -extern std::unique_ptr<CConnman> g_connman; -extern std::unique_ptr<BanMan> g_banman; void Discover(); void StartMapPort(); void InterruptMapPort(); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index fd31c962c2..d03817834d 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -982,14 +982,12 @@ void Misbehaving(NodeId pnode, int howmuch, const std::string& message) EXCLUSIV * banning/disconnecting us. We use this to determine which unaccepted * transactions from a whitelisted peer that we can safely relay. */ -static bool TxRelayMayResultInDisconnect(const CValidationState& state) -{ - assert(IsTransactionReason(state.GetReason())); - return state.GetReason() == ValidationInvalidReason::CONSENSUS; +static bool TxRelayMayResultInDisconnect(const TxValidationState& state) { + return state.GetResult() == TxValidationResult::TX_CONSENSUS; } /** - * Potentially ban a node based on the contents of a CValidationState object + * Potentially ban a node based on the contents of a BlockValidationState object * * @param[in] via_compact_block: this bool is passed in because net_processing should * punish peers differently depending on whether the data was provided in a compact @@ -997,23 +995,21 @@ static bool TxRelayMayResultInDisconnect(const CValidationState& state) * txs, the peer should not be punished. See BIP 152. * * @return Returns true if the peer was punished (probably disconnected) - * - * Changes here may need to be reflected in TxRelayMayResultInDisconnect(). */ -static bool MaybePunishNode(NodeId nodeid, const CValidationState& state, bool via_compact_block, const std::string& message = "") { - switch (state.GetReason()) { - case ValidationInvalidReason::NONE: +static bool MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, bool via_compact_block, const std::string& message = "") { + switch (state.GetResult()) { + case BlockValidationResult::BLOCK_RESULT_UNSET: break; // The node is providing invalid data: - case ValidationInvalidReason::CONSENSUS: - case ValidationInvalidReason::BLOCK_MUTATED: + case BlockValidationResult::BLOCK_CONSENSUS: + case BlockValidationResult::BLOCK_MUTATED: if (!via_compact_block) { LOCK(cs_main); Misbehaving(nodeid, 100, message); return true; } break; - case ValidationInvalidReason::CACHED_INVALID: + case BlockValidationResult::BLOCK_CACHED_INVALID: { LOCK(cs_main); CNodeState *node_state = State(nodeid); @@ -1029,30 +1025,24 @@ static bool MaybePunishNode(NodeId nodeid, const CValidationState& state, bool v } break; } - case ValidationInvalidReason::BLOCK_INVALID_HEADER: - case ValidationInvalidReason::BLOCK_CHECKPOINT: - case ValidationInvalidReason::BLOCK_INVALID_PREV: + case BlockValidationResult::BLOCK_INVALID_HEADER: + case BlockValidationResult::BLOCK_CHECKPOINT: + case BlockValidationResult::BLOCK_INVALID_PREV: { LOCK(cs_main); Misbehaving(nodeid, 100, message); } return true; // Conflicting (but not necessarily invalid) data or different policy: - case ValidationInvalidReason::BLOCK_MISSING_PREV: + case BlockValidationResult::BLOCK_MISSING_PREV: { // TODO: Handle this much more gracefully (10 DoS points is super arbitrary) LOCK(cs_main); Misbehaving(nodeid, 10, message); } return true; - case ValidationInvalidReason::RECENT_CONSENSUS_CHANGE: - case ValidationInvalidReason::BLOCK_TIME_FUTURE: - case ValidationInvalidReason::TX_NOT_STANDARD: - case ValidationInvalidReason::TX_MISSING_INPUTS: - case ValidationInvalidReason::TX_PREMATURE_SPEND: - case ValidationInvalidReason::TX_WITNESS_MUTATED: - case ValidationInvalidReason::TX_CONFLICT: - case ValidationInvalidReason::TX_MEMPOOL_POLICY: + case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE: + case BlockValidationResult::BLOCK_TIME_FUTURE: break; } if (message != "") { @@ -1061,6 +1051,39 @@ static bool MaybePunishNode(NodeId nodeid, const CValidationState& state, bool v return false; } +/** + * Potentially ban a node based on the contents of a TxValidationState object + * + * @return Returns true if the peer was punished (probably disconnected) + * + * Changes here may need to be reflected in TxRelayMayResultInDisconnect(). + */ +static bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message = "") { + switch (state.GetResult()) { + case TxValidationResult::TX_RESULT_UNSET: + break; + // The node is providing invalid data: + case TxValidationResult::TX_CONSENSUS: + { + LOCK(cs_main); + Misbehaving(nodeid, 100, message); + return true; + } + // Conflicting (but not necessarily invalid) data or different policy: + case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE: + case TxValidationResult::TX_NOT_STANDARD: + case TxValidationResult::TX_MISSING_INPUTS: + case TxValidationResult::TX_PREMATURE_SPEND: + case TxValidationResult::TX_WITNESS_MUTATED: + case TxValidationResult::TX_CONFLICT: + case TxValidationResult::TX_MEMPOOL_POLICY: + break; + } + if (message != "") { + LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message); + } + return false; +} @@ -1229,7 +1252,7 @@ void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CB * Handle invalid block rejection and consequent peer banning, maintain which * peers announce compact blocks. */ -void PeerLogicValidation::BlockChecked(const CBlock& block, const CValidationState& state) { +void PeerLogicValidation::BlockChecked(const CBlock& block, const BlockValidationState& state) { LOCK(cs_main); const uint256 hash(block.GetHash()); @@ -1240,7 +1263,7 @@ void PeerLogicValidation::BlockChecked(const CBlock& block, const CValidationSta if (state.IsInvalid() && it != mapBlockSource.end() && State(it->second.first)) { - MaybePunishNode(/*nodeid=*/ it->second.first, state, /*via_compact_block=*/ !it->second.second); + MaybePunishNodeForBlock(/*nodeid=*/ it->second.first, state, /*via_compact_block=*/ !it->second.second); } // Check that: // 1. The block is valid @@ -1378,7 +1401,7 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c } } // release cs_main before calling ActivateBestChain if (need_activate_chain) { - CValidationState state; + BlockValidationState state; if (!ActivateBestChain(state, Params(), a_recent_block)) { LogPrint(BCLog::NET, "failed to activate chain (%s)\n", FormatStateMessage(state)); } @@ -1674,11 +1697,10 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve } } - CValidationState state; - CBlockHeader first_invalid_header; - if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast, &first_invalid_header)) { + BlockValidationState state; + if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast)) { if (state.IsInvalid()) { - MaybePunishNode(pfrom->GetId(), state, via_compact_block, "invalid header received"); + MaybePunishNodeForBlock(pfrom->GetId(), state, via_compact_block, "invalid header received"); return false; } } @@ -1814,14 +1836,13 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se const CTransactionRef porphanTx = orphan_it->second.tx; const CTransaction& orphanTx = *porphanTx; NodeId fromPeer = orphan_it->second.fromPeer; - bool fMissingInputs2 = false; - // Use a new CValidationState because orphans come from different peers (and we call - // MaybePunishNode based on the source peer from the orphan map, not based on the peer + // Use a new TxValidationState because orphans come from different peers (and we call + // MaybePunishNodeForTx based on the source peer from the orphan map, not based on the peer // that relayed the previous transaction). - CValidationState orphan_state; + TxValidationState orphan_state; if (setMisbehaving.count(fromPeer)) continue; - if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &fMissingInputs2, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { + if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); RelayTransaction(orphanHash, *connman); for (unsigned int i = 0; i < orphanTx.vout.size(); i++) { @@ -1834,10 +1855,10 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se } EraseOrphanTx(orphanHash); done = true; - } else if (!fMissingInputs2) { + } else if (orphan_state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) { if (orphan_state.IsInvalid()) { // Punish peer that gave us an invalid orphan tx - if (MaybePunishNode(fromPeer, orphan_state, /*via_compact_block*/ false)) { + if (MaybePunishNodeForTx(fromPeer, orphan_state)) { setMisbehaving.insert(fromPeer); } LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n", orphanHash.ToString()); @@ -1845,8 +1866,7 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se // Has inputs but not accepted to mempool // Probably non-standard or insufficient fee LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", orphanHash.ToString()); - assert(IsTransactionReason(orphan_state.GetReason())); - if (!orphanTx.HasWitness() && orphan_state.GetReason() != ValidationInvalidReason::TX_WITNESS_MUTATED) { + if (!orphanTx.HasWitness() && orphan_state.GetResult() != TxValidationResult::TX_WITNESS_MUTATED) { // Do not use rejection cache for witness transactions or // witness-stripped transactions, as they can have been malleated. // See https://github.com/bitcoin/bitcoin/issues/8279 for details. @@ -1860,7 +1880,7 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se } } -bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CConnman* connman, const std::atomic<bool>& interruptMsgProc) +bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CConnman* connman, BanMan* banman, const std::atomic<bool>& interruptMsgProc) { LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(strCommand), vRecv.size(), pfrom->GetId()); if (gArgs.IsArgSet("-dropmessagestest") && GetRand(gArgs.GetArg("-dropmessagestest", 0)) == 0) @@ -2136,7 +2156,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60) addr.nTime = nNow - 5 * 24 * 60 * 60; pfrom->AddAddressKnown(addr); - if (g_banman->IsBanned(addr)) continue; // Do not process banned addresses beyond remembering we received them + if (banman->IsBanned(addr)) continue; // Do not process banned addresses beyond remembering we received them bool fReachable = IsReachable(addr); if (addr.nTime > nSince && !pfrom->fGetAddr && vAddr.size() <= 10 && addr.IsRoutable()) { @@ -2291,7 +2311,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LOCK(cs_most_recent_block); a_recent_block = most_recent_block; } - CValidationState state; + BlockValidationState state; if (!ActivateBestChain(state, Params(), a_recent_block)) { LogPrint(BCLog::NET, "failed to activate chain (%s)\n", FormatStateMessage(state)); } @@ -2471,8 +2491,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LOCK2(cs_main, g_cs_orphans); - bool fMissingInputs = false; - CValidationState state; + TxValidationState state; CNodeState* nodestate = State(pfrom->GetId()); nodestate->m_tx_download.m_tx_announced.erase(inv.hash); @@ -2482,7 +2501,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr std::list<CTransactionRef> lRemovedTxn; if (!AlreadyHave(inv) && - AcceptToMemoryPool(mempool, state, ptx, &fMissingInputs, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { + AcceptToMemoryPool(mempool, state, ptx, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { mempool.check(&::ChainstateActive().CoinsTip()); RelayTransaction(tx.GetHash(), *connman); for (unsigned int i = 0; i < tx.vout.size(); i++) { @@ -2504,7 +2523,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // Recursively process any orphan transactions that depended on this one ProcessOrphanTx(connman, pfrom->orphan_work_set, lRemovedTxn); } - else if (fMissingInputs) + else if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { bool fRejectedParents = false; // It may be the case that the orphans parents have all been rejected for (const CTxIn& txin : tx.vin) { @@ -2537,8 +2556,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr recentRejects->insert(tx.GetHash()); } } else { - assert(IsTransactionReason(state.GetReason())); - if (!tx.HasWitness() && state.GetReason() != ValidationInvalidReason::TX_WITNESS_MUTATED) { + if (!tx.HasWitness() && state.GetResult() != TxValidationResult::TX_WITNESS_MUTATED) { // Do not use rejection cache for witness transactions or // witness-stripped transactions, as they can have been malleated. // See https://github.com/bitcoin/bitcoin/issues/8279 for details. @@ -2593,7 +2611,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LogPrint(BCLog::MEMPOOLREJ, "%s from peer=%d was not accepted: %s\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); - MaybePunishNode(pfrom->GetId(), state, /*via_compact_block*/ false); + MaybePunishNodeForTx(pfrom->GetId(), state); } return true; } @@ -2627,10 +2645,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } const CBlockIndex *pindex = nullptr; - CValidationState state; + BlockValidationState state; if (!ProcessNewBlockHeaders({cmpctblock.header}, state, chainparams, &pindex)) { if (state.IsInvalid()) { - MaybePunishNode(pfrom->GetId(), state, /*via_compact_block*/ true, "invalid header via cmpctblock"); + MaybePunishNodeForBlock(pfrom->GetId(), state, /*via_compact_block*/ true, "invalid header via cmpctblock"); return true; } } @@ -2772,7 +2790,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } // cs_main if (fProcessBLOCKTXN) - return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, chainparams, connman, interruptMsgProc); + return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, chainparams, connman, banman, interruptMsgProc); if (fRevertToHeaderProcessing) { // Headers received from HB compact block peers are permitted to be @@ -2990,7 +3008,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr std::vector<CAddress> vAddr = connman->GetAddresses(); FastRandomContext insecure_rand; for (const CAddress &addr : vAddr) { - if (!g_banman->IsBanned(addr)) { + if (!banman->IsBanned(addr)) { pfrom->PushAddress(addr, insecure_rand); } } @@ -3310,7 +3328,7 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter bool fRet = false; try { - fRet = ProcessMessage(pfrom, strCommand, vRecv, msg.m_time, chainparams, connman, interruptMsgProc); + fRet = ProcessMessage(pfrom, strCommand, vRecv, msg.m_time, chainparams, connman, m_banman, interruptMsgProc); if (interruptMsgProc) return false; if (!pfrom->vRecvGetData.empty()) diff --git a/src/net_processing.h b/src/net_processing.h index e8bc3580dd..4adb7d3a21 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -40,7 +40,7 @@ public: /** * Overridden from CValidationInterface. */ - void BlockChecked(const CBlock& block, const CValidationState& state) override; + void BlockChecked(const CBlock& block, const BlockValidationState& state) override; /** * Overridden from CValidationInterface. */ diff --git a/src/node/context.cpp b/src/node/context.cpp new file mode 100644 index 0000000000..26a01420c8 --- /dev/null +++ b/src/node/context.cpp @@ -0,0 +1,13 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/context.h> + +#include <banman.h> +#include <interfaces/chain.h> +#include <net.h> +#include <net_processing.h> + +NodeContext::NodeContext() {} +NodeContext::~NodeContext() {} diff --git a/src/node/context.h b/src/node/context.h new file mode 100644 index 0000000000..2b124af4db --- /dev/null +++ b/src/node/context.h @@ -0,0 +1,44 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_CONTEXT_H +#define BITCOIN_NODE_CONTEXT_H + +#include <memory> +#include <vector> + +class BanMan; +class CConnman; +class PeerLogicValidation; +namespace interfaces { +class Chain; +class ChainClient; +} // namespace interfaces + +//! NodeContext struct containing references to chain state and connection +//! state. +//! +//! This is used by init, rpc, and test code to pass object references around +//! without needing to declare the same variables and parameters repeatedly, or +//! to use globals. More variables could be added to this struct (particularly +//! references to validation and mempool objects) to eliminate use of globals +//! and make code more modular and testable. The struct isn't intended to have +//! any member functions. It should just be a collection of references that can +//! be used without pulling in unwanted dependencies or functionality. +struct NodeContext +{ + std::unique_ptr<CConnman> connman; + std::unique_ptr<PeerLogicValidation> peer_logic; + std::unique_ptr<BanMan> banman; + std::unique_ptr<interfaces::Chain> chain; + std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients; + + //! Declare default constructor and destructor that are not inline, so code + //! instantiating the NodeContext struct doesn't need to #include class + //! definitions for all the unique_ptr members. + NodeContext(); + ~NodeContext(); +}; + +#endif // BITCOIN_NODE_CONTEXT_H diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 7783671a6c..2da3ecd8e3 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -6,6 +6,7 @@ #include <consensus/validation.h> #include <net.h> #include <net_processing.h> +#include <node/context.h> #include <util/validation.h> #include <validation.h> #include <validationinterface.h> @@ -13,12 +14,12 @@ #include <future> -TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) +TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) { // BroadcastTransaction can be called by either sendrawtransaction RPC or wallet RPCs. - // g_connman is assigned both before chain clients and before RPC server is accepting calls, - // and reset after chain clients and RPC sever are stopped. g_connman should never be null here. - assert(g_connman); + // node.connman is assigned both before chain clients and before RPC server is accepting calls, + // and reset after chain clients and RPC sever are stopped. node.connman should never be null here. + assert(node.connman); std::promise<void> promise; uint256 hashTx = tx->GetHash(); bool callback_set = false; @@ -36,18 +37,16 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err } if (!mempool.exists(hashTx)) { // Transaction is not already in the mempool. Submit it. - CValidationState state; - bool fMissingInputs; - if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs, + TxValidationState state; + if (!AcceptToMemoryPool(mempool, state, std::move(tx), nullptr /* plTxnReplaced */, false /* bypass_limits */, max_tx_fee)) { + err_string = FormatStateMessage(state); if (state.IsInvalid()) { - err_string = FormatStateMessage(state); - return TransactionError::MEMPOOL_REJECTED; - } else { - if (fMissingInputs) { + if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { return TransactionError::MISSING_INPUTS; } - err_string = FormatStateMessage(state); + return TransactionError::MEMPOOL_REJECTED; + } else { return TransactionError::MEMPOOL_ERROR; } } @@ -79,7 +78,7 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err } if (relay) { - RelayTransaction(hashTx, *g_connman); + RelayTransaction(hashTx, *node.connman); } return TransactionError::OK; diff --git a/src/node/transaction.h b/src/node/transaction.h index a3e56544a7..35873d8376 100644 --- a/src/node/transaction.h +++ b/src/node/transaction.h @@ -9,6 +9,8 @@ #include <primitives/transaction.h> #include <util/error.h> +struct NodeContext; + /** * Submit a transaction to the mempool and (optionally) relay it to all P2P peers. * @@ -18,6 +20,7 @@ * NOT be set while cs_main, cs_mempool or cs_wallet are held to avoid * deadlock. * + * @param[in] node reference to node context * @param[in] tx the transaction to broadcast * @param[out] &err_string reference to std::string to fill with error string if available * @param[in] max_tx_fee reject txs with fees higher than this (if 0, accept any fee) @@ -25,6 +28,6 @@ * @param[in] wait_callback, wait until callbacks have been processed to avoid stale result due to a sequentially RPC. * return error */ -NODISCARD TransactionError BroadcastTransaction(CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback); +NODISCARD TransactionError BroadcastTransaction(NodeContext& node, CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback); #endif // BITCOIN_NODE_TRANSACTION_H diff --git a/src/psbt.h b/src/psbt.h index 802a7c5ba7..9d996171bb 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -387,7 +387,7 @@ struct PSBTOutput /** A version of CTransaction with the PSBT format*/ struct PartiallySignedTransaction { - boost::optional<CMutableTransaction> tx; + Optional<CMutableTransaction> tx; std::vector<PSBTInput> inputs; std::vector<PSBTOutput> outputs; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 4fe440a679..8b32b70d1e 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -51,11 +51,10 @@ void EditAddressAndSubmit( * In each case, verify the resulting state of the address book and optionally * the warning message presented to the user. */ -void TestAddAddressesToSendBook() +void TestAddAddressesToSendBook(interfaces::Node& node) { TestChain100Setup test; - auto chain = interfaces::MakeChain(); - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateMock()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); @@ -101,10 +100,9 @@ void TestAddAddressesToSendBook() // Initialize relevant QT models. std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); - auto node = interfaces::MakeNode(); - OptionsModel optionsModel(*node); + OptionsModel optionsModel(node); AddWallet(wallet); - WalletModel walletModel(std::move(node->getWallets()[0]), *node, platformStyle.get(), &optionsModel); + WalletModel walletModel(interfaces::MakeWallet(wallet), node, platformStyle.get(), &optionsModel); RemoveWallet(wallet); EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress); editAddressDialog.setModel(walletModel.getAddressTableModel()); @@ -150,5 +148,5 @@ void AddressBookTests::addressBookTests() return; } #endif - TestAddAddressesToSendBook(); + TestAddAddressesToSendBook(m_node); } diff --git a/src/qt/test/addressbooktests.h b/src/qt/test/addressbooktests.h index beeb9e76a9..9944750ec8 100644 --- a/src/qt/test/addressbooktests.h +++ b/src/qt/test/addressbooktests.h @@ -4,8 +4,16 @@ #include <QObject> #include <QTest> +namespace interfaces { +class Node; +} // namespace interfaces + class AddressBookTests : public QObject { +public: + AddressBookTests(interfaces::Node& node) : m_node(node) {} + interfaces::Node& m_node; + Q_OBJECT private Q_SLOTS: diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index 3c2ffa6c00..1772de4c1b 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -41,7 +41,7 @@ void RPCNestedTests::rpcNestedTests() std::string result; std::string result2; std::string filtered; - auto node = interfaces::MakeNode(); + interfaces::Node* node = &m_node; RPCConsole::RPCExecuteCommandLine(*node, result, "getblockchaininfo()[chain]", &filtered); //simple result filtering with path QVERIFY(result=="main"); QVERIFY(filtered == "getblockchaininfo()[chain]"); diff --git a/src/qt/test/rpcnestedtests.h b/src/qt/test/rpcnestedtests.h index 97143ff78a..8789fe8373 100644 --- a/src/qt/test/rpcnestedtests.h +++ b/src/qt/test/rpcnestedtests.h @@ -8,8 +8,16 @@ #include <QObject> #include <QTest> +namespace interfaces { +class Node; +} // namespace interfaces + class RPCNestedTests : public QObject { +public: + RPCNestedTests(interfaces::Node& node) : m_node(node) {} + interfaces::Node& m_node; + Q_OBJECT private Q_SLOTS: diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index f272627f96..e6870cf1be 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -50,7 +50,7 @@ int main(int argc, char *argv[]) BasicTestingSetup dummy{CBaseChainParams::REGTEST}; } - auto node = interfaces::MakeNode(); + std::unique_ptr<interfaces::Node> node = interfaces::MakeNode(); bool fInvalid = false; @@ -76,7 +76,7 @@ int main(int argc, char *argv[]) if (QTest::qExec(&test1) != 0) { fInvalid = true; } - RPCNestedTests test3; + RPCNestedTests test3(*node); if (QTest::qExec(&test3) != 0) { fInvalid = true; } @@ -85,11 +85,11 @@ int main(int argc, char *argv[]) fInvalid = true; } #ifdef ENABLE_WALLET - WalletTests test5; + WalletTests test5(*node); if (QTest::qExec(&test5) != 0) { fInvalid = true; } - AddressBookTests test6; + AddressBookTests test6(*node); if (QTest::qExec(&test6) != 0) { fInvalid = true; } diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index eea874c0d4..881653cdac 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -126,21 +126,23 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st // QT_QPA_PLATFORM=xcb src/qt/test/test_bitcoin-qt # Linux // QT_QPA_PLATFORM=windows src/qt/test/test_bitcoin-qt # Windows // QT_QPA_PLATFORM=cocoa src/qt/test/test_bitcoin-qt # macOS -void TestGUI() +void TestGUI(interfaces::Node& node) { // Set up wallet and chain with 105 blocks (5 mature blocks for spending). TestChain100Setup test; for (int i = 0; i < 5; ++i) { test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); } - auto chain = interfaces::MakeChain(); - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateMock()); + node.context()->connman = std::move(test.m_node.connman); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); { + auto spk_man = wallet->GetLegacyScriptPubKeyMan(); LOCK(wallet->cs_wallet); + AssertLockHeld(spk_man->cs_wallet); wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive"); - wallet->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); + spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); } { auto locked_chain = wallet->chain().lock(); @@ -159,10 +161,9 @@ void TestGUI() std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); SendCoinsDialog sendCoinsDialog(platformStyle.get()); TransactionView transactionView(platformStyle.get()); - auto node = interfaces::MakeNode(); - OptionsModel optionsModel(*node); + OptionsModel optionsModel(node); AddWallet(wallet); - WalletModel walletModel(std::move(node->getWallets().back()), *node, platformStyle.get(), &optionsModel); + WalletModel walletModel(interfaces::MakeWallet(wallet), node, platformStyle.get(), &optionsModel); RemoveWallet(wallet); sendCoinsDialog.setModel(&walletModel); transactionView.setModel(&walletModel); @@ -260,5 +261,5 @@ void WalletTests::walletTests() return; } #endif - TestGUI(); + TestGUI(m_node); } diff --git a/src/qt/test/wallettests.h b/src/qt/test/wallettests.h index 342f7916c3..0a7b57a678 100644 --- a/src/qt/test/wallettests.h +++ b/src/qt/test/wallettests.h @@ -4,8 +4,16 @@ #include <QObject> #include <QTest> +namespace interfaces { +class Node; +} // namespace interfaces + class WalletTests : public QObject { + public: + WalletTests(interfaces::Node& node) : m_node(node) {} + interfaces::Node& m_node; + Q_OBJECT private Q_SLOTS: diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index fac6bcd60d..4ca8225392 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1469,7 +1469,7 @@ static UniValue preciousblock(const JSONRPCRequest& request) } } - CValidationState state; + BlockValidationState state; PreciousBlock(state, Params(), pblockindex); if (!state.IsValid()) { @@ -1494,7 +1494,7 @@ static UniValue invalidateblock(const JSONRPCRequest& request) }.Check(request); uint256 hash(ParseHashV(request.params[0], "blockhash")); - CValidationState state; + BlockValidationState state; CBlockIndex* pblockindex; { @@ -1544,7 +1544,7 @@ static UniValue reconsiderblock(const JSONRPCRequest& request) ResetBlockFailureFlags(pblockindex); } - CValidationState state; + BlockValidationState state; ActivateBestChain(state, Params()); if (!state.IsValid()) { @@ -2289,3 +2289,5 @@ void RegisterBlockchainRPCCommands(CRPCTable &t) for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) t.appendCommand(commands[vcidx].name, &commands[vcidx]); } + +NodeContext* g_rpc_node = nullptr; diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index ff461fbcbc..8a1264f824 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -17,6 +17,7 @@ class CBlock; class CBlockIndex; class CTxMemPool; class UniValue; +struct NodeContext; static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5; @@ -46,4 +47,9 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex /** Used by getblockstats to get feerates at different percentiles by weight */ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight); +//! Pointer to node state that needs to be declared as a global to be accessible +//! RPC methods. Due to limitations of the RPC framework, there's currently no +//! direct way to pass in state to RPC methods without globals. +extern NodeContext* g_rpc_node; + #endif diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 32e18312e1..dfca1697c1 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -30,6 +30,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "utxoupdatepsbt", 1, "descriptors" }, { "generatetoaddress", 0, "nblocks" }, { "generatetoaddress", 2, "maxtries" }, + { "generatetodescriptor", 0, "num_blocks" }, + { "generatetodescriptor", 2, "maxtries" }, { "getnetworkhashps", 0, "nblocks" }, { "getnetworkhashps", 1, "height" }, { "sendtoaddress", 1, "amount" }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 07c2958635..b3158d1e0c 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -13,12 +13,15 @@ #include <key_io.h> #include <miner.h> #include <net.h> +#include <node/context.h> #include <policy/fees.h> #include <pow.h> #include <rpc/blockchain.h> #include <rpc/server.h> #include <rpc/util.h> +#include <script/descriptor.h> #include <script/script.h> +#include <script/signingprovider.h> #include <shutdown.h> #include <txmempool.h> #include <univalue.h> @@ -140,6 +143,47 @@ static UniValue generateBlocks(const CScript& coinbase_script, int nGenerate, ui return blockHashes; } +static UniValue generatetodescriptor(const JSONRPCRequest& request) +{ + RPCHelpMan{ + "generatetodescriptor", + "\nMine blocks immediately to a specified descriptor (before the RPC call returns)\n", + { + {"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."}, + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor to send the newly generated bitcoin to."}, + {"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."}, + }, + RPCResult{ + "[ blockhashes ] (array) hashes of blocks generated\n"}, + RPCExamples{ + "\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")}, + } + .Check(request); + + const int num_blocks{request.params[0].get_int()}; + const int64_t max_tries{request.params[2].isNull() ? 1000000 : request.params[2].get_int()}; + + FlatSigningProvider key_provider; + std::string error; + const auto desc = Parse(request.params[1].get_str(), key_provider, error, /* require_checksum = */ false); + if (!desc) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); + } + if (desc->IsRange()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptor not accepted. Maybe pass through deriveaddresses first?"); + } + + FlatSigningProvider provider; + std::vector<CScript> coinbase_script; + if (!desc->Expand(0, key_provider, coinbase_script, provider)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys")); + } + + CHECK_NONFATAL(coinbase_script.size() == 1); + + return generateBlocks(coinbase_script.at(0), num_blocks, max_tries); +} + static UniValue generatetoaddress(const JSONRPCRequest& request) { RPCHelpMan{"generatetoaddress", @@ -252,7 +296,7 @@ static UniValue prioritisetransaction(const JSONRPCRequest& request) // NOTE: Assumes a conclusive result; if result is inconclusive, it must be handled by caller -static UniValue BIP22ValidationResult(const CValidationState& state) +static UniValue BIP22ValidationResult(const BlockValidationState& state) { if (state.IsValid()) return NullUniValue; @@ -401,7 +445,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) // TestBlockValidity only supports blocks built on the current Tip if (block.hashPrevBlock != pindexPrev->GetBlockHash()) return "inconclusive-not-best-prevblk"; - CValidationState state; + BlockValidationState state; TestBlockValidity(state, Params(), block, pindexPrev, false, true); return BIP22ValidationResult(state); } @@ -424,10 +468,10 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) if (strMode != "template") throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - if (g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) + if (g_rpc_node->connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!"); if (::ChainstateActive().IsInitialBlockDownload()) @@ -668,12 +712,12 @@ class submitblock_StateCatcher : public CValidationInterface public: uint256 hash; bool found; - CValidationState state; + BlockValidationState state; explicit submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), found(false), state() {} protected: - void BlockChecked(const CBlock& block, const CValidationState& stateIn) override { + void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override { if (block.GetHash() != hash) return; found = true; @@ -772,8 +816,8 @@ static UniValue submitheader(const JSONRPCRequest& request) } } - CValidationState state; - ProcessNewBlockHeaders({h}, state, Params(), /* ppindex */ nullptr, /* first_invalid */ nullptr); + BlockValidationState state; + ProcessNewBlockHeaders({h}, state, Params()); if (state.IsValid()) return NullUniValue; if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, FormatStateMessage(state)); @@ -961,6 +1005,7 @@ static const CRPCCommand commands[] = { "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} }, + { "generating", "generatetodescriptor", &generatetodescriptor, {"num_blocks","descriptor","maxtries"} }, { "util", "estimatesmartfee", &estimatesmartfee, {"conf_target", "estimate_mode"} }, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 7b1507e4dc..f443f37c6d 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -11,7 +11,9 @@ #include <net_processing.h> #include <net_permissions.h> #include <netbase.h> +#include <node/context.h> #include <policy/settings.h> +#include <rpc/blockchain.h> #include <rpc/protocol.h> #include <rpc/util.h> #include <sync.h> @@ -38,10 +40,10 @@ static UniValue getconnectioncount(const JSONRPCRequest& request) }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - return (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL); + return (int)g_rpc_node->connman->GetNodeCount(CConnman::CONNECTIONS_ALL); } static UniValue ping(const JSONRPCRequest& request) @@ -58,11 +60,11 @@ static UniValue ping(const JSONRPCRequest& request) }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); // Request that each node send a ping during next message processing pass - g_connman->ForEachNode([](CNode* pnode) { + g_rpc_node->connman->ForEachNode([](CNode* pnode) { pnode->fPingQueued = true; }); return NullUniValue; @@ -131,11 +133,11 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); std::vector<CNodeStats> vstats; - g_connman->GetNodeStats(vstats); + g_rpc_node->connman->GetNodeStats(vstats); UniValue ret(UniValue::VARR); @@ -234,7 +236,7 @@ static UniValue addnode(const JSONRPCRequest& request) }, }.ToString()); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); std::string strNode = request.params[0].get_str(); @@ -242,18 +244,18 @@ static UniValue addnode(const JSONRPCRequest& request) if (strCommand == "onetry") { CAddress addr; - g_connman->OpenNetworkConnection(addr, false, nullptr, strNode.c_str(), false, false, true); + g_rpc_node->connman->OpenNetworkConnection(addr, false, nullptr, strNode.c_str(), false, false, true); return NullUniValue; } if (strCommand == "add") { - if(!g_connman->AddNode(strNode)) + if(!g_rpc_node->connman->AddNode(strNode)) throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Node already added"); } else if(strCommand == "remove") { - if(!g_connman->RemoveAddedNode(strNode)) + if(!g_rpc_node->connman->RemoveAddedNode(strNode)) throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED, "Error: Node has not been added."); } @@ -279,7 +281,7 @@ static UniValue disconnectnode(const JSONRPCRequest& request) }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); bool success; @@ -288,11 +290,11 @@ static UniValue disconnectnode(const JSONRPCRequest& request) if (!address_arg.isNull() && id_arg.isNull()) { /* handle disconnect-by-address */ - success = g_connman->DisconnectNode(address_arg.get_str()); + success = g_rpc_node->connman->DisconnectNode(address_arg.get_str()); } else if (!id_arg.isNull() && (address_arg.isNull() || (address_arg.isStr() && address_arg.get_str().empty()))) { /* handle disconnect-by-id */ NodeId nodeid = (NodeId) id_arg.get_int64(); - success = g_connman->DisconnectNode(nodeid); + success = g_rpc_node->connman->DisconnectNode(nodeid); } else { throw JSONRPCError(RPC_INVALID_PARAMS, "Only one of address and nodeid should be provided."); } @@ -333,10 +335,10 @@ static UniValue getaddednodeinfo(const JSONRPCRequest& request) }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - std::vector<AddedNodeInfo> vInfo = g_connman->GetAddedNodeInfo(); + std::vector<AddedNodeInfo> vInfo = g_rpc_node->connman->GetAddedNodeInfo(); if (!request.params[0].isNull()) { bool found = false; @@ -399,21 +401,21 @@ static UniValue getnettotals(const JSONRPCRequest& request) + HelpExampleRpc("getnettotals", "") }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); UniValue obj(UniValue::VOBJ); - obj.pushKV("totalbytesrecv", g_connman->GetTotalBytesRecv()); - obj.pushKV("totalbytessent", g_connman->GetTotalBytesSent()); + obj.pushKV("totalbytesrecv", g_rpc_node->connman->GetTotalBytesRecv()); + obj.pushKV("totalbytessent", g_rpc_node->connman->GetTotalBytesSent()); obj.pushKV("timemillis", GetTimeMillis()); UniValue outboundLimit(UniValue::VOBJ); - outboundLimit.pushKV("timeframe", g_connman->GetMaxOutboundTimeframe()); - outboundLimit.pushKV("target", g_connman->GetMaxOutboundTarget()); - outboundLimit.pushKV("target_reached", g_connman->OutboundTargetReached(false)); - outboundLimit.pushKV("serve_historical_blocks", !g_connman->OutboundTargetReached(true)); - outboundLimit.pushKV("bytes_left_in_cycle", g_connman->GetOutboundTargetBytesLeft()); - outboundLimit.pushKV("time_left_in_cycle", g_connman->GetMaxOutboundTimeLeftInCycle()); + outboundLimit.pushKV("timeframe", g_rpc_node->connman->GetMaxOutboundTimeframe()); + outboundLimit.pushKV("target", g_rpc_node->connman->GetMaxOutboundTarget()); + outboundLimit.pushKV("target_reached", g_rpc_node->connman->OutboundTargetReached(false)); + outboundLimit.pushKV("serve_historical_blocks", !g_rpc_node->connman->OutboundTargetReached(true)); + outboundLimit.pushKV("bytes_left_in_cycle", g_rpc_node->connman->GetOutboundTargetBytesLeft()); + outboundLimit.pushKV("time_left_in_cycle", g_rpc_node->connman->GetMaxOutboundTimeLeftInCycle()); obj.pushKV("uploadtarget", outboundLimit); return obj; } @@ -492,16 +494,16 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) obj.pushKV("version", CLIENT_VERSION); obj.pushKV("subversion", strSubVersion); obj.pushKV("protocolversion",PROTOCOL_VERSION); - if (g_connman) { - ServiceFlags services = g_connman->GetLocalServices(); + if (g_rpc_node->connman) { + ServiceFlags services = g_rpc_node->connman->GetLocalServices(); obj.pushKV("localservices", strprintf("%016x", services)); obj.pushKV("localservicesnames", GetServicesNames(services)); } obj.pushKV("localrelay", g_relay_txes); obj.pushKV("timeoffset", GetTimeOffset()); - if (g_connman) { - obj.pushKV("networkactive", g_connman->GetNetworkActive()); - obj.pushKV("connections", (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL)); + if (g_rpc_node->connman) { + obj.pushKV("networkactive", g_rpc_node->connman->GetNetworkActive()); + obj.pushKV("connections", (int)g_rpc_node->connman->GetNodeCount(CConnman::CONNECTIONS_ALL)); } obj.pushKV("networks", GetNetworksInfo()); obj.pushKV("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); @@ -546,7 +548,7 @@ static UniValue setban(const JSONRPCRequest& request) if (request.fHelp || !help.IsValidNumArgs(request.params.size()) || (strCommand != "add" && strCommand != "remove")) { throw std::runtime_error(help.ToString()); } - if (!g_banman) { + if (!g_rpc_node->banman) { throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); } @@ -570,7 +572,7 @@ static UniValue setban(const JSONRPCRequest& request) if (strCommand == "add") { - if (isSubnet ? g_banman->IsBanned(subNet) : g_banman->IsBanned(netAddr)) { + if (isSubnet ? g_rpc_node->banman->IsBanned(subNet) : g_rpc_node->banman->IsBanned(netAddr)) { throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned"); } @@ -583,20 +585,20 @@ static UniValue setban(const JSONRPCRequest& request) absolute = true; if (isSubnet) { - g_banman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute); - if (g_connman) { - g_connman->DisconnectNode(subNet); + g_rpc_node->banman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute); + if (g_rpc_node->connman) { + g_rpc_node->connman->DisconnectNode(subNet); } } else { - g_banman->Ban(netAddr, BanReasonManuallyAdded, banTime, absolute); - if (g_connman) { - g_connman->DisconnectNode(netAddr); + g_rpc_node->banman->Ban(netAddr, BanReasonManuallyAdded, banTime, absolute); + if (g_rpc_node->connman) { + g_rpc_node->connman->DisconnectNode(netAddr); } } } else if(strCommand == "remove") { - if (!( isSubnet ? g_banman->Unban(subNet) : g_banman->Unban(netAddr) )) { + if (!( isSubnet ? g_rpc_node->banman->Unban(subNet) : g_rpc_node->banman->Unban(netAddr) )) { throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Unban failed. Requested address/subnet was not previously banned."); } } @@ -615,12 +617,12 @@ static UniValue listbanned(const JSONRPCRequest& request) }, }.Check(request); - if(!g_banman) { + if(!g_rpc_node->banman) { throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); } banmap_t banMap; - g_banman->GetBanned(banMap); + g_rpc_node->banman->GetBanned(banMap); UniValue bannedAddresses(UniValue::VARR); for (const auto& entry : banMap) @@ -649,11 +651,11 @@ static UniValue clearbanned(const JSONRPCRequest& request) + HelpExampleRpc("clearbanned", "") }, }.Check(request); - if (!g_banman) { + if (!g_rpc_node->banman) { throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); } - g_banman->ClearBanned(); + g_rpc_node->banman->ClearBanned(); return NullUniValue; } @@ -669,13 +671,13 @@ static UniValue setnetworkactive(const JSONRPCRequest& request) RPCExamples{""}, }.Check(request); - if (!g_connman) { + if (!g_rpc_node->connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } - g_connman->SetNetworkActive(request.params[0].get_bool()); + g_rpc_node->connman->SetNetworkActive(request.params[0].get_bool()); - return g_connman->GetNetworkActive(); + return g_rpc_node->connman->GetNetworkActive(); } static UniValue getnodeaddresses(const JSONRPCRequest& request) @@ -701,7 +703,7 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request) + HelpExampleRpc("getnodeaddresses", "8") }, }.Check(request); - if (!g_connman) { + if (!g_rpc_node->connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } @@ -713,7 +715,7 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request) } } // returns a shuffled list of CAddress - std::vector<CAddress> vAddr = g_connman->GetAddresses(); + std::vector<CAddress> vAddr = g_rpc_node->connman->GetAddresses(); UniValue ret(UniValue::VARR); int address_return_count = std::min<int>(count, vAddr.size()); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 487f74c3e1..17380f113f 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -11,6 +11,7 @@ #include <key_io.h> #include <merkleblock.h> #include <node/coin.h> +#include <node/context.h> #include <node/psbt.h> #include <node/transaction.h> #include <policy/policy.h> @@ -18,6 +19,7 @@ #include <primitives/transaction.h> #include <psbt.h> #include <random.h> +#include <rpc/blockchain.h> #include <rpc/rawtransaction_util.h> #include <rpc/server.h> #include <rpc/util.h> @@ -817,7 +819,7 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) std::string err_string; AssertLockNotHeld(cs_main); - const TransactionError err = BroadcastTransaction(tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true); + const TransactionError err = BroadcastTransaction(*g_rpc_node, tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true); if (TransactionError::OK != err) { throw JSONRPCTransactionError(err, err_string); } @@ -893,20 +895,21 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) UniValue result_0(UniValue::VOBJ); result_0.pushKV("txid", tx_hash.GetHex()); - CValidationState state; - bool missing_inputs; + TxValidationState state; bool test_accept_res; { LOCK(cs_main); - test_accept_res = AcceptToMemoryPool(mempool, state, std::move(tx), &missing_inputs, + test_accept_res = AcceptToMemoryPool(mempool, state, std::move(tx), nullptr /* plTxnReplaced */, false /* bypass_limits */, max_raw_tx_fee, /* test_accept */ true); } result_0.pushKV("allowed", test_accept_res); if (!test_accept_res) { if (state.IsInvalid()) { - result_0.pushKV("reject-reason", strprintf("%s", state.GetRejectReason())); - } else if (missing_inputs) { - result_0.pushKV("reject-reason", "missing-inputs"); + if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { + result_0.pushKV("reject-reason", "missing-inputs"); + } else { + result_0.pushKV("reject-reason", strprintf("%s", state.GetRejectReason())); + } } else { result_0.pushKV("reject-reason", state.GetRejectReason()); } diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index adda90c104..653b287e97 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -13,8 +13,6 @@ #include <tuple> -InitInterfaces* g_rpc_interfaces = nullptr; - void RPCTypeCheck(const UniValue& params, const std::list<UniValueType>& typesExpected, bool fAllowNull) diff --git a/src/rpc/util.h b/src/rpc/util.h index ec36956c95..221638aa9e 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -25,12 +25,6 @@ class FillableSigningProvider; 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; /** Wrapper for UniValue::VType, which includes typeAny: * Used to denote don't care type. */ diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index 4eec2311d4..c40fecac5c 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -63,8 +63,6 @@ FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvide class FillableSigningProvider : public SigningProvider { protected: - mutable CCriticalSection cs_KeyStore; - using KeyMap = std::map<CKeyID, CKey>; using ScriptMap = std::map<CScriptID, CScript>; @@ -74,6 +72,8 @@ protected: void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); public: + mutable CCriticalSection cs_KeyStore; + virtual bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); virtual bool AddKey(const CKey &key) { return AddKeyPubKey(key, key.GetPubKey()); } virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index ba293b7836..4a15bf0c77 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -102,8 +102,8 @@ static bool BuildChain(const CBlockIndex* pindex, const CScript& coinbase_script block = std::make_shared<CBlock>(CreateBlock(pindex, no_txns, coinbase_script_pub_key)); CBlockHeader header = block->GetBlockHeader(); - CValidationState state; - if (!ProcessNewBlockHeaders({header}, state, Params(), &pindex, nullptr)) { + BlockValidationState state; + if (!ProcessNewBlockHeaders({header}, state, Params(), &pindex)) { return false; } } diff --git a/src/test/fuzz/eval_script.cpp b/src/test/fuzz/eval_script.cpp index 9444cd489e..7acdd76857 100644 --- a/src/test/fuzz/eval_script.cpp +++ b/src/test/fuzz/eval_script.cpp @@ -2,12 +2,19 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <pubkey.h> #include <script/interpreter.h> -#include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <util/memory.h> #include <limits> +void initialize() +{ + static const auto verify_handle = MakeUnique<ECCVerifyHandle>(); +} + void test_one_input(const std::vector<uint8_t>& buffer) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); diff --git a/src/test/fuzz/parse_iso8601.cpp b/src/test/fuzz/parse_iso8601.cpp new file mode 100644 index 0000000000..c86f8a853e --- /dev/null +++ b/src/test/fuzz/parse_iso8601.cpp @@ -0,0 +1,32 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <util/time.h> + +#include <cassert> +#include <cstdint> +#include <string> +#include <vector> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + + const int64_t random_time = fuzzed_data_provider.ConsumeIntegral<int64_t>(); + const std::string random_string = fuzzed_data_provider.ConsumeRemainingBytesAsString(); + + const std::string iso8601_datetime = FormatISO8601DateTime(random_time); + const int64_t parsed_time_1 = ParseISO8601DateTime(iso8601_datetime); + if (random_time >= 0) { + assert(parsed_time_1 >= 0); + if (iso8601_datetime.length() == 20) { + assert(parsed_time_1 == random_time); + } + } + + const int64_t parsed_time_2 = ParseISO8601DateTime(random_string); + assert(parsed_time_2 >= 0); +} diff --git a/src/test/fuzz/script_flags.cpp b/src/test/fuzz/script_flags.cpp index 0bf5cd5c72..08622d0979 100644 --- a/src/test/fuzz/script_flags.cpp +++ b/src/test/fuzz/script_flags.cpp @@ -2,8 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <pubkey.h> #include <script/interpreter.h> #include <streams.h> +#include <util/memory.h> #include <version.h> #include <test/fuzz/fuzz.h> @@ -11,6 +13,11 @@ /** Flags that are not forbidden by an assert */ static bool IsValidFlagCombination(unsigned flags); +void initialize() +{ + static const auto verify_handle = MakeUnique<ECCVerifyHandle>(); +} + void test_one_input(const std::vector<uint8_t>& buffer) { CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp index 383d879040..76b230ef3c 100644 --- a/src/test/fuzz/transaction.cpp +++ b/src/test/fuzz/transaction.cpp @@ -42,7 +42,7 @@ void test_one_input(const std::vector<uint8_t>& buffer) return; } - CValidationState state_with_dupe_check; + TxValidationState state_with_dupe_check; (void)CheckTransaction(tx, state_with_dupe_check); const CFeeRate dust_relay_fee{DUST_RELAY_TX_FEE}; diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index fed65afdbf..f5f217b841 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -128,9 +128,8 @@ BOOST_AUTO_TEST_CASE(caddrdb_read) CDataStream ssPeers2 = AddrmanToStream(addrmanUncorrupted); CAddrMan addrman2; - CAddrDB adb; BOOST_CHECK(addrman2.size() == 0); - BOOST_CHECK(adb.Read(addrman2, ssPeers2)); + BOOST_CHECK(CAddrDB::Read(addrman2, ssPeers2)); BOOST_CHECK(addrman2.size() == 3); } @@ -160,9 +159,8 @@ BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted) CDataStream ssPeers2 = AddrmanToStream(addrmanCorrupted); CAddrMan addrman2; - CAddrDB adb; BOOST_CHECK(addrman2.size() == 0); - BOOST_CHECK(!adb.Read(addrman2, ssPeers2)); + BOOST_CHECK(!CAddrDB::Read(addrman2, ssPeers2)); BOOST_CHECK(addrman2.size() == 0); } diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 5ae0812243..faff1931cd 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -7,8 +7,8 @@ #include <rpc/util.h> #include <core_io.h> -#include <init.h> #include <interfaces/chain.h> +#include <node/context.h> #include <test/setup_common.h> #include <util/time.h> @@ -112,14 +112,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; + NodeContext node; + node.chain = interfaces::MakeChain(node); + g_rpc_node = &node; 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; + g_rpc_node = nullptr; } BOOST_AUTO_TEST_CASE(rpc_createraw_op_return) diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp index f451d80984..ec28d6a0ad 100644 --- a/src/test/script_p2sh_tests.cpp +++ b/src/test/script_p2sh_tests.cpp @@ -209,20 +209,21 @@ BOOST_AUTO_TEST_CASE(is) p2sh << OP_HASH160 << ToByteVector(dummy) << OP_EQUAL; BOOST_CHECK(p2sh.IsPayToScriptHash()); - // Not considered pay-to-script-hash if using one of the OP_PUSHDATA opcodes: std::vector<unsigned char> direct = {OP_HASH160, 20}; direct.insert(direct.end(), 20, 0); direct.push_back(OP_EQUAL); BOOST_CHECK(CScript(direct.begin(), direct.end()).IsPayToScriptHash()); + + // Not considered pay-to-script-hash if using one of the OP_PUSHDATA opcodes: std::vector<unsigned char> pushdata1 = {OP_HASH160, OP_PUSHDATA1, 20}; pushdata1.insert(pushdata1.end(), 20, 0); pushdata1.push_back(OP_EQUAL); BOOST_CHECK(!CScript(pushdata1.begin(), pushdata1.end()).IsPayToScriptHash()); - std::vector<unsigned char> pushdata2 = {OP_HASH160, 20, 0}; + std::vector<unsigned char> pushdata2 = {OP_HASH160, OP_PUSHDATA2, 20, 0}; pushdata2.insert(pushdata2.end(), 20, 0); pushdata2.push_back(OP_EQUAL); BOOST_CHECK(!CScript(pushdata2.begin(), pushdata2.end()).IsPayToScriptHash()); - std::vector<unsigned char> pushdata4 = {OP_HASH160, 20, 0, 0, 0}; + std::vector<unsigned char> pushdata4 = {OP_HASH160, OP_PUSHDATA4, 20, 0, 0, 0}; pushdata4.insert(pushdata4.end(), 20, 0); pushdata4.push_back(OP_EQUAL); BOOST_CHECK(!CScript(pushdata4.begin(), pushdata4.end()).IsPayToScriptHash()); diff --git a/src/test/setup_common.cpp b/src/test/setup_common.cpp index bbdf1ef830..3425bd59c1 100644 --- a/src/test/setup_common.cpp +++ b/src/test/setup_common.cpp @@ -15,6 +15,7 @@ #include <net.h> #include <noui.h> #include <pow.h> +#include <rpc/blockchain.h> #include <rpc/register.h> #include <rpc/server.h> #include <script/sigcache.h> @@ -76,6 +77,7 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha const CChainParams& chainparams = Params(); // Ideally we'd move all the RPC tests to the functional testing framework // instead of unit tests, but for now we need these here. + g_rpc_node = &m_node; RegisterAllCoreRPCCommands(tableRPC); // We have to run a scheduler thread to prevent ActivateBestChain @@ -95,7 +97,7 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha throw std::runtime_error("LoadGenesisBlock failed."); } - CValidationState state; + BlockValidationState state; if (!ActivateBestChain(state, chainparams)) { throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", FormatStateMessage(state))); } @@ -104,8 +106,8 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha for (int i = 0; i < nScriptCheckThreads - 1; i++) threadGroup.create_thread([i]() { return ThreadScriptCheck(i); }); - g_banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); - g_connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests. + m_node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + m_node.connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests. } TestingSetup::~TestingSetup() @@ -114,8 +116,9 @@ TestingSetup::~TestingSetup() threadGroup.join_all(); GetMainSignals().FlushBackgroundCallbacks(); GetMainSignals().UnregisterBackgroundSignalScheduler(); - g_connman.reset(); - g_banman.reset(); + g_rpc_node = nullptr; + m_node.connman.reset(); + m_node.banman.reset(); UnloadBlockIndex(); g_chainstate.reset(); pblocktree.reset(); diff --git a/src/test/setup_common.h b/src/test/setup_common.h index 6c9494898c..5731b50e31 100644 --- a/src/test/setup_common.h +++ b/src/test/setup_common.h @@ -8,6 +8,7 @@ #include <chainparamsbase.h> #include <fs.h> #include <key.h> +#include <node/context.h> #include <pubkey.h> #include <random.h> #include <scheduler.h> @@ -67,6 +68,7 @@ private: * Included are coins database, script check threads setup. */ struct TestingSetup : public BasicTestingSetup { + NodeContext m_node; boost::thread_group threadGroup; CScheduler scheduler; diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index 15f8db899b..b18f9df72d 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -193,7 +193,7 @@ BOOST_AUTO_TEST_CASE(sighash_from_data) CDataStream stream(ParseHex(raw_tx), SER_NETWORK, PROTOCOL_VERSION); stream >> tx; - CValidationState state; + TxValidationState state; BOOST_CHECK_MESSAGE(CheckTransaction(*tx, state), strTest); BOOST_CHECK(state.IsValid()); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 34192c6b6a..a8c8918733 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -152,7 +152,7 @@ BOOST_AUTO_TEST_CASE(tx_valid) CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION); CTransaction tx(deserialize, stream); - CValidationState state; + TxValidationState state; BOOST_CHECK_MESSAGE(CheckTransaction(tx, state), strTest); BOOST_CHECK(state.IsValid()); @@ -239,7 +239,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid) CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION ); CTransaction tx(deserialize, stream); - CValidationState state; + TxValidationState state; fValid = CheckTransaction(tx, state) && state.IsValid(); PrecomputedTransactionData txdata(tx); @@ -274,7 +274,7 @@ BOOST_AUTO_TEST_CASE(basic_transaction_tests) CDataStream stream(vch, SER_DISK, CLIENT_VERSION); CMutableTransaction tx; stream >> tx; - CValidationState state; + TxValidationState state; BOOST_CHECK_MESSAGE(CheckTransaction(CTransaction(tx), state) && state.IsValid(), "Simple deserialized transaction should be valid."); // Check that duplicate txins fail @@ -706,7 +706,9 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) BOOST_CHECK_EQUAL(nDustThreshold, 546); // dust: t.vout[0].nValue = nDustThreshold - 1; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "dust"); // not dust: t.vout[0].nValue = nDustThreshold; BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); @@ -716,14 +718,18 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) dustRelayFee = CFeeRate(3702); // dust: t.vout[0].nValue = 673 - 1; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "dust"); // not dust: t.vout[0].nValue = 673; BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); t.vout[0].scriptPubKey = CScript() << OP_1; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "scriptpubkey"); // MAX_OP_RETURN_RELAY-byte TX_NULL_DATA (standard) t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); @@ -733,7 +739,9 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) // MAX_OP_RETURN_RELAY+1-byte TX_NULL_DATA (non-standard) t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3800"); BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY + 1, t.vout[0].scriptPubKey.size()); + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "scriptpubkey"); // Data payload can be encoded in any way... t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex(""); @@ -748,7 +756,9 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) // ...so long as it only contains PUSHDATA's t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RETURN; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "scriptpubkey"); // TX_NULL_DATA w/o PUSHDATA t.vout.resize(1); @@ -759,15 +769,21 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vout.resize(2); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); t.vout[1].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "multi-op-return"); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); t.vout[1].scriptPubKey = CScript() << OP_RETURN; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "multi-op-return"); t.vout[0].scriptPubKey = CScript() << OP_RETURN; t.vout[1].scriptPubKey = CScript() << OP_RETURN; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "multi-op-return"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp index 2356e0ccdc..391ebfadfb 100644 --- a/src/test/txvalidation_tests.cpp +++ b/src/test/txvalidation_tests.cpp @@ -30,7 +30,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup) BOOST_CHECK(CTransaction(coinbaseTx).IsCoinBase()); - CValidationState state; + TxValidationState state; LOCK(cs_main); @@ -39,7 +39,6 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup) BOOST_CHECK_EQUAL( false, AcceptToMemoryPool(mempool, state, MakeTransactionRef(coinbaseTx), - nullptr /* pfMissingInputs */, nullptr /* plTxnReplaced */, true /* bypass_limits */, 0 /* nAbsurdFee */)); @@ -50,7 +49,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup) // Check that the validation state reflects the unsuccessful attempt. BOOST_CHECK(state.IsInvalid()); BOOST_CHECK_EQUAL(state.GetRejectReason(), "coinbase"); - BOOST_CHECK(state.GetReason() == ValidationInvalidReason::CONSENSUS); + BOOST_CHECK(state.GetResult() == TxValidationResult::TX_CONSENSUS); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index 193858cca9..144230b114 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -13,7 +13,7 @@ #include <boost/test/unit_test.hpp> -bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks); +bool CheckInputs(const CTransaction& tx, TxValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks); BOOST_AUTO_TEST_SUITE(tx_validationcache_tests) @@ -22,8 +22,8 @@ ToMemPool(const CMutableTransaction& tx) { LOCK(cs_main); - CValidationState state; - return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx), nullptr /* pfMissingInputs */, + TxValidationState state; + return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx), nullptr /* plTxnReplaced */, true /* bypass_limits */, 0 /* nAbsurdFee */); } @@ -114,7 +114,7 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail // If we add many more flags, this loop can get too expensive, but we can // rewrite in the future to randomly pick a set of flags to evaluate. for (uint32_t test_flags=0; test_flags < (1U << 16); test_flags += 1) { - CValidationState state; + TxValidationState state; // Filter out incompatible flag choices if ((test_flags & SCRIPT_VERIFY_CLEANSTACK)) { // CLEANSTACK requires P2SH and WITNESS, see VerifyScript() in @@ -201,7 +201,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) { LOCK(cs_main); - CValidationState state; + TxValidationState state; PrecomputedTransactionData ptd_spend_tx(spend_tx); BOOST_CHECK(!CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); @@ -270,7 +270,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) // Make it valid, and check again invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100; - CValidationState state; + TxValidationState state; PrecomputedTransactionData txdata(invalid_with_cltv_tx); BOOST_CHECK(CheckInputs(CTransaction(invalid_with_cltv_tx), state, ::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); } @@ -298,7 +298,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) // Make it valid, and check again invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100; - CValidationState state; + TxValidationState state; PrecomputedTransactionData txdata(invalid_with_csv_tx); BOOST_CHECK(CheckInputs(CTransaction(invalid_with_csv_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); } @@ -359,7 +359,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) // Invalidate vin[1] tx.vin[1].scriptWitness.SetNull(); - CValidationState state; + TxValidationState state; PrecomputedTransactionData txdata(tx); // This transaction is now invalid under segwit, because of the second input. BOOST_CHECK(!CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); diff --git a/src/test/util.cpp b/src/test/util.cpp index b7bb6deeaa..ed031270f2 100644 --- a/src/test/util.cpp +++ b/src/test/util.cpp @@ -32,13 +32,15 @@ std::string getnewaddress(CWallet& w) void importaddress(CWallet& wallet, const std::string& address) { + auto spk_man = wallet.GetLegacyScriptPubKeyMan(); LOCK(wallet.cs_wallet); + AssertLockHeld(spk_man->cs_wallet); const auto dest = DecodeDestination(address); assert(IsValidDestination(dest)); const auto script = GetScriptForDestination(dest); wallet.MarkDirty(); - assert(!wallet.HaveWatchOnly(script)); - if (!wallet.AddWatchOnly(script, 0 /* nCreateTime */)) assert(false); + assert(!spk_man->HaveWatchOnly(script)); + if (!spk_man->AddWatchOnly(script, 0 /* nCreateTime */)) assert(false); wallet.SetAddressBook(dest, /* label */ "", "receive"); } #endif // ENABLE_WALLET diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index b3368d44b6..aca9f475ac 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -151,7 +151,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) } bool ignored; - CValidationState state; + BlockValidationState state; std::vector<CBlockHeader> headers; std::transform(blocks.begin(), blocks.end(), std::back_inserter(headers), [](std::shared_ptr<const CBlock> b) { return b->GetBlockHeader(); }); @@ -278,14 +278,13 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) // Add the txs to the tx pool { LOCK(cs_main); - CValidationState state; + TxValidationState state; std::list<CTransactionRef> plTxnReplaced; for (const auto& tx : txs) { BOOST_REQUIRE(AcceptToMemoryPool( ::mempool, state, tx, - /* pfMissingInputs */ &ignored, &plTxnReplaced, /* bypass_limits */ false, /* nAbsurdFee */ 0)); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index e4c1fd4bc6..08f935c24f 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -8,6 +8,7 @@ #include <consensus/consensus.h> #include <consensus/tx_verify.h> #include <consensus/validation.h> +#include <optional.h> #include <validation.h> #include <policy/policy.h> #include <policy/fees.h> @@ -155,7 +156,7 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntr // GetMemPoolParents() is only valid for entries in the mempool, so we // iterate mapTx to find parents. for (unsigned int i = 0; i < tx.vin.size(); i++) { - boost::optional<txiter> piter = GetIter(tx.vin[i].prevout.hash); + Optional<txiter> piter = GetIter(tx.vin[i].prevout.hash); if (piter) { parentHashes.insert(*piter); if (parentHashes.size() + 1 > limitAncestorCount) { @@ -591,9 +592,9 @@ void CTxMemPool::clear() static void CheckInputsAndUpdateCoins(const CTransaction& tx, CCoinsViewCache& mempoolDuplicate, const int64_t spendheight) { - CValidationState state; + TxValidationState dummy_state; // Not used. CheckTxInputs() should always pass CAmount txfee = 0; - bool fCheckResult = tx.IsCoinBase() || Consensus::CheckTxInputs(tx, state, mempoolDuplicate, spendheight, txfee); + bool fCheckResult = tx.IsCoinBase() || Consensus::CheckTxInputs(tx, dummy_state, mempoolDuplicate, spendheight, txfee); assert(fCheckResult); UpdateCoins(tx, mempoolDuplicate, std::numeric_limits<int>::max()); } @@ -860,11 +861,11 @@ const CTransaction* CTxMemPool::GetConflictTx(const COutPoint& prevout) const return it == mapNextTx.end() ? nullptr : it->second; } -boost::optional<CTxMemPool::txiter> CTxMemPool::GetIter(const uint256& txid) const +Optional<CTxMemPool::txiter> CTxMemPool::GetIter(const uint256& txid) const { auto it = mapTx.find(txid); if (it != mapTx.end()) return it; - return boost::optional<txiter>{}; + return Optional<txiter>{}; } CTxMemPool::setEntries CTxMemPool::GetIterSet(const std::set<uint256>& hashes) const diff --git a/src/txmempool.h b/src/txmempool.h index b51e800001..9ccede9d4d 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -17,6 +17,7 @@ #include <coins.h> #include <crypto/siphash.h> #include <indirectmap.h> +#include <optional.h> #include <policy/feerate.h> #include <primitives/transaction.h> #include <sync.h> @@ -602,7 +603,7 @@ public: const CTransaction* GetConflictTx(const COutPoint& prevout) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** Returns an iterator to the given hash, if found */ - boost::optional<txiter> GetIter(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs); + Optional<txiter> GetIter(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** Translate a set of hashes into a set of pool iterators to avoid repeated lookups */ setEntries GetIterSet(const std::set<uint256>& hashes) const EXCLUSIVE_LOCKS_REQUIRED(cs); diff --git a/src/univalue/README.md b/src/univalue/README.md index 36aa786a4c..7c62c33970 100644 --- a/src/univalue/README.md +++ b/src/univalue/README.md @@ -12,21 +12,10 @@ an arbitrary depth. This class is aligned with the JSON standard, [RFC 7159](https://tools.ietf.org/html/rfc7159.html). -## Installation +## Library usage -This project is a standard GNU -[autotools](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html) -project. Build and install instructions are available in the `INSTALL` -file provided with GNU autotools. - -``` -$ ./autogen.sh -$ ./configure -$ make -``` - -## Design - -UniValue provides a single dynamic RAII C++ object class, -and minimizes template use (contra json_spirit). +This is a fork of univalue used by Bitcoin Core. It is not maintained for usage +by other projects. Notably, the API may break in non-backward-compatible ways. +Other projects looking for a maintained library should use the upstream +univalue at https://github.com/jgarzik/univalue. diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h index 91b104e56e..6080516353 100644 --- a/src/univalue/include/univalue.h +++ b/src/univalue/include/univalue.h @@ -47,7 +47,6 @@ public: std::string s(val_); setStr(s); } - ~UniValue() {} void clear(); diff --git a/src/univalue/lib/univalue_get.cpp b/src/univalue/lib/univalue_get.cpp index eabcf2dad1..0ad6146545 100644 --- a/src/univalue/lib/univalue_get.cpp +++ b/src/univalue/lib/univalue_get.cpp @@ -35,7 +35,7 @@ bool ParseInt32(const std::string& str, int32_t *out) errno = 0; // strtol will not set errno if valid long int n = strtol(str.c_str(), &endp, 10); if(out) *out = (int32_t)n; - // Note that strtol returns a *long int*, so even if strtol doesn't report a over/underflow + // Note that strtol returns a *long int*, so even if strtol doesn't report an over/underflow // we still have to check that the returned value is within the range of an *int32_t*. On 64-bit // platforms the size of these types may be different. return endp && *endp == 0 && !errno && diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 1e7d24c71c..46042f5634 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -138,7 +138,7 @@ std::string EncodeBase64(const unsigned char* pch, size_t len) std::string EncodeBase64(const std::string& str) { - return EncodeBase64((const unsigned char*)str.c_str(), str.size()); + return EncodeBase64((const unsigned char*)str.data(), str.size()); } std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) @@ -207,7 +207,7 @@ std::string EncodeBase32(const unsigned char* pch, size_t len) std::string EncodeBase32(const std::string& str) { - return EncodeBase32((const unsigned char*)str.c_str(), str.size()); + return EncodeBase32((const unsigned char*)str.data(), str.size()); } std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) diff --git a/src/util/translation.h b/src/util/translation.h index 0e6eb5a094..fc45da440a 100644 --- a/src/util/translation.h +++ b/src/util/translation.h @@ -6,7 +6,7 @@ #define BITCOIN_UTIL_TRANSLATION_H #include <tinyformat.h> - +#include <functional> /** * Bilingual messages: diff --git a/src/util/validation.cpp b/src/util/validation.cpp index 9a0d889447..bd52f57751 100644 --- a/src/util/validation.cpp +++ b/src/util/validation.cpp @@ -8,8 +8,8 @@ #include <consensus/validation.h> #include <tinyformat.h> -/** Convert CValidationState to a human-readable message for logging */ -std::string FormatStateMessage(const CValidationState &state) +/** Convert ValidationState to a human-readable message for logging */ +std::string FormatStateMessage(const ValidationState &state) { return strprintf("%s%s", state.GetRejectReason(), diff --git a/src/util/validation.h b/src/util/validation.h index 32559853ee..da2cf9f102 100644 --- a/src/util/validation.h +++ b/src/util/validation.h @@ -8,10 +8,10 @@ #include <string> -class CValidationState; +class ValidationState; -/** Convert CValidationState to a human-readable message for logging */ -std::string FormatStateMessage(const CValidationState &state); +/** Convert ValidationState to a human-readable message for logging */ +std::string FormatStateMessage(const ValidationState &state); extern const std::string strMessageMagic; diff --git a/src/validation.cpp b/src/validation.cpp index 9301066c6a..ca6d2176b3 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -181,7 +181,7 @@ std::unique_ptr<CBlockTreeDB> pblocktree; // See definition for documentation static void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight); static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight); -bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr); +bool CheckInputs(const CTransaction& tx, TxValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr); static FILE* OpenUndoFile(const FlatFilePos &pos, bool fReadOnly = false); static FlatFileSeq BlockFileSeq(); static FlatFileSeq UndoFileSeq(); @@ -363,9 +363,9 @@ static void UpdateMempoolForReorg(DisconnectedBlockTransactions& disconnectpool, auto it = disconnectpool.queuedTx.get<insertion_order>().rbegin(); while (it != disconnectpool.queuedTx.get<insertion_order>().rend()) { // ignore validation errors in resurrected transactions - CValidationState stateDummy; + TxValidationState stateDummy; if (!fAddToMempool || (*it)->IsCoinBase() || - !AcceptToMemoryPool(mempool, stateDummy, *it, nullptr /* pfMissingInputs */, + !AcceptToMemoryPool(mempool, stateDummy, *it, nullptr /* plTxnReplaced */, true /* bypass_limits */, 0 /* nAbsurdFee */)) { // If the transaction doesn't make it in to the mempool, remove any // transactions that depend on it (which would now be orphans). @@ -391,7 +391,7 @@ static void UpdateMempoolForReorg(DisconnectedBlockTransactions& disconnectpool, // Used to avoid mempool polluting consensus critical paths if CCoinsViewMempool // were somehow broken and returning the wrong scriptPubKeys -static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& view, const CTxMemPool& pool, +static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& view, const CTxMemPool& pool, unsigned int flags, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); @@ -441,8 +441,7 @@ public: // around easier. struct ATMPArgs { const CChainParams& m_chainparams; - CValidationState &m_state; - bool* m_missing_inputs; + TxValidationState &m_state; const int64_t m_accept_time; std::list<CTransactionRef>* m_replaced_transactions; const bool m_bypass_limits; @@ -502,15 +501,15 @@ private: bool Finalize(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); // Compare a package's feerate against minimum allowed. - bool CheckFeeRate(size_t package_size, CAmount package_fee, CValidationState& state) + bool CheckFeeRate(size_t package_size, CAmount package_fee, TxValidationState& state) { CAmount mempoolRejectFee = m_pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(package_size); if (mempoolRejectFee > 0 && package_fee < mempoolRejectFee) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee)); + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee)); } if (package_fee < ::minRelayTxFee.GetFee(package_size)) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, "min relay fee not met", strprintf("%d < %d", package_fee, ::minRelayTxFee.GetFee(package_size))); + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met", strprintf("%d < %d", package_fee, ::minRelayTxFee.GetFee(package_size))); } return true; } @@ -537,8 +536,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) const uint256& hash = ws.m_hash; // Copy/alias what we need out of args - CValidationState &state = args.m_state; - bool* pfMissingInputs = args.m_missing_inputs; + TxValidationState &state = args.m_state; const int64_t nAcceptTime = args.m_accept_time; const bool bypass_limits = args.m_bypass_limits; const CAmount& nAbsurdFee = args.m_absurd_fee; @@ -554,38 +552,34 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) CAmount& nConflictingFees = ws.m_conflicting_fees; size_t& nConflictingSize = ws.m_conflicting_size; - if (pfMissingInputs) { - *pfMissingInputs = false; - } - if (!CheckTransaction(tx, state)) return false; // state filled in by CheckTransaction // Coinbase is only valid in a block, not as a loose transaction if (tx.IsCoinBase()) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "coinbase"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase"); // Rather not work on nonstandard transactions (unless -testnet/-regtest) std::string reason; if (fRequireStandard && !IsStandardTx(tx, reason)) - return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, reason); + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason); // Do not work on transactions that are too small. // A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes. // Transactions smaller than this are not relayed to mitigate CVE-2017-12842 by not relaying // 64-byte transactions. if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) < MIN_STANDARD_TX_NONWITNESS_SIZE) - return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, "tx-size-small"); + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "tx-size-small"); // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. if (!CheckFinalTx(tx, STANDARD_LOCKTIME_VERIFY_FLAGS)) - return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, "non-final"); + return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final"); // is it already in the memory pool? if (m_pool.exists(hash)) { - return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, "txn-already-in-mempool"); + return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-already-in-mempool"); } // Check for conflicts with in-memory transactions @@ -617,7 +611,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) } } if (fReplacementOptOut) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, "txn-mempool-conflict"); + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict"); } setConflicts.insert(ptxConflicting->GetHash()); @@ -643,14 +637,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) for (size_t out = 0; out < tx.vout.size(); out++) { // Optimistically just do efficient check of cache for outputs if (coins_cache.HaveCoinInCache(COutPoint(hash, out))) { - return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, "txn-already-known"); + return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-already-known"); } } // Otherwise assume this might be an orphan tx for which we just haven't seen parents yet - if (pfMissingInputs) { - *pfMissingInputs = true; - } - return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid() + return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent"); } } @@ -668,7 +659,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Must keep pool.cs for this unless we change CheckSequenceLocks to take a // CoinsViewCache instead of create its own if (!CheckSequenceLocks(m_pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) - return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, "non-BIP68-final"); + return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final"); CAmount nFees = 0; if (!Consensus::CheckTxInputs(tx, state, m_view, GetSpendHeight(m_view), nFees)) { @@ -677,11 +668,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Check for non-standard pay-to-script-hash in inputs if (fRequireStandard && !AreInputsStandard(tx, m_view)) - return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, "bad-txns-nonstandard-inputs"); + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-nonstandard-inputs"); // Check for non-standard witness in P2WSH if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, m_view)) - return state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, "bad-witness-nonstandard"); + return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard"); int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS); @@ -705,7 +696,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) unsigned int nSize = entry->GetTxSize(); if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST) - return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, "bad-txns-too-many-sigops", + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops", strprintf("%d", nSigOpsCost)); // No transactions are allowed below minRelayTxFee except from disconnected @@ -713,7 +704,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) if (!bypass_limits && !CheckFeeRate(nSize, nModifiedFees, state)) return false; if (nAbsurdFee && nFees > nAbsurdFee) - return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "absurdly-high-fee", strprintf("%d > %d", nFees, nAbsurdFee)); const CTxMemPool::setEntries setIterConflicting = m_pool.GetIterSet(setConflicts); @@ -771,7 +762,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html if (nSize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || !m_pool.CalculateMemPoolAncestors(*entry, setAncestors, 2, m_limit_ancestor_size, m_limit_descendants + 1, m_limit_descendant_size + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, "too-long-mempool-chain", errString); + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", errString); } } @@ -784,7 +775,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) const uint256 &hashAncestor = ancestorIt->GetTx().GetHash(); if (setConflicts.count(hashAncestor)) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-spends-conflicting-tx", + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-spends-conflicting-tx", strprintf("%s spends conflicting transaction %s", hash.ToString(), hashAncestor.ToString())); @@ -824,7 +815,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize()); if (newFeeRate <= oldFeeRate) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, "insufficient fee", + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", strprintf("rejecting replacement %s; new feerate %s <= old feerate %s", hash.ToString(), newFeeRate.ToString(), @@ -852,7 +843,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) nConflictingSize += it->GetTxSize(); } } else { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, "too many potential replacements", + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too many potential replacements", strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n", hash.ToString(), nConflictingCount, @@ -876,7 +867,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // it's cheaper to just check if the new input refers to a // tx that's in the mempool. if (m_pool.exists(tx.vin[j].prevout.hash)) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, "replacement-adds-unconfirmed", + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "replacement-adds-unconfirmed", strprintf("replacement %s adds unconfirmed input, idx %d", hash.ToString(), j)); } @@ -888,7 +879,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // transactions would not be paid for. if (nModifiedFees < nConflictingFees) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, "insufficient fee", + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s", hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees))); } @@ -898,7 +889,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) CAmount nDeltaFees = nModifiedFees - nConflictingFees; if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize)) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, "insufficient fee", + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s", hash.ToString(), FormatMoney(nDeltaFees), @@ -912,7 +903,7 @@ bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, Workspace& ws, Precompute { const CTransaction& tx = *ws.m_ptx; - CValidationState &state = args.m_state; + TxValidationState &state = args.m_state; constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS; @@ -922,14 +913,13 @@ bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, Workspace& ws, Precompute // SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we // need to turn both off, and compare against just turning off CLEANSTACK // to see if the failure is specifically due to witness validation. - CValidationState stateDummy; // Want reported failures to be from first CheckInputs - if (!tx.HasWitness() && CheckInputs(tx, stateDummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) && - !CheckInputs(tx, stateDummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) { + TxValidationState state_dummy; // Want reported failures to be from first CheckInputs + if (!tx.HasWitness() && CheckInputs(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) && + !CheckInputs(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) { // Only the witness is missing, so the transaction itself may be fine. - state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, + state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, state.GetRejectReason(), state.GetDebugMessage()); } - assert(IsTransactionReason(state.GetReason())); return false; // state filled in by CheckInputs } @@ -941,7 +931,7 @@ bool MemPoolAccept::ConsensusScriptChecks(ATMPArgs& args, Workspace& ws, Precomp const CTransaction& tx = *ws.m_ptx; const uint256& hash = ws.m_hash; - CValidationState &state = args.m_state; + TxValidationState &state = args.m_state; const CChainParams& chainparams = args.m_chainparams; // Check again against the current block tip's script verification @@ -972,7 +962,7 @@ bool MemPoolAccept::Finalize(ATMPArgs& args, Workspace& ws) { const CTransaction& tx = *ws.m_ptx; const uint256& hash = ws.m_hash; - CValidationState &state = args.m_state; + TxValidationState &state = args.m_state; const bool bypass_limits = args.m_bypass_limits; CTxMemPool::setEntries& allConflicting = ws.m_all_conflicting; @@ -1010,7 +1000,7 @@ bool MemPoolAccept::Finalize(ATMPArgs& args, Workspace& ws) if (!bypass_limits) { LimitMempoolSize(m_pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)}); if (!m_pool.exists(hash)) - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, "mempool full"); + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full"); } return true; } @@ -1047,12 +1037,12 @@ bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs } // anon namespace /** (try to) add transaction to memory pool with a specified acceptance time **/ -static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, - bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced, +static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx, + int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced, bool bypass_limits, const CAmount nAbsurdFee, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { std::vector<COutPoint> coins_to_uncache; - MemPoolAccept::ATMPArgs args { chainparams, state, pfMissingInputs, nAcceptTime, plTxnReplaced, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept }; + MemPoolAccept::ATMPArgs args { chainparams, state, nAcceptTime, plTxnReplaced, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept }; bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args); if (!res) { // Remove coins that were not present in the coins cache before calling ATMPW; @@ -1064,17 +1054,17 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo ::ChainstateActive().CoinsTip().Uncache(hashTx); } // After we've (potentially) uncached entries, ensure our coins cache is still within its size limits - CValidationState stateDummy; - ::ChainstateActive().FlushStateToDisk(chainparams, stateDummy, FlushStateMode::PERIODIC); + BlockValidationState state_dummy; + ::ChainstateActive().FlushStateToDisk(chainparams, state_dummy, FlushStateMode::PERIODIC); return res; } -bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, - bool* pfMissingInputs, std::list<CTransactionRef>* plTxnReplaced, +bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx, + std::list<CTransactionRef>* plTxnReplaced, bool bypass_limits, const CAmount nAbsurdFee, bool test_accept) { const CChainParams& chainparams = Params(); - return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, pfMissingInputs, GetTime(), plTxnReplaced, bypass_limits, nAbsurdFee, test_accept); + return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, GetTime(), plTxnReplaced, bypass_limits, nAbsurdFee, test_accept); } /** @@ -1419,8 +1409,8 @@ void static InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(c CheckForkWarningConditions(); } -void CChainState::InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) { - if (state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { +void CChainState::InvalidBlockFound(CBlockIndex *pindex, const BlockValidationState &state) { + if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; m_blockman.m_failed_blocks.insert(pindex); setDirtyBlockIndex.insert(pindex); @@ -1493,7 +1483,7 @@ void InitScriptExecutionCache() { * * Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp */ -bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool CheckInputs(const CTransaction& tx, TxValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { if (tx.IsCoinBase()) return true; @@ -1545,10 +1535,10 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi CScriptCheck check2(coin.out, tx, i, flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata); if (check2()) - return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); } // MANDATORY flag failures correspond to - // ValidationInvalidReason::CONSENSUS. Because CONSENSUS + // TxValidationResult::TX_CONSENSUS. Because CONSENSUS // failures are the most serious case of validation // failures, we may need to consider using // RECENT_CONSENSUS_CHANGE for any script failure that @@ -1556,7 +1546,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi // support, to avoid splitting the network (but this // depends on the details of how net_processing handles // such errors). - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); + return state.Invalid(TxValidationResult::TX_CONSENSUS, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); } } @@ -1641,7 +1631,7 @@ static bool AbortNode(const std::string& strMessage, const std::string& userMess return false; } -static bool AbortNode(CValidationState& state, const std::string& strMessage, const std::string& userMessage = "", unsigned int prefix = 0) +static bool AbortNode(BlockValidationState& state, const std::string& strMessage, const std::string& userMessage = "", unsigned int prefix = 0) { AbortNode(strMessage, userMessage, prefix); return state.Error(strMessage); @@ -1755,9 +1745,9 @@ void static FlushBlockFile(bool fFinalize = false) } } -static bool FindUndoPos(CValidationState &state, int nFile, FlatFilePos &pos, unsigned int nAddSize); +static bool FindUndoPos(BlockValidationState &state, int nFile, FlatFilePos &pos, unsigned int nAddSize); -static bool WriteUndoDataForBlock(const CBlockUndo& blockundo, CValidationState& state, CBlockIndex* pindex, const CChainParams& chainparams) +static bool WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex* pindex, const CChainParams& chainparams) { // Write undo information to disk if (pindex->GetUndoPos().IsNull()) { @@ -1896,7 +1886,7 @@ static int64_t nBlocksTotal = 0; /** Apply the effects of this block (with given index) on the UTXO set represented by coins. * Validity checks that depend on the UTXO set are also done; ConnectBlock() * can fail if those validity checks fail (among other reasons). */ -bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, +bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck) { AssertLockHeld(cs_main); @@ -1918,7 +1908,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // re-enforce that rule here (at least until we make it impossible for // GetAdjustedTime() to go backward). if (!CheckBlock(block, state, chainparams.GetConsensus(), !fJustCheck, !fJustCheck)) { - if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED) { + if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) { // We don't write down blocks to disk if they may have been // corrupted, so this should be impossible unless we're having hardware // problems. @@ -2058,8 +2048,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl for (const auto& tx : block.vtx) { for (size_t o = 0; o < tx->vout.size(); o++) { if (view.HaveCoin(COutPoint(tx->GetHash(), o))) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): tried to overwrite transaction"), - "bad-txns-BIP30"); + LogPrintf("ERROR: ConnectBlock(): tried to overwrite transaction\n"); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-BIP30"); } } } @@ -2097,21 +2087,17 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl if (!tx.IsCoinBase()) { CAmount txfee = 0; - if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, txfee)) { - if (!IsBlockReason(state.GetReason())) { - // CheckTxInputs may return MISSING_INPUTS or - // PREMATURE_SPEND but we can't return that, as it's not - // defined for a block, so we reset the reason flag to - // CONSENSUS here. - state.Invalid(ValidationInvalidReason::CONSENSUS, false, - state.GetRejectReason(), state.GetDebugMessage()); - } + TxValidationState tx_state; + if (!Consensus::CheckTxInputs(tx, tx_state, view, pindex->nHeight, txfee)) { + // Any transaction validation failure in ConnectBlock is a block consensus failure + state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, + tx_state.GetRejectReason(), tx_state.GetDebugMessage()); return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state)); } nFees += txfee; if (!MoneyRange(nFees)) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: accumulated fee in the block out of range.", __func__), - "bad-txns-accumulated-fee-outofrange"); + LogPrintf("ERROR: %s: accumulated fee in the block out of range.\n", __func__); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-accumulated-fee-outofrange"); } // Check that transaction is BIP68 final @@ -2123,8 +2109,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl } if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: contains a non-BIP68-final transaction", __func__), - "bad-txns-nonfinal"); + LogPrintf("ERROR: %s: contains a non-BIP68-final transaction\n", __func__); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-nonfinal"); } } @@ -2133,26 +2119,21 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // * p2sh (when P2SH enabled in flags and excludes coinbase) // * witness (when witness enabled in flags and excludes coinbase) nSigOpsCost += GetTransactionSigOpCost(tx, view, flags); - if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) - return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): too many sigops"), - "bad-blk-sigops"); + if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) { + LogPrintf("ERROR: ConnectBlock(): too many sigops\n"); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-sigops"); + } txdata.emplace_back(tx); if (!tx.IsCoinBase()) { std::vector<CScriptCheck> vChecks; bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ - if (fScriptChecks && !CheckInputs(tx, state, view, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) { - if (state.GetReason() == ValidationInvalidReason::TX_NOT_STANDARD) { - // CheckInputs may return NOT_STANDARD for extra flags we passed, - // but we can't return that, as it's not defined for a block, so - // we reset the reason flag to CONSENSUS here. - // In the event of a future soft-fork, we may need to - // consider whether rewriting to CONSENSUS or - // RECENT_CONSENSUS_CHANGE would be more appropriate. - state.Invalid(ValidationInvalidReason::CONSENSUS, false, - state.GetRejectReason(), state.GetDebugMessage()); - } + TxValidationState tx_state; + if (fScriptChecks && !CheckInputs(tx, tx_state, view, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) { + // Any transaction validation failure in ConnectBlock is a block consensus failure + state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, + tx_state.GetRejectReason(), tx_state.GetDebugMessage()); return error("ConnectBlock(): CheckInputs on %s failed with %s", tx.GetHash().ToString(), FormatStateMessage(state)); } @@ -2169,14 +2150,15 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2), MILLI * (nTime3 - nTime2) / block.vtx.size(), nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs-1), nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal); CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus()); - if (block.vtx[0]->GetValueOut() > blockReward) - return state.Invalid(ValidationInvalidReason::CONSENSUS, - error("ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)", - block.vtx[0]->GetValueOut(), blockReward), - "bad-cb-amount"); - - if (!control.Wait()) - return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: CheckQueue failed", __func__), "block-validation-failed"); + if (block.vtx[0]->GetValueOut() > blockReward) { + LogPrintf("ERROR: ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)\n", block.vtx[0]->GetValueOut(), blockReward); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-amount"); + } + + if (!control.Wait()) { + LogPrintf("ERROR: %s: CheckQueue failed\n", __func__); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "block-validation-failed"); + } int64_t nTime4 = GetTimeMicros(); nTimeVerify += nTime4 - nTime2; LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1, MILLI * (nTime4 - nTime2), nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal); @@ -2206,7 +2188,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl bool CChainState::FlushStateToDisk( const CChainParams& chainparams, - CValidationState &state, + BlockValidationState &state, FlushStateMode mode, int nManualPruneHeight) { @@ -2317,7 +2299,7 @@ bool CChainState::FlushStateToDisk( } void CChainState::ForceFlushStateToDisk() { - CValidationState state; + BlockValidationState state; const CChainParams& chainparams = Params(); if (!this->FlushStateToDisk(chainparams, state, FlushStateMode::ALWAYS)) { LogPrintf("%s: failed to flush state (%s)\n", __func__, FormatStateMessage(state)); @@ -2325,7 +2307,7 @@ void CChainState::ForceFlushStateToDisk() { } void CChainState::PruneAndFlush() { - CValidationState state; + BlockValidationState state; fCheckForPruning = true; const CChainParams& chainparams = Params(); @@ -2411,7 +2393,7 @@ void static UpdateTip(const CBlockIndex* pindexNew, const CChainParams& chainPar * disconnectpool (note that the caller is responsible for mempool consistency * in any case). */ -bool CChainState::DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions *disconnectpool) +bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions *disconnectpool) { CBlockIndex *pindexDelete = m_chain.Tip(); assert(pindexDelete); @@ -2531,7 +2513,7 @@ public: * * The block is added to connectTrace if connection succeeds. */ -bool CChainState::ConnectTip(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions &disconnectpool) +bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions &disconnectpool) { assert(pindexNew->pprev == m_chain.Tip()); // Read block from disk. @@ -2663,7 +2645,7 @@ void CChainState::PruneBlockIndexCandidates() { * * @returns true unless a system error occurred */ -bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) +bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) { AssertLockHeld(cs_main); @@ -2710,10 +2692,10 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) { if (state.IsInvalid()) { // The block violates a consensus rule. - if (state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { + if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { InvalidChainFound(vpindexToConnect.front()); } - state = CValidationState(); + state = BlockValidationState(); fInvalidFound = true; fContinue = false; break; @@ -2781,7 +2763,7 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) { } } -bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) { +bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) { // Note that while we're often called here from ProcessNewBlock, this is // far from a guarantee. Things in the P2P/RPC will often end up calling // us in the middle of ProcessNewBlock - do not assume pblock is set @@ -2881,11 +2863,11 @@ bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams& return true; } -bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) { +bool ActivateBestChain(BlockValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) { return ::ChainstateActive().ActivateBestChain(state, chainparams, std::move(pblock)); } -bool CChainState::PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex *pindex) +bool CChainState::PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex *pindex) { { LOCK(cs_main); @@ -2913,11 +2895,11 @@ bool CChainState::PreciousBlock(CValidationState& state, const CChainParams& par return ActivateBestChain(state, params, std::shared_ptr<const CBlock>()); } -bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex *pindex) { +bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex *pindex) { return ::ChainstateActive().PreciousBlock(state, params, pindex); } -bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex) +bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex) { CBlockIndex* to_mark_failed = pindex; bool pindex_was_in_chain = false; @@ -3053,7 +3035,7 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c return true; } -bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex) { +bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex) { return ::ChainstateActive().InvalidateBlock(state, chainparams, pindex); } @@ -3227,7 +3209,7 @@ static bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize, unsigned int n return true; } -static bool FindUndoPos(CValidationState &state, int nFile, FlatFilePos &pos, unsigned int nAddSize) +static bool FindUndoPos(BlockValidationState &state, int nFile, FlatFilePos &pos, unsigned int nAddSize) { pos.nFile = nFile; @@ -3249,16 +3231,16 @@ static bool FindUndoPos(CValidationState &state, int nFile, FlatFilePos &pos, un return true; } -static bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true) +static bool CheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true) { // Check proof of work matches claimed amount if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) - return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, "high-hash", "proof of work failed"); + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "high-hash", "proof of work failed"); return true; } -bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot) +bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot) { // These are checks that are independent of context. @@ -3275,13 +3257,13 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P bool mutated; uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated); if (block.hashMerkleRoot != hashMerkleRoot2) - return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, "bad-txnmrklroot", "hashMerkleRoot mismatch"); + return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-txnmrklroot", "hashMerkleRoot mismatch"); // Check for merkle tree malleability (CVE-2012-2459): repeating sequences // of transactions in a block without affecting the merkle root of a block, // while still invalidating it. if (mutated) - return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, "bad-txns-duplicate", "duplicate transaction"); + return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-txns-duplicate", "duplicate transaction"); } // All potential-corruption validation must be done before we do any @@ -3292,28 +3274,34 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P // Size limits if (block.vtx.empty() || block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT || ::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-blk-length", "size limits failed"); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-length", "size limits failed"); // First transaction must be coinbase, the rest must not be if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-cb-missing", "first tx is not coinbase"); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-missing", "first tx is not coinbase"); for (unsigned int i = 1; i < block.vtx.size(); i++) if (block.vtx[i]->IsCoinBase()) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-cb-multiple", "more than one coinbase"); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-multiple", "more than one coinbase"); // Check transactions - for (const auto& tx : block.vtx) - if (!CheckTransaction(*tx, state)) - return state.Invalid(state.GetReason(), false, state.GetRejectReason(), - strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), state.GetDebugMessage())); - + // Must check for duplicate inputs (see CVE-2018-17144) + for (const auto& tx : block.vtx) { + TxValidationState tx_state; + if (!CheckTransaction(*tx, tx_state)) { + // CheckBlock() does context-free validation checks. The only + // possible failures are consensus failures. + assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), + strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), tx_state.GetDebugMessage())); + } + } unsigned int nSigOps = 0; for (const auto& tx : block.vtx) { nSigOps += GetLegacySigOpCount(*tx); } if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-blk-sigops", "out-of-bounds SigOpCount"); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-sigops", "out-of-bounds SigOpCount"); if (fCheckPOW && fCheckMerkleRoot) block.fChecked = true; @@ -3406,7 +3394,7 @@ static CBlockIndex* GetLastCheckpoint(const CCheckpointData& data) EXCLUSIVE_LOC * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& params, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, const CChainParams& params, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { assert(pindexPrev != nullptr); const int nHeight = pindexPrev->nHeight + 1; @@ -3414,7 +3402,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta // Check proof of work const Consensus::Params& consensusParams = params.GetConsensus(); if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) - return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, "bad-diffbits", "incorrect proof of work"); + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", "incorrect proof of work"); // Check against checkpoints if (fCheckpointsEnabled) { @@ -3422,24 +3410,26 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our // g_blockman.m_block_index. CBlockIndex* pcheckpoint = GetLastCheckpoint(params.Checkpoints()); - if (pcheckpoint && nHeight < pcheckpoint->nHeight) - return state.Invalid(ValidationInvalidReason::BLOCK_CHECKPOINT, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), "bad-fork-prior-to-checkpoint"); + if (pcheckpoint && nHeight < pcheckpoint->nHeight) { + LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight); + return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT, "bad-fork-prior-to-checkpoint"); + } } // Check timestamp against prev if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) - return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, "time-too-old", "block's timestamp is too early"); + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "time-too-old", "block's timestamp is too early"); // Check timestamp if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME) - return state.Invalid(ValidationInvalidReason::BLOCK_TIME_FUTURE, false, "time-too-new", "block timestamp too far in the future"); + return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", "block timestamp too far in the future"); // Reject outdated version blocks when 95% (75% on testnet) of the network has upgraded: // check for version 2, 3 and 4 upgrades if((block.nVersion < 2 && nHeight >= consensusParams.BIP34Height) || (block.nVersion < 3 && nHeight >= consensusParams.BIP66Height) || (block.nVersion < 4 && nHeight >= consensusParams.BIP65Height)) - return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, strprintf("bad-version(0x%08x)", block.nVersion), + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, strprintf("bad-version(0x%08x)", block.nVersion), strprintf("rejected nVersion=0x%08x block", block.nVersion)); return true; @@ -3451,7 +3441,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) +static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) { const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; @@ -3469,7 +3459,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // Check that all transactions are finalized for (const auto& tx : block.vtx) { if (!IsFinalTx(*tx, nHeight, nLockTimeCutoff)) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-nonfinal", "non-final transaction"); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-nonfinal", "non-final transaction"); } } @@ -3479,7 +3469,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c CScript expect = CScript() << nHeight; if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() || !std::equal(expect.begin(), expect.end(), block.vtx[0]->vin[0].scriptSig.begin())) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-cb-height", "block height mismatch in coinbase"); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-height", "block height mismatch in coinbase"); } } @@ -3501,11 +3491,11 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // already does not permit it, it is impossible to trigger in the // witness tree. if (block.vtx[0]->vin[0].scriptWitness.stack.size() != 1 || block.vtx[0]->vin[0].scriptWitness.stack[0].size() != 32) { - return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, "bad-witness-nonce-size", strprintf("%s : invalid witness reserved value size", __func__)); + return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-witness-nonce-size", strprintf("%s : invalid witness reserved value size", __func__)); } CHash256().Write(hashWitness.begin(), 32).Write(&block.vtx[0]->vin[0].scriptWitness.stack[0][0], 32).Finalize(hashWitness.begin()); if (memcmp(hashWitness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) { - return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, "bad-witness-merkle-match", strprintf("%s : witness merkle commitment mismatch", __func__)); + return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-witness-merkle-match", strprintf("%s : witness merkle commitment mismatch", __func__)); } fHaveWitness = true; } @@ -3515,7 +3505,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c if (!fHaveWitness) { for (const auto& tx : block.vtx) { if (tx->HasWitness()) { - return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, "unexpected-witness", strprintf("%s : unexpected witness data found", __func__)); + return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "unexpected-witness", strprintf("%s : unexpected witness data found", __func__)); } } } @@ -3527,13 +3517,13 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // the block hash, so we couldn't mark the block as permanently // failed). if (GetBlockWeight(block) > MAX_BLOCK_WEIGHT) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-blk-weight", strprintf("%s : weight limit failed", __func__)); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-weight", strprintf("%s : weight limit failed", __func__)); } return true; } -bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) +bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) { AssertLockHeld(cs_main); // Check for duplicate @@ -3546,8 +3536,10 @@ bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, CValidationState pindex = miSelf->second; if (ppindex) *ppindex = pindex; - if (pindex->nStatus & BLOCK_FAILED_MASK) - return state.Invalid(ValidationInvalidReason::CACHED_INVALID, error("%s: block %s is marked invalid", __func__, hash.ToString()), "duplicate"); + if (pindex->nStatus & BLOCK_FAILED_MASK) { + LogPrintf("ERROR: %s: block %s is marked invalid\n", __func__, hash.ToString()); + return state.Invalid(BlockValidationResult::BLOCK_CACHED_INVALID, "duplicate"); + } return true; } @@ -3557,11 +3549,15 @@ bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, CValidationState // Get prev block index CBlockIndex* pindexPrev = nullptr; BlockMap::iterator mi = m_block_index.find(block.hashPrevBlock); - if (mi == m_block_index.end()) - return state.Invalid(ValidationInvalidReason::BLOCK_MISSING_PREV, error("%s: prev block not found", __func__), "prev-blk-not-found"); + if (mi == m_block_index.end()) { + LogPrintf("ERROR: %s: prev block not found\n", __func__); + return state.Invalid(BlockValidationResult::BLOCK_MISSING_PREV, "prev-blk-not-found"); + } pindexPrev = (*mi).second; - if (pindexPrev->nStatus & BLOCK_FAILED_MASK) - return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_PREV, error("%s: prev block invalid", __func__), "bad-prevblk"); + if (pindexPrev->nStatus & BLOCK_FAILED_MASK) { + LogPrintf("ERROR: %s: prev block invalid\n", __func__); + return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk"); + } if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); @@ -3598,7 +3594,8 @@ bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, CValidationState setDirtyBlockIndex.insert(invalid_walk); invalid_walk = invalid_walk->pprev; } - return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_PREV, error("%s: prev block invalid", __func__), "bad-prevblk"); + LogPrintf("ERROR: %s: prev block invalid\n", __func__); + return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk"); } } } @@ -3613,9 +3610,8 @@ bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, CValidationState } // Exposed wrapper for AcceptBlockHeader -bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex, CBlockHeader *first_invalid) +bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex) { - if (first_invalid != nullptr) first_invalid->SetNull(); { LOCK(cs_main); for (const CBlockHeader& header : headers) { @@ -3624,7 +3620,6 @@ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidatio ::ChainstateActive().CheckBlockIndex(chainparams.GetConsensus()); if (!accepted) { - if (first_invalid) *first_invalid = header; return false; } if (ppindex) { @@ -3660,7 +3655,7 @@ static FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, const CChai } /** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */ -bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) +bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) { const CBlock& block = *pblock; @@ -3710,8 +3705,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CVali if (!CheckBlock(block, state, chainparams.GetConsensus()) || !ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) { - assert(IsBlockReason(state.GetReason())); - if (state.IsInvalid() && state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { + if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; setDirtyBlockIndex.insert(pindex); } @@ -3750,7 +3744,7 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<cons { CBlockIndex *pindex = nullptr; if (fNewBlock) *fNewBlock = false; - CValidationState state; + BlockValidationState state; // CheckBlock() does not support multi-threaded block validation because CBlock::fChecked can cause data race. // Therefore, the following critical section must include the CheckBlock() call as well. @@ -3771,14 +3765,14 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<cons NotifyHeaderTip(); - CValidationState state; // Only used to report errors, not invalidity - ignore it + BlockValidationState state; // Only used to report errors, not invalidity - ignore it if (!::ChainstateActive().ActivateBestChain(state, chainparams, pblock)) return error("%s: ActivateBestChain failed (%s)", __func__, FormatStateMessage(state)); return true; } -bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams, const CBlock& block, CBlockIndex* pindexPrev, bool fCheckPOW, bool fCheckMerkleRoot) +bool TestBlockValidity(BlockValidationState& state, const CChainParams& chainparams, const CBlock& block, CBlockIndex* pindexPrev, bool fCheckPOW, bool fCheckMerkleRoot) { AssertLockHeld(cs_main); assert(pindexPrev && pindexPrev == ::ChainActive().Tip()); @@ -3889,7 +3883,7 @@ static void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPr /* This function is called from the RPC code for pruneblockchain */ void PruneBlockFilesManual(int nManualPruneHeight) { - CValidationState state; + BlockValidationState state; const CChainParams& chainparams = Params(); if (!::ChainstateActive().FlushStateToDisk( chainparams, state, FlushStateMode::NONE, nManualPruneHeight)) { @@ -4185,7 +4179,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, CBlockIndex* pindex; CBlockIndex* pindexFailure = nullptr; int nGoodTransactions = 0; - CValidationState state; + BlockValidationState state; int reportDone = 0; LogPrintf("[0%%]..."); /* Continued */ for (pindex = ::ChainActive().Tip(); pindex && pindex->pprev; pindex = pindex->pprev) { @@ -4429,7 +4423,7 @@ bool CChainState::RewindBlockIndex(const CChainParams& params) } // nHeight is now the height of the first insufficiently-validated block, or tipheight + 1 - CValidationState state; + BlockValidationState state; // Loop until the tip is below nHeight, or we reach a pruned block. while (!ShutdownRequested()) { { @@ -4497,7 +4491,7 @@ bool RewindBlockIndex(const CChainParams& params) { // FlushStateToDisk can possibly read ::ChainActive(). Be conservative // and skip it here, we're about to -reindex-chainstate anyway, so // it'll get called a bunch real soon. - CValidationState state; + BlockValidationState state; if (!::ChainstateActive().FlushStateToDisk(params, state, FlushStateMode::ALWAYS)) { LogPrintf("RewindBlockIndex: unable to flush state to disk (%s)\n", FormatStateMessage(state)); return false; @@ -4649,7 +4643,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFi // process in case the block isn't known yet CBlockIndex* pindex = LookupBlockIndex(hash); if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) { - CValidationState state; + BlockValidationState state; if (::ChainstateActive().AcceptBlock(pblock, state, chainparams, nullptr, true, dbp, nullptr)) { nLoaded++; } @@ -4663,7 +4657,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFi // Activate the genesis block so normal node progress can continue if (hash == chainparams.GetConsensus().hashGenesisBlock) { - CValidationState state; + BlockValidationState state; if (!ActivateBestChain(state, chainparams)) { break; } @@ -4686,7 +4680,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFi LogPrint(BCLog::REINDEX, "%s: Processing out of order child %s of %s\n", __func__, pblockrecursive->GetHash().ToString(), head.ToString()); LOCK(cs_main); - CValidationState dummy; + BlockValidationState dummy; if (::ChainstateActive().AcceptBlock(pblockrecursive, dummy, chainparams, nullptr, true, &it->second, nullptr)) { nLoaded++; @@ -4963,10 +4957,10 @@ bool LoadMempool(CTxMemPool& pool) if (amountdelta) { pool.PrioritiseTransaction(tx->GetHash(), amountdelta); } - CValidationState state; + TxValidationState state; if (nTime + nExpiryTimeout > nNow) { LOCK(cs_main); - AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, nullptr /* pfMissingInputs */, nTime, + AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, nTime, nullptr /* plTxnReplaced */, false /* bypass_limits */, 0 /* nAbsurdFee */, false /* test_accept */); if (state.IsValid()) { diff --git a/src/validation.h b/src/validation.h index d17a320a47..7f9582adfd 100644 --- a/src/validation.h +++ b/src/validation.h @@ -32,6 +32,7 @@ #include <vector> class CChainState; +class BlockValidationState; class CBlockIndex; class CBlockTreeDB; class CBlockUndo; @@ -41,7 +42,7 @@ class CConnman; class CScriptCheck; class CBlockPolicyEstimator; class CTxMemPool; -class CValidationState; +class TxValidationState; struct ChainTxData; struct DisconnectedBlockTransactions; @@ -221,9 +222,8 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<cons * @param[out] state This may be set to an Error state if any error occurred processing them * @param[in] chainparams The params for the chain we want to connect to * @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers - * @param[out] first_invalid First header that fails validation, if one exists */ -bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr, CBlockHeader* first_invalid = nullptr) LOCKS_EXCLUDED(cs_main); +bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main); /** Open a block file (blk?????.dat) */ FILE* OpenBlockFile(const FlatFilePos &pos, bool fReadOnly = false); @@ -248,7 +248,7 @@ bool GetTransaction(const uint256& hash, CTransactionRef& tx, const Consensus::P * May not be called with cs_main held. May not be called in a * validationinterface callback. */ -bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock = std::shared_ptr<const CBlock>()); +bool ActivateBestChain(BlockValidationState& state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock = std::shared_ptr<const CBlock>()); CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams); /** Guess verification progress (as a fraction between 0.0=genesis and 1.0=current tip). */ @@ -272,8 +272,8 @@ void PruneBlockFilesManual(int nManualPruneHeight); /** (try to) add transaction to memory pool * plTxnReplaced will be appended to with all transactions replaced from mempool **/ -bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, - bool* pfMissingInputs, std::list<CTransactionRef>* plTxnReplaced, +bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx, + std::list<CTransactionRef>* plTxnReplaced, bool bypass_limits, const CAmount nAbsurdFee, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Get the BIP9 state for a given deployment at the current tip. */ @@ -368,10 +368,10 @@ bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex); /** Functions for validating blocks and updating the block tree */ /** Context-independent validity checks */ -bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true, bool fCheckMerkleRoot = true); +bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true, bool fCheckMerkleRoot = true); /** Check a block is completely valid from start to finish (only works on top of our current best block) */ -bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams, const CBlock& block, CBlockIndex* pindexPrev, bool fCheckPOW = true, bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main); +bool TestBlockValidity(BlockValidationState& state, const CChainParams& chainparams, const CBlock& block, CBlockIndex* pindexPrev, bool fCheckPOW = true, bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Check whether witness commitments are required for a block, and whether to enforce NULLDUMMY (BIP 147) rules. * Note that transaction witness validation rules are always enforced when P2SH is enforced. */ @@ -488,7 +488,7 @@ public: */ bool AcceptBlockHeader( const CBlockHeader& block, - CValidationState& state, + BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); }; @@ -652,7 +652,7 @@ public: */ bool FlushStateToDisk( const CChainParams& chainparams, - CValidationState &state, + BlockValidationState &state, FlushStateMode mode, int nManualPruneHeight = 0); @@ -678,23 +678,23 @@ public: * @returns true unless a system error occurred */ bool ActivateBestChain( - CValidationState& state, + BlockValidationState& state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) LOCKS_EXCLUDED(cs_main); - bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Block (dis)connection on a given view: DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view); - bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, + bool ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Apply the effects of a block disconnection on the UTXO set. - bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); + bool DisconnectTip(BlockValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); // Manual block validity manipulation: - bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); - bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Replay blocks that aren't fully applied to the database. */ @@ -720,10 +720,10 @@ public: bool LoadChainTip(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); private: - bool ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); - bool ConnectTip(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); + bool ActivateBestChainStep(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); + bool ConnectTip(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); - void InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void InvalidBlockFound(CBlockIndex *pindex, const BlockValidationState &state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); CBlockIndex* FindMostWorkChain() EXCLUSIVE_LOCKS_REQUIRED(cs_main); void ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos, const Consensus::Params& consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -738,10 +738,10 @@ private: * May not be called in a * validationinterface callback. */ -bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main); +bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main); /** Mark a block as invalid. */ -bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); +bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); /** Remove invalidity status from a block and its descendants. */ void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index cf4a529a6d..a46b4003f1 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -32,7 +32,7 @@ struct MainSignalsInstance { boost::signals2::signal<void (const std::shared_ptr<const CBlock> &)> BlockDisconnected; boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool; boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed; - boost::signals2::signal<void (const CBlock&, const CValidationState&)> BlockChecked; + boost::signals2::signal<void (const CBlock&, const BlockValidationState&)> BlockChecked; boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock; // We are not allowed to assume the scheduler only runs in one thread, @@ -168,7 +168,7 @@ void CMainSignals::ChainStateFlushed(const CBlockLocator &locator) { }); } -void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& state) { +void CMainSignals::BlockChecked(const CBlock& block, const BlockValidationState& state) { m_internals->BlockChecked(block, state); } diff --git a/src/validationinterface.h b/src/validationinterface.h index 3ce617b827..dc8425869b 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -13,12 +13,12 @@ #include <memory> extern CCriticalSection cs_main; +class BlockValidationState; class CBlock; class CBlockIndex; struct CBlockLocator; class CConnman; class CValidationInterface; -class CValidationState; class uint256; class CScheduler; class CTxMemPool; @@ -134,11 +134,11 @@ protected: virtual void ChainStateFlushed(const CBlockLocator &locator) {} /** * Notifies listeners of a block validation result. - * If the provided CValidationState IsValid, the provided block + * If the provided BlockValidationState IsValid, the provided block * is guaranteed to be the current best block at the time the * callback was generated (not necessarily now) */ - virtual void BlockChecked(const CBlock&, const CValidationState&) {} + virtual void BlockChecked(const CBlock&, const BlockValidationState&) {} /** * Notifies listeners that a block which builds directly on our current tip * has been received and connected to the headers tree, though not validated yet */ @@ -180,7 +180,7 @@ public: void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &); void BlockDisconnected(const std::shared_ptr<const CBlock> &); void ChainStateFlushed(const CBlockLocator &); - void BlockChecked(const CBlock&, const CValidationState&); + void BlockChecked(const CBlock&, const BlockValidationState&); void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&); }; diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 92a290530c..fca4b75c45 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -5,13 +5,12 @@ #ifndef BITCOIN_WALLET_COINCONTROL_H #define BITCOIN_WALLET_COINCONTROL_H +#include <optional.h> #include <policy/feerate.h> #include <policy/fees.h> #include <primitives/transaction.h> #include <wallet/wallet.h> -#include <boost/optional.hpp> - const int DEFAULT_MIN_DEPTH = 0; const int DEFAULT_MAX_DEPTH = 9999999; @@ -22,7 +21,7 @@ public: //! Custom change destination, if not set an address is generated CTxDestination destChange; //! Override the default change type if set, ignored if destChange is set - boost::optional<OutputType> m_change_type; + Optional<OutputType> m_change_type; //! If false, allows unselected inputs, but requires all selected inputs be used bool fAllowOtherInputs; //! Includes watch only addresses which are solvable @@ -30,11 +29,11 @@ public: //! Override automatic min/max checks on fee, m_feerate must be set if true bool fOverrideFeeRate; //! Override the wallet's m_pay_tx_fee if set - boost::optional<CFeeRate> m_feerate; + Optional<CFeeRate> m_feerate; //! Override the default confirmation target if set - boost::optional<unsigned int> m_confirm_target; + Optional<unsigned int> m_confirm_target; //! Override the wallet's m_signal_rbf if set - boost::optional<bool> m_signal_bip125_rbf; + Optional<bool> m_signal_bip125_rbf; //! Avoid partial use of funds sent to a given address bool m_avoid_partial_spends; //! Forbids inclusion of dirty (previously used) addresses diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 8a37f374a1..870e235964 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -4,11 +4,10 @@ #include <wallet/coinselection.h> +#include <optional.h> #include <util/system.h> #include <util/moneystr.h> -#include <boost/optional.hpp> - // Descending order comparator struct { bool operator()(const OutputGroup& a, const OutputGroup& b) const @@ -219,7 +218,7 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group nValueRet = 0; // List of values less than target - boost::optional<OutputGroup> lowest_larger; + Optional<OutputGroup> lowest_larger; std::vector<OutputGroup> applicable_groups; CAmount nTotalLower = 0; diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index f6179aa298..b50f00e7d1 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -23,7 +23,7 @@ int CCrypter::BytesToKeySHA512AES(const std::vector<unsigned char>& chSalt, cons unsigned char buf[CSHA512::OUTPUT_SIZE]; CSHA512 di; - di.Write((const unsigned char*)strKeyData.c_str(), strKeyData.size()); + di.Write((const unsigned char*)strKeyData.data(), strKeyData.size()); di.Write(chSalt.data(), chSalt.size()); di.Finalize(buf); diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 3657a157b6..a8b3df1f2e 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -6,6 +6,7 @@ #include <init.h> #include <interfaces/chain.h> #include <net.h> +#include <node/context.h> #include <outputtype.h> #include <util/moneystr.h> #include <util/system.h> @@ -25,8 +26,8 @@ public: //! Wallets parameter interaction bool ParameterInteraction() const override; - //! Add wallets that should be opened to list of init interfaces. - void Construct(InitInterfaces& interfaces) const override; + //! Add wallets that should be opened to list of chain clients. + void Construct(NodeContext& node) const override; }; const WalletInitInterface& g_wallet_init_interface = WalletInit(); @@ -125,12 +126,12 @@ bool WalletInit::ParameterInteraction() const return true; } -void WalletInit::Construct(InitInterfaces& interfaces) const +void WalletInit::Construct(NodeContext& node) const { if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { LogPrintf("Wallet disabled!\n"); return; } gArgs.SoftSetArg("-wallet", ""); - interfaces.chain_clients.emplace_back(interfaces::MakeWalletClient(*interfaces.chain, gArgs.GetArgs("-wallet"))); + node.chain_clients.emplace_back(interfaces::MakeWalletClient(*node.chain, gArgs.GetArgs("-wallet"))); } diff --git a/src/wallet/ismine.cpp b/src/wallet/ismine.cpp deleted file mode 100644 index 029b922785..0000000000 --- a/src/wallet/ismine.cpp +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-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 <wallet/ismine.h> - -#include <key.h> -#include <script/script.h> -#include <script/signingprovider.h> -#include <wallet/wallet.h> - -typedef std::vector<unsigned char> valtype; - -namespace { - -/** - * This is an enum that tracks the execution context of a script, similar to - * SigVersion in script/interpreter. It is separate however because we want to - * distinguish between top-level scriptPubKey execution and P2SH redeemScript - * execution (a distinction that has no impact on consensus rules). - */ -enum class IsMineSigVersion -{ - TOP = 0, //!< scriptPubKey execution - P2SH = 1, //!< P2SH redeemScript - WITNESS_V0 = 2, //!< P2WSH witness script execution -}; - -/** - * This is an internal representation of isminetype + invalidity. - * Its order is significant, as we return the max of all explored - * possibilities. - */ -enum class IsMineResult -{ - NO = 0, //!< Not ours - WATCH_ONLY = 1, //!< Included in watch-only balance - SPENDABLE = 2, //!< Included in all balances - INVALID = 3, //!< Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness) -}; - -bool PermitsUncompressed(IsMineSigVersion sigversion) -{ - return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH; -} - -bool HaveKeys(const std::vector<valtype>& pubkeys, const CWallet& keystore) -{ - for (const valtype& pubkey : pubkeys) { - CKeyID keyID = CPubKey(pubkey).GetID(); - if (!keystore.HaveKey(keyID)) return false; - } - return true; -} - -IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion) -{ - IsMineResult ret = IsMineResult::NO; - - std::vector<valtype> vSolutions; - txnouttype whichType = Solver(scriptPubKey, vSolutions); - - CKeyID keyID; - switch (whichType) - { - case TX_NONSTANDARD: - case TX_NULL_DATA: - case TX_WITNESS_UNKNOWN: - break; - case TX_PUBKEY: - keyID = CPubKey(vSolutions[0]).GetID(); - if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) { - return IsMineResult::INVALID; - } - if (keystore.HaveKey(keyID)) { - ret = std::max(ret, IsMineResult::SPENDABLE); - } - break; - case TX_WITNESS_V0_KEYHASH: - { - if (sigversion == IsMineSigVersion::WITNESS_V0) { - // P2WPKH inside P2WSH is invalid. - return IsMineResult::INVALID; - } - if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { - // We do not support bare witness outputs unless the P2SH version of it would be - // acceptable as well. This protects against matching before segwit activates. - // This also applies to the P2WSH case. - break; - } - ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(PKHash(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0)); - break; - } - case TX_PUBKEYHASH: - keyID = CKeyID(uint160(vSolutions[0])); - if (!PermitsUncompressed(sigversion)) { - CPubKey pubkey; - if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) { - return IsMineResult::INVALID; - } - } - if (keystore.HaveKey(keyID)) { - ret = std::max(ret, IsMineResult::SPENDABLE); - } - break; - case TX_SCRIPTHASH: - { - if (sigversion != IsMineSigVersion::TOP) { - // P2SH inside P2WSH or P2SH is invalid. - return IsMineResult::INVALID; - } - CScriptID scriptID = CScriptID(uint160(vSolutions[0])); - CScript subscript; - if (keystore.GetCScript(scriptID, subscript)) { - ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH)); - } - break; - } - case TX_WITNESS_V0_SCRIPTHASH: - { - if (sigversion == IsMineSigVersion::WITNESS_V0) { - // P2WSH inside P2WSH is invalid. - return IsMineResult::INVALID; - } - if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { - break; - } - uint160 hash; - CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(hash.begin()); - CScriptID scriptID = CScriptID(hash); - CScript subscript; - if (keystore.GetCScript(scriptID, subscript)) { - ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0)); - } - break; - } - - case TX_MULTISIG: - { - // Never treat bare multisig outputs as ours (they can still be made watchonly-though) - if (sigversion == IsMineSigVersion::TOP) { - break; - } - - // Only consider transactions "mine" if we own ALL the - // keys involved. Multi-signature transactions that are - // partially owned (somebody else has a key that can spend - // them) enable spend-out-from-under-you attacks, especially - // in shared-wallet situations. - std::vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1); - if (!PermitsUncompressed(sigversion)) { - for (size_t i = 0; i < keys.size(); i++) { - if (keys[i].size() != 33) { - return IsMineResult::INVALID; - } - } - } - if (HaveKeys(keys, keystore)) { - ret = std::max(ret, IsMineResult::SPENDABLE); - } - break; - } - } - - if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) { - ret = std::max(ret, IsMineResult::WATCH_ONLY); - } - return ret; -} - -} // namespace - -isminetype IsMine(const CWallet& keystore, const CScript& scriptPubKey) -{ - switch (IsMineInner(keystore, scriptPubKey, IsMineSigVersion::TOP)) { - case IsMineResult::INVALID: - case IsMineResult::NO: - return ISMINE_NO; - case IsMineResult::WATCH_ONLY: - return ISMINE_WATCH_ONLY; - case IsMineResult::SPENDABLE: - return ISMINE_SPENDABLE; - } - assert(false); -} - -isminetype IsMine(const CWallet& keystore, const CTxDestination& dest) -{ - CScript script = GetScriptForDestination(dest); - return IsMine(keystore, script); -} diff --git a/src/wallet/ismine.h b/src/wallet/ismine.h index 41555fcb93..0bc6c90354 100644 --- a/src/wallet/ismine.h +++ b/src/wallet/ismine.h @@ -28,9 +28,6 @@ enum isminetype : unsigned int /** used for bitflags of isminetype */ typedef uint8_t isminefilter; -isminetype IsMine(const CWallet& wallet, const CScript& scriptPubKey); -isminetype IsMine(const CWallet& wallet, const CTxDestination& dest); - /** * Cachable amount subdivided into watchonly and spendable parts. */ diff --git a/src/wallet/psbtwallet.cpp b/src/wallet/psbtwallet.cpp index 721a244afb..aa13cacca4 100644 --- a/src/wallet/psbtwallet.cpp +++ b/src/wallet/psbtwallet.cpp @@ -39,12 +39,12 @@ TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& ps return TransactionError::SIGHASH_MISMATCH; } - complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), psbtx, i, sighash_type); + complete &= SignPSBTInput(HidingSigningProvider(pwallet->GetSigningProvider(), !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 < psbtx.tx->vout.size(); ++i) { - UpdatePSBTOutput(HidingSigningProvider(pwallet, true, !bip32derivs), psbtx, i); + UpdatePSBTOutput(HidingSigningProvider(pwallet->GetSigningProvider(), true, !bip32derivs), psbtx, i); } return TransactionError::OK; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 9af567fe0d..f7353ebbbb 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -54,11 +54,11 @@ static std::string DecodeDumpString(const std::string &str) { return ret.str(); } -static bool GetWalletAddressesForKey(CWallet* const pwallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static bool GetWalletAddressesForKey(LegacyScriptPubKeyMan* spk_man, CWallet* const pwallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { bool fLabelFound = false; CKey key; - pwallet->GetKey(keyid, key); + spk_man->GetKey(keyid, key); for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) { if (pwallet->mapAddressBook.count(dest)) { if (!strAddr.empty()) { @@ -87,6 +87,15 @@ static void RescanWallet(CWallet& wallet, const WalletRescanReserver& reserver, } } +static LegacyScriptPubKeyMan& GetLegacyScriptPubKeyMan(CWallet& wallet) +{ + LegacyScriptPubKeyMan* spk_man = wallet.GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + return *spk_man; +} + UniValue importprivkey(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -125,6 +134,8 @@ UniValue importprivkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } + LegacyScriptPubKeyMan& spk_man = GetLegacyScriptPubKeyMan(*wallet); + WalletRescanReserver reserver(pwallet); bool fRescan = true; { @@ -251,6 +262,7 @@ UniValue importaddress(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan& spk_man = GetLegacyScriptPubKeyMan(*pwallet); std::string strLabel; if (!request.params[1].isNull()) @@ -453,6 +465,7 @@ UniValue importpubkey(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan& spk_man = GetLegacyScriptPubKeyMan(*wallet); std::string strLabel; if (!request.params[1].isNull()) @@ -536,6 +549,8 @@ UniValue importwallet(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan& spk_man = GetLegacyScriptPubKeyMan(*wallet); + if (pwallet->chain().havePruned()) { // Exit early and print an error. // If a block is pruned after this check, we will import the key(s), @@ -693,6 +708,8 @@ UniValue dumpprivkey(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan& spk_man = GetLegacyScriptPubKeyMan(*wallet); + auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -703,12 +720,12 @@ UniValue dumpprivkey(const JSONRPCRequest& request) if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } - auto keyid = GetKeyForDestination(*pwallet, dest); + auto keyid = GetKeyForDestination(spk_man, dest); if (keyid.IsNull()) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); } CKey vchSecret; - if (!pwallet->GetKey(keyid, vchSecret)) { + if (!spk_man.GetKey(keyid, vchSecret)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); } return EncodeSecret(vchSecret); @@ -742,8 +759,11 @@ UniValue dumpwallet(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan& spk_man = GetLegacyScriptPubKeyMan(*wallet); + auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); + AssertLockHeld(spk_man.cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -765,10 +785,10 @@ UniValue dumpwallet(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); std::map<CKeyID, int64_t> mapKeyBirth; - const std::map<CKeyID, int64_t>& mapKeyPool = pwallet->GetAllReserveKeys(); + const std::map<CKeyID, int64_t>& mapKeyPool = spk_man.GetAllReserveKeys(); pwallet->GetKeyBirthTimes(*locked_chain, mapKeyBirth); - std::set<CScriptID> scripts = pwallet->GetCScripts(); + std::set<CScriptID> scripts = spk_man.GetCScripts(); // sort time/key pairs std::vector<std::pair<int64_t, CKeyID> > vKeyBirth; @@ -787,11 +807,11 @@ UniValue dumpwallet(const JSONRPCRequest& request) file << "\n"; // add the base58check encoded extended master if the wallet uses HD - CKeyID seed_id = pwallet->GetHDChain().seed_id; + CKeyID seed_id = spk_man.GetHDChain().seed_id; if (!seed_id.IsNull()) { CKey seed; - if (pwallet->GetKey(seed_id, seed)) { + if (spk_man.GetKey(seed_id, seed)) { CExtKey masterKey; masterKey.SetSeed(seed.begin(), seed.size()); @@ -804,20 +824,20 @@ UniValue dumpwallet(const JSONRPCRequest& request) std::string strAddr; std::string strLabel; CKey key; - if (pwallet->GetKey(keyid, key)) { + if (spk_man.GetKey(keyid, key)) { file << strprintf("%s %s ", EncodeSecret(key), strTime); - if (GetWalletAddressesForKey(pwallet, keyid, strAddr, strLabel)) { + if (GetWalletAddressesForKey(&spk_man, pwallet, keyid, strAddr, strLabel)) { file << strprintf("label=%s", strLabel); } else if (keyid == seed_id) { file << "hdseed=1"; } else if (mapKeyPool.count(keyid)) { file << "reserve=1"; - } else if (pwallet->mapKeyMetadata[keyid].hdKeypath == "s") { + } else if (spk_man.mapKeyMetadata[keyid].hdKeypath == "s") { file << "inactivehdseed=1"; } else { file << "change=1"; } - file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(pwallet->mapKeyMetadata[keyid].key_origin.path) : "")); + file << strprintf(" # addr=%s%s\n", strAddr, (spk_man.mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(spk_man.mapKeyMetadata[keyid].key_origin.path) : "")); } } file << "\n"; @@ -826,11 +846,11 @@ UniValue dumpwallet(const JSONRPCRequest& request) std::string create_time = "0"; std::string address = EncodeDestination(ScriptHash(scriptid)); // get birth times for scripts with metadata - auto it = pwallet->m_script_metadata.find(scriptid); - if (it != pwallet->m_script_metadata.end()) { + auto it = spk_man.m_script_metadata.find(scriptid); + if (it != spk_man.m_script_metadata.end()) { create_time = FormatISO8601DateTime(it->second.nCreateTime); } - if(pwallet->GetCScript(scriptid, script)) { + if(spk_man.GetCScript(scriptid, script)) { file << strprintf("%s %s script=1", HexStr(script.begin(), script.end()), create_time); file << strprintf(" # addr=%s\n", address); } @@ -1206,7 +1226,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con // Check whether we have any work to do for (const CScript& script : script_pub_keys) { - if (::IsMine(*pwallet, script) & ISMINE_SPENDABLE) { + if (pwallet->IsMine(script) & ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script.begin(), script.end()) + "\")"); } } @@ -1326,6 +1346,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); + LegacyScriptPubKeyMan& spk_man = GetLegacyScriptPubKeyMan(*wallet); + const UniValue& requests = mainRequest.params[0]; //Default options diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e571501221..bfa4cf2bbe 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5,9 +5,9 @@ #include <amount.h> #include <core_io.h> -#include <init.h> #include <interfaces/chain.h> #include <key_io.h> +#include <node/context.h> #include <outputtype.h> #include <policy/feerate.h> #include <policy/fees.h> @@ -68,7 +68,7 @@ static bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWall /** Checks if a CKey is in the given CWallet compressed or otherwise*/ -bool HaveKey(const CWallet& wallet, const CKey& key) +bool HaveKey(const SigningProvider& wallet, const CKey& key) { CKey key2; key2.Set(key.begin(), key.end(), !key.IsCompressed()); @@ -303,7 +303,7 @@ static UniValue setlabel(const JSONRPCRequest& request) std::string label = LabelFromValue(request.params[1]); - if (IsMine(*pwallet, dest)) { + if (pwallet->IsMine(dest)) { pwallet->SetAddressBook(dest, label, "receive"); } else { pwallet->SetAddressBook(dest, label, "send"); @@ -550,9 +550,11 @@ static UniValue signmessage(const JSONRPCRequest& request) throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } + const SigningProvider* provider = pwallet->GetSigningProvider(); + CKey key; CKeyID keyID(*pkhash); - if (!pwallet->GetKey(keyID, key)) { + if (!provider->GetKey(keyID, key)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); } @@ -610,7 +612,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } CScript scriptPubKey = GetScriptForDestination(dest); - if (!IsMine(*pwallet, scriptPubKey)) { + if (!pwallet->IsMine(scriptPubKey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); } @@ -694,7 +696,7 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) for (const CTxOut& txout : wtx.tx->vout) { CTxDestination address; - if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwallet, address) && setAddress.count(address)) { + if (ExtractDestination(txout.scriptPubKey, address) && pwallet->IsMine(address) && setAddress.count(address)) { if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) nAmount += txout.nValue; } @@ -964,6 +966,11 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -980,7 +987,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) { pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str())); } else { - pubkeys.push_back(AddrToPubKey(pwallet, keys_or_addrs[i].get_str())); + pubkeys.push_back(AddrToPubKey(spk_man, keys_or_addrs[i].get_str())); } } @@ -993,7 +1000,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) // Construct using pay-to-script-hash: CScript inner; - CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, *pwallet, inner); + CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, *spk_man, inner); pwallet->SetAddressBook(dest, label, "send"); UniValue result(UniValue::VOBJ); @@ -1064,7 +1071,7 @@ static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * co continue; } - isminefilter mine = IsMine(*pwallet, address); + isminefilter mine = pwallet->IsMine(address); if(!(mine & filter)) continue; @@ -1288,7 +1295,7 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* con for (const COutputEntry& s : listSent) { UniValue entry(UniValue::VOBJ); - if (involvesWatchonly || (::IsMine(*pwallet, s.destination) & ISMINE_WATCH_ONLY)) { + if (involvesWatchonly || (pwallet->IsMine(s.destination) & ISMINE_WATCH_ONLY)) { entry.pushKV("involvesWatchonly", true); } MaybePushAddress(entry, s.destination); @@ -1319,7 +1326,7 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* con continue; } UniValue entry(UniValue::VOBJ); - if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) { + if (involvesWatchonly || (pwallet->IsMine(r.destination) & ISMINE_WATCH_ONLY)) { entry.pushKV("involvesWatchonly", true); } MaybePushAddress(entry, r.destination); @@ -2379,7 +2386,8 @@ static UniValue getbalances(const JSONRPCRequest& request) } balances.pushKV("mine", balances_mine); } - if (wallet.HaveWatchOnly()) { + auto spk_man = wallet.GetLegacyScriptPubKeyMan(); + if (spk_man && spk_man->HaveWatchOnly()) { UniValue balances_watchonly{UniValue::VOBJ}; balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted)); balances_watchonly.pushKV("untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending)); @@ -2449,7 +2457,15 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) obj.pushKV("txcount", (int)pwallet->mapWallet.size()); obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime()); obj.pushKV("keypoolsize", (int64_t)kpExternalSize); - CKeyID seed_id = pwallet->GetHDChain().seed_id; + + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (spk_man) { + CKeyID seed_id = spk_man->GetHDChain().seed_id; + if (!seed_id.IsNull()) { + obj.pushKV("hdseedid", seed_id.GetHex()); + } + } + if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)); } @@ -2457,9 +2473,6 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) obj.pushKV("unlocked_until", pwallet->nRelockTime); } obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK())); - if (!seed_id.IsNull()) { - obj.pushKV("hdseedid", seed_id.GetHex()); - } obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)); if (pwallet->IsScanning()) { @@ -2574,7 +2587,7 @@ static UniValue loadwallet(const JSONRPCRequest& request) std::string error; std::vector<std::string> warning; - std::shared_ptr<CWallet> const wallet = LoadWallet(*g_rpc_interfaces->chain, location, error, warning); + std::shared_ptr<CWallet> const wallet = LoadWallet(*g_rpc_chain, location, error, warning); if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error); UniValue obj(UniValue::VOBJ); @@ -2700,7 +2713,7 @@ static UniValue createwallet(const JSONRPCRequest& request) std::string error; std::shared_ptr<CWallet> wallet; - WalletCreationStatus status = CreateWallet(*g_rpc_interfaces->chain, passphrase, flags, request.params[0].get_str(), error, warnings, wallet); + WalletCreationStatus status = CreateWallet(*g_rpc_chain, passphrase, flags, request.params[0].get_str(), error, warnings, wallet); switch (status) { case WalletCreationStatus::CREATION_FAILED: throw JSONRPCError(RPC_WALLET_ERROR, error); @@ -2920,10 +2933,11 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("label", i->second.name); } + const SigningProvider* provider = pwallet->GetSigningProvider(); if (scriptPubKey.IsPayToScriptHash()) { const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address)); CScript redeemScript; - if (pwallet->GetCScript(hash, redeemScript)) { + if (provider->GetCScript(hash, redeemScript)) { entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); // Now check if the redeemScript is actually a P2WSH script CTxDestination witness_destination; @@ -2935,7 +2949,7 @@ static UniValue listunspent(const JSONRPCRequest& request) CScriptID id; CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); CScript witnessScript; - if (pwallet->GetCScript(id, witnessScript)) { + if (provider->GetCScript(id, witnessScript)) { entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); } } @@ -2945,7 +2959,7 @@ static UniValue listunspent(const JSONRPCRequest& request) CScriptID id; CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); CScript witnessScript; - if (pwallet->GetCScript(id, witnessScript)) { + if (provider->GetCScript(id, witnessScript)) { entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); } } @@ -2957,7 +2971,7 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); if (out.fSolvable) { - auto descriptor = InferDescriptor(scriptPubKey, *pwallet); + auto descriptor = InferDescriptor(scriptPubKey, *pwallet->GetLegacyScriptPubKeyMan()); entry.pushKV("desc", descriptor->ToString()); } if (avoid_reuse) entry.pushKV("reused", reused); @@ -3267,7 +3281,7 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) // Parse the prevtxs array ParsePrevouts(request.params[1], nullptr, coins); - return SignTransaction(mtx, pwallet, coins, request.params[2]); + return SignTransaction(mtx, &*pwallet->GetLegacyScriptPubKeyMan(), coins, request.params[2]); } static UniValue bumpfee(const JSONRPCRequest& request) @@ -3539,7 +3553,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request) class DescribeWalletAddressVisitor : public boost::static_visitor<UniValue> { public: - CWallet * const pwallet; + const SigningProvider * const provider; void ProcessSubScript(const CScript& subscript, UniValue& obj) const { @@ -3575,7 +3589,7 @@ public: } } - explicit DescribeWalletAddressVisitor(CWallet* _pwallet) : pwallet(_pwallet) {} + explicit DescribeWalletAddressVisitor(const SigningProvider* _provider) : provider(_provider) {} UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); } @@ -3584,7 +3598,7 @@ public: CKeyID keyID(pkhash); UniValue obj(UniValue::VOBJ); CPubKey vchPubKey; - if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) { + if (provider && provider->GetPubKey(keyID, vchPubKey)) { obj.pushKV("pubkey", HexStr(vchPubKey)); obj.pushKV("iscompressed", vchPubKey.IsCompressed()); } @@ -3596,7 +3610,7 @@ public: CScriptID scriptID(scripthash); UniValue obj(UniValue::VOBJ); CScript subscript; - if (pwallet && pwallet->GetCScript(scriptID, subscript)) { + if (provider && provider->GetCScript(scriptID, subscript)) { ProcessSubScript(subscript, obj); } return obj; @@ -3606,7 +3620,7 @@ public: { UniValue obj(UniValue::VOBJ); CPubKey pubkey; - if (pwallet && pwallet->GetPubKey(CKeyID(id), pubkey)) { + if (provider && provider->GetPubKey(CKeyID(id), pubkey)) { obj.pushKV("pubkey", HexStr(pubkey)); } return obj; @@ -3619,7 +3633,7 @@ public: CRIPEMD160 hasher; uint160 hash; hasher.Write(id.begin(), 32).Finalize(hash.begin()); - if (pwallet && pwallet->GetCScript(CScriptID(hash), subscript)) { + if (provider && provider->GetCScript(CScriptID(hash), subscript)) { ProcessSubScript(subscript, obj); } return obj; @@ -3632,8 +3646,12 @@ static UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& de { UniValue ret(UniValue::VOBJ); UniValue detail = DescribeAddress(dest); + const SigningProvider* provider = nullptr; + if (pwallet) { + provider = pwallet->GetSigningProvider(); + } ret.pushKVs(detail); - ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(pwallet), dest)); + ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(provider), dest)); return ret; } @@ -3722,13 +3740,14 @@ UniValue getaddressinfo(const JSONRPCRequest& request) CScript scriptPubKey = GetScriptForDestination(dest); ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); + const SigningProvider* provider = pwallet->GetSigningProvider(); - isminetype mine = IsMine(*pwallet, dest); + isminetype mine = pwallet->IsMine(dest); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); - bool solvable = IsSolvable(*pwallet, scriptPubKey); + bool solvable = IsSolvable(*provider, scriptPubKey); ret.pushKV("solvable", solvable); if (solvable) { - ret.pushKV("desc", InferDescriptor(scriptPubKey, *pwallet)->ToString()); + ret.pushKV("desc", InferDescriptor(scriptPubKey, *provider)->ToString()); } ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); UniValue detail = DescribeWalletAddress(pwallet, dest); @@ -3738,7 +3757,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) } ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); const CKeyMetadata* meta = nullptr; - CKeyID key_id = GetKeyForDestination(*pwallet, dest); + CKeyID key_id = GetKeyForDestination(*provider, dest); if (!key_id.IsNull()) { auto it = pwallet->mapKeyMetadata.find(key_id); if (it != pwallet->mapKeyMetadata.end()) { @@ -3916,6 +3935,11 @@ UniValue sethdseed(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + if (pwallet->chain().isInitialBlockDownload()) { throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download"); } @@ -3941,22 +3965,22 @@ UniValue sethdseed(const JSONRPCRequest& request) CPubKey master_pub_key; if (request.params[1].isNull()) { - master_pub_key = pwallet->GenerateNewSeed(); + master_pub_key = spk_man->GenerateNewSeed(); } else { CKey key = DecodeSecret(request.params[1].get_str()); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); } - if (HaveKey(*pwallet, key)) { + if (HaveKey(*spk_man, key)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)"); } - master_pub_key = pwallet->DeriveNewSeed(key); + master_pub_key = spk_man->DeriveNewSeed(key); } - pwallet->SetHDSeed(master_pub_key); - if (flush_key_pool) pwallet->NewKeyPool(); + spk_man->SetHDSeed(master_pub_key); + if (flush_key_pool) spk_man->NewKeyPool(); return NullUniValue; } @@ -4231,3 +4255,5 @@ void RegisterWalletRPCCommands(interfaces::Chain& chain, std::vector<std::unique for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) handlers.emplace_back(chain.handleRpc(commands[vcidx])); } + +interfaces::Chain* g_rpc_chain = nullptr; diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 1c0523c90b..31d3f7a5f9 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -21,6 +21,12 @@ class Chain; class Handler; } +//! Pointer to chain interface that needs to be declared as a global to be +//! accessible loadwallet and createwallet methods. Due to limitations of the +//! RPC framework, there's currently no direct way to pass in state to RPC +//! methods without globals. +extern interfaces::Chain* g_rpc_chain; + void RegisterWalletRPCCommands(interfaces::Chain& chain, std::vector<std::unique_ptr<interfaces::Handler>>& handlers); /** diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp new file mode 100644 index 0000000000..259bfcd76d --- /dev/null +++ b/src/wallet/scriptpubkeyman.cpp @@ -0,0 +1,1276 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <key_io.h> +#include <outputtype.h> +#include <script/descriptor.h> +#include <util/bip32.h> +#include <util/strencodings.h> +#include <util/translation.h> +#include <wallet/scriptpubkeyman.h> +#include <wallet/wallet.h> + +bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error) +{ + LOCK(cs_wallet); + error.clear(); + TopUpKeyPool(); + + // Generate a new key that is added to wallet + CPubKey new_key; + if (!GetKeyFromPool(new_key)) { + error = "Error: Keypool ran out, please call keypoolrefill first"; + return false; + } + LearnRelatedScripts(new_key, type); + dest = GetDestinationForKey(new_key, type); + + m_wallet.SetAddressBook(dest, label, "receive"); + return true; +} + +typedef std::vector<unsigned char> valtype; + +namespace { + +/** + * This is an enum that tracks the execution context of a script, similar to + * SigVersion in script/interpreter. It is separate however because we want to + * distinguish between top-level scriptPubKey execution and P2SH redeemScript + * execution (a distinction that has no impact on consensus rules). + */ +enum class IsMineSigVersion +{ + TOP = 0, //!< scriptPubKey execution + P2SH = 1, //!< P2SH redeemScript + WITNESS_V0 = 2, //!< P2WSH witness script execution +}; + +/** + * This is an internal representation of isminetype + invalidity. + * Its order is significant, as we return the max of all explored + * possibilities. + */ +enum class IsMineResult +{ + NO = 0, //!< Not ours + WATCH_ONLY = 1, //!< Included in watch-only balance + SPENDABLE = 2, //!< Included in all balances + INVALID = 3, //!< Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness) +}; + +bool PermitsUncompressed(IsMineSigVersion sigversion) +{ + return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH; +} + +bool HaveKeys(const std::vector<valtype>& pubkeys, const LegacyScriptPubKeyMan& keystore) +{ + for (const valtype& pubkey : pubkeys) { + CKeyID keyID = CPubKey(pubkey).GetID(); + if (!keystore.HaveKey(keyID)) return false; + } + return true; +} + +IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion) +{ + IsMineResult ret = IsMineResult::NO; + + std::vector<valtype> vSolutions; + txnouttype whichType = Solver(scriptPubKey, vSolutions); + + CKeyID keyID; + switch (whichType) + { + case TX_NONSTANDARD: + case TX_NULL_DATA: + case TX_WITNESS_UNKNOWN: + break; + case TX_PUBKEY: + keyID = CPubKey(vSolutions[0]).GetID(); + if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) { + return IsMineResult::INVALID; + } + if (keystore.HaveKey(keyID)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } + break; + case TX_WITNESS_V0_KEYHASH: + { + if (sigversion == IsMineSigVersion::WITNESS_V0) { + // P2WPKH inside P2WSH is invalid. + return IsMineResult::INVALID; + } + if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { + // We do not support bare witness outputs unless the P2SH version of it would be + // acceptable as well. This protects against matching before segwit activates. + // This also applies to the P2WSH case. + break; + } + ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(PKHash(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0)); + break; + } + case TX_PUBKEYHASH: + keyID = CKeyID(uint160(vSolutions[0])); + if (!PermitsUncompressed(sigversion)) { + CPubKey pubkey; + if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) { + return IsMineResult::INVALID; + } + } + if (keystore.HaveKey(keyID)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } + break; + case TX_SCRIPTHASH: + { + if (sigversion != IsMineSigVersion::TOP) { + // P2SH inside P2WSH or P2SH is invalid. + return IsMineResult::INVALID; + } + CScriptID scriptID = CScriptID(uint160(vSolutions[0])); + CScript subscript; + if (keystore.GetCScript(scriptID, subscript)) { + ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH)); + } + break; + } + case TX_WITNESS_V0_SCRIPTHASH: + { + if (sigversion == IsMineSigVersion::WITNESS_V0) { + // P2WSH inside P2WSH is invalid. + return IsMineResult::INVALID; + } + if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { + break; + } + uint160 hash; + CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(hash.begin()); + CScriptID scriptID = CScriptID(hash); + CScript subscript; + if (keystore.GetCScript(scriptID, subscript)) { + ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0)); + } + break; + } + + case TX_MULTISIG: + { + // Never treat bare multisig outputs as ours (they can still be made watchonly-though) + if (sigversion == IsMineSigVersion::TOP) { + break; + } + + // Only consider transactions "mine" if we own ALL the + // keys involved. Multi-signature transactions that are + // partially owned (somebody else has a key that can spend + // them) enable spend-out-from-under-you attacks, especially + // in shared-wallet situations. + std::vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1); + if (!PermitsUncompressed(sigversion)) { + for (size_t i = 0; i < keys.size(); i++) { + if (keys[i].size() != 33) { + return IsMineResult::INVALID; + } + } + } + if (HaveKeys(keys, keystore)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } + break; + } + } + + if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) { + ret = std::max(ret, IsMineResult::WATCH_ONLY); + } + return ret; +} + +} // namespace + +isminetype LegacyScriptPubKeyMan::IsMine(const CScript& script) const +{ + switch (IsMineInner(*this, script, IsMineSigVersion::TOP)) { + case IsMineResult::INVALID: + case IsMineResult::NO: + return ISMINE_NO; + case IsMineResult::WATCH_ONLY: + return ISMINE_WATCH_ONLY; + case IsMineResult::SPENDABLE: + return ISMINE_SPENDABLE; + } + assert(false); +} + +bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) +{ + { + LOCK(cs_KeyStore); + if (!SetCrypted()) + return false; + + bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys + bool keyFail = false; + CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); + for (; mi != mapCryptedKeys.end(); ++mi) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; + CKey key; + if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) + { + keyFail = true; + break; + } + keyPass = true; + if (fDecryptionThoroughlyChecked) + break; + } + if (keyPass && keyFail) + { + LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); + throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); + } + if (keyFail || (!keyPass && !accept_no_keys)) + return false; + vMasterKey = vMasterKeyIn; + fDecryptionThoroughlyChecked = true; + } + NotifyStatusChanged(this); + return true; +} + +bool LegacyScriptPubKeyMan::EncryptKeys(CKeyingMaterial& vMasterKeyIn) +{ + LOCK(cs_KeyStore); + if (!mapCryptedKeys.empty() || IsCrypted()) + return false; + + fUseCrypto = true; + for (const KeyMap::value_type& mKey : mapKeys) + { + const CKey &key = mKey.second; + CPubKey vchPubKey = key.GetPubKey(); + CKeyingMaterial vchSecret(key.begin(), key.end()); + std::vector<unsigned char> vchCryptedSecret; + if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) + return false; + if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) + return false; + } + mapKeys.clear(); + return true; +} + +void LegacyScriptPubKeyMan::UpgradeKeyMetadata() +{ + AssertLockHeld(cs_wallet); + if (m_storage.IsLocked() || m_storage.IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) { + return; + } + + std::unique_ptr<WalletBatch> batch = MakeUnique<WalletBatch>(m_storage.GetDatabase()); + for (auto& meta_pair : mapKeyMetadata) { + CKeyMetadata& meta = meta_pair.second; + if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin && meta.hdKeypath != "s") { // If the hdKeypath is "s", that's the seed and it doesn't have a key origin + CKey key; + GetKey(meta.hd_seed_id, key); + CExtKey masterKey; + masterKey.SetSeed(key.begin(), key.size()); + // Add to map + CKeyID master_id = masterKey.key.GetPubKey().GetID(); + std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint); + if (!ParseHDKeypath(meta.hdKeypath, meta.key_origin.path)) { + throw std::runtime_error("Invalid stored hdKeypath"); + } + meta.has_key_origin = true; + if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) { + meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN; + } + + // Write meta to wallet + CPubKey pubkey; + if (GetPubKey(meta_pair.first, pubkey)) { + batch->WriteKeyMetadata(meta, pubkey, true); + } + } + } + batch.reset(); //write before setting the flag + m_storage.SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA); +} + +bool LegacyScriptPubKeyMan::IsHDEnabled() const +{ + return !hdChain.seed_id.IsNull(); +} + +bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal) +{ + LOCK(cs_wallet); + // Check if the keypool has keys + bool keypool_has_keys; + if (internal && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) { + keypool_has_keys = setInternalKeyPool.size() > 0; + } else { + keypool_has_keys = KeypoolCountExternalKeys() > 0; + } + // If the keypool doesn't have keys, check if we can generate them + if (!keypool_has_keys) { + return CanGenerateKeys(); + } + return keypool_has_keys; +} + +static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) { + if (setKeyPool.empty()) { + return GetTime(); + } + + CKeyPool keypool; + int64_t nIndex = *(setKeyPool.begin()); + if (!batch.ReadPool(nIndex, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed"); + } + assert(keypool.vchPubKey.IsValid()); + return keypool.nTime; +} + +int64_t LegacyScriptPubKeyMan::GetOldestKeyPoolTime() +{ + LOCK(cs_wallet); + + WalletBatch batch(m_storage.GetDatabase()); + + // load oldest key from keypool, get time and return + int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch); + if (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) { + oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch), oldestKey); + if (!set_pre_split_keypool.empty()) { + oldestKey = std::max(GetOldestKeyTimeInPool(set_pre_split_keypool, batch), oldestKey); + } + } + + return oldestKey; +} + +size_t LegacyScriptPubKeyMan::KeypoolCountExternalKeys() +{ + AssertLockHeld(cs_wallet); + return setExternalKeyPool.size() + set_pre_split_keypool.size(); +} + +/** + * Update wallet first key creation time. This should be called whenever keys + * are added to the wallet, with the oldest key creation time. + */ +void LegacyScriptPubKeyMan::UpdateTimeFirstKey(int64_t nCreateTime) +{ + AssertLockHeld(cs_wallet); + if (nCreateTime <= 1) { + // Cannot determine birthday information, so set the wallet birthday to + // the beginning of time. + nTimeFirstKey = 1; + } else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) { + nTimeFirstKey = nCreateTime; + } +} + +bool LegacyScriptPubKeyMan::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +{ + WalletBatch batch(m_storage.GetDatabase()); + return LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(batch, secret, pubkey); +} + +bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const CPubKey& pubkey) +{ + AssertLockHeld(cs_wallet); + + // Make sure we aren't adding private keys to private key disabled wallets + assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + + // FillableSigningProvider has no concept of wallet databases, but calls AddCryptedKey + // which is overridden below. To avoid flushes, the database handle is + // tunneled through to it. + bool needsDB = !encrypted_batch; + if (needsDB) { + encrypted_batch = &batch; + } + if (!AddKeyPubKeyInner(secret, pubkey)) { + if (needsDB) encrypted_batch = nullptr; + return false; + } + if (needsDB) encrypted_batch = nullptr; + + // check if we need to remove from watch-only + CScript script; + script = GetScriptForDestination(PKHash(pubkey)); + if (HaveWatchOnly(script)) { + RemoveWatchOnly(script); + } + script = GetScriptForRawPubKey(pubkey); + if (HaveWatchOnly(script)) { + RemoveWatchOnly(script); + } + + if (!IsCrypted()) { + return batch.WriteKey(pubkey, + secret.GetPrivKey(), + mapKeyMetadata[pubkey.GetID()]); + } + m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + return true; +} + +bool LegacyScriptPubKeyMan::LoadCScript(const CScript& redeemScript) +{ + /* A sanity check was added in pull #3843 to avoid adding redeemScripts + * that never can be redeemed. However, old wallets may still contain + * these. Do not add them to the wallet and warn. */ + if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) + { + std::string strAddr = EncodeDestination(ScriptHash(redeemScript)); + WalletLogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); + return true; + } + + return FillableSigningProvider::AddCScript(redeemScript); +} + +void LegacyScriptPubKeyMan::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta) +{ + AssertLockHeld(cs_wallet); + UpdateTimeFirstKey(meta.nCreateTime); + mapKeyMetadata[keyID] = meta; +} + +void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta) +{ + AssertLockHeld(cs_wallet); + UpdateTimeFirstKey(meta.nCreateTime); + m_script_metadata[script_id] = meta; +} + +bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::AddKeyPubKey(key, pubkey); + } + + if (m_storage.IsLocked()) { + return false; + } + + std::vector<unsigned char> vchCryptedSecret; + CKeyingMaterial vchSecret(key.begin(), key.end()); + if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { + return false; + } + + if (!AddCryptedKey(pubkey, vchCryptedSecret)) { + return false; + } + return true; +} + +bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) +{ + return AddCryptedKeyInner(vchPubKey, vchCryptedSecret); +} + +bool LegacyScriptPubKeyMan::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) +{ + LOCK(cs_KeyStore); + if (!SetCrypted()) { + return false; + } + + mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); + ImplicitlyLearnRelatedKeyScripts(vchPubKey); + return true; +} + +bool LegacyScriptPubKeyMan::AddCryptedKey(const CPubKey &vchPubKey, + const std::vector<unsigned char> &vchCryptedSecret) +{ + if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret)) + return false; + { + LOCK(cs_wallet); + if (encrypted_batch) + return encrypted_batch->WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); + else + return WalletBatch(m_storage.GetDatabase()).WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); + } +} + +bool LegacyScriptPubKeyMan::HaveWatchOnly(const CScript &dest) const +{ + LOCK(cs_KeyStore); + return setWatchOnly.count(dest) > 0; +} + +bool LegacyScriptPubKeyMan::HaveWatchOnly() const +{ + LOCK(cs_KeyStore); + return (!setWatchOnly.empty()); +} + +static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) +{ + std::vector<std::vector<unsigned char>> solutions; + return Solver(dest, solutions) == TX_PUBKEY && + (pubKeyOut = CPubKey(solutions[0])).IsFullyValid(); +} + +bool LegacyScriptPubKeyMan::RemoveWatchOnly(const CScript &dest) +{ + AssertLockHeld(cs_wallet); + { + LOCK(cs_KeyStore); + setWatchOnly.erase(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) { + mapWatchKeys.erase(pubKey.GetID()); + } + // Related CScripts are not removed; having superfluous scripts around is + // harmless (see comment in ImplicitlyLearnRelatedKeyScripts). + } + + if (!HaveWatchOnly()) + NotifyWatchonlyChanged(false); + if (!WalletBatch(m_storage.GetDatabase()).EraseWatchOnly(dest)) + return false; + + return true; +} + +bool LegacyScriptPubKeyMan::LoadWatchOnly(const CScript &dest) +{ + return AddWatchOnlyInMem(dest); +} + +bool LegacyScriptPubKeyMan::AddWatchOnlyInMem(const CScript &dest) +{ + LOCK(cs_KeyStore); + setWatchOnly.insert(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) { + mapWatchKeys[pubKey.GetID()] = pubKey; + ImplicitlyLearnRelatedKeyScripts(pubKey); + } + return true; +} + +bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) +{ + if (!AddWatchOnlyInMem(dest)) + return false; + const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)]; + UpdateTimeFirstKey(meta.nCreateTime); + NotifyWatchonlyChanged(true); + if (batch.WriteWatchOnly(dest, meta)) { + m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + return true; + } + return false; +} + +bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) +{ + m_script_metadata[CScriptID(dest)].nCreateTime = create_time; + return AddWatchOnlyWithDB(batch, dest); +} + +bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest) +{ + WalletBatch batch(m_storage.GetDatabase()); + return AddWatchOnlyWithDB(batch, dest); +} + +bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest, int64_t nCreateTime) +{ + m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime; + return AddWatchOnly(dest); +} + +void LegacyScriptPubKeyMan::SetHDChain(const CHDChain& chain, bool memonly) +{ + LOCK(cs_wallet); + if (!memonly && !WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain)) + throw std::runtime_error(std::string(__func__) + ": writing chain failed"); + + hdChain = chain; +} + +bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::HaveKey(address); + } + return mapCryptedKeys.count(address) > 0; +} + +bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::GetKey(address, keyOut); + } + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; + return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); + } + return false; +} + +bool LegacyScriptPubKeyMan::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const +{ + CKeyMetadata meta; + { + LOCK(cs_wallet); + auto it = mapKeyMetadata.find(keyID); + if (it != mapKeyMetadata.end()) { + meta = it->second; + } + } + if (meta.has_key_origin) { + std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint); + info.path = meta.key_origin.path; + } else { // Single pubkeys get the master fingerprint of themselves + std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint); + } + return true; +} + +bool LegacyScriptPubKeyMan::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const +{ + LOCK(cs_KeyStore); + WatchKeyMap::const_iterator it = mapWatchKeys.find(address); + if (it != mapWatchKeys.end()) { + pubkey_out = it->second; + return true; + } + return false; +} + +bool LegacyScriptPubKeyMan::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) { + return GetWatchPubKey(address, vchPubKeyOut); + } + return true; + } + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + vchPubKeyOut = (*mi).second.first; + return true; + } + // Check for watch-only pubkeys + return GetWatchPubKey(address, vchPubKeyOut); +} + +CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, bool internal) +{ + assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)); + AssertLockHeld(cs_wallet); + bool fCompressed = m_storage.CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets + + CKey secret; + + // Create new metadata + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + + // use HD key derivation if HD was enabled during wallet creation and a seed is present + if (IsHDEnabled()) { + DeriveNewChildKey(batch, metadata, secret, (m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); + } else { + secret.MakeNewKey(fCompressed); + } + + // Compressed public keys were introduced in version 0.6.0 + if (fCompressed) { + m_storage.SetMinVersion(FEATURE_COMPRPUBKEY); + } + + CPubKey pubkey = secret.GetPubKey(); + assert(secret.VerifyPubKey(pubkey)); + + mapKeyMetadata[pubkey.GetID()] = metadata; + UpdateTimeFirstKey(nCreationTime); + + if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) { + throw std::runtime_error(std::string(__func__) + ": AddKey failed"); + } + return pubkey; +} + +const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; + +void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal) +{ + // for now we use a fixed keypath scheme of m/0'/0'/k + CKey seed; //seed (256bit) + CExtKey masterKey; //hd master key + CExtKey accountKey; //key at m/0' + CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal) + CExtKey childKey; //key at m/0'/0'/<n>' + + // try to get the seed + if (!GetKey(hdChain.seed_id, seed)) + throw std::runtime_error(std::string(__func__) + ": seed not found"); + + masterKey.SetSeed(seed.begin(), seed.size()); + + // derive m/0' + // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) + masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT); + + // derive m/0'/0' (external chain) OR m/0'/1' (internal chain) + assert(internal ? m_storage.CanSupportFeature(FEATURE_HD_SPLIT) : true); + accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0)); + + // derive child key at next index, skip keys already known to the wallet + do { + // always derive hardened keys + // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range + // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 + if (internal) { + chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'"; + metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); + metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT); + metadata.key_origin.path.push_back(hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + hdChain.nInternalChainCounter++; + } + else { + chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'"; + metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); + metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); + metadata.key_origin.path.push_back(hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + hdChain.nExternalChainCounter++; + } + } while (HaveKey(childKey.key.GetPubKey().GetID())); + secret = childKey.key; + metadata.hd_seed_id = hdChain.seed_id; + CKeyID master_id = masterKey.key.GetPubKey().GetID(); + std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint); + metadata.has_key_origin = true; + // update the chain model in the database + if (!batch.WriteHDChain(hdChain)) + throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); +} + +void LegacyScriptPubKeyMan::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) +{ + AssertLockHeld(cs_wallet); + if (keypool.m_pre_split) { + set_pre_split_keypool.insert(nIndex); + } else if (keypool.fInternal) { + setInternalKeyPool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } + m_max_keypool_index = std::max(m_max_keypool_index, nIndex); + m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex; + + // If no metadata exists yet, create a default with the pool key's + // creation time. Note that this may be overwritten by actually + // stored metadata for that key later, which is fine. + CKeyID keyid = keypool.vchPubKey.GetID(); + if (mapKeyMetadata.count(keyid) == 0) + mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); +} + +bool LegacyScriptPubKeyMan::CanGenerateKeys() +{ + // A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a non-HD wallet (pre FEATURE_HD) + LOCK(cs_wallet); + return IsHDEnabled() || !m_storage.CanSupportFeature(FEATURE_HD); +} + +CPubKey LegacyScriptPubKeyMan::GenerateNewSeed() +{ + assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + CKey key; + key.MakeNewKey(true); + return DeriveNewSeed(key); +} + +CPubKey LegacyScriptPubKeyMan::DeriveNewSeed(const CKey& key) +{ + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + + // calculate the seed + CPubKey seed = key.GetPubKey(); + assert(key.VerifyPubKey(seed)); + + // set the hd keypath to "s" -> Seed, refers the seed to itself + metadata.hdKeypath = "s"; + metadata.has_key_origin = false; + metadata.hd_seed_id = seed.GetID(); + + { + LOCK(cs_wallet); + + // mem store the metadata + mapKeyMetadata[seed.GetID()] = metadata; + + // write the key&metadata to the database + if (!AddKeyPubKey(key, seed)) + throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); + } + + return seed; +} + +void LegacyScriptPubKeyMan::SetHDSeed(const CPubKey& seed) +{ + LOCK(cs_wallet); + // store the keyid (hash160) together with + // the child index counter in the database + // as a hdchain object + CHDChain newHdChain; + newHdChain.nVersion = m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; + newHdChain.seed_id = seed.GetID(); + SetHDChain(newHdChain, false); + NotifyCanGetAddressesChanged(); + m_wallet.UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); +} + +/** + * Mark old keypool keys as used, + * and generate all new keys + */ +bool LegacyScriptPubKeyMan::NewKeyPool() +{ + if (m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + return false; + } + { + LOCK(cs_wallet); + WalletBatch batch(m_storage.GetDatabase()); + + for (const int64_t nIndex : setInternalKeyPool) { + batch.ErasePool(nIndex); + } + setInternalKeyPool.clear(); + + for (const int64_t nIndex : setExternalKeyPool) { + batch.ErasePool(nIndex); + } + setExternalKeyPool.clear(); + + for (const int64_t nIndex : set_pre_split_keypool) { + batch.ErasePool(nIndex); + } + set_pre_split_keypool.clear(); + + m_pool_key_to_index.clear(); + + if (!TopUpKeyPool()) { + return false; + } + WalletLogPrintf("LegacyScriptPubKeyMan::NewKeyPool rewrote keypool\n"); + } + return true; +} + +bool LegacyScriptPubKeyMan::TopUpKeyPool(unsigned int kpSize) +{ + if (!CanGenerateKeys()) { + return false; + } + { + LOCK(cs_wallet); + + if (m_storage.IsLocked()) return false; + + // Top up key pool + unsigned int nTargetSize; + if (kpSize > 0) + nTargetSize = kpSize; + else + nTargetSize = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0); + + // count amount of available keys (internal, external) + // make sure the keypool of external and internal keys fits the user selected target (-keypool) + int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0); + int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0); + + if (!IsHDEnabled() || !m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) + { + // don't create extra internal keys + missingInternal = 0; + } + bool internal = false; + WalletBatch batch(m_storage.GetDatabase()); + for (int64_t i = missingInternal + missingExternal; i--;) + { + if (i < missingInternal) { + internal = true; + } + + CPubKey pubkey(GenerateNewKey(batch, internal)); + AddKeypoolPubkeyWithDB(pubkey, internal, batch); + } + if (missingInternal + missingExternal > 0) { + WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size()); + } + } + NotifyCanGetAddressesChanged(); + return true; +} + +void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch) +{ + LOCK(cs_wallet); + assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys? + int64_t index = ++m_max_keypool_index; + if (!batch.WritePool(index, CKeyPool(pubkey, internal))) { + throw std::runtime_error(std::string(__func__) + ": writing imported pubkey failed"); + } + if (internal) { + setInternalKeyPool.insert(index); + } else { + setExternalKeyPool.insert(index); + } + m_pool_key_to_index[pubkey.GetID()] = index; +} + +void LegacyScriptPubKeyMan::KeepKey(int64_t nIndex) +{ + // Remove from key pool + WalletBatch batch(m_storage.GetDatabase()); + batch.ErasePool(nIndex); + WalletLogPrintf("keypool keep %d\n", nIndex); +} + +void LegacyScriptPubKeyMan::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) +{ + // Return to key pool + { + LOCK(cs_wallet); + if (fInternal) { + setInternalKeyPool.insert(nIndex); + } else if (!set_pre_split_keypool.empty()) { + set_pre_split_keypool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } + m_pool_key_to_index[pubkey.GetID()] = nIndex; + NotifyCanGetAddressesChanged(); + } + WalletLogPrintf("keypool return %d\n", nIndex); +} + +bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, bool internal) +{ + if (!CanGetAddresses(internal)) { + return false; + } + + CKeyPool keypool; + { + LOCK(cs_wallet); + int64_t nIndex; + if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (m_storage.IsLocked()) return false; + WalletBatch batch(m_storage.GetDatabase()); + result = GenerateNewKey(batch, internal); + return true; + } + KeepKey(nIndex); + result = keypool.vchPubKey; + } + return true; +} + +bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) +{ + nIndex = -1; + keypool.vchPubKey = CPubKey(); + { + LOCK(cs_wallet); + + TopUpKeyPool(); + + bool fReturningInternal = fRequestedInternal; + fReturningInternal &= (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) || m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + bool use_split_keypool = set_pre_split_keypool.empty(); + std::set<int64_t>& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool; + + // Get the oldest key + if (setKeyPool.empty()) { + return false; + } + + WalletBatch batch(m_storage.GetDatabase()); + + auto it = setKeyPool.begin(); + nIndex = *it; + setKeyPool.erase(it); + if (!batch.ReadPool(nIndex, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read failed"); + } + CPubKey pk; + if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) { + throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); + } + // If the key was pre-split keypool, we don't care about what type it is + if (use_split_keypool && keypool.fInternal != fReturningInternal) { + throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); + } + if (!keypool.vchPubKey.IsValid()) { + throw std::runtime_error(std::string(__func__) + ": keypool entry invalid"); + } + + m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); + WalletLogPrintf("keypool reserve %d\n", nIndex); + } + NotifyCanGetAddressesChanged(); + return true; +} + +void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType type) +{ + if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) { + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + CScript witprog = GetScriptForDestination(witdest); + // Make sure the resulting program is solvable. + assert(IsSolvable(*this, witprog)); + AddCScript(witprog); + } +} + +void LegacyScriptPubKeyMan::LearnAllRelatedScripts(const CPubKey& key) +{ + // OutputType::P2SH_SEGWIT always adds all necessary scripts for all types. + LearnRelatedScripts(key, OutputType::P2SH_SEGWIT); +} + +void LegacyScriptPubKeyMan::MarkReserveKeysAsUsed(int64_t keypool_id) +{ + AssertLockHeld(cs_wallet); + bool internal = setInternalKeyPool.count(keypool_id); + if (!internal) assert(setExternalKeyPool.count(keypool_id) || set_pre_split_keypool.count(keypool_id)); + std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : (set_pre_split_keypool.empty() ? &setExternalKeyPool : &set_pre_split_keypool); + auto it = setKeyPool->begin(); + + WalletBatch batch(m_storage.GetDatabase()); + while (it != std::end(*setKeyPool)) { + const int64_t& index = *(it); + if (index > keypool_id) break; // set*KeyPool is ordered + + CKeyPool keypool; + if (batch.ReadPool(index, keypool)) { //TODO: This should be unnecessary + m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); + } + LearnAllRelatedScripts(keypool.vchPubKey); + batch.ErasePool(index); + WalletLogPrintf("keypool index %d removed\n", index); + it = setKeyPool->erase(it); + } +} + +std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider) +{ + std::vector<CScript> dummy; + FlatSigningProvider out; + InferDescriptor(spk, provider)->Expand(0, DUMMY_SIGNING_PROVIDER, dummy, out); + std::vector<CKeyID> ret; + for (const auto& entry : out.pubkeys) { + ret.push_back(entry.first); + } + return ret; +} + +void LegacyScriptPubKeyMan::MarkPreSplitKeys() +{ + WalletBatch batch(m_storage.GetDatabase()); + for (auto it = setExternalKeyPool.begin(); it != setExternalKeyPool.end();) { + int64_t index = *it; + CKeyPool keypool; + if (!batch.ReadPool(index, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read keypool entry failed"); + } + keypool.m_pre_split = true; + if (!batch.WritePool(index, keypool)) { + throw std::runtime_error(std::string(__func__) + ": writing modified keypool entry failed"); + } + set_pre_split_keypool.insert(index); + it = setExternalKeyPool.erase(it); + } +} + +bool LegacyScriptPubKeyMan::AddCScript(const CScript& redeemScript) +{ + WalletBatch batch(m_storage.GetDatabase()); + return AddCScriptWithDB(batch, redeemScript); +} + +bool LegacyScriptPubKeyMan::AddCScriptWithDB(WalletBatch& batch, const CScript& redeemScript) +{ + if (!FillableSigningProvider::AddCScript(redeemScript)) + return false; + if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) { + m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + return true; + } + return false; +} + +bool LegacyScriptPubKeyMan::AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info) +{ + LOCK(cs_wallet); + std::copy(info.fingerprint, info.fingerprint + 4, mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint); + mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path; + mapKeyMetadata[pubkey.GetID()].has_key_origin = true; + mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path); + return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); +} + +bool LegacyScriptPubKeyMan::ImportScripts(const std::set<CScript> scripts, int64_t timestamp) +{ + WalletBatch batch(m_storage.GetDatabase()); + for (const auto& entry : scripts) { + CScriptID id(entry); + if (HaveCScript(id)) { + WalletLogPrintf("Already have script %s, skipping\n", HexStr(entry)); + continue; + } + if (!AddCScriptWithDB(batch, entry)) { + return false; + } + + if (timestamp > 0) { + m_script_metadata[CScriptID(entry)].nCreateTime = timestamp; + } + } + if (timestamp > 0) { + UpdateTimeFirstKey(timestamp); + } + + return true; +} + +bool LegacyScriptPubKeyMan::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) +{ + WalletBatch batch(m_storage.GetDatabase()); + for (const auto& entry : privkey_map) { + const CKey& key = entry.second; + CPubKey pubkey = key.GetPubKey(); + const CKeyID& id = entry.first; + assert(key.VerifyPubKey(pubkey)); + // Skip if we already have the key + if (HaveKey(id)) { + WalletLogPrintf("Already have key with pubkey %s, skipping\n", HexStr(pubkey)); + continue; + } + mapKeyMetadata[id].nCreateTime = timestamp; + // If the private key is not present in the wallet, insert it. + if (!AddKeyPubKeyWithDB(batch, key, pubkey)) { + return false; + } + UpdateTimeFirstKey(timestamp); + } + return true; +} + +bool LegacyScriptPubKeyMan::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) +{ + WalletBatch batch(m_storage.GetDatabase()); + for (const auto& entry : key_origins) { + AddKeyOriginWithDB(batch, entry.second.first, entry.second.second); + } + for (const CKeyID& id : ordered_pubkeys) { + auto entry = pubkey_map.find(id); + if (entry == pubkey_map.end()) { + continue; + } + const CPubKey& pubkey = entry->second; + CPubKey temp; + if (GetPubKey(id, temp)) { + // Already have pubkey, skipping + WalletLogPrintf("Already have pubkey %s, skipping\n", HexStr(temp)); + continue; + } + if (!AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) { + return false; + } + mapKeyMetadata[id].nCreateTime = timestamp; + + // Add to keypool only works with pubkeys + if (add_keypool) { + AddKeypoolPubkeyWithDB(pubkey, internal, batch); + NotifyCanGetAddressesChanged(); + } + } + return true; +} + +bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) +{ + WalletBatch batch(m_storage.GetDatabase()); + for (const CScript& script : script_pub_keys) { + if (!have_solving_data || !IsMine(script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated + if (!AddWatchOnlyWithDB(batch, script, timestamp)) { + return false; + } + } + CTxDestination dest; + ExtractDestination(script, dest); + if (apply_label && IsValidDestination(dest)) { + m_wallet.SetAddressBookWithDB(batch, dest, label, "receive"); + } + } + return true; +} + +std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::GetKeys(); + } + std::set<CKeyID> set_address; + for (const auto& mi : mapCryptedKeys) { + set_address.insert(mi.first); + } + return set_address; +} + +// Temporary CWallet accessors and aliases. +LegacyScriptPubKeyMan::LegacyScriptPubKeyMan(CWallet& wallet) + : ScriptPubKeyMan(wallet), + m_wallet(wallet), + cs_wallet(wallet.cs_wallet), + vMasterKey(wallet.vMasterKey), + fUseCrypto(wallet.fUseCrypto), + fDecryptionThoroughlyChecked(wallet.fDecryptionThoroughlyChecked) {} + +bool LegacyScriptPubKeyMan::SetCrypted() { return m_wallet.SetCrypted(); } +bool LegacyScriptPubKeyMan::IsCrypted() const { return m_wallet.IsCrypted(); } +void LegacyScriptPubKeyMan::NotifyWatchonlyChanged(bool fHaveWatchOnly) const { return m_wallet.NotifyWatchonlyChanged(fHaveWatchOnly); } +void LegacyScriptPubKeyMan::NotifyCanGetAddressesChanged() const { return m_wallet.NotifyCanGetAddressesChanged(); } +template<typename... Params> void LegacyScriptPubKeyMan::WalletLogPrintf(const std::string& fmt, const Params&... parameters) const { return m_wallet.WalletLogPrintf(fmt, parameters...); } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h new file mode 100644 index 0000000000..16a5c9b979 --- /dev/null +++ b/src/wallet/scriptpubkeyman.h @@ -0,0 +1,355 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_WALLET_SCRIPTPUBKEYMAN_H +#define BITCOIN_WALLET_SCRIPTPUBKEYMAN_H + +#include <script/signingprovider.h> +#include <script/standard.h> +#include <wallet/crypter.h> +#include <wallet/ismine.h> +#include <wallet/walletdb.h> +#include <wallet/walletutil.h> + +#include <boost/signals2/signal.hpp> + +enum class OutputType; + +// Wallet storage things that ScriptPubKeyMans need in order to be able to store things to the wallet database. +// It provides access to things that are part of the entire wallet and not specific to a ScriptPubKeyMan such as +// wallet flags, wallet version, encryption keys, encryption status, and the database itself. This allows a +// ScriptPubKeyMan to have callbacks into CWallet without causing a circular dependency. +// WalletStorage should be the same for all ScriptPubKeyMans of a wallet. +class WalletStorage +{ +public: + virtual ~WalletStorage() = default; + virtual const std::string GetDisplayName() const = 0; + virtual WalletDatabase& GetDatabase() = 0; + virtual bool IsWalletFlagSet(uint64_t) const = 0; + virtual void SetWalletFlag(uint64_t) = 0; + virtual void UnsetWalletFlagWithDB(WalletBatch&, uint64_t) = 0; + virtual bool CanSupportFeature(enum WalletFeature) const = 0; + virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr, bool = false) = 0; + virtual bool IsLocked() const = 0; +}; + +//! Default for -keypool +static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; + +/** A key from a CWallet's keypool + * + * The wallet holds one (for pre HD-split wallets) or several keypools. These + * are sets of keys that have not yet been used to provide addresses or receive + * change. + * + * The Bitcoin Core wallet was originally a collection of unrelated private + * keys with their associated addresses. If a non-HD wallet generated a + * key/address, gave that address out and then restored a backup from before + * that key's generation, then any funds sent to that address would be + * lost definitively. + * + * The keypool was implemented to avoid this scenario (commit: 10384941). The + * wallet would generate a set of keys (100 by default). When a new public key + * was required, either to give out as an address or to use in a change output, + * it would be drawn from the keypool. The keypool would then be topped up to + * maintain 100 keys. This ensured that as long as the wallet hadn't used more + * than 100 keys since the previous backup, all funds would be safe, since a + * restored wallet would be able to scan for all owned addresses. + * + * A keypool also allowed encrypted wallets to give out addresses without + * having to be decrypted to generate a new private key. + * + * With the introduction of HD wallets (commit: f1902510), the keypool + * essentially became an address look-ahead pool. Restoring old backups can no + * longer definitively lose funds as long as the addresses used were from the + * wallet's HD seed (since all private keys can be rederived from the seed). + * However, if many addresses were used since the backup, then the wallet may + * not know how far ahead in the HD chain to look for its addresses. The + * keypool is used to implement a 'gap limit'. The keypool maintains a set of + * keys (by default 1000) ahead of the last used key and scans for the + * addresses of those keys. This avoids the risk of not seeing transactions + * involving the wallet's addresses, or of re-using the same address. + * + * The HD-split wallet feature added a second keypool (commit: 02592f4c). There + * is an external keypool (for addresses to hand out) and an internal keypool + * (for change addresses). + * + * Keypool keys are stored in the wallet/keystore's keymap. The keypool data is + * stored as sets of indexes in the wallet (setInternalKeyPool, + * setExternalKeyPool and set_pre_split_keypool), and a map from the key to the + * index (m_pool_key_to_index). The CKeyPool object is used to + * serialize/deserialize the pool data to/from the database. + */ +class CKeyPool +{ +public: + //! The time at which the key was generated. Set in AddKeypoolPubKeyWithDB + int64_t nTime; + //! The public key + CPubKey vchPubKey; + //! Whether this keypool entry is in the internal keypool (for change outputs) + bool fInternal; + //! Whether this key was generated for a keypool before the wallet was upgraded to HD-split + bool m_pre_split; + + CKeyPool(); + CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn); + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + int nVersion = s.GetVersion(); + if (!(s.GetType() & SER_GETHASH)) + READWRITE(nVersion); + READWRITE(nTime); + READWRITE(vchPubKey); + if (ser_action.ForRead()) { + try { + READWRITE(fInternal); + } + catch (std::ios_base::failure&) { + /* flag as external address if we can't read the internal boolean + (this will be the case for any wallet before the HD chain split version) */ + fInternal = false; + } + try { + READWRITE(m_pre_split); + } + catch (std::ios_base::failure&) { + /* flag as postsplit address if we can't read the m_pre_split boolean + (this will be the case for any wallet that upgrades to HD chain split)*/ + m_pre_split = false; + } + } + else { + READWRITE(fInternal); + READWRITE(m_pre_split); + } + } +}; + +/* + * A class implementing ScriptPubKeyMan manages some (or all) scriptPubKeys used in a wallet. + * It contains the scripts and keys related to the scriptPubKeys it manages. + * A ScriptPubKeyMan will be able to give out scriptPubKeys to be used, as well as marking + * when a scriptPubKey has been used. It also handles when and how to store a scriptPubKey + * and its related scripts and keys, including encryption. + */ +class ScriptPubKeyMan +{ +protected: + WalletStorage& m_storage; + +public: + ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {} +}; + +class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider +{ +private: + using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; + using WatchOnlySet = std::set<CScript>; + using WatchKeyMap = std::map<CKeyID, CPubKey>; + + //! will encrypt previously unencrypted keys + bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + + CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); + WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore); + WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore); + + bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey); + + WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr; + + /* the HD chain data model (external chain counters) */ + CHDChain hdChain; + + /* HD derive new child key (on internal or external chain) */ + void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_wallet); + std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet); + std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_wallet); + int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0; + std::map<CKeyID, int64_t> m_pool_key_to_index; + + int64_t nTimeFirstKey GUARDED_BY(cs_wallet) = 0; + + /** + * Private version of AddWatchOnly method which does not accept a + * timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if + * the watch key did not previously have a timestamp associated with it. + * Because this is an inherited virtual method, it is accessible despite + * being marked private, but it is marked private anyway to encourage use + * of the other AddWatchOnly which accepts a timestamp and sets + * nTimeFirstKey more intelligently for more efficient rescans. + */ + bool AddWatchOnly(const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddWatchOnlyInMem(const CScript &dest); + + /** Add a KeyOriginInfo to the wallet */ + bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info); + + //! Adds a key to the store, and saves it to disk. + bool AddKeyPubKeyWithDB(WalletBatch &batch,const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + //! Adds a watch-only address to the store, and saves it to disk. + bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch); + + //! Adds a script to the store and saves it to disk + bool AddCScriptWithDB(WalletBatch& batch, const CScript& script); + + public: + //! Fetches a key from the keypool + bool GetKeyFromPool(CPubKey &key, bool internal = false); + void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + // Map from Key ID to key metadata. + std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_wallet); + + // Map from Script ID to key metadata (for watch-only keys). + std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet); + + /** + * keystore implementation + * Generate a new key + */ + CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Adds a key to the store, and saves it to disk. + bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Adds a key to the store, without saving it to disk (used by LoadWallet) + bool LoadKey(const CKey& key, const CPubKey &pubkey) { return AddKeyPubKeyInner(key, pubkey); } + //! Load metadata (used by LoadWallet) + void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo + void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + //! Adds an encrypted key to the store, and saves it to disk. + bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) + bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + bool GetKey(const CKeyID &address, CKey& keyOut) const override; + bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; + bool HaveKey(const CKeyID &address) const override; + std::set<CKeyID> GetKeys() const override; + bool AddCScript(const CScript& redeemScript) override; + bool LoadCScript(const CScript& redeemScript); + + //! Adds a watch-only address to the store, and saves it to disk. + bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool RemoveWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) + bool LoadWatchOnly(const CScript &dest); + //! Returns whether the watch-only script is in the wallet + bool HaveWatchOnly(const CScript &dest) const; + //! Returns whether there are any watch-only things in the wallet + bool HaveWatchOnly() const; + //! Fetches a pubkey from mapWatchKeys if it exists there + bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const; + + bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + bool NewKeyPool(); + size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool TopUpKeyPool(unsigned int kpSize = 0); + + /** + * Reserves a key from the keypool and sets nIndex to its index + * + * @param[out] nIndex the index of the key in keypool + * @param[out] keypool the keypool the key was drawn from, which could be the + * the pre-split pool if present, or the internal or external pool + * @param fRequestedInternal true if the caller would like the key drawn + * from the internal keypool, false if external is preferred + * + * @return true if succeeded, false if failed due to empty keypool + * @throws std::runtime_error if keypool read failed, key was invalid, + * was not found in the wallet, or was misclassified in the internal + * or external keypool + */ + bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); + void KeepKey(int64_t nIndex); + void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); + int64_t GetOldestKeyPoolTime(); + /** + * Marks all keys in the keypool up to and including reserve_key as used. + */ + void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; } + bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error); + + isminetype IsMine(const CScript& script) const; + + /* Set the HD chain model (chain child index counters) */ + void SetHDChain(const CHDChain& chain, bool memonly); + const CHDChain& GetHDChain() const { return hdChain; } + + /* Returns true if HD is enabled */ + bool IsHDEnabled() const; + + /* Returns true if the wallet can generate new keys */ + bool CanGenerateKeys(); + + /* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */ + bool CanGetAddresses(bool internal = false); + + /* Generates a new HD seed (will not be activated) */ + CPubKey GenerateNewSeed(); + + /* Derives a new HD seed (will not be activated) */ + CPubKey DeriveNewSeed(const CKey& key); + + /* Set the current HD seed (will reset the chain child index counters) + Sets the seed's version based on the current wallet version (so the + caller must ensure the current wallet version is correct before calling + this function). */ + void SetHDSeed(const CPubKey& key); + + /** + * Explicitly make the wallet learn the related scripts for outputs to the + * given key. This is purely to make the wallet file compatible with older + * software, as FillableSigningProvider automatically does this implicitly for all + * keys now. + */ + void LearnRelatedScripts(const CPubKey& key, OutputType); + + /** + * Same as LearnRelatedScripts, but when the OutputType is not known (and could + * be anything). + */ + void LearnAllRelatedScripts(const CPubKey& key); + + /** Implement lookup of key origin information through wallet key metadata. */ + bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + + // Temporary CWallet accessors and aliases. + friend class CWallet; + friend class ReserveDestination; + LegacyScriptPubKeyMan(CWallet& wallet); + bool SetCrypted(); + bool IsCrypted() const; + void NotifyWatchonlyChanged(bool fHaveWatchOnly) const; + void NotifyCanGetAddressesChanged() const; + template<typename... Params> void WalletLogPrintf(const std::string& fmt, const Params&... parameters) const; + CWallet& m_wallet; + CCriticalSection& cs_wallet; + CKeyingMaterial& vMasterKey GUARDED_BY(cs_KeyStore); + std::atomic<bool>& fUseCrypto; + bool& fDecryptionThoroughlyChecked; +}; + +#endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 9e7f0ed773..397e6ea9d3 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_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 <node/context.h> #include <wallet/wallet.h> #include <wallet/coinselection.h> #include <wallet/coincontrol.h> @@ -28,7 +29,8 @@ std::vector<std::unique_ptr<CWalletTx>> wtxn; typedef std::set<CInputCoin> CoinSet; static std::vector<COutput> vCoins; -static auto testChain = interfaces::MakeChain(); +static NodeContext testNode; +static auto testChain = interfaces::MakeChain(testNode); static CWallet testWallet(testChain.get(), WalletLocation(), WalletDatabase::CreateDummy()); static CAmount balance = 0; diff --git a/src/wallet/test/init_test_fixture.h b/src/wallet/test/init_test_fixture.h index e2b7075085..eb4e72c88b 100644 --- a/src/wallet/test/init_test_fixture.h +++ b/src/wallet/test/init_test_fixture.h @@ -6,6 +6,7 @@ #define BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H #include <interfaces/chain.h> +#include <node/context.h> #include <test/setup_common.h> @@ -17,7 +18,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(); + NodeContext m_node; + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node); std::unique_ptr<interfaces::ChainClient> m_chain_client; }; diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp index 062fef7748..24636fd599 100644 --- a/src/wallet/test/ismine_tests.cpp +++ b/src/wallet/test/ismine_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <key.h> +#include <node/context.h> #include <script/script.h> #include <script/standard.h> #include <test/setup_common.h> @@ -26,7 +27,8 @@ BOOST_AUTO_TEST_CASE(ismine_standard) CKey uncompressedKey; uncompressedKey.MakeNewKey(false); CPubKey uncompressedPubkey = uncompressedKey.GetPubKey(); - std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(); + NodeContext node; + std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node); CScript scriptPubKey; isminetype result; @@ -38,12 +40,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) scriptPubKey = GetScriptForRawPubKey(pubkeys[0]); // Keystore does not have key - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has key - BOOST_CHECK(keystore.AddKey(keys[0])); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -54,12 +56,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) scriptPubKey = GetScriptForRawPubKey(uncompressedPubkey); // Keystore does not have key - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has key - BOOST_CHECK(keystore.AddKey(uncompressedKey)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -70,12 +72,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) scriptPubKey = GetScriptForDestination(PKHash(pubkeys[0])); // Keystore does not have key - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has key - BOOST_CHECK(keystore.AddKey(keys[0])); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -86,12 +88,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) scriptPubKey = GetScriptForDestination(PKHash(uncompressedPubkey)); // Keystore does not have key - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has key - BOOST_CHECK(keystore.AddKey(uncompressedKey)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -104,17 +106,17 @@ BOOST_AUTO_TEST_CASE(ismine_standard) scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); // Keystore does not have redeemScript or key - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has redeemScript but no key - BOOST_CHECK(keystore.AddCScript(redeemScript)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has redeemScript and key - BOOST_CHECK(keystore.AddKey(keys[0])); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -127,11 +129,11 @@ BOOST_AUTO_TEST_CASE(ismine_standard) CScript redeemscript = GetScriptForDestination(ScriptHash(redeemscript_inner)); scriptPubKey = GetScriptForDestination(ScriptHash(redeemscript)); - BOOST_CHECK(keystore.AddCScript(redeemscript)); - BOOST_CHECK(keystore.AddCScript(redeemscript_inner)); - BOOST_CHECK(keystore.AddCScript(scriptPubKey)); - BOOST_CHECK(keystore.AddKey(keys[0])); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemscript)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemscript_inner)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -144,11 +146,11 @@ BOOST_AUTO_TEST_CASE(ismine_standard) CScript witnessscript = GetScriptForDestination(ScriptHash(redeemscript)); scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript)); - BOOST_CHECK(keystore.AddCScript(witnessscript)); - BOOST_CHECK(keystore.AddCScript(redeemscript)); - BOOST_CHECK(keystore.AddCScript(scriptPubKey)); - BOOST_CHECK(keystore.AddKey(keys[0])); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemscript)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -160,10 +162,10 @@ BOOST_AUTO_TEST_CASE(ismine_standard) CScript witnessscript = GetScriptForDestination(WitnessV0KeyHash(PKHash(pubkeys[0]))); scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript)); - BOOST_CHECK(keystore.AddCScript(witnessscript)); - BOOST_CHECK(keystore.AddCScript(scriptPubKey)); - BOOST_CHECK(keystore.AddKey(keys[0])); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -176,11 +178,11 @@ BOOST_AUTO_TEST_CASE(ismine_standard) CScript witnessscript = GetScriptForDestination(WitnessV0ScriptHash(witnessscript_inner)); scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript)); - BOOST_CHECK(keystore.AddCScript(witnessscript_inner)); - BOOST_CHECK(keystore.AddCScript(witnessscript)); - BOOST_CHECK(keystore.AddCScript(scriptPubKey)); - BOOST_CHECK(keystore.AddKey(keys[0])); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript_inner)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -188,13 +190,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(PKHash(pubkeys[0]))); // Keystore implicitly has key and P2SH redeemScript - BOOST_CHECK(keystore.AddCScript(scriptPubKey)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -202,17 +204,17 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(uncompressedKey)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(PKHash(uncompressedPubkey))); // Keystore has key, but no P2SH redeemScript - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has key and P2SH redeemScript - BOOST_CHECK(keystore.AddCScript(scriptPubKey)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -224,25 +226,25 @@ BOOST_AUTO_TEST_CASE(ismine_standard) scriptPubKey = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]}); // Keystore does not have any keys - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has 1/2 keys - BOOST_CHECK(keystore.AddKey(uncompressedKey)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has 2/2 keys - BOOST_CHECK(keystore.AddKey(keys[1])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1])); - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has 2/2 keys and the script - BOOST_CHECK(keystore.AddCScript(scriptPubKey)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -250,19 +252,19 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(uncompressedKey)); - BOOST_CHECK(keystore.AddKey(keys[1])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1])); CScript redeemScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]}); scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); // Keystore has no redeemScript - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has redeemScript - BOOST_CHECK(keystore.AddCScript(redeemScript)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -270,24 +272,24 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); - BOOST_CHECK(keystore.AddKey(keys[1])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1])); CScript witnessScript = GetScriptForMultisig(2, {pubkeys[0], pubkeys[1]}); scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); // Keystore has keys, but no witnessScript or P2SH redeemScript - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has keys and witnessScript, but no P2SH redeemScript - BOOST_CHECK(keystore.AddCScript(witnessScript)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has keys, witnessScript, P2SH redeemScript - BOOST_CHECK(keystore.AddCScript(scriptPubKey)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -295,24 +297,24 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(uncompressedKey)); - BOOST_CHECK(keystore.AddKey(keys[1])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1])); CScript witnessScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]}); scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); // Keystore has keys, but no witnessScript or P2SH redeemScript - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has keys and witnessScript, but no P2SH redeemScript - BOOST_CHECK(keystore.AddCScript(witnessScript)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has keys, witnessScript, P2SH redeemScript - BOOST_CHECK(keystore.AddCScript(scriptPubKey)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -326,19 +328,19 @@ BOOST_AUTO_TEST_CASE(ismine_standard) scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); // Keystore has no witnessScript, P2SH redeemScript, or keys - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has witnessScript and P2SH redeemScript, but no keys - BOOST_CHECK(keystore.AddCScript(redeemScript)); - BOOST_CHECK(keystore.AddCScript(witnessScript)); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript)); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript)); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has keys, witnessScript, P2SH redeemScript - BOOST_CHECK(keystore.AddKey(keys[0])); - BOOST_CHECK(keystore.AddKey(keys[1])); - result = IsMine(keystore, scriptPubKey); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1])); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -346,12 +348,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); scriptPubKey.clear(); scriptPubKey << OP_RETURN << ToByteVector(pubkeys[0]); - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -359,12 +361,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); scriptPubKey.clear(); scriptPubKey << OP_0 << ToByteVector(ParseHex("aabb")); - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -372,12 +374,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); scriptPubKey.clear(); scriptPubKey << OP_16 << ToByteVector(ParseHex("aabb")); - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -385,12 +387,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); scriptPubKey.clear(); scriptPubKey << OP_9 << OP_ADD << OP_11 << OP_EQUAL; - result = IsMine(keystore, scriptPubKey); + result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } } diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index 0400f1207c..27a64ff12f 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -16,6 +16,7 @@ BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup) BOOST_AUTO_TEST_CASE(psbt_updater_test) { + auto spk_man = m_wallet.GetLegacyScriptPubKeyMan(); LOCK(m_wallet.cs_wallet); // Create prevtxs and add to wallet @@ -35,23 +36,23 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) CScript rs1; CDataStream s_rs1(ParseHex("475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae"), SER_NETWORK, PROTOCOL_VERSION); s_rs1 >> rs1; - m_wallet.AddCScript(rs1); + spk_man->AddCScript(rs1); CScript rs2; CDataStream s_rs2(ParseHex("2200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903"), SER_NETWORK, PROTOCOL_VERSION); s_rs2 >> rs2; - m_wallet.AddCScript(rs2); + spk_man->AddCScript(rs2); CScript ws1; CDataStream s_ws1(ParseHex("47522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae"), SER_NETWORK, PROTOCOL_VERSION); s_ws1 >> ws1; - m_wallet.AddCScript(ws1); + spk_man->AddCScript(ws1); // Add hd seed CKey key = DecodeSecret("5KSSJQ7UNfFGwVgpCZDSHm5rVNhMFcFtvWM3zQ8mW4qNDEN7LFd"); // Mainnet and uncompressed form of cUkG8i1RFfWGWy5ziR11zJ5V4U4W3viSFCfyJmZnvQaUsd1xuF3T - CPubKey master_pub_key = m_wallet.DeriveNewSeed(key); - m_wallet.SetHDSeed(master_pub_key); - m_wallet.NewKeyPool(); + CPubKey master_pub_key = spk_man->DeriveNewSeed(key); + spk_man->SetHDSeed(master_pub_key); + spk_man->NewKeyPool(); // Call FillPSBT PartiallySignedTransaction psbtx; diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index c1dbecdf8c..def6f1934e 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -9,6 +9,7 @@ #include <interfaces/chain.h> #include <interfaces/wallet.h> +#include <node/context.h> #include <wallet/wallet.h> #include <memory> @@ -18,7 +19,8 @@ struct WalletTestingSetup: public TestingSetup { explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); - std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); + NodeContext m_node; + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node); std::unique_ptr<interfaces::ChainClient> m_chain_client = interfaces::MakeWalletClient(*m_chain, {}); CWallet m_wallet; }; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index a2b2a7b227..72e1b4e83b 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -9,6 +9,7 @@ #include <vector> #include <interfaces/chain.h> +#include <node/context.h> #include <policy/policy.h> #include <rpc/server.h> #include <test/setup_common.h> @@ -27,8 +28,10 @@ BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) static void AddKey(CWallet& wallet, const CKey& key) { + auto spk_man = wallet.GetLegacyScriptPubKeyMan(); LOCK(wallet.cs_wallet); - wallet.AddKeyPubKey(key, key.GetPubKey()); + AssertLockHeld(spk_man->cs_wallet); + spk_man->AddKeyPubKey(key, key.GetPubKey()); } BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) @@ -39,7 +42,8 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = ::ChainActive().Tip(); - auto chain = interfaces::MakeChain(); + NodeContext node; + auto chain = interfaces::MakeChain(node); auto locked_chain = chain->lock(); LockAssertion lock(::cs_main); @@ -118,7 +122,8 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = ::ChainActive().Tip(); - auto chain = interfaces::MakeChain(); + NodeContext node; + auto chain = interfaces::MakeChain(node); auto locked_chain = chain->lock(); LockAssertion lock(::cs_main); @@ -185,7 +190,8 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) SetMockTime(KEY_TIME); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); - auto chain = interfaces::MakeChain(); + NodeContext node; + auto chain = interfaces::MakeChain(node); auto locked_chain = chain->lock(); LockAssertion lock(::cs_main); @@ -194,9 +200,11 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // Import key into wallet and call dumpwallet to create backup file. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); + auto spk_man = wallet->GetLegacyScriptPubKeyMan(); LOCK(wallet->cs_wallet); - wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; - wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + AssertLockHeld(spk_man->cs_wallet); + spk_man->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; + spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); JSONRPCRequest request; request.params.setArray(); @@ -239,14 +247,17 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // debit functions. BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { - auto chain = interfaces::MakeChain(); + NodeContext node; + auto chain = interfaces::MakeChain(node); CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); + auto spk_man = wallet.GetLegacyScriptPubKeyMan(); CWalletTx wtx(&wallet, m_coinbase_txns.back()); auto locked_chain = chain->lock(); LockAssertion lock(::cs_main); LOCK(wallet.cs_wallet); + AssertLockHeld(spk_man->cs_wallet); wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0); @@ -254,10 +265,10 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) // cache the current immature credit amount, which is 0. BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 0); - // Invalidate the cached value, add the key, and make sure a new immature + // Invalidate the cached vanue, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); - wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey())); BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50*COIN); } @@ -337,37 +348,38 @@ BOOST_AUTO_TEST_CASE(LoadReceiveRequests) BOOST_CHECK_EQUAL(values[1], "val_rr1"); } -// Test some watch-only wallet methods by the procedure of loading (LoadWatchOnly), +// Test some watch-only LegacyScriptPubKeyMan methods by the procedure of loading (LoadWatchOnly), // checking (HaveWatchOnly), getting (GetWatchPubKey) and removing (RemoveWatchOnly) a // given PubKey, resp. its corresponding P2PK Script. Results of the the impact on // the address -> PubKey map is dependent on whether the PubKey is a point on the curve -static void TestWatchOnlyPubKey(CWallet& wallet, const CPubKey& add_pubkey) +static void TestWatchOnlyPubKey(LegacyScriptPubKeyMan* spk_man, const CPubKey& add_pubkey) { CScript p2pk = GetScriptForRawPubKey(add_pubkey); CKeyID add_address = add_pubkey.GetID(); CPubKey found_pubkey; - LOCK(wallet.cs_wallet); + LOCK(spk_man->cs_wallet); // all Scripts (i.e. also all PubKeys) are added to the general watch-only set - BOOST_CHECK(!wallet.HaveWatchOnly(p2pk)); - wallet.LoadWatchOnly(p2pk); - BOOST_CHECK(wallet.HaveWatchOnly(p2pk)); + BOOST_CHECK(!spk_man->HaveWatchOnly(p2pk)); + spk_man->LoadWatchOnly(p2pk); + BOOST_CHECK(spk_man->HaveWatchOnly(p2pk)); // only PubKeys on the curve shall be added to the watch-only address -> PubKey map bool is_pubkey_fully_valid = add_pubkey.IsFullyValid(); if (is_pubkey_fully_valid) { - BOOST_CHECK(wallet.GetWatchPubKey(add_address, found_pubkey)); + BOOST_CHECK(spk_man->GetWatchPubKey(add_address, found_pubkey)); BOOST_CHECK(found_pubkey == add_pubkey); } else { - BOOST_CHECK(!wallet.GetWatchPubKey(add_address, found_pubkey)); + BOOST_CHECK(!spk_man->GetWatchPubKey(add_address, found_pubkey)); BOOST_CHECK(found_pubkey == CPubKey()); // passed key is unchanged } - wallet.RemoveWatchOnly(p2pk); - BOOST_CHECK(!wallet.HaveWatchOnly(p2pk)); + AssertLockHeld(spk_man->cs_wallet); + spk_man->RemoveWatchOnly(p2pk); + BOOST_CHECK(!spk_man->HaveWatchOnly(p2pk)); if (is_pubkey_fully_valid) { - BOOST_CHECK(!wallet.GetWatchPubKey(add_address, found_pubkey)); + BOOST_CHECK(!spk_man->GetWatchPubKey(add_address, found_pubkey)); BOOST_CHECK(found_pubkey == add_pubkey); // passed key is unchanged } } @@ -382,37 +394,38 @@ static void PollutePubKey(CPubKey& pubkey) assert(pubkey.IsValid()); } -// Test watch-only wallet logic for PubKeys +// Test watch-only logic for PubKeys BOOST_AUTO_TEST_CASE(WatchOnlyPubKeys) { CKey key; CPubKey pubkey; + LegacyScriptPubKeyMan* spk_man = m_wallet.GetLegacyScriptPubKeyMan(); - BOOST_CHECK(!m_wallet.HaveWatchOnly()); + BOOST_CHECK(!spk_man->HaveWatchOnly()); // uncompressed valid PubKey key.MakeNewKey(false); pubkey = key.GetPubKey(); assert(!pubkey.IsCompressed()); - TestWatchOnlyPubKey(m_wallet, pubkey); + TestWatchOnlyPubKey(spk_man, pubkey); // uncompressed cryptographically invalid PubKey PollutePubKey(pubkey); - TestWatchOnlyPubKey(m_wallet, pubkey); + TestWatchOnlyPubKey(spk_man, pubkey); // compressed valid PubKey key.MakeNewKey(true); pubkey = key.GetPubKey(); assert(pubkey.IsCompressed()); - TestWatchOnlyPubKey(m_wallet, pubkey); + TestWatchOnlyPubKey(spk_man, pubkey); // compressed cryptographically invalid PubKey PollutePubKey(pubkey); - TestWatchOnlyPubKey(m_wallet, pubkey); + TestWatchOnlyPubKey(spk_man, pubkey); // invalid empty PubKey pubkey = CPubKey(); - TestWatchOnlyPubKey(m_wallet, pubkey); + TestWatchOnlyPubKey(spk_man, pubkey); } class ListCoinsTestingSetup : public TestChain100Setup @@ -466,7 +479,8 @@ public: return it->second; } - std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); + NodeContext m_node; + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node); std::unique_ptr<CWallet> wallet; }; @@ -538,7 +552,8 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { - auto chain = interfaces::MakeChain(); + NodeContext node; + auto chain = interfaces::MakeChain(node); std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 159d4f78c6..069ae57878 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -210,9 +210,9 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& } // Set a seed for the wallet - CPubKey master_pub_key = wallet->GenerateNewSeed(); - wallet->SetHDSeed(master_pub_key); - wallet->NewKeyPool(); + CPubKey master_pub_key = wallet->m_spk_man->GenerateNewSeed(); + wallet->m_spk_man->SetHDSeed(master_pub_key); + wallet->m_spk_man->NewKeyPool(); // Relock the wallet wallet->Lock(); @@ -224,8 +224,6 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& return WalletCreationStatus::SUCCESS; } -const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; - const uint256 CWalletTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); /** @defgroup mapWallet @@ -238,17 +236,7 @@ std::string COutput::ToString() const return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); } -std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider) -{ - std::vector<CScript> dummy; - FlatSigningProvider out; - InferDescriptor(spk, provider)->Expand(0, DUMMY_SIGNING_PROVIDER, dummy, out); - std::vector<CKeyID> ret; - for (const auto& entry : out.pubkeys) { - ret.push_back(entry.first); - } - return ret; -} +std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider); const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const { @@ -259,354 +247,12 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const return &(it->second); } -CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) -{ - assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); - assert(!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)); - AssertLockHeld(cs_wallet); - bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets - - CKey secret; - - // Create new metadata - int64_t nCreationTime = GetTime(); - CKeyMetadata metadata(nCreationTime); - - // use HD key derivation if HD was enabled during wallet creation and a seed is present - if (IsHDEnabled()) { - DeriveNewChildKey(batch, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); - } else { - secret.MakeNewKey(fCompressed); - } - - // Compressed public keys were introduced in version 0.6.0 - if (fCompressed) { - SetMinVersion(FEATURE_COMPRPUBKEY); - } - - CPubKey pubkey = secret.GetPubKey(); - assert(secret.VerifyPubKey(pubkey)); - - mapKeyMetadata[pubkey.GetID()] = metadata; - UpdateTimeFirstKey(nCreationTime); - - if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) { - throw std::runtime_error(std::string(__func__) + ": AddKey failed"); - } - return pubkey; -} - -void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal) -{ - // for now we use a fixed keypath scheme of m/0'/0'/k - CKey seed; //seed (256bit) - CExtKey masterKey; //hd master key - CExtKey accountKey; //key at m/0' - CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal) - CExtKey childKey; //key at m/0'/0'/<n>' - - // try to get the seed - if (!GetKey(hdChain.seed_id, seed)) - throw std::runtime_error(std::string(__func__) + ": seed not found"); - - masterKey.SetSeed(seed.begin(), seed.size()); - - // derive m/0' - // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) - masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT); - - // derive m/0'/0' (external chain) OR m/0'/1' (internal chain) - assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true); - accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0)); - - // derive child key at next index, skip keys already known to the wallet - do { - // always derive hardened keys - // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range - // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 - if (internal) { - chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); - metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'"; - metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); - metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT); - metadata.key_origin.path.push_back(hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); - hdChain.nInternalChainCounter++; - } - else { - chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); - metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'"; - metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); - metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); - metadata.key_origin.path.push_back(hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); - hdChain.nExternalChainCounter++; - } - } while (HaveKey(childKey.key.GetPubKey().GetID())); - secret = childKey.key; - metadata.hd_seed_id = hdChain.seed_id; - CKeyID master_id = masterKey.key.GetPubKey().GetID(); - std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint); - metadata.has_key_origin = true; - // update the chain model in the database - if (!batch.WriteHDChain(hdChain)) - throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); -} - -bool CWallet::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const CPubKey& pubkey) -{ - AssertLockHeld(cs_wallet); - - // Make sure we aren't adding private keys to private key disabled wallets - assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); - - // FillableSigningProvider has no concept of wallet databases, but calls AddCryptedKey - // which is overridden below. To avoid flushes, the database handle is - // tunneled through to it. - bool needsDB = !encrypted_batch; - if (needsDB) { - encrypted_batch = &batch; - } - if (!AddKeyPubKeyInner(secret, pubkey)) { - if (needsDB) encrypted_batch = nullptr; - return false; - } - if (needsDB) encrypted_batch = nullptr; - - // check if we need to remove from watch-only - CScript script; - script = GetScriptForDestination(PKHash(pubkey)); - if (HaveWatchOnly(script)) { - RemoveWatchOnly(script); - } - script = GetScriptForRawPubKey(pubkey); - if (HaveWatchOnly(script)) { - RemoveWatchOnly(script); - } - - if (!IsCrypted()) { - return batch.WriteKey(pubkey, - secret.GetPrivKey(), - mapKeyMetadata[pubkey.GetID()]); - } - UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); - return true; -} - -bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) -{ - WalletBatch batch(*database); - return CWallet::AddKeyPubKeyWithDB(batch, secret, pubkey); -} - -bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, - const std::vector<unsigned char> &vchCryptedSecret) -{ - if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret)) - return false; - { - LOCK(cs_wallet); - if (encrypted_batch) - return encrypted_batch->WriteCryptedKey(vchPubKey, - vchCryptedSecret, - mapKeyMetadata[vchPubKey.GetID()]); - else - return WalletBatch(*database).WriteCryptedKey(vchPubKey, - vchCryptedSecret, - mapKeyMetadata[vchPubKey.GetID()]); - } -} - -void CWallet::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta) -{ - AssertLockHeld(cs_wallet); - UpdateTimeFirstKey(meta.nCreateTime); - mapKeyMetadata[keyID] = meta; -} - -void CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta) -{ - AssertLockHeld(cs_wallet); - UpdateTimeFirstKey(meta.nCreateTime); - m_script_metadata[script_id] = meta; -} - void CWallet::UpgradeKeyMetadata() { - AssertLockHeld(cs_wallet); - if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) { - return; - } - - std::unique_ptr<WalletBatch> batch = MakeUnique<WalletBatch>(*database); - for (auto& meta_pair : mapKeyMetadata) { - CKeyMetadata& meta = meta_pair.second; - if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin && meta.hdKeypath != "s") { // If the hdKeypath is "s", that's the seed and it doesn't have a key origin - CKey key; - GetKey(meta.hd_seed_id, key); - CExtKey masterKey; - masterKey.SetSeed(key.begin(), key.size()); - // Add to map - CKeyID master_id = masterKey.key.GetPubKey().GetID(); - std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint); - if (!ParseHDKeypath(meta.hdKeypath, meta.key_origin.path)) { - throw std::runtime_error("Invalid stored hdKeypath"); - } - meta.has_key_origin = true; - if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) { - meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN; - } - - // Write meta to wallet - CPubKey pubkey; - if (GetPubKey(meta_pair.first, pubkey)) { - batch->WriteKeyMetadata(meta, pubkey, true); - } - } - } - batch.reset(); //write before setting the flag - SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA); -} - -bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) -{ - return AddCryptedKeyInner(vchPubKey, vchCryptedSecret); -} - -/** - * Update wallet first key creation time. This should be called whenever keys - * are added to the wallet, with the oldest key creation time. - */ -void CWallet::UpdateTimeFirstKey(int64_t nCreateTime) -{ - AssertLockHeld(cs_wallet); - if (nCreateTime <= 1) { - // Cannot determine birthday information, so set the wallet birthday to - // the beginning of time. - nTimeFirstKey = 1; - } else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) { - nTimeFirstKey = nCreateTime; - } -} - -bool CWallet::AddCScript(const CScript& redeemScript) -{ - WalletBatch batch(*database); - return AddCScriptWithDB(batch, redeemScript); -} - -bool CWallet::AddCScriptWithDB(WalletBatch& batch, const CScript& redeemScript) -{ - if (!FillableSigningProvider::AddCScript(redeemScript)) - return false; - if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) { - UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); - return true; - } - return false; -} - -bool CWallet::LoadCScript(const CScript& redeemScript) -{ - /* A sanity check was added in pull #3843 to avoid adding redeemScripts - * that never can be redeemed. However, old wallets may still contain - * these. Do not add them to the wallet and warn. */ - if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) - { - std::string strAddr = EncodeDestination(ScriptHash(redeemScript)); - WalletLogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); - return true; - } - - return FillableSigningProvider::AddCScript(redeemScript); -} - -static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) -{ - std::vector<std::vector<unsigned char>> solutions; - return Solver(dest, solutions) == TX_PUBKEY && - (pubKeyOut = CPubKey(solutions[0])).IsFullyValid(); -} - -bool CWallet::AddWatchOnlyInMem(const CScript &dest) -{ - LOCK(cs_KeyStore); - setWatchOnly.insert(dest); - CPubKey pubKey; - if (ExtractPubKey(dest, pubKey)) { - mapWatchKeys[pubKey.GetID()] = pubKey; - ImplicitlyLearnRelatedKeyScripts(pubKey); - } - return true; -} - -bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) -{ - if (!AddWatchOnlyInMem(dest)) - return false; - const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)]; - UpdateTimeFirstKey(meta.nCreateTime); - NotifyWatchonlyChanged(true); - if (batch.WriteWatchOnly(dest, meta)) { - UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); - return true; - } - return false; -} - -bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) -{ - m_script_metadata[CScriptID(dest)].nCreateTime = create_time; - return AddWatchOnlyWithDB(batch, dest); -} - -bool CWallet::AddWatchOnly(const CScript& dest) -{ - WalletBatch batch(*database); - return AddWatchOnlyWithDB(batch, dest); -} - -bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime) -{ - m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime; - return AddWatchOnly(dest); -} - -bool CWallet::RemoveWatchOnly(const CScript &dest) -{ - AssertLockHeld(cs_wallet); - { - LOCK(cs_KeyStore); - setWatchOnly.erase(dest); - CPubKey pubKey; - if (ExtractPubKey(dest, pubKey)) { - mapWatchKeys.erase(pubKey.GetID()); - } - // Related CScripts are not removed; having superfluous scripts around is - // harmless (see comment in ImplicitlyLearnRelatedKeyScripts). + if (m_spk_man) { + AssertLockHeld(m_spk_man->cs_wallet); + m_spk_man->UpgradeKeyMetadata(); } - - if (!HaveWatchOnly()) - NotifyWatchonlyChanged(false); - if (!WalletBatch(*database).EraseWatchOnly(dest)) - return false; - - return true; -} - -bool CWallet::LoadWatchOnly(const CScript &dest) -{ - return AddWatchOnlyInMem(dest); -} - -bool CWallet::HaveWatchOnly(const CScript &dest) const -{ - LOCK(cs_KeyStore); - return setWatchOnly.count(dest) > 0; -} - -bool CWallet::HaveWatchOnly() const -{ - LOCK(cs_KeyStore); - return (!setWatchOnly.empty()); } bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys) @@ -887,14 +533,15 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) } encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey); - if (!EncryptKeys(_vMasterKey)) - { - encrypted_batch->TxnAbort(); - delete encrypted_batch; - encrypted_batch = nullptr; - // We now probably have half of our keys encrypted in memory, and half not... - // die and let the user reload the unencrypted wallet. - assert(false); + if (auto spk_man = m_spk_man.get()) { + if (!spk_man->EncryptKeys(_vMasterKey)) { + encrypted_batch->TxnAbort(); + delete encrypted_batch; + encrypted_batch = nullptr; + // We now probably have half of our keys encrypted in memory, and half not... + // die and let the user reload the unencrypted wallet. + assert(false); + } } // Encryption was introduced in version 0.4.0 @@ -915,11 +562,11 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) Unlock(strWalletPassphrase); // if we are using HD, replace the HD seed with a new one - if (IsHDEnabled()) { - SetHDSeed(GenerateNewSeed()); + if (m_spk_man->IsHDEnabled()) { + m_spk_man->SetHDSeed(m_spk_man->GenerateNewSeed()); } - NewKeyPool(); + m_spk_man->NewKeyPool(); Lock(); // Need to completely rewrite the wallet file; if we don't, bdb might keep @@ -1051,7 +698,7 @@ void CWallet::SetUsedDestinationState(const uint256& hash, unsigned int n, bool CTxDestination dst; if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) { - if (::IsMine(*this, dst)) { + if (IsMine(dst)) { LOCK(cs_wallet); if (used && !GetDestData(dst, "used", nullptr)) { AddDestData(dst, "used", "p"); // p for "present", opposite of absent (null) @@ -1065,7 +712,7 @@ void CWallet::SetUsedDestinationState(const uint256& hash, unsigned int n, bool bool CWallet::IsUsedDestination(const CTxDestination& dst) const { LOCK(cs_wallet); - return ::IsMine(*this, dst) && GetDestData(dst, "used", nullptr); + return IsMine(dst) && GetDestData(dst, "used", nullptr); } bool CWallet::IsUsedDestination(const uint256& hash, unsigned int n) const @@ -1225,13 +872,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::St // loop though all outputs for (const CTxOut& txout: tx.vout) { // extract addresses and check if they match with an unused keypool key - for (const auto& keyid : GetAffectedKeys(txout.scriptPubKey, *this)) { - std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid); - if (mi != m_pool_key_to_index.end()) { + for (const auto& keyid : GetAffectedKeys(txout.scriptPubKey, *m_spk_man)) { + std::map<CKeyID, int64_t>::const_iterator mi = m_spk_man->m_pool_key_to_index.find(keyid); + if (mi != m_spk_man->m_pool_key_to_index.end()) { WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__); MarkReserveKeysAsUsed(mi->second); - if (!TopUpKeyPool()) { + if (!m_spk_man->TopUpKeyPool()) { WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); } } @@ -1487,7 +1134,21 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const isminetype CWallet::IsMine(const CTxOut& txout) const { - return ::IsMine(*this, txout.scriptPubKey); + return IsMine(txout.scriptPubKey); +} + +isminetype CWallet::IsMine(const CTxDestination& dest) const +{ + return IsMine(GetScriptForDestination(dest)); +} + +isminetype CWallet::IsMine(const CScript& script) const +{ + isminetype result = ISMINE_NO; + if (auto spk_man = m_spk_man.get()) { + result = spk_man->IsMine(script); + } + return result; } CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const @@ -1511,7 +1172,7 @@ bool CWallet::IsChange(const CScript& script) const // a better way of identifying which outputs are 'the send' and which are // 'the change' will need to be implemented (maybe extend CWalletTx to remember // which output, if any, was change). - if (::IsMine(*this, script)) + if (IsMine(script)) { CTxDestination address; if (!ExtractDestination(script, address)) @@ -1601,92 +1262,24 @@ CAmount CWallet::GetChange(const CTransaction& tx) const return nChange; } -CPubKey CWallet::GenerateNewSeed() -{ - assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); - CKey key; - key.MakeNewKey(true); - return DeriveNewSeed(key); -} - -CPubKey CWallet::DeriveNewSeed(const CKey& key) -{ - int64_t nCreationTime = GetTime(); - CKeyMetadata metadata(nCreationTime); - - // calculate the seed - CPubKey seed = key.GetPubKey(); - assert(key.VerifyPubKey(seed)); - - // set the hd keypath to "s" -> Seed, refers the seed to itself - metadata.hdKeypath = "s"; - metadata.has_key_origin = false; - metadata.hd_seed_id = seed.GetID(); - - { - LOCK(cs_wallet); - - // mem store the metadata - mapKeyMetadata[seed.GetID()] = metadata; - - // write the key&metadata to the database - if (!AddKeyPubKey(key, seed)) - throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); - } - - return seed; -} - -void CWallet::SetHDSeed(const CPubKey& seed) -{ - LOCK(cs_wallet); - // store the keyid (hash160) together with - // the child index counter in the database - // as a hdchain object - CHDChain newHdChain; - newHdChain.nVersion = CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; - newHdChain.seed_id = seed.GetID(); - SetHDChain(newHdChain, false); - NotifyCanGetAddressesChanged(); - UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); -} - -void CWallet::SetHDChain(const CHDChain& chain, bool memonly) -{ - LOCK(cs_wallet); - if (!memonly && !WalletBatch(*database).WriteHDChain(chain)) - throw std::runtime_error(std::string(__func__) + ": writing chain failed"); - - hdChain = chain; -} - bool CWallet::IsHDEnabled() const { - return !hdChain.seed_id.IsNull(); -} - -bool CWallet::CanGenerateKeys() -{ - // A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a non-HD wallet (pre FEATURE_HD) - LOCK(cs_wallet); - return IsHDEnabled() || !CanSupportFeature(FEATURE_HD); + bool result = true; + if (auto spk_man = m_spk_man.get()) { + result &= spk_man->IsHDEnabled(); + } + return result; } bool CWallet::CanGetAddresses(bool internal) { - LOCK(cs_wallet); - // Check if the keypool has keys - bool keypool_has_keys; - if (internal && CanSupportFeature(FEATURE_HD_SPLIT)) { - keypool_has_keys = setInternalKeyPool.size() > 0; - } else { - keypool_has_keys = KeypoolCountExternalKeys() > 0; - } - // If the keypool doesn't have keys, check if we can generate them - if (!keypool_has_keys) { - return CanGenerateKeys(); + { + auto spk_man = m_spk_man.get(); + if (spk_man && spk_man->CanGetAddresses(internal)) { + return true; + } } - return keypool_has_keys; + return false; } void CWallet::SetWalletFlag(uint64_t flags) @@ -1745,7 +1338,9 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig const CScript& scriptPubKey = txout.scriptPubKey; SignatureData sigdata; - if (!ProduceSignature(*this, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { + const SigningProvider* provider = GetSigningProvider(); + + if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { return false; } UpdateInput(tx_in, sigdata); @@ -1770,97 +1365,43 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> bool CWallet::ImportScripts(const std::set<CScript> scripts, int64_t timestamp) { - WalletBatch batch(*database); - for (const auto& entry : scripts) { - CScriptID id(entry); - if (HaveCScript(id)) { - WalletLogPrintf("Already have script %s, skipping\n", HexStr(entry)); - continue; - } - if (!AddCScriptWithDB(batch, entry)) { - return false; - } - - if (timestamp > 0) { - m_script_metadata[CScriptID(entry)].nCreateTime = timestamp; - } - } - if (timestamp > 0) { - UpdateTimeFirstKey(timestamp); + auto spk_man = GetLegacyScriptPubKeyMan(); + if (!spk_man) { + return false; } - - return true; + AssertLockHeld(spk_man->cs_wallet); + return spk_man->ImportScripts(scripts, timestamp); } bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) { - WalletBatch batch(*database); - for (const auto& entry : privkey_map) { - const CKey& key = entry.second; - CPubKey pubkey = key.GetPubKey(); - const CKeyID& id = entry.first; - assert(key.VerifyPubKey(pubkey)); - // Skip if we already have the key - if (HaveKey(id)) { - WalletLogPrintf("Already have key with pubkey %s, skipping\n", HexStr(pubkey)); - continue; - } - mapKeyMetadata[id].nCreateTime = timestamp; - // If the private key is not present in the wallet, insert it. - if (!AddKeyPubKeyWithDB(batch, key, pubkey)) { - return false; - } - UpdateTimeFirstKey(timestamp); + auto spk_man = GetLegacyScriptPubKeyMan(); + if (!spk_man) { + return false; } - return true; + AssertLockHeld(spk_man->cs_wallet); + return spk_man->ImportPrivKeys(privkey_map, timestamp); } bool CWallet::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) { - WalletBatch batch(*database); - for (const auto& entry : key_origins) { - AddKeyOriginWithDB(batch, entry.second.first, entry.second.second); - } - for (const CKeyID& id : ordered_pubkeys) { - auto entry = pubkey_map.find(id); - if (entry == pubkey_map.end()) { - continue; - } - const CPubKey& pubkey = entry->second; - CPubKey temp; - if (GetPubKey(id, temp)) { - // Already have pubkey, skipping - WalletLogPrintf("Already have pubkey %s, skipping\n", HexStr(temp)); - continue; - } - if (!AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) { - return false; - } - mapKeyMetadata[id].nCreateTime = timestamp; - - // Add to keypool only works with pubkeys - if (add_keypool) { - AddKeypoolPubkeyWithDB(pubkey, internal, batch); - NotifyCanGetAddressesChanged(); - } + auto spk_man = GetLegacyScriptPubKeyMan(); + if (!spk_man) { + return false; } - return true; + AssertLockHeld(spk_man->cs_wallet); + return spk_man->ImportPubKeys(ordered_pubkeys, pubkey_map, key_origins, add_keypool, internal, timestamp); } bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) { - WalletBatch batch(*database); - for (const CScript& script : script_pub_keys) { - if (!have_solving_data || !::IsMine(*this, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated - if (!AddWatchOnlyWithDB(batch, script, timestamp)) { - return false; - } - } - CTxDestination dest; - ExtractDestination(script, dest); - if (apply_label && IsValidDestination(dest)) { - SetAddressBookWithDB(batch, dest, label, "receive"); - } + auto spk_man = GetLegacyScriptPubKeyMan(); + if (!spk_man) { + return false; + } + AssertLockHeld(spk_man->cs_wallet); + if (!spk_man->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, apply_label, timestamp)) { + return false; } return true; } @@ -2541,7 +2082,9 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< continue; } - bool solvable = IsSolvable(*this, wtx.tx->vout[i].scriptPubKey); + const SigningProvider* provider = GetSigningProvider(); + + bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); @@ -2775,7 +2318,13 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey; const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue; SignatureData sigdata; - if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&tx, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) { + + const SigningProvider* provider = GetSigningProvider(); + if (!provider) { + return false; + } + + if (!ProduceSignature(*provider, MutableTransactionSignatureCreator(&tx, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) { return false; } UpdateInput(input, sigdata); @@ -3233,7 +2782,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std const CScript& scriptPubKey = coin.txout.scriptPubKey; SignatureData sigdata; - if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) + const SigningProvider* provider = GetSigningProvider(); + if (!provider || !ProduceSignature(*provider, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) { strFailReason = _("Signing transaction failed").translated; return false; @@ -3341,7 +2891,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { setInternalKeyPool.clear(); setExternalKeyPool.clear(); - m_pool_key_to_index.clear(); + m_spk_man->m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -3378,7 +2928,7 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256 { setInternalKeyPool.clear(); setExternalKeyPool.clear(); - m_pool_key_to_index.clear(); + m_spk_man->m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -3403,7 +2953,7 @@ DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx) LOCK(cs_wallet); setInternalKeyPool.clear(); setExternalKeyPool.clear(); - m_pool_key_to_index.clear(); + m_spk_man->m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -3427,7 +2977,7 @@ bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& add if (!strPurpose.empty()) /* update purpose only if requested */ mapAddressBook[address].purpose = strPurpose; } - NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO, + NotifyAddressBookChanged(this, address, strName, IsMine(address) != ISMINE_NO, strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) ); if (!strPurpose.empty() && !batch.WritePurpose(EncodeDestination(address), strPurpose)) return false; @@ -3454,258 +3004,50 @@ bool CWallet::DelAddressBook(const CTxDestination& address) mapAddressBook.erase(address); } - NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address) != ISMINE_NO, "", CT_DELETED); + NotifyAddressBookChanged(this, address, "", IsMine(address) != ISMINE_NO, "", CT_DELETED); WalletBatch(*database).ErasePurpose(EncodeDestination(address)); return WalletBatch(*database).EraseName(EncodeDestination(address)); } -/** - * Mark old keypool keys as used, - * and generate all new keys - */ -bool CWallet::NewKeyPool() -{ - if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - return false; - } - { - LOCK(cs_wallet); - WalletBatch batch(*database); - - for (const int64_t nIndex : setInternalKeyPool) { - batch.ErasePool(nIndex); - } - setInternalKeyPool.clear(); - - for (const int64_t nIndex : setExternalKeyPool) { - batch.ErasePool(nIndex); - } - setExternalKeyPool.clear(); - - for (const int64_t nIndex : set_pre_split_keypool) { - batch.ErasePool(nIndex); - } - set_pre_split_keypool.clear(); - - m_pool_key_to_index.clear(); - - if (!TopUpKeyPool()) { - return false; - } - WalletLogPrintf("CWallet::NewKeyPool rewrote keypool\n"); - } - return true; -} - size_t CWallet::KeypoolCountExternalKeys() { AssertLockHeld(cs_wallet); - return setExternalKeyPool.size() + set_pre_split_keypool.size(); -} -void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) -{ - AssertLockHeld(cs_wallet); - if (keypool.m_pre_split) { - set_pre_split_keypool.insert(nIndex); - } else if (keypool.fInternal) { - setInternalKeyPool.insert(nIndex); - } else { - setExternalKeyPool.insert(nIndex); + unsigned int count = 0; + if (auto spk_man = m_spk_man.get()) { + AssertLockHeld(spk_man->cs_wallet); + count += spk_man->KeypoolCountExternalKeys(); } - m_max_keypool_index = std::max(m_max_keypool_index, nIndex); - m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex; - // If no metadata exists yet, create a default with the pool key's - // creation time. Note that this may be overwritten by actually - // stored metadata for that key later, which is fine. - CKeyID keyid = keypool.vchPubKey.GetID(); - if (mapKeyMetadata.count(keyid) == 0) - mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); + return count; } bool CWallet::TopUpKeyPool(unsigned int kpSize) { - if (!CanGenerateKeys()) { - return false; - } - { - LOCK(cs_wallet); - - if (IsLocked()) return false; - - // Top up key pool - unsigned int nTargetSize; - if (kpSize > 0) - nTargetSize = kpSize; - else - nTargetSize = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0); - - // count amount of available keys (internal, external) - // make sure the keypool of external and internal keys fits the user selected target (-keypool) - int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0); - int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0); - - if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) - { - // don't create extra internal keys - missingInternal = 0; - } - bool internal = false; - WalletBatch batch(*database); - for (int64_t i = missingInternal + missingExternal; i--;) - { - if (i < missingInternal) { - internal = true; - } - - CPubKey pubkey(GenerateNewKey(batch, internal)); - AddKeypoolPubkeyWithDB(pubkey, internal, batch); - } - if (missingInternal + missingExternal > 0) { - WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size()); - } - } - NotifyCanGetAddressesChanged(); - return true; -} - -void CWallet::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch) -{ - LOCK(cs_wallet); - assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys? - int64_t index = ++m_max_keypool_index; - if (!batch.WritePool(index, CKeyPool(pubkey, internal))) { - throw std::runtime_error(std::string(__func__) + ": writing imported pubkey failed"); - } - if (internal) { - setInternalKeyPool.insert(index); - } else { - setExternalKeyPool.insert(index); + bool res = true; + if (auto spk_man = m_spk_man.get()) { + res &= spk_man->TopUpKeyPool(kpSize); } - m_pool_key_to_index[pubkey.GetID()] = index; -} - -bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) -{ - nIndex = -1; - keypool.vchPubKey = CPubKey(); - { - LOCK(cs_wallet); - - TopUpKeyPool(); - - bool fReturningInternal = fRequestedInternal; - fReturningInternal &= (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); - bool use_split_keypool = set_pre_split_keypool.empty(); - std::set<int64_t>& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool; - - // Get the oldest key - if (setKeyPool.empty()) { - return false; - } - - WalletBatch batch(*database); - - auto it = setKeyPool.begin(); - nIndex = *it; - setKeyPool.erase(it); - if (!batch.ReadPool(nIndex, keypool)) { - throw std::runtime_error(std::string(__func__) + ": read failed"); - } - CPubKey pk; - if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) { - throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); - } - // If the key was pre-split keypool, we don't care about what type it is - if (use_split_keypool && keypool.fInternal != fReturningInternal) { - throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); - } - if (!keypool.vchPubKey.IsValid()) { - throw std::runtime_error(std::string(__func__) + ": keypool entry invalid"); - } - - m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); - WalletLogPrintf("keypool reserve %d\n", nIndex); - } - NotifyCanGetAddressesChanged(); - return true; -} - -void CWallet::KeepKey(int64_t nIndex) -{ - // Remove from key pool - WalletBatch batch(*database); - batch.ErasePool(nIndex); - WalletLogPrintf("keypool keep %d\n", nIndex); -} - -void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) -{ - // Return to key pool - { - LOCK(cs_wallet); - if (fInternal) { - setInternalKeyPool.insert(nIndex); - } else if (!set_pre_split_keypool.empty()) { - set_pre_split_keypool.insert(nIndex); - } else { - setExternalKeyPool.insert(nIndex); - } - m_pool_key_to_index[pubkey.GetID()] = nIndex; - NotifyCanGetAddressesChanged(); - } - WalletLogPrintf("keypool return %d\n", nIndex); -} - -bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) -{ - if (!CanGetAddresses(internal)) { - return false; - } - - CKeyPool keypool; - { - LOCK(cs_wallet); - int64_t nIndex; - if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - if (IsLocked()) return false; - WalletBatch batch(*database); - result = GenerateNewKey(batch, internal); - return true; - } - KeepKey(nIndex); - result = keypool.vchPubKey; - } - return true; + return res; } bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error) { - LOCK(cs_wallet); error.clear(); - - TopUpKeyPool(); - - // Generate a new key that is added to wallet - CPubKey new_key; - if (!GetKeyFromPool(new_key)) { - error = "Error: Keypool ran out, please call keypoolrefill first"; - return false; + bool result = false; + auto spk_man = m_spk_man.get(); + if (spk_man) { + result = spk_man->GetNewDestination(type, label, dest, error); } - LearnRelatedScripts(new_key, type); - dest = GetDestinationForKey(new_key, type); - - SetAddressBook(dest, label, "receive"); - return true; + return result; } bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error) { error.clear(); - TopUpKeyPool(); + m_spk_man->TopUpKeyPool(); ReserveDestination reservedest(this); if (!reservedest.GetReservedDestination(type, dest, true)) { @@ -3717,35 +3059,12 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des return true; } -static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) { - if (setKeyPool.empty()) { - return GetTime(); - } - - CKeyPool keypool; - int64_t nIndex = *(setKeyPool.begin()); - if (!batch.ReadPool(nIndex, keypool)) { - throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed"); - } - assert(keypool.vchPubKey.IsValid()); - return keypool.nTime; -} - int64_t CWallet::GetOldestKeyPoolTime() { - LOCK(cs_wallet); - - WalletBatch batch(*database); - - // load oldest key from keypool, get time and return - int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch); - if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) { - oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch), oldestKey); - if (!set_pre_split_keypool.empty()) { - oldestKey = std::max(GetOldestKeyTimeInPool(set_pre_split_keypool, batch), oldestKey); - } + int64_t oldestKey = std::numeric_limits<int64_t>::max(); + if (auto spk_man = m_spk_man.get()) { + oldestKey = spk_man->GetOldestKeyPoolTime(); } - return oldestKey; } @@ -3898,6 +3217,11 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestination& dest, bool internal) { + m_spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!m_spk_man) { + return false; + } + if (!pwallet->CanGetAddresses(internal)) { return false; } @@ -3905,14 +3229,14 @@ bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestin if (nIndex == -1) { CKeyPool keypool; - if (!pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal)) { + if (!m_spk_man->ReserveKeyFromKeyPool(nIndex, keypool, internal)) { return false; } vchPubKey = keypool.vchPubKey; fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); - pwallet->LearnRelatedScripts(vchPubKey, type); + m_spk_man->LearnRelatedScripts(vchPubKey, type); address = GetDestinationForKey(vchPubKey, type); dest = address; return true; @@ -3921,7 +3245,7 @@ bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestin void ReserveDestination::KeepDestination() { if (nIndex != -1) - pwallet->KeepKey(nIndex); + m_spk_man->KeepKey(nIndex); nIndex = -1; vchPubKey = CPubKey(); address = CNoDestination(); @@ -3930,37 +3254,13 @@ void ReserveDestination::KeepDestination() void ReserveDestination::ReturnDestination() { if (nIndex != -1) { - pwallet->ReturnKey(nIndex, fInternal, vchPubKey); + m_spk_man->ReturnKey(nIndex, fInternal, vchPubKey); } nIndex = -1; vchPubKey = CPubKey(); address = CNoDestination(); } -void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) -{ - AssertLockHeld(cs_wallet); - bool internal = setInternalKeyPool.count(keypool_id); - if (!internal) assert(setExternalKeyPool.count(keypool_id) || set_pre_split_keypool.count(keypool_id)); - std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : (set_pre_split_keypool.empty() ? &setExternalKeyPool : &set_pre_split_keypool); - auto it = setKeyPool->begin(); - - WalletBatch batch(*database); - while (it != std::end(*setKeyPool)) { - const int64_t& index = *(it); - if (index > keypool_id) break; // set*KeyPool is ordered - - CKeyPool keypool; - if (batch.ReadPool(index, keypool)) { //TODO: This should be unnecessary - m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); - } - LearnAllRelatedScripts(keypool.vchPubKey); - batch.ErasePool(index); - WalletLogPrintf("keypool index %d removed\n", index); - it = setKeyPool->erase(it); - } -} - void CWallet::LockCoin(const COutPoint& output) { AssertLockHeld(cs_wallet); @@ -4003,8 +3303,12 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C AssertLockHeld(cs_wallet); mapKeyBirth.clear(); + LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan(); + assert(spk_man != nullptr); + AssertLockHeld(spk_man->cs_wallet); + // get birth times for keys with metadata - for (const auto& entry : mapKeyMetadata) { + for (const auto& entry : spk_man->mapKeyMetadata) { if (entry.second.nCreateTime) { mapKeyBirth[entry.first] = entry.second.nCreateTime; } @@ -4014,7 +3318,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C const Optional<int> tip_height = locked_chain.getHeight(); const int max_height = tip_height && *tip_height > 144 ? *tip_height - 144 : 0; // the tip can be reorganized; use a 144-block safety margin std::map<CKeyID, int> mapKeyFirstBlock; - for (const CKeyID &keyid : GetKeys()) { + for (const CKeyID &keyid : spk_man->GetKeys()) { if (mapKeyBirth.count(keyid) == 0) mapKeyFirstBlock[keyid] = max_height; } @@ -4031,7 +3335,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C // ... which are already in a block for (const CTxOut &txout : wtx.tx->vout) { // iterate over all their outputs - for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *this)) { + for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *spk_man)) { // ... and all their affected keys std::map<CKeyID, int>::iterator rit = mapKeyFirstBlock.find(keyid); if (rit != mapKeyFirstBlock.end() && *height < rit->second) @@ -4156,24 +3460,6 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const return values; } -void CWallet::MarkPreSplitKeys() -{ - WalletBatch batch(*database); - for (auto it = setExternalKeyPool.begin(); it != setExternalKeyPool.end();) { - int64_t index = *it; - CKeyPool keypool; - if (!batch.ReadPool(index, keypool)) { - throw std::runtime_error(std::string(__func__) + ": read keypool entry failed"); - } - keypool.m_pre_split = true; - if (!batch.WritePool(index, keypool)) { - throw std::runtime_error(std::string(__func__) + ": writing modified keypool entry failed"); - } - set_pre_split_keypool.insert(index); - it = setExternalKeyPool.erase(it); - } -} - bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::vector<std::string>& warnings) { // Do some checking on wallet path. It should be either a: @@ -4316,13 +3602,13 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, bool hd_upgrade = false; bool split_upgrade = false; - if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->IsHDEnabled()) { + if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->m_spk_man->IsHDEnabled()) { walletInstance->WalletLogPrintf("Upgrading wallet to HD\n"); walletInstance->SetMinVersion(FEATURE_HD); // generate a new master key - CPubKey masterPubKey = walletInstance->GenerateNewSeed(); - walletInstance->SetHDSeed(masterPubKey); + CPubKey masterPubKey = walletInstance->m_spk_man->GenerateNewSeed(); + walletInstance->m_spk_man->SetHDSeed(masterPubKey); hd_upgrade = true; } // Upgrade to HD chain split if necessary @@ -4337,7 +3623,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, } // Regenerate the keypool if upgraded to HD if (hd_upgrade) { - if (!walletInstance->TopUpKeyPool()) { + if (!walletInstance->m_spk_man->TopUpKeyPool()) { error = _("Unable to generate keys").translated; return nullptr; } @@ -4352,12 +3638,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, walletInstance->SetWalletFlags(wallet_creation_flags, false); if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) { // generate a new seed - CPubKey seed = walletInstance->GenerateNewSeed(); - walletInstance->SetHDSeed(seed); + CPubKey seed = walletInstance->m_spk_man->GenerateNewSeed(); + walletInstance->m_spk_man->SetHDSeed(seed); } // Top up the keypool - if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) { + if (walletInstance->m_spk_man->CanGenerateKeys() && !walletInstance->m_spk_man->TopUpKeyPool()) { error = _("Unable to generate initial keys").translated; return nullptr; } @@ -4650,23 +3936,6 @@ bool CWalletTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const return GetBlocksToMaturity(locked_chain) > 0; } -void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type) -{ - if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) { - CTxDestination witdest = WitnessV0KeyHash(key.GetID()); - CScript witprog = GetScriptForDestination(witdest); - // Make sure the resulting program is solvable. - assert(IsSolvable(*this, witprog)); - AddCScript(witprog); - } -} - -void CWallet::LearnAllRelatedScripts(const CPubKey& key) -{ - // OutputType::P2SH_SEGWIT always adds all necessary scripts for all types. - LearnRelatedScripts(key, OutputType::P2SH_SEGWIT); -} - std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const { std::vector<OutputGroup> groups; std::map<CTxDestination, OutputGroup> gmap; @@ -4697,35 +3966,6 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu return groups; } -bool CWallet::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const -{ - CKeyMetadata meta; - { - LOCK(cs_wallet); - auto it = mapKeyMetadata.find(keyID); - if (it != mapKeyMetadata.end()) { - meta = it->second; - } - } - if (meta.has_key_origin) { - std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint); - info.path = meta.key_origin.path; - } else { // Single pubkeys get the master fingerprint of themselves - std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint); - } - return true; -} - -bool CWallet::AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info) -{ - LOCK(cs_wallet); - std::copy(info.fingerprint, info.fingerprint + 4, mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint); - mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path; - mapKeyMetadata[pubkey.GetID()].has_key_origin = true; - mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path); - return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); -} - bool CWallet::SetCrypted() { LOCK(cs_KeyStore); @@ -4760,168 +4000,17 @@ bool CWallet::Lock() return true; } -bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) -{ - { - LOCK(cs_KeyStore); - if (!SetCrypted()) - return false; - - bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys - bool keyFail = false; - CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); - for (; mi != mapCryptedKeys.end(); ++mi) - { - const CPubKey &vchPubKey = (*mi).second.first; - const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; - CKey key; - if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) - { - keyFail = true; - break; - } - keyPass = true; - if (fDecryptionThoroughlyChecked) - break; - } - if (keyPass && keyFail) - { - LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); - throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); - } - if (keyFail || (!keyPass && !accept_no_keys)) - return false; - vMasterKey = vMasterKeyIn; - fDecryptionThoroughlyChecked = true; - } - NotifyStatusChanged(this); - return true; -} - -bool CWallet::HaveKey(const CKeyID &address) const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return FillableSigningProvider::HaveKey(address); - } - return mapCryptedKeys.count(address) > 0; -} - -bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return FillableSigningProvider::GetKey(address, keyOut); - } - - CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); - if (mi != mapCryptedKeys.end()) - { - const CPubKey &vchPubKey = (*mi).second.first; - const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; - return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); - } - return false; -} - -bool CWallet::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const +ScriptPubKeyMan* CWallet::GetScriptPubKeyMan() const { - LOCK(cs_KeyStore); - WatchKeyMap::const_iterator it = mapWatchKeys.find(address); - if (it != mapWatchKeys.end()) { - pubkey_out = it->second; - return true; - } - return false; + return m_spk_man.get(); } -bool CWallet::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +const SigningProvider* CWallet::GetSigningProvider() const { - LOCK(cs_KeyStore); - if (!IsCrypted()) { - if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) { - return GetWatchPubKey(address, vchPubKeyOut); - } - return true; - } - - CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); - if (mi != mapCryptedKeys.end()) - { - vchPubKeyOut = (*mi).second.first; - return true; - } - // Check for watch-only pubkeys - return GetWatchPubKey(address, vchPubKeyOut); + return m_spk_man.get(); } -std::set<CKeyID> CWallet::GetKeys() const +LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const { - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return FillableSigningProvider::GetKeys(); - } - std::set<CKeyID> set_address; - for (const auto& mi : mapCryptedKeys) { - set_address.insert(mi.first); - } - return set_address; -} - -bool CWallet::EncryptKeys(CKeyingMaterial& vMasterKeyIn) -{ - LOCK(cs_KeyStore); - if (!mapCryptedKeys.empty() || IsCrypted()) - return false; - - fUseCrypto = true; - for (const KeyMap::value_type& mKey : mapKeys) - { - const CKey &key = mKey.second; - CPubKey vchPubKey = key.GetPubKey(); - CKeyingMaterial vchSecret(key.begin(), key.end()); - std::vector<unsigned char> vchCryptedSecret; - if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) - return false; - if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) - return false; - } - mapKeys.clear(); - return true; -} - -bool CWallet::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return FillableSigningProvider::AddKeyPubKey(key, pubkey); - } - - if (IsLocked()) { - return false; - } - - std::vector<unsigned char> vchCryptedSecret; - CKeyingMaterial vchSecret(key.begin(), key.end()); - if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { - return false; - } - - if (!AddCryptedKey(pubkey, vchCryptedSecret)) { - return false; - } - return true; -} - - -bool CWallet::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) -{ - LOCK(cs_KeyStore); - if (!SetCrypted()) { - return false; - } - - mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); - ImplicitlyLearnRelatedKeyScripts(vchPubKey); - return true; + return m_spk_man.get(); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 85c277ff50..f3b791441c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -18,7 +18,7 @@ #include <validationinterface.h> #include <wallet/coinselection.h> #include <wallet/crypter.h> -#include <wallet/ismine.h> +#include <wallet/scriptpubkeyman.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> @@ -57,8 +57,6 @@ enum class WalletCreationStatus { WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::vector<std::string>& warnings, std::shared_ptr<CWallet>& result); -//! Default for -keypool -static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; //! -paytxfee default constexpr CAmount DEFAULT_PAY_TX_FEE = 0; //! -fallbackfee default @@ -99,58 +97,12 @@ struct FeeCalculation; enum class FeeEstimateMode; class ReserveDestination; -/** (client) version numbers for particular wallet features */ -enum WalletFeature -{ - FEATURE_BASE = 10500, // the earliest version new wallets supports (only useful for getwalletinfo's clientversion output) - - FEATURE_WALLETCRYPT = 40000, // wallet encryption - FEATURE_COMPRPUBKEY = 60000, // compressed public keys - - FEATURE_HD = 130000, // Hierarchical key derivation after BIP32 (HD Wallet) - - FEATURE_HD_SPLIT = 139900, // Wallet with HD chain split (change outputs will use m/0'/1'/k) - - FEATURE_NO_DEFAULT_KEY = 159900, // Wallet without a default key written - - FEATURE_PRE_SPLIT_KEYPOOL = 169900, // Upgraded to HD SPLIT and can have a pre-split keypool - - FEATURE_LATEST = FEATURE_PRE_SPLIT_KEYPOOL -}; - //! Default for -addresstype constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::BECH32}; //! Default for -changetype constexpr OutputType DEFAULT_CHANGE_TYPE{OutputType::CHANGE_AUTO}; -enum WalletFlags : uint64_t { - // wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown - // unknown wallet flags in the lower section <= (1 << 31) will be tolerated - - // will categorize coins as clean (not reused) and dirty (reused), and handle - // them with privacy considerations in mind - WALLET_FLAG_AVOID_REUSE = (1ULL << 0), - - // Indicates that the metadata has already been upgraded to contain key origins - WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1), - - // will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys) - WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32), - - //! Flag set when a wallet contains no HD seed and no private keys, scripts, - //! addresses, and other watch only things, and is therefore "blank." - //! - //! The only function this flag serves is to distinguish a blank wallet from - //! a newly created wallet when the wallet database is loaded, to avoid - //! initialization that should only happen on first run. - //! - //! This flag is also a mandatory flag to prevent previous versions of - //! bitcoin from opening the wallet, thinking it was newly created, and - //! then improperly reinitializing it. - WALLET_FLAG_BLANK_WALLET = (1ULL << 33), -}; - static constexpr uint64_t KNOWN_WALLET_FLAGS = WALLET_FLAG_AVOID_REUSE | WALLET_FLAG_BLANK_WALLET @@ -169,99 +121,6 @@ static const std::map<std::string,WalletFlags> WALLET_FLAG_MAP{ extern const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS; -/** A key from a CWallet's keypool - * - * The wallet holds one (for pre HD-split wallets) or several keypools. These - * are sets of keys that have not yet been used to provide addresses or receive - * change. - * - * The Bitcoin Core wallet was originally a collection of unrelated private - * keys with their associated addresses. If a non-HD wallet generated a - * key/address, gave that address out and then restored a backup from before - * that key's generation, then any funds sent to that address would be - * lost definitively. - * - * The keypool was implemented to avoid this scenario (commit: 10384941). The - * wallet would generate a set of keys (100 by default). When a new public key - * was required, either to give out as an address or to use in a change output, - * it would be drawn from the keypool. The keypool would then be topped up to - * maintain 100 keys. This ensured that as long as the wallet hadn't used more - * than 100 keys since the previous backup, all funds would be safe, since a - * restored wallet would be able to scan for all owned addresses. - * - * A keypool also allowed encrypted wallets to give out addresses without - * having to be decrypted to generate a new private key. - * - * With the introduction of HD wallets (commit: f1902510), the keypool - * essentially became an address look-ahead pool. Restoring old backups can no - * longer definitively lose funds as long as the addresses used were from the - * wallet's HD seed (since all private keys can be rederived from the seed). - * However, if many addresses were used since the backup, then the wallet may - * not know how far ahead in the HD chain to look for its addresses. The - * keypool is used to implement a 'gap limit'. The keypool maintains a set of - * keys (by default 1000) ahead of the last used key and scans for the - * addresses of those keys. This avoids the risk of not seeing transactions - * involving the wallet's addresses, or of re-using the same address. - * - * The HD-split wallet feature added a second keypool (commit: 02592f4c). There - * is an external keypool (for addresses to hand out) and an internal keypool - * (for change addresses). - * - * Keypool keys are stored in the wallet/keystore's keymap. The keypool data is - * stored as sets of indexes in the wallet (setInternalKeyPool, - * setExternalKeyPool and set_pre_split_keypool), and a map from the key to the - * index (m_pool_key_to_index). The CKeyPool object is used to - * serialize/deserialize the pool data to/from the database. - */ -class CKeyPool -{ -public: - //! The time at which the key was generated. Set in AddKeypoolPubKeyWithDB - int64_t nTime; - //! The public key - CPubKey vchPubKey; - //! Whether this keypool entry is in the internal keypool (for change outputs) - bool fInternal; - //! Whether this key was generated for a keypool before the wallet was upgraded to HD-split - bool m_pre_split; - - CKeyPool(); - CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn); - - ADD_SERIALIZE_METHODS; - - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) { - int nVersion = s.GetVersion(); - if (!(s.GetType() & SER_GETHASH)) - READWRITE(nVersion); - READWRITE(nTime); - READWRITE(vchPubKey); - if (ser_action.ForRead()) { - try { - READWRITE(fInternal); - } - catch (std::ios_base::failure&) { - /* flag as external address if we can't read the internal boolean - (this will be the case for any wallet before the HD chain split version) */ - fInternal = false; - } - try { - READWRITE(m_pre_split); - } - catch (std::ios_base::failure&) { - /* flag as postsplit address if we can't read the m_pre_split boolean - (this will be the case for any wallet that upgrades to HD chain split)*/ - m_pre_split = false; - } - } - else { - READWRITE(fInternal); - READWRITE(m_pre_split); - } - } -}; - /** A wrapper to reserve an address from a wallet * * ReserveDestination is used to reserve an address. @@ -282,6 +141,7 @@ class ReserveDestination protected: //! The wallet to reserve from CWallet* pwallet; + LegacyScriptPubKeyMan* m_spk_man{nullptr}; //! The index of the address's key in the keypool int64_t nIndex{-1}; //! The public key for the address @@ -722,10 +582,9 @@ struct CoinSelectionParams class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime /** - * A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, - * and provides the ability to create new transactions. + * A CWallet maintains a set of transactions and balances, and provides the ability to create new transactions. */ -class CWallet final : public FillableSigningProvider, private interfaces::Chain::Notifications +class CWallet final : public WalletStorage, private interfaces::Chain::Notifications { private: CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore); @@ -737,22 +596,8 @@ private: //! keeps track of whether Unlock has run a thorough check before bool fDecryptionThoroughlyChecked; - using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; - using WatchOnlySet = std::set<CScript>; - using WatchKeyMap = std::map<CKeyID, CPubKey>; - bool SetCrypted(); - - //! will encrypt previously unencrypted keys - bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); - bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys = false); - CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); - WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore); - WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore); - - bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); - bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey); std::atomic<bool> fAbortRescan{false}; std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver @@ -761,8 +606,6 @@ private: std::mutex mutexScanning; friend class WalletRescanReserver; - WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr; - //! the current wallet version: clients below this version are not able to load the wallet int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE}; @@ -812,52 +655,12 @@ private: * Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */ void SyncTransaction(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - /* the HD chain data model (external chain counters) */ - CHDChain hdChain; - - /* HD derive new child key (on internal or external chain) */ - void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - - std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_wallet); - std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet); - std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_wallet); - int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0; - std::map<CKeyID, int64_t> m_pool_key_to_index; std::atomic<uint64_t> m_wallet_flags{0}; - int64_t nTimeFirstKey GUARDED_BY(cs_wallet) = 0; - - /** - * Private version of AddWatchOnly method which does not accept a - * timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if - * the watch key did not previously have a timestamp associated with it. - * Because this is an inherited virtual method, it is accessible despite - * being marked private, but it is marked private anyway to encourage use - * of the other AddWatchOnly which accepts a timestamp and sets - * nTimeFirstKey more intelligently for more efficient rescans. - */ - bool AddWatchOnly(const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool AddWatchOnlyInMem(const CScript &dest); - - /** Add a KeyOriginInfo to the wallet */ - bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info); - - //! Adds a key to the store, and saves it to disk. - bool AddKeyPubKeyWithDB(WalletBatch &batch,const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - - //! Adds a watch-only address to the store, and saves it to disk. - bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - - void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch); - bool SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::string& strPurpose); - //! Adds a script to the store and saves it to disk - bool AddCScriptWithDB(WalletBatch& batch, const CScript& script); - //! Unsets a wallet flag and saves it to disk - void UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag); + void UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag) override; /** Interface for accessing chain state. */ interfaces::Chain* m_chain; @@ -878,9 +681,6 @@ private: */ uint256 m_last_block_processed GUARDED_BY(cs_wallet); - //! Fetches a key from the keypool - bool GetKeyFromPool(CPubKey &key, bool internal = false); - public: /* * Main wallet lock. @@ -895,6 +695,7 @@ public: { return *database; } + WalletDatabase& GetDatabase() override { return *database; } /** * Select a set of coins such that nValueRet >= nTargetValue and at least @@ -910,15 +711,6 @@ public: */ const std::string& GetName() const { return m_location.GetName(); } - void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - - // Map from Key ID to key metadata. - std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_wallet); - - // Map from Script ID to key metadata (for watch-only keys). - std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet); - typedef std::map<unsigned int, CMasterKey> MasterKeyMap; MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID = 0; @@ -942,7 +734,7 @@ public: } bool IsCrypted() const { return fUseCrypto; } - bool IsLocked() const; + bool IsLocked() const override; bool Lock(); /** Interface to assert chain access and if successful lock it */ @@ -972,7 +764,7 @@ public: const CWalletTx* GetWalletTx(const uint256& hash) const; //! check whether we are allowed to upgrade (or already support) to the named feature - bool CanSupportFeature(enum WalletFeature wf) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } + bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } /** * populate vCoins with vector of available COutputs. @@ -1022,34 +814,10 @@ public: int64_t ScanningDuration() const { return fScanningWallet ? GetTimeMillis() - m_scanning_start : 0; } double ScanningProgress() const { return fScanningWallet ? (double) m_scanning_progress : 0; } - /** - * keystore implementation - * Generate a new key - */ - CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - //! Adds a key to the store, and saves it to disk. - bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - //! Adds a key to the store, without saving it to disk (used by LoadWallet) - bool LoadKey(const CKey& key, const CPubKey &pubkey) { return AddKeyPubKeyInner(key, pubkey); } - //! Load metadata (used by LoadWallet) - void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } - void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - - //! Adds an encrypted key to the store, and saves it to disk. - bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); - //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) - bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); - bool GetKey(const CKeyID &address, CKey& keyOut) const override; - bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; - bool HaveKey(const CKeyID &address) const override; - std::set<CKeyID> GetKeys() const override; - bool AddCScript(const CScript& redeemScript) override; - bool LoadCScript(const CScript& redeemScript); //! Adds a destination data tuple to the store, and saves it to disk bool AddDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -1062,18 +830,6 @@ public: //! Get all destination values matching a prefix. std::vector<std::string> GetDestValues(const std::string& prefix) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - //! Adds a watch-only address to the store, and saves it to disk. - bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool RemoveWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) - bool LoadWatchOnly(const CScript &dest); - //! Returns whether the watch-only script is in the wallet - bool HaveWatchOnly(const CScript &dest) const; - //! Returns whether there are any watch-only things in the wallet - bool HaveWatchOnly() const; - //! Fetches a pubkey from mapWatchKeys if it exists there - bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const; - //! Holds a timestamp at which point the wallet is scheduled (externally) to be relocked. Caller must arrange for actual relocking to occur via Lock(). int64_t nRelockTime = 0; @@ -1189,33 +945,10 @@ public: /** Absolute maximum transaction fee (in satoshis) used by default for the wallet */ CAmount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE}; - bool NewKeyPool(); size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool TopUpKeyPool(unsigned int kpSize = 0); - /** - * Reserves a key from the keypool and sets nIndex to its index - * - * @param[out] nIndex the index of the key in keypool - * @param[out] keypool the keypool the key was drawn from, which could be the - * the pre-split pool if present, or the internal or external pool - * @param fRequestedInternal true if the caller would like the key drawn - * from the internal keypool, false if external is preferred - * - * @return true if succeeded, false if failed due to empty keypool - * @throws std::runtime_error if keypool read failed, key was invalid, - * was not found in the wallet, or was misclassified in the internal - * or external keypool - */ - bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); - void KeepKey(int64_t nIndex); - void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); int64_t GetOldestKeyPoolTime(); - /** - * Marks all keys in the keypool up to and including reserve_key as used. - */ - void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - 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(interfaces::Chain::Lock& locked_chain); @@ -1225,6 +958,8 @@ public: bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error); bool GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error); + isminetype IsMine(const CTxDestination& dest) const; + isminetype IsMine(const CScript& script) const; isminetype IsMine(const CTxIn& txin) const; /** * Returns amount of debit if the input matches the @@ -1261,7 +996,7 @@ public: } //! signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower - void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr, bool fExplicit = false); + void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr, bool fExplicit = false) override; //! change which version we're allowed to upgrade to (note that this does not immediately imply upgrading to that format) bool SetMaxVersion(int nVersion); @@ -1340,31 +1075,12 @@ public: bool BackupWallet(const std::string& strDest); - /* Set the HD chain model (chain child index counters) */ - void SetHDChain(const CHDChain& chain, bool memonly); - const CHDChain& GetHDChain() const { return hdChain; } - /* Returns true if HD is enabled */ bool IsHDEnabled() const; - /* Returns true if the wallet can generate new keys */ - bool CanGenerateKeys(); - /* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */ bool CanGetAddresses(bool internal = false); - /* Generates a new HD seed (will not be activated) */ - CPubKey GenerateNewSeed(); - - /* Derives a new HD seed (will not be activated) */ - CPubKey DeriveNewSeed(const CKey& key); - - /* Set the current HD seed (will reset the chain child index counters) - Sets the seed's version based on the current wallet version (so the - caller must ensure the current wallet version is correct before calling - this function). */ - void SetHDSeed(const CPubKey& key); - /** * Blocks until the wallet state is up-to-date to /at least/ the current * chain at the time this function is entered @@ -1373,35 +1089,21 @@ public: */ void BlockUntilSyncedToCurrentChain() LOCKS_EXCLUDED(cs_main, cs_wallet); - /** - * Explicitly make the wallet learn the related scripts for outputs to the - * given key. This is purely to make the wallet file compatible with older - * software, as FillableSigningProvider automatically does this implicitly for all - * keys now. - */ - void LearnRelatedScripts(const CPubKey& key, OutputType); - - /** - * Same as LearnRelatedScripts, but when the OutputType is not known (and could - * be anything). - */ - void LearnAllRelatedScripts(const CPubKey& key); - /** set a single wallet flag */ - void SetWalletFlag(uint64_t flags); + void SetWalletFlag(uint64_t flags) override; /** Unsets a single wallet flag */ void UnsetWalletFlag(uint64_t flag); /** check if a certain wallet flag is set */ - bool IsWalletFlagSet(uint64_t flag) const; + bool IsWalletFlagSet(uint64_t flag) const override; /** overwrite all flags by the given uint64_t returns false if unknown, non-tolerable flags are present */ bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly); /** Returns a bracketed wallet name for displaying in logs, will return [default wallet] if the wallet has no name */ - const std::string GetDisplayName() const { + const std::string GetDisplayName() const override { std::string wallet_name = GetName().length() == 0 ? "default wallet" : GetName(); return strprintf("[%s]", wallet_name); }; @@ -1412,8 +1114,28 @@ public: LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...); }; - /** Implement lookup of key origin information through wallet key metadata. */ - bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + ScriptPubKeyMan* GetScriptPubKeyMan() const; + const SigningProvider* GetSigningProvider() const; + LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const; + + // Temporary LegacyScriptPubKeyMan accessors and aliases. + friend class LegacyScriptPubKeyMan; + std::unique_ptr<LegacyScriptPubKeyMan> m_spk_man = MakeUnique<LegacyScriptPubKeyMan>(*this); + CCriticalSection& cs_KeyStore = m_spk_man->cs_KeyStore; + LegacyScriptPubKeyMan::KeyMap& mapKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapKeys; + LegacyScriptPubKeyMan::ScriptMap& mapScripts GUARDED_BY(cs_KeyStore) = m_spk_man->mapScripts; + LegacyScriptPubKeyMan::CryptedKeyMap& mapCryptedKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapCryptedKeys; + LegacyScriptPubKeyMan::WatchOnlySet& setWatchOnly GUARDED_BY(cs_KeyStore) = m_spk_man->setWatchOnly; + LegacyScriptPubKeyMan::WatchKeyMap& mapWatchKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapWatchKeys; + WalletBatch*& encrypted_batch GUARDED_BY(cs_wallet) = m_spk_man->encrypted_batch; + std::set<int64_t>& setInternalKeyPool GUARDED_BY(cs_wallet) = m_spk_man->setInternalKeyPool; + std::set<int64_t>& setExternalKeyPool GUARDED_BY(cs_wallet) = m_spk_man->setExternalKeyPool; + int64_t& nTimeFirstKey GUARDED_BY(cs_wallet) = m_spk_man->nTimeFirstKey; + std::map<CKeyID, CKeyMetadata>& mapKeyMetadata GUARDED_BY(cs_wallet) = m_spk_man->mapKeyMetadata; + std::map<CScriptID, CKeyMetadata>& m_script_metadata GUARDED_BY(cs_wallet) = m_spk_man->m_script_metadata; + void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(m_spk_man->cs_wallet); m_spk_man->MarkPreSplitKeys(); } + void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(m_spk_man->cs_wallet); m_spk_man->MarkReserveKeysAsUsed(keypool_id); } + using CryptedKeyMap = LegacyScriptPubKeyMan::CryptedKeyMap; }; /** diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index a9e6763c6d..2ba7cdac36 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -196,7 +196,7 @@ public: static bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, - CWalletScanState &wss, std::string& strType, std::string& strErr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) + CWalletScanState &wss, std::string& strType, std::string& strErr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet, pwallet->GetLegacyScriptPubKeyMan()->cs_wallet) { try { // Unserialize @@ -250,8 +250,9 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssKey >> script; char fYes; ssValue >> fYes; - if (fYes == '1') - pwallet->LoadWatchOnly(script); + if (fYes == '1') { + pwallet->GetLegacyScriptPubKeyMan()->LoadWatchOnly(script); + } } else if (strType == DBKeys::KEY) { CPubKey vchPubKey; ssKey >> vchPubKey; @@ -302,12 +303,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = "Error reading wallet database: CPrivKey corrupt"; return false; } - if (!pwallet->LoadKey(key, vchPubKey)) + if (!pwallet->GetLegacyScriptPubKeyMan()->LoadKey(key, vchPubKey)) { - strErr = "Error reading wallet database: LoadKey failed"; + strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadKey failed"; return false; } } else if (strType == DBKeys::MASTER_KEY) { + // Master encryption key is loaded into only the wallet and not any of the ScriptPubKeyMans. unsigned int nID; ssKey >> nID; CMasterKey kMasterKey; @@ -332,9 +334,9 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssValue >> vchPrivKey; wss.nCKeys++; - if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey)) + if (!pwallet->GetLegacyScriptPubKeyMan()->LoadCryptedKey(vchPubKey, vchPrivKey)) { - strErr = "Error reading wallet database: LoadCryptedKey failed"; + strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedKey failed"; return false; } wss.fIsEncrypted = true; @@ -344,14 +346,14 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CKeyMetadata keyMeta; ssValue >> keyMeta; wss.nKeyMeta++; - pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta); + pwallet->GetLegacyScriptPubKeyMan()->LoadKeyMetadata(vchPubKey.GetID(), keyMeta); } else if (strType == DBKeys::WATCHMETA) { CScript script; ssKey >> script; CKeyMetadata keyMeta; ssValue >> keyMeta; wss.nKeyMeta++; - pwallet->LoadScriptMetadata(CScriptID(script), keyMeta); + pwallet->GetLegacyScriptPubKeyMan()->LoadScriptMetadata(CScriptID(script), keyMeta); } else if (strType == DBKeys::DEFAULTKEY) { // We don't want or need the default key, but if there is one set, // we want to make sure that it is valid so that we can detect corruption @@ -367,15 +369,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CKeyPool keypool; ssValue >> keypool; - pwallet->LoadKeyPool(nIndex, keypool); + pwallet->GetLegacyScriptPubKeyMan()->LoadKeyPool(nIndex, keypool); } else if (strType == DBKeys::CSCRIPT) { uint160 hash; ssKey >> hash; CScript script; ssValue >> script; - if (!pwallet->LoadCScript(script)) + if (!pwallet->GetLegacyScriptPubKeyMan()->LoadCScript(script)) { - strErr = "Error reading wallet database: LoadCScript failed"; + strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCScript failed"; return false; } } else if (strType == DBKeys::ORDERPOSNEXT) { @@ -389,7 +391,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, } else if (strType == DBKeys::HDCHAIN) { CHDChain chain; ssValue >> chain; - pwallet->SetHDChain(chain, true); + pwallet->GetLegacyScriptPubKeyMan()->SetHDChain(chain, true); } else if (strType == DBKeys::FLAGS) { uint64_t flags; ssValue >> flags; @@ -432,6 +434,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) DBErrors result = DBErrors::LOAD_OK; LOCK(pwallet->cs_wallet); + AssertLockHeld(pwallet->GetLegacyScriptPubKeyMan()->cs_wallet); try { int nMinVersion = 0; if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) { @@ -512,8 +515,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys, wss.m_unknown_records); // nTimeFirstKey is only reliable if all keys have metadata - if ((wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta) - pwallet->UpdateTimeFirstKey(1); + if ((wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta) { + auto spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (spk_man) { + spk_man->UpdateTimeFirstKey(1); + } + } for (const uint256& hash : wss.vWalletUpgrade) WriteTx(pwallet->mapWallet.at(hash)); @@ -706,6 +713,7 @@ bool WalletBatch::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, C { // Required in LoadKeyMetadata(): LOCK(dummyWallet->cs_wallet); + AssertLockHeld(dummyWallet->GetLegacyScriptPubKeyMan()->cs_wallet); fReadOK = ReadKeyValue(dummyWallet, ssKey, ssValue, dummyWss, strType, strErr); } diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index 3b920fdbbb..dc0cac60bd 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -37,8 +37,9 @@ static std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs:: wallet_instance->SetMinVersion(FEATURE_HD_SPLIT); // generate a new HD seed - CPubKey seed = wallet_instance->GenerateNewSeed(); - wallet_instance->SetHDSeed(seed); + auto spk_man = wallet_instance->GetLegacyScriptPubKeyMan(); + CPubKey seed = spk_man->GenerateNewSeed(); + spk_man->SetHDSeed(seed); tfm::format(std::cout, "Topping up keypool...\n"); wallet_instance->TopUpKeyPool(); @@ -94,7 +95,7 @@ static void WalletShowInfo(CWallet* wallet_instance) tfm::format(std::cout, "Wallet info\n===========\n"); tfm::format(std::cout, "Encrypted: %s\n", wallet_instance->IsCrypted() ? "yes" : "no"); - tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->GetHDChain().seed_id.IsNull() ? "no" : "yes"); + tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->IsHDEnabled() ? "yes" : "no"); tfm::format(std::cout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize()); tfm::format(std::cout, "Transactions: %zu\n", wallet_instance->mapWallet.size()); tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->mapAddressBook.size()); diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h index ba2f913841..044c757e68 100644 --- a/src/wallet/walletutil.h +++ b/src/wallet/walletutil.h @@ -9,6 +9,54 @@ #include <vector> +/** (client) version numbers for particular wallet features */ +enum WalletFeature +{ + FEATURE_BASE = 10500, // the earliest version new wallets supports (only useful for getwalletinfo's clientversion output) + + FEATURE_WALLETCRYPT = 40000, // wallet encryption + FEATURE_COMPRPUBKEY = 60000, // compressed public keys + + FEATURE_HD = 130000, // Hierarchical key derivation after BIP32 (HD Wallet) + + FEATURE_HD_SPLIT = 139900, // Wallet with HD chain split (change outputs will use m/0'/1'/k) + + FEATURE_NO_DEFAULT_KEY = 159900, // Wallet without a default key written + + FEATURE_PRE_SPLIT_KEYPOOL = 169900, // Upgraded to HD SPLIT and can have a pre-split keypool + + FEATURE_LATEST = FEATURE_PRE_SPLIT_KEYPOOL +}; + + + +enum WalletFlags : uint64_t { + // wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown + // unknown wallet flags in the lower section <= (1 << 31) will be tolerated + + // will categorize coins as clean (not reused) and dirty (reused), and handle + // them with privacy considerations in mind + WALLET_FLAG_AVOID_REUSE = (1ULL << 0), + + // Indicates that the metadata has already been upgraded to contain key origins + WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1), + + // will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys) + WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32), + + //! Flag set when a wallet contains no HD seed and no private keys, scripts, + //! addresses, and other watch only things, and is therefore "blank." + //! + //! The only function this flag serves is to distinguish a blank wallet from + //! a newly created wallet when the wallet database is loaded, to avoid + //! initialization that should only happen on first run. + //! + //! This flag is also a mandatory flag to prevent previous versions of + //! bitcoin from opening the wallet, thinking it was newly created, and + //! then improperly reinitializing it. + WALLET_FLAG_BLANK_WALLET = (1ULL << 33), +}; + //! Get the path of the wallet directory. fs::path GetWalletDir(); diff --git a/src/walletinitinterface.h b/src/walletinitinterface.h index 2e1fdf4f3a..7ccda1c566 100644 --- a/src/walletinitinterface.h +++ b/src/walletinitinterface.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_WALLETINITINTERFACE_H #define BITCOIN_WALLETINITINTERFACE_H -struct InitInterfaces; +struct NodeContext; class WalletInitInterface { public: @@ -15,8 +15,8 @@ public: virtual void AddWalletOptions() const = 0; /** Check wallet parameter interaction */ virtual bool ParameterInteraction() const = 0; - /** Add wallets that should be opened to list of init interfaces. */ - virtual void Construct(InitInterfaces& interfaces) const = 0; + /** Add wallets that should be opened to list of chain clients. */ + virtual void Construct(NodeContext& node) const = 0; virtual ~WalletInitInterface() {} }; diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index c956af1cbe..41a9b50ea6 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -41,6 +41,7 @@ class RawTransactionsTest(BitcoinTestFramework): connect_nodes(self.nodes[0], 3) def run_test(self): + self.log.info("Connect nodes, set fees, generate blocks, and sync") self.min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee'] # This test is not meant to test fee estimation and we'd like # to be sure all txs are sent at a consistent desired feerate @@ -90,7 +91,8 @@ class RawTransactionsTest(BitcoinTestFramework): self.test_option_subtract_fee_from_outputs() def test_change_position(self): - # ensure that setting changePosition in fundraw with an exact match is handled properly + """Ensure setting changePosition in fundraw with an exact match is handled properly.""" + self.log.info("Test fundrawtxn changePosition option") rawmatch = self.nodes[2].createrawtransaction([], {self.nodes[2].getnewaddress():50}) rawmatch = self.nodes[2].fundrawtransaction(rawmatch, {"changePosition":1, "subtractFeeFromOutputs":[0]}) assert_equal(rawmatch["changepos"], -1) @@ -115,9 +117,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.sync_all() def test_simple(self): - ############### - # simple test # - ############### + self.log.info("Test fundrawtxn") inputs = [ ] outputs = { self.nodes[0].getnewaddress() : 1.0 } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) @@ -127,9 +127,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert len(dec_tx['vin']) > 0 #test that we have enough inputs def test_simple_two_coins(self): - ############################## - # simple test with two coins # - ############################## + self.log.info("Test fundrawtxn with 2 coins") inputs = [ ] outputs = { self.nodes[0].getnewaddress() : 2.2 } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) @@ -141,9 +139,8 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') def test_simple_two_outputs(self): - ################################ - # simple test with two outputs # - ################################ + self.log.info("Test fundrawtxn with 2 outputs") + inputs = [ ] outputs = { self.nodes[0].getnewaddress() : 2.6, self.nodes[1].getnewaddress() : 2.5 } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) @@ -159,9 +156,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') def test_change(self): - ######################################################################### - # test a fundrawtransaction with a VIN greater than the required amount # - ######################################################################### + self.log.info("Test fundrawtxn with a vin > required amount") utx = get_unspent(self.nodes[2].listunspent(), 5) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}] @@ -181,9 +176,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee def test_no_change(self): - ##################################################################### - # test a fundrawtransaction with which will not get a change output # - ##################################################################### + self.log.info("Test fundrawtxn not having a change output") utx = get_unspent(self.nodes[2].listunspent(), 5) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}] @@ -203,9 +196,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee def test_invalid_option(self): - #################################################### - # test a fundrawtransaction with an invalid option # - #################################################### + self.log.info("Test fundrawtxn with an invalid option") utx = get_unspent(self.nodes[2].listunspent(), 5) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ] @@ -220,9 +211,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-3, "Unexpected key reserveChangeKey", lambda: self.nodes[2].fundrawtransaction(hexstring=rawtx, options={'reserveChangeKey': True})) def test_invalid_change_address(self): - ############################################################ - # test a fundrawtransaction with an invalid change address # - ############################################################ + self.log.info("Test fundrawtxn with an invalid change address") utx = get_unspent(self.nodes[2].listunspent(), 5) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ] @@ -234,9 +223,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-5, "changeAddress must be a valid bitcoin address", self.nodes[2].fundrawtransaction, rawtx, {'changeAddress':'foobar'}) def test_valid_change_address(self): - ############################################################ - # test a fundrawtransaction with a provided change address # - ############################################################ + self.log.info("Test fundrawtxn with a provided change address") utx = get_unspent(self.nodes[2].listunspent(), 5) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ] @@ -253,9 +240,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(change, out['scriptPubKey']['addresses'][0]) def test_change_type(self): - ######################################################### - # test a fundrawtransaction with a provided change type # - ######################################################### + self.log.info("Test fundrawtxn with a provided change type") utx = get_unspent(self.nodes[2].listunspent(), 5) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ] @@ -268,9 +253,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal('witness_v0_keyhash', dec_tx['vout'][rawtx['changepos']]['scriptPubKey']['type']) def test_coin_selection(self): - ######################################################################### - # test a fundrawtransaction with a VIN smaller than the required amount # - ######################################################################### + self.log.info("Test fundrawtxn with a vin < required amount") utx = get_unspent(self.nodes[2].listunspent(), 1) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}] @@ -302,9 +285,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(len(dec_tx['vout']), 2) def test_two_vin(self): - ########################################### - # test a fundrawtransaction with two VINs # - ########################################### + self.log.info("Test fundrawtxn with 2 vins") utx = get_unspent(self.nodes[2].listunspent(), 1) utx2 = get_unspent(self.nodes[2].listunspent(), 5) @@ -335,9 +316,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(matchingIns, 2) #we now must see two vins identical to vins given as params def test_two_vin_two_vout(self): - ######################################################### - # test a fundrawtransaction with two VINs and two vOUTs # - ######################################################### + self.log.info("Test fundrawtxn with 2 vins and 2 vouts") utx = get_unspent(self.nodes[2].listunspent(), 1) utx2 = get_unspent(self.nodes[2].listunspent(), 5) @@ -360,52 +339,54 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(len(dec_tx['vout']), 3) def test_invalid_input(self): - ############################################## - # test a fundrawtransaction with invalid vin # - ############################################## + self.log.info("Test fundrawtxn with an invalid vin") inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin! outputs = { self.nodes[0].getnewaddress() : 1.0} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx) def test_fee_p2pkh(self): - ############################################################ - #compare fee of a standard pubkeyhash transaction + """Compare fee of a standard pubkeyhash transaction.""" + self.log.info("Test fundrawtxn p2pkh fee") inputs = [] outputs = {self.nodes[1].getnewaddress():1.1} rawtx = self.nodes[0].createrawtransaction(inputs, outputs) fundedTx = self.nodes[0].fundrawtransaction(rawtx) - #create same transaction over sendtoaddress + # Create same transaction over sendtoaddress. txId = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] - #compare fee + # Compare fee. feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert feeDelta >= 0 and feeDelta <= self.fee_tolerance - ############################################################ def test_fee_p2pkh_multi_out(self): - ############################################################ - #compare fee of a standard pubkeyhash transaction with multiple outputs + """Compare fee of a standard pubkeyhash transaction with multiple outputs.""" + self.log.info("Test fundrawtxn p2pkh fee with multiple outputs") inputs = [] - outputs = {self.nodes[1].getnewaddress():1.1,self.nodes[1].getnewaddress():1.2,self.nodes[1].getnewaddress():0.1,self.nodes[1].getnewaddress():1.3,self.nodes[1].getnewaddress():0.2,self.nodes[1].getnewaddress():0.3} + outputs = { + self.nodes[1].getnewaddress():1.1, + self.nodes[1].getnewaddress():1.2, + self.nodes[1].getnewaddress():0.1, + self.nodes[1].getnewaddress():1.3, + self.nodes[1].getnewaddress():0.2, + self.nodes[1].getnewaddress():0.3, + } rawtx = self.nodes[0].createrawtransaction(inputs, outputs) fundedTx = self.nodes[0].fundrawtransaction(rawtx) - #create same transaction over sendtoaddress + + # Create same transaction over sendtoaddress. txId = self.nodes[0].sendmany("", outputs) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] - #compare fee + # Compare fee. feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert feeDelta >= 0 and feeDelta <= self.fee_tolerance - ############################################################ def test_fee_p2sh(self): - ############################################################ - #compare fee of a 2of2 multisig p2sh transaction - - # create 2of2 addr + """Compare fee of a 2-of-2 multisig p2sh transaction.""" + # Create 2-of-2 addr. addr1 = self.nodes[1].getnewaddress() addr2 = self.nodes[1].getnewaddress() @@ -419,20 +400,19 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[0].createrawtransaction(inputs, outputs) fundedTx = self.nodes[0].fundrawtransaction(rawtx) - #create same transaction over sendtoaddress + # Create same transaction over sendtoaddress. txId = self.nodes[0].sendtoaddress(mSigObj, 1.1) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] - #compare fee + # Compare fee. feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert feeDelta >= 0 and feeDelta <= self.fee_tolerance - ############################################################ def test_fee_4of5(self): - ############################################################ - #compare fee of a standard pubkeyhash transaction + """Compare fee of a standard pubkeyhash transaction.""" + self.log.info("Test fundrawtxn fee with 4-of-5 addresses") - # create 4of5 addr + # Create 4-of-5 addr. addr1 = self.nodes[1].getnewaddress() addr2 = self.nodes[1].getnewaddress() addr3 = self.nodes[1].getnewaddress() @@ -445,37 +425,50 @@ class RawTransactionsTest(BitcoinTestFramework): addr4Obj = self.nodes[1].getaddressinfo(addr4) addr5Obj = self.nodes[1].getaddressinfo(addr5) - mSigObj = self.nodes[1].addmultisigaddress(4, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey'], addr4Obj['pubkey'], addr5Obj['pubkey']])['address'] + mSigObj = self.nodes[1].addmultisigaddress( + 4, + [ + addr1Obj['pubkey'], + addr2Obj['pubkey'], + addr3Obj['pubkey'], + addr4Obj['pubkey'], + addr5Obj['pubkey'], + ] + )['address'] inputs = [] outputs = {mSigObj:1.1} rawtx = self.nodes[0].createrawtransaction(inputs, outputs) fundedTx = self.nodes[0].fundrawtransaction(rawtx) - #create same transaction over sendtoaddress + # Create same transaction over sendtoaddress. txId = self.nodes[0].sendtoaddress(mSigObj, 1.1) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] - #compare fee + # Compare fee. feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert feeDelta >= 0 and feeDelta <= self.fee_tolerance - ############################################################ def test_spend_2of2(self): - ############################################################ - # spend a 2of2 multisig transaction over fundraw + """Spend a 2-of-2 multisig transaction over fundraw.""" + self.log.info("Test fundrawtxn spending 2-of-2 multisig") - # create 2of2 addr + # Create 2-of-2 addr. addr1 = self.nodes[2].getnewaddress() addr2 = self.nodes[2].getnewaddress() addr1Obj = self.nodes[2].getaddressinfo(addr1) addr2Obj = self.nodes[2].getaddressinfo(addr2) - mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] + mSigObj = self.nodes[2].addmultisigaddress( + 2, + [ + addr1Obj['pubkey'], + addr2Obj['pubkey'], + ] + )['address'] - - # send 1.2 BTC to msig addr + # Send 1.2 BTC to msig addr. self.nodes[0].sendtoaddress(mSigObj, 1.2) self.sync_all() self.nodes[1].generate(1) @@ -493,18 +486,18 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[1].generate(1) self.sync_all() - # make sure funds are received at node1 + # Make sure funds are received at node1. assert_equal(oldBalance+Decimal('1.10000000'), self.nodes[1].getbalance()) def test_locked_wallet(self): - ############################################################ - # locked wallet test + self.log.info("Test fundrawtxn with locked wallet") + self.nodes[1].encryptwallet("test") self.stop_nodes() self.start_nodes() # This test is not meant to test fee estimation and we'd like - # to be sure all txs are sent at a consistent desired feerate + # to be sure all txns are sent at a consistent desired feerate. for node in self.nodes: node.settxfee(self.min_relay_tx_fee) @@ -513,11 +506,11 @@ class RawTransactionsTest(BitcoinTestFramework): connect_nodes(self.nodes[0], 2) connect_nodes(self.nodes[0], 3) # Again lock the watchonly UTXO or nodes[0] may spend it, because - # lockunspent is memory-only and thus lost on restart + # lockunspent is memory-only and thus lost on restart. self.nodes[0].lockunspent(False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}]) self.sync_all() - # drain the keypool + # Drain the keypool. self.nodes[1].getnewaddress() self.nodes[1].getrawchangeaddress() inputs = [] @@ -527,7 +520,7 @@ class RawTransactionsTest(BitcoinTestFramework): # creating the key must be impossible because the wallet is locked assert_raises_rpc_error(-4, "Keypool ran out, please call keypoolrefill first", self.nodes[1].fundrawtransaction, rawtx) - #refill the keypool + # Refill the keypool. self.nodes[1].walletpassphrase("test", 100) self.nodes[1].keypoolrefill(8) #need to refill the keypool to get an internal change address self.nodes[1].walletlock() @@ -541,22 +534,21 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[1].createrawtransaction(inputs, outputs) fundedTx = self.nodes[1].fundrawtransaction(rawtx) - #now we need to unlock + # Now we need to unlock. self.nodes[1].walletpassphrase("test", 600) signedTx = self.nodes[1].signrawtransactionwithwallet(fundedTx['hex']) self.nodes[1].sendrawtransaction(signedTx['hex']) self.nodes[1].generate(1) self.sync_all() - # make sure funds are received at node1 + # Make sure funds are received at node1. assert_equal(oldBalance+Decimal('51.10000000'), self.nodes[0].getbalance()) def test_many_inputs_fee(self): - ############################################### - # multiple (~19) inputs tx test | Compare fee # - ############################################### + """Multiple (~19) inputs tx test | Compare fee.""" + self.log.info("Test fundrawtxn fee with many inputs") - #empty node1, send some small coins from node0 to node1 + # Empty node1, send some small coins from node0 to node1. self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True) self.sync_all() self.nodes[0].generate(1) @@ -567,26 +559,25 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() - #fund a tx with ~20 small inputs + # Fund a tx with ~20 small inputs. inputs = [] outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04} rawtx = self.nodes[1].createrawtransaction(inputs, outputs) fundedTx = self.nodes[1].fundrawtransaction(rawtx) - #create same transaction over sendtoaddress + # Create same transaction over sendtoaddress. txId = self.nodes[1].sendmany("", outputs) signedFee = self.nodes[1].getrawmempool(True)[txId]['fee'] - #compare fee + # Compare fee. feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert feeDelta >= 0 and feeDelta <= self.fee_tolerance * 19 #~19 inputs def test_many_inputs_send(self): - ############################################# - # multiple (~19) inputs tx test | sign/send # - ############################################# + """Multiple (~19) inputs tx test | sign/send.""" + self.log.info("Test fundrawtxn sign+send with many inputs") - #again, empty node1, send some small coins from node0 to node1 + # Again, empty node1, send some small coins from node0 to node1. self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True) self.sync_all() self.nodes[0].generate(1) @@ -597,7 +588,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() - #fund a tx with ~20 small inputs + # Fund a tx with ~20 small inputs. oldBalance = self.nodes[0].getbalance() inputs = [] @@ -612,9 +603,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward def test_op_return(self): - ##################################################### - # test fundrawtransaction with OP_RETURN and no vin # - ##################################################### + self.log.info("Test fundrawtxn with OP_RETURN and no vin") rawtx = "0100000000010000000000000000066a047465737400000000" dec_tx = self.nodes[2].decoderawtransaction(rawtx) @@ -629,9 +618,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(len(dec_tx['vout']), 2) # one change output added def test_watchonly(self): - ################################################## - # test a fundrawtransaction using only watchonly # - ################################################## + self.log.info("Test fundrawtxn using only watchonly") inputs = [] outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount / 2} @@ -646,15 +633,13 @@ class RawTransactionsTest(BitcoinTestFramework): assert_greater_than(result["changepos"], -1) def test_all_watched_funds(self): - ############################################################### - # test fundrawtransaction using the entirety of watched funds # - ############################################################### + self.log.info("Test fundrawtxn using entirety of watched funds") inputs = [] outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) - # Backward compatibility test (2nd param is includeWatching) + # Backward compatibility test (2nd param is includeWatching). result = self.nodes[3].fundrawtransaction(rawtx, True) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) assert_equal(len(res_dec["vin"]), 2) @@ -673,11 +658,9 @@ class RawTransactionsTest(BitcoinTestFramework): self.sync_all() def test_option_feerate(self): - ####################### - # Test feeRate option # - ####################### + self.log.info("Test fundrawtxn feeRate option") - # Make sure there is exactly one input so coin selection can't skew the result + # Make sure there is exactly one input so coin selection can't skew the result. assert_equal(len(self.nodes[3].listunspent(1)), 1) inputs = [] @@ -692,9 +675,8 @@ class RawTransactionsTest(BitcoinTestFramework): assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate) def test_address_reuse(self): - ################################ - # Test no address reuse occurs # - ################################ + """Test no address reuse occurs.""" + self.log.info("Test fundrawtxn does not reuse addresses") rawtx = self.nodes[3].createrawtransaction(inputs=[], outputs={self.nodes[3].getnewaddress(): 1}) result3 = self.nodes[3].fundrawtransaction(rawtx) @@ -705,15 +687,13 @@ class RawTransactionsTest(BitcoinTestFramework): changeaddress += out['scriptPubKey']['addresses'][0] assert changeaddress != "" nextaddr = self.nodes[3].getnewaddress() - # Now the change address key should be removed from the keypool + # Now the change address key should be removed from the keypool. assert changeaddress != nextaddr def test_option_subtract_fee_from_outputs(self): - ###################################### - # Test subtractFeeFromOutputs option # - ###################################### + self.log.info("Test fundrawtxn subtractFeeFromOutputs option") - # Make sure there is exactly one input so coin selection can't skew the result + # Make sure there is exactly one input so coin selection can't skew the result. assert_equal(len(self.nodes[3].listunspent(1)), 1) inputs = [] @@ -744,38 +724,39 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[3].createrawtransaction(inputs, outputs) result = [self.nodes[3].fundrawtransaction(rawtx), - # split the fee between outputs 0, 2, and 3, but not output 1 + # Split the fee between outputs 0, 2, and 3, but not output 1. self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0, 2, 3]})] dec_tx = [self.nodes[3].decoderawtransaction(result[0]['hex']), self.nodes[3].decoderawtransaction(result[1]['hex'])] - # Nested list of non-change output amounts for each transaction + # Nested list of non-change output amounts for each transaction. output = [[out['value'] for i, out in enumerate(d['vout']) if i != r['changepos']] for d, r in zip(dec_tx, result)] - # List of differences in output amounts between normal and subtractFee transactions + # List of differences in output amounts between normal and subtractFee transactions. share = [o0 - o1 for o0, o1 in zip(output[0], output[1])] - # output 1 is the same in both transactions + # Output 1 is the same in both transactions. assert_equal(share[1], 0) - # the other 3 outputs are smaller as a result of subtractFeeFromOutputs + # The other 3 outputs are smaller as a result of subtractFeeFromOutputs. assert_greater_than(share[0], 0) assert_greater_than(share[2], 0) assert_greater_than(share[3], 0) - # outputs 2 and 3 take the same share of the fee + # Outputs 2 and 3 take the same share of the fee. assert_equal(share[2], share[3]) - # output 0 takes at least as much share of the fee, and no more than 2 satoshis more, than outputs 2 and 3 + # Output 0 takes at least as much share of the fee, and no more than 2 + # satoshis more, than outputs 2 and 3. assert_greater_than_or_equal(share[0], share[2]) assert_greater_than_or_equal(share[2] + Decimal(2e-8), share[0]) - # the fee is the same in both transactions + # The fee is the same in both transactions. assert_equal(result[0]['fee'], result[1]['fee']) - # the total subtracted from the outputs is equal to the fee + # The total subtracted from the outputs is equal to the fee. assert_equal(share[0] + share[2] + share[3], result[0]['fee']) if __name__ == '__main__': diff --git a/test/functional/rpc_invalidateblock.py b/test/functional/rpc_invalidateblock.py index 595b40f7cb..1fdc134f97 100755 --- a/test/functional/rpc_invalidateblock.py +++ b/test/functional/rpc_invalidateblock.py @@ -5,7 +5,7 @@ """Test the invalidateblock RPC.""" from test_framework.test_framework import BitcoinTestFramework -from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE +from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR from test_framework.util import ( assert_equal, connect_nodes, @@ -62,7 +62,7 @@ class InvalidateTest(BitcoinTestFramework): wait_until(lambda: self.nodes[1].getblockcount() == 4, timeout=5) self.log.info("Verify that we reconsider all ancestors as well") - blocks = self.nodes[1].generatetoaddress(10, ADDRESS_BCRT1_UNSPENDABLE) + blocks = self.nodes[1].generatetodescriptor(10, ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR) assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) # Invalidate the two blocks at the tip self.nodes[1].invalidateblock(blocks[-1]) @@ -74,7 +74,7 @@ class InvalidateTest(BitcoinTestFramework): assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) self.log.info("Verify that we reconsider all descendants") - blocks = self.nodes[1].generatetoaddress(10, ADDRESS_BCRT1_UNSPENDABLE) + blocks = self.nodes[1].generatetodescriptor(10, ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR) assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) # Invalidate the two blocks at the tip self.nodes[1].invalidateblock(blocks[-2]) diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 74fea07350..4ee46d5f53 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -209,7 +209,7 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[2].signrawtransactionwithwallet(rawtx) # This will raise an exception since there are missing inputs - assert_raises_rpc_error(-25, "Missing inputs", self.nodes[2].sendrawtransaction, rawtx['hex']) + assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx['hex']) ##################################### # getrawtransaction with block hash # diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index 194f2f061b..97585fe054 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -12,6 +12,7 @@ from .util import hex_str_to_bytes from . import segwit_addr ADDRESS_BCRT1_UNSPENDABLE = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj' +ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR = 'addr(bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj)#juyq9d97' class AddressType(enum.Enum): diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index cde99a2219..4d7967273a 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -316,6 +316,7 @@ def initialize_datadir(dirname, n, chain): f.write("listenonion=0\n") f.write("printtoconsole=0\n") f.write("upnp=0\n") + f.write("shrinkdebugfile=0\n") os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True) os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True) return datadir diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index e0b523b718..6665d2e7f5 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -181,6 +181,7 @@ BASE_SCRIPTS = [ 'mining_basic.py', 'wallet_bumpfee.py', 'wallet_bumpfee_totalfee_deprecation.py', + 'wallet_implicitsegwit.py', 'rpc_named_arguments.py', 'wallet_listsinceblock.py', 'p2p_leak.py', diff --git a/test/functional/wallet_implicitsegwit.py b/test/functional/wallet_implicitsegwit.py new file mode 100755 index 0000000000..379fa6a12f --- /dev/null +++ b/test/functional/wallet_implicitsegwit.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the wallet implicit segwit feature.""" + +import test_framework.address as address +from test_framework.test_framework import BitcoinTestFramework + +# TODO: Might be nice to test p2pk here too +address_types = ('legacy', 'bech32', 'p2sh-segwit') + +def key_to_address(key, address_type): + if address_type == 'legacy': + return address.key_to_p2pkh(key) + elif address_type == 'p2sh-segwit': + return address.key_to_p2sh_p2wpkh(key) + elif address_type == 'bech32': + return address.key_to_p2wpkh(key) + +def send_a_to_b(receive_node, send_node): + keys = {} + for a in address_types: + a_address = receive_node.getnewaddress(address_type=a) + pubkey = receive_node.getaddressinfo(a_address)['pubkey'] + keys[a] = pubkey + for b in address_types: + b_address = key_to_address(pubkey, b) + send_node.sendtoaddress(address=b_address, amount=1) + return keys + +def check_implicit_transactions(implicit_keys, implicit_node): + # The implicit segwit node allows conversion all possible ways + txs = implicit_node.listtransactions(None, 99999) + for a in address_types: + pubkey = implicit_keys[a] + for b in address_types: + b_address = key_to_address(pubkey, b) + assert(('receive', b_address) in tuple((tx['category'], tx['address']) for tx in txs)) + +class ImplicitSegwitTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + self.log.info("Manipulating addresses and sending transactions to all variations") + implicit_keys = send_a_to_b(self.nodes[0], self.nodes[1]) + + self.sync_all() + + self.log.info("Checking that transactions show up correctly without a restart") + check_implicit_transactions(implicit_keys, self.nodes[0]) + + self.log.info("Checking that transactions still show up correctly after a restart") + self.restart_node(0) + self.restart_node(1) + + check_implicit_transactions(implicit_keys, self.nodes[0]) + +if __name__ == '__main__': + ImplicitSegwitTest().main() diff --git a/test/lint/git-subtree-check.sh b/test/lint/git-subtree-check.sh index 85e8b841b6..7b5707a17a 100755 --- a/test/lint/git-subtree-check.sh +++ b/test/lint/git-subtree-check.sh @@ -4,7 +4,8 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. export LC_ALL=C -DIR="$1" +# Strip trailing / from directory path (in case it was added by autocomplete) +DIR="${1%/}" COMMIT="$2" if [ -z "$COMMIT" ]; then COMMIT=HEAD diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 8607fc4371..ccd12b5823 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -30,7 +30,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "policy/fees -> txmempool -> validation -> policy/fees" "qt/guiutil -> qt/walletmodel -> qt/optionsmodel -> qt/guiutil" "txmempool -> validation -> validationinterface -> txmempool" - "wallet/ismine -> wallet/wallet -> wallet/ismine" + "wallet/scriptpubkeyman -> wallet/wallet -> wallet/scriptpubkeyman" ) EXIT_CODE=0 diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index 9f34d0f4dd..99127e01f8 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -20,6 +20,9 @@ FALSE_POSITIVES = [ ("src/wallet/wallet.h", "WalletLogPrintf(std::string fmt, Params... parameters)"), ("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"), ("src/logging.h", "LogPrintf(const char* fmt, const Args&... args)"), + ("src/wallet/scriptpubkeyman.h", "WalletLogPrintf(const std::string& fmt, const Params&... parameters)"), + ("src/wallet/scriptpubkeyman.cpp", "WalletLogPrintf(fmt, parameters...)"), + ("src/wallet/scriptpubkeyman.cpp", "WalletLogPrintf(const std::string& fmt, const Params&... parameters)"), ] diff --git a/test/lint/lint-logs.sh b/test/lint/lint-logs.sh index 632ed7c812..72b6b2e9d5 100755 --- a/test/lint/lint-logs.sh +++ b/test/lint/lint-logs.sh @@ -15,6 +15,7 @@ export LC_ALL=C UNTERMINATED_LOGS=$(git grep --extended-regexp "LogPrintf?\(" -- "*.cpp" | \ grep -v '\\n"' | \ + grep -v '\.\.\.' | \ grep -v "/\* Continued \*/" | \ grep -v "LogPrint()" | \ grep -v "LogPrintf()") |