aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml22
-rw-r--r--Makefile.am9
-rwxr-xr-xci/lint/04_install.sh21
-rw-r--r--depends/Makefile4
-rw-r--r--doc/files.md139
-rw-r--r--src/Makefile.am2
-rw-r--r--src/Makefile.test.include15
-rw-r--r--src/qt/bitcoingui.cpp3
-rw-r--r--src/qt/rpcconsole.cpp10
-rw-r--r--src/qt/rpcconsole.h1
-rw-r--r--src/qt/transactionrecord.cpp10
-rw-r--r--src/qt/transactiontablemodel.cpp1
-rw-r--r--src/script/descriptor.cpp66
-rw-r--r--src/script/descriptor.h78
-rw-r--r--src/test/fuzz/deserialize.cpp5
-rw-r--r--src/test/fuzz/transaction.cpp81
-rw-r--r--src/test/util_tests.cpp124
-rw-r--r--src/util/spanparsing.cpp67
-rw-r--r--src/util/spanparsing.h50
-rw-r--r--src/util/system.cpp4
-rw-r--r--src/wallet/rpcwallet.cpp110
-rw-r--r--src/wallet/test/wallet_tests.cpp78
-rwxr-xr-xtest/functional/combine_logs.py10
-rwxr-xr-xtest/functional/wallet_backup.py8
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):