diff options
-rw-r--r-- | .travis.yml | 22 | ||||
-rw-r--r-- | Makefile.am | 9 | ||||
-rwxr-xr-x | ci/lint/04_install.sh | 21 | ||||
-rw-r--r-- | depends/Makefile | 4 | ||||
-rw-r--r-- | doc/files.md | 139 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/Makefile.test.include | 15 | ||||
-rw-r--r-- | src/qt/bitcoingui.cpp | 3 | ||||
-rw-r--r-- | src/qt/rpcconsole.cpp | 10 | ||||
-rw-r--r-- | src/qt/rpcconsole.h | 1 | ||||
-rw-r--r-- | src/qt/transactionrecord.cpp | 10 | ||||
-rw-r--r-- | src/qt/transactiontablemodel.cpp | 1 | ||||
-rw-r--r-- | src/script/descriptor.cpp | 66 | ||||
-rw-r--r-- | src/script/descriptor.h | 78 | ||||
-rw-r--r-- | src/test/fuzz/deserialize.cpp | 5 | ||||
-rw-r--r-- | src/test/fuzz/transaction.cpp | 81 | ||||
-rw-r--r-- | src/test/util_tests.cpp | 124 | ||||
-rw-r--r-- | src/util/spanparsing.cpp | 67 | ||||
-rw-r--r-- | src/util/spanparsing.h | 50 | ||||
-rw-r--r-- | src/util/system.cpp | 4 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 110 | ||||
-rw-r--r-- | src/wallet/test/wallet_tests.cpp | 78 | ||||
-rwxr-xr-x | test/functional/combine_logs.py | 10 | ||||
-rwxr-xr-x | test/functional/wallet_backup.py | 8 |
24 files changed, 700 insertions, 218 deletions
diff --git a/.travis.yml b/.travis.yml index 6f27e47b88..f59c7fc7e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -88,6 +88,28 @@ jobs: script: - set -o errexit; source ./ci/extended_lint/06_script.sh + - stage: extended-lint + name: 'lint macOS 10.12 (compat)' + os: osx + # Use the earliest macOS that can build our lint dependencies: + # Xcode 8.3.3, macOS 10.12, JDK 1.8.0_112-b16 + # https://docs.travis-ci.com/user/reference/osx/#OS-X-Version + osx_image: xcode8.3 + # TODO: if you're updating osx_image, try using "rvm:" to supply the + # version of ruby required by homebrew. Despite this "rvm:" declaration, + # brew update installs ruby 2.3.7 as its first action. + language: ruby + rvm: + - 2.3.7 + env: + cache: false + install: + - set -o errexit; source ./ci/lint/04_install.sh + before_script: + - set -o errexit; source ./ci/lint/05_before_script.sh + script: + - set -o errexit; source ./ci/lint/06_script.sh + - stage: test name: 'ARM [GOAL: install] [unit tests, no functional tests]' env: >- diff --git a/Makefile.am b/Makefile.am index 8b1e2a6b5b..9e54dc210d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,6 +2,10 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +# Pattern rule to print variables, e.g. make print-top_srcdir +print-%: + @echo $* = $($*) + ACLOCAL_AMFLAGS = -I build-aux/m4 SUBDIRS = src if ENABLE_MAN @@ -39,7 +43,10 @@ OSX_INSTALLER_ICONS=$(top_srcdir)/src/qt/res/icons/bitcoin.icns OSX_PLIST=$(top_builddir)/share/qt/Info.plist #not installed OSX_QT_TRANSLATIONS = da,de,es,hu,ru,uk,zh_CN,zh_TW -DIST_DOCS = $(wildcard doc/*.md) $(wildcard doc/release-notes/*.md) +DIST_DOCS = \ + README.md \ + $(wildcard doc/*.md) \ + $(wildcard doc/release-notes/*.md) DIST_CONTRIB = $(top_srcdir)/contrib/bitcoin-cli.bash-completion \ $(top_srcdir)/contrib/bitcoin-tx.bash-completion \ $(top_srcdir)/contrib/bitcoind.bash-completion \ diff --git a/ci/lint/04_install.sh b/ci/lint/04_install.sh index 12c3bfce45..9f7e1b310d 100755 --- a/ci/lint/04_install.sh +++ b/ci/lint/04_install.sh @@ -6,9 +6,22 @@ export LC_ALL=C +if [ "$TRAVIS_OS_NAME" == "osx" ]; then + # update first to install required ruby dependency + travis_retry brew update + travis_retry brew reinstall git -- --with-pcre2 # for --perl-regexp + travis_retry brew install grep # gnu grep for --perl-regexp support + PATH="$(brew --prefix grep)/libexec/gnubin:$PATH" + travis_retry brew install shellcheck + travis_retry brew upgrade python + PATH="$(brew --prefix python)/bin:$PATH" + export PATH +else + SHELLCHECK_VERSION=v0.6.0 + travis_retry curl --silent "https://storage.googleapis.com/shellcheck/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar --xz -xf - --directory /tmp/ + PATH="/tmp/shellcheck-${SHELLCHECK_VERSION}:${PATH}" + export PATH +fi + travis_retry pip3 install codespell==1.15.0 travis_retry pip3 install flake8==3.7.8 - -SHELLCHECK_VERSION=v0.6.0 -curl -s "https://storage.googleapis.com/shellcheck/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar --xz -xf - --directory /tmp/ -export PATH="/tmp/shellcheck-${SHELLCHECK_VERSION}:${PATH}" diff --git a/depends/Makefile b/depends/Makefile index b7e9a9213e..25ff135ea6 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -1,5 +1,9 @@ .NOTPARALLEL : +# Pattern rule to print variables, e.g. make print-top_srcdir +print-%: + @echo $* = $($*) + SOURCES_PATH ?= $(BASEDIR)/sources WORK_PATH = $(BASEDIR)/work BASE_CACHE ?= $(BASEDIR)/built diff --git a/doc/files.md b/doc/files.md index 85c27f3fd0..c2296b45fa 100644 --- a/doc/files.md +++ b/doc/files.md @@ -1,37 +1,102 @@ -Filename | Description ---------------------|---------------------------------------------------------------------------------------------------------------------------- -banlist.dat | stores the IPs/Subnets of banned nodes -bitcoin.conf | contains configuration settings for bitcoind or bitcoin-qt -bitcoind.pid | stores the process id of bitcoind while running -blocks/blk000??.dat | block data (custom, 128 MiB per file); since 0.8.0 -blocks/rev000??.dat | block undo data (custom); since 0.8.0 (format changed since pre-0.8) -blocks/index/* | block index (LevelDB); since 0.8.0 -chainstate/* | blockchain state database (LevelDB); since 0.8.0 -database/* | BDB database environment; only used for wallet since 0.8.0; moved to wallets/ directory on new installs since 0.16.0 -db.log | wallet database log file; moved to wallets/ directory on new installs since 0.16.0 -debug.log | contains debug information and general logging generated by bitcoind or bitcoin-qt -fee_estimates.dat | stores statistics used to estimate minimum transaction fees and priorities required for confirmation; since 0.10.0 -indexes/txindex/* | optional transaction index database (LevelDB); since 0.17.0 -mempool.dat | dump of the mempool's transactions; since 0.14.0 -peers.dat | peer IP address database (custom format); since 0.7.0 -wallet.dat | personal wallet (BDB) with keys and transactions; moved to wallets/ directory on new installs since 0.16.0 -wallets/database/* | BDB database environment; used for wallets since 0.16.0 -wallets/db.log | wallet database log file; since 0.16.0 -wallets/wallet.dat | personal wallet (BDB) with keys and transactions; since 0.16.0 -.cookie | session RPC authentication cookie (written at start when cookie authentication is used, deleted on shutdown): since 0.12.0 -onion_private_key | cached Tor hidden service private key for `-listenonion`: since 0.12.0 -guisettings.ini.bak | backup of former GUI settings after `-resetguisettings` is used - -Only used in pre-0.8.0 ---------------------- -* blktree/*; block chain index (LevelDB); since pre-0.8, replaced by blocks/index/* in 0.8.0 -* coins/*; unspent transaction output database (LevelDB); since pre-0.8, replaced by chainstate/* in 0.8.0 - -Only used before 0.8.0 ---------------------- -* blkindex.dat: block chain index database (BDB); replaced by {chainstate/*,blocks/index/*,blocks/rev000??.dat} in 0.8.0 -* blk000?.dat: block data (custom, 2 GiB per file); replaced by blocks/blk000??.dat in 0.8.0 - -Only used before 0.7.0 ---------------------- -* addr.dat: peer IP address database (BDB); replaced by peers.dat in 0.7.0 +# Bitcoin Core file system + +**Contents** + +- [Data directory location](#data-directory-location) + +- [Data directory layout](#data-directory-layout) + +- [Multi-wallet environment](#multi-wallet-environment) + +- [GUI settings](#gui-settings) + +- [Legacy subdirectories and files](#legacy-subdirectories-and-files) + +- [Notes](#notes) + +## Data directory location + +The data directory is the default location where the Bitcoin Core files are stored. + +1. The default data directory paths for supported platforms are: + +Platform | Data directory path +---------|-------------------- +Linux | `$HOME/.bitcoin/` +macOS | `$HOME/Library/Application Support/Bitcoin/` +Windows | `%APPDATA%\Bitcoin\` <sup>[\[1\]](#note1)</sup> + +2. The non-default data directory path can be specified by `-datadir` option. + +3. All content of the data directory, except for `bitcoin.conf` file, is chain-specific. This means the actual data directory paths for non-mainnet cases differ: + +Chain option | Data directory path +--------------------|-------------------- +no option (mainnet) | *path_to_datadir*`/` +`-testnet` | *path_to_datadir*`/testnet3/` +`-regtest` | *path_to_datadir*`/regtest/` + +## Data directory layout + +Subdirectory | File(s) | Description +-------------------|-----------------------|------------ +`blocks/` | | Blocks directory; can be specified by `-blocksdir` option (except for `blocks/index/`) +`blocks/index/` | LevelDB database | Block index; `-blocksdir` option does not affect this path +`blocks/` | `blkNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Actual Bitcoin blocks (in network format, dumped in raw on disk, 128 MiB per file) +`blocks/` | `revNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Block undo data (custom format) +`chainstate/` | LevelDB database | Blockchain state (a compact representation of all currently unspent transaction outputs and some metadata about the transactions they are from) +`indexes/txindex/` | LevelDB database | Transaction index; *optional*, used if `-txindex=1` +`indexes/blockfilter/basic/db/` | LevelDB database | Blockfilter index LevelDB database for the basic filtertype; *optional*, used if `-blockfilterindex=basic` +`indexes/blockfilter/basic/` | `fltrNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Blockfilter index filters for the basic filtertype; *optional*, used if `-blockfilterindex=basic` +`wallets/` | | [Contains wallets](#multi-wallet-environment); can be specified by `-walletdir` option; if `wallets/` subdirectory does not exist, a wallet resides in the data directory +`./` | `banlist.dat` | Stores the IPs/subnets of banned nodes +`./` | `bitcoin.conf` | Contains [configuration settings](bitcoin-conf.md) for `bitcoind` or `bitcoin-qt`; can be specified by `-conf` option +`./` | `bitcoind.pid` | Stores the process ID (PID) of `bitcoind` or `bitcoin-qt` while running; created at start and deleted on shutdown; can be specified by `-pid` option +`./` | `debug.log` | Contains debug information and general logging generated by `bitcoind` or `bitcoin-qt`; can be specified by `-debuglogfile` option +`./` | `fee_estimates.dat` | Stores statistics used to estimate minimum transaction fees and priorities required for confirmation +`./` | `guisettings.ini.bak` | Backup of former [GUI settings](#gui-settings) after `-resetguisettings` option is used +`./` | `mempool.dat` | Dump of the mempool's transactions +`./` | `onion_private_key` | Cached Tor hidden service private key for `-listenonion` option +`./` | `peers.dat` | Peer IP address database (custom format) +`./` | `.cookie` | Session RPC authentication cookie; if used, created at start and deleted on shutdown; can be specified by `-rpccookiefile` option +`./` | `.lock` | Data directory lock file + +## Multi-wallet environment + +Wallets are Berkeley DB (BDB) databases: + +Subdirectory | File(s) | Description +-------------|-------------------|------------ +`database/` | BDB logging files | Part of BDB environment; created at start and deleted on shutdown; a user *must keep it as safe* as personal wallet `wallet.dat` +`./` | `db.log` | BDB error file +`./` | `wallet.dat` | Personal wallet (BDB) with keys and transactions +`./` | `.walletlock` | Wallet lock file + +1. Each user-defined wallet named "wallet_name" resides in `wallets/wallet_name/` subdirectory. + +2. The default (unnamed) wallet resides in `wallets/` subdirectory; if the latter does not exist, the wallet resides in the data directory. + +3. A wallet database path can be specified by `-wallet` option. + +## GUI settings + +`bitcoin-qt` uses [`QSettings`](https://doc.qt.io/qt-5/qsettings.html) class; this implies platform-specific [locations where application settings are stored](https://doc.qt.io/qt-5/qsettings.html#locations-where-application-settings-are-stored). + +## Legacy subdirectories and files + +These subdirectories and files are no longer used by the Bitcoin Core: + +Path | Description | Repository notes +---------------|-------------|----------------- +`blktree/` | Blockchain index; replaced by `blocks/index/` in [0.8.0](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.8.0.md#improvements) | [PR #2231](https://github.com/bitcoin/bitcoin/pull/2231), [`8fdc94cc`](https://github.com/bitcoin/bitcoin/commit/8fdc94cc8f0341e96b1edb3a5b56811c0b20bd15) +`coins/` | Unspent transaction output database; replaced by `chainstate/` in 0.8.0 | [PR #2231](https://github.com/bitcoin/bitcoin/pull/2231), [`8fdc94cc`](https://github.com/bitcoin/bitcoin/commit/8fdc94cc8f0341e96b1edb3a5b56811c0b20bd15) +`blkindex.dat` | Blockchain index BDB database; replaced by {`chainstate/`, `blocks/index/`, `blocks/revNNNNN.dat`<sup>[\[2\]](#note2)</sup>} in 0.8.0 | [PR #1677](https://github.com/bitcoin/bitcoin/pull/1677) +`blk000?.dat` | Block data (custom format, 2 GiB per file); replaced by `blocks/blkNNNNN.dat`<sup>[\[2\]](#note2)</sup> in 0.8.0 | [PR #1677](https://github.com/bitcoin/bitcoin/pull/1677) +`addr.dat` | Peer IP address BDB database; replaced by `peers.dat` in [0.7.0](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.7.0.md) | [PR #1198](https://github.com/bitcoin/bitcoin/pull/1198), [`928d3a01`](https://github.com/bitcoin/bitcoin/commit/928d3a011cc66c7f907c4d053f674ea77dc611cc) + +## Notes + +<a name="note1">1</a>. The `/` (slash, U+002F) is used as the platform-independent path component separator in this paper. + +<a name="note2">2</a>. `NNNNN` matches `[0-9]{5}` regex. + diff --git a/src/Makefile.am b/src/Makefile.am index 84254e45d1..eec84122ae 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -208,6 +208,7 @@ BITCOIN_CORE_H = \ util/bytevectorhash.h \ util/error.h \ util/fees.h \ + util/spanparsing.h \ util/system.h \ util/macros.h \ util/memory.h \ @@ -505,6 +506,7 @@ libbitcoin_util_a_SOURCES = \ util/moneystr.cpp \ util/rbf.cpp \ util/threadnames.cpp \ + util/spanparsing.cpp \ util/strencodings.cpp \ util/string.cpp \ util/time.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index d3fe138133..48df50d100 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -22,7 +22,7 @@ FUZZ_TARGETS = \ test/fuzz/netaddr_deserialize \ test/fuzz/script_flags \ test/fuzz/service_deserialize \ - test/fuzz/transaction_deserialize \ + test/fuzz/transaction \ test/fuzz/txoutcompressor_deserialize \ test/fuzz/txundo_deserialize @@ -202,12 +202,6 @@ test_fuzz_block_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_block_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_block_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) -test_fuzz_transaction_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp -test_fuzz_transaction_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DTRANSACTION_DESERIALIZE=1 -test_fuzz_transaction_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -test_fuzz_transaction_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -test_fuzz_transaction_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) - test_fuzz_blocklocator_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp test_fuzz_blocklocator_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DBLOCKLOCATOR_DESERIALIZE=1 test_fuzz_blocklocator_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) @@ -321,6 +315,13 @@ test_fuzz_blocktransactionsrequest_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCO test_fuzz_blocktransactionsrequest_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_blocktransactionsrequest_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_blocktransactionsrequest_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) + +test_fuzz_transaction_SOURCES = $(FUZZ_SUITE) test/fuzz/transaction.cpp +test_fuzz_transaction_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_transaction_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_transaction_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_transaction_LDADD = $(FUZZ_SUITE_LD_COMMON) + endif # ENABLE_FUZZ nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 7671fde705..2878a8eb14 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -493,6 +493,7 @@ void BitcoinGUI::createMenuBar() window_menu->addSeparator(); for (RPCConsole::TabTypes tab_type : rpcConsole->tabs()) { QAction* tab_action = window_menu->addAction(rpcConsole->tabTitle(tab_type)); + tab_action->setShortcut(rpcConsole->tabShortcut(tab_type)); connect(tab_action, &QAction::triggered, [this, tab_type] { rpcConsole->setTabFocus(tab_type); showDebugWindow(); @@ -747,9 +748,9 @@ void BitcoinGUI::createTrayIconMenu() trayIconMenu->addAction(signMessageAction); trayIconMenu->addAction(verifyMessageAction); trayIconMenu->addSeparator(); - trayIconMenu->addAction(openRPCConsoleAction); } trayIconMenu->addAction(optionsAction); + trayIconMenu->addAction(openRPCConsoleAction); #ifndef Q_OS_MAC // This is built-in on macOS trayIconMenu->addSeparator(); trayIconMenu->addAction(quitAction); diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index eccc34e12f..4f6629bfe1 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -1276,6 +1276,16 @@ QString RPCConsole::tabTitle(TabTypes tab_type) const return ui->tabWidget->tabText(tab_type); } +QKeySequence RPCConsole::tabShortcut(TabTypes tab_type) const +{ + switch (tab_type) { + case TAB_INFO: return QKeySequence(Qt::CTRL + Qt::Key_I); + case TAB_CONSOLE: return QKeySequence(Qt::CTRL + Qt::Key_T); + case TAB_GRAPH: return QKeySequence(Qt::CTRL + Qt::Key_N); + case TAB_PEERS: return QKeySequence(Qt::CTRL + Qt::Key_P); + } +} + void RPCConsole::updateAlerts(const QString& warnings) { this->ui->label_alerts->setVisible(!warnings.isEmpty()); diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 3f7a74ba03..6b0f07baf1 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -68,6 +68,7 @@ public: std::vector<TabTypes> tabs() const { return {TAB_INFO, TAB_CONSOLE, TAB_GRAPH, TAB_PEERS}; } QString tabTitle(TabTypes tab_type) const; + QKeySequence tabShortcut(TabTypes tab_type) const; protected: virtual bool eventFilter(QObject* obj, QEvent *event); diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 9de90759fa..08ba030d65 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -93,10 +93,14 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface if (fAllFromMe && fAllToMe) { // Payment to self - CAmount nChange = wtx.change; + std::string address; + for (auto it = wtx.txout_address.begin(); it != wtx.txout_address.end(); ++it) { + if (it != wtx.txout_address.begin()) address += ", "; + address += EncodeDestination(*it); + } - parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", - -(nDebit - nChange), nCredit - nChange)); + CAmount nChange = wtx.change; + parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, address, -(nDebit - nChange), nCredit - nChange)); parts.last().involvesWatchAddress = involvesWatchAddress; // maybe pass to TransactionRecord as constructor argument } else if (fAllFromMe) diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 8d0cb54151..fed55577ca 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -394,6 +394,7 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, b case TransactionRecord::SendToOther: return QString::fromStdString(wtx->address) + watchAddress; case TransactionRecord::SendToSelf: + return lookupAddress(wtx->address, tooltip) + watchAddress; default: return tr("(n/a)") + watchAddress; } diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index b223349eb1..ed1bd4cda9 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -11,6 +11,7 @@ #include <span.h> #include <util/bip32.h> +#include <util/spanparsing.h> #include <util/system.h> #include <util/strencodings.h> @@ -640,63 +641,6 @@ enum class ParseScriptContext { P2WSH, }; -/** Parse a constant. If successful, sp is updated to skip the constant and return true. */ -bool Const(const std::string& str, Span<const char>& sp) -{ - if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) { - sp = sp.subspan(str.size()); - return true; - } - return false; -} - -/** Parse a function call. If successful, sp is updated to be the function's argument(s). */ -bool Func(const std::string& str, Span<const char>& sp) -{ - if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) { - sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2); - return true; - } - return false; -} - -/** Return the expression that sp begins with, and update sp to skip it. */ -Span<const char> Expr(Span<const char>& sp) -{ - int level = 0; - auto it = sp.begin(); - while (it != sp.end()) { - if (*it == '(') { - ++level; - } else if (level && *it == ')') { - --level; - } else if (level == 0 && (*it == ')' || *it == ',')) { - break; - } - ++it; - } - Span<const char> ret = sp.first(it - sp.begin()); - sp = sp.subspan(it - sp.begin()); - return ret; -} - -/** Split a string on every instance of sep, returning a vector. */ -std::vector<Span<const char>> Split(const Span<const char>& sp, char sep) -{ - std::vector<Span<const char>> ret; - auto it = sp.begin(); - auto start = it; - while (it != sp.end()) { - if (*it == sep) { - ret.emplace_back(start, it); - start = it + 1; - } - ++it; - } - ret.emplace_back(start, it); - return ret; -} - /** Parse a key path, being passed a split list of elements (the first element is ignored). */ NODISCARD bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, std::string& error) { @@ -723,6 +667,8 @@ NODISCARD bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& /** Parse a public key that excludes origin information. */ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out, std::string& error) { + using namespace spanparsing; + auto split = Split(sp, '/'); std::string str(split[0].begin(), split[0].end()); if (str.size() == 0) { @@ -782,6 +728,8 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, boo /** Parse a public key including origin information (if enabled). */ std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out, std::string& error) { + using namespace spanparsing; + auto origin_split = Split(sp, ']'); if (origin_split.size() > 2) { error = "Multiple ']' characters found for a single pubkey"; @@ -816,6 +764,8 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool per /** Parse a script in a particular context. */ std::unique_ptr<DescriptorImpl> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) { + using namespace spanparsing; + auto expr = Expr(sp); bool sorted_multi = false; if (Func("pk", expr)) { @@ -1012,6 +962,8 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo /** Check a descriptor checksum, and update desc to be the checksum-less part. */ bool CheckChecksum(Span<const char>& sp, bool require_checksum, std::string& error, std::string* out_checksum = nullptr) { + using namespace spanparsing; + auto check_split = Split(sp, '#'); if (check_split.size() > 2) { error = "Multiple '#' symbols"; diff --git a/src/script/descriptor.h b/src/script/descriptor.h index 0195ca0939..5a1b55259a 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -11,22 +11,24 @@ #include <vector> -// Descriptors are strings that describe a set of scriptPubKeys, together with -// all information necessary to solve them. By combining all information into -// one, they avoid the need to separately import keys and scripts. -// -// Descriptors may be ranged, which occurs when the public keys inside are -// specified in the form of HD chains (xpubs). -// -// Descriptors always represent public information - public keys and scripts - -// but in cases where private keys need to be conveyed along with a descriptor, -// they can be included inside by changing public keys to private keys (WIF -// format), and changing xpubs by xprvs. -// -// Reference documentation about the descriptor language can be found in -// doc/descriptors.md. - -/** Interface for parsed descriptor objects. */ + +/** \brief Interface for parsed descriptor objects. + * + * Descriptors are strings that describe a set of scriptPubKeys, together with + * all information necessary to solve them. By combining all information into + * one, they avoid the need to separately import keys and scripts. + * + * Descriptors may be ranged, which occurs when the public keys inside are + * specified in the form of HD chains (xpubs). + * + * Descriptors always represent public information - public keys and scripts - + * but in cases where private keys need to be conveyed along with a descriptor, + * they can be included inside by changing public keys to private keys (WIF + * format), and changing xpubs by xprvs. + * + * Reference documentation about the descriptor language can be found in + * doc/descriptors.md. + */ struct Descriptor { virtual ~Descriptor() = default; @@ -45,51 +47,51 @@ struct Descriptor { /** Expand a descriptor at a specified position. * - * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored. - * provider: the provider to query for private keys in case of hardened derivation. - * output_scripts: the expanded scriptPubKeys will be put here. - * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider). - * cache: vector which will be overwritten with cache data necessary to evaluate the descriptor at this point without access to private keys. + * @param[in] pos: The position at which to expand the descriptor. If IsRange() is false, this is ignored. + * @param[in] provider: The provider to query for private keys in case of hardened derivation. + * @param[out] output_scripts: The expanded scriptPubKeys. + * @param[out] out: Scripts and public keys necessary for solving the expanded scriptPubKeys (may be equal to `provider`). + * @param[out] cache: Cache data necessary to evaluate the descriptor at this point without access to private keys. */ virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache = nullptr) const = 0; /** Expand a descriptor at a specified position using cached expansion data. * - * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored. - * cache: vector from which cached expansion data will be read. - * output_scripts: the expanded scriptPubKeys will be put here. - * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider). + * @param[in] pos: The position at which to expand the descriptor. If IsRange() is false, this is ignored. + * @param[in] cache: Cached expansion data. + * @param[out] output_scripts: The expanded scriptPubKeys. + * @param[out] out: Scripts and public keys necessary for solving the expanded scriptPubKeys (may be equal to `provider`). */ virtual bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0; /** Expand the private key for a descriptor at a specified position, if possible. * - * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored. - * provider: the provider to query for the private keys. - * out: any private keys available for the specified pos will be placed here. + * @param[in] pos: The position at which to expand the descriptor. If IsRange() is false, this is ignored. + * @param[in] provider: The provider to query for the private keys. + * @param[out] out: Any private keys available for the specified `pos`. */ virtual void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const = 0; }; -/** Parse a descriptor string. Included private keys are put in out. +/** Parse a `descriptor` string. Included private keys are put in `out`. * - * If the descriptor has a checksum, it must be valid. If require_checksum + * If the descriptor has a checksum, it must be valid. If `require_checksum` * is set, the checksum is mandatory - otherwise it is optional. * * If a parse error occurs, or the checksum is missing/invalid, or anything - * else is wrong, nullptr is returned. + * else is wrong, `nullptr` is returned. */ std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out, std::string& error, bool require_checksum = false); -/** Get the checksum for a descriptor. +/** Get the checksum for a `descriptor`. * - * If it already has one, and it is correct, return the checksum in the input. - * If it already has one that is wrong, return "". - * If it does not already have one, return the checksum that would need to be added. + * - If it already has one, and it is correct, return the checksum in the input. + * - If it already has one that is wrong, return "". + * - If it does not already have one, return the checksum that would need to be added. */ std::string GetDescriptorChecksum(const std::string& descriptor); -/** Find a descriptor for the specified script, using information from provider where possible. +/** Find a descriptor for the specified `script`, using information from `provider` where possible. * * A non-ranged descriptor which only generates the specified script will be returned in all * circumstances. @@ -98,9 +100,9 @@ std::string GetDescriptorChecksum(const std::string& descriptor); * descriptor. * * - If all information for solving `script` is present in `provider`, a descriptor will be returned - * which is `IsSolvable()` and encapsulates said information. + * which is IsSolvable() and encapsulates said information. * - Failing that, if `script` corresponds to a known address type, an "addr()" descriptor will be - * returned (which is not `IsSolvable()`). + * returned (which is not IsSolvable()). * - Failing that, a "raw()" descriptor is returned. */ std::unique_ptr<Descriptor> InferDescriptor(const CScript& script, const SigningProvider& provider); diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index 3a74143dc2..3a6876ad39 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.cpp @@ -40,11 +40,6 @@ void test_one_input(const std::vector<uint8_t>& buffer) CBlock block; ds >> block; } catch (const std::ios_base::failure& e) {return;} -#elif TRANSACTION_DESERIALIZE - try - { - CTransaction tx(deserialize, ds); - } catch (const std::ios_base::failure& e) {return;} #elif BLOCKLOCATOR_DESERIALIZE try { diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp new file mode 100644 index 0000000000..96d7947b07 --- /dev/null +++ b/src/test/fuzz/transaction.cpp @@ -0,0 +1,81 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <coins.h> +#include <consensus/tx_check.h> +#include <consensus/tx_verify.h> +#include <consensus/validation.h> +#include <core_io.h> +#include <core_memusage.h> +#include <policy/policy.h> +#include <policy/settings.h> +#include <primitives/transaction.h> +#include <streams.h> +#include <test/fuzz/fuzz.h> +#include <util/rbf.h> +#include <validation.h> +#include <version.h> + +#include <cassert> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); + try { + int nVersion; + ds >> nVersion; + ds.SetVersion(nVersion); + } catch (const std::ios_base::failure& e) { + return; + } + bool valid = true; + const CTransaction tx = [&] { + try { + return CTransaction(deserialize, ds); + } catch (const std::ios_base::failure& e) { + valid = false; + return CTransaction(); + } + }(); + if (!valid) { + return; + } + + CValidationState state_with_dupe_check; + const bool valid_with_dupe_check = CheckTransaction(tx, state_with_dupe_check, /* fCheckDuplicateInputs= */ true); + CValidationState state_without_dupe_check; + const bool valid_without_dupe_check = CheckTransaction(tx, state_without_dupe_check, /* fCheckDuplicateInputs= */ false); + if (valid_with_dupe_check) { + assert(valid_without_dupe_check); + } + + const CFeeRate dust_relay_fee{DUST_RELAY_TX_FEE}; + std::string reason; + const bool is_standard_with_permit_bare_multisig = IsStandardTx(tx, /* permit_bare_multisig= */ true, dust_relay_fee, reason); + const bool is_standard_without_permit_bare_multisig = IsStandardTx(tx, /* permit_bare_multisig= */ false, dust_relay_fee, reason); + if (is_standard_without_permit_bare_multisig) { + assert(is_standard_with_permit_bare_multisig); + } + + (void)tx.GetHash(); + (void)tx.GetTotalSize(); + try { + (void)tx.GetValueOut(); + } catch (const std::runtime_error&) { + } + (void)tx.GetWitnessHash(); + (void)tx.HasWitness(); + (void)tx.IsCoinBase(); + (void)tx.IsNull(); + (void)tx.ToString(); + + (void)EncodeHexTx(tx); + (void)GetLegacySigOpCount(tx); + (void)GetTransactionWeight(tx); + (void)GetVirtualTransactionSize(tx); + (void)IsFinalTx(tx, /* nBlockHeight= */ 1024, /* nBlockTime= */ 1024); + (void)IsStandardTx(tx, reason); + (void)RecursiveDynamicUsage(tx); + (void)SignalsOptInRBF(tx); +} diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index d0cd4b0a03..31a66b6fa9 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -12,6 +12,7 @@ #include <util/strencodings.h> #include <util/string.h> #include <util/time.h> +#include <util/spanparsing.h> #include <stdint.h> #include <thread> @@ -1572,4 +1573,127 @@ BOOST_AUTO_TEST_CASE(test_Capitalize) BOOST_CHECK_EQUAL(Capitalize("\x00\xfe\xff"), "\x00\xfe\xff"); } +static std::string SpanToStr(Span<const char>& span) +{ + return std::string(span.begin(), span.end()); +} + +BOOST_AUTO_TEST_CASE(test_spanparsing) +{ + using namespace spanparsing; + std::string input; + Span<const char> sp; + bool success; + + // Const(...): parse a constant, update span to skip it if successful + input = "MilkToastHoney"; + sp = MakeSpan(input); + success = Const("", sp); // empty + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), "MilkToastHoney"); + + success = Const("Milk", sp); + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), "ToastHoney"); + + success = Const("Bread", sp); + BOOST_CHECK(!success); + + success = Const("Toast", sp); + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), "Honey"); + + success = Const("Honeybadger", sp); + BOOST_CHECK(!success); + + success = Const("Honey", sp); + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), ""); + + // Func(...): parse a function call, update span to argument if successful + input = "Foo(Bar(xy,z()))"; + sp = MakeSpan(input); + + success = Func("FooBar", sp); + BOOST_CHECK(!success); + + success = Func("Foo(", sp); + BOOST_CHECK(!success); + + success = Func("Foo", sp); + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), "Bar(xy,z())"); + + success = Func("Bar", sp); + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), "xy,z()"); + + success = Func("xy", sp); + BOOST_CHECK(!success); + + // Expr(...): return expression that span begins with, update span to skip it + Span<const char> result; + + input = "(n*(n-1))/2"; + sp = MakeSpan(input); + result = Expr(sp); + BOOST_CHECK_EQUAL(SpanToStr(result), "(n*(n-1))/2"); + BOOST_CHECK_EQUAL(SpanToStr(sp), ""); + + input = "foo,bar"; + sp = MakeSpan(input); + result = Expr(sp); + BOOST_CHECK_EQUAL(SpanToStr(result), "foo"); + BOOST_CHECK_EQUAL(SpanToStr(sp), ",bar"); + + input = "(aaaaa,bbbbb()),c"; + sp = MakeSpan(input); + result = Expr(sp); + BOOST_CHECK_EQUAL(SpanToStr(result), "(aaaaa,bbbbb())"); + BOOST_CHECK_EQUAL(SpanToStr(sp), ",c"); + + input = "xyz)foo"; + sp = MakeSpan(input); + result = Expr(sp); + BOOST_CHECK_EQUAL(SpanToStr(result), "xyz"); + BOOST_CHECK_EQUAL(SpanToStr(sp), ")foo"); + + input = "((a),(b),(c)),xxx"; + sp = MakeSpan(input); + result = Expr(sp); + BOOST_CHECK_EQUAL(SpanToStr(result), "((a),(b),(c))"); + BOOST_CHECK_EQUAL(SpanToStr(sp), ",xxx"); + + // Split(...): split a string on every instance of sep, return vector + std::vector<Span<const char>> results; + + input = "xxx"; + results = Split(MakeSpan(input), 'x'); + BOOST_CHECK_EQUAL(results.size(), 4); + BOOST_CHECK_EQUAL(SpanToStr(results[0]), ""); + BOOST_CHECK_EQUAL(SpanToStr(results[1]), ""); + BOOST_CHECK_EQUAL(SpanToStr(results[2]), ""); + BOOST_CHECK_EQUAL(SpanToStr(results[3]), ""); + + input = "one#two#three"; + results = Split(MakeSpan(input), '-'); + BOOST_CHECK_EQUAL(results.size(), 1); + BOOST_CHECK_EQUAL(SpanToStr(results[0]), "one#two#three"); + + input = "one#two#three"; + results = Split(MakeSpan(input), '#'); + BOOST_CHECK_EQUAL(results.size(), 3); + BOOST_CHECK_EQUAL(SpanToStr(results[0]), "one"); + BOOST_CHECK_EQUAL(SpanToStr(results[1]), "two"); + BOOST_CHECK_EQUAL(SpanToStr(results[2]), "three"); + + input = "*foo*bar*"; + results = Split(MakeSpan(input), '*'); + BOOST_CHECK_EQUAL(results.size(), 4); + BOOST_CHECK_EQUAL(SpanToStr(results[0]), ""); + BOOST_CHECK_EQUAL(SpanToStr(results[1]), "foo"); + BOOST_CHECK_EQUAL(SpanToStr(results[2]), "bar"); + BOOST_CHECK_EQUAL(SpanToStr(results[3]), ""); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/spanparsing.cpp b/src/util/spanparsing.cpp new file mode 100644 index 0000000000..0c8575399a --- /dev/null +++ b/src/util/spanparsing.cpp @@ -0,0 +1,67 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <util/spanparsing.h> + +#include <span.h> + +#include <string> +#include <vector> + +namespace spanparsing { + +bool Const(const std::string& str, Span<const char>& sp) +{ + if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) { + sp = sp.subspan(str.size()); + return true; + } + return false; +} + +bool Func(const std::string& str, Span<const char>& sp) +{ + if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) { + sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2); + return true; + } + return false; +} + +Span<const char> Expr(Span<const char>& sp) +{ + int level = 0; + auto it = sp.begin(); + while (it != sp.end()) { + if (*it == '(') { + ++level; + } else if (level && *it == ')') { + --level; + } else if (level == 0 && (*it == ')' || *it == ',')) { + break; + } + ++it; + } + Span<const char> ret = sp.first(it - sp.begin()); + sp = sp.subspan(it - sp.begin()); + return ret; +} + +std::vector<Span<const char>> Split(const Span<const char>& sp, char sep) +{ + std::vector<Span<const char>> ret; + auto it = sp.begin(); + auto start = it; + while (it != sp.end()) { + if (*it == sep) { + ret.emplace_back(start, it); + start = it + 1; + } + ++it; + } + ret.emplace_back(start, it); + return ret; +} + +} // namespace spanparsing diff --git a/src/util/spanparsing.h b/src/util/spanparsing.h new file mode 100644 index 0000000000..63f54758bd --- /dev/null +++ b/src/util/spanparsing.h @@ -0,0 +1,50 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_SPANPARSING_H +#define BITCOIN_UTIL_SPANPARSING_H + +#include <span.h> + +#include <string> +#include <vector> + +namespace spanparsing { + +/** Parse a constant. + * + * If sp's initial part matches str, sp is updated to skip that part, and true is returned. + * Otherwise sp is unmodified and false is returned. + */ +bool Const(const std::string& str, Span<const char>& sp); + +/** Parse a function call. + * + * If sp's initial part matches str + "(", and sp ends with ")", sp is updated to be the + * section between the braces, and true is returned. Otherwise sp is unmodified and false + * is returned. + */ +bool Func(const std::string& str, Span<const char>& sp); + +/** Extract the expression that sp begins with. + * + * This function will return the initial part of sp, up to (but not including) the first + * comma or closing brace, skipping ones that are surrounded by braces. So for example, + * for "foo(bar(1),2),3" the initial part "foo(bar(1),2)" will be returned. sp will be + * updated to skip the initial part that is returned. + */ +Span<const char> Expr(Span<const char>& sp); + +/** Split a string on every instance of sep, returning a vector. + * + * If sep does not occur in sp, a singleton with the entirety of sp is returned. + * + * Note that this function does not care about braces, so splitting + * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}. + */ +std::vector<Span<const char>> Split(const Span<const char>& sp, char sep); + +} // namespace spanparsing + +#endif // BITCOIN_UTIL_SPANPARSING_H diff --git a/src/util/system.cpp b/src/util/system.cpp index 8098cde093..f22256615f 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1153,12 +1153,12 @@ void SetupEnvironment() } #endif // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale - // may be invalid, in which case the "C" locale is used as fallback. + // may be invalid, in which case the "C.UTF-8" locale is used as fallback. #if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) try { std::locale(""); // Raises a runtime error if current locale is invalid } catch (const std::runtime_error&) { - setenv("LC_ALL", "C", 1); + setenv("LC_ALL", "C.UTF-8", 1); } #elif defined(WIN32) // Set the default input/output charset is utf-8 diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0904c03669..dc437e5cd8 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1190,7 +1190,7 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) RPCResult{ "[\n" " {\n" - " \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n" + " \"involvesWatchonly\" : true, (bool) Only returns true if imported addresses were involved in transaction.\n" " \"address\" : \"receivingaddress\", (string) The receiving address\n" " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " received by the address\n" " \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n" @@ -1240,7 +1240,7 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) RPCResult{ "[\n" " {\n" - " \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n" + " \"involvesWatchonly\" : true, (bool) Only returns true if imported addresses were involved in transaction.\n" " \"amount\" : x.xxx, (numeric) The total amount received by addresses with this label\n" " \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n" " \"label\" : \"label\" (string) The label of the receiving address. The default label is \"\".\n" @@ -1359,6 +1359,27 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* con } } +static const std::string TransactionDescriptionString() +{ + return " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Negative confirmations means the\n" + " transaction conflicted that many blocks ago.\n" + " \"generated\": xxx, (bool) Only present if transaction only input is a coinbase one.\n" + " \"trusted\": xxx, (bool) Only present if we consider transaction to be trusted and so safe to spend from.\n" + " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n" + " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n" + " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" + " \"txid\": \"transactionid\", (string) The transaction id.\n" + " \"walletconflicts\": [ (array) Conflicting transaction ids.\n" + " \"txid\", (string) The transaction id.\n" + " ...\n" + " ],\n" + " \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n" + " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT).\n" + " \"comment\": \"...\", (string) If a comment is associated with the transaction, only present if not empty.\n" + " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" + " may be unknown for unconfirmed transactions not in the mempool\n"; +} + UniValue listtransactions(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -1381,6 +1402,7 @@ UniValue listtransactions(const JSONRPCRequest& request) RPCResult{ "[\n" " {\n" + " \"involvesWatchonly\": xxx, (bool) Only returns true if imported addresses were involved in transaction.\n" " \"address\":\"address\", (string) The bitcoin address of the transaction.\n" " \"category\": (string) The transaction category.\n" " \"send\" Transactions sent.\n" @@ -1394,19 +1416,8 @@ UniValue listtransactions(const JSONRPCRequest& request) " \"vout\": n, (numeric) the vout value\n" " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" " 'send' category of transactions.\n" - " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Negative confirmations indicate the\n" - " transaction conflicts with the block chain\n" - " \"trusted\": xxx, (bool) Whether we consider the outputs of this unconfirmed transaction safe to spend.\n" - " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n" - " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n" - " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" - " \"txid\": \"transactionid\", (string) The transaction id.\n" - " \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n" - " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT).\n" - " \"comment\": \"...\", (string) If a comment is associated with the transaction.\n" - " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" - " may be unknown for unconfirmed transactions not in the mempool\n" - " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n" + + TransactionDescriptionString() + + " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n" " 'send' category of transactions.\n" " }\n" "]\n" @@ -1515,6 +1526,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) RPCResult{ "{\n" " \"transactions\": [\n" + " \"involvesWatchonly\": xxx, (bool) Only returns true if imported addresses were involved in transaction.\n" " \"address\":\"address\", (string) The bitcoin address of the transaction.\n" " \"category\": (string) The transaction category.\n" " \"send\" Transactions sent.\n" @@ -1526,17 +1538,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) " for all other categories\n" " \"vout\" : n, (numeric) the vout value\n" " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the 'send' category of transactions.\n" - " \"confirmations\": n, (numeric) The number of confirmations for the transaction.\n" - " When it's < 0, it means the transaction conflicted that many blocks ago.\n" - " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n" - " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n" - " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" - " \"txid\": \"transactionid\", (string) The transaction id.\n" - " \"time\": xxx, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT).\n" - " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT).\n" - " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" - " may be unknown for unconfirmed transactions not in the mempool\n" - " \"abandoned\": xxx, (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the 'send' category of transactions.\n" + + TransactionDescriptionString() + + " \"abandoned\": xxx, (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the 'send' category of transactions.\n" " \"comment\": \"...\", (string) If a comment is associated with the transaction.\n" " \"label\" : \"label\" (string) A comment for the address/transaction, if any\n" " \"to\": \"...\", (string) If a comment to is associated with the transaction.\n" @@ -1655,40 +1658,33 @@ static UniValue gettransaction(const JSONRPCRequest& request) }, RPCResult{ "{\n" - " \"amount\" : x.xxx, (numeric) The transaction amount in " + CURRENCY_UNIT + "\n" - " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" + " \"amount\" : x.xxx, (numeric) The transaction amount in " + CURRENCY_UNIT + "\n" + " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" " 'send' category of transactions.\n" - " \"confirmations\" : n, (numeric) The number of confirmations\n" - " \"blockhash\" : \"hash\", (string) The block hash\n" - " \"blockindex\" : xx, (numeric) The index of the transaction in the block that includes it\n" - " \"blocktime\" : ttt, (numeric) The time in seconds since epoch (1 Jan 1970 GMT)\n" - " \"txid\" : \"transactionid\", (string) The transaction id.\n" - " \"time\" : ttt, (numeric) The transaction time in seconds since epoch (1 Jan 1970 GMT)\n" - " \"timereceived\" : ttt, (numeric) The time received in seconds since epoch (1 Jan 1970 GMT)\n" - " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" - " may be unknown for unconfirmed transactions not in the mempool\n" - " \"details\" : [\n" - " {\n" - " \"address\" : \"address\", (string) The bitcoin address involved in the transaction\n" - " \"category\" : (string) The transaction category.\n" - " \"send\" Transactions sent.\n" - " \"receive\" Non-coinbase transactions received.\n" - " \"generate\" Coinbase transactions received with more than 100 confirmations.\n" - " \"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" - " \"orphan\" Orphaned coinbase transactions received.\n" - " \"amount\" : x.xxx, (numeric) The amount in " + CURRENCY_UNIT + "\n" - " \"label\" : \"label\", (string) A comment for the address/transaction, if any\n" - " \"vout\" : n, (numeric) the vout value\n" - " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" + + TransactionDescriptionString() + + " \"details\" : [\n" + " {\n" + " \"involvesWatchonly\": xxx, (bool) Only returns true if imported addresses were involved in transaction.\n" + " \"address\" : \"address\", (string) The bitcoin address involved in the transaction\n" + " \"category\" : (string) The transaction category.\n" + " \"send\" Transactions sent.\n" + " \"receive\" Non-coinbase transactions received.\n" + " \"generate\" Coinbase transactions received with more than 100 confirmations.\n" + " \"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" + " \"orphan\" Orphaned coinbase transactions received.\n" + " \"amount\" : x.xxx, (numeric) The amount in " + CURRENCY_UNIT + "\n" + " \"label\" : \"label\", (string) A comment for the address/transaction, if any\n" + " \"vout\" : n, (numeric) the vout value\n" + " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" " 'send' category of transactions.\n" - " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n" + " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n" " 'send' category of transactions.\n" - " }\n" - " ,...\n" - " ],\n" - " \"hex\" : \"data\" (string) Raw data for transaction\n" - " \"decoded\" : transaction (json object) Optional, the decoded transaction (only present when `verbose` is passed), equivalent to the\n" - " RPC decoderawtransaction method, or the RPC getrawtransaction method when `verbose` is passed.\n" + " }\n" + " ,...\n" + " ],\n" + " \"hex\" : \"data\" (string) Raw data for transaction\n" + " \"decoded\" : transaction (json object) Optional, the decoded transaction (only present when `verbose` is passed), equivalent to the\n" + " RPC decoderawtransaction method, or the RPC getrawtransaction method when `verbose` is passed.\n" "}\n" }, RPCExamples{ diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index fc3be2b6ab..73523ca36d 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -338,6 +338,84 @@ BOOST_AUTO_TEST_CASE(LoadReceiveRequests) BOOST_CHECK_EQUAL(values[1], "val_rr1"); } +// Test some watch-only wallet methods by the procedure of loading (LoadWatchOnly), +// checking (HaveWatchOnly), getting (GetWatchPubKey) and removing (RemoveWatchOnly) a +// given PubKey, resp. its corresponding P2PK Script. Results of the the impact on +// the address -> PubKey map is dependent on whether the PubKey is a point on the curve +static void TestWatchOnlyPubKey(CWallet& wallet, const CPubKey& add_pubkey) +{ + CScript p2pk = GetScriptForRawPubKey(add_pubkey); + CKeyID add_address = add_pubkey.GetID(); + CPubKey found_pubkey; + LOCK(wallet.cs_wallet); + + // all Scripts (i.e. also all PubKeys) are added to the general watch-only set + BOOST_CHECK(!wallet.HaveWatchOnly(p2pk)); + wallet.LoadWatchOnly(p2pk); + BOOST_CHECK(wallet.HaveWatchOnly(p2pk)); + + // only PubKeys on the curve shall be added to the watch-only address -> PubKey map + bool is_pubkey_fully_valid = add_pubkey.IsFullyValid(); + if (is_pubkey_fully_valid) { + BOOST_CHECK(wallet.GetWatchPubKey(add_address, found_pubkey)); + BOOST_CHECK(found_pubkey == add_pubkey); + } else { + BOOST_CHECK(!wallet.GetWatchPubKey(add_address, found_pubkey)); + BOOST_CHECK(found_pubkey == CPubKey()); // passed key is unchanged + } + + wallet.RemoveWatchOnly(p2pk); + BOOST_CHECK(!wallet.HaveWatchOnly(p2pk)); + + if (is_pubkey_fully_valid) { + BOOST_CHECK(!wallet.GetWatchPubKey(add_address, found_pubkey)); + BOOST_CHECK(found_pubkey == add_pubkey); // passed key is unchanged + } +} + +// Cryptographically invalidate a PubKey whilst keeping length and first byte +static void PollutePubKey(CPubKey& pubkey) +{ + std::vector<unsigned char> pubkey_raw(pubkey.begin(), pubkey.end()); + std::fill(pubkey_raw.begin()+1, pubkey_raw.end(), 0); + pubkey = CPubKey(pubkey_raw); + assert(!pubkey.IsFullyValid()); + assert(pubkey.IsValid()); +} + +// Test watch-only wallet logic for PubKeys +BOOST_AUTO_TEST_CASE(WatchOnlyPubKeys) +{ + CKey key; + CPubKey pubkey; + + BOOST_CHECK(!m_wallet.HaveWatchOnly()); + + // uncompressed valid PubKey + key.MakeNewKey(false); + pubkey = key.GetPubKey(); + assert(!pubkey.IsCompressed()); + TestWatchOnlyPubKey(m_wallet, pubkey); + + // uncompressed cryptographically invalid PubKey + PollutePubKey(pubkey); + TestWatchOnlyPubKey(m_wallet, pubkey); + + // compressed valid PubKey + key.MakeNewKey(true); + pubkey = key.GetPubKey(); + assert(pubkey.IsCompressed()); + TestWatchOnlyPubKey(m_wallet, pubkey); + + // compressed cryptographically invalid PubKey + PollutePubKey(pubkey); + TestWatchOnlyPubKey(m_wallet, pubkey); + + // invalid empty PubKey + pubkey = CPubKey(); + TestWatchOnlyPubKey(m_wallet, pubkey); +} + class ListCoinsTestingSetup : public TestChain100Setup { public: diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index 367d0f6916..a70d9c4ac1 100755 --- a/test/functional/combine_logs.py +++ b/test/functional/combine_logs.py @@ -8,7 +8,6 @@ If no argument is provided, the most recent test directory will be used.""" import argparse from collections import defaultdict, namedtuple -import glob import heapq import itertools import os @@ -78,10 +77,11 @@ def read_logs(tmp_dir): for each of the input log files.""" # Find out what the folder is called that holds the debug.log file - chain = glob.glob("{}/node0/*/debug.log".format(tmp_dir)) - if chain: - chain = chain[0] # pick the first one if more than one chain was found (should never happen) - chain = re.search(r'node0/(.+?)/debug\.log$', chain).group(1) # extract the chain name + glob = pathlib.Path(tmp_dir).glob('node0/**/debug.log') + path = next(glob, None) + if path: + assert next(glob, None) is None # more than one debug.log, should never happen + chain = re.search(r'node0/(.+?)/debug\.log$', path.as_posix()).group(1) # extract the chain name else: chain = 'regtest' # fallback to regtest (should only happen when none exists) diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py index 93178c5ab2..bb835dc811 100755 --- a/test/functional/wallet_backup.py +++ b/test/functional/wallet_backup.py @@ -48,7 +48,13 @@ class WalletBackupTest(BitcoinTestFramework): self.num_nodes = 4 self.setup_clean_chain = True # nodes 1, 2,3 are spenders, let's give them a keypool=100 - self.extra_args = [["-keypool=100"], ["-keypool=100"], ["-keypool=100"], []] + # whitelist all peers to speed up tx relay / mempool sync + self.extra_args = [ + ["-keypool=100", "-whitelist=127.0.0.1"], + ["-keypool=100", "-whitelist=127.0.0.1"], + ["-keypool=100", "-whitelist=127.0.0.1"], + ["-whitelist=127.0.0.1"] + ] self.rpc_timeout = 120 def skip_test_if_missing_module(self): |