diff --git a/README.md b/README.md index 238f16e94abc..c224df3b397c 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ There are also [regression and integration tests](/test), written in Python, that are run automatically on the build server. These tests can be run (if the [test dependencies](/test) are installed) with: `test/functional/test_runner.py` -The Travis CI system makes sure that every pull request is built for Windows, Linux, and OS X, and that unit/sanity tests are run automatically. +The Travis CI system makes sure that every pull request is built for Windows, Linux, and macOS, and that unit/sanity tests are run automatically. ### Manual Quality Assurance (QA) Testing diff --git a/configure.ac b/configure.ac index c727c6756331..c11362776296 100644 --- a/configure.ac +++ b/configure.ac @@ -281,6 +281,10 @@ AC_LANG_PUSH([C++]) AX_CHECK_COMPILE_FLAG([-Werror],[CXXFLAG_WERROR="-Werror"],[CXXFLAG_WERROR=""]) if test "x$enable_debug" = xyes; then + # Clear default -g -O2 flags + if test "x$CXXFLAGS_overridden" = xno; then + CXXFLAGS="" + fi # Prefer -Og, fall back to -O0 if that is unavailable. AX_CHECK_COMPILE_FLAG( [-Og], diff --git a/contrib/devtools/update-translations.py b/contrib/devtools/update-translations.py index f4e77b343c2c..3779c3f48bb9 100755 --- a/contrib/devtools/update-translations.py +++ b/contrib/devtools/update-translations.py @@ -30,6 +30,10 @@ LOCALE_DIR = 'src/qt/locale' # Minimum number of messages for translation to be considered at all MIN_NUM_MESSAGES = 10 +# Regexp to check for Bitcoin addresses +ADDRESS_REGEXP = re.compile('([13]|bc1)[a-zA-Z0-9]{30,}') +# Regexp to check for Dash addresses +ADDRESS_REGEXP_DASH = re.compile('[X7][a-zA-Z0-9]{30,}') def check_at_repository_root(): if not os.path.exists('.git'): @@ -125,6 +129,18 @@ def escape_cdata(text): text = text.replace('"', '"') return text +def contains_bitcoin_addr(text, errors): + if text != None and ADDRESS_REGEXP.search(text) != None: + errors.append('Translation "%s" contains a bitcoin address. This will be removed.' % (text)) + return True + return False + +def contains_dash_addr(text, errors): + if text != None and ADDRESS_REGEXP_DASH.search(text) != None: + errors.append('Translation "%s" contains a Dash address. This will be removed.' % (text)) + return True + return False + def postprocess_translations(reduce_diff_hacks=False): print('Checking and postprocessing...') @@ -163,7 +179,8 @@ def postprocess_translations(reduce_diff_hacks=False): if translation is None: continue errors = [] - valid = check_format_specifiers(source, translation, errors, numerus) + valid = check_format_specifiers(source, translation, errors, numerus) and not contains_bitcoin_addr(translation, errors) + valid = valid and not contains_dash_addr(translation, errors) for error in errors: print('%s: %s' % (filename, error)) diff --git a/contrib/init/README.md b/contrib/init/README.md index e0adc72f3df4..1dc3a1455479 100644 --- a/contrib/init/README.md +++ b/contrib/init/README.md @@ -5,7 +5,7 @@ Upstart: dashd.conf OpenRC: dashd.openrc dashd.openrcconf CentOS: dashd.init -OS X: org.dash.dashd.plist +macOS: org.dash.dashd.plist ``` have been made available to assist packagers in creating node packages here. diff --git a/depends/README.md b/depends/README.md index ba79881e62ce..6a9a87be1616 100644 --- a/depends/README.md +++ b/depends/README.md @@ -22,7 +22,7 @@ Common `host-platform-triplets` for cross compilation are: - `i686-w64-mingw32` for Win32 - `x86_64-w64-mingw32` for Win64 -- `x86_64-apple-darwin14` for MacOSX +- `x86_64-apple-darwin14` for macOS - `arm-linux-gnueabihf` for Linux ARM 32 bit - `aarch64-linux-gnu` for Linux ARM 64 bit - `riscv32-linux-gnu` for Linux RISC-V 32 bit @@ -57,7 +57,7 @@ The following can be set when running make: make FOO=bar SOURCES_PATH: downloaded sources will be placed here BASE_CACHE: built packages will be placed here - SDK_PATH: Path where sdk's can be found (used by OSX) + SDK_PATH: Path where sdk's can be found (used by macOS) FALLBACK_DOWNLOAD_PATH: If a source file can't be fetched, try here before giving up NO_QT: Don't download/build/cache qt and its dependencies NO_WALLET: Don't download/build/cache libs needed to enable the wallet @@ -72,7 +72,7 @@ options will be passed to Dash Core's configure. In this case, `--disable-wallet Additional targets: download: run 'make download' to fetch all sources without building them - download-osx: run 'make download-osx' to fetch all sources needed for osx builds + download-osx: run 'make download-osx' to fetch all sources needed for macOS builds download-win: run 'make download-win' to fetch all sources needed for win builds download-linux: run 'make download-linux' to fetch all sources needed for linux builds diff --git a/depends/description.md b/depends/description.md index 74f9ef3f205e..9fc7093be4f3 100644 --- a/depends/description.md +++ b/depends/description.md @@ -7,7 +7,7 @@ In theory, binaries for any target OS/architecture can be created, from a builder running any OS/architecture. In practice, build-side tools must be specified when the defaults don't fit, and packages must be amended to work on new hosts. For now, a build architecture of x86_64 is assumed, either on -Linux or OSX. +Linux or macOS. ### No reliance on timestamps diff --git a/doc/README.md b/doc/README.md index b9109feeff1f..ec8298b7bbcd 100644 --- a/doc/README.md +++ b/doc/README.md @@ -18,7 +18,7 @@ Unpack the files into a directory and run: Unpack the files into a directory, and then run dash-qt.exe. -### OS X +### macOS Drag Dash-Qt to your applications folder, and then run Dash-Qt. @@ -35,7 +35,7 @@ Building --------------------- The following are developer notes on how to build Dash Core on your native platform. They are not complete guides, but include notes on the necessary libraries, compile flags, etc. -- [OS X Build Notes](build-osx.md) +- [macOS Build Notes](build-osx.md) - [Unix Build Notes](build-unix.md) - [Windows Build Notes](build-windows.md) - [OpenBSD Build Notes](build-openbsd.md) diff --git a/doc/README_osx.md b/doc/README_osx.md index 975be4be9ed6..739e22d6341c 100644 --- a/doc/README_osx.md +++ b/doc/README_osx.md @@ -1,12 +1,12 @@ -Deterministic OS X DMG Notes. +Deterministic macOS DMG Notes. -Working OS X DMGs are created in Linux by combining a recent clang, +Working macOS DMGs are created in Linux by combining a recent clang, the Apple binutils (ld, ar, etc) and DMG authoring tools. Apple uses clang extensively for development and has upstreamed the necessary functionality so that a vanilla clang can take advantage. It supports the use of -F, -target, -mmacosx-version-min, and --sysroot, which are all necessary -when building for OS X. +when building for macOS. Apple's version of binutils (called cctools) contains lots of functionality missing in the FSF's binutils. In addition to extra linker options for @@ -38,7 +38,7 @@ Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.1 Unfortunately, the usual linux tools (7zip, hpmount, loopback mount) are incapable of opening this file. To create a tarball suitable for Gitian input, there are two options: -Using Mac OS X, you can mount the dmg, and then create it with: +Using macOS, you can mount the dmg, and then create it with: ``` $ hdiutil attach Xcode_7.3.1.dmg $ tar -C /Volumes/Xcode/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ -czf MacOSX10.11.sdk.tar.gz MacOSX10.11.sdk @@ -81,7 +81,7 @@ Background images and other features can be added to DMG files by inserting a .DS_Store before creation. This is generated by the script contrib/macdeploy/custom_dsstore.py. -As of OS X Mavericks (10.9), using an Apple-blessed key to sign binaries is a +As of OS X 10.9 Mavericks, using an Apple-blessed key to sign binaries is a requirement in order to satisfy the new Gatekeeper requirements. Because this private key cannot be shared, we'll have to be a bit creative in order for the build process to remain somewhat deterministic. Here's how it works: diff --git a/doc/build-osx.md b/doc/build-osx.md index 35a912488697..fe90bfe81587 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -1,11 +1,11 @@ -Mac OS X Build Instructions and Notes +macOS Build Instructions and Notes ==================================== The commands in this guide should be executed in a Terminal application. The built-in one is located in `/Applications/Utilities/Terminal.app`. Preparation ----------- -Install the OS X command line tools: +Install the macOS command line tools: `xcode-select --install` diff --git a/doc/gitian-building.md b/doc/gitian-building.md index c32955dfdfe2..c418e59b974f 100644 --- a/doc/gitian-building.md +++ b/doc/gitian-building.md @@ -360,7 +360,7 @@ offline. Building Dash Core ---------------- -To build Dash Core (for Linux, OS X and Windows) just follow the steps under 'perform +To build Dash Core (for Linux, macOS and Windows) just follow the steps under 'perform Gitian builds' in [doc/release-process.md](release-process.md#setup-and-perform-gitian-builds) in the Dash Core repository. This may take some time as it will build all the dependencies needed for each descriptor. diff --git a/doc/init.md b/doc/init.md index e20d3501e8ca..071435fea0b5 100644 --- a/doc/init.md +++ b/doc/init.md @@ -15,7 +15,7 @@ Service User All three Linux startup configurations assume the existence of a "dashcore" user and group. They must be created before attempting to use these scripts. -The OS X configuration assumes dashd will be set up for the current user. +The macOS configuration assumes dashd will be set up for the current user. Configuration --------------------------------- @@ -65,7 +65,7 @@ reasons to make the configuration file and data directory only readable by the dashcore user and group. Access to dash-cli and other dashd rpc clients can then be controlled by group membership. -### Mac OS X +### macOS Binary: `/usr/local/bin/dashd` Configuration file: `~/Library/Application Support/DashCore/dash.conf` @@ -111,7 +111,7 @@ Using this script, you can adjust the path and flags to the dashd program by setting the DASHD and FLAGS environment variables in the file /etc/sysconfig/dashd. You can also use the DAEMONOPTS environment variable here. -### Mac OS X +### macOS Copy org.dash.dashd.plist into ~/Library/LaunchAgents. Load the launch agent by running `launchctl load ~/Library/LaunchAgents/org.dash.dashd.plist`. diff --git a/doc/release-process.md b/doc/release-process.md index 425ecba97e0d..5ea90f7fe711 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -85,7 +85,7 @@ Ensure gitian-builder is up-to-date: echo '5a60e0a4b3e0b4d655317b2f12a810211c50242138322b16e7e01c6fbb89d92f inputs/osslsigncode-2.0.tar.gz' | sha256sum -c popd -Create the OS X SDK tarball, see the [OS X readme](README_osx.md) for details, and copy it into the inputs directory. +Create the macOS SDK tarball, see the [macOS readme](README_osx.md) for details, and copy it into the inputs directory. ### Optional: Seed the Gitian sources cache and offline git repositories @@ -107,7 +107,7 @@ NOTE: Offline builds must use the --url flag to ensure Gitian fetches only from The gbuild invocations below DO NOT DO THIS by default. -### Build and sign Dash Core for Linux, Windows, and OS X: +### Build and sign Dash Core for Linux, Windows, and macOS: pushd ./gitian-builder ./bin/gbuild --num-make 2 --memory 3000 --commit dash=v${VERSION} ../dash/contrib/gitian-descriptors/gitian-linux.yml @@ -130,7 +130,7 @@ Build output expected: 1. source tarball (`dash-${VERSION}.tar.gz`) 2. linux 32-bit and 64-bit dist tarballs (`dash-${VERSION}-linux[32|64].tar.gz`) 3. windows 32-bit and 64-bit unsigned installers and dist zips (`dash-${VERSION}-win[32|64]-setup-unsigned.exe`, `dash-${VERSION}-win[32|64].zip`) - 4. OS X unsigned installer and dist tarball (`dash-${VERSION}-osx-unsigned.dmg`, `dash-${VERSION}-osx64.tar.gz`) + 4. macOS unsigned installer and dist tarball (`dash-${VERSION}-osx-unsigned.dmg`, `dash-${VERSION}-osx64.tar.gz`) 5. Gitian signatures (in `gitian.sigs/${VERSION}-/(your Gitian key)/`) ### Verify other gitian builders signatures to your own. (Optional) @@ -160,13 +160,13 @@ Commit your signature to gitian.sigs: git push # Assuming you can push to the gitian.sigs tree popd -Codesigner only: Create Windows/OS X detached signatures: +Codesigner only: Create Windows/macOS detached signatures: - Only one person handles codesigning. Everyone else should skip to the next step. -- Only once the Windows/OS X builds each have 3 matching signatures may they be signed with their respective release keys. +- Only once the Windows/macOS builds each have 3 matching signatures may they be signed with their respective release keys. -Codesigner only: Sign the osx binary: +Codesigner only: Sign the macOS binary: - transfer dashcore-osx-unsigned.tar.gz to osx for signing + transfer dashcore-osx-unsigned.tar.gz to macOS for signing tar xf dashcore-osx-unsigned.tar.gz ./detached-sig-create.sh -s "Key ID" -o runtime Enter the keychain password and authorize the signature @@ -191,12 +191,12 @@ Codesigner only: Commit the detached codesign payloads: git tag -s v${VERSION} HEAD git push the current branch and new tag -Non-codesigners: wait for Windows/OS X detached signatures: +Non-codesigners: wait for Windows/macOS detached signatures: -- Once the Windows/OS X builds each have 3 matching signatures, they will be signed with their respective release keys. +- Once the Windows/macOS builds each have 3 matching signatures, they will be signed with their respective release keys. - Detached signatures will then be committed to the [dash-detached-sigs](https://github.com/dashpay/dash-detached-sigs) repository, which can be combined with the unsigned apps to create signed binaries. -Create (and optionally verify) the signed OS X binary: +Create (and optionally verify) the signed macOS binary: pushd ./gitian-builder ./bin/gbuild -i --commit signature=v${VERSION} ../dash/contrib/gitian-descriptors/gitian-osx-signer.yml @@ -215,7 +215,7 @@ Create (and optionally verify) the signed Windows binaries: mv build/out/dash-*win32-setup.exe ../dash-${VERSION}-win32-setup.exe popd -Commit your signature for the signed OS X/Windows binaries: +Commit your signature for the signed macOS/Windows binaries: pushd gitian.sigs git add ${VERSION}-osx-signed/"${SIGNER}" diff --git a/src/Makefile.test.include b/src/Makefile.test.include index e1dbff91ec01..9d5053c2175b 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -195,8 +195,8 @@ if EMBEDDED_UNIVALUE endif %.cpp.test: %.cpp - @echo Running tests: `cat $< | grep "BOOST_FIXTURE_TEST_SUITE(\|BOOST_AUTO_TEST_SUITE(" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1` from $< - $(AM_V_at)$(TEST_BINARY) -l test_suite -t "`cat $< | grep "BOOST_FIXTURE_TEST_SUITE(\|BOOST_AUTO_TEST_SUITE(" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1`" > $<.log 2>&1 || (cat $<.log && false) + @echo Running tests: `cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1` from $< + $(AM_V_at)$(TEST_BINARY) -l test_suite -t "`cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1`" > $<.log 2>&1 || (cat $<.log && false) test/data/%.json.h: test/data/%.json @$(MKDIR_P) $(@D) diff --git a/src/arith_uint256.h b/src/arith_uint256.h index d4b0ec6152de..311ac9a40438 100644 --- a/src/arith_uint256.h +++ b/src/arith_uint256.h @@ -64,14 +64,6 @@ class base_uint explicit base_uint(const std::string& str); - bool operator!() const - { - for (int i = 0; i < WIDTH; i++) - if (pn[i] != 0) - return false; - return true; - } - const base_uint operator~() const { base_uint ret; diff --git a/src/bench/bench_dash.cpp b/src/bench/bench_dash.cpp index fcbc5f50ac10..749085d3fd9f 100644 --- a/src/bench/bench_dash.cpp +++ b/src/bench/bench_dash.cpp @@ -6,12 +6,11 @@ #include #include +#include #include -#include +#include #include -#include - -#include +#include #include @@ -48,17 +47,25 @@ static void SetupBenchArgs() gArgs.AddArg("-plot-plotlyurl=", strprintf("URL to use for plotly.js (default: %s)", DEFAULT_PLOT_PLOTLYURL), false, OptionsCategory::OPTIONS); gArgs.AddArg("-plot-width=", strprintf("Plot width in pixel (default: %u)", DEFAULT_PLOT_WIDTH), false, OptionsCategory::OPTIONS); gArgs.AddArg("-plot-height=", strprintf("Plot height in pixel (default: %u)", DEFAULT_PLOT_HEIGHT), false, OptionsCategory::OPTIONS); + + // Hidden + gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); + gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN); } int main(int argc, char** argv) { SetupBenchArgs(); - gArgs.ParseParameters(argc, argv); + std::string error; + if (!gArgs.ParseParameters(argc, argv, error)) { + fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + return EXIT_FAILURE; + } if (HelpRequested(gArgs)) { std::cout << gArgs.GetHelpMessage(); - return 0; + return EXIT_SUCCESS; } // Set the datadir after parsing the bench options @@ -82,8 +89,11 @@ int main(int argc, char** argv) std::string scaling_str = gArgs.GetArg("-scaling", DEFAULT_BENCH_SCALING); bool is_list_only = gArgs.GetBoolArg("-list", false); - double scaling_factor = boost::lexical_cast(scaling_str); - + double scaling_factor; + if (!ParseDouble(scaling_str, &scaling_factor)) { + fprintf(stderr, "Error parsing scaling factor as double: %s\n", scaling_str.c_str()); + return EXIT_FAILURE; + } std::unique_ptr printer(new benchmark::ConsolePrinter()); std::string printer_arg = gArgs.GetArg("-printer", DEFAULT_BENCH_PRINTER); @@ -103,4 +113,6 @@ int main(int argc, char** argv) CleanupBLSTests(); ECC_Stop(); + + return EXIT_SUCCESS; } diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 86b2d8700060..d68795bd0371 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -37,24 +37,22 @@ static void CoinSelection(benchmark::State& state) std::vector groups; LOCK(wallet.cs_wallet); - while (state.KeepRunning()) { - // Add coins. - for (int i = 0; i < 1000; i++) - addCoin(1000 * COIN, wallet, groups); - addCoin(3 * COIN, wallet, groups); + // Add coins. + for (int i = 0; i < 1000; ++i) { + addCoin(1000 * COIN, wallet, groups); + } + addCoin(3 * COIN, wallet, groups); + const CoinEligibilityFilter filter_standard(1, 6, 0); + const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0); + while (state.KeepRunning()) { std::set setCoinsRet; CAmount nValueRet; bool bnb_used; - CoinEligibilityFilter filter_standard(1, 6, 0); - CoinSelectionParams coin_selection_params(false, 34, 148, CFeeRate(0), 0); - bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) - || wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used); + bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used); assert(success); assert(nValueRet == 1003 * COIN); assert(setCoinsRet.size() == 2); - - groups.clear(); } } diff --git a/src/coinjoin/coinjoin-client.cpp b/src/coinjoin/coinjoin-client.cpp index e03fdd349d09..0dfe53edeb07 100644 --- a/src/coinjoin/coinjoin-client.cpp +++ b/src/coinjoin/coinjoin-client.cpp @@ -449,7 +449,7 @@ bool CCoinJoinClientSession::SendDenominate(const std::vector pwalletIn, con SignatureData sigdata; bool res = ProduceSignature(*pwallet, DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata); assert(res); - UpdateTransaction(dummyTx, nIn, sigdata); + UpdateInput(dummyTx.vin[nIn], sigdata); nIn++; } } diff --git a/src/coinjoin/coinjoin.h b/src/coinjoin/coinjoin.h index 6912110b6f6a..e5b926934943 100644 --- a/src/coinjoin/coinjoin.h +++ b/src/coinjoin/coinjoin.h @@ -154,7 +154,7 @@ class CCoinJoinAccept friend bool operator==(const CCoinJoinAccept& a, const CCoinJoinAccept& b) { - return a.nDenom == b.nDenom && a.txCollateral == b.txCollateral; + return a.nDenom == b.nDenom && CTransaction(a.txCollateral) == CTransaction(b.txCollateral); } }; diff --git a/src/dash-cli.cpp b/src/dash-cli.cpp index 7ea565af51c7..cf97031aa0b4 100644 --- a/src/dash-cli.cpp +++ b/src/dash-cli.cpp @@ -43,6 +43,7 @@ static void SetupCliArgs() gArgs.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcclienttimeout=", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcconnect=", strprintf("Send commands to node running on (default: %s)", DEFAULT_RPCCONNECT), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpccookiefile=", _("Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcpassword=", "Password for JSON-RPC connections", false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcport=", strprintf("Connect to JSON-RPC on (default: %u or testnet: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort()), false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcuser=", "Username for JSON-RPC connections", false, OptionsCategory::OPTIONS); @@ -52,6 +53,22 @@ static void SetupCliArgs() gArgs.AddArg("-stdinrpcpass", strprintf("Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password."), false, OptionsCategory::OPTIONS); SetupChainParamsBaseOptions(); + + // Hidden + gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); + gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN); +} + +/** libevent event log callback */ +static void libevent_log_cb(int severity, const char *msg) +{ +#ifndef EVENT_LOG_ERR // EVENT_LOG_ERR was added in 2.0.19; but before then _EVENT_LOG_ERR existed. +# define EVENT_LOG_ERR _EVENT_LOG_ERR +#endif + // Ignore everything other than errors + if (severity >= EVENT_LOG_ERR) { + throw std::runtime_error(strprintf("libevent error: %s", msg)); + } } ////////////////////////////////////////////////////////////////////////////// @@ -83,7 +100,11 @@ static int AppInitRPC(int argc, char* argv[]) // Parameters // SetupCliArgs(); - gArgs.ParseParameters(argc, argv); + std::string error; + if (!gArgs.ParseParameters(argc, argv, error)) { + fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + return EXIT_FAILURE; + } if (gArgs.IsArgSet("-printcrashinfo")) { std::cout << GetCrashInfoStrFromSerializedStr(gArgs.GetArg("-printcrashinfo", "")) << std::endl; @@ -114,10 +135,8 @@ static int AppInitRPC(int argc, char* argv[]) fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return EXIT_FAILURE; } - try { - gArgs.ReadConfigFiles(); - } catch (const std::exception& e) { - fprintf(stderr,"Error reading configuration file: %s\n", e.what()); + if (!gArgs.ReadConfigFiles(error, true)) { + fprintf(stderr, "Error reading configuration file: %s\n", error.c_str()); return EXIT_FAILURE; } if (!datadirFromCmdLine && !fs::is_directory(GetDataDir(false))) { @@ -517,6 +536,7 @@ int main(int argc, char* argv[]) fprintf(stderr, "Error: Initializing networking failed\n"); return EXIT_FAILURE; } + event_set_log_callback(&libevent_log_cb); try { int ret = AppInitRPC(argc, argv); diff --git a/src/dash-tx.cpp b/src/dash-tx.cpp index fd4152b1d7e3..5fe7e7d6f552 100644 --- a/src/dash-tx.cpp +++ b/src/dash-tx.cpp @@ -61,6 +61,10 @@ static void SetupBitcoinTxArgs() gArgs.AddArg("load=NAME:FILENAME", "Load JSON file FILENAME into register NAME", false, OptionsCategory::REGISTER_COMMANDS); gArgs.AddArg("set=NAME:JSON-STRING", "Set register NAME to given JSON-STRING", false, OptionsCategory::REGISTER_COMMANDS); + + // Hidden + gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); + gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN); } // @@ -73,7 +77,11 @@ static int AppInitRawTx(int argc, char* argv[]) // Parameters // SetupBitcoinTxArgs(); - gArgs.ParseParameters(argc, argv); + std::string error; + if (!gArgs.ParseParameters(argc, argv, error)) { + fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + return EXIT_FAILURE; + } if (gArgs.IsArgSet("-printcrashinfo")) { std::cout << GetCrashInfoStrFromSerializedStr(gArgs.GetArg("-printcrashinfo", "")) << std::endl; @@ -501,7 +509,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) // mergedTx will end up with all the signatures; it // starts as a clone of the raw tx: CMutableTransaction mergedTx{tx}; - const CTransaction txv{tx}; + const CMutableTransaction txv{tx}; CCoinsView viewDummy; CCoinsViewCache view(&viewDummy); @@ -584,7 +592,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) // Sign what we can: for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { - const CTxIn& txin = mergedTx.vin[i]; + CTxIn& txin = mergedTx.vin[i]; const Coin& coin = view.AccessCoin(txin.prevout); if (coin.IsSpent()) { continue; @@ -592,14 +600,12 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) const CScript& prevPubKey = coin.out.scriptPubKey; const CAmount& amount = coin.out.nValue; - SignatureData sigdata; + SignatureData sigdata = DataFromTransaction(mergedTx, i, coin.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mergedTx.vout.size())) ProduceSignature(keystore, MutableTransactionSignatureCreator(&mergedTx, i, amount, nHashType), prevPubKey, sigdata); - // ... and merge in other signatures: - sigdata = CombineSignatures(prevPubKey, MutableTransactionSignatureChecker(&mergedTx, i, amount), sigdata, DataFromTransaction(txv, i)); - UpdateTransaction(mergedTx, i, sigdata); + UpdateInput(txin, sigdata); } tx = mergedTx; diff --git a/src/dashd.cpp b/src/dashd.cpp index a5e90b1b3d8b..3dd8012093c8 100644 --- a/src/dashd.cpp +++ b/src/dashd.cpp @@ -65,10 +65,11 @@ static bool AppInit(int argc, char* argv[]) // // If Qt is used, parameters/dash.conf are parsed in qt/dash.cpp's main() SetupServerArgs(); -#if HAVE_DECL_DAEMON - gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", false, OptionsCategory::OPTIONS); -#endif - gArgs.ParseParameters(argc, argv); + std::string error; + if (!gArgs.ParseParameters(argc, argv, error)) { + fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + return false; + } if (gArgs.IsArgSet("-printcrashinfo")) { std::cout << GetCrashInfoStrFromSerializedStr(gArgs.GetArg("-printcrashinfo", "")) << std::endl; @@ -103,11 +104,8 @@ static bool AppInit(int argc, char* argv[]) fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return false; } - try - { - gArgs.ReadConfigFiles(); - } catch (const std::exception& e) { - fprintf(stderr,"Error reading configuration file: %s\n", e.what()); + if (!gArgs.ReadConfigFiles(error)) { + fprintf(stderr, "Error reading configuration file: %s\n", error.c_str()); return false; } if (!datadirFromCmdLine && !fs::is_directory(GetDataDir(false))) diff --git a/src/hdchain.cpp b/src/hdchain.cpp index 192c8dc2e1e5..78a6631698f9 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -55,7 +55,7 @@ void CHDChain::Debug(const std::string& strName) const std::cout << "seed: " << HexStr(vchSeed).c_str() << std::endl; CExtKey extkey; - extkey.SetMaster(vchSeed.data(), vchSeed.size()); + extkey.SetSeed(vchSeed.data(), vchSeed.size()); std::cout << "extended private masterkey: " << EncodeExtKey(extkey).c_str() << std::endl; @@ -167,7 +167,7 @@ void CHDChain::DeriveChildExtKey(uint32_t nAccountIndex, bool fInternal, uint32_ CExtKey changeKey; //key at m/purpose'/coin_type'/account'/change CExtKey childKey; //key at m/purpose'/coin_type'/account'/change/address_index - masterKey.SetMaster(vchSeed.data(), vchSeed.size()); + masterKey.SetSeed(vchSeed.data(), vchSeed.size()); // Use hardened derivation for purpose, coin_type and account // (keys >= 0x80000000 are hardened after bip32) diff --git a/src/init.cpp b/src/init.cpp index d1b32f4f6053..800307cf1dac 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -110,7 +110,7 @@ std::unique_ptr peerLogic; class DummyWalletInit : public WalletInitInterface { public: - void AddWalletOptions() const override {} + void AddWalletOptions() const override; bool ParameterInteraction() const override {return true;} void RegisterRPC(CRPCTable &) const override {} bool Verify() const override {return true;} @@ -127,6 +127,21 @@ class DummyWalletInit : public WalletInitInterface { bool InitAutoBackup() const override {return true;} }; +void DummyWalletInit::AddWalletOptions() const +{ + std::vector opts = {"-createwalletbackups=", "-disablewallet", "-instantsendnotify=", + "-keypool=", "-rescan=", "-salvagewallet", "-spendzeroconfchange", "-upgradewallet", + "-wallet=", "-walletbackupsdir=", "-walletbroadcast", "-walletdir=", + "-walletnotify=", "-zapwallettxes=", "-discardfee=", "-fallbackfee=", + "-mintxfee=", "-paytxfee=", "-txconfirmtarget=", "-hdseed=", "-mnemonic=", + "-mnemonicpassphrase=", "-usehd", "-keepass", "-keepassid=", "-keepasskey=", + "-keepassname=", "-keepassport=", "-enablecoinjoin", "-coinjoinamount=", + "-coinjoinautostart", "-coinjoindenomsgoal=", "-coinjoindenomshardcap=", "-coinjoinmultisession", + "-coinjoinrounds=", "-coinjoinsessions=", "-dblogsize=", "-flushwallet", "-privdb", + "-walletrejectlongchains"}; + gArgs.AddHiddenArgs(opts); +} + const WalletInitInterface& g_wallet_init_interface = DummyWalletInit(); #endif @@ -476,6 +491,12 @@ void SetupServerArgs() const auto regtestLLMQ = CreateChainParams(CBaseChainParams::REGTEST)->GetConsensus().llmqs.at(Consensus::LLMQ_TEST); + // Hidden Options + std::vector hidden_args = {"-rpcssl", "-benchmark", "-h", "-help", "-socks", "-tor", "-debugnet", "-whitelistalwaysrelay", + "-blockminsize", "-dbcrashratio", "-forcecompactdb", + // GUI args. These will be overwritten by SetupUIArgs for the GUI + "-allowselfsignedrootcertificates", "-choosedatadir", "-lang=", "-min", "-resetguisettings", "-rootcertificates=", "-splash", "-uiplatform"}; + // Set all of the args and their help // When adding new options to the categories, please keep and ensure alphabetical ordering. @@ -503,6 +524,8 @@ void SetupServerArgs() gArgs.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), false, OptionsCategory::OPTIONS); #ifndef WIN32 gArgs.AddArg("-pid=", strprintf("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), false, OptionsCategory::OPTIONS); +#else + hidden_args.emplace_back("-pid"); #endif gArgs.AddArg("-prune=", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex, -rescan and -disablegovernance=false. " "Warning: Reverting this setting requires re-downloading the entire blockchain. " @@ -510,6 +533,8 @@ void SetupServerArgs() gArgs.AddArg("-syncmempool", strprintf("Sync mempool from other nodes on start (default: %u)", DEFAULT_SYNC_MEMPOOL), false, OptionsCategory::OPTIONS); #ifndef WIN32 gArgs.AddArg("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)", false, OptionsCategory::OPTIONS); +#else + hidden_args.emplace_back("-sysperms"); #endif gArgs.AddArg("-version", "Print version and exit", false, OptionsCategory::OPTIONS); @@ -559,6 +584,8 @@ void SetupServerArgs() #else gArgs.AddArg("-upnp", strprintf("Use UPnP to map the listening port (default: %u)", 0), false, OptionsCategory::CONNECTION); #endif +#else + hidden_args.emplace_back("-upnp"); #endif gArgs.AddArg("-whitebind=", "Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6", false, OptionsCategory::CONNECTION); gArgs.AddArg("-whitelist=", "Whitelist peers connecting from the given IP address (e.g. 1.2.3.4) or CIDR notated network (e.g. 1.2.3.0/24). Can be specified multiple times." @@ -568,6 +595,7 @@ void SetupServerArgs() #if ENABLE_ZMQ gArgs.AddArg("-zmqpubhashblock=
", "Enable publish hash block in
", false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashchainlock=
", "Enable publish hash block (locked via ChainLocks) in
", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubhashgovernanceobject=
", "Enable publish hash of governance objects (like proposals) in
", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubhashgovernancevote=
", "Enable publish hash of governance votes in
", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubhashinstantsenddoublespend=
", "Enable publish transaction hashes of attempted InstantSend double spend in
", false, OptionsCategory::ZMQ); @@ -575,10 +603,34 @@ void SetupServerArgs() gArgs.AddArg("-zmqpubhashtx=
", "Enable publish hash transaction in
", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubhashtxlock=
", "Enable publish hash transaction (locked via InstantSend) in
", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubrawblock=
", "Enable publish raw block in
", false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawchainlock=
", "Enable publish raw block (locked via ChainLocks) in
", false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawchainlocksig=
", "Enable publish raw block (locked via ChainLocks) and CLSIG message in
", false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawgovernancevote=
", "Enable publish raw governance objects (like proposals) in
", false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawgovernanceobject=
", "Enable publish raw governance votes in
", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubrawinstantsenddoublespend=
", "Enable publish raw transactions of attempted InstantSend double spend in
", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubrawrecoveredsig=
", "Enable publish raw recovered signatures (recovered by LLMQs) in
", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubrawtx=
", "Enable publish raw transaction in
", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubrawtxlock=
", "Enable publish raw transaction (locked via InstantSend) in
", false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawtxlocksig=
", "Enable publish raw transaction (locked via InstantSend) and ISLOCK in
", false, OptionsCategory::ZMQ); +#else + hidden_args.emplace_back("-zmqpubhashblock=
"); + hidden_args.emplace_back("-zmqpubhashchainlock=
"); + hidden_args.emplace_back("-zmqpubhashgovernanceobject=
"); + hidden_args.emplace_back("-zmqpubhashgovernancevote=
"); + hidden_args.emplace_back("-zmqpubhashinstantsenddoublespend=
"); + hidden_args.emplace_back("-zmqpubhashrecoveredsig=
"); + hidden_args.emplace_back("-zmqpubhashtx=
"); + hidden_args.emplace_back("-zmqpubhashtxlock=
"); + hidden_args.emplace_back("-zmqpubrawblock=
"); + hidden_args.emplace_back("-zmqpubrawchainlock=
"); + hidden_args.emplace_back("-zmqpubrawchainlocksig=
"); + hidden_args.emplace_back("-zmqpubrawgovernancevote=
"); + hidden_args.emplace_back("-zmqpubrawgovernanceobject=
"); + hidden_args.emplace_back("-zmqpubrawinstantsenddoublespend=
"); + hidden_args.emplace_back("-zmqpubrawrecoveredsig=
"); + hidden_args.emplace_back("-zmqpubrawtx=
"); + hidden_args.emplace_back("-zmqpubrawtxlock=
"); + hidden_args.emplace_back("-zmqpubrawtxlocksig=
"); #endif gArgs.AddArg("-checkblockindex", strprintf("Do a full consistency check for mapBlockIndex, setBlockIndexCandidates, chainActive and mapBlocksUnlinked occasionally. (default: %u)", defaultChainParams->DefaultConsistencyChecks()), true, OptionsCategory::DEBUG_TEST); @@ -598,9 +650,12 @@ void SetupServerArgs() gArgs.AddArg("-watchquorums=", strprintf("Watch and validate quorum communication (default: %u)", llmq::DEFAULT_WATCH_QUORUMS), true, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-addrmantest", "Allows to test address relay on localhost", true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-budgetparams=::", "Override masternode, budget and superblock start heights", false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-debug=", strprintf("Output debugging information (default: %u, supplying is optional)", 0) + ". " + "If is not supplied or if = 1, output all debugging information. can be: " + ListLogCategories() + ".", false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-debugexclude=", strprintf("Exclude debugging information for a category. Can be used in conjunction with -debug=1 to output debug logs for all categories except one or more specified categories."), false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-dip3params=:", "Override DIP3 activation and enforcement heights", false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-dip8params=", "Override DIP8 activation height", false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-disablegovernance", strprintf("Disable governance validation (0-1, default: %u)", 0), false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-help-debug", "Show all debugging options (usage: --help -help-debug)", false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-highsubsidyblocks=", strprintf("The number of blocks with a higher than normal subsidy to mine at the start of a devnet (default: %u)", devnetConsensus.nHighSubsidyBlocks), false, OptionsCategory::DEBUG_TEST); @@ -622,6 +677,7 @@ void SetupServerArgs() gArgs.AddArg("-minsporkkeys=", "Overrides minimum spork signers to change spork value. Only useful for regtest and devnet. Using this on mainnet or testnet will ban you.", false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-printpriority", strprintf("Log transaction fee per kB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), true, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-printtoconsole", "Send trace/debug info to console instead of debug.log file", false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-pushversion", "Protocol version to report to other nodes", false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-shrinkdebugfile", "Shrink debug.log file on client startup (default: 1 when no -debug)", false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-sporkaddr=", "Override spork address. Only useful for regtest and devnet. Using this on mainnet or testnet will ban you.", false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-sporkkey=", "Set the private key to be used for signing spork messages.", false, OptionsCategory::DEBUG_TEST); @@ -668,6 +724,14 @@ void SetupServerArgs() gArgs.AddArg("-statsport=", strprintf("Specify statsd port (default: %u)", DEFAULT_STATSD_PORT), false, OptionsCategory::STATSD); gArgs.AddArg("-statsns=", strprintf("Specify additional namespace prefix (default: %s)", DEFAULT_STATSD_NAMESPACE), false, OptionsCategory::STATSD); gArgs.AddArg("-statsperiod=", strprintf("Specify the number of seconds between periodic measurements (default: %d)", DEFAULT_STATSD_PERIOD), false, OptionsCategory::STATSD); +#if HAVE_DECL_DAEMON + gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", false, OptionsCategory::OPTIONS); +#else + hidden_args.emplace_back("-daemon"); +#endif + + // Add the hidden options + gArgs.AddHiddenArgs(hidden_args); } std::string LicenseInfo() diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 99b68d6786af..7279a9a7b02f 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -160,11 +160,11 @@ class NodeImpl : public Node CoinJoinOptionsImpl m_coinjoin; #endif - void parseParameters(int argc, const char* const argv[]) override + bool parseParameters(int argc, const char* const argv[], std::string& error) override { - gArgs.ParseParameters(argc, argv); + return gArgs.ParseParameters(argc, argv, error); } - void readConfigFiles() override { gArgs.ReadConfigFiles(); } + bool readConfigFiles(std::string& error) override { return gArgs.ReadConfigFiles(error); } bool softSetArg(const std::string& arg, const std::string& value) override { return gArgs.SoftSetArg(arg, value); } bool softSetBoolArg(const std::string& arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); } void selectParams(const std::string& network) override { SelectParams(network); } diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 58223ca66cb1..f2a918be279d 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -93,7 +93,7 @@ class Node virtual ~Node() {} //! Set command line arguments. - virtual void parseParameters(int argc, const char* const argv[]) = 0; + virtual bool parseParameters(int argc, const char* const argv[], std::string& error) = 0; //! Set a command line argument if it doesn't already have a value virtual bool softSetArg(const std::string& arg, const std::string& value) = 0; @@ -102,7 +102,7 @@ class Node virtual bool softSetBoolArg(const std::string& arg, bool value) = 0; //! Load settings from configuration file. - virtual void readConfigFiles() = 0; + virtual bool readConfigFiles(std::string& error) = 0; //! Choose network parameters. virtual void selectParams(const std::string& network) = 0; diff --git a/src/key.cpp b/src/key.cpp index a75d56618d84..860f1ded8d76 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -273,7 +273,7 @@ bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const { return key.Derive(out.key, out.chaincode, _nChild, chaincode); } -void CExtKey::SetMaster(const unsigned char *seed, unsigned int nSeedLen) { +void CExtKey::SetSeed(const unsigned char *seed, unsigned int nSeedLen) { static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'}; std::vector> vout(64); CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(vout.data()); diff --git a/src/key.h b/src/key.h index 585363107bd4..92e31652da03 100644 --- a/src/key.h +++ b/src/key.h @@ -158,7 +158,7 @@ struct CExtKey { void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]); bool Derive(CExtKey& out, unsigned int nChild) const; CExtPubKey Neuter() const; - void SetMaster(const unsigned char* seed, unsigned int nSeedLen); + void SetSeed(const unsigned char* seed, unsigned int nSeedLen); template void Serialize(Stream& s) const { diff --git a/src/policy/policy.h b/src/policy/policy.h index 3a8764d4085a..012013934f4e 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -43,26 +43,26 @@ static const unsigned int DUST_RELAY_TX_FEE = 3000; * with. However scripts violating these flags may still be present in valid * blocks and we must accept those blocks. */ -static const unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY_FLAGS | - SCRIPT_VERIFY_DERSIG | - SCRIPT_VERIFY_STRICTENC | - SCRIPT_VERIFY_MINIMALDATA | - SCRIPT_VERIFY_NULLDUMMY | - SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS | - SCRIPT_VERIFY_CLEANSTACK | - SCRIPT_VERIFY_NULLFAIL | - SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | - SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | - SCRIPT_VERIFY_LOW_S | - SCRIPT_ENABLE_DIP0020_OPCODES | - SCRIPT_VERIFY_CONST_SCRIPTCODE; +static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY_FLAGS | + SCRIPT_VERIFY_DERSIG | + SCRIPT_VERIFY_STRICTENC | + SCRIPT_VERIFY_MINIMALDATA | + SCRIPT_VERIFY_NULLDUMMY | + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS | + SCRIPT_VERIFY_CLEANSTACK | + SCRIPT_VERIFY_NULLFAIL | + SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | + SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | + SCRIPT_VERIFY_LOW_S | + SCRIPT_ENABLE_DIP0020_OPCODES | + SCRIPT_VERIFY_CONST_SCRIPTCODE; /** For convenience, standard but not mandatory verify flags. */ -static const unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; +static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; /** Used as the flags parameter to sequence and nLocktime checks in non-consensus code. */ -static const unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE | - LOCKTIME_MEDIAN_TIME_PAST; +static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE | + LOCKTIME_MEDIAN_TIME_PAST; CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee); diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index a2c8fcd92721..03221d7d8822 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -91,9 +91,9 @@ uint256 CTransaction::ComputeHash() const } /* For backward compatibility, the hash is initialized to 0. TODO: remove the need for this default constructor entirely. */ -CTransaction::CTransaction() : vin(), vout(), nVersion(CTransaction::CURRENT_VERSION), nType(TRANSACTION_NORMAL), nLockTime(0), hash() {} -CTransaction::CTransaction(const CMutableTransaction &tx) : vin(tx.vin), vout(tx.vout), nVersion(tx.nVersion), nType(tx.nType), nLockTime(tx.nLockTime), vExtraPayload(tx.vExtraPayload), hash(ComputeHash()) {} -CTransaction::CTransaction(CMutableTransaction &&tx) : vin(std::move(tx.vin)), vout(std::move(tx.vout)), nVersion(tx.nVersion), nType(tx.nType), nLockTime(tx.nLockTime), vExtraPayload(tx.vExtraPayload), hash(ComputeHash()) {} +CTransaction::CTransaction() : vin(), vout(), nVersion(CTransaction::CURRENT_VERSION), nType(TRANSACTION_NORMAL), nLockTime(0), hash{} {} +CTransaction::CTransaction(const CMutableTransaction& tx) : vin(tx.vin), vout(tx.vout), nVersion(tx.nVersion), nType(tx.nType), nLockTime(tx.nLockTime), vExtraPayload(tx.vExtraPayload), hash{ComputeHash()} {} +CTransaction::CTransaction(CMutableTransaction&& tx) : vin(std::move(tx.vin)), vout(std::move(tx.vout)), nVersion(tx.nVersion), nType(tx.nType), nLockTime(tx.nLockTime), vExtraPayload(tx.vExtraPayload), hash{ComputeHash()} {} CAmount CTransaction::GetValueOut() const { diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 06744ddb7867..091c498a8e85 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -232,9 +232,7 @@ class CTransaction return vin.empty() && vout.empty(); } - const uint256& GetHash() const { - return hash; - } + const uint256& GetHash() const { return hash; } // Return sum of txouts. CAmount GetValueOut() const; @@ -277,7 +275,7 @@ struct CMutableTransaction std::vector vExtraPayload; // only available for special transaction types CMutableTransaction(); - CMutableTransaction(const CTransaction& tx); + explicit CMutableTransaction(const CTransaction& tx); SERIALIZE_METHODS(CMutableTransaction, obj) { @@ -303,17 +301,6 @@ struct CMutableTransaction uint256 GetHash() const; std::string ToString() const; - - friend bool operator==(const CMutableTransaction& a, const CMutableTransaction& b) - { - return a.GetHash() == b.GetHash(); - } - - friend bool operator!=(const CMutableTransaction& a, const CMutableTransaction& b) - { - return !(a == b); - } - }; typedef std::shared_ptr CTransactionRef; diff --git a/src/pubkey.h b/src/pubkey.h index 875ab76bf6de..cf61c4aabed7 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -33,10 +33,10 @@ class CPubKey /** * secp256k1: */ - static const unsigned int PUBLIC_KEY_SIZE = 65; - static const unsigned int COMPRESSED_PUBLIC_KEY_SIZE = 33; - static const unsigned int SIGNATURE_SIZE = 72; - static const unsigned int COMPACT_SIGNATURE_SIZE = 65; + static constexpr unsigned int PUBLIC_KEY_SIZE = 65; + static constexpr unsigned int COMPRESSED_PUBLIC_KEY_SIZE = 33; + static constexpr unsigned int SIGNATURE_SIZE = 72; + static constexpr unsigned int COMPACT_SIGNATURE_SIZE = 65; /** * see www.keylength.com * script supports up to 75 for single byte push diff --git a/src/qt/README.md b/src/qt/README.md index cba965b7a750..255d8400b1a9 100644 --- a/src/qt/README.md +++ b/src/qt/README.md @@ -4,7 +4,7 @@ The current precise version for Qt 5 is specified in [qt.mk](/depends/packages/q ## Compile and run -See build instructions ([OSX](/doc/build-osx.md), [Windows](/doc/build-windows.md), [Unix](/doc/build-unix.md), etc). +See build instructions ([macOS](/doc/build-osx.md), [Windows](/doc/build-windows.md), [Unix](/doc/build-unix.md), etc). To run: @@ -65,7 +65,7 @@ Represents the view to a single wallet. * `guiconstants.h`: UI colors, app name, etc * `guiutil.h`: several helper functions * `macdockiconhandler.(h/cpp)` -* `macdockiconhandler.(h/cpp)`: display notifications in OSX +* `macdockiconhandler.(h/cpp)`: display notifications in macOS ## Contribute @@ -81,9 +81,9 @@ the UI layout. Download and install the community edition of [Qt Creator](https://www.qt.io/download/). Uncheck everything except Qt Creator during the installation process. -Instructions for OSX: +Instructions for macOS: -1. Make sure you installed everything through Homebrew mentioned in the [OSX build instructions](/doc/build-osx.md) +1. Make sure you installed everything through Homebrew mentioned in the [macOS build instructions](/doc/build-osx.md) 2. Use `./configure` with the `--enable-debug` flag 3. In Qt Creator do "New Project" -> Import Project -> Import Existing Project 4. Enter "dash-qt" as project name, enter src/qt as location diff --git a/src/qt/dash.cpp b/src/qt/dash.cpp index 21efd607afb0..b1fc4c735df1 100644 --- a/src/qt/dash.cpp +++ b/src/qt/dash.cpp @@ -580,15 +580,9 @@ int main(int argc, char *argv[]) std::unique_ptr node = interfaces::MakeNode(); - /// 1. Parse command-line options. These take precedence over anything else. - // Command-line options take precedence: - node->setupServerArgs(); - SetupUIArgs(); - node->parseParameters(argc, argv); - // Do not refer to data directory yet, this can be overridden by Intro::pickDataDirectory - /// 2. Basic Qt initialization (not dependent on parameters or configuration) + /// 1. Basic Qt initialization (not dependent on parameters or configuration) Q_INIT_RESOURCE(dash); Q_INIT_RESOURCE(dash_locale); @@ -613,6 +607,17 @@ int main(int argc, char *argv[]) qRegisterMetaType("WalletModel*"); #endif + /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these + // Command-line options take precedence: + node->setupServerArgs(); + SetupUIArgs(); + std::string error; + if (!node->parseParameters(argc, argv, error)) { + QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), + QObject::tr("Error parsing command line arguments: %1.").arg(QString::fromStdString(error))); + return EXIT_FAILURE; + } + /// 3. Application identification // must be set before OptionsModel is initialized or translations are loaded, // as it is used to locate QSettings @@ -654,11 +659,9 @@ int main(int argc, char *argv[]) QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(gArgs.GetArg("-datadir", "")))); return EXIT_FAILURE; } - try { - node->readConfigFiles(); - } catch (const std::exception& e) { + if (!node->readConfigFiles(error)) { QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), - QObject::tr("Error: Cannot parse configuration file: %1. Only use key=value syntax.").arg(e.what())); + QObject::tr("Error: Cannot parse configuration file: %1.").arg(QString::fromStdString(error))); return EXIT_FAILURE; } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 05f1477754c4..1365ba236224 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -734,19 +734,17 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) if (coin.IsSpent()) { throw JSONRPCError(RPC_VERIFY_ERROR, "Input not found or already spent"); } - const CScript& prevPubKey = coin.out.scriptPubKey; - const CAmount& amount = coin.out.nValue; - SignatureData sigdata; // ... and merge in other signatures: for (const CMutableTransaction& txv : txVariants) { if (txv.vin.size() > i) { - sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i)); + sigdata.MergeSignatureData(DataFromTransaction(txv, i, coin.out)); } } + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&mergedTx, i, coin.out.nValue, 1), coin.out.scriptPubKey, sigdata); - UpdateTransaction(mergedTx, i, sigdata); + UpdateInput(txin, sigdata); } return EncodeHexTx(mergedTx); @@ -871,14 +869,13 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival const CScript& prevPubKey = coin.out.scriptPubKey; const CAmount& amount = coin.out.nValue; - SignatureData sigdata; + SignatureData sigdata = DataFromTransaction(mtx, i, coin.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mtx.vout.size())) { ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata); } - sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(mtx, i)); - UpdateTransaction(mtx, i, sigdata); + UpdateInput(txin, sigdata); ScriptError serror = SCRIPT_ERR_OK; if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) { diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 0f7b9e33034d..5e8a5ca9f862 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1032,7 +1032,7 @@ bool EvalScript(std::vector >& stack, const CScript& CSHA256() .Write(vchMessage.data(), vchMessage.size()) .Finalize(vchHash.data()); - fSuccess = checker.VerifySignature(vchSig, CPubKey(vchPubKey), uint256(vchHash)); + fSuccess = CPubKey(vchPubKey).Verify(uint256(vchHash), vchSig); } if (!fSuccess && (flags & SCRIPT_VERIFY_NULLFAIL) && vchSig.size()) { @@ -1308,9 +1308,11 @@ namespace { * Wrapper that serializes like CTransaction, but with the modifications * required for the signature hash done in-place */ -class CTransactionSignatureSerializer { +template +class CTransactionSignatureSerializer +{ private: - const CTransaction& txTo; //!< reference to the spending transaction (the one being serialized) + const T& txTo; //!< reference to the spending transaction (the one being serialized) const CScript& scriptCode; //!< output script being consumed const unsigned int nIn; //!< input index of txTo being signed const bool fAnyoneCanPay; //!< whether the hashtype has the SIGHASH_ANYONECANPAY flag set @@ -1318,7 +1320,7 @@ class CTransactionSignatureSerializer { const bool fHashNone; //!< whether the hashtype is SIGHASH_NONE public: - CTransactionSignatureSerializer(const CTransaction &txToIn, const CScript &scriptCodeIn, unsigned int nInIn, int nHashTypeIn) : + CTransactionSignatureSerializer(const T& txToIn, const CScript& scriptCodeIn, unsigned int nInIn, int nHashTypeIn) : txTo(txToIn), scriptCode(scriptCodeIn), nIn(nInIn), fAnyoneCanPay(!!(nHashTypeIn & SIGHASH_ANYONECANPAY)), fHashSingle((nHashTypeIn & 0x1f) == SIGHASH_SINGLE), @@ -1402,7 +1404,9 @@ class CTransactionSignatureSerializer { } }; -uint256 GetPrevoutHash(const CTransaction& txTo) { +template +uint256 GetPrevoutHash(const T& txTo) +{ CHashWriter ss(SER_GETHASH, 0); for (const auto& txin : txTo.vin) { ss << txin.prevout; @@ -1410,7 +1414,9 @@ uint256 GetPrevoutHash(const CTransaction& txTo) { return ss.GetHash(); } -uint256 GetSequenceHash(const CTransaction& txTo) { +template +uint256 GetSequenceHash(const T& txTo) +{ CHashWriter ss(SER_GETHASH, 0); for (const auto& txin : txTo.vin) { ss << txin.nSequence; @@ -1418,7 +1424,9 @@ uint256 GetSequenceHash(const CTransaction& txTo) { return ss.GetHash(); } -uint256 GetOutputsHash(const CTransaction& txTo) { +template +uint256 GetOutputsHash(const T& txTo) +{ CHashWriter ss(SER_GETHASH, 0); for (const auto& txout : txTo.vout) { ss << txout; @@ -1428,14 +1436,20 @@ uint256 GetOutputsHash(const CTransaction& txTo) { } // namespace -PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo) +template +PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo) { hashPrevouts = GetPrevoutHash(txTo); hashSequence = GetSequenceHash(txTo); hashOutputs = GetOutputsHash(txTo); } -uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache) +// explicit instantiation +template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); +template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); + +template +uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache) { assert(nIn < txTo.vin.size()); @@ -1450,7 +1464,7 @@ uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsig } // Wrapper to serialize only the necessary parts of the transaction being signed - CTransactionSignatureSerializer txTmp(txTo, scriptCode, nIn, nHashType); + CTransactionSignatureSerializer txTmp(txTo, scriptCode, nIn, nHashType); // Serialize and hash CHashWriter ss(SER_GETHASH, 0); @@ -1458,12 +1472,14 @@ uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsig return ss.GetHash(); } -bool BaseSignatureChecker::VerifySignature(const std::vector& vchSig, const CPubKey& pubkey, const uint256& sighash) const +template +bool GenericTransactionSignatureChecker::VerifySignature(const std::vector& vchSig, const CPubKey& pubkey, const uint256& sighash) const { return pubkey.Verify(sighash, vchSig); } -bool TransactionSignatureChecker::CheckSig(const std::vector& vchSigIn, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const +template +bool GenericTransactionSignatureChecker::CheckSig(const std::vector& vchSigIn, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const { CPubKey pubkey(vchPubKey); if (!pubkey.IsValid()) @@ -1484,7 +1500,8 @@ bool TransactionSignatureChecker::CheckSig(const std::vector& vch return true; } -bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const +template +bool GenericTransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const { // There are two kinds of nLockTime: lock-by-blockheight // and lock-by-blocktime, distinguished by whether @@ -1520,7 +1537,8 @@ bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) con return true; } -bool TransactionSignatureChecker::CheckSequence(const CScriptNum& nSequence) const +template +bool GenericTransactionSignatureChecker::CheckSequence(const CScriptNum& nSequence) const { // Relative lock times are supported by comparing the passed // in operand to the sequence number of the input. @@ -1566,6 +1584,10 @@ bool TransactionSignatureChecker::CheckSequence(const CScriptNum& nSequence) con return true; } +// explicit instantiation +template class GenericTransactionSignatureChecker; +template class GenericTransactionSignatureChecker; + bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror) { set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR); diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 631ffc834085..dcd98b9a569e 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -110,7 +110,8 @@ struct PrecomputedTransactionData { uint256 hashPrevouts, hashSequence, hashOutputs; - explicit PrecomputedTransactionData(const CTransaction& tx); + template + explicit PrecomputedTransactionData(const T& tx); }; enum class SigVersion @@ -118,13 +119,12 @@ enum class SigVersion BASE = 0, }; -uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr); +template +uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr); class BaseSignatureChecker { public: - virtual bool VerifySignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const; - virtual bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const { return false; @@ -143,33 +143,29 @@ class BaseSignatureChecker virtual ~BaseSignatureChecker() {} }; -class TransactionSignatureChecker : public BaseSignatureChecker +template +class GenericTransactionSignatureChecker : public BaseSignatureChecker { private: - const CTransaction* txTo; + const T* txTo; unsigned int nIn; const CAmount amount; const PrecomputedTransactionData* txdata; -public: - TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {} - TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} - - // The overriden functions are now final. - bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const final; - bool CheckLockTime(const CScriptNum& nLockTime) const final; - bool CheckSequence(const CScriptNum& nSequence) const final; -}; - -class MutableTransactionSignatureChecker : public TransactionSignatureChecker -{ -private: - const CTransaction txTo; +protected: + virtual bool VerifySignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const; public: - MutableTransactionSignatureChecker(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amount) : TransactionSignatureChecker(&txTo, nInIn, amount), txTo(*txToIn) {} + GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {} + GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} + bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; + bool CheckLockTime(const CScriptNum& nLockTime) const override; + bool CheckSequence(const CScriptNum& nSequence) const override; }; +using TransactionSignatureChecker = GenericTransactionSignatureChecker; +using MutableTransactionSignatureChecker = GenericTransactionSignatureChecker; + bool EvalScript(std::vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr); bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* error = nullptr); diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index 90ede11a7c7b..40bb3358c6f6 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -27,6 +27,19 @@ enum class IsMineSigVersion P2SH = 1, //! P2SH redeemScript }; +/** + * 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 (P2SH inside P2SH) +}; + bool PermitsUncompressed(IsMineSigVersion sigversion) { return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH; @@ -41,15 +54,13 @@ bool HaveKeys(const std::vector& pubkeys, const CKeyStore& keystore) return true; } -isminetype IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid, IsMineSigVersion sigversion) +IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion) { + IsMineResult ret = IsMineResult::NO; + std::vector vSolutions; txnouttype whichType; - if (!Solver(scriptPubKey, whichType, vSolutions)) { - if (keystore.HaveWatchOnly(scriptPubKey)) - return ISMINE_WATCH_UNSOLVABLE; - return ISMINE_NO; - } + Solver(scriptPubKey, whichType, vSolutions); CKeyID keyID; switch (whichType) @@ -60,39 +71,43 @@ isminetype IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, b case TX_PUBKEY: keyID = CPubKey(vSolutions[0]).GetID(); if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) { - isInvalid = true; - return ISMINE_NO; + return IsMineResult::INVALID; + } + if (keystore.HaveKey(keyID)) { + ret = std::max(ret, IsMineResult::SPENDABLE); } - if (keystore.HaveKey(keyID)) - return ISMINE_SPENDABLE; break; case TX_PUBKEYHASH: keyID = CKeyID(uint160(vSolutions[0])); if (!PermitsUncompressed(sigversion)) { CPubKey pubkey; if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) { - isInvalid = true; - return ISMINE_NO; + return IsMineResult::INVALID; } } - if (keystore.HaveKey(keyID)) - return ISMINE_SPENDABLE; + if (keystore.HaveKey(keyID)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } break; case TX_SCRIPTHASH: { + if (sigversion != IsMineSigVersion::TOP) { + // P2SH inside P2SH is invalid. + return IsMineResult::INVALID; + } CScriptID scriptID = CScriptID(uint160(vSolutions[0])); CScript subscript; if (keystore.GetCScript(scriptID, subscript)) { - isminetype ret = IsMineInner(keystore, subscript, isInvalid, IsMineSigVersion::P2SH); - if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid)) - return ret; + ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH)); } break; } case TX_MULTISIG: { // Never treat bare multisig outputs as ours (they can still be made watchonly-though) - if (sigversion == IsMineSigVersion::TOP) break; + if (sigversion == IsMineSigVersion::TOP) { + break; + } // Only consider transactions "mine" if we own ALL the // keys involved. Multi-signature transactions that are @@ -103,36 +118,37 @@ isminetype IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, b if (!PermitsUncompressed(sigversion)) { for (size_t i = 0; i < keys.size(); i++) { if (keys[i].size() != 33) { - isInvalid = true; - return ISMINE_NO; + return IsMineResult::INVALID; } } } - if (HaveKeys(keys, keystore)) - return ISMINE_SPENDABLE; + if (HaveKeys(keys, keystore)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } break; } } - if (keystore.HaveWatchOnly(scriptPubKey)) { - // TODO: This could be optimized some by doing some work after the above solver - SignatureData sigs; - return ProduceSignature(keystore, DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigs) ? ISMINE_WATCH_SOLVABLE : ISMINE_WATCH_UNSOLVABLE; + if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) { + ret = std::max(ret, IsMineResult::WATCH_ONLY); } - return ISMINE_NO; + return ret; } } // namespace -isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid) -{ - return IsMineInner(keystore, scriptPubKey, isInvalid, IsMineSigVersion::TOP); -} - isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) { - bool isInvalid = false; - return IsMine(keystore, scriptPubKey, isInvalid); + 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 CKeyStore& keystore, const CTxDestination& dest) diff --git a/src/script/ismine.h b/src/script/ismine.h index 37f4acae0318..c012cc292e3a 100644 --- a/src/script/ismine.h +++ b/src/script/ismine.h @@ -17,23 +17,13 @@ class CScript; enum isminetype { ISMINE_NO = 0, - //! Indicates that we don't know how to create a scriptSig that would solve this if we were given the appropriate private keys - ISMINE_WATCH_UNSOLVABLE = 1, - //! Indicates that we know how to create a scriptSig that would solve this if we were given the appropriate private keys - ISMINE_WATCH_SOLVABLE = 2, - ISMINE_WATCH_ONLY = ISMINE_WATCH_SOLVABLE | ISMINE_WATCH_UNSOLVABLE, - ISMINE_SPENDABLE = 4, + ISMINE_WATCH_ONLY = 1, + ISMINE_SPENDABLE = 2, ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE }; /** used for bitflags of isminetype */ typedef uint8_t isminefilter; -/* isInvalid becomes true when the script is found invalid by consensus or policy. This will terminate the recursion - * and return a ISMINE_NO immediately, as an invalid script should never be considered as "mine". This is needed as - * different SIGVERSION may have different network rules. Currently there is no use of isInvalid but it could be - * used in the future. See https://github.com/bitcoin/bitcoin/pull/8499 (segwit policy limits) as an example. - */ -isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid); isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest); diff --git a/src/script/script.cpp b/src/script/script.cpp index f83a5c9e27a2..78bc07a6f332 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -145,11 +145,6 @@ const char* GetOpName(opcodetype opcode) case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; - // Note: - // The template matching params OP_SMALLINTEGER/etc are defined in opcodetype enum - // as kind of implementation hack, they are *NOT* real opcodes. If found in real - // Script, just let the default: case deal with them. - default: return "OP_UNKNOWN"; } diff --git a/src/script/script.h b/src/script/script.h index 4a2a506f56e1..05456538afb0 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -187,12 +187,6 @@ enum opcodetype OP_CHECKDATASIG = 0xba, OP_CHECKDATASIGVERIFY = 0xbb, - // template matching params - OP_SMALLINTEGER = 0xfa, - OP_PUBKEYS = 0xfb, - OP_PUBKEYHASH = 0xfd, - OP_PUBKEY = 0xfe, - OP_INVALIDOPCODE = 0xff, }; diff --git a/src/script/sign.cpp b/src/script/sign.cpp index a071171a0a82..4231250bd0eb 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -14,9 +14,9 @@ typedef std::vector valtype; -TransactionSignatureCreator::TransactionSignatureCreator(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {} +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {} -bool TransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const +bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const { CKey key; if (!provider.GetKey(address, key)) @@ -29,27 +29,48 @@ bool TransactionSignatureCreator::CreateSig(const SigningProvider& provider, std return true; } -static bool Sign1(const SigningProvider& provider, const CKeyID& address, const BaseSignatureCreator& creator, const CScript& scriptCode, std::vector& ret, SigVersion sigversion) +static bool GetCScript(const SigningProvider& provider, const SignatureData& sigdata, const CScriptID& scriptid, CScript& script) { - std::vector vchSig; - if (!creator.CreateSig(provider, vchSig, address, scriptCode, sigversion)) - return false; - ret.push_back(vchSig); - return true; + if (provider.GetCScript(scriptid, script)) { + return true; + } + // Look for scripts in SignatureData + if (CScriptID(sigdata.redeem_script) == scriptid) { + script = sigdata.redeem_script; + return true; + } + return false; } -static bool SignN(const SigningProvider& provider, const std::vector& multisigdata, const BaseSignatureCreator& creator, const CScript& scriptCode, std::vector& ret, SigVersion sigversion) +static bool GetPubKey(const SigningProvider& provider, const SignatureData& sigdata, const CKeyID& address, CPubKey& pubkey) { - int nSigned = 0; - int nRequired = multisigdata.front()[0]; - for (unsigned int i = 1; i < multisigdata.size()-1 && nSigned < nRequired; i++) - { - const valtype& pubkey = multisigdata[i]; - CKeyID keyID = CPubKey(pubkey).GetID(); - if (Sign1(provider, keyID, creator, scriptCode, ret, sigversion)) - ++nSigned; + if (provider.GetPubKey(address, pubkey)) { + return true; } - return nSigned==nRequired; + // Look for pubkey in all partial sigs + const auto it = sigdata.signatures.find(address); + if (it != sigdata.signatures.end()) { + pubkey = it->second.first; + return true; + } + return false; +} + +static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector& sig_out, const CKeyID& keyid, const CScript& scriptcode, SigVersion sigversion) +{ + const auto it = sigdata.signatures.find(keyid); + if (it != sigdata.signatures.end()) { + sig_out = it->second.second; + return true; + } + if (creator.CreateSig(provider, sig_out, keyid, scriptcode, sigversion)) { + CPubKey pubkey; + GetPubKey(provider, sigdata, keyid, pubkey); + auto i = sigdata.signatures.emplace(keyid, SigPair(pubkey, sig_out)); + assert(i.second); + return true; + } + return false; } /** @@ -59,46 +80,57 @@ static bool SignN(const SigningProvider& provider, const std::vector& m * Returns false if scriptPubKey could not be completely satisfied. */ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& scriptPubKey, - std::vector& ret, txnouttype& whichTypeRet, SigVersion sigversion) + std::vector& ret, txnouttype& whichTypeRet, SigVersion sigversion, SignatureData& sigdata) { CScript scriptRet; uint160 h160; ret.clear(); + std::vector sig; std::vector vSolutions; if (!Solver(scriptPubKey, whichTypeRet, vSolutions)) return false; - CKeyID keyID; switch (whichTypeRet) { case TX_NONSTANDARD: case TX_NULL_DATA: return false; case TX_PUBKEY: - keyID = CPubKey(vSolutions[0]).GetID(); - return Sign1(provider, keyID, creator, scriptPubKey, ret, sigversion); - case TX_PUBKEYHASH: - keyID = CKeyID(uint160(vSolutions[0])); - if (!Sign1(provider, keyID, creator, scriptPubKey, ret, sigversion)) - return false; - else - { - CPubKey vch; - provider.GetPubKey(keyID, vch); - ret.push_back(ToByteVector(vch)); - } + if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]).GetID(), scriptPubKey, sigversion)) return false; + ret.push_back(std::move(sig)); return true; + case TX_PUBKEYHASH: { + CKeyID keyID = CKeyID(uint160(vSolutions[0])); + if (!CreateSig(creator, sigdata, provider, sig, keyID, scriptPubKey, sigversion)) return false; + ret.push_back(std::move(sig)); + CPubKey pubkey; + GetPubKey(provider, sigdata, keyID, pubkey); + ret.push_back(ToByteVector(pubkey)); + return true; + } case TX_SCRIPTHASH: - if (provider.GetCScript(uint160(vSolutions[0]), scriptRet)) { + if (GetCScript(provider, sigdata, uint160(vSolutions[0]), scriptRet)) { ret.push_back(std::vector(scriptRet.begin(), scriptRet.end())); return true; } return false; - case TX_MULTISIG: + case TX_MULTISIG: { + size_t required = vSolutions.front()[0]; ret.push_back(valtype()); // workaround CHECKMULTISIG bug - return (SignN(provider, vSolutions, creator, scriptPubKey, ret, sigversion)); + for (size_t i = 1; i < vSolutions.size() - 1; ++i) { + CPubKey pubkey = CPubKey(vSolutions[i]); + if (ret.size() < required + 1 && CreateSig(creator, sigdata, provider, sig, pubkey.GetID(), scriptPubKey, sigversion)) { + ret.push_back(std::move(sig)); + } + } + bool ok = ret.size() == required + 1; + for (size_t i = 0; i + ret.size() < required + 1; ++i) { + ret.push_back(valtype()); + } + return ok; + } default: return false; @@ -122,9 +154,11 @@ static CScript PushAll(const std::vector& values) bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata) { + if (sigdata.complete) return true; + std::vector result; txnouttype whichType; - bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE); + bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata); bool P2SH = false; CScript subscript; @@ -134,7 +168,8 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato // the final scriptSig is the signatures from that // and then the serialized subscript: subscript = CScript(result[0].begin(), result[0].end()); - solved = solved && SignStep(provider, creator, subscript, result, whichType, SigVersion::BASE) && whichType != TX_SCRIPTHASH; + sigdata.redeem_script = subscript; + solved = solved && SignStep(provider, creator, subscript, result, whichType, SigVersion::BASE, sigdata) && whichType != TX_SCRIPTHASH; P2SH = true; } @@ -144,104 +179,29 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato sigdata.scriptSig = PushAll(result); // Test solution - return solved && VerifyScript(sigdata.scriptSig, fromPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker()); + sigdata.complete = solved && VerifyScript(sigdata.scriptSig, fromPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker()); + return sigdata.complete; } -SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn) +class SignatureExtractorChecker final : public BaseSignatureChecker { - SignatureData data; - assert(tx.vin.size() > nIn); - data.scriptSig = tx.vin[nIn].scriptSig; - return data; -} +private: + SignatureData& sigdata; + BaseSignatureChecker& checker; -void UpdateInput(CTxIn& input, const SignatureData& data) -{ - input.scriptSig = data.scriptSig; -} - -void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data) -{ - assert(tx.vin.size() > nIn); - UpdateInput(tx.vin[nIn], data); -} - -bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType) -{ - assert(nIn < txTo.vin.size()); - - CTransaction txToConst(txTo); - TransactionSignatureCreator creator(&txToConst, nIn, amount, nHashType); - - SignatureData sigdata; - bool ret = ProduceSignature(provider, creator, fromPubKey, sigdata); - UpdateTransaction(txTo, nIn, sigdata); - return ret; -} - -bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType) -{ - assert(nIn < txTo.vin.size()); - CTxIn& txin = txTo.vin[nIn]; - assert(txin.prevout.n < txFrom.vout.size()); - const CTxOut& txout = txFrom.vout[txin.prevout.n]; - - return SignSignature(provider, txout.scriptPubKey, txTo, nIn, txout.nValue, nHashType); -} +public: + SignatureExtractorChecker(SignatureData& sigdata, BaseSignatureChecker& checker) : sigdata(sigdata), checker(checker) {} + bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; +}; -static std::vector CombineMultisig(const CScript& scriptPubKey, const BaseSignatureChecker& checker, - const std::vector& vSolutions, - const std::vector& sigs1, const std::vector& sigs2, SigVersion sigversion) +bool SignatureExtractorChecker::CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const { - // Combine all the signatures we've got: - std::set allsigs; - for (const valtype& v : sigs1) - { - if (!v.empty()) - allsigs.insert(v); - } - for (const valtype& v : sigs2) - { - if (!v.empty()) - allsigs.insert(v); - } - - // Build a map of pubkey -> signature by matching sigs to pubkeys: - assert(vSolutions.size() > 1); - unsigned int nSigsRequired = vSolutions.front()[0]; - unsigned int nPubKeys = vSolutions.size()-2; - std::map sigs; - for (const valtype& sig : allsigs) - { - for (unsigned int i = 0; i < nPubKeys; i++) - { - const valtype& pubkey = vSolutions[i+1]; - if (sigs.count(pubkey)) - continue; // Already got a sig for this pubkey - - if (checker.CheckSig(sig, pubkey, scriptPubKey, sigversion)) - { - sigs[pubkey] = sig; - break; - } - } - } - // Now build a merged CScript: - unsigned int nSigsHave = 0; - std::vector result; result.push_back(valtype()); // pop-one-too-many workaround - for (unsigned int i = 0; i < nPubKeys && nSigsHave < nSigsRequired; i++) - { - if (sigs.count(vSolutions[i+1])) - { - result.push_back(sigs[vSolutions[i+1]]); - ++nSigsHave; - } + if (checker.CheckSig(scriptSig, vchPubKey, scriptCode, sigversion)) { + CPubKey pubkey(vchPubKey); + sigdata.signatures.emplace(pubkey.GetID(), SigPair(pubkey, scriptSig)); + return true; } - // Fill any missing with OP_0: - for (unsigned int i = nSigsHave; i < nSigsRequired; i++) - result.push_back(valtype()); - - return result; + return false; } namespace @@ -264,59 +224,98 @@ struct Stacks }; } -static Stacks CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecker& checker, - const txnouttype txType, const std::vector& vSolutions, - Stacks sigs1, Stacks sigs2, SigVersion sigversion) +// Extracts signatures and scripts from incomplete scriptSigs. Please do not extend this, use PSBT instead +SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout) { - switch (txType) - { - case TX_NONSTANDARD: - case TX_NULL_DATA: - // Don't know anything about this, assume bigger one is correct: - if (sigs1.script.size() >= sigs2.script.size()) - return sigs1; - return sigs2; - case TX_PUBKEY: - case TX_PUBKEYHASH: - // Signatures are bigger than placeholders or empty scripts: - if (sigs1.script.empty() || sigs1.script[0].empty()) - return sigs2; - return sigs1; - case TX_SCRIPTHASH: - if (sigs1.script.empty() || sigs1.script.back().empty()) - return sigs2; - else if (sigs2.script.empty() || sigs2.script.back().empty()) - return sigs1; - else - { - // Recur to combine: - valtype spk = sigs1.script.back(); - CScript pubKey2(spk.begin(), spk.end()); - - txnouttype txType2; - std::vector > vSolutions2; - Solver(pubKey2, txType2, vSolutions2); - sigs1.script.pop_back(); - sigs2.script.pop_back(); - Stacks result = CombineSignatures(pubKey2, checker, txType2, vSolutions2, sigs1, sigs2, sigversion); - result.script.push_back(spk); - return result; + SignatureData data; + assert(tx.vin.size() > nIn); + data.scriptSig = tx.vin[nIn].scriptSig; + Stacks stack(data); + + // Get signatures + MutableTransactionSignatureChecker tx_checker(&tx, nIn, txout.nValue); + SignatureExtractorChecker extractor_checker(data, tx_checker); + if (VerifyScript(data.scriptSig, txout.scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, extractor_checker)) { + data.complete = true; + return data; + } + + // Get scripts + txnouttype script_type; + std::vector> solutions; + Solver(txout.scriptPubKey, script_type, solutions); + SigVersion sigversion = SigVersion::BASE; + CScript next_script = txout.scriptPubKey; + + if (script_type == TX_SCRIPTHASH && !stack.script.empty() && !stack.script.back().empty()) { + // Get the redeemScript + CScript redeem_script(stack.script.back().begin(), stack.script.back().end()); + data.redeem_script = redeem_script; + next_script = std::move(redeem_script); + + // Get redeemScript type + Solver(next_script, script_type, solutions); + stack.script.pop_back(); + } + + if (script_type == TX_MULTISIG && !stack.script.empty()) { + // Build a map of pubkey -> signature by matching sigs to pubkeys: + assert(solutions.size() > 1); + unsigned int num_pubkeys = solutions.size()-2; + unsigned int last_success_key = 0; + for (const valtype& sig : stack.script) { + for (unsigned int i = last_success_key; i < num_pubkeys; ++i) { + const valtype& pubkey = solutions[i+1]; + // We either have a signature for this pubkey, or we have found a signature and it is valid + if (data.signatures.count(CPubKey(pubkey).GetID()) || extractor_checker.CheckSig(sig, pubkey, next_script, sigversion)) { + last_success_key = i + 1; + break; + } + } } - case TX_MULTISIG: - return Stacks(CombineMultisig(scriptPubKey, checker, vSolutions, sigs1.script, sigs2.script, sigversion)); - default: - return Stacks(); } + + return data; +} + +void UpdateInput(CTxIn& input, const SignatureData& data) +{ + input.scriptSig = data.scriptSig; +} + +void SignatureData::MergeSignatureData(SignatureData sigdata) +{ + if (complete) return; + if (sigdata.complete) { + *this = std::move(sigdata); + return; + } + if (redeem_script.empty() && !sigdata.redeem_script.empty()) { + redeem_script = sigdata.redeem_script; + } + signatures.insert(std::make_move_iterator(sigdata.signatures.begin()), std::make_move_iterator(sigdata.signatures.end())); +} + +bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType) +{ + assert(nIn < txTo.vin.size()); + + MutableTransactionSignatureCreator creator(&txTo, nIn, amount, nHashType); + + SignatureData sigdata; + bool ret = ProduceSignature(provider, creator, fromPubKey, sigdata); + UpdateInput(txTo.vin.at(nIn), sigdata); + return ret; } -SignatureData CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecker& checker, - const SignatureData& scriptSig1, const SignatureData& scriptSig2) +bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType) { - txnouttype txType; - std::vector > vSolutions; - Solver(scriptPubKey, txType, vSolutions); + assert(nIn < txTo.vin.size()); + CTxIn& txin = txTo.vin[nIn]; + assert(txin.prevout.n < txFrom.vout.size()); + const CTxOut& txout = txFrom.vout[txin.prevout.n]; - return CombineSignatures(scriptPubKey, checker, txType, vSolutions, Stacks(scriptSig1), Stacks(scriptSig2), SigVersion::BASE).Output(); + return SignSignature(provider, txout.scriptPubKey, txTo, nIn, txout.nValue, nHashType); } namespace { @@ -352,3 +351,18 @@ class DummySignatureCreator final : public BaseSignatureCreator { } const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR = DummySignatureCreator(); +const SigningProvider& DUMMY_SIGNING_PROVIDER = SigningProvider(); + +bool IsSolvable(const SigningProvider& provider, const CScript& script) +{ + // This check is to make sure that the script we created can actually be solved for and signed by us + // if we were to have the private keys. This is just to make sure that the script is valid and that, + // if found in a transaction, we would still accept and relay that transaction. + SignatureData sigs; + if (ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, script, sigs)) { + // VerifyScript check is just defensive, and should never fail. + assert(VerifyScript(sigs.scriptSig, script, STANDARD_SCRIPT_VERIFY_FLAGS, DUMMY_CHECKER)); + return true; + } + return false; +} diff --git a/src/script/sign.h b/src/script/sign.h index 53c25e336dca..fa6c560b7fd0 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -21,11 +21,13 @@ class SigningProvider { public: virtual ~SigningProvider() {} - virtual bool GetCScript(const CScriptID &scriptid, CScript& script) const =0; - virtual bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const =0; - virtual bool GetKey(const CKeyID &address, CKey& key) const =0; + virtual bool GetCScript(const CScriptID &scriptid, CScript& script) const { return false; } + virtual bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const { return false; } + virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; } }; +extern const SigningProvider& DUMMY_SIGNING_PROVIDER; + /** Interface for signature creators. */ class BaseSignatureCreator { public: @@ -37,34 +39,36 @@ class BaseSignatureCreator { }; /** A signature creator for transactions. */ -class TransactionSignatureCreator : public BaseSignatureCreator { - const CTransaction* txTo; +class MutableTransactionSignatureCreator : public BaseSignatureCreator { + const CMutableTransaction* txTo; unsigned int nIn; int nHashType; CAmount amount; - const TransactionSignatureChecker checker; + const MutableTransactionSignatureChecker checker; public: - TransactionSignatureCreator(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn=SIGHASH_ALL); + MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn = SIGHASH_ALL); const BaseSignatureChecker& Checker() const override{ return checker; } bool CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override; }; -class MutableTransactionSignatureCreator : public TransactionSignatureCreator { - CTransaction tx; - -public: - MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amount, int nHashTypeIn) : TransactionSignatureCreator(&tx, nInIn, amount, nHashTypeIn), tx(*txToIn) {} -}; - /** A signature creator that just produces 72-byte empty signatures. */ extern const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR; +typedef std::pair> SigPair; + +// This struct contains information from a transaction input and also contains signatures for that input. +// The information contained here can be used to create a signature and is also filled by ProduceSignature +// in order to construct final scriptSigs and scriptWitnesses. struct SignatureData { - CScript scriptSig; + bool complete = false; ///< Stores whether the scriptSig is complete + CScript scriptSig; ///< The scriptSig of an input. Contains complete signatures or the traditional partial signatures format + CScript redeem_script; ///< The redeemScript (if any) for the input + std::map signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig. SignatureData() {} explicit SignatureData(const CScript& script) : scriptSig(script) {} + void MergeSignatureData(SignatureData sigdata); }; /** Produce a script signature using a generic signature creator. */ @@ -74,12 +78,14 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType); bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType); -/** Combine two script signatures using a generic signature checker, intelligently, possibly with OP_0 placeholders. */ -SignatureData CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecker& checker, const SignatureData& scriptSig1, const SignatureData& scriptSig2); - -/** Extract signature data from a transaction, and insert it. */ -SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn); -void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data); +/** Extract signature data from a transaction input, and insert it. */ +SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout); void UpdateInput(CTxIn& input, const SignatureData& data); +/* Check whether we know how to sign for an output like this, assuming we + * have all private keys. While this function does not need private keys, the passed + * provider is used to look up public keys and redeemscripts by hash. + * Solvability is unrelated to whether we consider this output to be ours. */ +bool IsSolvable(const SigningProvider& provider, const CScript& script); + #endif // BITCOIN_SCRIPT_SIGN_H diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 07c1a10bb6ae..6a3202d37606 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -32,22 +32,54 @@ const char* GetTxnOutputType(txnouttype t) return nullptr; } -bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector >& vSolutionsRet) +static bool MatchPayToPubkey(const CScript& script, valtype& pubkey) { - // Templates - static std::multimap mTemplates; - if (mTemplates.empty()) - { - // Standard tx, sender provides pubkey, receiver adds signature - mTemplates.insert(std::make_pair(TX_PUBKEY, CScript() << OP_PUBKEY << OP_CHECKSIG)); + if (script.size() == CPubKey::PUBLIC_KEY_SIZE + 2 && script[0] == CPubKey::PUBLIC_KEY_SIZE && script.back() == OP_CHECKSIG) { + pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::PUBLIC_KEY_SIZE + 1); + return CPubKey::ValidSize(pubkey); + } + if (script.size() == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 2 && script[0] == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE && script.back() == OP_CHECKSIG) { + pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 1); + return CPubKey::ValidSize(pubkey); + } + return false; +} - // Bitcoin address tx, sender provides hash of pubkey, receiver provides signature and pubkey - mTemplates.insert(std::make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG)); +static bool MatchPayToPubkeyHash(const CScript& script, valtype& pubkeyhash) +{ + if (script.size() == 25 && script[0] == OP_DUP && script[1] == OP_HASH160 && script[2] == 20 && script[23] == OP_EQUALVERIFY && script[24] == OP_CHECKSIG) { + pubkeyhash = valtype(script.begin () + 3, script.begin() + 23); + return true; + } + return false; +} + +/** Test for "small positive integer" script opcodes - OP_1 through OP_16. */ +static constexpr bool IsSmallInteger(opcodetype opcode) +{ + return opcode >= OP_1 && opcode <= OP_16; +} - // Sender provides N pubkeys, receivers provides M signatures - mTemplates.insert(std::make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG)); +static bool MatchMultisig(const CScript& script, unsigned int& required, std::vector& pubkeys) +{ + opcodetype opcode; + valtype data; + CScript::const_iterator it = script.begin(); + if (script.size() < 1 || script.back() != OP_CHECKMULTISIG) return false; + + if (!script.GetOp(it, opcode, data) || !IsSmallInteger(opcode)) return false; + required = CScript::DecodeOP_N(opcode); + while (script.GetOp(it, opcode, data) && CPubKey::ValidSize(data)) { + pubkeys.emplace_back(std::move(data)); } + if (!IsSmallInteger(opcode)) return false; + unsigned int keys = CScript::DecodeOP_N(opcode); + if (pubkeys.size() != keys || keys < required) return false; + return (it + 1 == script.end()); +} +bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector >& vSolutionsRet) +{ vSolutionsRet.clear(); // Shortcut for pay-to-script-hash, which are more constrained than the other types: @@ -70,84 +102,27 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& tplate : mTemplates) - { - const CScript& script2 = tplate.second; - vSolutionsRet.clear(); + std::vector data; + if (MatchPayToPubkey(scriptPubKey, data)) { + typeRet = TX_PUBKEY; + vSolutionsRet.push_back(std::move(data)); + return true; + } - opcodetype opcode1, opcode2; - std::vector vch1, vch2; + if (MatchPayToPubkeyHash(scriptPubKey, data)) { + typeRet = TX_PUBKEYHASH; + vSolutionsRet.push_back(std::move(data)); + return true; + } - // Compare - CScript::const_iterator pc1 = script1.begin(); - CScript::const_iterator pc2 = script2.begin(); - while (true) - { - if (pc1 == script1.end() && pc2 == script2.end()) - { - // Found a match - typeRet = tplate.first; - if (typeRet == TX_MULTISIG) - { - // Additional checks for TX_MULTISIG: - unsigned char m = vSolutionsRet.front()[0]; - unsigned char n = vSolutionsRet.back()[0]; - if (m < 1 || n < 1 || m > n || vSolutionsRet.size()-2 != n) - return false; - } - return true; - } - if (!script1.GetOp(pc1, opcode1, vch1)) - break; - if (!script2.GetOp(pc2, opcode2, vch2)) - break; - - // Template matching opcodes: - if (opcode2 == OP_PUBKEYS) - { - while (CPubKey::ValidSize(vch1)) - { - vSolutionsRet.push_back(vch1); - if (!script1.GetOp(pc1, opcode1, vch1)) - break; - } - if (!script2.GetOp(pc2, opcode2, vch2)) - break; - // Normal situation is to fall through - // to other if/else statements - } - - if (opcode2 == OP_PUBKEY) - { - if (!CPubKey::ValidSize(vch1)) - break; - vSolutionsRet.push_back(vch1); - } - else if (opcode2 == OP_PUBKEYHASH) - { - if (vch1.size() != sizeof(uint160)) - break; - vSolutionsRet.push_back(vch1); - } - else if (opcode2 == OP_SMALLINTEGER) - { // Single-byte small integer pushed onto vSolutions - if (opcode1 == OP_0 || - (opcode1 >= OP_1 && opcode1 <= OP_16)) - { - char n = (char)CScript::DecodeOP_N(opcode1); - vSolutionsRet.push_back(valtype(1, n)); - } - else - break; - } - else if (opcode1 != opcode2 || vch1 != vch2) - { - // Others must match exactly - break; - } - } + unsigned int required; + std::vector> keys; + if (MatchMultisig(scriptPubKey, required, keys)) { + typeRet = TX_MULTISIG; + vSolutionsRet.push_back({static_cast(required)}); // safe as required is in range 1..16 + vSolutionsRet.insert(vSolutionsRet.end(), keys.begin(), keys.end()); + vSolutionsRet.push_back({static_cast(keys.size())}); // safe as size is in range 1..16 + return true; } vSolutionsRet.clear(); diff --git a/src/test/arith_uint256_tests.cpp b/src/test/arith_uint256_tests.cpp index 302181fae08a..621e782fdf5e 100644 --- a/src/test/arith_uint256_tests.cpp +++ b/src/test/arith_uint256_tests.cpp @@ -198,13 +198,6 @@ BOOST_AUTO_TEST_CASE( shifts ) { // "<<" ">>" "<<=" ">>=" BOOST_AUTO_TEST_CASE( unaryOperators ) // ! ~ - { - BOOST_CHECK(!ZeroL); - BOOST_CHECK(!(!OneL)); - for (unsigned int i = 0; i < 256; ++i) - BOOST_CHECK(!(!(OneL< seed = ParseHex(test.strHexMaster); CExtKey key; CExtPubKey pubkey; - key.SetMaster(seed.data(), seed.size()); + key.SetSeed(seed.data(), seed.size()); pubkey = key.Neuter(); for (const TestDerivation &derive : test.vDerive) { unsigned char data[74]; diff --git a/src/test/bip39_tests.cpp b/src/test/bip39_tests.cpp index e5ffc6242fe4..cefccc67798f 100644 --- a/src/test/bip39_tests.cpp +++ b/src/test/bip39_tests.cpp @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(bip39_vectors) CExtKey key; CExtPubKey pubkey; - key.SetMaster(seed.data(), 64); + key.SetSeed(seed.data(), 64); pubkey = key.Neuter(); // printf("CBitcoinExtKey: %s\n", EncodeExtKey(key).c_str()); diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 7ded9d4fabf7..2b23a62b96e5 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -312,7 +312,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) if (InsecureRandRange(10) == 0 && coinbase_coins.size()) { auto utxod = FindRandomFrom(coinbase_coins); // Reuse the exact same coinbase - tx = std::get<0>(utxod->second); + tx = CMutableTransaction{std::get<0>(utxod->second)}; // shouldn't be available for reconnection if it's been duplicated disconnected_coins.erase(utxod->first); @@ -331,7 +331,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) // 1/20 times reconnect a previously disconnected tx if (randiter % 20 == 2 && disconnected_coins.size()) { auto utxod = FindRandomFrom(disconnected_coins); - tx = std::get<0>(utxod->second); + tx = CMutableTransaction{std::get<0>(utxod->second)}; prevout = tx.vin[0].prevout; if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) { disconnected_coins.erase(utxod->first); diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index eec164c50083..ce76a35b624e 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -27,11 +27,21 @@ static void ResetArgs(const std::string& strArg) for (std::string& s : vecArg) vecChar.push_back(s.c_str()); - gArgs.ParseParameters(vecChar.size(), vecChar.data()); + std::string error; + gArgs.ParseParameters(vecChar.size(), vecChar.data(), error); +} + +static void SetupArgs(const std::vector& args) +{ + gArgs.ClearArgs(); + for (const std::string& arg : args) { + gArgs.AddArg(arg, "", false, OptionsCategory::OPTIONS); + } } BOOST_AUTO_TEST_CASE(boolarg) { + SetupArgs({"-foo"}); ResetArgs("-foo"); BOOST_CHECK(gArgs.GetBoolArg("-foo", false)); BOOST_CHECK(gArgs.GetBoolArg("-foo", true)); @@ -84,6 +94,7 @@ BOOST_AUTO_TEST_CASE(boolarg) BOOST_AUTO_TEST_CASE(stringarg) { + SetupArgs({"-foo", "-bar"}); ResetArgs(""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), ""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", "eleven"), "eleven"); @@ -108,6 +119,7 @@ BOOST_AUTO_TEST_CASE(stringarg) BOOST_AUTO_TEST_CASE(intarg) { + SetupArgs({"-foo", "-bar"}); ResetArgs(""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 11), 11); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 0), 0); @@ -127,6 +139,7 @@ BOOST_AUTO_TEST_CASE(intarg) BOOST_AUTO_TEST_CASE(doubledash) { + SetupArgs({"-foo", "-bar"}); ResetArgs("--foo"); BOOST_CHECK_EQUAL(gArgs.GetBoolArg("-foo", false), true); @@ -137,6 +150,7 @@ BOOST_AUTO_TEST_CASE(doubledash) BOOST_AUTO_TEST_CASE(boolargno) { + SetupArgs({"-foo", "-bar"}); ResetArgs("-nofoo"); BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index e927de17ae8d..95ef91dfa455 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -588,9 +588,6 @@ inline CTransactionRef make_tx(std::vector&& output_values, std::vector return MakeTransactionRef(tx); } -#define MK_OUTPUTS(amounts...) std::vector{amounts} -#define MK_INPUTS(txs...) std::vector{txs} -#define MK_INPUT_IDX(idxes...) std::vector{idxes} BOOST_AUTO_TEST_CASE(MempoolAncestryTests) { @@ -604,8 +601,8 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) // // [tx1] // - CTransactionRef tx1 = make_tx(MK_OUTPUTS(10 * COIN)); - pool.addUnchecked(tx1->GetHash(), entry.Fee(10000LL).FromTx(*tx1)); + CTransactionRef tx1 = make_tx(/* output_values */ {10 * COIN}); + pool.addUnchecked(tx1->GetHash(), entry.Fee(10000LL).FromTx(tx1)); // Ancestors / descendants should be 1 / 1 (itself / itself) pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants); @@ -616,8 +613,8 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) // // [tx1].0 <- [tx2] // - CTransactionRef tx2 = make_tx(MK_OUTPUTS(495 * CENT, 5 * COIN), MK_INPUTS(tx1)); - pool.addUnchecked(tx2->GetHash(), entry.Fee(10000LL).FromTx(*tx2)); + CTransactionRef tx2 = make_tx(/* output_values */ {495 * CENT, 5 * COIN}, /* inputs */ {tx1}); + pool.addUnchecked(tx2->GetHash(), entry.Fee(10000LL).FromTx(tx2)); // Ancestors / descendants should be: // transaction ancestors descendants @@ -635,8 +632,8 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) // // [tx1].0 <- [tx2].0 <- [tx3] // - CTransactionRef tx3 = make_tx(MK_OUTPUTS(290 * CENT, 200 * CENT), MK_INPUTS(tx2)); - pool.addUnchecked(tx3->GetHash(), entry.Fee(10000LL).FromTx(*tx3)); + CTransactionRef tx3 = make_tx(/* output_values */ {290 * CENT, 200 * CENT}, /* inputs */ {tx2}); + pool.addUnchecked(tx3->GetHash(), entry.Fee(10000LL).FromTx(tx3)); // Ancestors / descendants should be: // transaction ancestors descendants @@ -660,8 +657,8 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) // | // \---1 <- [tx4] // - CTransactionRef tx4 = make_tx(MK_OUTPUTS(290 * CENT, 250 * CENT), MK_INPUTS(tx2), MK_INPUT_IDX(1)); - pool.addUnchecked(tx4->GetHash(), entry.Fee(10000LL).FromTx(*tx4)); + CTransactionRef tx4 = make_tx(/* output_values */ {290 * CENT, 250 * CENT}, /* inputs */ {tx2}, /* input_indices */ {1}); + pool.addUnchecked(tx4->GetHash(), entry.Fee(10000LL).FromTx(tx4)); // Ancestors / descendants should be: // transaction ancestors descendants @@ -696,15 +693,15 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) CAmount v = 5 * COIN; for (uint64_t i = 0; i < 5; i++) { CTransactionRef& tyi = *ty[i]; - tyi = make_tx(MK_OUTPUTS(v), i > 0 ? MK_INPUTS(*ty[i-1]) : std::vector()); + tyi = make_tx(/* output_values */ {v}, /* inputs */ i > 0 ? std::vector{*ty[i - 1]} : std::vector{}); v -= 50 * CENT; - pool.addUnchecked(tyi->GetHash(), entry.Fee(10000LL).FromTx(*tyi)); + pool.addUnchecked(tyi->GetHash(), entry.Fee(10000LL).FromTx(tyi)); pool.GetTransactionAncestry(tyi->GetHash(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, i+1); BOOST_CHECK_EQUAL(descendants, i+1); } - CTransactionRef ty6 = make_tx(MK_OUTPUTS(5 * COIN), MK_INPUTS(tx3, ty5)); - pool.addUnchecked(ty6->GetHash(), entry.Fee(10000LL).FromTx(*ty6)); + CTransactionRef ty6 = make_tx(/* output_values */ {5 * COIN}, /* inputs */ {tx3, ty5}); + pool.addUnchecked(ty6->GetHash(), entry.Fee(10000LL).FromTx(ty6)); // Ancestors / descendants should be: // transaction ancestors descendants diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 554bb8e1ec97..0a503f391f3c 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -320,143 +320,132 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) CScript scriptPubKey; isminetype result; - bool isInvalid; // P2PK compressed { CBasicKeyStore keystore; - scriptPubKey.clear(); - scriptPubKey << ToByteVector(pubkeys[0]) << OP_CHECKSIG; + scriptPubKey = GetScriptForRawPubKey(pubkeys[0]); // Keystore does not have key - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has key keystore.AddKey(keys[0]); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2PK uncompressed { CBasicKeyStore keystore; - scriptPubKey.clear(); - scriptPubKey << ToByteVector(uncompressedPubkey) << OP_CHECKSIG; + scriptPubKey = GetScriptForRawPubKey(uncompressedPubkey); // Keystore does not have key - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has key keystore.AddKey(uncompressedKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2PKH compressed { CBasicKeyStore keystore; - scriptPubKey.clear(); - scriptPubKey << OP_DUP << OP_HASH160 << ToByteVector(pubkeys[0].GetID()) << OP_EQUALVERIFY << OP_CHECKSIG; + scriptPubKey = GetScriptForDestination(pubkeys[0].GetID()); // Keystore does not have key - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has key keystore.AddKey(keys[0]); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2PKH uncompressed { CBasicKeyStore keystore; - scriptPubKey.clear(); - scriptPubKey << OP_DUP << OP_HASH160 << ToByteVector(uncompressedPubkey.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG; + scriptPubKey = GetScriptForDestination(uncompressedPubkey.GetID()); // Keystore does not have key - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has key keystore.AddKey(uncompressedKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2SH { CBasicKeyStore keystore; - CScript redeemScript; - redeemScript << OP_DUP << OP_HASH160 << ToByteVector(pubkeys[0].GetID()) << OP_EQUALVERIFY << OP_CHECKSIG; - - scriptPubKey.clear(); - scriptPubKey << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; + CScript redeemScript = GetScriptForDestination(pubkeys[0].GetID()); + scriptPubKey = GetScriptForDestination(CScriptID(redeemScript)); // Keystore does not have redeemScript or key - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has redeemScript but no key keystore.AddCScript(redeemScript); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has redeemScript and key keystore.AddKey(keys[0]); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); + } + + // (P2PKH inside) P2SH inside P2SH (invalid) + { + CBasicKeyStore keystore; + + CScript redeemscript_inner = GetScriptForDestination(pubkeys[0].GetID()); + CScript redeemscript = GetScriptForDestination(CScriptID(redeemscript_inner)); + scriptPubKey = GetScriptForDestination(CScriptID(redeemscript)); + + keystore.AddCScript(redeemscript); + keystore.AddCScript(redeemscript_inner); + keystore.AddCScript(scriptPubKey); + keystore.AddKey(keys[0]); + result = IsMine(keystore, scriptPubKey); + BOOST_CHECK_EQUAL(result, ISMINE_NO); } // scriptPubKey multisig { CBasicKeyStore keystore; - scriptPubKey.clear(); - scriptPubKey << OP_2 << - ToByteVector(uncompressedPubkey) << - ToByteVector(pubkeys[1]) << - OP_2 << OP_CHECKMULTISIG; + scriptPubKey = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]}); // Keystore does not have any keys - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has 1/2 keys keystore.AddKey(uncompressedKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has 2/2 keys keystore.AddKey(keys[1]); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has 2/2 keys and the script keystore.AddCScript(scriptPubKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); } // P2SH multisig @@ -465,25 +454,17 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) keystore.AddKey(uncompressedKey); keystore.AddKey(keys[1]); - CScript redeemScript; - redeemScript << OP_2 << - ToByteVector(uncompressedPubkey) << - ToByteVector(pubkeys[1]) << - OP_2 << OP_CHECKMULTISIG; - - scriptPubKey.clear(); - scriptPubKey << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; + CScript redeemScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]}); + scriptPubKey = GetScriptForDestination(CScriptID(redeemScript)); // Keystore has no redeemScript - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has redeemScript keystore.AddCScript(redeemScript); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // OP_RETURN @@ -494,9 +475,8 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) scriptPubKey.clear(); scriptPubKey << OP_RETURN << ToByteVector(pubkeys[0]); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); } // Nonstandard @@ -507,9 +487,8 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) scriptPubKey.clear(); scriptPubKey << OP_9 << OP_ADD << OP_11 << OP_EQUAL; - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); } } diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 508dbf3770b1..8e03aef8508b 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -133,7 +133,7 @@ CMutableTransaction BuildCreditingTransaction(const CScript& scriptPubKey) return txCredit; } -CMutableTransaction BuildSpendingTransaction(const CScript& scriptSig, const CMutableTransaction& txCredit) +CMutableTransaction BuildSpendingTransaction(const CScript& scriptSig, const CTransaction& txCredit) { CMutableTransaction txSpend; txSpend.nVersion = 1; @@ -155,7 +155,7 @@ void DoTest(const CScript& scriptPubKey, const CScript& scriptSig, int flags, co bool expect = (scriptError == SCRIPT_ERR_OK); bool fEnableDIP0020Opcodes = (SCRIPT_ENABLE_DIP0020_OPCODES & flags) != 0; ScriptError err; - CMutableTransaction txCredit = BuildCreditingTransaction(scriptPubKey); + const CTransaction txCredit{BuildCreditingTransaction(scriptPubKey)}; CMutableTransaction tx = BuildSpendingTransaction(scriptSig, txCredit); CMutableTransaction tx2 = tx; BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, flags, MutableTransactionSignatureChecker(&tx, 0, txCredit.vout[0].nValue), &err) == expect, message); @@ -1018,7 +1018,7 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG12) CScript scriptPubKey12; scriptPubKey12 << OP_1 << ToByteVector(key1.GetPubKey()) << ToByteVector(key2.GetPubKey()) << OP_2 << OP_CHECKMULTISIG; - CMutableTransaction txFrom12 = BuildCreditingTransaction(scriptPubKey12); + const CTransaction txFrom12{BuildCreditingTransaction(scriptPubKey12)}; CMutableTransaction txTo12 = BuildSpendingTransaction(CScript(), txFrom12); CScript goodsig1 = sign_multisig(scriptPubKey12, key1, txTo12); @@ -1049,7 +1049,7 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) CScript scriptPubKey23; scriptPubKey23 << OP_2 << ToByteVector(key1.GetPubKey()) << ToByteVector(key2.GetPubKey()) << ToByteVector(key3.GetPubKey()) << OP_3 << OP_CHECKMULTISIG; - CMutableTransaction txFrom23 = BuildCreditingTransaction(scriptPubKey23); + const CTransaction txFrom23{BuildCreditingTransaction(scriptPubKey23)}; CMutableTransaction txTo23 = BuildSpendingTransaction(CScript(), txFrom23); std::vector keys; @@ -1106,10 +1106,19 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_INVALID_STACK_OPERATION, ScriptErrorString(err)); } +/* Wrapper around ProduceSignature to combine two scriptsigs */ +SignatureData CombineSignatures(const CTxOut& txout, const CMutableTransaction& tx, const SignatureData& scriptSig1, const SignatureData& scriptSig2) +{ + SignatureData data; + data.MergeSignatureData(scriptSig1); + data.MergeSignatureData(scriptSig2); + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&tx, 0, txout.nValue), txout.scriptPubKey, data); + return data; +} + BOOST_AUTO_TEST_CASE(script_combineSigs) { - // Test the CombineSignatures function - CAmount amount; + // Test the ProduceSignature's ability to combine signatures function CBasicKeyStore keystore; std::vector keys; std::vector pubkeys; @@ -1125,52 +1134,51 @@ BOOST_AUTO_TEST_CASE(script_combineSigs) CMutableTransaction txFrom = BuildCreditingTransaction(GetScriptForDestination(keys[0].GetPubKey().GetID())); CMutableTransaction txTo = BuildSpendingTransaction(CScript(), txFrom); CScript& scriptPubKey = txFrom.vout[0].scriptPubKey; - CScript& scriptSig = txTo.vin[0].scriptSig; + SignatureData scriptSig; SignatureData empty; - SignatureData combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, empty); + SignatureData combined = CombineSignatures(txFrom.vout[0], txTo, empty, empty); BOOST_CHECK(combined.scriptSig.empty()); // Single signature case: SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); // changes scriptSig - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), empty); - BOOST_CHECK(combined.scriptSig == scriptSig); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSig); - CScript scriptSigCopy = scriptSig; + scriptSig = DataFromTransaction(txTo, 0, txFrom.vout[0]); + combined = CombineSignatures(txFrom.vout[0], txTo, scriptSig, empty); + BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); + combined = CombineSignatures(txFrom.vout[0], txTo, empty, scriptSig); + BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); + SignatureData scriptSigCopy = scriptSig; // Signing again will give a different, valid signature: SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSigCopy), SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSigCopy || combined.scriptSig == scriptSig); + scriptSig = DataFromTransaction(txTo, 0, txFrom.vout[0]); + combined = CombineSignatures(txFrom.vout[0], txTo, scriptSigCopy, scriptSig); + BOOST_CHECK(combined.scriptSig == scriptSigCopy.scriptSig || combined.scriptSig == scriptSig.scriptSig); // P2SH, single-signature case: CScript pkSingle; pkSingle << ToByteVector(keys[0].GetPubKey()) << OP_CHECKSIG; keystore.AddCScript(pkSingle); scriptPubKey = GetScriptForDestination(CScriptID(pkSingle)); SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), empty); - BOOST_CHECK(combined.scriptSig == scriptSig); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSig); + scriptSig = DataFromTransaction(txTo, 0, txFrom.vout[0]); + combined = CombineSignatures(txFrom.vout[0], txTo, scriptSig, empty); + BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); + combined = CombineSignatures(txFrom.vout[0], txTo, empty, scriptSig); + BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); scriptSigCopy = scriptSig; SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSigCopy), SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSigCopy || combined.scriptSig == scriptSig); - // dummy scriptSigCopy with placeholder, should always choose non-placeholder: - scriptSigCopy = CScript() << OP_0 << std::vector(pkSingle.begin(), pkSingle.end()); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSigCopy), SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSig); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), SignatureData(scriptSigCopy)); - BOOST_CHECK(combined.scriptSig == scriptSig); + scriptSig = DataFromTransaction(txTo, 0, txFrom.vout[0]); + combined = CombineSignatures(txFrom.vout[0], txTo, scriptSigCopy, scriptSig); + BOOST_CHECK(combined.scriptSig == scriptSigCopy.scriptSig || combined.scriptSig == scriptSig.scriptSig); // Hardest case: Multisig 2-of-3 scriptPubKey = GetScriptForMultisig(2, pubkeys); keystore.AddCScript(scriptPubKey); SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), empty); - BOOST_CHECK(combined.scriptSig == scriptSig); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSig); + scriptSig = DataFromTransaction(txTo, 0, txFrom.vout[0]); + combined = CombineSignatures(txFrom.vout[0], txTo, scriptSig, empty); + BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); + combined = CombineSignatures(txFrom.vout[0], txTo, empty, scriptSig); + BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); // A couple of partially-signed versions: std::vector sig1; @@ -1197,22 +1205,28 @@ BOOST_AUTO_TEST_CASE(script_combineSigs) CScript complete12 = CScript() << OP_0 << sig1 << sig2; CScript complete13 = CScript() << OP_0 << sig1 << sig3; CScript complete23 = CScript() << OP_0 << sig2 << sig3; - - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial1a), SignatureData(partial1b)); + SignatureData partial1_sigs; + partial1_sigs.signatures.emplace(keys[0].GetPubKey().GetID(), SigPair(keys[0].GetPubKey(), sig1)); + SignatureData partial2_sigs; + partial2_sigs.signatures.emplace(keys[1].GetPubKey().GetID(), SigPair(keys[1].GetPubKey(), sig2)); + SignatureData partial3_sigs; + partial3_sigs.signatures.emplace(keys[2].GetPubKey().GetID(), SigPair(keys[2].GetPubKey(), sig3)); + + combined = CombineSignatures(txFrom.vout[0], txTo, partial1_sigs, partial1_sigs); BOOST_CHECK(combined.scriptSig == partial1a); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial1a), SignatureData(partial2a)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial1_sigs, partial2_sigs); BOOST_CHECK(combined.scriptSig == complete12); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial2a), SignatureData(partial1a)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial2_sigs, partial1_sigs); BOOST_CHECK(combined.scriptSig == complete12); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial1b), SignatureData(partial2b)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial1_sigs, partial2_sigs); BOOST_CHECK(combined.scriptSig == complete12); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial3b), SignatureData(partial1b)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial3_sigs, partial1_sigs); BOOST_CHECK(combined.scriptSig == complete13); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial2a), SignatureData(partial3a)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial2_sigs, partial3_sigs); BOOST_CHECK(combined.scriptSig == complete23); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial3b), SignatureData(partial2b)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial3_sigs, partial2_sigs); BOOST_CHECK(combined.scriptSig == complete23); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial3b), SignatureData(partial3a)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial3_sigs, partial3_sigs); BOOST_CHECK(combined.scriptSig == partial3c); } diff --git a/src/test/test_dash.cpp b/src/test/test_dash.cpp index 9c170ce7d2f0..786dc00ca894 100644 --- a/src/test/test_dash.cpp +++ b/src/test/test_dash.cpp @@ -232,7 +232,7 @@ CBlock TestChainSetup::CreateBlock(const std::vector& txns, if (!CalcCbTxMerkleRootQuorums(block, chainActive.Tip(), cbTx.merkleRootQuorums, state)) { BOOST_ASSERT(false); } - CMutableTransaction tmpTx = *block.vtx[0]; + CMutableTransaction tmpTx{*block.vtx[0]}; SetTxPayload(tmpTx, cbTx); block.vtx[0] = MakeTransactionRef(tmpTx); } diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index 6b1f115787ca..f6a435cee176 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -24,7 +24,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi BOOST_AUTO_TEST_SUITE(tx_validationcache_tests) static bool -ToMemPool(CMutableTransaction& tx) +ToMemPool(const CMutableTransaction& tx) { LOCK(cs_main); @@ -114,7 +114,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) // should fail. // Capture this interaction with the upgraded_nop argument: set it when evaluating // any script flag that is implemented as an upgraded NOP code. -static void ValidateCheckInputsForAllFlags(CMutableTransaction &tx, uint32_t failing_flags, bool add_to_cache) +static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t failing_flags, bool add_to_cache) { PrecomputedTransactionData txdata(tx); // If we add many more flags, this loop can get too expensive, but we can diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 65eb7f82b355..2b564d851209 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -132,27 +132,37 @@ struct TestArgsManager : public ArgsManager LOCK(cs_args); m_config_args.clear(); } - ReadConfigStream(streamConfig); + std::string error; + ReadConfigStream(streamConfig, error); } void SetNetworkOnlyArg(const std::string arg) { LOCK(cs_args); m_network_only_args.insert(arg); } + void SetupArgs(int argv, const char* args[]) + { + for (int i = 0; i < argv; ++i) { + AddArg(args[i], "", false, OptionsCategory::OPTIONS); + } + } }; BOOST_AUTO_TEST_CASE(util_ParseParameters) { TestArgsManager testArgs; + const char* avail_args[] = {"-a", "-b", "-ccc", "-d"}; const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"}; - testArgs.ParseParameters(0, (char**)argv_test); + std::string error; + testArgs.SetupArgs(4, avail_args); + testArgs.ParseParameters(0, (char**)argv_test, error); BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); - testArgs.ParseParameters(1, (char**)argv_test); + testArgs.ParseParameters(1, (char**)argv_test, error); BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); - testArgs.ParseParameters(7, (char**)argv_test); + testArgs.ParseParameters(7, (char**)argv_test, error); // expectation: -ignored is ignored (program name argument), // -a, -b and -ccc end up in map, -d ignored because it is after // a non-option argument (non-GNU option parsing) @@ -173,9 +183,12 @@ BOOST_AUTO_TEST_CASE(util_ParseParameters) BOOST_AUTO_TEST_CASE(util_GetBoolArg) { TestArgsManager testArgs; + const char* avail_args[] = {"-a", "-b", "-c", "-d", "-e", "-f"}; const char *argv_test[] = { "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"}; - testArgs.ParseParameters(7, (char**)argv_test); + std::string error; + testArgs.SetupArgs(6, avail_args); + testArgs.ParseParameters(7, (char**)argv_test, error); // Each letter should be set. for (char opt : "abcdef") @@ -207,8 +220,11 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) TestArgsManager testArgs; // Params test + const char* avail_args[] = {"-foo", "-bar"}; const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"}; - testArgs.ParseParameters(4, (char**)argv_test); + testArgs.SetupArgs(2, avail_args); + std::string error; + testArgs.ParseParameters(4, (char**)argv_test, error); // This was passed twice, second one overrides the negative setting. BOOST_CHECK(!testArgs.IsArgNegated("-foo")); @@ -220,7 +236,7 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) // Config test const char *conf_test = "nofoo=1\nfoo=1\nnobar=0\n"; - testArgs.ParseParameters(1, (char**)argv_test); + testArgs.ParseParameters(1, (char**)argv_test, error); testArgs.ReadConfigString(conf_test); // This was passed twice, second one overrides the negative setting, @@ -235,7 +251,7 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) // Combined test const char *combo_test_args[] = {"ignored", "-nofoo", "-bar"}; const char *combo_test_conf = "foo=1\nnobar=1\n"; - testArgs.ParseParameters(3, (char**)combo_test_args); + testArgs.ParseParameters(3, (char**)combo_test_args, error); testArgs.ReadConfigString(combo_test_conf); // Command line overrides, but doesn't erase old setting @@ -275,6 +291,8 @@ BOOST_AUTO_TEST_CASE(util_ReadConfigStream) "iii=2\n"; TestArgsManager test_args; + const char* avail_args[] = {"-a", "-b", "-ccc", "-d", "-e", "-fff", "-ggg", "-h", "-i", "-iii"}; + test_args.SetupArgs(10, avail_args); test_args.ReadConfigString(str_config); // expectation: a, b, ccc, d, fff, ggg, h, i end up in map @@ -472,6 +490,8 @@ BOOST_AUTO_TEST_CASE(util_GetArg) BOOST_AUTO_TEST_CASE(util_GetChainName) { TestArgsManager test_args; + const char* avail_args[] = {"-testnet", "-regtest"}; + test_args.SetupArgs(2, avail_args); const char* argv_testnet[] = {"cmd", "-testnet"}; const char* argv_regtest[] = {"cmd", "-regtest"}; @@ -481,39 +501,40 @@ BOOST_AUTO_TEST_CASE(util_GetChainName) // equivalent to "-testnet" // regtest in testnet section is ignored const char* testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1"; + std::string error; - test_args.ParseParameters(0, (char**)argv_testnet); + test_args.ParseParameters(0, (char**)argv_testnet, error); BOOST_CHECK_EQUAL(test_args.GetChainName(), "main"); - test_args.ParseParameters(2, (char**)argv_testnet); + test_args.ParseParameters(2, (char**)argv_testnet, error); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_regtest); + test_args.ParseParameters(2, (char**)argv_regtest, error); BOOST_CHECK_EQUAL(test_args.GetChainName(), "regtest"); - test_args.ParseParameters(3, (char**)argv_test_no_reg); + test_args.ParseParameters(3, (char**)argv_test_no_reg, error); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(3, (char**)argv_both); + test_args.ParseParameters(3, (char**)argv_both, error); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - test_args.ParseParameters(0, (char**)argv_testnet); + test_args.ParseParameters(0, (char**)argv_testnet, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_testnet); + test_args.ParseParameters(2, (char**)argv_testnet, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_regtest); + test_args.ParseParameters(2, (char**)argv_regtest, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - test_args.ParseParameters(3, (char**)argv_test_no_reg); + test_args.ParseParameters(3, (char**)argv_test_no_reg, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(3, (char**)argv_both); + test_args.ParseParameters(3, (char**)argv_both, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); @@ -521,23 +542,23 @@ BOOST_AUTO_TEST_CASE(util_GetChainName) // [test] regtest=1 potentially relevant) doesn't break things test_args.SelectConfigNetwork("test"); - test_args.ParseParameters(0, (char**)argv_testnet); + test_args.ParseParameters(0, (char**)argv_testnet, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_testnet); + test_args.ParseParameters(2, (char**)argv_testnet, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_regtest); + test_args.ParseParameters(2, (char**)argv_regtest, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - test_args.ParseParameters(2, (char**)argv_test_no_reg); + test_args.ParseParameters(2, (char**)argv_test_no_reg, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(3, (char**)argv_both); + test_args.ParseParameters(3, (char**)argv_both, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); } diff --git a/src/txmempool.cpp b/src/txmempool.cpp index c683a5135ab3..4b5ef21abb98 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1108,18 +1108,18 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const // Check children against mapNextTx CTxMemPool::setEntries setChildrenCheck; auto iter = mapNextTx.lower_bound(COutPoint(it->GetTx().GetHash(), 0)); - int64_t childSizes = 0; + uint64_t child_sizes = 0; for (; iter != mapNextTx.end() && iter->first->hash == it->GetTx().GetHash(); ++iter) { txiter childit = mapTx.find(iter->second->GetHash()); assert(childit != mapTx.end()); // mapNextTx points to in-mempool transactions if (setChildrenCheck.insert(childit).second) { - childSizes += childit->GetTxSize(); + child_sizes += childit->GetTxSize(); } } assert(setChildrenCheck == GetMemPoolChildren(it)); // Also check to make sure size is greater than sum with immediate children. // just a sanity check, not definitive that this calc is correct... - assert(it->GetSizeWithDescendants() >= childSizes + it->GetTxSize()); + assert(it->GetSizeWithDescendants() >= child_sizes + it->GetTxSize()); if (fDependsWait) waitingOnDependants.push_back(&(*it)); diff --git a/src/util/system.cpp b/src/util/system.cpp index 75c90f9e97a4..850b26717ae1 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -420,7 +420,7 @@ void ArgsManager::SelectConfigNetwork(const std::string& network) m_network = network; } -void ArgsManager::ParseParameters(int argc, const char* const argv[]) +bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::string& error) { LOCK(cs_args); m_override_args.clear(); @@ -452,6 +452,14 @@ void ArgsManager::ParseParameters(int argc, const char* const argv[]) } else { m_override_args[key].push_back(val); } + + // Check that the arg is known + if (!(IsSwitchChar(key[0]) && key.size() == 1)) { + if (!IsArgKnown(key, error)) { + error = strprintf("Invalid parameter %s", key.c_str()); + return false; + } + } } // we do not allow -includeconf from command line, so we clear it here @@ -459,11 +467,28 @@ void ArgsManager::ParseParameters(int argc, const char* const argv[]) if (it != m_override_args.end()) { if (it->second.size() > 0) { for (const auto& ic : it->second) { - fprintf(stderr, "warning: -includeconf cannot be used from commandline; ignoring -includeconf=%s\n", ic.c_str()); + error += "-includeconf cannot be used from commandline; -includeconf=" + ic + "\n"; } - m_override_args.erase(it); + return false; } } + return true; +} + +bool ArgsManager::IsArgKnown(const std::string& key, std::string& error) +{ + size_t option_index = key.find('.'); + std::string arg_no_net; + if (option_index == std::string::npos) { + arg_no_net = key; + } else { + arg_no_net = std::string("-") + key.substr(option_index + 1, std::string::npos); + } + + for (const auto& arg_map : m_available_args) { + if (arg_map.second.count(arg_no_net)) return true; + } + return false; } std::vector ArgsManager::GetArgs(const std::string& strArg) const @@ -557,62 +582,108 @@ void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strV void ArgsManager::AddArg(const std::string& name, const std::string& help, const bool debug_only, const OptionsCategory& cat) { - std::pair key(cat, name); - assert(m_available_args.count(key) == 0); - m_available_args.emplace(key, std::pair(help, debug_only)); + // Split arg name from its help param + size_t eq_index = name.find('='); + if (eq_index == std::string::npos) { + eq_index = name.size(); + } + + std::map& arg_map = m_available_args[cat]; + auto ret = arg_map.emplace(name.substr(0, eq_index), Arg(name.substr(eq_index, name.size() - eq_index), help, debug_only)); + assert(ret.second); // Make sure an insertion actually happened +} + +void ArgsManager::AddHiddenArgs(const std::vector& names) +{ + for (const std::string& name : names) { + AddArg(name, "", false, OptionsCategory::HIDDEN); + } } std::string ArgsManager::GetHelpMessage() { const bool show_debug = gArgs.GetBoolArg("-help-debug", false); - std::string usage = HelpMessageGroup("Options:"); - - OptionsCategory last_cat = OptionsCategory::OPTIONS; - for (auto& arg : m_available_args) { - if (arg.first.first != last_cat) { - last_cat = arg.first.first; - if (last_cat == OptionsCategory::CONNECTION) + std::string usage = ""; + for (const auto& arg_map : m_available_args) { + switch(arg_map.first) { + case OptionsCategory::OPTIONS: + usage += HelpMessageGroup("Options:"); + break; + case OptionsCategory::CONNECTION: usage += HelpMessageGroup("Connection options:"); - else if (last_cat == OptionsCategory::INDEXING) + break; + case OptionsCategory::INDEXING: usage += HelpMessageGroup("Indexing options:"); - else if (last_cat == OptionsCategory::MASTERNODE) + break; + case OptionsCategory::MASTERNODE: usage += HelpMessageGroup("Masternode options:"); - else if (last_cat == OptionsCategory::STATSD) + break; + case OptionsCategory::STATSD: usage += HelpMessageGroup("Statsd options:"); - else if (last_cat == OptionsCategory::ZMQ) + break; + case OptionsCategory::ZMQ: usage += HelpMessageGroup("ZeroMQ notification options:"); - else if (last_cat == OptionsCategory::DEBUG_TEST) + break; + case OptionsCategory::DEBUG_TEST: usage += HelpMessageGroup("Debugging/Testing options:"); - else if (last_cat == OptionsCategory::NODE_RELAY) + break; + case OptionsCategory::NODE_RELAY: usage += HelpMessageGroup("Node relay options:"); - else if (last_cat == OptionsCategory::BLOCK_CREATION) + break; + case OptionsCategory::BLOCK_CREATION: usage += HelpMessageGroup("Block creation options:"); - else if (last_cat == OptionsCategory::RPC) + break; + case OptionsCategory::RPC: usage += HelpMessageGroup("RPC server options:"); - else if (last_cat == OptionsCategory::WALLET) + break; + case OptionsCategory::WALLET: usage += HelpMessageGroup("Wallet options:"); - else if (last_cat == OptionsCategory::WALLET) + break; + case OptionsCategory::WALLET_FEE: usage += HelpMessageGroup("Wallet fee options:"); - else if (last_cat == OptionsCategory::WALLET) + break; + case OptionsCategory::WALLET_HD: usage += HelpMessageGroup("HD wallet options:"); - else if (last_cat == OptionsCategory::WALLET) + break; + case OptionsCategory::WALLET_KEEPASS: usage += HelpMessageGroup("KeePass options:"); - else if (last_cat == OptionsCategory::WALLET) + break; + case OptionsCategory::WALLET_COINJOIN: usage += HelpMessageGroup("CoinJoin options:"); - else if (last_cat == OptionsCategory::WALLET_DEBUG_TEST && show_debug) - usage += HelpMessageGroup("Wallet debugging/testing options:"); - else if (last_cat == OptionsCategory::CHAINPARAMS) + break; + case OptionsCategory::WALLET_DEBUG_TEST: + if (show_debug) usage += HelpMessageGroup("Wallet debugging/testing options:"); + break; + case OptionsCategory::CHAINPARAMS: usage += HelpMessageGroup("Chain selection options:"); - else if (last_cat == OptionsCategory::GUI) + break; + case OptionsCategory::GUI: usage += HelpMessageGroup("UI Options:"); - else if (last_cat == OptionsCategory::COMMANDS) + break; + case OptionsCategory::COMMANDS: usage += HelpMessageGroup("Commands:"); - else if (last_cat == OptionsCategory::REGISTER_COMMANDS) + break; + case OptionsCategory::REGISTER_COMMANDS: usage += HelpMessageGroup("Register Commands:"); + break; + default: + break; } - if (show_debug || !arg.second.second) { - usage += HelpMessageOpt(arg.first.second, arg.second.first); + + // When we get to the hidden options, stop + if (arg_map.first == OptionsCategory::HIDDEN) break; + + for (const auto& arg : arg_map.second) { + if (show_debug || !arg.second.m_debug_only) { + std::string name; + if (arg.second.m_help_param.empty()) { + name = arg.first; + } else { + name = arg.first + arg.second.m_help_param; + } + usage += HelpMessageOpt(name, arg.second.m_help_text); + } } } return usage; @@ -782,7 +853,7 @@ fs::path GetConfigFile(const std::string& confPath) return AbsPathForConfigVal(fs::path(confPath), false); } -void ArgsManager::ReadConfigStream(std::istream& stream) +bool ArgsManager::ReadConfigStream(std::istream& stream, std::string& error, bool ignore_invalid_keys) { LOCK(cs_args); @@ -793,15 +864,23 @@ void ArgsManager::ReadConfigStream(std::istream& stream) { std::string strKey = std::string("-") + it->string_key; std::string strValue = it->value[0]; + if (InterpretNegatedOption(strKey, strValue)) { m_config_args[strKey].clear(); } else { m_config_args[strKey].push_back(strValue); } + + // Check that the arg is known + if (!IsArgKnown(strKey, error) && !ignore_invalid_keys) { + error = strprintf("Invalid configuration value %s", it->string_key.c_str()); + return false; + } } + return true; } -void ArgsManager::ReadConfigFiles() +bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) { { LOCK(cs_args); @@ -812,41 +891,73 @@ void ArgsManager::ReadConfigFiles() fsbridge::ifstream stream(GetConfigFile(confPath)); if (stream.good()) { - ReadConfigStream(stream); + if (!ReadConfigStream(stream, error, ignore_invalid_keys)) { + return false; + } // if there is an -includeconf in the override args, but it is empty, that means the user // passed '-noincludeconf' on the command line, in which case we should not include anything if (m_override_args.count("-includeconf") == 0) { + std::string chain_id = GetChainName(); std::vector includeconf(GetArgs("-includeconf")); { // We haven't set m_network yet (that happens in SelectParams()), so manually check // for network.includeconf args. - std::vector includeconf_net(GetArgs(std::string("-") + GetChainName() + ".includeconf")); + std::vector includeconf_net(GetArgs(std::string("-") + chain_id + ".includeconf")); includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); } + // Remove -includeconf from configuration, so we can warn about recursion + // later + { + LOCK(cs_args); + m_config_args.erase("-includeconf"); + m_config_args.erase(std::string("-") + chain_id + ".includeconf"); + } + for (const std::string& to_include : includeconf) { fsbridge::ifstream include_config(GetConfigFile(to_include)); if (include_config.good()) { - ReadConfigStream(include_config); + if (!ReadConfigStream(include_config, error, ignore_invalid_keys)) { + return false; + } LogPrintf("Included configuration file %s\n", to_include.c_str()); } else { - fprintf(stderr, "Failed to include configuration file %s\n", to_include.c_str()); + error = "Failed to include configuration file " + to_include; + return false; + } + } + + // Warn about recursive -includeconf + includeconf = GetArgs("-includeconf"); + { + std::vector includeconf_net(GetArgs(std::string("-") + chain_id + ".includeconf")); + includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); + std::string chain_id_final = GetChainName(); + if (chain_id_final != chain_id) { + // Also warn about recursive includeconf for the chain that was specified in one of the includeconfs + includeconf_net = GetArgs(std::string("-") + chain_id_final + ".includeconf"); + includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); } } + for (const std::string& to_include : includeconf) { + fprintf(stderr, "warning: -includeconf cannot be used from included files; ignoring -includeconf=%s\n", to_include.c_str()); + } } } else { // Create an empty dash.conf if it does not exist FILE* configFile = fopen(GetConfigFile(confPath).string().c_str(), "a"); if (configFile != nullptr) fclose(configFile); - return; // Nothing to read, so just return + return true; // Nothing to read, so just return } // If datadir is changed in .conf file: ClearDatadirCache(); if (!fs::is_directory(GetDataDir(false))) { - throw std::runtime_error(strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str())); + error = strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str()); + return false; } + return true; } std::string ArgsManager::GetChainName() const diff --git a/src/util/system.h b/src/util/system.h index c864c350dad8..f26b70bed16c 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -141,8 +141,7 @@ inline bool IsSwitchChar(char c) #endif } -enum class OptionsCategory -{ +enum class OptionsCategory { OPTIONS, CONNECTION, INDEXING, @@ -162,7 +161,9 @@ enum class OptionsCategory RPC, GUI, COMMANDS, - REGISTER_COMMANDS + REGISTER_COMMANDS, + + HIDDEN // Always the last option to avoid printing these in the help }; class ArgsManager @@ -170,14 +171,23 @@ class ArgsManager protected: friend class ArgsManagerHelper; + struct Arg + { + std::string m_help_param; + std::string m_help_text; + bool m_debug_only; + + Arg(const std::string& help_param, const std::string& help_text, bool debug_only) : m_help_param(help_param), m_help_text(help_text), m_debug_only(debug_only) {}; + }; + mutable CCriticalSection cs_args; std::map> m_override_args; std::map> m_config_args; std::string m_network; std::set m_network_only_args; - std::map, std::pair> m_available_args; + std::map> m_available_args; - void ReadConfigStream(std::istream& stream); + bool ReadConfigStream(std::istream& stream, std::string& error, bool ignore_invalid_keys = false); public: ArgsManager(); @@ -187,8 +197,8 @@ class ArgsManager */ void SelectConfigNetwork(const std::string& network); - void ParseParameters(int argc, const char*const argv[]); - void ReadConfigFiles(); + bool ParseParameters(int argc, const char* const argv[], std::string& error); + bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false); /** * Log warnings for options in m_section_only_args when @@ -291,10 +301,25 @@ class ArgsManager */ void AddArg(const std::string& name, const std::string& help, const bool debug_only, const OptionsCategory& cat); + /** + * Add many hidden arguments + */ + void AddHiddenArgs(const std::vector& args); + + /** + * Clear available arguments + */ + void ClearArgs() { m_available_args.clear(); } + /** * Get the help string */ std::string GetHelpMessage(); + + /** + * Check whether we know of this arg + */ + bool IsArgKnown(const std::string& key, std::string& error); }; extern ArgsManager gArgs; diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index d82d350c84c9..4f7669ae980f 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -35,7 +35,7 @@ class CCoinControl bool fAllowOtherInputs; //! If false, only include as many inputs as necessary to fulfill a coin selection request. Only usable together with fAllowOtherInputs bool fRequireAllInputs; - //! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria + //! Includes watch only addresses which are solvable bool fAllowWatchOnly; //! Override automatic min/max checks on fee, m_feerate must be set if true bool fOverrideFeeRate; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index ec793aff9ce8..7f9d4ee7f065 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -939,7 +939,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) file << "# HD seed: " << HexStr(vchSeed) << "\n\n"; CExtKey masterKey; - masterKey.SetMaster(&vchSeed[0], vchSeed.size()); + masterKey.SetSeed(&vchSeed[0], vchSeed.size()); file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n"; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 2ef5acb12b3d..e0573ea9a6fa 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2584,8 +2584,8 @@ bool CWalletTx::IsTrusted() const bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const { - CMutableTransaction tx1 = *this->tx; - CMutableTransaction tx2 = *_tx.tx; + CMutableTransaction tx1 {*this->tx}; + CMutableTransaction tx2 {*_tx.tx}; for (auto& txin : tx1.vin) txin.scriptSig = CScript(); for (auto& txin : tx2.vin) txin.scriptSig = CScript(); return CTransaction(tx1) == CTransaction(tx2); @@ -2965,10 +2965,10 @@ void CWallet::AvailableCoins(std::vector &vCoins, bool fOnlySafe, const continue; } - bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO); - bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO; + bool solvable = IsSolvable(*this, pcoin->tx->vout[i].scriptPubKey); + bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); - vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx)); + vCoins.push_back(COutput(pcoin, i, nDepth, spendable, solvable, safeTx)); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { @@ -3713,7 +3713,7 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CTransac strFailReason = _("Signing transaction failed"); return false; } else { - UpdateTransaction(txNew, nIn, sigdata); + UpdateInput(txNew.vin[nIn], sigdata); } nIn++; @@ -3891,19 +3891,18 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CTransac if (sign) { - CTransaction txNewConst(txNew); int nIn = 0; for(const auto& coin : vecCoins) { const CScript& scriptPubKey = coin.txout.scriptPubKey; SignatureData sigdata; - if (!ProduceSignature(*this, TransactionSignatureCreator(&txNewConst, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) + if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) { strFailReason = _("Signing transaction failed"); return false; } else { - UpdateTransaction(txNew, nIn, sigdata); + UpdateInput(txNew.vin.at(nIn), sigdata); } nIn++; @@ -4325,7 +4324,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) return true; } -void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fInternal) +bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) { nIndex = -1; keypool.vchPubKey = CPubKey(); @@ -4335,12 +4334,13 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fIn if (!IsLocked(true)) TopUpKeyPool(); - fInternal = fInternal && IsHDEnabled(); - std::set& setKeyPool = fInternal ? setInternalKeyPool : setExternalKeyPool; + bool fReturningInternal = IsHDEnabled() && fRequestedInternal; + std::set& setKeyPool = fReturningInternal ? setInternalKeyPool : setExternalKeyPool; // Get the oldest key - if(setKeyPool.empty()) - return; + if (setKeyPool.empty()) { + return false; + } WalletBatch batch(*database); @@ -4352,14 +4352,17 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fIn if (!HaveKey(keypool.vchPubKey.GetID())) { throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); } - if (keypool.fInternal != fInternal) { + if (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"); + } - assert(keypool.vchPubKey.IsValid()); m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); LogPrintf("keypool reserve %d\n", nIndex); } + return true; } void CWallet::KeepKey(int64_t nIndex) @@ -4393,10 +4396,8 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) CKeyPool keypool; { LOCK(cs_wallet); - int64_t nIndex = 0; - ReserveKeyFromKeyPool(nIndex, keypool, internal); - if (nIndex == -1) - { + int64_t nIndex; + if (!ReserveKeyFromKeyPool(nIndex, keypool, internal)) { if (IsLocked(true)) return false; // TODO: implement keypool for all accouts? @@ -4599,13 +4600,10 @@ bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool fInternalIn) if (nIndex == -1) { CKeyPool keypool; - pwallet->ReserveKeyFromKeyPool(nIndex, keypool, fInternalIn); - if (nIndex != -1) { - vchPubKey = keypool.vchPubKey; - } - else { + if (!pwallet->ReserveKeyFromKeyPool(nIndex, keypool, fInternalIn)) { return false; } + vchPubKey = keypool.vchPubKey; fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index c835b6c26718..10c9c211ea99 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1075,7 +1075,22 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); size_t KeypoolCountInternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool TopUpKeyPool(unsigned int kpSize = 0); - void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fInternal); + + /** + * 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); bool GetKeyFromPool(CPubKey &key, bool fInternal /*= false*/); diff --git a/test/functional/feature_addressindex.py b/test/functional/feature_addressindex.py index 7767d860bba5..3775b13995ac 100755 --- a/test/functional/feature_addressindex.py +++ b/test/functional/feature_addressindex.py @@ -23,10 +23,10 @@ def set_test_params(self): def setup_network(self): self.add_nodes(self.num_nodes) # Nodes 0/1 are "wallet" nodes - self.start_node(0, ["-relaypriority=0"]) + self.start_node(0, []) self.start_node(1, ["-addressindex"]) # Nodes 2/3 are used for testing - self.start_node(2, ["-addressindex", "-relaypriority=0"]) + self.start_node(2, ["-addressindex"]) self.start_node(3, ["-addressindex"]) connect_nodes(self.nodes[0], 1) connect_nodes(self.nodes[0], 2) diff --git a/test/functional/feature_help.py b/test/functional/feature_help.py index a7d236b41006..8add06cfa729 100755 --- a/test/functional/feature_help.py +++ b/test/functional/feature_help.py @@ -36,6 +36,17 @@ def run_test(self): output = self.nodes[0].process.stdout.read() assert b'version' in output self.log.info("Version text received: {} (...)".format(output[0:60])) + + # Test that arguments not in the help results in an error + self.log.info("Start dashdd with -fakearg to make sure it does not start") + self.nodes[0].start(extra_args=['-fakearg'], stderr=subprocess.PIPE, stdout=subprocess.PIPE) + # Node should exit immediately and output an error to stderr + ret_code = self.nodes[0].process.wait(timeout=1) + assert_equal(ret_code, 1) + output = self.nodes[0].process.stderr.read() + assert b'Error parsing command line arguments' in output + self.log.info("Error message received: {} (...)".format(output[0:60])) + # Clean up TestNode state self.nodes[0].running = False self.nodes[0].process = None diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py index 0e318eb8c114..a450aad17cda 100755 --- a/test/functional/feature_includeconf.py +++ b/test/functional/feature_includeconf.py @@ -15,9 +15,8 @@ file. """ import os -import tempfile -from test_framework.test_framework import BitcoinTestFramework, assert_equal +from test_framework.test_framework import BitcoinTestFramework class IncludeConfTest(BitcoinTestFramework): def set_test_params(self): @@ -42,34 +41,37 @@ def run_test(self): subversion = self.nodes[0].getnetworkinfo()["subversion"] assert subversion.endswith("main; relative)/") - self.log.info("-includeconf cannot be used as command-line arg. subversion should still end with 'main; relative)/'") + self.log.info("-includeconf cannot be used as command-line arg") self.stop_node(0) - with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr: - self.start_node(0, extra_args=["-includeconf=relative2.conf"], stderr=log_stderr) - - subversion = self.nodes[0].getnetworkinfo()["subversion"] - assert subversion.endswith("main; relative)/") - log_stderr.seek(0) - stderr = log_stderr.read().decode('utf-8').strip() - assert_equal(stderr, 'warning: -includeconf cannot be used from commandline; ignoring -includeconf=relative2.conf') + self.nodes[0].assert_start_raises_init_error(extra_args=["-includeconf=relative2.conf"], expected_msg="Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf=relative2.conf") self.log.info("-includeconf cannot be used recursively. subversion should end with 'main; relative)/'") with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f: f.write("includeconf=relative2.conf\n") - - self.restart_node(0) + self.start_node(0) subversion = self.nodes[0].getnetworkinfo()["subversion"] assert subversion.endswith("main; relative)/") + self.stop_node(0, expected_stderr="warning: -includeconf cannot be used from included files; ignoring -includeconf=relative2.conf") + + self.log.info("-includeconf cannot contain invalid arg") + with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: + f.write("foo=bar\n") + self.nodes[0].assert_start_raises_init_error(expected_msg="Error reading configuration file: Invalid configuration value foo") + + self.log.info("-includeconf cannot be invalid path") + os.remove(os.path.join(self.options.tmpdir, "node0", "relative.conf")) + self.nodes[0].assert_start_raises_init_error(expected_msg="Error reading configuration file: Failed to include configuration file relative.conf") self.log.info("multiple -includeconf args can be used from the base config file. subversion should end with 'main; relative; relative2)/'") with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: + # Restore initial file contents f.write("uacomment=relative\n") with open(os.path.join(self.options.tmpdir, "node0", "dash.conf"), "a", encoding='utf8') as f: f.write("includeconf=relative2.conf\n") - self.restart_node(0) + self.start_node(0) subversion = self.nodes[0].getnetworkinfo()["subversion"] assert subversion.endswith("main; relative; relative2)/") diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 8805e4fa4a9c..4aed419d0a09 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -12,7 +12,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * import os -import sys MIN_BLOCKS_TO_KEEP = 288 @@ -55,7 +54,7 @@ def setup_network(self): self.sync_blocks(self.nodes[0:5]) def setup_nodes(self): - self.add_nodes(self.num_nodes, self.extra_args, timewait=900, stderr=sys.stdout) + self.add_nodes(self.num_nodes, self.extra_args, timewait=900) self.start_nodes() def create_big_chain(self): diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index 94f7722bf90d..581e4dd3c38a 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -13,8 +13,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, disconnect_nodes, connect_nodes_bi, sync_blocks -import sys - class P2PIgnoreInv(P2PInterface): firstAddrnServices = 0 def on_inv(self, message): @@ -34,7 +32,6 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 - self.stderr = sys.stdout self.extra_args = [['-prune=550', '-txindex=0', '-addrmantest'], [], []] def disconnect_all(self): @@ -117,6 +114,8 @@ def run_test(self): # sync must be possible, node 1 is no longer in IBD and should therefore connect to node 0 (NODE_NETWORK_LIMITED) sync_blocks([self.nodes[0], self.nodes[1]]) + self.stop_node(0, expected_stderr='Warning: You are starting with governance validation disabled. This is expected because you are running a pruned node.') + if __name__ == '__main__': NodeNetworkLimitedTest().main() diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 3257713d523d..93af518c2eb3 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -21,7 +21,6 @@ from decimal import Decimal import http.client import subprocess -import sys from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -50,7 +49,6 @@ class BlockchainTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True - self.stderr = sys.stdout self.extra_args = [['-stopatheight=207', '-prune=1', '-txindex=0']] def run_test(self): @@ -105,7 +103,7 @@ def _test_getblockchaininfo(self): assert res['pruned'] assert not res['automatic_pruning'] - self.restart_node(0, ['-stopatheight=207', '-txindex=0']) + self.restart_node(0, ['-stopatheight=207', '-txindex=0'], expected_stderr='Warning: You are starting with governance validation disabled. This is expected because you are running a pruned node.') res = self.nodes[0].getblockchaininfo() # should have exact keys assert_equal(sorted(res.keys()), keys) diff --git a/test/functional/rpc_fundrawtransaction_hd.py b/test/functional/rpc_fundrawtransaction_hd.py index 1b8601bd2345..b4545ad19c57 100755 --- a/test/functional/rpc_fundrawtransaction_hd.py +++ b/test/functional/rpc_fundrawtransaction_hd.py @@ -3,8 +3,6 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -import sys - from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * @@ -15,7 +13,6 @@ def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 4 self.extra_args = [['-usehd=1']] * self.num_nodes - self.stderr = sys.stdout def setup_network(self): super().setup_network() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 8e9936648552..4383903ff83a 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -163,9 +163,11 @@ def main(self): self.extra_args_from_options = self.options.dashd_extra_args - os.environ['PATH'] = config['environment']['BUILDDIR'] + os.pathsep + \ - config['environment']['BUILDDIR'] + os.path.sep + "qt" + os.pathsep + \ - os.environ['PATH'] + os.environ['PATH'] = os.pathsep.join([ + os.path.join(config['environment']['BUILDDIR'], 'src'), + os.path.join(config['environment']['BUILDDIR'], 'src', 'qt'), + os.environ['PATH'] + ]) # Set up temp directory and start logging if self.options.tmpdir: @@ -270,12 +272,9 @@ def setup_network(self): def setup_nodes(self): """Override this method to customize test node setup""" extra_args = None - stderr = None if hasattr(self, "extra_args"): extra_args = self.extra_args - if hasattr(self, "stderr"): - stderr = self.stderr - self.add_nodes(self.num_nodes, extra_args, stderr=stderr) + self.add_nodes(self.num_nodes, extra_args) self.start_nodes() def run_test(self): @@ -284,7 +283,7 @@ def run_test(self): # Public helper methods. These can be accessed by the subclass test scripts. - def add_nodes(self, num_nodes, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None): + def add_nodes(self, num_nodes, extra_args=None, rpchost=None, timewait=None, binary=None): """Instantiate TestNode objects""" if self.bind_to_localhost_only: extra_confs = [["bind=127.0.0.1"]] * num_nodes @@ -299,7 +298,7 @@ def add_nodes(self, num_nodes, extra_args=None, rpchost=None, timewait=None, bin assert_equal(len(binary), num_nodes) old_num_nodes = len(self.nodes) for i in range(num_nodes): - self.nodes.append(TestNode(old_num_nodes + i, get_datadir_path(self.options.tmpdir, old_num_nodes + i), self.extra_args_from_options, chain=self.chain, rpchost=rpchost, timewait=timewait, bitcoind=binary[i], bitcoin_cli=self.options.bitcoincli, stderr=stderr, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, extra_conf=extra_confs[i], extra_args=extra_args[i], use_cli=self.options.usecli)) + self.nodes.append(TestNode(old_num_nodes + i, get_datadir_path(self.options.tmpdir, old_num_nodes + i), self.extra_args_from_options, chain=self.chain, rpchost=rpchost, timewait=timewait, bitcoind=binary[i], bitcoin_cli=self.options.bitcoincli, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, extra_conf=extra_confs[i], extra_args=extra_args[i], use_cli=self.options.usecli)) def start_node(self, i, *args, **kwargs): """Start a dashd""" @@ -312,7 +311,7 @@ def start_node(self, i, *args, **kwargs): if self.options.coveragedir is not None: coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc) - def start_nodes(self, extra_args=None, stderr=None, *args, **kwargs): + def start_nodes(self, extra_args=None, *args, **kwargs): """Start multiple dashds""" if extra_args is None: @@ -320,7 +319,7 @@ def start_nodes(self, extra_args=None, stderr=None, *args, **kwargs): assert_equal(len(extra_args), self.num_nodes) try: for i, node in enumerate(self.nodes): - node.start(extra_args[i], stderr, *args, **kwargs) + node.start(extra_args[i], *args, **kwargs) for node in self.nodes: node.wait_for_rpc_connection() except: @@ -332,24 +331,24 @@ def start_nodes(self, extra_args=None, stderr=None, *args, **kwargs): for node in self.nodes: coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc) - def stop_node(self, i, wait=0): + def stop_node(self, i, expected_stderr='', wait=0): """Stop a dashd test node""" - self.nodes[i].stop_node(wait=wait) + self.nodes[i].stop_node(expected_stderr=expected_stderr, wait=wait) self.nodes[i].wait_until_stopped() - def stop_nodes(self, wait=0): + def stop_nodes(self, expected_stderr='', wait=0): """Stop multiple dashd test nodes""" for node in self.nodes: # Issue RPC to stop nodes - node.stop_node(wait=wait) + node.stop_node(expected_stderr=expected_stderr, wait=wait) for node in self.nodes: # Wait for nodes to stop node.wait_until_stopped() - def restart_node(self, i, extra_args=None): + def restart_node(self, i, extra_args=None, expected_stderr=''): """Stop and start a test node""" - self.stop_node(i) + self.stop_node(i, expected_stderr) self.start_node(i, extra_args) def wait_for_node_exit(self, i, timeout): @@ -440,7 +439,7 @@ def _start_logging(self): rpc_handler.setLevel(logging.DEBUG) rpc_logger.addHandler(rpc_handler) - def _initialize_chain(self, extra_args=None, stderr=None): + def _initialize_chain(self, extra_args=None): """Initialize a pre-mined blockchain for use by the test. Create a cache of a 200-block-long chain (with wallet) for MAX_NODES @@ -470,7 +469,7 @@ def _initialize_chain(self, extra_args=None, stderr=None): args.append("-connect=127.0.0.1:" + str(p2p_port(0))) if extra_args is not None: args.extend(extra_args) - self.nodes.append(TestNode(i, get_datadir_path(self.options.cachedir, i), chain=self.chain, extra_conf=["bind=127.0.0.1"], extra_args=[],extra_args_from_options=self.extra_args_from_options, rpchost=None, timewait=None, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, stderr=stderr, mocktime=self.mocktime, coverage_dir=None)) + self.nodes.append(TestNode(i, get_datadir_path(self.options.cachedir, i), chain=self.chain, extra_conf=["bind=127.0.0.1"], extra_args=[], extra_args_from_options=self.extra_args_from_options, rpchost=None, timewait=None, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, mocktime=self.mocktime, coverage_dir=None)) self.nodes[i].args = args self.start_node(i) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 3ffc61555f45..f08482af3acf 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -60,10 +60,12 @@ class TestNode(): To make things easier for the test writer, any unrecognised messages will be dispatched to the RPC connection.""" - def __init__(self, i, datadir, extra_args_from_options, chain, rpchost, timewait, bitcoind, bitcoin_cli, stderr, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False): + def __init__(self, i, datadir, extra_args_from_options, chain, rpchost, timewait, bitcoind, bitcoin_cli, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False): self.index = i self.datadir = datadir self.chain = chain + self.stdout_dir = os.path.join(self.datadir, "stdout") + self.stderr_dir = os.path.join(self.datadir, "stderr") self.rpchost = rpchost if timewait: self.rpc_timeout = timewait @@ -72,7 +74,6 @@ def __init__(self, i, datadir, extra_args_from_options, chain, rpchost, timewait self.rpc_timeout = 60 self.rpc_timeout *= Options.timeout_scale self.binary = bitcoind - self.stderr = stderr self.coverage_dir = coverage_dir self.mocktime = mocktime if extra_conf != None: @@ -135,20 +136,33 @@ def __getattr__(self, name): assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection") return getattr(self.rpc, name) - def start(self, extra_args=None, stderr=None, *args, **kwargs): + def start(self, extra_args=None, stdout=None, stderr=None, *args, **kwargs): """Start the node.""" if extra_args is None: extra_args = self.extra_args + + # Add a new stdout and stderr file each time dashd is started if stderr is None: - stderr = self.stderr + stderr = tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) + if stdout is None: + stdout = tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) + self.stderr = stderr + self.stdout = stdout + all_args = self.args + self.extra_args_from_options + extra_args if self.mocktime != 0: all_args = all_args + ["-mocktime=%d" % self.mocktime] + # Delete any existing cookie file -- if such a file exists (eg due to # unclean shutdown), it will get overwritten anyway by dashd, and # potentially interfere with our attempt to authenticate delete_cookie_file(self.datadir, self.chain) - self.process = subprocess.Popen(all_args, stderr=stderr, *args, **kwargs) + + # add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are written to stderr and not the terminal + subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1") + + self.process = subprocess.Popen(all_args, env=subp_env, stdout=stdout, stderr=stderr, *args, **kwargs) + self.running = True self.log.debug("dashd started, waiting for RPC to come up") @@ -190,7 +204,7 @@ def get_wallet_rpc(self, wallet_name): wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name)) return self.rpc / wallet_path - def stop_node(self, wait=0): + def stop_node(self, expected_stderr='', wait=0): """Stop the node.""" if not self.running: return @@ -199,6 +213,13 @@ def stop_node(self, wait=0): self.stop(wait=wait) except http.client.CannotSendRequest: self.log.exception("Unable to stop node.") + + # Check that stderr is as expected + self.stderr.seek(0) + stderr = self.stderr.read().decode('utf-8').strip() + if stderr != expected_stderr: + raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr)) + del self.p2ps[:] def is_node_stopped(self): @@ -251,9 +272,10 @@ def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, mat Will throw if dashd starts without an error. Will throw if an expected_msg is provided and it does not match dashd's stdout.""" - with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr: + with tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) as log_stderr, \ + tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) as log_stdout: try: - self.start(extra_args, stderr=log_stderr, *args, **kwargs) + self.start(extra_args, stdout=log_stdout, stderr=log_stderr, *args, **kwargs) self.wait_for_rpc_connection() self.stop_node() self.wait_until_stopped() diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index ac8622e0a9ea..abc0d71ba6b5 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -334,6 +334,8 @@ def initialize_datadir(dirname, n, chain): f.write("discover=0\n") f.write("listenonion=0\n") f.write("printtoconsole=0\n") + os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True) + os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True) return datadir def get_datadir_path(dirname, n): diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 796b1bc74aa0..d9571494744f 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -457,10 +457,6 @@ def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, t self.test_list = test_list self.flags = flags self.num_running = 0 - # In case there is a graveyard of zombie dashds, we can apply a - # pseudorandom offset to hopefully jump over them. - # (625 is PORT_RANGE/MAX_NODES) - self.portseed_offset = int(time.time() * 1000) % 625 self.jobs = [] def get_next(self): @@ -468,7 +464,7 @@ def get_next(self): # Add tests self.num_running += 1 test = self.test_list.pop(0) - portseed = len(self.test_list) + self.portseed_offset + portseed = len(self.test_list) portseed_arg = ["--portseed={}".format(portseed)] log_stdout = tempfile.SpooledTemporaryFile(max_size=2**16) log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16) diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 2d097ac58455..0b4ef954dc38 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -4,7 +4,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet.""" from decimal import Decimal -import sys import time from test_framework.test_framework import BitcoinTestFramework @@ -25,7 +24,7 @@ def set_test_params(self): self.extra_args = [['-usehd={:d}'.format(i%2==0)] for i in range(4)] def setup_network(self): - self.add_nodes(4, self.extra_args, stderr=sys.stdout) + self.add_nodes(4, self.extra_args) self.start_node(0) self.start_node(1) self.start_node(2) diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py index b71d5cfa69f5..6ab1efde736f 100755 --- a/test/functional/wallet_dump.py +++ b/test/functional/wallet_dump.py @@ -4,7 +4,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the dumpwallet RPC.""" import os -import sys from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -35,10 +34,10 @@ def read_dump(file_name, addrs, script_addrs, hd_master_addr_old): addr_keypath = comment.split(" addr=")[1] addr = addr_keypath.split(" ")[0] keypath = None - if keytype == "inactivehdmaster=1": + if keytype == "inactivehdseed=1": # ensure the old master is still available assert(hd_master_addr_old == addr) - elif keytype == "hdmaster=1": + elif keytype == "hdseed=1": # ensure we have generated a new hd master key assert(hd_master_addr_old != addr) hd_master_addr_ret = addr @@ -80,13 +79,13 @@ def setup_network(self): # use our own cache and -usehd=1 as extra arg as the default cache is run with -usehd=0 self.options.tmpdir = os.path.join(self.options.tmpdir, 'hd') self.options.cachedir = os.path.join(self.options.cachedir, 'hd') - self._initialize_chain(extra_args=self.extra_args[0], stderr=sys.stdout) + self._initialize_chain(extra_args=self.extra_args[0]) self.set_cache_mocktime() # Use 1 minute timeout because the initial getnewaddress RPC can take # longer than the default 30 seconds due to an expensive # CWallet::TopUpKeyPool call, and the encryptwallet RPC made later in # the test often takes even longer. - self.add_nodes(self.num_nodes, extra_args=self.extra_args, timewait=60, stderr=sys.stdout) + self.add_nodes(self.num_nodes, extra_args=self.extra_args, timewait=60) self.start_nodes() def run_test (self): diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index 38c107d7e43e..3b285d3fed1c 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -4,7 +4,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test Hierarchical Deterministic wallet function.""" -import sys import shutil import os @@ -21,7 +20,7 @@ def set_test_params(self): self.extra_args = [['-usehd=0'], ['-usehd=1', '-keypool=0']] def setup_network(self): - self.add_nodes(self.num_nodes, self.extra_args, stderr=sys.stdout) + self.add_nodes(self.num_nodes, self.extra_args) self.start_nodes() def run_test(self): diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py index 7f9b93bd80a2..eba242753403 100755 --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -25,7 +25,6 @@ import collections import enum import itertools -import sys Call = enum.Enum("Call", "single multi") Data = enum.Enum("Data", "address pub priv") @@ -131,7 +130,7 @@ def setup_network(self): # txindex is enabled by default in Dash and needs to be disabled for import-rescan.py extra_args[i] += ["-prune=1", "-txindex=0", "-reindex"] - self.add_nodes(self.num_nodes, extra_args=extra_args, stderr=sys.stdout) + self.add_nodes(self.num_nodes, extra_args=extra_args) self.start_nodes() for i in range(1, self.num_nodes): connect_nodes(self.nodes[i], 0) @@ -189,6 +188,10 @@ def run_test(self): variant.check(variant.sent_txid, variant.sent_amount, 1) else: variant.check() + for i, import_node in enumerate(IMPORT_NODES, 2): + if import_node.prune: + self.stop_node(i, expected_stderr='Warning: You are starting with governance validation disabled. This is expected because you are running a pruned node.') + if __name__ == "__main__": ImportRescanTest().main() diff --git a/test/functional/wallet_keypool_hd.py b/test/functional/wallet_keypool_hd.py index b7e838c42c94..fc78e3f89047 100755 --- a/test/functional/wallet_keypool_hd.py +++ b/test/functional/wallet_keypool_hd.py @@ -7,8 +7,6 @@ # Add python-bitcoinrpc to module search path: -import sys - from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * @@ -18,7 +16,6 @@ def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [['-usehd=1']] - self.stderr = sys.stdout def run_test(self): nodes = self.nodes diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py index 88e6e38173cf..ca410909c4ff 100755 --- a/test/functional/wallet_keypool_topup.py +++ b/test/functional/wallet_keypool_topup.py @@ -12,7 +12,6 @@ - connect node1 to node0. Verify that they sync and node1 receives its funds.""" import os import shutil -import sys from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -25,8 +24,7 @@ class KeypoolRestoreTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [['-usehd=0'], ['-usehd=1', '-keypool=100', '-keypoolmin=20']] - self.stderr = sys.stdout + self.extra_args = [['-usehd=0'], ['-usehd=1', '-keypool=100']] def run_test(self): wallet_path = os.path.join(self.nodes[1].datadir, self.chain, "wallets", "wallet.dat") diff --git a/test/functional/wallet_upgradetohd.py b/test/functional/wallet_upgradetohd.py index 2ad81805423e..de4c249157a0 100755 --- a/test/functional/wallet_upgradetohd.py +++ b/test/functional/wallet_upgradetohd.py @@ -8,7 +8,6 @@ Test upgrade to a Hierarchical Deterministic wallet via upgradetohd rpc """ -import sys import shutil import os @@ -24,7 +23,7 @@ def set_test_params(self): self.num_nodes = 1 def setup_network(self): - self.add_nodes(self.num_nodes, stderr=sys.stdout) + self.add_nodes(self.num_nodes) self.start_nodes() def recover_non_hd(self):