aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml1
-rwxr-xr-xci/test/00_setup_env_native_asan.sh2
-rw-r--r--configure.ac16
-rwxr-xr-xcontrib/guix/libexec/build.sh8
-rw-r--r--contrib/guix/libexec/prelude.bash2
-rw-r--r--contrib/guix/manifest.scm9
-rw-r--r--doc/README.md1
-rw-r--r--doc/build-netbsd.md37
-rw-r--r--doc/cjdns.md95
-rw-r--r--doc/i2p.md10
-rw-r--r--doc/tor.md11
-rw-r--r--src/Makefile.am1
-rw-r--r--src/bench/coin_selection.cpp24
-rw-r--r--src/core_write.cpp4
-rw-r--r--src/fs.h18
-rw-r--r--src/init.cpp7
-rw-r--r--src/net.h13
-rw-r--r--src/net_processing.cpp2
-rw-r--r--src/node/miner.h2
-rw-r--r--src/qt/optionsmodel.cpp5
-rw-r--r--src/rpc/rawtransaction.cpp154
-rw-r--r--src/rpc/register.h9
-rw-r--r--src/rpc/txoutproof.cpp183
-rw-r--r--src/scheduler.cpp2
-rw-r--r--src/test/net_tests.cpp5
-rw-r--r--src/txmempool.cpp16
-rw-r--r--src/txmempool.h8
-rw-r--r--src/wallet/bdb.cpp17
-rw-r--r--src/wallet/bdb.h12
-rw-r--r--src/wallet/coinselection.cpp57
-rw-r--r--src/wallet/coinselection.h142
-rw-r--r--src/wallet/db.cpp10
-rw-r--r--src/wallet/db.h9
-rw-r--r--src/wallet/dump.cpp11
-rw-r--r--src/wallet/dump.h5
-rw-r--r--src/wallet/feebumper.cpp6
-rw-r--r--src/wallet/init.cpp4
-rw-r--r--src/wallet/interfaces.cpp17
-rw-r--r--src/wallet/load.cpp3
-rw-r--r--src/wallet/rpc/coins.cpp24
-rw-r--r--src/wallet/rpc/wallet.cpp3
-rw-r--r--src/wallet/salvage.cpp3
-rw-r--r--src/wallet/salvage.h3
-rw-r--r--src/wallet/spend.cpp112
-rw-r--r--src/wallet/spend.h57
-rw-r--r--src/wallet/sqlite.cpp8
-rw-r--r--src/wallet/sqlite.h3
-rw-r--r--src/wallet/test/coinselector_tests.cpp107
-rw-r--r--src/wallet/test/db_tests.cpp10
-rw-r--r--src/wallet/test/init_test_fixture.cpp9
-rw-r--r--src/wallet/test/init_tests.cpp8
-rw-r--r--src/wallet/test/wallet_tests.cpp20
-rw-r--r--src/wallet/wallet.cpp1
-rw-r--r--src/wallet/walletdb.cpp5
-rw-r--r--src/wallet/wallettool.cpp9
-rwxr-xr-xtest/functional/feature_proxy.py40
-rwxr-xr-xtest/functional/feature_pruning.py4
-rwxr-xr-xtest/functional/interface_zmq.py278
-rwxr-xr-xtest/functional/mempool_package_onemore.py58
-rwxr-xr-xtest/functional/rpc_createmultisig.py103
-rw-r--r--test/functional/test_framework/wallet.py43
-rwxr-xr-xtest/functional/test_runner.py12
62 files changed, 1062 insertions, 796 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index e07ff9796e..e0ab1536a0 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -2,6 +2,7 @@ env: # Global defaults
PACKAGE_MANAGER_INSTALL: "apt-get update && apt-get install -y"
MAKEJOBS: "-j10"
TEST_RUNNER_PORT_MIN: "14000" # Must be larger than 12321, which is used for the http cache. See https://cirrus-ci.org/guide/writing-tasks/#http-cache
+ CI_FAILFAST_TEST_LEAVE_DANGLING: "1" # Cirrus CI does not care about dangling process and setting this variable avoids killing the CI script itself on error
CCACHE_SIZE: "200M"
CCACHE_DIR: "/tmp/ccache_dir"
CCACHE_NOHASHDIR: "1" # Debug info might contain a stale path if the build dir changes, but this is fine
diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh
index b03a7edb54..69883e3609 100755
--- a/ci/test/00_setup_env_native_asan.sh
+++ b/ci/test/00_setup_env_native_asan.sh
@@ -11,4 +11,4 @@ export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent-
export DOCKER_NAME_TAG=ubuntu:22.04
export NO_DEPENDS=1
export GOAL="install"
-export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++"
+export BITCOIN_CONFIG="--enable-c++20 --enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++"
diff --git a/configure.ac b/configure.ac
index 63ff6a1ebd..298b72a381 100644
--- a/configure.ac
+++ b/configure.ac
@@ -78,8 +78,18 @@ AC_ARG_WITH([seccomp],
[seccomp_found=$withval],
[seccomp_found=auto])
+AC_ARG_ENABLE([c++20],
+ [AS_HELP_STRING([--enable-c++20],
+ [enable compilation in c++20 mode (disabled by default)])],
+ [use_cxx20=$enableval],
+ [use_cxx20=no])
+
dnl Require C++17 compiler (no GNU extensions)
+if test "$use_cxx20" = "no"; then
AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory])
+else
+AX_CXX_COMPILE_STDCXX([20], [noext], [mandatory])
+fi
dnl Check if -latomic is required for <std::atomic>
CHECK_ATOMIC
@@ -96,10 +106,8 @@ fi
AC_PROG_OBJCXX
])
-dnl Since libtool 1.5.2 (released 2004-01-25), on Linux libtool no longer
-dnl sets RPATH for any directories in the dynamic linker search path.
-dnl See more: https://wiki.debian.org/RpathIssue
-LT_PREREQ([1.5.2])
+dnl OpenBSD ships with 2.4.2
+LT_PREREQ([2.4.2])
dnl Libtool init checks.
LT_INIT([pic-only win32-dll])
diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh
index 20a8a4f070..4eeb360603 100755
--- a/contrib/guix/libexec/build.sh
+++ b/contrib/guix/libexec/build.sh
@@ -167,7 +167,6 @@ case "$HOST" in
*linux*)
glibc_dynamic_linker=$(
case "$HOST" in
- i686-linux-gnu) echo /lib/ld-linux.so.2 ;;
x86_64-linux-gnu) echo /lib64/ld-linux-x86-64.so.2 ;;
arm-linux-gnueabihf) echo /lib/ld-linux-armhf.so.3 ;;
aarch64-linux-gnu) echo /lib/ld-linux-aarch64.so.1 ;;
@@ -204,19 +203,12 @@ make -C depends --jobs="$JOBS" HOST="$HOST" \
${SOURCES_PATH+SOURCES_PATH="$SOURCES_PATH"} \
${BASE_CACHE+BASE_CACHE="$BASE_CACHE"} \
${SDK_PATH+SDK_PATH="$SDK_PATH"} \
- i686_linux_CC=i686-linux-gnu-gcc \
- i686_linux_CXX=i686-linux-gnu-g++ \
- i686_linux_AR=i686-linux-gnu-ar \
- i686_linux_RANLIB=i686-linux-gnu-ranlib \
- i686_linux_NM=i686-linux-gnu-nm \
- i686_linux_STRIP=i686-linux-gnu-strip \
x86_64_linux_CC=x86_64-linux-gnu-gcc \
x86_64_linux_CXX=x86_64-linux-gnu-g++ \
x86_64_linux_AR=x86_64-linux-gnu-ar \
x86_64_linux_RANLIB=x86_64-linux-gnu-ranlib \
x86_64_linux_NM=x86_64-linux-gnu-nm \
x86_64_linux_STRIP=x86_64-linux-gnu-strip \
- qt_config_opts_i686_linux='-platform linux-g++ -xplatform bitcoin-linux-g++' \
qt_config_opts_x86_64_linux='-platform linux-g++ -xplatform bitcoin-linux-g++' \
FORCE_USE_SYSTEM_CLANG=1
diff --git a/contrib/guix/libexec/prelude.bash b/contrib/guix/libexec/prelude.bash
index 086df48fbe..f24c120863 100644
--- a/contrib/guix/libexec/prelude.bash
+++ b/contrib/guix/libexec/prelude.bash
@@ -51,7 +51,7 @@ fi
time-machine() {
# shellcheck disable=SC2086
guix time-machine --url=https://git.savannah.gnu.org/git/guix.git \
- --commit=ae03f401381e956c4c41b4cf495cbde964fa43d0 \
+ --commit=34e9eae68c9583acce5abc4100add3d88932a5ae \
--cores="$JOBS" \
--keep-failed \
--fallback \
diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm
index 008531e154..371312be7e 100644
--- a/contrib/guix/manifest.scm
+++ b/contrib/guix/manifest.scm
@@ -162,17 +162,13 @@ desirable for building Bitcoin Core release binaries."
(define (make-gcc-with-pthreads gcc)
(package-with-extra-configure-variable gcc "--enable-threads" "posix"))
-;; Required to support std::filesystem for mingw-w64 target.
-(define (make-gcc-without-newlib gcc)
- (package-with-extra-configure-variable gcc "--with-newlib" "no"))
-
(define (make-mingw-pthreads-cross-toolchain target)
"Create a cross-compilation toolchain package for TARGET"
(let* ((xbinutils (cross-binutils target))
(pthreads-xlibc mingw-w64-x86_64-winpthreads)
(pthreads-xgcc (make-gcc-with-pthreads
(cross-gcc target
- #:xgcc (make-gcc-without-newlib (make-ssp-fixed-gcc base-gcc))
+ #:xgcc (make-ssp-fixed-gcc base-gcc)
#:xbinutils xbinutils
#:libc pthreads-xlibc))))
;; Define a meta-package that propagates the resulting XBINUTILS, XLIBC, and
@@ -506,8 +502,7 @@ and endian independent.")
("python-certvalidator" ,python-certvalidator)
("python-elfesteem" ,python-elfesteem)
("python-requests" ,python-requests)
- ("python-macholib" ,python-macholib)
- ("libcrypto" ,openssl)))
+ ("python-macholib" ,python-macholib)))
;; There are no tests, but attempting to run python setup.py test leads to
;; problems, just disable the test
(arguments '(#:tests? #f))
diff --git a/doc/README.md b/doc/README.md
index c200ac3753..33f71f4807 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -73,6 +73,7 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th
- [Assets Attribution](assets-attribution.md)
- [Assumeutxo design](assumeutxo.md)
- [bitcoin.conf Configuration File](bitcoin-conf.md)
+- [CJDNS Support](cjdns.md)
- [Files](files.md)
- [Fuzz-testing](fuzzing.md)
- [I2P Support](i2p.md)
diff --git a/doc/build-netbsd.md b/doc/build-netbsd.md
index 762406bf6b..ba9a80f83b 100644
--- a/doc/build-netbsd.md
+++ b/doc/build-netbsd.md
@@ -27,15 +27,33 @@ git clone https://github.com/bitcoin/bitcoin.git
See [dependencies.md](dependencies.md) for a complete overview.
-### Building BerkeleyDB
+### Building Bitcoin Core
+
+**Important**: Use `gmake` (the non-GNU `make` will exit with an error).
+
+#### With descriptor wallet:
+
+The descriptor wallet uses `sqlite3`. You can install it using:
+```bash
+pkgin install sqlite3
+```
+
+```bash
+./autogen.sh
+./configure --with-gui=no --without-bdb \
+ CPPFLAGS="-I/usr/pkg/include" \
+ LDFLAGS="-L/usr/pkg/lib" \
+ BOOST_CPPFLAGS="-I/usr/pkg/include" \
+ MAKE=gmake
+```
-BerkeleyDB is only necessary for the wallet functionality. To skip this, pass
-`--disable-wallet` to `./configure` and skip to the next section.
+#### With legacy wallet:
+
+BerkeleyDB is use for legacy wallet functionality.
It is recommended to use Berkeley DB 4.8. You cannot use the BerkeleyDB library
-from ports, for the same reason as boost above (g++/libstd++ incompatibility).
-If you have to build it yourself, you can use [the installation script included
-in contrib/](/contrib/install_db4.sh) like so:
+from ports.
+You can use [the installation script included in contrib/](/contrib/install_db4.sh) like so:
```bash
./contrib/install_db4.sh `pwd`
@@ -47,11 +65,6 @@ from the root of the repository. Then set `BDB_PREFIX` for the next section:
export BDB_PREFIX="$PWD/db4"
```
-### Building Bitcoin Core
-
-**Important**: Use `gmake` (the non-GNU `make` will exit with an error).
-
-With wallet:
```bash
./autogen.sh
./configure --with-gui=no CPPFLAGS="-I/usr/pkg/include" \
@@ -62,7 +75,7 @@ With wallet:
MAKE=gmake
```
-Without wallet:
+#### Without wallet:
```bash
./autogen.sh
./configure --with-gui=no --disable-wallet \
diff --git a/doc/cjdns.md b/doc/cjdns.md
new file mode 100644
index 0000000000..5b2bcaf874
--- /dev/null
+++ b/doc/cjdns.md
@@ -0,0 +1,95 @@
+# CJDNS support in Bitcoin Core
+
+It is possible to run Bitcoin Core over CJDNS, an encrypted IPv6 network that
+uses public-key cryptography for address allocation and a distributed hash table
+for routing.
+
+## What is CJDNS?
+
+CJDNS is like a distributed, shared VPN with multiple entry points where every
+participant can reach any other participant. All participants use addresses from
+the `fc00::/8` network (reserved IPv6 range). Installation and configuration is
+done outside of Bitcoin Core, similarly to a VPN (either in the host/OS or on
+the network router).
+
+Compared to IPv4/IPv6, CJDNS provides end-to-end encryption and protects nodes
+from traffic analysis and filtering.
+
+Used with Tor and I2P, CJDNS is a complementary option that can enhance network
+redundancy and robustness for both the Bitcoin network and individual nodes.
+
+Each network has different characteristics. For instance, Tor is widely used but
+somewhat centralized. I2P connections have a source address and I2P is slow.
+CJDNS is fast but does not hide the sender and the recipient from intermediate
+routers.
+
+## Installing CJDNS and connecting to the network
+
+To install and set up CJDNS, follow the instructions at
+https://github.com/cjdelisle/cjdns#cjdns.
+
+Don't skip steps
+["2. Find a friend"](https://github.com/cjdelisle/cjdns#2-find-a-friend) and
+["3. Connect your node to your friend's
+node"](https://github.com/cjdelisle/cjdns#3-connect-your-node-to-your-friends-node).
+You need to be connected to the CJDNS network before it will work with your
+Bitcoin Core node.
+
+Typically, CJDNS might be launched from its directory with
+`sudo ./cjdroute < cjdroute.conf` and it sheds permissions after setting up the
+[TUN](https://en.wikipedia.org/wiki/TUN/TAP) interface. You may also [launch it as an
+unprivileged user](https://github.com/cjdelisle/cjdns/blob/master/doc/non-root-user.md)
+with some additional setup.
+
+The network connection can be checked by running `./tools/peerStats` from the
+CJDNS directory.
+
+## Run Bitcoin Core with CJDNS
+
+Once you are connected to the CJDNS network, the following Bitcoin Core
+configuration option makes CJDNS peers automatically reachable:
+
+```
+-cjdnsreachable
+```
+
+When enabled, this option tells Bitcoin Core that it is running in an
+environment where a connection to an `fc00::/8` address will be to the CJDNS
+network instead of to an [RFC4193](https://datatracker.ietf.org/doc/html/rfc4193)
+IPv6 local network. This helps Bitcoin Core perform better address management:
+ - Your node can consider incoming `fc00::/8` connections to be from the CJDNS
+ network rather than from an IPv6 private one.
+ - If one of your node's local addresses is `fc00::/8`, then it can choose to
+ gossip that address to peers.
+
+## Additional configuration options related to CJDNS
+
+```
+-onlynet=cjdns
+```
+
+Make automatic outbound connections only to CJDNS addresses. Inbound and manual
+connections are not affected by this option. It can be specified multiple times
+to allow multiple networks, e.g. onlynet=cjdns, onlynet=i2p, onlynet=onion.
+
+CJDNS support was added to Bitcoin Core in version 23.0 and there may be fewer
+CJDNS peers than Tor or IP ones. You can use `bitcoin-cli -addrinfo` to see the
+number of CJDNS addresses known to your node.
+
+In general, a node can be run with both an onion service and CJDNS (or any/all
+of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if one of
+the networks has issues. There are a number of ways to configure this; see
+[doc/tor.md](https://github.com/bitcoin/bitcoin/blob/master/doc/tor.md) for
+details.
+
+## CJDNS-related information in Bitcoin Core
+
+There are several ways to see your CJDNS address in Bitcoin Core:
+- in the "Local addresses" output of CLI `-netinfo`
+- in the "localaddresses" output of RPC `getnetworkinfo`
+
+To see which CJDNS peers your node is connected to, use `bitcoin-cli -netinfo 4`
+or the `getpeerinfo` RPC (i.e. `bitcoin-cli getpeerinfo`).
+
+To see which CJDNS addresses your node knows, use the `getnodeaddresses 0 cjdns`
+RPC.
diff --git a/doc/i2p.md b/doc/i2p.md
index e45b5efb9b..39f65c4e5f 100644
--- a/doc/i2p.md
+++ b/doc/i2p.md
@@ -80,15 +80,15 @@ phase when syncing up a new node can be very slow. This phase can be sped up by
using other networks, for instance `onlynet=onion`, at the same time.
In general, a node can be run with both onion and I2P hidden services (or
-any/all of IPv4/IPv6/onion/I2P), which can provide a potential fallback if one
-of the networks has issues.
+any/all of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if
+one of the networks has issues.
## I2P-related information in Bitcoin Core
There are several ways to see your I2P address in Bitcoin Core:
-- in the debug log (grep for `AddLocal`, the I2P address ends in `.b32.i2p`)
-- in the output of the `getnetworkinfo` RPC in the "localaddresses" section
-- in the output of `bitcoin-cli -netinfo` peer connections dashboard
+- in the "Local addresses" output of CLI `-netinfo`
+- in the "localaddresses" output of RPC `getnetworkinfo`
+- in the debug log (grep for `AddLocal`; the I2P address ends in `.b32.i2p`)
To see which I2P peers your node is connected to, use `bitcoin-cli -netinfo 4`
or the `getpeerinfo` RPC (e.g. `bitcoin-cli getpeerinfo`).
diff --git a/doc/tor.md b/doc/tor.md
index b7c4f7d425..08d031d084 100644
--- a/doc/tor.md
+++ b/doc/tor.md
@@ -16,9 +16,9 @@ configure Tor.
## How to see information about your Tor configuration via Bitcoin Core
There are several ways to see your local onion address in Bitcoin Core:
-- in the debug log (grep for "tor:" or "AddLocal")
-- in the output of RPC `getnetworkinfo` in the "localaddresses" section
-- in the output of the CLI `-netinfo` peer connections dashboard
+- in the "Local addresses" output of CLI `-netinfo`
+- in the "localaddresses" output of RPC `getnetworkinfo`
+- in the debug log (grep for "AddLocal"; the Tor address ends in `.onion`)
You may set the `-debug=tor` config logging option to have additional
information in the debug log about your Tor configuration.
@@ -27,6 +27,9 @@ CLI `-addrinfo` returns the number of addresses known to your node per
network. This can be useful to see how many onion peers your node knows,
e.g. for `-onlynet=onion`.
+To fetch a number of onion addresses that your node knows, for example seven
+addresses, use the `getnodeaddresses 7 onion` RPC.
+
## 1. Run Bitcoin Core behind a Tor proxy
The first step is running Bitcoin Core behind a Tor proxy. This will already anonymize all
@@ -58,7 +61,7 @@ outgoing connections, but more is possible.
-onlynet=onion Make automatic outbound connections only to .onion addresses.
Inbound and manual connections are not affected by this option.
It can be specified multiple times to allow multiple networks,
- e.g. onlynet=onion, onlynet=i2p.
+ e.g. onlynet=onion, onlynet=i2p, onlynet=cjdns.
In a typical situation, this suffices to run behind a Tor proxy:
diff --git a/src/Makefile.am b/src/Makefile.am
index e940736b71..af6bcaf4d7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -378,6 +378,7 @@ libbitcoin_node_a_SOURCES = \
rpc/rawtransaction.cpp \
rpc/server.cpp \
rpc/server_util.cpp \
+ rpc/txoutproof.cpp \
script/sigcache.cpp \
shutdown.cpp \
signet.cpp \
diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp
index 609c592d20..16a8948f99 100644
--- a/src/bench/coin_selection.cpp
+++ b/src/bench/coin_selection.cpp
@@ -13,7 +13,6 @@
using node::NodeContext;
using wallet::AttemptSelection;
-using wallet::CInputCoin;
using wallet::COutput;
using wallet::CWallet;
using wallet::CWalletTx;
@@ -58,14 +57,21 @@ static void CoinSelection(benchmark::Bench& bench)
// Create coins
std::vector<COutput> coins;
for (const auto& wtx : wtxs) {
- coins.emplace_back(wallet, *wtx, 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */);
+ coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/ 6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx->GetTxTime(), /*from_me=*/ true);
}
const CoinEligibilityFilter filter_standard(1, 6, 0);
- const CoinSelectionParams coin_selection_params(/* change_output_size= */ 34,
- /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
- /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
+ FastRandomContext rand{};
+ const CoinSelectionParams coin_selection_params{
+ rand,
+ /* change_output_size= */ 34,
+ /* change_spend_size= */ 148,
+ /* effective_feerate= */ CFeeRate(0),
+ /* long_term_feerate= */ CFeeRate(0),
+ /* discard_feerate= */ CFeeRate(0),
+ /* tx_noinputs_size= */ 0,
+ /* avoid_partial= */ false,
+ };
bench.run([&] {
auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, coin_selection_params);
assert(result);
@@ -74,17 +80,15 @@ static void CoinSelection(benchmark::Bench& bench)
});
}
-typedef std::set<CInputCoin> CoinSet;
-
// Copied from src/wallet/test/coinselector_tests.cpp
static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>& set)
{
CMutableTransaction tx;
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
- CInputCoin coin(MakeTransactionRef(tx), nInput);
+ COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 0, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ true);
set.emplace_back();
- set.back().Insert(coin, 0, true, 0, 0, false);
+ set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
// Copied from src/wallet/test/coinselector_tests.cpp
static CAmount make_hard_case(int utxos, std::vector<OutputGroup>& utxo_pool)
diff --git a/src/core_write.cpp b/src/core_write.cpp
index c4b6b8d27e..8ec75880fe 100644
--- a/src/core_write.cpp
+++ b/src/core_write.cpp
@@ -153,7 +153,9 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool include
CTxDestination address;
out.pushKV("asm", ScriptToAsmStr(scriptPubKey));
- out.pushKV("desc", InferDescriptor(scriptPubKey, DUMMY_SIGNING_PROVIDER)->ToString());
+ if (include_address) {
+ out.pushKV("desc", InferDescriptor(scriptPubKey, DUMMY_SIGNING_PROVIDER)->ToString());
+ }
if (include_hex) out.pushKV("hex", HexStr(scriptPubKey));
std::vector<std::vector<unsigned char>> solns;
diff --git a/src/fs.h b/src/fs.h
index 00b786453c..8e145cbb8e 100644
--- a/src/fs.h
+++ b/src/fs.h
@@ -51,12 +51,26 @@ public:
// Disallow std::string conversion method to avoid locale-dependent encoding on windows.
std::string string() const = delete;
+ std::string u8string() const
+ {
+ const auto& utf8_str{std::filesystem::path::u8string()};
+ // utf8_str might either be std::string (C++17) or std::u8string
+ // (C++20). Convert both to std::string. This method can be removed
+ // after switching to C++20.
+ return std::string{utf8_str.begin(), utf8_str.end()};
+ }
+
// Required for path overloads in <fstream>.
// See https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=96e0367ead5d8dcac3bec2865582e76e2fbab190
path& make_preferred() { std::filesystem::path::make_preferred(); return *this; }
path filename() const { return std::filesystem::path::filename(); }
};
+static inline path u8path(const std::string& utf8_str)
+{
+ return std::filesystem::u8path(utf8_str);
+}
+
// Disallow implicit std::string conversion for absolute to avoid
// locale-dependent encoding on windows.
static inline path absolute(const path& p)
@@ -116,8 +130,8 @@ static inline std::string PathToString(const path& path)
// use here, because these methods encode the path using C++'s narrow
// multibyte encoding, which on Windows corresponds to the current "code
// page", which is unpredictable and typically not able to represent all
- // valid paths. So std::filesystem::path::u8string() and
- // std::filesystem::u8path() functions are used instead on Windows. On
+ // valid paths. So fs::path::u8string() and
+ // fs::u8path() functions are used instead on Windows. On
// POSIX, u8string/u8path functions are not safe to use because paths are
// not always valid UTF-8, so plain string methods which do not transform
// the path there are used.
diff --git a/src/init.cpp b/src/init.cpp
index e181beab63..de78fdb7b4 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -444,7 +444,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-asmap=<file>", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bind=<addr>[:<port>][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), signetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
- argsman.AddArg("-cjdnsreachable", "If set then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-cjdnsreachable", "If set, then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network, see doc/cjdns.md) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -853,12 +853,14 @@ bool AppInitParameterInteraction(const ArgsManager& args)
nLocalServices = ServiceFlags(nLocalServices | NODE_COMPACT_FILTERS);
}
- // if using block pruning, then disallow txindex and coinstatsindex
if (args.GetIntArg("-prune", 0)) {
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX))
return InitError(_("Prune mode is incompatible with -txindex."));
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX))
return InitError(_("Prune mode is incompatible with -coinstatsindex."));
+ if (args.GetBoolArg("-reindex-chainstate", false)) {
+ return InitError(_("Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead."));
+ }
}
// If -forcednsseed is set to true, ensure -dnsseed has not been set to false
@@ -1300,6 +1302,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
}
for (int n = 0; n < NET_MAX; n++) {
enum Network net = (enum Network)n;
+ assert(IsReachable(net));
if (!nets.count(net))
SetReachable(net, false);
}
diff --git a/src/net.h b/src/net.h
index a38310938b..05e71072e6 100644
--- a/src/net.h
+++ b/src/net.h
@@ -99,15 +99,22 @@ struct AddedNodeInfo
class CNodeStats;
class CClientUIInterface;
-struct CSerializedNetMsg
-{
+struct CSerializedNetMsg {
CSerializedNetMsg() = default;
CSerializedNetMsg(CSerializedNetMsg&&) = default;
CSerializedNetMsg& operator=(CSerializedNetMsg&&) = default;
- // No copying, only moves.
+ // No implicit copying, only moves.
CSerializedNetMsg(const CSerializedNetMsg& msg) = delete;
CSerializedNetMsg& operator=(const CSerializedNetMsg&) = delete;
+ CSerializedNetMsg Copy() const
+ {
+ CSerializedNetMsg copy;
+ copy.data = data;
+ copy.m_type = m_type;
+ return copy;
+ }
+
std::vector<unsigned char> data;
std::string m_type;
};
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 77efac3364..34dd7991be 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -1624,7 +1624,7 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
hashBlock.ToString(), pnode->GetId());
const CSerializedNetMsg& ser_cmpctblock{lazy_ser.get()};
- m_connman.PushMessage(pnode, CSerializedNetMsg{ser_cmpctblock.data, ser_cmpctblock.m_type});
+ m_connman.PushMessage(pnode, ser_cmpctblock.Copy());
state.pindexBestHeaderSent = pindex;
}
});
diff --git a/src/node/miner.h b/src/node/miner.h
index 97c55f2864..5fd9abc280 100644
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -45,7 +45,7 @@ struct CTxMemPoolModifiedEntry {
nSigOpCostWithAncestors = entry->GetSigOpCostWithAncestors();
}
- int64_t GetModifiedFee() const { return iter->GetModifiedFee(); }
+ CAmount GetModifiedFee() const { return iter->GetModifiedFee(); }
uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; }
CAmount GetModFeesWithAncestors() const { return nModFeesWithAncestors; }
size_t GetTxSize() const { return iter->GetTxSize(); }
diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp
index 5d9ed5bf23..057767eb26 100644
--- a/src/qt/optionsmodel.cpp
+++ b/src/qt/optionsmodel.cpp
@@ -151,8 +151,11 @@ void OptionsModel::Init(bool resetSettings)
if (!settings.contains("fListen"))
settings.setValue("fListen", DEFAULT_LISTEN);
- if (!gArgs.SoftSetBoolArg("-listen", settings.value("fListen").toBool()))
+ if (!gArgs.SoftSetBoolArg("-listen", settings.value("fListen").toBool())) {
addOverriddenOption("-listen");
+ } else if (!settings.value("fListen").toBool()) {
+ gArgs.SoftSetBoolArg("-listenonion", false);
+ }
if (!settings.contains("server")) {
settings.setValue("server", false);
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 1ef531b293..8e60e82f35 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -11,7 +11,6 @@
#include <core_io.h>
#include <index/txindex.h>
#include <key_io.h>
-#include <merkleblock.h>
#include <node/blockstorage.h>
#include <node/coin.h>
#include <node/context.h>
@@ -268,155 +267,6 @@ static RPCHelpMan getrawtransaction()
};
}
-static RPCHelpMan gettxoutproof()
-{
- return RPCHelpMan{"gettxoutproof",
- "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n"
- "\nNOTE: By default this function only works sometimes. This is when there is an\n"
- "unspent output in the utxo for this transaction. To make it always work,\n"
- "you need to maintain a transaction index, using the -txindex command line option or\n"
- "specify the block in which the transaction is included manually (by blockhash).\n",
- {
- {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
- {
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
- },
- },
- {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"},
- },
- RPCResult{
- RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
- },
- RPCExamples{""},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- std::set<uint256> setTxids;
- UniValue txids = request.params[0].get_array();
- if (txids.empty()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
- }
- for (unsigned int idx = 0; idx < txids.size(); idx++) {
- auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
- if (!ret.second) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
- }
- }
-
- const CBlockIndex* pblockindex = nullptr;
- uint256 hashBlock;
- ChainstateManager& chainman = EnsureAnyChainman(request.context);
- if (!request.params[1].isNull()) {
- LOCK(cs_main);
- hashBlock = ParseHashV(request.params[1], "blockhash");
- pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
- if (!pblockindex) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
- }
- } else {
- LOCK(cs_main);
- CChainState& active_chainstate = chainman.ActiveChainstate();
-
- // Loop through txids and try to find which block they're in. Exit loop once a block is found.
- for (const auto& tx : setTxids) {
- const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx);
- if (!coin.IsSpent()) {
- pblockindex = active_chainstate.m_chain[coin.nHeight];
- break;
- }
- }
- }
-
-
- // Allow txindex to catch up if we need to query it and before we acquire cs_main.
- if (g_txindex && !pblockindex) {
- g_txindex->BlockUntilSyncedToCurrentChain();
- }
-
- LOCK(cs_main);
-
- if (pblockindex == nullptr) {
- const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
- if (!tx || hashBlock.IsNull()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
- }
- pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
- if (!pblockindex) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
- }
- }
-
- CBlock block;
- if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
- }
-
- unsigned int ntxFound = 0;
- for (const auto& tx : block.vtx) {
- if (setTxids.count(tx->GetHash())) {
- ntxFound++;
- }
- }
- if (ntxFound != setTxids.size()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
- }
-
- CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
- CMerkleBlock mb(block, setTxids);
- ssMB << mb;
- std::string strHex = HexStr(ssMB);
- return strHex;
-},
- };
-}
-
-static RPCHelpMan verifytxoutproof()
-{
- return RPCHelpMan{"verifytxoutproof",
- "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
- "and throwing an RPC error if the block is not in our best chain\n",
- {
- {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
- },
- RPCResult{
- RPCResult::Type::ARR, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
- }
- },
- RPCExamples{""},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
- CMerkleBlock merkleBlock;
- ssMB >> merkleBlock;
-
- UniValue res(UniValue::VARR);
-
- std::vector<uint256> vMatch;
- std::vector<unsigned int> vIndex;
- if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
- return res;
-
- ChainstateManager& chainman = EnsureAnyChainman(request.context);
- LOCK(cs_main);
-
- const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
- if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
- }
-
- // Check if proof is valid, only add results if so
- if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
- for (const uint256& hash : vMatch) {
- res.push_back(hash.GetHex());
- }
- }
-
- return res;
-},
- };
-}
-
static RPCHelpMan createrawtransaction()
{
return RPCHelpMan{"createrawtransaction",
@@ -1121,6 +971,7 @@ static RPCHelpMan decodepsbt()
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
{RPCResult::Type::STR, "asm", "The asm"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
@@ -2089,9 +1940,6 @@ static const CRPCCommand commands[] =
{ "rawtransactions", &utxoupdatepsbt, },
{ "rawtransactions", &joinpsbts, },
{ "rawtransactions", &analyzepsbt, },
-
- { "blockchain", &gettxoutproof, },
- { "blockchain", &verifytxoutproof, },
};
// clang-format on
for (const auto& c : commands) {
diff --git a/src/rpc/register.h b/src/rpc/register.h
index cc3a5e0a63..5a604ad428 100644
--- a/src/rpc/register.h
+++ b/src/rpc/register.h
@@ -9,25 +9,20 @@
* headers for everything under src/rpc/ */
class CRPCTable;
-/** Register block chain RPC commands */
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
-/** Register mempool RPC commands */
void RegisterMempoolRPCCommands(CRPCTable&);
-/** Register P2P networking RPC commands */
+void RegisterTxoutProofRPCCommands(CRPCTable&);
void RegisterNetRPCCommands(CRPCTable &tableRPC);
-/** Register miscellaneous RPC commands */
void RegisterMiscRPCCommands(CRPCTable &tableRPC);
-/** Register mining RPC commands */
void RegisterMiningRPCCommands(CRPCTable &tableRPC);
-/** Register raw transaction RPC commands */
void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC);
-/** Register raw transaction RPC commands */
void RegisterSignerRPCCommands(CRPCTable &tableRPC);
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
{
RegisterBlockchainRPCCommands(t);
RegisterMempoolRPCCommands(t);
+ RegisterTxoutProofRPCCommands(t);
RegisterNetRPCCommands(t);
RegisterMiscRPCCommands(t);
RegisterMiningRPCCommands(t);
diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp
new file mode 100644
index 0000000000..2700fb400c
--- /dev/null
+++ b/src/rpc/txoutproof.cpp
@@ -0,0 +1,183 @@
+// Copyright (c) 2010 Satoshi Nakamoto
+// Copyright (c) 2009-2022 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 <chain.h>
+#include <chainparams.h>
+#include <coins.h>
+#include <index/txindex.h>
+#include <merkleblock.h>
+#include <node/blockstorage.h>
+#include <primitives/transaction.h>
+#include <rpc/server.h>
+#include <rpc/server_util.h>
+#include <rpc/util.h>
+#include <univalue.h>
+#include <util/strencodings.h>
+#include <validation.h>
+
+using node::GetTransaction;
+using node::ReadBlockFromDisk;
+
+static RPCHelpMan gettxoutproof()
+{
+ return RPCHelpMan{"gettxoutproof",
+ "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n"
+ "\nNOTE: By default this function only works sometimes. This is when there is an\n"
+ "unspent output in the utxo for this transaction. To make it always work,\n"
+ "you need to maintain a transaction index, using the -txindex command line option or\n"
+ "specify the block in which the transaction is included manually (by blockhash).\n",
+ {
+ {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
+ },
+ },
+ {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"},
+ },
+ RPCResult{
+ RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
+ },
+ RPCExamples{""},
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ std::set<uint256> setTxids;
+ UniValue txids = request.params[0].get_array();
+ if (txids.empty()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
+ }
+ for (unsigned int idx = 0; idx < txids.size(); idx++) {
+ auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
+ if (!ret.second) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
+ }
+ }
+
+ const CBlockIndex* pblockindex = nullptr;
+ uint256 hashBlock;
+ ChainstateManager& chainman = EnsureAnyChainman(request.context);
+ if (!request.params[1].isNull()) {
+ LOCK(cs_main);
+ hashBlock = ParseHashV(request.params[1], "blockhash");
+ pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
+ if (!pblockindex) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
+ }
+ } else {
+ LOCK(cs_main);
+ CChainState& active_chainstate = chainman.ActiveChainstate();
+
+ // Loop through txids and try to find which block they're in. Exit loop once a block is found.
+ for (const auto& tx : setTxids) {
+ const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx);
+ if (!coin.IsSpent()) {
+ pblockindex = active_chainstate.m_chain[coin.nHeight];
+ break;
+ }
+ }
+ }
+
+
+ // Allow txindex to catch up if we need to query it and before we acquire cs_main.
+ if (g_txindex && !pblockindex) {
+ g_txindex->BlockUntilSyncedToCurrentChain();
+ }
+
+ LOCK(cs_main);
+
+ if (pblockindex == nullptr) {
+ const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
+ if (!tx || hashBlock.IsNull()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
+ }
+ pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
+ if (!pblockindex) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
+ }
+ }
+
+ CBlock block;
+ if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
+ }
+
+ unsigned int ntxFound = 0;
+ for (const auto& tx : block.vtx) {
+ if (setTxids.count(tx->GetHash())) {
+ ntxFound++;
+ }
+ }
+ if (ntxFound != setTxids.size()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
+ }
+
+ CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
+ CMerkleBlock mb(block, setTxids);
+ ssMB << mb;
+ std::string strHex = HexStr(ssMB);
+ return strHex;
+ },
+ };
+}
+
+static RPCHelpMan verifytxoutproof()
+{
+ return RPCHelpMan{"verifytxoutproof",
+ "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
+ "and throwing an RPC error if the block is not in our best chain\n",
+ {
+ {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
+ },
+ RPCResult{
+ RPCResult::Type::ARR, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
+ }
+ },
+ RPCExamples{""},
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
+ CMerkleBlock merkleBlock;
+ ssMB >> merkleBlock;
+
+ UniValue res(UniValue::VARR);
+
+ std::vector<uint256> vMatch;
+ std::vector<unsigned int> vIndex;
+ if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
+ return res;
+
+ ChainstateManager& chainman = EnsureAnyChainman(request.context);
+ LOCK(cs_main);
+
+ const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
+ if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
+ }
+
+ // Check if proof is valid, only add results if so
+ if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
+ for (const uint256& hash : vMatch) {
+ res.push_back(hash.GetHex());
+ }
+ }
+
+ return res;
+ },
+ };
+}
+
+void RegisterTxoutProofRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ // category actor (function)
+ // -------- ----------------
+ {"blockchain", &gettxoutproof},
+ {"blockchain", &verifytxoutproof},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}
diff --git a/src/scheduler.cpp b/src/scheduler.cpp
index 0b2ad3c553..197d009f7c 100644
--- a/src/scheduler.cpp
+++ b/src/scheduler.cpp
@@ -111,7 +111,7 @@ static void Repeat(CScheduler& s, CScheduler::Function f, std::chrono::milliseco
void CScheduler::scheduleEvery(CScheduler::Function f, std::chrono::milliseconds delta)
{
- scheduleFromNow([=] { Repeat(*this, f, delta); }, delta);
+ scheduleFromNow([this, f, delta] { Repeat(*this, f, delta); }, delta);
}
size_t CScheduler::getQueueInfo(std::chrono::system_clock::time_point& first,
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index fcb1a80765..e7c01bd6d0 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -732,26 +732,31 @@ BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network)
BOOST_CHECK(IsReachable(NET_IPV6));
BOOST_CHECK(IsReachable(NET_ONION));
BOOST_CHECK(IsReachable(NET_I2P));
+ BOOST_CHECK(IsReachable(NET_CJDNS));
SetReachable(NET_IPV4, false);
SetReachable(NET_IPV6, false);
SetReachable(NET_ONION, false);
SetReachable(NET_I2P, false);
+ SetReachable(NET_CJDNS, false);
BOOST_CHECK(!IsReachable(NET_IPV4));
BOOST_CHECK(!IsReachable(NET_IPV6));
BOOST_CHECK(!IsReachable(NET_ONION));
BOOST_CHECK(!IsReachable(NET_I2P));
+ BOOST_CHECK(!IsReachable(NET_CJDNS));
SetReachable(NET_IPV4, true);
SetReachable(NET_IPV6, true);
SetReachable(NET_ONION, true);
SetReachable(NET_I2P, true);
+ SetReachable(NET_CJDNS, true);
BOOST_CHECK(IsReachable(NET_IPV4));
BOOST_CHECK(IsReachable(NET_IPV6));
BOOST_CHECK(IsReachable(NET_ONION));
BOOST_CHECK(IsReachable(NET_I2P));
+ BOOST_CHECK(IsReachable(NET_CJDNS));
}
BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal)
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index fb5652d0a0..33918cceab 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -54,16 +54,6 @@ struct update_ancestor_state
int64_t modifySigOpsCost;
};
-struct update_fee_delta
-{
- explicit update_fee_delta(int64_t _feeDelta) : feeDelta(_feeDelta) { }
-
- void operator() (CTxMemPoolEntry &e) { e.UpdateFeeDelta(feeDelta); }
-
-private:
- int64_t feeDelta;
-};
-
bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp)
{
AssertLockHeld(cs_main);
@@ -99,7 +89,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee,
nModFeesWithAncestors{nFee},
nSigOpCostWithAncestors{sigOpCost} {}
-void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta)
+void CTxMemPoolEntry::UpdateFeeDelta(CAmount newFeeDelta)
{
nModFeesWithDescendants += newFeeDelta - feeDelta;
nModFeesWithAncestors += newFeeDelta - feeDelta;
@@ -496,7 +486,7 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces
CAmount delta{0};
ApplyDelta(entry.GetTx().GetHash(), delta);
if (delta) {
- mapTx.modify(newit, update_fee_delta(delta));
+ mapTx.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateFeeDelta(delta); });
}
// Update cachedInnerUsage to include contained transaction's usage.
@@ -931,7 +921,7 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD
delta += nFeeDelta;
txiter it = mapTx.find(hash);
if (it != mapTx.end()) {
- mapTx.modify(it, update_fee_delta(delta));
+ mapTx.modify(it, [&delta](CTxMemPoolEntry& e) { e.UpdateFeeDelta(delta); });
// Now update all ancestors' modified fees with descendants
setEntries setAncestors;
uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
diff --git a/src/txmempool.h b/src/txmempool.h
index e7e5a3c402..f5d5abc62e 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -101,7 +101,7 @@ private:
const unsigned int entryHeight; //!< Chain height when entering the mempool
const bool spendsCoinbase; //!< keep track of transactions that spend a coinbase
const int64_t sigOpCost; //!< Total sigop cost
- int64_t feeDelta{0}; //!< Used for determining the priority of the transaction for mining in a block
+ CAmount feeDelta{0}; //!< Used for determining the priority of the transaction for mining in a block
LockPoints lockPoints; //!< Track the height and time at which tx was final
// Information about descendants of this transaction that are in the
@@ -131,7 +131,7 @@ public:
std::chrono::seconds GetTime() const { return std::chrono::seconds{nTime}; }
unsigned int GetHeight() const { return entryHeight; }
int64_t GetSigOpCost() const { return sigOpCost; }
- int64_t GetModifiedFee() const { return nFee + feeDelta; }
+ CAmount GetModifiedFee() const { return nFee + feeDelta; }
size_t DynamicMemoryUsage() const { return nUsageSize; }
const LockPoints& GetLockPoints() const { return lockPoints; }
@@ -140,8 +140,8 @@ public:
// Adjusts the ancestor state
void UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps);
// Updates the fee delta used for mining priority score, and the
- // modified fees with descendants.
- void UpdateFeeDelta(int64_t feeDelta);
+ // modified fees with descendants/ancestors.
+ void UpdateFeeDelta(CAmount newFeeDelta);
// Update the LockPoints after a reorg
void UpdateLockPoints(const LockPoints& lp);
diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp
index 49f0abf9e7..0d0456af4b 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -60,12 +60,12 @@ bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
* erases the weak pointer from the g_dbenvs map.
* @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the directory path key was not already in the map.
*/
-std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory)
+std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory, bool use_shared_memory)
{
LOCK(cs_db);
auto inserted = g_dbenvs.emplace(fs::PathToString(env_directory), std::weak_ptr<BerkeleyEnvironment>());
if (inserted.second) {
- auto env = std::make_shared<BerkeleyEnvironment>(env_directory);
+ auto env = std::make_shared<BerkeleyEnvironment>(env_directory, use_shared_memory);
inserted.first->second = env;
return env;
}
@@ -113,7 +113,7 @@ void BerkeleyEnvironment::Reset()
fMockDb = false;
}
-BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path) : strPath(fs::PathToString(dir_path))
+BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path, bool use_shared_memory) : strPath(fs::PathToString(dir_path)), m_use_shared_memory(use_shared_memory)
{
Reset();
}
@@ -145,8 +145,9 @@ bool BerkeleyEnvironment::Open(bilingual_str& err)
LogPrintf("BerkeleyEnvironment::Open: LogDir=%s ErrorFile=%s\n", fs::PathToString(pathLogDir), fs::PathToString(pathErrorFile));
unsigned int nEnvFlags = 0;
- if (gArgs.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB))
+ if (!m_use_shared_memory) {
nEnvFlags |= DB_PRIVATE;
+ }
dbenv->set_lg_dir(fs::PathToString(pathLogDir).c_str());
dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
@@ -188,7 +189,7 @@ bool BerkeleyEnvironment::Open(bilingual_str& err)
}
//! Construct an in-memory mock Berkeley environment for testing
-BerkeleyEnvironment::BerkeleyEnvironment()
+BerkeleyEnvironment::BerkeleyEnvironment() : m_use_shared_memory(false)
{
Reset();
@@ -377,7 +378,7 @@ void BerkeleyBatch::Flush()
nMinutes = 1;
if (env) { // env is nullptr for dummy databases (i.e. in tests). Don't actually flush if env is nullptr so we don't segfault
- env->dbenv->txn_checkpoint(nMinutes ? gArgs.GetIntArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
+ env->dbenv->txn_checkpoint(nMinutes ? m_database.m_max_log_mb * 1024 : 0, nMinutes, 0);
}
}
@@ -831,13 +832,13 @@ std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, con
{
LOCK(cs_db); // Lock env.m_databases until insert in BerkeleyDatabase constructor
std::string data_filename = fs::PathToString(data_file.filename());
- std::shared_ptr<BerkeleyEnvironment> env = GetBerkeleyEnv(data_file.parent_path());
+ std::shared_ptr<BerkeleyEnvironment> env = GetBerkeleyEnv(data_file.parent_path(), options.use_shared_memory);
if (env->m_databases.count(data_filename)) {
error = Untranslated(strprintf("Refusing to load database. Data file '%s' is already loaded.", fs::PathToString(env->Directory() / data_filename)));
status = DatabaseStatus::FAILED_ALREADY_LOADED;
return nullptr;
}
- db = std::make_unique<BerkeleyDatabase>(std::move(env), std::move(data_filename));
+ db = std::make_unique<BerkeleyDatabase>(std::move(env), std::move(data_filename), options);
}
if (options.verify && !db->Verify(error)) {
diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h
index b924890d81..fd6c76183e 100644
--- a/src/wallet/bdb.h
+++ b/src/wallet/bdb.h
@@ -32,8 +32,6 @@
struct bilingual_str;
namespace wallet {
-static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
-static const bool DEFAULT_WALLET_PRIVDB = true;
struct WalletDatabaseFileId {
u_int8_t value[DB_FILE_ID_LEN];
@@ -56,8 +54,9 @@ public:
std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases;
std::unordered_map<std::string, WalletDatabaseFileId> m_fileids;
std::condition_variable_any m_db_in_use;
+ bool m_use_shared_memory;
- explicit BerkeleyEnvironment(const fs::path& env_directory);
+ explicit BerkeleyEnvironment(const fs::path& env_directory, bool use_shared_memory);
BerkeleyEnvironment();
~BerkeleyEnvironment();
void Reset();
@@ -85,7 +84,7 @@ public:
};
/** Get BerkeleyEnvironment given a directory path. */
-std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory);
+std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory, bool use_shared_memory);
class BerkeleyBatch;
@@ -98,8 +97,8 @@ public:
BerkeleyDatabase() = delete;
/** Create DB handle to real database */
- BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename) :
- WalletDatabase(), env(std::move(env)), strFile(std::move(filename))
+ BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename, const DatabaseOptions& options) :
+ WalletDatabase(), env(std::move(env)), strFile(std::move(filename)), m_max_log_mb(options.max_log_mb)
{
auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this));
assert(inserted.second);
@@ -160,6 +159,7 @@ public:
std::unique_ptr<Db> m_db;
std::string strFile;
+ int64_t m_max_log_mb;
/** Make a BerkeleyBatch connected to this database */
std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override;
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 20891a3e28..38c5939232 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -50,8 +50,8 @@ struct {
* The Branch and Bound algorithm is described in detail in Murch's Master Thesis:
* https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
*
- * @param const std::vector<CInputCoin>& utxo_pool The set of UTXOs that we are choosing from.
- * These UTXOs will be sorted in descending order by effective value and the CInputCoins'
+ * @param const std::vector<OutputGroup>& utxo_pool The set of UTXO groups that we are choosing from.
+ * These UTXO groups will be sorted in descending order by effective value and the OutputGroups'
* values are their effective values.
* @param const CAmount& selection_target This is the value that we want to select. It is the lower
* bound of the range.
@@ -165,14 +165,14 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
return result;
}
-std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value)
+std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng)
{
SelectionResult result(target_value);
std::vector<size_t> indexes;
indexes.resize(utxo_pool.size());
std::iota(indexes.begin(), indexes.end(), 0);
- Shuffle(indexes.begin(), indexes.end(), FastRandomContext());
+ Shuffle(indexes.begin(), indexes.end(), rng);
CAmount selected_eff_value = 0;
for (const size_t i : indexes) {
@@ -187,7 +187,7 @@ std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& ut
return std::nullopt;
}
-static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue,
+static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue,
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
{
std::vector<char> vfIncluded;
@@ -195,8 +195,6 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
vfBest.assign(groups.size(), true);
nBest = nTotalLower;
- FastRandomContext insecure_rand;
-
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
{
vfIncluded.assign(groups.size(), false);
@@ -233,7 +231,7 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
}
}
-std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue)
+std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue, FastRandomContext& rng)
{
SelectionResult result(nTargetValue);
@@ -242,7 +240,7 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
std::vector<OutputGroup> applicable_groups;
CAmount nTotalLower = 0;
- Shuffle(groups.begin(), groups.end(), FastRandomContext());
+ Shuffle(groups.begin(), groups.end(), rng);
for (const OutputGroup& group : groups) {
if (group.GetSelectionAmount() == nTargetValue) {
@@ -274,9 +272,9 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
std::vector<char> vfBest;
CAmount nBest;
- ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
+ ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) {
- ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
+ ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
}
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
@@ -311,29 +309,29 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
******************************************************************************/
-void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only) {
+void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) {
// Compute the effective value first
- const CAmount coin_fee = output.m_input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.m_input_bytes);
+ const CAmount coin_fee = output.input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.input_bytes);
const CAmount ev = output.txout.nValue - coin_fee;
// Filter for positive only here before adding the coin
if (positive_only && ev <= 0) return;
m_outputs.push_back(output);
- CInputCoin& coin = m_outputs.back();
+ COutput& coin = m_outputs.back();
- coin.m_fee = coin_fee;
- fee += coin.m_fee;
+ coin.fee = coin_fee;
+ fee += coin.fee;
- coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.m_input_bytes);
- long_term_fee += coin.m_long_term_fee;
+ coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
+ long_term_fee += coin.long_term_fee;
coin.effective_value = ev;
effective_value += coin.effective_value;
- m_from_me &= from_me;
- m_value += output.txout.nValue;
- m_depth = std::min(m_depth, depth);
+ m_from_me &= coin.from_me;
+ m_value += coin.txout.nValue;
+ m_depth = std::min(m_depth, coin.depth);
// ancestors here express the number of ancestors the new coin will end up having, which is
// the sum, rather than the max; this will overestimate in the cases where multiple inputs
// have common ancestors
@@ -355,7 +353,7 @@ CAmount OutputGroup::GetSelectionAmount() const
return m_subtract_fee_outputs ? m_value : effective_value;
}
-CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
+CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
{
// This function should not be called with empty inputs as that would mean the selection failed
assert(!inputs.empty());
@@ -363,8 +361,8 @@ CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cos
// Always consider the cost of spending an input now vs in the future.
CAmount waste = 0;
CAmount selected_effective_value = 0;
- for (const CInputCoin& coin : inputs) {
- waste += coin.m_fee - coin.m_long_term_fee;
+ for (const COutput& coin : inputs) {
+ waste += coin.fee - coin.long_term_fee;
selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue;
}
@@ -409,14 +407,14 @@ void SelectionResult::AddInput(const OutputGroup& group)
m_use_effective = !group.m_subtract_fee_outputs;
}
-const std::set<CInputCoin>& SelectionResult::GetInputSet() const
+const std::set<COutput>& SelectionResult::GetInputSet() const
{
return m_selected_inputs;
}
-std::vector<CInputCoin> SelectionResult::GetShuffledInputVector() const
+std::vector<COutput> SelectionResult::GetShuffledInputVector() const
{
- std::vector<CInputCoin> coins(m_selected_inputs.begin(), m_selected_inputs.end());
+ std::vector<COutput> coins(m_selected_inputs.begin(), m_selected_inputs.end());
Shuffle(coins.begin(), coins.end(), FastRandomContext());
return coins;
}
@@ -428,4 +426,9 @@ bool SelectionResult::operator<(SelectionResult other) const
// As this operator is only used in std::min_element, we want the result that has more inputs when waste are equal.
return *m_waste < *other.m_waste || (*m_waste == *other.m_waste && m_selected_inputs.size() > other.m_selected_inputs.size());
}
+
+std::string COutput::ToString() const
+{
+ return strprintf("COutput(%s, %d, %d) [%s]", outpoint.hash.ToString(), outpoint.n, depth, FormatMoney(txout.nValue));
+}
} // namespace wallet
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 496a026999..504d57aa78 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -19,62 +19,77 @@ static constexpr CAmount MIN_CHANGE{COIN / 100};
static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2;
/** A UTXO under consideration for use in funding a new transaction. */
-class CInputCoin {
+class COutput
+{
public:
- CInputCoin(const CTransactionRef& tx, unsigned int i)
- {
- if (!tx)
- throw std::invalid_argument("tx should not be null");
- if (i >= tx->vout.size())
- throw std::out_of_range("The output index is out of range");
-
- outpoint = COutPoint(tx->GetHash(), i);
- txout = tx->vout[i];
- effective_value = txout.nValue;
- }
+ /** The outpoint identifying this UTXO */
+ COutPoint outpoint;
- CInputCoin(const CTransactionRef& tx, unsigned int i, int input_bytes) : CInputCoin(tx, i)
- {
- m_input_bytes = input_bytes;
- }
+ /** The output itself */
+ CTxOut txout;
- CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in)
- {
- outpoint = outpoint_in;
- txout = txout_in;
- effective_value = txout.nValue;
- }
+ /**
+ * Depth in block chain.
+ * If > 0: the tx is on chain and has this many confirmations.
+ * If = 0: the tx is waiting confirmation.
+ * If < 0: a conflicting tx is on chain and has this many confirmations. */
+ int depth;
- CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in, int input_bytes) : CInputCoin(outpoint_in, txout_in)
- {
- m_input_bytes = input_bytes;
- }
+ /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
+ int input_bytes;
- COutPoint outpoint;
- CTxOut txout;
+ /** Whether we have the private keys to spend this output */
+ bool spendable;
+
+ /** Whether we know how to spend this output, ignoring the lack of keys */
+ bool solvable;
+
+ /**
+ * Whether this output is considered safe to spend. Unconfirmed transactions
+ * from outside keys and unconfirmed replacement transactions are considered
+ * unsafe and will not be used to fund new spending transactions.
+ */
+ bool safe;
+
+ /** The time of the transaction containing this output as determined by CWalletTx::nTimeSmart */
+ int64_t time;
+
+ /** Whether the transaction containing this output is sent from the owning wallet */
+ bool from_me;
+
+ /** The output's value minus fees required to spend it. Initialized as the output's absolute value. */
CAmount effective_value;
- CAmount m_fee{0};
- CAmount m_long_term_fee{0};
- /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
- int m_input_bytes{-1};
+ /** The fee required to spend this output at the transaction's target feerate. */
+ CAmount fee{0};
- bool operator<(const CInputCoin& rhs) const {
- return outpoint < rhs.outpoint;
- }
+ /** The fee required to spend this output at the consolidation feerate. */
+ CAmount long_term_fee{0};
- bool operator!=(const CInputCoin& rhs) const {
- return outpoint != rhs.outpoint;
- }
+ COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me)
+ : outpoint(outpoint),
+ txout(txout),
+ depth(depth),
+ input_bytes(input_bytes),
+ spendable(spendable),
+ solvable(solvable),
+ safe(safe),
+ time(time),
+ from_me(from_me),
+ effective_value(txout.nValue)
+ {}
- bool operator==(const CInputCoin& rhs) const {
- return outpoint == rhs.outpoint;
+ std::string ToString() const;
+
+ bool operator<(const COutput& rhs) const {
+ return outpoint < rhs.outpoint;
}
};
/** Parameters for one iteration of Coin Selection. */
-struct CoinSelectionParams
-{
+struct CoinSelectionParams {
+ /** Randomness to use in the context of coin selection. */
+ FastRandomContext& rng_fast;
/** Size of a change output in bytes, determined by the output type. */
size_t change_output_size = 0;
/** Size of the input to spend a change output in virtual bytes. */
@@ -100,17 +115,20 @@ struct CoinSelectionParams
* reuse. Dust outputs are not eligible to be added to output groups and thus not considered. */
bool m_avoid_partial_spends = false;
- CoinSelectionParams(size_t change_output_size, size_t change_spend_size, CFeeRate effective_feerate,
- CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial) :
- change_output_size(change_output_size),
- change_spend_size(change_spend_size),
- m_effective_feerate(effective_feerate),
- m_long_term_feerate(long_term_feerate),
- m_discard_feerate(discard_feerate),
- tx_noinputs_size(tx_noinputs_size),
- m_avoid_partial_spends(avoid_partial)
- {}
- CoinSelectionParams() {}
+ CoinSelectionParams(FastRandomContext& rng_fast, size_t change_output_size, size_t change_spend_size, CFeeRate effective_feerate,
+ CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial)
+ : rng_fast{rng_fast},
+ change_output_size(change_output_size),
+ change_spend_size(change_spend_size),
+ m_effective_feerate(effective_feerate),
+ m_long_term_feerate(long_term_feerate),
+ m_discard_feerate(discard_feerate),
+ tx_noinputs_size(tx_noinputs_size),
+ m_avoid_partial_spends(avoid_partial)
+ {
+ }
+ CoinSelectionParams(FastRandomContext& rng_fast)
+ : rng_fast{rng_fast} {}
};
/** Parameters for filtering which OutputGroups we may use in coin selection.
@@ -139,7 +157,7 @@ struct CoinEligibilityFilter
struct OutputGroup
{
/** The list of UTXOs contained in this output group. */
- std::vector<CInputCoin> m_outputs;
+ std::vector<COutput> m_outputs;
/** Whether the UTXOs were sent by the wallet to itself. This is relevant because we may want at
* least a certain number of confirmations on UTXOs received from outside wallets while trusting
* our own UTXOs more. */
@@ -176,7 +194,7 @@ struct OutputGroup
m_subtract_fee_outputs(params.m_subtract_fee_outputs)
{}
- void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only);
+ void Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only);
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
CAmount GetSelectionAmount() const;
};
@@ -198,13 +216,13 @@ struct OutputGroup
* @param[in] use_effective_value Whether to use the input's effective value (when true) or the real value (when false).
* @return The waste
*/
-[[nodiscard]] CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
+[[nodiscard]] CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
struct SelectionResult
{
private:
/** Set of inputs selected by the algorithm to use in the transaction */
- std::set<CInputCoin> m_selected_inputs;
+ std::set<COutput> m_selected_inputs;
/** The target the algorithm selected for. Note that this may not be equal to the recipient amount as it can include non-input fees */
const CAmount m_target;
/** Whether the input values for calculations should be the effective value (true) or normal value (false) */
@@ -230,9 +248,9 @@ public:
[[nodiscard]] CAmount GetWaste() const;
/** Get m_selected_inputs */
- const std::set<CInputCoin>& GetInputSet() const;
- /** Get the vector of CInputCoins that will be used to fill in a CTransaction's vin */
- std::vector<CInputCoin> GetShuffledInputVector() const;
+ const std::set<COutput>& GetInputSet() const;
+ /** Get the vector of COutputs that will be used to fill in a CTransaction's vin */
+ std::vector<COutput> GetShuffledInputVector() const;
bool operator<(SelectionResult other) const;
};
@@ -246,10 +264,10 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
* @param[in] target_value The target value to select for
* @returns If successful, a SelectionResult, otherwise, std::nullopt
*/
-std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value);
+std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng);
// Original coin selection algorithm as a fallback
-std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue);
+std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue, FastRandomContext& rng);
} // namespace wallet
#endif // BITCOIN_WALLET_COINSELECTION_H
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index 0ed2658129..8e79ee678e 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -6,6 +6,7 @@
#include <chainparams.h>
#include <fs.h>
#include <logging.h>
+#include <util/system.h>
#include <wallet/db.h>
#include <exception>
@@ -137,4 +138,13 @@ bool IsSQLiteFile(const fs::path& path)
// Check the application id matches our network magic
return memcmp(Params().MessageStart(), app_id, 4) == 0;
}
+
+void ReadDatabaseArgs(const ArgsManager& args, DatabaseOptions& options)
+{
+ // Override current options with args values, if any were specified
+ options.use_unsafe_sync = args.GetBoolArg("-unsafesqlitesync", options.use_unsafe_sync);
+ options.use_shared_memory = !args.GetBoolArg("-privdb", !options.use_shared_memory);
+ options.max_log_mb = args.GetIntArg("-dblogsize", options.max_log_mb);
+}
+
} // namespace wallet
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 5825b00e3a..f09844c37e 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -16,6 +16,7 @@
#include <optional>
#include <string>
+class ArgsManager;
struct bilingual_str;
namespace wallet {
@@ -207,7 +208,12 @@ struct DatabaseOptions {
std::optional<DatabaseFormat> require_format;
uint64_t create_flags = 0;
SecureString create_passphrase;
- bool verify = true;
+
+ // Specialized options. Not every option is supported by every backend.
+ bool verify = true; //!< Check data integrity on load.
+ bool use_unsafe_sync = false; //!< Disable file sync for faster performance.
+ bool use_shared_memory = false; //!< Let other processes access the database.
+ int64_t max_log_mb = 100; //!< Max log size to allow before consolidating.
};
enum class DatabaseStatus {
@@ -227,6 +233,7 @@ enum class DatabaseStatus {
/** Recursively list database paths in directory. */
std::vector<fs::path> ListDatabases(const fs::path& path);
+void ReadDatabaseArgs(const ArgsManager& args, DatabaseOptions& options);
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
fs::path BDBDataFile(const fs::path& path);
diff --git a/src/wallet/dump.cpp b/src/wallet/dump.cpp
index 6d8508fc72..d80c3e25b0 100644
--- a/src/wallet/dump.cpp
+++ b/src/wallet/dump.cpp
@@ -19,10 +19,10 @@ namespace wallet {
static const std::string DUMP_MAGIC = "BITCOIN_CORE_WALLET_DUMP";
uint32_t DUMP_VERSION = 1;
-bool DumpWallet(CWallet& wallet, bilingual_str& error)
+bool DumpWallet(const ArgsManager& args, CWallet& wallet, bilingual_str& error)
{
// Get the dumpfile
- std::string dump_filename = gArgs.GetArg("-dumpfile", "");
+ std::string dump_filename = args.GetArg("-dumpfile", "");
if (dump_filename.empty()) {
error = _("No dump file provided. To use dump, -dumpfile=<filename> must be provided.");
return false;
@@ -114,10 +114,10 @@ static void WalletToolReleaseWallet(CWallet* wallet)
delete wallet;
}
-bool CreateFromDump(const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
+bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
// Get the dumpfile
- std::string dump_filename = gArgs.GetArg("-dumpfile", "");
+ std::string dump_filename = args.GetArg("-dumpfile", "");
if (dump_filename.empty()) {
error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.");
return false;
@@ -171,7 +171,7 @@ bool CreateFromDump(const std::string& name, const fs::path& wallet_path, biling
return false;
}
// Get the data file format with format_value as the default
- std::string file_format = gArgs.GetArg("-format", format_value);
+ std::string file_format = args.GetArg("-format", format_value);
if (file_format.empty()) {
error = _("No wallet file format provided. To use createfromdump, -format=<format> must be provided.");
return false;
@@ -193,6 +193,7 @@ bool CreateFromDump(const std::string& name, const fs::path& wallet_path, biling
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(args, options);
options.require_create = true;
options.require_format = data_format;
std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error);
diff --git a/src/wallet/dump.h b/src/wallet/dump.h
index a879c4db35..bf683e9843 100644
--- a/src/wallet/dump.h
+++ b/src/wallet/dump.h
@@ -11,11 +11,12 @@
#include <vector>
struct bilingual_str;
+class ArgsManager;
namespace wallet {
class CWallet;
-bool DumpWallet(CWallet& wallet, bilingual_str& error);
-bool CreateFromDump(const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
+bool DumpWallet(const ArgsManager& args, CWallet& wallet, bilingual_str& error);
+bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
} // namespace wallet
#endif // BITCOIN_WALLET_DUMP_H
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 3552c14160..bc53180c0e 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -233,12 +233,6 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
// Write back transaction
mtx = CMutableTransaction(*tx_new);
- // Mark new tx not replaceable, if requested.
- if (!coin_control.m_signal_bip125_rbf.value_or(wallet.m_signal_rbf)) {
- for (auto& input : mtx.vin) {
- if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
- }
- }
return Result::OK;
}
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 7a83dbc35d..7e21126298 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -80,9 +80,9 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
argsman.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
#ifdef USE_BDB
- argsman.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
+ argsman.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DatabaseOptions().max_log_mb), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
- argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
+ argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", !DatabaseOptions().use_shared_memory), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
#else
argsman.AddHiddenArgs({"-dblogsize", "-flushwallet", "-privdb"});
#endif
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 9083c304b2..98e843385c 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -111,6 +111,17 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
return result;
}
+WalletTxOut MakeWalletTxOut(const CWallet& wallet,
+ const COutput& output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+{
+ WalletTxOut result;
+ result.txout = output.txout;
+ result.time = output.time;
+ result.depth_in_main_chain = output.depth;
+ result.is_spent = wallet.IsSpent(output.outpoint.hash, output.outpoint.n);
+ return result;
+}
+
class WalletImpl : public Wallet
{
public:
@@ -419,8 +430,8 @@ public:
for (const auto& entry : ListCoins(*m_wallet)) {
auto& group = result[entry.first];
for (const auto& coin : entry.second) {
- group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i),
- MakeWalletTxOut(*m_wallet, *coin.tx, coin.i, coin.nDepth));
+ group.emplace_back(coin.outpoint,
+ MakeWalletTxOut(*m_wallet, coin));
}
}
return result;
@@ -544,6 +555,7 @@ public:
std::shared_ptr<CWallet> wallet;
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(*m_context.args, options);
options.require_create = true;
options.create_flags = wallet_creation_flags;
options.create_passphrase = passphrase;
@@ -553,6 +565,7 @@ public:
{
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(*m_context.args, options);
options.require_existing = true;
return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
}
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index 633d8c5450..98ce95dcd1 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -57,6 +57,7 @@ bool VerifyWallets(WalletContext& context)
if (!args.IsArgSet("wallet")) {
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(args, options);
bilingual_str error_string;
options.require_existing = true;
options.verify = false;
@@ -84,6 +85,7 @@ bool VerifyWallets(WalletContext& context)
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(args, options);
options.require_existing = true;
options.verify = true;
bilingual_str error_string;
@@ -112,6 +114,7 @@ bool LoadWallets(WalletContext& context)
}
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(*context.args, options);
options.require_existing = true;
options.verify = false; // No need to verify, assuming verified earlier in VerifyWallets()
bilingual_str error;
diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp
index 035541babd..781ae3c6e0 100644
--- a/src/wallet/rpc/coins.cpp
+++ b/src/wallet/rpc/coins.cpp
@@ -648,16 +648,16 @@ RPCHelpMan listunspent()
for (const COutput& out : vecOutputs) {
CTxDestination address;
- const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
+ const CScript& scriptPubKey = out.txout.scriptPubKey;
bool fValidAddress = ExtractDestination(scriptPubKey, address);
- bool reused = avoid_reuse && pwallet->IsSpentKey(out.tx->GetHash(), out.i);
+ bool reused = avoid_reuse && pwallet->IsSpentKey(out.outpoint.hash, out.outpoint.n);
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
continue;
UniValue entry(UniValue::VOBJ);
- entry.pushKV("txid", out.tx->GetHash().GetHex());
- entry.pushKV("vout", out.i);
+ entry.pushKV("txid", out.outpoint.hash.GetHex());
+ entry.pushKV("vout", (int)out.outpoint.n);
if (fValidAddress) {
entry.pushKV("address", EncodeDestination(address));
@@ -702,21 +702,21 @@ RPCHelpMan listunspent()
}
entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
- entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue));
- entry.pushKV("confirmations", out.nDepth);
- if (!out.nDepth) {
+ entry.pushKV("amount", ValueFromAmount(out.txout.nValue));
+ entry.pushKV("confirmations", out.depth);
+ if (!out.depth) {
size_t ancestor_count, descendant_count, ancestor_size;
CAmount ancestor_fees;
- pwallet->chain().getTransactionAncestry(out.tx->GetHash(), ancestor_count, descendant_count, &ancestor_size, &ancestor_fees);
+ pwallet->chain().getTransactionAncestry(out.outpoint.hash, ancestor_count, descendant_count, &ancestor_size, &ancestor_fees);
if (ancestor_count) {
entry.pushKV("ancestorcount", uint64_t(ancestor_count));
entry.pushKV("ancestorsize", uint64_t(ancestor_size));
entry.pushKV("ancestorfees", uint64_t(ancestor_fees));
}
}
- entry.pushKV("spendable", out.fSpendable);
- entry.pushKV("solvable", out.fSolvable);
- if (out.fSolvable) {
+ entry.pushKV("spendable", out.spendable);
+ entry.pushKV("solvable", out.solvable);
+ if (out.solvable) {
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
if (provider) {
auto descriptor = InferDescriptor(scriptPubKey, *provider);
@@ -724,7 +724,7 @@ RPCHelpMan listunspent()
}
}
if (avoid_reuse) entry.pushKV("reused", reused);
- entry.pushKV("safe", out.fSafe);
+ entry.pushKV("safe", out.safe);
results.push_back(entry);
}
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index 1380f1a77a..804331eb5d 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -8,6 +8,7 @@
#include <rpc/server.h>
#include <rpc/util.h>
#include <util/translation.h>
+#include <wallet/context.h>
#include <wallet/receive.h>
#include <wallet/rpc/wallet.h>
#include <wallet/rpc/util.h>
@@ -220,6 +221,7 @@ static RPCHelpMan loadwallet()
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(*context.args, options);
options.require_existing = true;
bilingual_str error;
std::vector<bilingual_str> warnings;
@@ -381,6 +383,7 @@ static RPCHelpMan createwallet()
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(*context.args, options);
options.require_create = true;
options.create_flags = flags;
options.create_passphrase = passphrase;
diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp
index 1ecc96fe0e..9ba3c7fd2c 100644
--- a/src/wallet/salvage.cpp
+++ b/src/wallet/salvage.cpp
@@ -23,10 +23,11 @@ static bool KeyFilter(const std::string& type)
return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN;
}
-bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
+bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(args, options);
options.require_existing = true;
options.verify = false;
options.require_format = DatabaseFormat::BERKELEY;
diff --git a/src/wallet/salvage.h b/src/wallet/salvage.h
index 332aceb262..e4822c3c75 100644
--- a/src/wallet/salvage.h
+++ b/src/wallet/salvage.h
@@ -9,10 +9,11 @@
#include <fs.h>
#include <streams.h>
+class ArgsManager;
struct bilingual_str;
namespace wallet {
-bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
+bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
} // namespace wallet
#endif // BITCOIN_WALLET_SALVAGE_H
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index a707ef89d2..304a9f10ca 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -29,11 +29,6 @@ int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out
return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig);
}
-std::string COutput::ToString() const
-{
- return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
-}
-
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig)
{
CMutableTransaction txn;
@@ -158,6 +153,8 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
continue;
}
+ bool tx_from_me = CachedTxIsFromMe(wallet, wtx, ISMINE_ALL);
+
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
// Only consider selected coins if add_inputs is false
if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(COutPoint(entry.first, i))) {
@@ -190,8 +187,9 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
+ int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));
- vCoins.push_back(COutput(wallet, wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
+ vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me);
// Checks the sum amount of all UTXO's.
if (nMinimumSumAmount != MAX_MONEY) {
@@ -218,8 +216,8 @@ CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinContr
std::vector<COutput> vCoins;
AvailableCoins(wallet, vCoins, coinControl);
for (const COutput& out : vCoins) {
- if (out.fSpendable) {
- balance += out.tx->tx->vout[out.i].nValue;
+ if (out.spendable) {
+ balance += out.txout.nValue;
}
}
return balance;
@@ -243,6 +241,12 @@ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransactio
return ptx->vout[n];
}
+const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint)
+{
+ AssertLockHeld(wallet.cs_wallet);
+ return FindNonChangeParentOutput(wallet, *wallet.GetWalletTx(outpoint.hash)->tx, outpoint.n);
+}
+
std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
{
AssertLockHeld(wallet.cs_wallet);
@@ -254,8 +258,8 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
for (const COutput& coin : availableCoins) {
CTxDestination address;
- if ((coin.fSpendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) &&
- ExtractDestination(FindNonChangeParentOutput(wallet, *coin.tx->tx, coin.i).scriptPubKey, address)) {
+ if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
+ ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
result[address].emplace_back(std::move(coin));
}
}
@@ -268,14 +272,15 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
for (const COutPoint& output : lockedCoins) {
auto it = wallet.mapWallet.find(output.hash);
if (it != wallet.mapWallet.end()) {
- int depth = wallet.GetTxDepthInMainChain(it->second);
- if (depth >= 0 && output.n < it->second.tx->vout.size() &&
- wallet.IsMine(it->second.tx->vout[output.n]) == is_mine_filter
+ const auto& wtx = it->second;
+ int depth = wallet.GetTxDepthInMainChain(wtx);
+ if (depth >= 0 && output.n < wtx.tx->vout.size() &&
+ wallet.IsMine(wtx.tx->vout[output.n]) == is_mine_filter
) {
CTxDestination address;
- if (ExtractDestination(FindNonChangeParentOutput(wallet, *it->second.tx, output.n).scriptPubKey, address)) {
+ if (ExtractDestination(FindNonChangeParentOutput(wallet, *wtx.tx, output.n).scriptPubKey, address)) {
result[address].emplace_back(
- wallet, it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */);
+ COutPoint(wtx.GetHash(), output.n), wtx.tx->vout.at(output.n), depth, GetTxSpendSize(wallet, wtx, output.n), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL));
}
}
}
@@ -292,15 +297,14 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
// Allowing partial spends means no grouping. Each COutput gets its own OutputGroup.
for (const COutput& output : outputs) {
// Skip outputs we cannot spend
- if (!output.fSpendable) continue;
+ if (!output.spendable) continue;
size_t ancestors, descendants;
- wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
- CInputCoin input_coin = output.GetInputCoin();
+ wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
// Make an OutputGroup containing just this output
OutputGroup group{coin_sel_params};
- group.Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only);
+ group.Insert(output, ancestors, descendants, positive_only);
// Check the OutputGroup's eligibility. Only add the eligible ones.
if (positive_only && group.GetSelectionAmount() <= 0) continue;
@@ -312,18 +316,17 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
// We want to combine COutputs that have the same scriptPubKey into single OutputGroups
// except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.
// To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.
- // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added
+ // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput is added
// to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
- // OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector.
+ // OUTPUT_GROUP_MAX_ENTRIES COutputs, a new OutputGroup is added to the end of the vector.
std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
for (const auto& output : outputs) {
// Skip outputs we cannot spend
- if (!output.fSpendable) continue;
+ if (!output.spendable) continue;
size_t ancestors, descendants;
- wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
- CInputCoin input_coin = output.GetInputCoin();
- CScript spk = input_coin.txout.scriptPubKey;
+ wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
+ CScript spk = output.txout.scriptPubKey;
std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
@@ -332,7 +335,7 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
groups.emplace_back(coin_sel_params);
}
- // Get the last OutputGroup in the vector so that we can add the CInputCoin to it
+ // Get the last OutputGroup in the vector so that we can add the COutput to it
// A pointer is used here so that group can be reassigned later if it is full.
OutputGroup* group = &groups.back();
@@ -344,8 +347,8 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
group = &groups.back();
}
- // Add the input_coin to group
- group->Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only);
+ // Add the output to group
+ group->Insert(output, ancestors, descendants, positive_only);
}
// Now we go through the entire map and pull out the OutputGroups
@@ -386,7 +389,7 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
std::vector<OutputGroup> all_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, false /* positive_only */);
// While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output.
// So we need to include that for KnapsackSolver as well, as we are expecting to create a change output.
- if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue + coin_selection_params.m_change_fee)}) {
+ if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue + coin_selection_params.m_change_fee, coin_selection_params.rng_fast)}) {
knapsack_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
results.push_back(*knapsack_result);
}
@@ -394,7 +397,7 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
// We include the minimum final change for SRD as we do want to avoid making really small change.
// KnapsackSolver does not need this because it includes MIN_CHANGE internally.
const CAmount srd_target = nTargetValue + coin_selection_params.m_change_fee + MIN_FINAL_CHANGE;
- if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target)}) {
+ if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target, coin_selection_params.rng_fast)}) {
srd_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
results.push_back(*srd_result);
}
@@ -421,11 +424,11 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
{
for (const COutput& out : vCoins) {
- if (!out.fSpendable) continue;
- /* Set depth, from_me, ancestors, and descendants to 0 or false as these don't matter for preset inputs as no actual selection is being done.
+ if (!out.spendable) continue;
+ /* Set ancestors and descendants to 0 as these don't matter for preset inputs as no actual selection is being done.
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
*/
- preset_inputs.Insert(out.GetInputCoin(), 0, false, 0, 0, false);
+ preset_inputs.Insert(out, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
SelectionResult result(nTargetValue);
result.AddInput(preset_inputs);
@@ -434,7 +437,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
}
// calculate value from preset inputs and store them
- std::set<CInputCoin> setPresetCoins;
+ std::set<COutPoint> preset_coins;
std::vector<COutPoint> vPresetInputs;
coin_control.ListSelected(vPresetInputs);
@@ -462,27 +465,29 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
}
- CInputCoin coin(outpoint, txout, input_bytes);
- if (coin.m_input_bytes == -1) {
+ if (input_bytes == -1) {
return std::nullopt; // Not solvable, can't estimate size for fee
}
- coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes);
+
+ /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
+ COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
+ output.effective_value = output.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(output.input_bytes);
if (coin_selection_params.m_subtract_fee_outputs) {
- value_to_select -= coin.txout.nValue;
+ value_to_select -= output.txout.nValue;
} else {
- value_to_select -= coin.effective_value;
+ value_to_select -= output.effective_value;
}
- setPresetCoins.insert(coin);
- /* Set depth, from_me, ancestors, and descendants to 0 or false as don't matter for preset inputs as no actual selection is being done.
+ preset_coins.insert(outpoint);
+ /* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done.
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
*/
- preset_inputs.Insert(coin, 0, false, 0, 0, false);
+ preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
// remove preset inputs from vCoins so that Coin Selection doesn't pick them.
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
{
- if (setPresetCoins.count(it->GetInputCoin()))
+ if (preset_coins.count(it->outpoint))
it = vCoins.erase(it);
else
++it;
@@ -501,7 +506,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
// Cases where we have 101+ outputs all pointing to the same destination may result in
// privacy leaks as they will potentially be deterministically sorted. We solve that by
// explicitly shuffling the outputs before processing
- Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
+ Shuffle(vCoins.begin(), vCoins.end(), coin_selection_params.rng_fast);
}
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
@@ -585,7 +590,8 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256&
* Set a height-based locktime for new transactions (uses the height of the
* current chain tip unless we are not synced with the current chain
*/
-static void DiscourageFeeSniping(CMutableTransaction& tx, interfaces::Chain& chain, const uint256& block_hash, int block_height)
+static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
+ interfaces::Chain& chain, const uint256& block_hash, int block_height)
{
// All inputs must be added by now
assert(!tx.vin.empty());
@@ -616,8 +622,8 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, interfaces::Chain& cha
// that transactions that are delayed after signing for whatever reason,
// e.g. high-latency mix networks and some CoinJoin implementations, have
// better privacy.
- if (GetRandInt(10) == 0) {
- tx.nLockTime = std::max(0, int(tx.nLockTime) - GetRandInt(100));
+ if (rng_fast.randrange(10) == 0) {
+ tx.nLockTime = std::max(0, int(tx.nLockTime) - int(rng_fast.randrange(100)));
}
} else {
// If our chain is lagging behind, we can't discourage fee sniping nor help
@@ -653,9 +659,10 @@ static bool CreateTransactionInternal(
{
AssertLockHeld(wallet.cs_wallet);
+ FastRandomContext rng_fast;
CMutableTransaction txNew; // The resulting transaction that we make
- CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
+ CoinSelectionParams coin_selection_params{rng_fast}; // Parameters for coin selection, init with dummy
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
// Set the long term feerate estimate to the wallet's consolidate feerate
@@ -782,10 +789,9 @@ static bool CreateTransactionInternal(
assert(change_and_fee >= 0);
CTxOut newTxOut(change_and_fee, scriptChange);
- if (nChangePosInOut == -1)
- {
+ if (nChangePosInOut == -1) {
// Insert change txn at random position:
- nChangePosInOut = GetRandInt(txNew.vout.size()+1);
+ nChangePosInOut = rng_fast.randrange(txNew.vout.size() + 1);
}
else if ((unsigned int)nChangePosInOut > txNew.vout.size())
{
@@ -797,7 +803,7 @@ static bool CreateTransactionInternal(
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
// Shuffle selected coins and fill in final vin
- std::vector<CInputCoin> selected_coins = result->GetShuffledInputVector();
+ std::vector<COutput> selected_coins = result->GetShuffledInputVector();
// The sequence number is set to non-maxint so that DiscourageFeeSniping
// works.
@@ -811,7 +817,7 @@ static bool CreateTransactionInternal(
for (const auto& coin : selected_coins) {
txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
}
- DiscourageFeeSniping(txNew, wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight());
+ DiscourageFeeSniping(txNew, rng_fast, wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight());
// Calculate the transaction fee
TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index 4453fb2762..e43aac5273 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -11,61 +11,11 @@
#include <wallet/wallet.h>
namespace wallet {
-/** Get the marginal bytes if spending the specified output from this transaction */
+/** Get the marginal bytes if spending the specified output from this transaction.
+ * use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the
+ * size of the input spend. This should only be set when watch-only outputs are allowed */
int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false);
-class COutput
-{
-public:
- const CWalletTx *tx;
-
- /** Index in tx->vout. */
- int i;
-
- /**
- * Depth in block chain.
- * If > 0: the tx is on chain and has this many confirmations.
- * If = 0: the tx is waiting confirmation.
- * If < 0: a conflicting tx is on chain and has this many confirmations. */
- int nDepth;
-
- /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
- int nInputBytes;
-
- /** Whether we have the private keys to spend this output */
- bool fSpendable;
-
- /** Whether we know how to spend this output, ignoring the lack of keys */
- bool fSolvable;
-
- /** Whether to use the maximum sized, 72 byte signature when calculating the size of the input spend. This should only be set when watch-only outputs are allowed */
- bool use_max_sig;
-
- /**
- * Whether this output is considered safe to spend. Unconfirmed transactions
- * from outside keys and unconfirmed replacement transactions are considered
- * unsafe and will not be used to fund new spending transactions.
- */
- bool fSafe;
-
- COutput(const CWallet& wallet, const CWalletTx& wtx, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false)
- {
- tx = &wtx; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in;
- // If known and signable by the given wallet, compute nInputBytes
- // Failure will keep this value -1
- if (fSpendable) {
- nInputBytes = GetTxSpendSize(wallet, wtx, i, use_max_sig);
- }
- }
-
- std::string ToString() const;
-
- inline CInputCoin GetInputCoin() const
- {
- return CInputCoin(tx->tx, i, nInputBytes);
- }
-};
-
//Get the marginal bytes of spending the specified output
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* pwallet, bool use_max_sig = false);
@@ -93,6 +43,7 @@ CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinContr
* Find non-change parent output.
*/
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
/**
* Return list of available coins and locked coins grouped by non-change output address.
diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp
index 55949e6e7a..3f860289f9 100644
--- a/src/wallet/sqlite.cpp
+++ b/src/wallet/sqlite.cpp
@@ -83,8 +83,8 @@ static void SetPragma(sqlite3* db, const std::string& key, const std::string& va
}
}
-SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, bool mock)
- : WalletDatabase(), m_mock(mock), m_dir_path(fs::PathToString(dir_path)), m_file_path(fs::PathToString(file_path))
+SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock)
+ : WalletDatabase(), m_mock(mock), m_dir_path(fs::PathToString(dir_path)), m_file_path(fs::PathToString(file_path)), m_use_unsafe_sync(options.use_unsafe_sync)
{
{
LOCK(g_sqlite_mutex);
@@ -255,7 +255,7 @@ void SQLiteDatabase::Open()
// Enable fullfsync for the platforms that use it
SetPragma(m_db, "fullfsync", "true", "Failed to enable fullfsync");
- if (gArgs.GetBoolArg("-unsafesqlitesync", false)) {
+ if (m_use_unsafe_sync) {
// Use normal synchronous mode for the journal
LogPrintf("WARNING SQLite is configured to not wait for data to be flushed to disk. Data loss and corruption may occur.\n");
SetPragma(m_db, "synchronous", "OFF", "Failed to set synchronous mode to OFF");
@@ -546,7 +546,7 @@ std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const D
{
try {
fs::path data_file = SQLiteDataFile(path);
- auto db = std::make_unique<SQLiteDatabase>(data_file.parent_path(), data_file);
+ auto db = std::make_unique<SQLiteDatabase>(data_file.parent_path(), data_file, options);
if (options.verify && !db->Verify(error)) {
status = DatabaseStatus::FAILED_VERIFY;
return nullptr;
diff --git a/src/wallet/sqlite.h b/src/wallet/sqlite.h
index 3ed598d0d2..47b7ebb0ec 100644
--- a/src/wallet/sqlite.h
+++ b/src/wallet/sqlite.h
@@ -69,7 +69,7 @@ public:
SQLiteDatabase() = delete;
/** Create DB handle to real database */
- SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, bool mock = false);
+ SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock = false);
~SQLiteDatabase();
@@ -113,6 +113,7 @@ public:
std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override;
sqlite3* m_db{nullptr};
+ bool m_use_unsafe_sync;
};
std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index b9f12158ca..14bec8ca4c 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -28,20 +28,20 @@ BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup)
// we repeat those tests this many times and only complain if all iterations of the test fail
#define RANDOM_REPEATS 5
-typedef std::set<CInputCoin> CoinSet;
+typedef std::set<COutput> CoinSet;
static const CoinEligibilityFilter filter_standard(1, 6, 0);
static const CoinEligibilityFilter filter_confirmed(1, 1, 0);
static const CoinEligibilityFilter filter_standard_extra(6, 6, 0);
static int nextLockTime = 0;
-static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set)
+static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& set)
{
CMutableTransaction tx;
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
- set.emplace_back(MakeTransactionRef(tx), nInput);
+ set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
}
static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
@@ -50,9 +50,9 @@ static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
- CInputCoin coin(MakeTransactionRef(tx), nInput);
+ COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
OutputGroup group;
- group.Insert(coin, 1, false, 0, 0, true);
+ group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true);
result.AddInput(group);
}
@@ -62,10 +62,10 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
- CInputCoin coin(MakeTransactionRef(tx), nInput);
+ COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
coin.effective_value = nValue - fee;
- coin.m_fee = fee;
- coin.m_long_term_fee = long_term_fee;
+ coin.fee = fee;
+ coin.long_term_fee = long_term_fee;
set.insert(coin);
}
@@ -82,24 +82,13 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
assert(destination_ok);
tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
}
- if (fIsFromMe) {
- // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
- // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
- tx.vin.resize(1);
- }
uint256 txid = tx.GetHash();
LOCK(wallet.cs_wallet);
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
assert(ret.second);
CWalletTx& wtx = (*ret.first).second;
- if (fIsFromMe)
- {
- wtx.m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1);
- wtx.m_is_cache_empty = false;
- }
- COutput output(wallet, wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
- coins.push_back(output);
+ coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe);
}
/** Check if SelectionResult a is equivalent to SelectionResult b.
@@ -124,11 +113,14 @@ static bool EquivalentResult(const SelectionResult& a, const SelectionResult& b)
/** Check if this selection is equal to another one. Equal means same inputs (i.e same value and prevout) */
static bool EqualResult(const SelectionResult& a, const SelectionResult& b)
{
- std::pair<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin());
+ std::pair<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin(),
+ [](const COutput& a, const COutput& b) {
+ return a.outpoint == b.outpoint;
+ });
return ret.first == a.GetInputSet().end() && ret.second == b.GetInputSet().end();
}
-static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
+static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool)
{
utxo_pool.clear();
CAmount target = 0;
@@ -140,34 +132,30 @@ static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
return target;
}
-inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins)
-{
- static std::vector<OutputGroup> static_groups;
- static_groups.clear();
- for (auto& coin : coins) {
- static_groups.emplace_back();
- static_groups.back().Insert(coin, 0, true, 0, 0, false);
- }
- return static_groups;
-}
-
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
{
static std::vector<OutputGroup> static_groups;
static_groups.clear();
for (auto& coin : coins) {
static_groups.emplace_back();
- static_groups.back().Insert(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0, false);
+ static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
return static_groups;
}
inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& coins, CWallet& wallet, const CoinEligibilityFilter& filter)
{
- CoinSelectionParams coin_selection_params(/* change_output_size= */ 0,
- /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(0),
- /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
+ FastRandomContext rand{};
+ CoinSelectionParams coin_selection_params{
+ rand,
+ /* change_output_size= */ 0,
+ /* change_spend_size= */ 0,
+ /* effective_feerate= */ CFeeRate(0),
+ /* long_term_feerate= */ CFeeRate(0),
+ /* discard_feerate= */ CFeeRate(0),
+ /* tx_noinputs_size= */ 0,
+ /* avoid_partial= */ false,
+ };
static std::vector<OutputGroup> static_groups;
static_groups = GroupOutputs(wallet, coins, coin_selection_params, filter, /*positive_only=*/false);
return static_groups;
@@ -176,8 +164,9 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>
// Branch and bound coin selection tests
BOOST_AUTO_TEST_CASE(bnb_search_test)
{
+ FastRandomContext rand{};
// Setup
- std::vector<CInputCoin> utxo_pool;
+ std::vector<COutput> utxo_pool;
SelectionResult expected_result(CAmount(0));
/////////////////////////
@@ -301,10 +290,16 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
}
// Make sure that effective value is working in AttemptSelection when BnB is used
- CoinSelectionParams coin_selection_params_bnb(/* change_output_size= */ 0,
- /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(3000),
- /* long_term_feerate= */ CFeeRate(1000), /* discard_feerate= */ CFeeRate(1000),
- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
+ CoinSelectionParams coin_selection_params_bnb{
+ rand,
+ /* change_output_size= */ 0,
+ /* change_spend_size= */ 0,
+ /* effective_feerate= */ CFeeRate(3000),
+ /* long_term_feerate= */ CFeeRate(1000),
+ /* discard_feerate= */ CFeeRate(1000),
+ /* tx_noinputs_size= */ 0,
+ /* avoid_partial= */ false,
+ };
{
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
wallet->LoadWallet();
@@ -315,13 +310,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
std::vector<COutput> coins;
add_coin(coins, *wallet, 1);
- coins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
+ coins.at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
// Test fees subtracted from output:
coins.clear();
add_coin(coins, *wallet, 1 * CENT);
- coins.at(0).nInputBytes = 40;
+ coins.at(0).input_bytes = 40;
coin_selection_params_bnb.m_subtract_fee_outputs = true;
const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
BOOST_CHECK(result9);
@@ -342,7 +337,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
add_coin(coins, *wallet, 2 * CENT, 6 * 24, false, 0, true);
CCoinControl coin_control;
coin_control.fAllowOtherInputs = true;
- coin_control.Select(COutPoint(coins.at(0).tx->GetHash(), coins.at(0).i));
+ coin_control.Select(coins.at(0).outpoint);
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
const auto result10 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(result10);
@@ -351,6 +346,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
BOOST_AUTO_TEST_CASE(knapsack_solver_test)
{
+ FastRandomContext rand{};
+ const auto temp1{[&rand](std::vector<OutputGroup>& g, const CAmount& v) { return KnapsackSolver(g, v, rand); }};
+ const auto KnapsackSolver{temp1};
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
wallet->LoadWallet();
LOCK(wallet->cs_wallet);
@@ -660,6 +658,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
{
+ FastRandomContext rand{};
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
wallet->LoadWallet();
LOCK(wallet->cs_wallet);
@@ -673,7 +672,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
add_coin(coins, *wallet, 1000 * COIN);
add_coin(coins, *wallet, 3 * COIN);
- const auto result = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1003 * COIN);
+ const auto result = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1003 * COIN, rand);
BOOST_CHECK(result);
BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN);
BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U);
@@ -714,10 +713,16 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
CAmount target = rand.randrange(balance - 1000) + 1000;
// Perform selection
- CoinSelectionParams cs_params(/* change_output_size= */ 34,
- /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
- /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
+ CoinSelectionParams cs_params{
+ rand,
+ /* change_output_size= */ 34,
+ /* change_spend_size= */ 148,
+ /* effective_feerate= */ CFeeRate(0),
+ /* long_term_feerate= */ CFeeRate(0),
+ /* discard_feerate= */ CFeeRate(0),
+ /* tx_noinputs_size= */ 0,
+ /* avoid_partial= */ false,
+ };
CCoinControl cc;
const auto result = SelectCoins(*wallet, coins, target, cc, cs_params);
BOOST_CHECK(result);
diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp
index 35ae3707f8..fbf1e0efd3 100644
--- a/src/wallet/test/db_tests.cpp
+++ b/src/wallet/test/db_tests.cpp
@@ -19,13 +19,13 @@ static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, s
{
fs::path data_file = BDBDataFile(path);
database_filename = fs::PathToString(data_file.filename());
- return GetBerkeleyEnv(data_file.parent_path());
+ return GetBerkeleyEnv(data_file.parent_path(), false);
}
BOOST_AUTO_TEST_CASE(getwalletenv_file)
{
std::string test_name = "test_name.dat";
- const fs::path datadir = gArgs.GetDataDirNet();
+ const fs::path datadir = m_args.GetDataDirNet();
fs::path file_path = datadir / test_name;
std::ofstream f{file_path};
f.close();
@@ -39,7 +39,7 @@ BOOST_AUTO_TEST_CASE(getwalletenv_file)
BOOST_AUTO_TEST_CASE(getwalletenv_directory)
{
std::string expected_name = "wallet.dat";
- const fs::path datadir = gArgs.GetDataDirNet();
+ const fs::path datadir = m_args.GetDataDirNet();
std::string filename;
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(datadir, filename);
@@ -49,8 +49,8 @@ BOOST_AUTO_TEST_CASE(getwalletenv_directory)
BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_multiple)
{
- fs::path datadir = gArgs.GetDataDirNet() / "1";
- fs::path datadir_2 = gArgs.GetDataDirNet() / "2";
+ fs::path datadir = m_args.GetDataDirNet() / "1";
+ fs::path datadir_2 = m_args.GetDataDirNet() / "2";
std::string filename;
std::shared_ptr<BerkeleyEnvironment> env_1 = GetWalletEnv(datadir, filename);
diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp
index be38cebafd..34c22a9c0d 100644
--- a/src/wallet/test/init_test_fixture.cpp
+++ b/src/wallet/test/init_test_fixture.cpp
@@ -15,12 +15,12 @@
namespace wallet {
InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName) : BasicTestingSetup(chainName)
{
- m_wallet_loader = MakeWalletLoader(*m_node.chain, *Assert(m_node.args));
+ m_wallet_loader = MakeWalletLoader(*m_node.chain, m_args);
std::string sep;
sep += fs::path::preferred_separator;
- m_datadir = gArgs.GetDataDirNet();
+ m_datadir = m_args.GetDataDirNet();
m_cwd = fs::current_path();
m_walletdir_path_cases["default"] = m_datadir / "wallets";
@@ -42,14 +42,11 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainNam
InitWalletDirTestingSetup::~InitWalletDirTestingSetup()
{
- gArgs.LockSettings([&](util::Settings& settings) {
- settings.forced_settings.erase("walletdir");
- });
fs::current_path(m_cwd);
}
void InitWalletDirTestingSetup::SetWalletDir(const fs::path& walletdir_path)
{
- gArgs.ForceSetArg("-walletdir", fs::PathToString(walletdir_path));
+ m_args.ForceSetArg("-walletdir", fs::PathToString(walletdir_path));
}
} // namespace wallet
diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp
index 7fdecc5642..fb0a07e181 100644
--- a/src/wallet/test/init_tests.cpp
+++ b/src/wallet/test/init_tests.cpp
@@ -18,7 +18,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_default)
SetWalletDir(m_walletdir_path_cases["default"]);
bool result = m_wallet_loader->verify();
BOOST_CHECK(result == true);
- fs::path walletdir = gArgs.GetPathArg("-walletdir");
+ fs::path walletdir = m_args.GetPathArg("-walletdir");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
BOOST_CHECK_EQUAL(walletdir, expected_path);
}
@@ -28,7 +28,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom)
SetWalletDir(m_walletdir_path_cases["custom"]);
bool result = m_wallet_loader->verify();
BOOST_CHECK(result == true);
- fs::path walletdir = gArgs.GetPathArg("-walletdir");
+ fs::path walletdir = m_args.GetPathArg("-walletdir");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["custom"]);
BOOST_CHECK_EQUAL(walletdir, expected_path);
}
@@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing)
SetWalletDir(m_walletdir_path_cases["trailing"]);
bool result = m_wallet_loader->verify();
BOOST_CHECK(result == true);
- fs::path walletdir = gArgs.GetPathArg("-walletdir");
+ fs::path walletdir = m_args.GetPathArg("-walletdir");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
BOOST_CHECK_EQUAL(walletdir, expected_path);
}
@@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing2)
SetWalletDir(m_walletdir_path_cases["trailing2"]);
bool result = m_wallet_loader->verify();
BOOST_CHECK(result == true);
- fs::path walletdir = gArgs.GetPathArg("-walletdir");
+ fs::path walletdir = m_args.GetPathArg("-walletdir");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
BOOST_CHECK_EQUAL(walletdir, expected_path);
}
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index c59f7e6f05..c1fa2c3c60 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -221,7 +221,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
wallet->SetupLegacyScriptPubKeyMan();
WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash()));
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
AddWallet(context, wallet);
UniValue keys;
keys.setArray();
@@ -277,12 +277,12 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
SetMockTime(KEY_TIME);
m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
- std::string backup_file = fs::PathToString(gArgs.GetDataDirNet() / "wallet.backup");
+ std::string backup_file = fs::PathToString(m_args.GetDataDirNet() / "wallet.backup");
// Import key into wallet and call dumpwallet to create backup file.
{
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
{
auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
@@ -310,7 +310,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
wallet->SetupLegacyScriptPubKeyMan();
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
JSONRPCRequest request;
request.context = &context;
request.params.setArray();
@@ -588,7 +588,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
for (const auto& group : list) {
for (const auto& coin : group.second) {
LOCK(wallet->cs_wallet);
- wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
+ wallet->LockCoin(coin.outpoint);
}
}
{
@@ -716,10 +716,10 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup)
//! rescanning where new transactions in new blocks could be lost.
BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
{
- gArgs.ForceSetArg("-unsafesqlitesync", "1");
+ m_args.ForceSetArg("-unsafesqlitesync", "1");
// Create new wallet with known key and unload it.
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
context.chain = m_node.chain.get();
auto wallet = TestLoadWallet(context);
CKey key;
@@ -812,7 +812,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
{
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
auto wallet = TestLoadWallet(context);
BOOST_CHECK(wallet);
UnloadWallet(std::move(wallet));
@@ -820,9 +820,9 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
{
- gArgs.ForceSetArg("-unsafesqlitesync", "1");
+ m_args.ForceSetArg("-unsafesqlitesync", "1");
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
context.chain = m_node.chain.get();
auto wallet = TestLoadWallet(context);
CKey key;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 261d042529..be64b4cdbb 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -366,6 +366,7 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& backup_file, const std::string& wallet_name, std::optional<bool> load_on_start, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
DatabaseOptions options;
+ ReadDatabaseArgs(*context.args, options);
options.require_existing = true;
if (!fs::exists(backup_file)) {
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 6e100524a4..1251c9f37e 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -1189,10 +1189,11 @@ std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase()
/** Return object for accessing temporary in-memory database. */
std::unique_ptr<WalletDatabase> CreateMockWalletDatabase()
{
+ DatabaseOptions options;
#ifdef USE_SQLITE
- return std::make_unique<SQLiteDatabase>("", "", true);
+ return std::make_unique<SQLiteDatabase>("", "", options, true);
#elif USE_BDB
- return std::make_unique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "");
+ return std::make_unique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "", options);
#endif
}
} // namespace wallet
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index 9cd18dd0a5..769175b5a8 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -140,6 +140,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
if (command == "create") {
DatabaseOptions options;
+ ReadDatabaseArgs(args, options);
options.require_create = true;
// If -legacy is set, use it. Otherwise default to false.
bool make_legacy = args.GetBoolArg("-legacy", false);
@@ -165,6 +166,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
}
} else if (command == "info") {
DatabaseOptions options;
+ ReadDatabaseArgs(args, options);
options.require_existing = true;
const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, args, options);
if (!wallet_instance) return false;
@@ -174,7 +176,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
#ifdef USE_BDB
bilingual_str error;
std::vector<bilingual_str> warnings;
- bool ret = RecoverDatabaseFile(path, error, warnings);
+ bool ret = RecoverDatabaseFile(args, path, error, warnings);
if (!ret) {
for (const auto& warning : warnings) {
tfm::format(std::cerr, "%s\n", warning.original);
@@ -190,11 +192,12 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
#endif
} else if (command == "dump") {
DatabaseOptions options;
+ ReadDatabaseArgs(args, options);
options.require_existing = true;
const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, args, options);
if (!wallet_instance) return false;
bilingual_str error;
- bool ret = DumpWallet(*wallet_instance, error);
+ bool ret = DumpWallet(args, *wallet_instance, error);
if (!ret && !error.empty()) {
tfm::format(std::cerr, "%s\n", error.original);
return ret;
@@ -204,7 +207,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
} else if (command == "createfromdump") {
bilingual_str error;
std::vector<bilingual_str> warnings;
- bool ret = CreateFromDump(name, path, error, warnings);
+ bool ret = CreateFromDump(args, name, path, error, warnings);
for (const auto& warning : warnings) {
tfm::format(std::cout, "%s\n", warning.original);
}
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index fb0f6d7cb7..14308b3fd1 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -30,6 +30,11 @@ addnode connect to generic DNS name
addnode connect to a CJDNS address
- Test getnetworkinfo for each node
+
+- Test passing invalid -proxy
+- Test passing invalid -onion
+- Test passing -onlynet=onion without -proxy or -onion
+- Test passing -onlynet=onion with -onion=0 and with -noonion
"""
import socket
@@ -263,12 +268,13 @@ class ProxyTest(BitcoinTestFramework):
n2 = networks_dict(self.nodes[2].getnetworkinfo())
assert_equal(NETWORKS, n2.keys())
+ proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}'
for net in NETWORKS:
if net == NET_I2P:
expected_proxy = ''
expected_randomize = False
else:
- expected_proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}'
+ expected_proxy = proxy
expected_randomize = True
assert_equal(n2[net]['proxy'], expected_proxy)
assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize)
@@ -279,11 +285,9 @@ class ProxyTest(BitcoinTestFramework):
if self.have_ipv6:
n3 = networks_dict(self.nodes[3].getnetworkinfo())
assert_equal(NETWORKS, n3.keys())
+ proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}'
for net in NETWORKS:
- if net == NET_I2P or net == NET_ONION:
- expected_proxy = ''
- else:
- expected_proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}'
+ expected_proxy = '' if net == NET_I2P or net == NET_ONION else proxy
assert_equal(n3[net]['proxy'], expected_proxy)
assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False)
@@ -305,6 +309,32 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n4['i2p']['reachable'], False)
assert_equal(n4['cjdns']['reachable'], True)
+ self.stop_node(1)
+
+ self.log.info("Test passing invalid -proxy raises expected init error")
+ self.nodes[1].extra_args = ["-proxy=abc:def"]
+ msg = "Error: Invalid -proxy address or hostname: 'abc:def'"
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing invalid -onion raises expected init error")
+ self.nodes[1].extra_args = ["-onion=xyz:abc"]
+ msg = "Error: Invalid -onion address or hostname: 'xyz:abc'"
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ msg = (
+ "Error: Outbound connections restricted to Tor (-onlynet=onion) but "
+ "the proxy for reaching the Tor network is not provided (no -proxy= "
+ "and no -onion= given) or it is explicitly forbidden (-onion=0)"
+ )
+ self.log.info("Test passing -onlynet=onion without -proxy or -onion raises expected init error")
+ self.nodes[1].extra_args = ["-onlynet=onion"]
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing -onlynet=onion with -onion=0/-noonion raises expected init error")
+ for arg in ["-onion=0", "-noonion"]:
+ self.nodes[1].extra_args = ["-onlynet=onion", arg]
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
if __name__ == '__main__':
ProxyTest().main()
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index ba3c5053cb..bf19384279 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -141,6 +141,10 @@ class PruneTest(BitcoinTestFramework):
expected_msg='Error: Prune mode is incompatible with -coinstatsindex.',
extra_args=['-prune=550', '-coinstatsindex'],
)
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg='Error: Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.',
+ extra_args=['-prune=550', '-reindex-chainstate'],
+ )
def test_height_min(self):
assert os.path.isfile(os.path.join(self.prunedir, "blk00000.dat")), "blk00000.dat is missing, pruning too early"
diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py
index 1ee12c0040..7d8d10589b 100755
--- a/test/functional/interface_zmq.py
+++ b/test/functional/interface_zmq.py
@@ -23,6 +23,9 @@ from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
+from test_framework.wallet import (
+ MiniWallet,
+)
from test_framework.netutil import test_ipv6_local
from io import BytesIO
from time import sleep
@@ -100,8 +103,6 @@ class ZMQTestSetupBlock:
class ZMQTest (BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
- if self.is_wallet_compiled():
- self.requires_wallet = True
# This test isn't testing txn relay/timing, so set whitelist on the
# peers for instant txn relay. This speeds up the test run time 2-3x.
self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes
@@ -111,6 +112,7 @@ class ZMQTest (BitcoinTestFramework):
self.skip_if_no_bitcoind_zmq()
def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
self.ctx = zmq.Context()
try:
self.test_basic()
@@ -211,25 +213,25 @@ class ZMQTest (BitcoinTestFramework):
assert_equal([txid.hex()], self.nodes[1].getblock(hash)["tx"])
- if self.is_wallet_compiled():
- self.log.info("Wait for tx from second node")
- payment_txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
- self.sync_all()
-
- # Should receive the broadcasted txid.
- txid = hashtx.receive()
- assert_equal(payment_txid, txid.hex())
+ self.wallet.rescan_utxos()
+ self.log.info("Wait for tx from second node")
+ payment_tx = self.wallet.send_self_transfer(from_node=self.nodes[1])
+ payment_txid = payment_tx['txid']
+ self.sync_all()
+ # Should receive the broadcasted txid.
+ txid = hashtx.receive()
+ assert_equal(payment_txid, txid.hex())
- # Should receive the broadcasted raw transaction.
- hex = rawtx.receive()
- assert_equal(payment_txid, hash256_reversed(hex).hex())
+ # Should receive the broadcasted raw transaction.
+ hex = rawtx.receive()
+ assert_equal(payment_tx['wtxid'], hash256_reversed(hex).hex())
- # Mining the block with this tx should result in second notification
- # after coinbase tx notification
- self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
- hashtx.receive()
- txid = hashtx.receive()
- assert_equal(payment_txid, txid.hex())
+ # Mining the block with this tx should result in second notification
+ # after coinbase tx notification
+ self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
+ hashtx.receive()
+ txid = hashtx.receive()
+ assert_equal(payment_txid, txid.hex())
self.log.info("Test the getzmqnotifications RPC")
@@ -243,9 +245,6 @@ class ZMQTest (BitcoinTestFramework):
assert_equal(self.nodes[1].getzmqnotifications(), [])
def test_reorg(self):
- if not self.is_wallet_compiled():
- self.log.info("Skipping reorg test because wallet is disabled")
- return
address = 'tcp://127.0.0.1:28333'
@@ -256,7 +255,7 @@ class ZMQTest (BitcoinTestFramework):
self.disconnect_nodes(0, 1)
# Generate 1 block in nodes[0] with 1 mempool tx and receive all notifications
- payment_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
+ payment_txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']
disconnect_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE, sync_fun=self.no_op)[0]
disconnect_cb = self.nodes[0].getblock(disconnect_block)["tx"][0]
assert_equal(self.nodes[0].getbestblockhash(), hashblock.receive().hex())
@@ -325,126 +324,124 @@ class ZMQTest (BitcoinTestFramework):
assert_equal((self.nodes[1].getblockhash(block_count-1), "C", None), seq.receive_sequence())
assert_equal((self.nodes[1].getblockhash(block_count), "C", None), seq.receive_sequence())
- # Rest of test requires wallet functionality
- if self.is_wallet_compiled():
- self.log.info("Wait for tx from second node")
- payment_txid = self.nodes[1].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=5.0, replaceable=True)
- self.sync_all()
- self.log.info("Testing sequence notifications with mempool sequence values")
-
- # Should receive the broadcasted txid.
- assert_equal((payment_txid, "A", seq_num), seq.receive_sequence())
- seq_num += 1
-
- self.log.info("Testing RBF notification")
- # Replace it to test eviction/addition notification
- rbf_info = self.nodes[1].bumpfee(payment_txid)
- self.sync_all()
- assert_equal((payment_txid, "R", seq_num), seq.receive_sequence())
- seq_num += 1
- assert_equal((rbf_info["txid"], "A", seq_num), seq.receive_sequence())
- seq_num += 1
-
- # Doesn't get published when mined, make a block and tx to "flush" the possibility
- # though the mempool sequence number does go up by the number of transactions
- # removed from the mempool by the block mining it.
- mempool_size = len(self.nodes[0].getrawmempool())
- c_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)[0]
- # Make sure the number of mined transactions matches the number of txs out of mempool
- mempool_size_delta = mempool_size - len(self.nodes[0].getrawmempool())
- assert_equal(len(self.nodes[0].getblock(c_block)["tx"])-1, mempool_size_delta)
- seq_num += mempool_size_delta
- payment_txid_2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
- self.sync_all()
- assert_equal((c_block, "C", None), seq.receive_sequence())
- assert_equal((payment_txid_2, "A", seq_num), seq.receive_sequence())
- seq_num += 1
-
- # Spot check getrawmempool results that they only show up when asked for
- assert type(self.nodes[0].getrawmempool()) is list
- assert type(self.nodes[0].getrawmempool(mempool_sequence=False)) is list
- assert "mempool_sequence" not in self.nodes[0].getrawmempool(verbose=True)
- assert_raises_rpc_error(-8, "Verbose results cannot contain mempool sequence values.", self.nodes[0].getrawmempool, True, True)
- assert_equal(self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"], seq_num)
-
- self.log.info("Testing reorg notifications")
- # Manually invalidate the last block to test mempool re-entry
- # N.B. This part could be made more lenient in exact ordering
- # since it greatly depends on inner-workings of blocks/mempool
- # during "deep" re-orgs. Probably should "re-construct"
- # blockchain/mempool state from notifications instead.
- block_count = self.nodes[0].getblockcount()
- best_hash = self.nodes[0].getbestblockhash()
- self.nodes[0].invalidateblock(best_hash)
- sleep(2) # Bit of room to make sure transaction things happened
-
- # Make sure getrawmempool mempool_sequence results aren't "queued" but immediately reflective
- # of the time they were gathered.
- assert self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"] > seq_num
-
- assert_equal((best_hash, "D", None), seq.receive_sequence())
- assert_equal((rbf_info["txid"], "A", seq_num), seq.receive_sequence())
- seq_num += 1
-
- # Other things may happen but aren't wallet-deterministic so we don't test for them currently
- self.nodes[0].reconsiderblock(best_hash)
- self.generatetoaddress(self.nodes[1], 1, ADDRESS_BCRT1_UNSPENDABLE)
-
- self.log.info("Evict mempool transaction by block conflict")
- orig_txid = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True)
-
- # More to be simply mined
- more_tx = []
- for _ in range(5):
- more_tx.append(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.1))
-
- raw_tx = self.nodes[0].getrawtransaction(orig_txid)
- bump_info = self.nodes[0].bumpfee(orig_txid)
- # Mine the pre-bump tx
- txs_to_add = [raw_tx] + [self.nodes[0].getrawtransaction(txid) for txid in more_tx]
- block = create_block(int(self.nodes[0].getbestblockhash(), 16), create_coinbase(self.nodes[0].getblockcount()+1), txlist=txs_to_add)
- add_witness_commitment(block)
- block.solve()
- assert_equal(self.nodes[0].submitblock(block.serialize().hex()), None)
- tip = self.nodes[0].getbestblockhash()
- assert_equal(int(tip, 16), block.sha256)
- orig_txid_2 = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True)
-
- # Flush old notifications until evicted tx original entry
+ self.log.info("Wait for tx from second node")
+ payment_tx = self.wallet.send_self_transfer(from_node=self.nodes[1])
+ payment_txid = payment_tx['txid']
+ self.sync_all()
+ self.log.info("Testing sequence notifications with mempool sequence values")
+
+ # Should receive the broadcasted txid.
+ assert_equal((payment_txid, "A", seq_num), seq.receive_sequence())
+ seq_num += 1
+
+ self.log.info("Testing RBF notification")
+ # Replace it to test eviction/addition notification
+ payment_tx['tx'].vout[0].nValue -= 1000
+ rbf_txid = self.nodes[1].sendrawtransaction(payment_tx['tx'].serialize().hex())
+ self.sync_all()
+ assert_equal((payment_txid, "R", seq_num), seq.receive_sequence())
+ seq_num += 1
+ assert_equal((rbf_txid, "A", seq_num), seq.receive_sequence())
+ seq_num += 1
+
+ # Doesn't get published when mined, make a block and tx to "flush" the possibility
+ # though the mempool sequence number does go up by the number of transactions
+ # removed from the mempool by the block mining it.
+ mempool_size = len(self.nodes[0].getrawmempool())
+ c_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)[0]
+ # Make sure the number of mined transactions matches the number of txs out of mempool
+ mempool_size_delta = mempool_size - len(self.nodes[0].getrawmempool())
+ assert_equal(len(self.nodes[0].getblock(c_block)["tx"])-1, mempool_size_delta)
+ seq_num += mempool_size_delta
+ payment_txid_2 = self.wallet.send_self_transfer(from_node=self.nodes[1])['txid']
+ self.sync_all()
+ assert_equal((c_block, "C", None), seq.receive_sequence())
+ assert_equal((payment_txid_2, "A", seq_num), seq.receive_sequence())
+ seq_num += 1
+
+ # Spot check getrawmempool results that they only show up when asked for
+ assert type(self.nodes[0].getrawmempool()) is list
+ assert type(self.nodes[0].getrawmempool(mempool_sequence=False)) is list
+ assert "mempool_sequence" not in self.nodes[0].getrawmempool(verbose=True)
+ assert_raises_rpc_error(-8, "Verbose results cannot contain mempool sequence values.", self.nodes[0].getrawmempool, True, True)
+ assert_equal(self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"], seq_num)
+
+ self.log.info("Testing reorg notifications")
+ # Manually invalidate the last block to test mempool re-entry
+ # N.B. This part could be made more lenient in exact ordering
+ # since it greatly depends on inner-workings of blocks/mempool
+ # during "deep" re-orgs. Probably should "re-construct"
+ # blockchain/mempool state from notifications instead.
+ block_count = self.nodes[0].getblockcount()
+ best_hash = self.nodes[0].getbestblockhash()
+ self.nodes[0].invalidateblock(best_hash)
+ sleep(2) # Bit of room to make sure transaction things happened
+
+ # Make sure getrawmempool mempool_sequence results aren't "queued" but immediately reflective
+ # of the time they were gathered.
+ assert self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"] > seq_num
+
+ assert_equal((best_hash, "D", None), seq.receive_sequence())
+ assert_equal((rbf_txid, "A", seq_num), seq.receive_sequence())
+ seq_num += 1
+
+ # Other things may happen but aren't wallet-deterministic so we don't test for them currently
+ self.nodes[0].reconsiderblock(best_hash)
+ self.generatetoaddress(self.nodes[1], 1, ADDRESS_BCRT1_UNSPENDABLE)
+
+ self.log.info("Evict mempool transaction by block conflict")
+ orig_tx = self.wallet.send_self_transfer(from_node=self.nodes[0])
+ orig_txid = orig_tx['txid']
+
+ # More to be simply mined
+ more_tx = []
+ for _ in range(5):
+ more_tx.append(self.wallet.send_self_transfer(from_node=self.nodes[0]))
+
+ orig_tx['tx'].vout[0].nValue -= 1000
+ bump_txid = self.nodes[0].sendrawtransaction(orig_tx['tx'].serialize().hex())
+ # Mine the pre-bump tx
+ txs_to_add = [orig_tx['hex']] + [tx['hex'] for tx in more_tx]
+ block = create_block(int(self.nodes[0].getbestblockhash(), 16), create_coinbase(self.nodes[0].getblockcount()+1), txlist=txs_to_add)
+ add_witness_commitment(block)
+ block.solve()
+ assert_equal(self.nodes[0].submitblock(block.serialize().hex()), None)
+ tip = self.nodes[0].getbestblockhash()
+ assert_equal(int(tip, 16), block.sha256)
+ orig_txid_2 = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']
+
+ # Flush old notifications until evicted tx original entry
+ (hash_str, label, mempool_seq) = seq.receive_sequence()
+ while hash_str != orig_txid:
(hash_str, label, mempool_seq) = seq.receive_sequence()
- while hash_str != orig_txid:
- (hash_str, label, mempool_seq) = seq.receive_sequence()
- mempool_seq += 1
+ mempool_seq += 1
- # Added original tx
- assert_equal(label, "A")
- # More transactions to be simply mined
- for i in range(len(more_tx)):
- assert_equal((more_tx[i], "A", mempool_seq), seq.receive_sequence())
- mempool_seq += 1
- # Bumped by rbf
- assert_equal((orig_txid, "R", mempool_seq), seq.receive_sequence())
- mempool_seq += 1
- assert_equal((bump_info["txid"], "A", mempool_seq), seq.receive_sequence())
+ # Added original tx
+ assert_equal(label, "A")
+ # More transactions to be simply mined
+ for i in range(len(more_tx)):
+ assert_equal((more_tx[i]['txid'], "A", mempool_seq), seq.receive_sequence())
mempool_seq += 1
- # Conflict announced first, then block
- assert_equal((bump_info["txid"], "R", mempool_seq), seq.receive_sequence())
- mempool_seq += 1
- assert_equal((tip, "C", None), seq.receive_sequence())
- mempool_seq += len(more_tx)
- # Last tx
- assert_equal((orig_txid_2, "A", mempool_seq), seq.receive_sequence())
- mempool_seq += 1
- self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
- self.sync_all() # want to make sure we didn't break "consensus" for other tests
+ # Bumped by rbf
+ assert_equal((orig_txid, "R", mempool_seq), seq.receive_sequence())
+ mempool_seq += 1
+ assert_equal((bump_txid, "A", mempool_seq), seq.receive_sequence())
+ mempool_seq += 1
+ # Conflict announced first, then block
+ assert_equal((bump_txid, "R", mempool_seq), seq.receive_sequence())
+ mempool_seq += 1
+ assert_equal((tip, "C", None), seq.receive_sequence())
+ mempool_seq += len(more_tx)
+ # Last tx
+ assert_equal((orig_txid_2, "A", mempool_seq), seq.receive_sequence())
+ mempool_seq += 1
+ self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
+ self.sync_all() # want to make sure we didn't break "consensus" for other tests
def test_mempool_sync(self):
"""
Use sequence notification plus getrawmempool sequence results to "sync mempool"
"""
- if not self.is_wallet_compiled():
- self.log.info("Skipping mempool sync test")
- return
self.log.info("Testing 'mempool sync' usage of sequence notifier")
[seq] = self.setup_zmq_test([("sequence", "tcp://127.0.0.1:28333")])
@@ -455,10 +452,10 @@ class ZMQTest (BitcoinTestFramework):
# Some transactions have been happening but we aren't consuming zmq notifications yet
# or we lost a ZMQ message somehow and want to start over
- txids = []
+ txs = []
num_txs = 5
for _ in range(num_txs):
- txids.append(self.nodes[1].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True))
+ txs.append(self.wallet.send_self_transfer(from_node=self.nodes[1]))
self.sync_all()
# 1) Consume backlog until we get a mempool sequence number
@@ -484,11 +481,12 @@ class ZMQTest (BitcoinTestFramework):
# Things continue to happen in the "interim" while waiting for snapshot results
# We have node 0 do all these to avoid p2p races with RBF announcements
for _ in range(num_txs):
- txids.append(self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=0.1, replaceable=True))
- self.nodes[0].bumpfee(txids[-1])
+ txs.append(self.wallet.send_self_transfer(from_node=self.nodes[0]))
+ txs[-1]['tx'].vout[0].nValue -= 1000
+ self.nodes[0].sendrawtransaction(txs[-1]['tx'].serialize().hex())
self.sync_all()
self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
- final_txid = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=0.1, replaceable=True)
+ final_txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']
# 3) Consume ZMQ backlog until we get to "now" for the mempool snapshot
while True:
diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py
index a6fb1dcf35..423a5bf2ee 100755
--- a/test/functional/mempool_package_onemore.py
+++ b/test/functional/mempool_package_onemore.py
@@ -7,74 +7,68 @@
size.
"""
-from decimal import Decimal
-
-from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- chain_transaction,
)
+from test_framework.wallet import MiniWallet
+
MAX_ANCESTORS = 25
MAX_DESCENDANTS = 25
+
class MempoolPackagesTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [["-maxorphantx=1000"]]
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ def chain_tx(self, utxos_to_spend, *, num_outputs=1):
+ return self.wallet.send_self_transfer_multi(
+ from_node=self.nodes[0],
+ utxos_to_spend=utxos_to_spend,
+ num_outputs=num_outputs)['new_utxos']
def run_test(self):
- # Mine some blocks and have them mature.
- self.generate(self.nodes[0], COINBASE_MATURITY + 1)
- utxo = self.nodes[0].listunspent(10)
- txid = utxo[0]['txid']
- vout = utxo[0]['vout']
- value = utxo[0]['amount']
+ self.wallet = MiniWallet(self.nodes[0])
+ self.wallet.rescan_utxos()
- fee = Decimal("0.0002")
# MAX_ANCESTORS transactions off a confirmed tx should be fine
chain = []
+ utxo = self.wallet.get_utxo()
for _ in range(4):
- (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2)
- vout = 0
- value = sent_value
- chain.append([txid, value])
+ utxo, utxo2 = self.chain_tx([utxo], num_outputs=2)
+ chain.append(utxo2)
for _ in range(MAX_ANCESTORS - 4):
- (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1)
- value = sent_value
- chain.append([txid, value])
- (second_chain, second_chain_value) = chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1)
+ utxo, = self.chain_tx([utxo])
+ chain.append(utxo)
+ second_chain, = self.chain_tx([self.wallet.get_utxo()])
# Check mempool has MAX_ANCESTORS + 1 transactions in it
assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 1)
# Adding one more transaction on to the chain should fail.
- assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", chain_transaction, self.nodes[0], [txid], [0], value, fee, 1)
+ assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_tx, [utxo])
# ...even if it chains on from some point in the middle of the chain.
- assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1)
- assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1)
+ assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[2]])
+ assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[1]])
# ...even if it chains on to two parent transactions with one in the chain.
- assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1)
+ assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0], second_chain])
# ...especially if its > 40k weight
- assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350)
+ assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0]], num_outputs=350)
# But not if it chains directly off the first transaction
- (replacable_txid, replacable_orig_value) = chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1)
+ replacable_tx = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], utxos_to_spend=[chain[0]])['tx']
# and the second chain should work just fine
- chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1)
+ self.chain_tx([second_chain])
# Make sure we can RBF the chain which used our carve-out rule
- second_tx_outputs = {self.nodes[0].getrawtransaction(replacable_txid, True)["vout"][0]['scriptPubKey']['address']: replacable_orig_value - (Decimal(1) / Decimal(100))}
- second_tx = self.nodes[0].createrawtransaction([{'txid': chain[0][0], 'vout': 1}], second_tx_outputs)
- signed_second_tx = self.nodes[0].signrawtransactionwithwallet(second_tx)
- self.nodes[0].sendrawtransaction(signed_second_tx['hex'])
+ replacable_tx.vout[0].nValue -= 1000000
+ self.nodes[0].sendrawtransaction(replacable_tx.serialize().hex())
# Finally, check that we added two transactions
assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 3)
+
if __name__ == '__main__':
MempoolPackagesTest().main()
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index 1a3d14100f..1695acaaa8 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -18,15 +18,18 @@ from test_framework.util import (
assert_equal,
)
from test_framework.wallet_util import bytes_to_wif
+from test_framework.wallet import (
+ MiniWallet,
+ getnewdestination,
+)
class RpcCreateMultiSigTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.supports_cli = False
-
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ if self.is_bdb_compiled():
+ self.requires_wallet = True
def get_keys(self):
self.pub = []
@@ -37,15 +40,20 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
k.generate()
self.pub.append(k.get_pubkey().get_bytes().hex())
self.priv.append(bytes_to_wif(k.get_bytes(), k.is_compressed))
- self.final = node2.getnewaddress()
+ if self.is_bdb_compiled():
+ self.final = node2.getnewaddress()
+ else:
+ self.final = getnewdestination()[2]
def run_test(self):
node0, node1, node2 = self.nodes
+ self.wallet = MiniWallet(test_node=node0)
- self.check_addmultisigaddress_errors()
+ if self.is_bdb_compiled():
+ self.check_addmultisigaddress_errors()
self.log.info('Generating blocks ...')
- self.generate(node0, 149)
+ self.generate(self.wallet, 149)
self.moved = 0
for self.nkeys in [3, 5]:
@@ -53,14 +61,14 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
for self.output_type in ["bech32", "p2sh-segwit", "legacy"]:
self.get_keys()
self.do_multisig()
-
- self.checkbalances()
+ if self.is_bdb_compiled():
+ self.checkbalances()
# Test mixed compressed and uncompressed pubkeys
self.log.info('Mixed compressed and uncompressed multisigs are not allowed')
- pk0 = node0.getaddressinfo(node0.getnewaddress())['pubkey']
- pk1 = node1.getaddressinfo(node1.getnewaddress())['pubkey']
- pk2 = node2.getaddressinfo(node2.getnewaddress())['pubkey']
+ pk0 = getnewdestination()[0].hex()
+ pk1 = getnewdestination()[0].hex()
+ pk2 = getnewdestination()[0].hex()
# decompress pk2
pk_obj = ECPubKey()
@@ -68,26 +76,30 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
pk_obj.compressed = False
pk2 = pk_obj.get_bytes().hex()
- node0.createwallet(wallet_name='wmulti0', disable_private_keys=True)
- wmulti0 = node0.get_wallet_rpc('wmulti0')
+ if self.is_bdb_compiled():
+ node0.createwallet(wallet_name='wmulti0', disable_private_keys=True)
+ wmulti0 = node0.get_wallet_rpc('wmulti0')
# Check all permutations of keys because order matters apparently
for keys in itertools.permutations([pk0, pk1, pk2]):
# Results should be the same as this legacy one
legacy_addr = node0.createmultisig(2, keys, 'legacy')['address']
- result = wmulti0.addmultisigaddress(2, keys, '', 'legacy')
- assert_equal(legacy_addr, result['address'])
- assert 'warnings' not in result
+
+ if self.is_bdb_compiled():
+ result = wmulti0.addmultisigaddress(2, keys, '', 'legacy')
+ assert_equal(legacy_addr, result['address'])
+ assert 'warnings' not in result
# Generate addresses with the segwit types. These should all make legacy addresses
for addr_type in ['bech32', 'p2sh-segwit']:
- result = wmulti0.createmultisig(2, keys, addr_type)
+ result = self.nodes[0].createmultisig(2, keys, addr_type)
assert_equal(legacy_addr, result['address'])
assert_equal(result['warnings'], ["Unable to make chosen address type, please ensure no uncompressed public keys are present."])
- result = wmulti0.addmultisigaddress(2, keys, '', addr_type)
- assert_equal(legacy_addr, result['address'])
- assert_equal(result['warnings'], ["Unable to make chosen address type, please ensure no uncompressed public keys are present."])
+ if self.is_bdb_compiled():
+ result = wmulti0.addmultisigaddress(2, keys, '', addr_type)
+ assert_equal(legacy_addr, result['address'])
+ assert_equal(result['warnings'], ["Unable to make chosen address type, please ensure no uncompressed public keys are present."])
self.log.info('Testing sortedmulti descriptors with BIP 67 test vectors')
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rpc_bip67.json'), encoding='utf-8') as f:
@@ -126,26 +138,29 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
bal0 = node0.getbalance()
bal1 = node1.getbalance()
bal2 = node2.getbalance()
+ balw = self.wallet.get_balance()
height = node0.getblockchaininfo()["blocks"]
assert 150 < height < 350
total = 149 * 50 + (height - 149 - 100) * 25
assert bal1 == 0
assert bal2 == self.moved
- assert bal0 + bal1 + bal2 == total
+ assert_equal(bal0 + bal1 + bal2 + balw, total)
def do_multisig(self):
node0, node1, node2 = self.nodes
- if 'wmulti' not in node1.listwallets():
- try:
- node1.loadwallet('wmulti')
- except JSONRPCException as e:
- path = os.path.join(self.options.tmpdir, "node1", "regtest", "wallets", "wmulti")
- if e.error['code'] == -18 and "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path) in e.error['message']:
- node1.createwallet(wallet_name='wmulti', disable_private_keys=True)
- else:
- raise
- wmulti = node1.get_wallet_rpc('wmulti')
+
+ if self.is_bdb_compiled():
+ if 'wmulti' not in node1.listwallets():
+ try:
+ node1.loadwallet('wmulti')
+ except JSONRPCException as e:
+ path = os.path.join(self.options.tmpdir, "node1", "regtest", "wallets", "wmulti")
+ if e.error['code'] == -18 and "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path) in e.error['message']:
+ node1.createwallet(wallet_name='wmulti', disable_private_keys=True)
+ else:
+ raise
+ wmulti = node1.get_wallet_rpc('wmulti')
# Construct the expected descriptor
desc = 'multi({},{})'.format(self.nsigs, ','.join(self.pub))
@@ -164,17 +179,19 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
if self.output_type == 'bech32':
assert madd[0:4] == "bcrt" # actually a bech32 address
- # compare against addmultisigaddress
- msigw = wmulti.addmultisigaddress(self.nsigs, self.pub, None, self.output_type)
- maddw = msigw["address"]
- mredeemw = msigw["redeemScript"]
- assert_equal(desc, drop_origins(msigw['descriptor']))
- # addmultisigiaddress and createmultisig work the same
- assert maddw == madd
- assert mredeemw == mredeem
-
- txid = node0.sendtoaddress(madd, 40)
-
+ if self.is_bdb_compiled():
+ # compare against addmultisigaddress
+ msigw = wmulti.addmultisigaddress(self.nsigs, self.pub, None, self.output_type)
+ maddw = msigw["address"]
+ mredeemw = msigw["redeemScript"]
+ assert_equal(desc, drop_origins(msigw['descriptor']))
+ # addmultisigiaddress and createmultisig work the same
+ assert maddw == madd
+ assert mredeemw == mredeem
+ wmulti.unloadwallet()
+
+ spk = bytes.fromhex(node0.validateaddress(madd)["scriptPubKey"])
+ txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=spk, amount=1300)
tx = node0.getrawtransaction(txid, True)
vout = [v["n"] for v in tx["vout"] if madd == v["scriptPubKey"]["address"]]
assert len(vout) == 1
@@ -225,8 +242,6 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
txinfo = node0.getrawtransaction(tx, True, blk)
self.log.info("n/m=%d/%d %s size=%d vsize=%d weight=%d" % (self.nsigs, self.nkeys, self.output_type, txinfo["size"], txinfo["vsize"], txinfo["weight"]))
- wmulti.unloadwallet()
-
if __name__ == '__main__':
RpcCreateMultiSigTest().main()
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index a650df70dd..37b8a2294d 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -96,6 +96,9 @@ class MiniWallet:
self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true()
self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey'])
+ def get_balance(self):
+ return sum(u['value'] for u in self._utxos)
+
def rescan_utxos(self):
"""Drop all utxos and rescan the utxo set"""
self._utxos = []
@@ -188,6 +191,46 @@ class MiniWallet:
txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex())
return txid, 1
+ def send_self_transfer_multi(self, **kwargs):
+ """
+ Create and send a transaction that spends the given UTXOs and creates a
+ certain number of outputs with equal amounts.
+
+ Returns a dictionary with
+ - txid
+ - serialized transaction in hex format
+ - transaction as CTransaction instance
+ - list of newly created UTXOs, ordered by vout index
+ """
+ tx = self.create_self_transfer_multi(**kwargs)
+ txid = self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx.serialize().hex())
+ return {'new_utxos': [self.get_utxo(txid=txid, vout=vout) for vout in range(len(tx.vout))],
+ 'txid': txid, 'hex': tx.serialize().hex(), 'tx': tx}
+
+ def create_self_transfer_multi(self, *, from_node, utxos_to_spend, num_outputs=1, fee_per_output=1000):
+ """
+ Create and return a transaction that spends the given UTXOs and creates a
+ certain number of outputs with equal amounts.
+ """
+ # create simple tx template (1 input, 1 output)
+ tx = self.create_self_transfer(fee_rate=0, from_node=from_node, utxo_to_spend=utxos_to_spend[0], mempool_valid=False)['tx']
+
+ # duplicate inputs, witnesses and outputs
+ tx.vin = [deepcopy(tx.vin[0]) for _ in range(len(utxos_to_spend))]
+ tx.wit.vtxinwit = [deepcopy(tx.wit.vtxinwit[0]) for _ in range(len(utxos_to_spend))]
+ tx.vout = [deepcopy(tx.vout[0]) for _ in range(num_outputs)]
+
+ # adapt input prevouts
+ for i, utxo in enumerate(utxos_to_spend):
+ tx.vin[i] = CTxIn(COutPoint(int(utxo['txid'], 16), utxo['vout']))
+
+ # adapt output amounts (use fixed fee per output)
+ inputs_value_total = sum([int(COIN * utxo['value']) for utxo in utxos_to_spend])
+ outputs_value_total = inputs_value_total - fee_per_output * num_outputs
+ for i in range(num_outputs):
+ tx.vout[i].nValue = outputs_value_total // num_outputs
+ return tx
+
def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node=None, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0):
"""Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
from_node = from_node or self._test_node
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index b0f24e3b97..306c8e7ff0 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -188,8 +188,7 @@ BASE_SCRIPTS = [
'rpc_decodescript.py',
'rpc_blockchain.py',
'rpc_deprecated.py',
- 'wallet_disable.py --legacy-wallet',
- 'wallet_disable.py --descriptors',
+ 'wallet_disable.py',
'p2p_addr_relay.py',
'p2p_getaddr_caching.py',
'p2p_getdata.py',
@@ -225,8 +224,7 @@ BASE_SCRIPTS = [
'feature_rbf.py --descriptors',
'mempool_packages.py',
'mempool_package_onemore.py',
- 'rpc_createmultisig.py --legacy-wallet',
- 'rpc_createmultisig.py --descriptors',
+ 'rpc_createmultisig.py',
'rpc_packages.py',
'mempool_package_limits.py',
'feature_versionbits_warning.py',
@@ -309,8 +307,7 @@ BASE_SCRIPTS = [
'feature_txindex_compatibility.py',
'feature_logging.py',
'feature_anchors.py',
- 'feature_coinstatsindex.py --legacy-wallet',
- 'feature_coinstatsindex.py --descriptors',
+ 'feature_coinstatsindex.py',
'wallet_orphanedreward.py',
'wallet_timelock.py',
'p2p_node_network_limited.py',
@@ -589,11 +586,12 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
# Clean up dangling processes if any. This may only happen with --failfast option.
# Killing the process group will also terminate the current process but that is
# not an issue
- if len(job_queue.jobs):
+ if not os.getenv("CI_FAILFAST_TEST_LEAVE_DANGLING") and len(job_queue.jobs):
os.killpg(os.getpgid(0), signal.SIGKILL)
sys.exit(not all_passed)
+
def print_results(test_results, max_len_name, runtime):
results = "\n" + BOLD[1] + "%s | %s | %s\n\n" % ("TEST".ljust(max_len_name), "STATUS ", "DURATION") + BOLD[0]