aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml2
-rw-r--r--.travis.yml4
-rw-r--r--ci/README.md2
-rw-r--r--ci/test/00_setup_env_arm.sh11
-rwxr-xr-xci/test/04_install.sh5
-rw-r--r--depends/README.md26
-rw-r--r--doc/release-notes.md1
-rw-r--r--src/Makefile.test.include23
-rw-r--r--src/bench/bench.cpp2
-rw-r--r--src/bench/mempool_stress.cpp8
-rw-r--r--src/bloom.h3
-rw-r--r--src/net.cpp3
-rw-r--r--src/net.h23
-rw-r--r--src/net_processing.cpp17
-rw-r--r--src/psbt.h1
-rw-r--r--src/rpc/blockchain.cpp18
-rw-r--r--src/rpc/mining.cpp8
-rw-r--r--src/rpc/util.cpp8
-rw-r--r--src/test/bech32_tests.cpp14
-rw-r--r--src/test/blockencodings_tests.cpp8
-rw-r--r--src/test/blockfilter_index_tests.cpp20
-rw-r--r--src/test/fuzz/bech32.cpp43
-rw-r--r--src/test/lib/blockfilter.cpp26
-rw-r--r--src/test/lib/blockfilter.h13
-rw-r--r--src/test/net_tests.cpp14
-rw-r--r--src/test/setup_common.cpp9
-rw-r--r--src/test/setup_common.h8
-rw-r--r--src/test/util/str.cpp21
-rw-r--r--src/test/util/str.h12
-rw-r--r--src/test/validation_block_tests.cpp6
-rw-r--r--src/wallet/rpcdump.cpp6
-rw-r--r--src/wallet/rpcwallet.cpp42
-rw-r--r--src/wallet/scriptpubkeyman.cpp162
-rw-r--r--src/wallet/scriptpubkeyman.h256
-rw-r--r--src/wallet/wallet.cpp216
-rw-r--r--src/wallet/wallet.h21
-rw-r--r--test/functional/README.md10
-rw-r--r--test/functional/test-shell.md186
-rwxr-xr-xtest/functional/test_framework/mininode.py3
-rwxr-xr-xtest/functional/test_framework/test_framework.py94
-rw-r--r--test/functional/test_framework/test_shell.py75
-rwxr-xr-xtest/functional/wallet_balance.py48
-rwxr-xr-xtest/functional/wallet_bumpfee.py12
-rwxr-xr-xtest/functional/wallet_listsinceblock.py49
-rwxr-xr-xtest/lint/lint-assertions.sh11
45 files changed, 1134 insertions, 416 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index 9464ec1685..517cd93585 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -45,8 +45,6 @@ task:
folder: "/tmp/ccache_dir"
depends_built_cache:
folder: "/tmp/cirrus-ci-build/depends/built"
- depends_sdk_cache:
- folder: "/tmp/cirrus-ci-build/depends/sdk-sources"
install_script:
- apt-get update
- apt-get -y install git bash ccache
diff --git a/.travis.yml b/.travis.yml
index 3ddafda6d2..51bb7d6e0b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -94,9 +94,11 @@ jobs:
- set -o errexit; source ./ci/extended_lint/06_script.sh
- stage: test
- name: 'ARM [GOAL: install] [unit tests, no functional tests]'
+ name: 'ARM [GOAL: install] [unit tests, functional tests]'
+ arch: arm64
env: >-
FILE_ENV="./ci/test/00_setup_env_arm.sh"
+ QEMU_USER_CMD="" # Can run the tests natively without qemu
- stage: test
name: 'Win64 [GOAL: deploy] [unit tests, no gui, no functional tests]'
diff --git a/ci/README.md b/ci/README.md
index 16c481158f..fb1cd7460b 100644
--- a/ci/README.md
+++ b/ci/README.md
@@ -12,7 +12,7 @@ To allow for a wide range of tested environments, but also ensure reproducibilit
requires `docker` to be installed. To install all requirements on Ubuntu, run
```
-sudo apt install docker.io ccache bash git
+sudo apt install docker.io bash git
```
To run the default test stage,
diff --git a/ci/test/00_setup_env_arm.sh b/ci/test/00_setup_env_arm.sh
index 9335f0b337..6e2542584c 100644
--- a/ci/test/00_setup_env_arm.sh
+++ b/ci/test/00_setup_env_arm.sh
@@ -7,11 +7,16 @@
export LC_ALL=C.UTF-8
export HOST=arm-linux-gnueabihf
-export QEMU_USER_CMD="qemu-arm -L /usr/arm-linux-gnueabihf/"
-export PACKAGES="python3 g++-arm-linux-gnueabihf busybox qemu-user"
+# The host arch is unknown, so we run the tests through qemu.
+# If the host is arm and wants to run the tests natively, it can set QEMU_USER_CMD to the empty string.
+export QEMU_USER_CMD="${QEMU_USER_CMD:"qemu-arm -L /usr/arm-linux-gnueabihf/"}"
+# We don't know whether the host can run the cross compiled binaries. To run them, either qemu-user or libc6:armhf for
+# the target is required, so install both.
+export DPKG_ADD_ARCH="armhf"
+export PACKAGES="python3 g++-arm-linux-gnueabihf busybox qemu-user libc6:armhf libstdc++6:armhf libfontconfig1:armhf libxcb1:armhf"
export USE_BUSY_BOX=true
export RUN_UNIT_TESTS=true
-export RUN_FUNCTIONAL_TESTS=false
+export RUN_FUNCTIONAL_TESTS=true
export GOAL="install"
# -Wno-psabi is to disable ABI warnings: "note: parameter passing for argument of type ... changed in GCC 7.1"
# This could be removed once the ABI change warning does not show up by default
diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh
index d0831a4c13..271ae82e5c 100755
--- a/ci/test/04_install.sh
+++ b/ci/test/04_install.sh
@@ -33,7 +33,7 @@ if [ "$TRAVIS_OS_NAME" == "osx" ]; then
fi
mkdir -p "${BASE_SCRATCH_DIR}"
-ccache echo "Creating ccache dir if it didn't already exist"
+mkdir -p "${CCACHE_DIR}"
if [ ! -d ${DIR_QA_ASSETS} ]; then
git clone https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS}
@@ -76,6 +76,9 @@ else
DOCKER_EXEC echo "Number of CPUs \(nproc\):" \$\(nproc\)
fi
+if [ -n "$DPKG_ADD_ARCH" ]; then
+ DOCKER_EXEC dpkg --add-architecture "$DPKG_ADD_ARCH"
+fi
if [ "$TRAVIS_OS_NAME" != "osx" ]; then
${CI_RETRY_EXE} DOCKER_EXEC apt-get update
diff --git a/depends/README.md b/depends/README.md
index aaa062774f..e5e2a8a653 100644
--- a/depends/README.md
+++ b/depends/README.md
@@ -30,17 +30,12 @@ Common `host-platform-triplets` for cross compilation are:
- `aarch64-linux-gnu` for Linux ARM 64 bit
- `riscv32-linux-gnu` for Linux RISC-V 32 bit
- `riscv64-linux-gnu` for Linux RISC-V 64 bit
+- `armv7a-linux-android` for Android ARM 32 bit
- `aarch64-linux-android` for Android ARM 64 bit
+- `i686-linux-android` for Android x86 32 bit
+- `x86_64-linux-android` for Android x86 64 bit
-The paths are automatically configured and no other options are needed unless targeting Android.
-Before proceeding with an Android build one needs to get the [Android SDK](https://developer.android.com/studio) and use the "SDK Manager" tool to download the NDK and one or more "Platform packages" (these are Android versions and have a corresponding API level).
-In order to build `ANDROID_API_LEVEL` (API level corresponding to the Android version targeted, e.g. Android 9.0 Pie is 28 and its "Platform package" needs to be available) and `ANDROID_TOOLCHAIN_BIN` (path to toolchain binaries depending on the platform the build is being performed on) need to be set.
-If the build includes Qt, environment variables `ANDROID_SDK` and `ANDROID_NDK` need to be set as well but can otherwise be omitted.
-This is an example command for a default build with no disabled dependencies:
-
- ANDROID_SDK=/home/user/Android/Sdk ANDROID_NDK=/home/user/Android/Sdk/ndk-bundle make HOST=aarch64-linux-android ANDROID_API_LEVEL=28 ANDROID_TOOLCHAIN_BIN=/home/user/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin
-
-
+The paths are automatically configured and no other options are needed unless targeting [Android](#Android).
### Install the required dependencies: Ubuntu & Debian
@@ -99,6 +94,19 @@ options will be passed to bitcoin's configure. In this case, `--disable-wallet`.
download-win: run 'make download-win' to fetch all sources needed for win builds
download-linux: run 'make download-linux' to fetch all sources needed for linux builds
+
+### Android
+
+Before proceeding with an Android build one needs to get the [Android SDK](https://developer.android.com/studio) and use the "SDK Manager" tool to download the NDK and one or more "Platform packages" (these are Android versions and have a corresponding API level).
+In order to build `ANDROID_API_LEVEL` (API level corresponding to the Android version targeted, e.g. Android 9.0 Pie is 28 and its "Platform package" needs to be available) and `ANDROID_TOOLCHAIN_BIN` (path to toolchain binaries depending on the platform the build is being performed on) need to be set.
+
+API levels from 24 to 29 have been tested to work.
+
+If the build includes Qt, environment variables `ANDROID_SDK` and `ANDROID_NDK` need to be set as well but can otherwise be omitted.
+This is an example command for a default build with no disabled dependencies:
+
+ ANDROID_SDK=/home/user/Android/Sdk ANDROID_NDK=/home/user/Android/Sdk/ndk-bundle make HOST=aarch64-linux-android ANDROID_API_LEVEL=28 ANDROID_TOOLCHAIN_BIN=/home/user/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin
+
### Other documentation
- [description.md](description.md): General description of the depends system
diff --git a/doc/release-notes.md b/doc/release-notes.md
index ea82962e75..a47c8802b0 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -85,6 +85,7 @@ Wallet
------
- The wallet now by default uses bech32 addresses when using RPC, and creates native segwit change outputs.
+- The way that output trust was computed has been fixed in #16766, which impacts confirmed/unconfirmed balance status and coin selection.
Low-level changes
=================
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index c3f0120005..48afa815d6 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -7,6 +7,7 @@ FUZZ_TARGETS = \
test/fuzz/address_deserialize \
test/fuzz/addrman_deserialize \
test/fuzz/banentry_deserialize \
+ test/fuzz/bech32 \
test/fuzz/block_deserialize \
test/fuzz/blockheader_deserialize \
test/fuzz/blocklocator_deserialize \
@@ -56,18 +57,24 @@ RAW_TEST_FILES =
GENERATED_TEST_FILES = $(JSON_TEST_FILES:.json=.json.h) $(RAW_TEST_FILES:.raw=.raw.h)
BITCOIN_TEST_SUITE = \
- test/lib/transaction_utils.h \
+ test/lib/blockfilter.cpp \
+ test/lib/blockfilter.h \
test/lib/transaction_utils.cpp \
+ test/lib/transaction_utils.h \
test/main.cpp \
test/setup_common.h \
- test/setup_common.cpp
+ test/setup_common.cpp \
+ test/util/str.h \
+ test/util/str.cpp
FUZZ_SUITE = \
- test/setup_common.h \
- test/setup_common.cpp \
test/fuzz/fuzz.cpp \
test/fuzz/fuzz.h \
- test/fuzz/FuzzedDataProvider.h
+ test/fuzz/FuzzedDataProvider.h \
+ test/setup_common.cpp \
+ test/setup_common.h \
+ test/util/str.cpp \
+ test/util/str.h
FUZZ_SUITE_LD_COMMON = \
$(LIBBITCOIN_SERVER) \
@@ -240,6 +247,12 @@ test_fuzz_banentry_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_banentry_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_banentry_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_bech32_SOURCES = $(FUZZ_SUITE) test/fuzz/bech32.cpp
+test_fuzz_bech32_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_bech32_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_bech32_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_bech32_LDADD = $(FUZZ_SUITE_LD_COMMON)
+
test_fuzz_txundo_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp
test_fuzz_txundo_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DTXUNDO_DESERIALIZE=1
test_fuzz_txundo_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
diff --git a/src/bench/bench.cpp b/src/bench/bench.cpp
index f2b520e893..1b6b1736a9 100644
--- a/src/bench/bench.cpp
+++ b/src/bench/bench.cpp
@@ -112,7 +112,7 @@ void benchmark::BenchRunner::RunAll(Printer& printer, uint64_t num_evals, double
printer.header();
for (const auto& p : benchmarks()) {
- TestingSetup test{CBaseChainParams::REGTEST};
+ RegTestingSetup test{};
{
LOCK(cs_main);
assert(::ChainActive().Height() == 0);
diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp
index a42ffaae62..389e2c096f 100644
--- a/src/bench/mempool_stress.cpp
+++ b/src/bench/mempool_stress.cpp
@@ -23,12 +23,6 @@ struct Available {
size_t vin_left{0};
size_t tx_count;
Available(CTransactionRef& ref, size_t tx_count) : ref(ref), tx_count(tx_count){}
- Available& operator=(Available other) {
- ref = other.ref;
- vin_left = other.vin_left;
- tx_count = other.tx_count;
- return *this;
- }
};
static void ComplexMemPool(benchmark::State& state)
@@ -66,7 +60,7 @@ static void ComplexMemPool(benchmark::State& state)
tx.vin.back().scriptSig = CScript() << coin.tx_count;
tx.vin.back().scriptWitness.stack.push_back(CScriptNum(coin.tx_count).getvch());
}
- if (coin.vin_left == coin.ref->vin.size()) {
+ if (coin.vin_left == coin.ref->vin.size()) {
coin = available_coins.back();
available_coins.pop_back();
}
diff --git a/src/bloom.h b/src/bloom.h
index 7d3aa878b0..c3f64ba4bc 100644
--- a/src/bloom.h
+++ b/src/bloom.h
@@ -115,9 +115,6 @@ public:
class CRollingBloomFilter
{
public:
- // A random bloom filter calls GetRand() at creation time.
- // Don't create global CRollingBloomFilter objects, as they may be
- // constructed before the randomizer is properly initialized.
CRollingBloomFilter(const unsigned int nElements, const double nFPRate);
void insert(const std::vector<unsigned char>& vKey);
diff --git a/src/net.cpp b/src/net.cpp
index 674f2ecf24..84692d2a79 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -2666,11 +2666,10 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn
addrBind(addrBindIn),
fInbound(fInboundIn),
nKeyedNetGroup(nKeyedNetGroupIn),
- addrKnown(5000, 0.001),
// Don't relay addr messages to peers that we connect to as block-relay-only
// peers (to prevent adversaries from inferring these links from addr
// traffic).
- m_addr_relay_peer(!block_relay_only),
+ m_addr_known{block_relay_only ? nullptr : MakeUnique<CRollingBloomFilter>(5000, 0.001)},
id(idIn),
nLocalHostNonce(nLocalHostNonceIn),
nLocalServices(nLocalServicesIn),
diff --git a/src/net.h b/src/net.h
index 1bbcc89478..9cd3b769ec 100644
--- a/src/net.h
+++ b/src/net.h
@@ -776,13 +776,12 @@ public:
// flood relay
std::vector<CAddress> vAddrToSend;
- CRollingBloomFilter addrKnown;
+ const std::unique_ptr<CRollingBloomFilter> m_addr_known;
bool fGetAddr{false};
int64_t nNextAddrSend GUARDED_BY(cs_sendProcessing){0};
int64_t nNextLocalAddrSend GUARDED_BY(cs_sendProcessing){0};
- const bool m_addr_relay_peer;
- bool IsAddrRelayPeer() const { return m_addr_relay_peer; }
+ bool IsAddrRelayPeer() const { return m_addr_known != nullptr; }
// List of block ids we still have announce.
// There is no final sorting before sending, as they are always sent immediately
@@ -809,7 +808,7 @@ public:
bool fSendMempool GUARDED_BY(cs_tx_inventory){false};
// Last time a "MEMPOOL" request was serviced.
std::atomic<std::chrono::seconds> m_last_mempool_req{std::chrono::seconds{0}};
- int64_t nNextInvSend{0};
+ std::chrono::microseconds nNextInvSend{0};
CCriticalSection cs_feeFilter;
// Minimum fee rate with which to filter inv's to this node
@@ -931,7 +930,8 @@ public:
void AddAddressKnown(const CAddress& _addr)
{
- addrKnown.insert(_addr.GetKey());
+ assert(m_addr_known);
+ m_addr_known->insert(_addr.GetKey());
}
void PushAddress(const CAddress& _addr, FastRandomContext &insecure_rand)
@@ -939,7 +939,8 @@ public:
// Known checking here is only to save space from duplicates.
// SendMessages will filter it again for knowns that were added
// after addresses were pushed.
- if (_addr.IsValid() && !addrKnown.contains(_addr.GetKey())) {
+ assert(m_addr_known);
+ if (_addr.IsValid() && !m_addr_known->contains(_addr.GetKey())) {
if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) {
vAddrToSend[insecure_rand.randrange(vAddrToSend.size())] = _addr;
} else {
@@ -990,11 +991,13 @@ public:
void MaybeSetAddrName(const std::string& addrNameIn);
};
-
-
-
-
/** Return a timestamp in the future (in microseconds) for exponentially distributed events. */
int64_t PoissonNextSend(int64_t now, int average_interval_seconds);
+/** Wrapper to return mockable type */
+inline std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval)
+{
+ return std::chrono::microseconds{PoissonNextSend(now.count(), average_interval.count())};
+}
+
#endif // BITCOIN_NET_H
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index d03817834d..f42a26ca3e 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -1340,7 +1340,7 @@ static void RelayAddress(const CAddress& addr, bool fReachable, CConnman* connma
// Relay to a limited number of other nodes
// Use deterministic randomness to send to the same nodes for 24 hours
- // at a time so the addrKnowns of the chosen nodes prevent repeats
+ // at a time so the m_addr_knowns of the chosen nodes prevent repeats
uint64_t hashAddr = addr.GetHash();
const CSipHasher hasher = connman->GetDeterministicRandomizer(RANDOMIZER_ID_ADDRESS_RELAY).Write(hashAddr << 32).Write((GetTime() + hashAddr) / (24*60*60));
FastRandomContext insecure_rand;
@@ -3575,6 +3575,8 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
// Address refresh broadcast
int64_t nNow = GetTimeMicros();
+ auto current_time = GetTime<std::chrono::microseconds>();
+
if (pto->IsAddrRelayPeer() && !::ChainstateActive().IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) {
AdvertiseLocal(pto);
pto->nNextLocalAddrSend = PoissonNextSend(nNow, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
@@ -3587,11 +3589,12 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
pto->nNextAddrSend = PoissonNextSend(nNow, AVG_ADDRESS_BROADCAST_INTERVAL);
std::vector<CAddress> vAddr;
vAddr.reserve(pto->vAddrToSend.size());
+ assert(pto->m_addr_known);
for (const CAddress& addr : pto->vAddrToSend)
{
- if (!pto->addrKnown.contains(addr.GetKey()))
+ if (!pto->m_addr_known->contains(addr.GetKey()))
{
- pto->addrKnown.insert(addr.GetKey());
+ pto->m_addr_known->insert(addr.GetKey());
vAddr.push_back(addr);
// receiver rejects addr messages larger than 1000
if (vAddr.size() >= 1000)
@@ -3795,13 +3798,13 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
LOCK(pto->m_tx_relay->cs_tx_inventory);
// Check whether periodic sends should happen
bool fSendTrickle = pto->HasPermission(PF_NOBAN);
- if (pto->m_tx_relay->nNextInvSend < nNow) {
+ if (pto->m_tx_relay->nNextInvSend < current_time) {
fSendTrickle = true;
if (pto->fInbound) {
- pto->m_tx_relay->nNextInvSend = connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL);
+ pto->m_tx_relay->nNextInvSend = std::chrono::microseconds{connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL)};
} else {
// Use half the delay for outbound peers, as there is less privacy concern for them.
- pto->m_tx_relay->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> 1);
+ pto->m_tx_relay->nNextInvSend = PoissonNextSend(current_time, std::chrono::seconds{INVENTORY_BROADCAST_INTERVAL >> 1});
}
}
@@ -3916,7 +3919,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
// Detect whether we're stalling
- const auto current_time = GetTime<std::chrono::microseconds>();
+ current_time = GetTime<std::chrono::microseconds>();
// nNow is the current system time (GetTimeMicros is not mockable) and
// should be replaced by the mockable current_time eventually
nNow = GetTimeMicros();
diff --git a/src/psbt.h b/src/psbt.h
index 9d996171bb..6a5c468058 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -401,7 +401,6 @@ struct PartiallySignedTransaction
bool AddInput(const CTxIn& txin, PSBTInput& psbtin);
bool AddOutput(const CTxOut& txout, const PSBTOutput& psbtout);
PartiallySignedTransaction() {}
- PartiallySignedTransaction(const PartiallySignedTransaction& psbt_in) : tx(psbt_in.tx), inputs(psbt_in.inputs), outputs(psbt_in.outputs), unknown(psbt_in.unknown) {}
explicit PartiallySignedTransaction(const CMutableTransaction& tx);
/**
* Finds the UTXO for a given input index
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 7ba66736a6..d08f852751 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -58,7 +58,7 @@ static CUpdatedBlock latestblock;
*/
double GetDifficulty(const CBlockIndex* blockindex)
{
- assert(blockindex);
+ CHECK_NONFATAL(blockindex);
int nShift = (blockindex->nBits >> 24) & 0xff;
double dDiff =
@@ -957,7 +957,7 @@ static UniValue pruneblockchain(const JSONRPCRequest& request)
PruneBlockFilesManual(height);
const CBlockIndex* block = ::ChainActive().Tip();
- assert(block);
+ CHECK_NONFATAL(block);
while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
block = block->pprev;
}
@@ -1252,7 +1252,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
obj.pushKV("pruned", fPruneMode);
if (fPruneMode) {
const CBlockIndex* block = tip;
- assert(block);
+ CHECK_NONFATAL(block);
while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
block = block->pprev;
}
@@ -1598,7 +1598,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request)
}
}
- assert(pindex != nullptr);
+ CHECK_NONFATAL(pindex != nullptr);
if (request.params[0].isNull()) {
blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1));
@@ -1771,7 +1771,7 @@ static UniValue getblockstats(const JSONRPCRequest& request)
}
}
- assert(pindex != nullptr);
+ CHECK_NONFATAL(pindex != nullptr);
std::set<std::string> stats;
if (!request.params[1].isNull()) {
@@ -1871,7 +1871,7 @@ static UniValue getblockstats(const JSONRPCRequest& request)
}
CAmount txfee = tx_total_in - tx_total_out;
- assert(MoneyRange(txfee));
+ CHECK_NONFATAL(MoneyRange(txfee));
if (do_medianfee) {
fee_array.push_back(txfee);
}
@@ -2008,7 +2008,7 @@ public:
explicit CoinsViewScanReserver() : m_could_reserve(false) {}
bool reserve() {
- assert (!m_could_reserve);
+ CHECK_NONFATAL(!m_could_reserve);
std::lock_guard<std::mutex> lock(g_utxosetscan);
if (g_scan_in_progress) {
return false;
@@ -2135,9 +2135,9 @@ UniValue scantxoutset(const JSONRPCRequest& request)
LOCK(cs_main);
::ChainstateActive().ForceFlushStateToDisk();
pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor());
- assert(pcursor);
+ CHECK_NONFATAL(pcursor);
tip = ::ChainActive().Tip();
- assert(tip);
+ CHECK_NONFATAL(tip);
}
bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins);
result.pushKV("success", res);
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 00b8dd0255..ab22155651 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -555,7 +555,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
// Need to update only after we know CreateNewBlock succeeded
pindexPrev = pindexPrevNew;
}
- assert(pindexPrev);
+ CHECK_NONFATAL(pindexPrev);
CBlock* pblock = &pblocktemplate->block; // pointer for convenience
const Consensus::Params& consensusParams = Params().GetConsensus();
@@ -597,7 +597,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
entry.pushKV("fee", pblocktemplate->vTxFees[index_in_template]);
int64_t nTxSigOps = pblocktemplate->vTxSigOpsCost[index_in_template];
if (fPreSegWit) {
- assert(nTxSigOps % WITNESS_SCALE_FACTOR == 0);
+ CHECK_NONFATAL(nTxSigOps % WITNESS_SCALE_FACTOR == 0);
nTxSigOps /= WITNESS_SCALE_FACTOR;
}
entry.pushKV("sigops", nTxSigOps);
@@ -686,9 +686,9 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
int64_t nSigOpLimit = MAX_BLOCK_SIGOPS_COST;
int64_t nSizeLimit = MAX_BLOCK_SERIALIZED_SIZE;
if (fPreSegWit) {
- assert(nSigOpLimit % WITNESS_SCALE_FACTOR == 0);
+ CHECK_NONFATAL(nSigOpLimit % WITNESS_SCALE_FACTOR == 0);
nSigOpLimit /= WITNESS_SCALE_FACTOR;
- assert(nSizeLimit % WITNESS_SCALE_FACTOR == 0);
+ CHECK_NONFATAL(nSizeLimit % WITNESS_SCALE_FACTOR == 0);
nSizeLimit /= WITNESS_SCALE_FACTOR;
}
result.pushKV("sigoplimit", nSigOpLimit);
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 653b287e97..cfa3509c65 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -428,7 +428,7 @@ RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RP
std::set<std::string> named_args;
for (const auto& arg : m_args) {
// Should have unique named arguments
- assert(named_args.insert(arg.m_name).second);
+ CHECK_NONFATAL(named_args.insert(arg.m_name).second);
}
}
@@ -620,11 +620,11 @@ std::string RPCArg::ToStringObj(const bool oneline) const
case Type::OBJ:
case Type::OBJ_USER_KEYS:
// Currently unused, so avoid writing dead code
- assert(false);
+ CHECK_NONFATAL(false);
// no default case, so the compiler can warn about missing cases
}
- assert(false);
+ CHECK_NONFATAL(false);
}
std::string RPCArg::ToString(const bool oneline) const
@@ -661,7 +661,7 @@ std::string RPCArg::ToString(const bool oneline) const
// no default case, so the compiler can warn about missing cases
}
- assert(false);
+ CHECK_NONFATAL(false);
}
static std::pair<int64_t, int64_t> ParseRange(const UniValue& value)
diff --git a/src/test/bech32_tests.cpp b/src/test/bech32_tests.cpp
index 0ba492c24e..a1dabee579 100644
--- a/src/test/bech32_tests.cpp
+++ b/src/test/bech32_tests.cpp
@@ -4,24 +4,12 @@
#include <bech32.h>
#include <test/setup_common.h>
+#include <test/util/str.h>
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(bech32_tests, BasicTestingSetup)
-static bool CaseInsensitiveEqual(const std::string &s1, const std::string &s2)
-{
- if (s1.size() != s2.size()) return false;
- for (size_t i = 0; i < s1.size(); ++i) {
- char c1 = s1[i];
- if (c1 >= 'A' && c1 <= 'Z') c1 -= ('A' - 'a');
- char c2 = s2[i];
- if (c2 >= 'A' && c2 <= 'Z') c2 -= ('A' - 'a');
- if (c1 != c2) return false;
- }
- return true;
-}
-
BOOST_AUTO_TEST_CASE(bip173_testvectors_valid)
{
static const std::string CASES[] = {
diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp
index 5ce8e6feb0..df589b63bf 100644
--- a/src/test/blockencodings_tests.cpp
+++ b/src/test/blockencodings_tests.cpp
@@ -3,8 +3,8 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <blockencodings.h>
-#include <consensus/merkle.h>
#include <chainparams.h>
+#include <consensus/merkle.h>
#include <pow.h>
#include <streams.h>
@@ -14,11 +14,7 @@
std::vector<std::pair<uint256, CTransactionRef>> extra_txn;
-struct RegtestingSetup : public TestingSetup {
- RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {}
-};
-
-BOOST_FIXTURE_TEST_SUITE(blockencodings_tests, RegtestingSetup)
+BOOST_FIXTURE_TEST_SUITE(blockencodings_tests, RegTestingSetup)
static CBlock BuildBlockTestCase() {
CBlock block;
diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp
index 4a15bf0c77..acc6d6a21b 100644
--- a/src/test/blockfilter_index_tests.cpp
+++ b/src/test/blockfilter_index_tests.cpp
@@ -8,8 +8,9 @@
#include <index/blockfilterindex.h>
#include <miner.h>
#include <pow.h>
-#include <test/setup_common.h>
#include <script/standard.h>
+#include <test/lib/blockfilter.h>
+#include <test/setup_common.h>
#include <util/time.h>
#include <validation.h>
@@ -17,23 +18,6 @@
BOOST_AUTO_TEST_SUITE(blockfilter_index_tests)
-static bool ComputeFilter(BlockFilterType filter_type, const CBlockIndex* block_index,
- BlockFilter& filter)
-{
- CBlock block;
- if (!ReadBlockFromDisk(block, block_index->GetBlockPos(), Params().GetConsensus())) {
- return false;
- }
-
- CBlockUndo block_undo;
- if (block_index->nHeight > 0 && !UndoReadFromDisk(block_undo, block_index)) {
- return false;
- }
-
- filter = BlockFilter(filter_type, block, block_undo);
- return true;
-}
-
static bool CheckFilterLookups(BlockFilterIndex& filter_index, const CBlockIndex* block_index,
uint256& last_header)
{
diff --git a/src/test/fuzz/bech32.cpp b/src/test/fuzz/bech32.cpp
new file mode 100644
index 0000000000..8b91f9bc96
--- /dev/null
+++ b/src/test/fuzz/bech32.cpp
@@ -0,0 +1,43 @@
+// Copyright (c) 2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <bech32.h>
+#include <test/fuzz/fuzz.h>
+#include <test/util/str.h>
+#include <util/strencodings.h>
+
+#include <cassert>
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <vector>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ const std::string random_string(buffer.begin(), buffer.end());
+ const std::pair<std::string, std::vector<uint8_t>> r1 = bech32::Decode(random_string);
+ if (r1.first.empty()) {
+ assert(r1.second.empty());
+ } else {
+ const std::string& hrp = r1.first;
+ const std::vector<uint8_t>& data = r1.second;
+ const std::string reencoded = bech32::Encode(hrp, data);
+ assert(CaseInsensitiveEqual(random_string, reencoded));
+ }
+
+ std::vector<unsigned char> input;
+ ConvertBits<8, 5, true>([&](unsigned char c) { input.push_back(c); }, buffer.begin(), buffer.end());
+ const std::string encoded = bech32::Encode("bc", input);
+ assert(!encoded.empty());
+
+ const std::pair<std::string, std::vector<uint8_t>> r2 = bech32::Decode(encoded);
+ if (r2.first.empty()) {
+ assert(r2.second.empty());
+ } else {
+ const std::string& hrp = r2.first;
+ const std::vector<uint8_t>& data = r2.second;
+ assert(hrp == "bc");
+ assert(data == input);
+ }
+}
diff --git a/src/test/lib/blockfilter.cpp b/src/test/lib/blockfilter.cpp
new file mode 100644
index 0000000000..ddcee85d7e
--- /dev/null
+++ b/src/test/lib/blockfilter.cpp
@@ -0,0 +1,26 @@
+// Copyright (c) 2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <test/lib/blockfilter.h>
+
+#include <chainparams.h>
+#include <validation.h>
+
+
+bool ComputeFilter(BlockFilterType filter_type, const CBlockIndex* block_index, BlockFilter& filter)
+{
+ CBlock block;
+ if (!ReadBlockFromDisk(block, block_index->GetBlockPos(), Params().GetConsensus())) {
+ return false;
+ }
+
+ CBlockUndo block_undo;
+ if (block_index->nHeight > 0 && !UndoReadFromDisk(block_undo, block_index)) {
+ return false;
+ }
+
+ filter = BlockFilter(filter_type, block, block_undo);
+ return true;
+}
+
diff --git a/src/test/lib/blockfilter.h b/src/test/lib/blockfilter.h
new file mode 100644
index 0000000000..392dacbe80
--- /dev/null
+++ b/src/test/lib/blockfilter.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_TEST_LIB_BLOCKFILTER_H
+#define BITCOIN_TEST_LIB_BLOCKFILTER_H
+
+#include <blockfilter.h>
+class CBlockIndex;
+
+bool ComputeFilter(BlockFilterType filter_type, const CBlockIndex* block_index, BlockFilter& filter);
+
+#endif // BITCOIN_TEST_LIB_BLOCKFILTER_H
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index f5f217b841..0c1452822d 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -301,5 +301,19 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle)
BOOST_CHECK_EQUAL(IsLocal(addr), false);
}
+BOOST_AUTO_TEST_CASE(PoissonNextSend)
+{
+ g_mock_deterministic_tests = true;
+
+ int64_t now = 5000;
+ int average_interval_seconds = 600;
+
+ auto poisson = ::PoissonNextSend(now, average_interval_seconds);
+ std::chrono::microseconds poisson_chrono = ::PoissonNextSend(std::chrono::microseconds{now}, std::chrono::seconds{average_interval_seconds});
+
+ BOOST_CHECK_EQUAL(poisson, poisson_chrono.count());
+
+ g_mock_deterministic_tests = false;
+}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/setup_common.cpp b/src/test/setup_common.cpp
index 3425bd59c1..797b72ff59 100644
--- a/src/test/setup_common.cpp
+++ b/src/test/setup_common.cpp
@@ -124,11 +124,12 @@ TestingSetup::~TestingSetup()
pblocktree.reset();
}
-TestChain100Setup::TestChain100Setup() : TestingSetup(CBaseChainParams::REGTEST)
+TestChain100Setup::TestChain100Setup()
{
// CreateAndProcessBlock() does not support building SegWit blocks, so don't activate in these tests.
// TODO: fix the code to support SegWit blocks.
gArgs.ForceSetArg("-segwitheight", "432");
+ // Need to recreate chainparams
SelectParams(CBaseChainParams::REGTEST);
// Generate a 100-block chain:
@@ -142,12 +143,9 @@ TestChain100Setup::TestChain100Setup() : TestingSetup(CBaseChainParams::REGTEST)
}
}
-//
// Create a new block with just given transactions, coinbase paying to
// scriptPubKey, and try to add it to the current chain.
-//
-CBlock
-TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey)
+CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey)
{
const CChainParams& chainparams = Params();
std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey);
@@ -175,6 +173,7 @@ TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>&
TestChain100Setup::~TestChain100Setup()
{
+ gArgs.ForceSetArg("-segwitheight", "0");
}
diff --git a/src/test/setup_common.h b/src/test/setup_common.h
index 5731b50e31..465baf90c3 100644
--- a/src/test/setup_common.h
+++ b/src/test/setup_common.h
@@ -76,6 +76,12 @@ struct TestingSetup : public BasicTestingSetup {
~TestingSetup();
};
+/** Identical to TestingSetup, but chain set to regtest */
+struct RegTestingSetup : public TestingSetup {
+ RegTestingSetup()
+ : TestingSetup{CBaseChainParams::REGTEST} {}
+};
+
class CBlock;
struct CMutableTransaction;
class CScript;
@@ -84,7 +90,7 @@ class CScript;
// Testing fixture that pre-creates a
// 100-block REGTEST-mode block chain
//
-struct TestChain100Setup : public TestingSetup {
+struct TestChain100Setup : public RegTestingSetup {
TestChain100Setup();
// Create a new block with just given transactions, coinbase paying to
diff --git a/src/test/util/str.cpp b/src/test/util/str.cpp
new file mode 100644
index 0000000000..c517fe44d9
--- /dev/null
+++ b/src/test/util/str.cpp
@@ -0,0 +1,21 @@
+// Copyright (c) 2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <test/util/str.h>
+
+#include <cstdint>
+#include <string>
+
+bool CaseInsensitiveEqual(const std::string& s1, const std::string& s2)
+{
+ if (s1.size() != s2.size()) return false;
+ for (size_t i = 0; i < s1.size(); ++i) {
+ char c1 = s1[i];
+ if (c1 >= 'A' && c1 <= 'Z') c1 -= ('A' - 'a');
+ char c2 = s2[i];
+ if (c2 >= 'A' && c2 <= 'Z') c2 -= ('A' - 'a');
+ if (c1 != c2) return false;
+ }
+ return true;
+}
diff --git a/src/test/util/str.h b/src/test/util/str.h
new file mode 100644
index 0000000000..63629501e8
--- /dev/null
+++ b/src/test/util/str.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_TEST_UTIL_STR_H
+#define BITCOIN_TEST_UTIL_STR_H
+
+#include <string>
+
+bool CaseInsensitiveEqual(const std::string& s1, const std::string& s2);
+
+#endif // BITCOIN_TEST_UTIL_STR_H
diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp
index aca9f475ac..ae998e92a5 100644
--- a/src/test/validation_block_tests.cpp
+++ b/src/test/validation_block_tests.cpp
@@ -18,13 +18,9 @@
#include <thread>
-struct RegtestingSetup : public TestingSetup {
- RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {}
-};
-
static const std::vector<unsigned char> V_OP_TRUE{OP_TRUE};
-BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegtestingSetup)
+BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegTestingSetup)
struct TestSubscriber : public CValidationInterface {
uint256 m_expected_tip;
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index f7353ebbbb..da4da4d9e0 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -168,7 +168,7 @@ UniValue importprivkey(const JSONRPCRequest& request)
if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
+ CHECK_NONFATAL(key.VerifyPubKey(pubkey));
CKeyID vchAddress = pubkey.GetID();
{
pwallet->MarkDirty();
@@ -639,7 +639,7 @@ UniValue importwallet(const JSONRPCRequest& request)
std::string label = std::get<3>(key_tuple);
CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
+ CHECK_NONFATAL(key.VerifyPubKey(pubkey));
CKeyID keyid = pubkey.GetID();
pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid)));
@@ -906,7 +906,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d
case TX_SCRIPTHASH: {
if (script_ctx == ScriptContext::P2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH");
if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside a P2WSH");
- assert(script_ctx == ScriptContext::TOP);
+ CHECK_NONFATAL(script_ctx == ScriptContext::TOP);
CScriptID id = CScriptID(uint160(solverdata[0]));
auto subscript = std::move(import_data.redeemscript); // Remove redeemscript from import_data to check for superfluous script later.
if (!subscript) return "missing redeemscript";
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index bfa4cf2bbe..7f998ab450 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -136,7 +136,7 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo
entry.pushKV("blockindex", wtx.m_confirm.nIndex);
int64_t block_time;
bool found_block = chain.findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &block_time);
- assert(found_block);
+ CHECK_NONFATAL(found_block);
entry.pushKV("blocktime", block_time);
} else {
entry.pushKV("trusted", wtx.IsTrusted(locked_chain));
@@ -1598,7 +1598,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
CWalletTx tx = pairWtx.second;
- if (depth == -1 || tx.GetDepthInMainChain(*locked_chain) < depth) {
+ if (depth == -1 || abs(tx.GetDepthInMainChain(*locked_chain)) < depth) {
ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, filter, nullptr /* filter_label */);
}
}
@@ -2943,7 +2943,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
CTxDestination witness_destination;
if (redeemScript.IsPayToWitnessScriptHash()) {
bool extracted = ExtractDestination(redeemScript, witness_destination);
- assert(extracted);
+ CHECK_NONFATAL(extracted);
// Also return the witness script
const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination);
CScriptID id;
@@ -3756,26 +3756,24 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
ret.pushKV("label", pwallet->mapAddressBook[dest].name);
}
ret.pushKV("ischange", pwallet->IsChange(scriptPubKey));
- const CKeyMetadata* meta = nullptr;
- CKeyID key_id = GetKeyForDestination(*provider, dest);
- if (!key_id.IsNull()) {
- auto it = pwallet->mapKeyMetadata.find(key_id);
- if (it != pwallet->mapKeyMetadata.end()) {
- meta = &it->second;
+
+ ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan();
+ if (spk_man) {
+ CKeyID key_id = GetKeyForDestination(*provider, dest);
+ const CKeyMetadata* meta = nullptr;
+ if (!key_id.IsNull()) {
+ meta = spk_man->GetMetadata(key_id);
}
- }
- if (!meta) {
- auto it = pwallet->m_script_metadata.find(CScriptID(scriptPubKey));
- if (it != pwallet->m_script_metadata.end()) {
- meta = &it->second;
+ if (!meta) {
+ meta = spk_man->GetMetadata(CScriptID(scriptPubKey));
}
- }
- if (meta) {
- ret.pushKV("timestamp", meta->nCreateTime);
- if (meta->has_key_origin) {
- ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path));
- ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
- ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4));
+ if (meta) {
+ ret.pushKV("timestamp", meta->nCreateTime);
+ if (meta->has_key_origin) {
+ ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path));
+ ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
+ ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4));
+ }
}
}
@@ -3833,7 +3831,7 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request)
// address strings, but build a separate set as a precaution just in
// case it does.
bool unique = addresses.emplace(address).second;
- assert(unique);
+ CHECK_NONFATAL(unique);
// UniValue::pushKV checks if the key exists in O(N)
// and since duplicate addresses are unexpected (checked with
// std::set in O(log(N))), UniValue::__pushKV is used instead,
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 259bfcd76d..bb13db11ba 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -11,9 +11,8 @@
#include <wallet/scriptpubkeyman.h>
#include <wallet/wallet.h>
-bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error)
+bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
{
- LOCK(cs_wallet);
error.clear();
TopUpKeyPool();
@@ -25,8 +24,6 @@ bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, const std::
}
LearnRelatedScripts(new_key, type);
dest = GetDestinationForKey(new_key, type);
-
- m_wallet.SetAddressBook(dest, label, "receive");
return true;
}
@@ -265,6 +262,48 @@ bool LegacyScriptPubKeyMan::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
return true;
}
+bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool)
+{
+ {
+ if (!ReserveKeyFromKeyPool(index, keypool, internal)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void LegacyScriptPubKeyMan::KeepDestination(int64_t index)
+{
+ KeepKey(index);
+}
+
+void LegacyScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey)
+{
+ ReturnKey(index, internal, pubkey);
+}
+
+bool LegacyScriptPubKeyMan::TopUp(unsigned int size)
+{
+ return TopUpKeyPool(size);
+}
+
+void LegacyScriptPubKeyMan::MarkUnusedAddresses(const CScript& script)
+{
+ AssertLockHeld(cs_wallet);
+ // extract addresses and check if they match with an unused keypool key
+ for (const auto& keyid : GetAffectedKeys(script, *this)) {
+ std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid);
+ if (mi != m_pool_key_to_index.end()) {
+ WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__);
+ MarkReserveKeysAsUsed(mi->second);
+
+ if (!TopUpKeyPool()) {
+ WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
+ }
+ }
+ }
+}
+
void LegacyScriptPubKeyMan::UpgradeKeyMetadata()
{
AssertLockHeld(cs_wallet);
@@ -298,8 +337,19 @@ void LegacyScriptPubKeyMan::UpgradeKeyMetadata()
}
}
}
- batch.reset(); //write before setting the flag
- m_storage.SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
+}
+
+bool LegacyScriptPubKeyMan::SetupGeneration(bool force)
+{
+ if ((CanGenerateKeys() && !force) || m_storage.IsLocked()) {
+ return false;
+ }
+
+ SetHDSeed(GenerateNewSeed());
+ if (!NewKeyPool()) {
+ return false;
+ }
+ return true;
}
bool LegacyScriptPubKeyMan::IsHDEnabled() const
@@ -324,6 +374,58 @@ bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal)
return keypool_has_keys;
}
+bool LegacyScriptPubKeyMan::Upgrade(int prev_version, std::string& error)
+{
+ AssertLockHeld(cs_wallet);
+ error = "";
+ bool hd_upgrade = false;
+ bool split_upgrade = false;
+ if (m_storage.CanSupportFeature(FEATURE_HD) && !IsHDEnabled()) {
+ WalletLogPrintf("Upgrading wallet to HD\n");
+ m_storage.SetMinVersion(FEATURE_HD);
+
+ // generate a new master key
+ CPubKey masterPubKey = GenerateNewSeed();
+ SetHDSeed(masterPubKey);
+ hd_upgrade = true;
+ }
+ // Upgrade to HD chain split if necessary
+ if (m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) {
+ WalletLogPrintf("Upgrading wallet to use HD chain split\n");
+ m_storage.SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL);
+ split_upgrade = FEATURE_HD_SPLIT > prev_version;
+ }
+ // Mark all keys currently in the keypool as pre-split
+ if (split_upgrade) {
+ MarkPreSplitKeys();
+ }
+ // Regenerate the keypool if upgraded to HD
+ if (hd_upgrade) {
+ if (!TopUpKeyPool()) {
+ error = _("Unable to generate keys").translated;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::HavePrivateKeys() const
+{
+ LOCK(cs_KeyStore);
+ return !mapKeys.empty() || !mapCryptedKeys.empty();
+}
+
+void LegacyScriptPubKeyMan::RewriteDB()
+{
+ AssertLockHeld(cs_wallet);
+ setInternalKeyPool.clear();
+ setExternalKeyPool.clear();
+ m_pool_key_to_index.clear();
+ // Note: can't top-up keypool here, because wallet is locked.
+ // User will be prompted to unlock wallet the next operation
+ // that requires a new key.
+}
+
static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) {
if (setKeyPool.empty()) {
return GetTime();
@@ -362,6 +464,33 @@ size_t LegacyScriptPubKeyMan::KeypoolCountExternalKeys()
return setExternalKeyPool.size() + set_pre_split_keypool.size();
}
+unsigned int LegacyScriptPubKeyMan::GetKeyPoolSize() const
+{
+ AssertLockHeld(cs_wallet);
+ return setInternalKeyPool.size() + setExternalKeyPool.size();
+}
+
+int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const
+{
+ AssertLockHeld(cs_wallet);
+ return nTimeFirstKey;
+}
+
+const CKeyMetadata* LegacyScriptPubKeyMan::GetMetadata(uint160 id) const
+{
+ AssertLockHeld(cs_wallet);
+ auto it = mapKeyMetadata.find(CKeyID(id));
+ if (it != mapKeyMetadata.end()) {
+ return &it->second;
+ } else {
+ auto it2 = m_script_metadata.find(CScriptID(id));
+ if (it2 != m_script_metadata.end()) {
+ return &it2->second;
+ }
+ }
+ return nullptr;
+}
+
/**
* Update wallet first key creation time. This should be called whenever keys
* are added to the wallet, with the oldest key creation time.
@@ -378,6 +507,11 @@ void LegacyScriptPubKeyMan::UpdateTimeFirstKey(int64_t nCreateTime)
}
}
+bool LegacyScriptPubKeyMan::LoadKey(const CKey& key, const CPubKey &pubkey)
+{
+ return AddKeyPubKeyInner(key, pubkey);
+}
+
bool LegacyScriptPubKeyMan::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
{
WalletBatch batch(m_storage.GetDatabase());
@@ -420,7 +554,7 @@ bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& s
secret.GetPrivKey(),
mapKeyMetadata[pubkey.GetID()]);
}
- m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
+ m_storage.UnsetBlankWalletFlag(batch);
return true;
}
@@ -577,7 +711,7 @@ bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch, const CScript
UpdateTimeFirstKey(meta.nCreateTime);
NotifyWatchonlyChanged(true);
if (batch.WriteWatchOnly(dest, meta)) {
- m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
+ m_storage.UnsetBlankWalletFlag(batch);
return true;
}
return false;
@@ -855,7 +989,8 @@ void LegacyScriptPubKeyMan::SetHDSeed(const CPubKey& seed)
newHdChain.seed_id = seed.GetID();
SetHDChain(newHdChain, false);
NotifyCanGetAddressesChanged();
- m_wallet.UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
+ WalletBatch batch(m_storage.GetDatabase());
+ m_storage.UnsetBlankWalletFlag(batch);
}
/**
@@ -1134,7 +1269,7 @@ bool LegacyScriptPubKeyMan::AddCScriptWithDB(WalletBatch& batch, const CScript&
if (!FillableSigningProvider::AddCScript(redeemScript))
return false;
if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) {
- m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
+ m_storage.UnsetBlankWalletFlag(batch);
return true;
}
return false;
@@ -1229,7 +1364,7 @@ bool LegacyScriptPubKeyMan::ImportPubKeys(const std::vector<CKeyID>& ordered_pub
return true;
}
-bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp)
+bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::set<CScript>& script_pub_keys, const bool have_solving_data, const int64_t timestamp)
{
WalletBatch batch(m_storage.GetDatabase());
for (const CScript& script : script_pub_keys) {
@@ -1238,11 +1373,6 @@ bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::string& label, const
return false;
}
}
- CTxDestination dest;
- ExtractDestination(script, dest);
- if (apply_label && IsValidDestination(dest)) {
- m_wallet.SetAddressBookWithDB(batch, dest, label, "receive");
- }
}
return true;
}
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 16a5c9b979..0dbf98ee94 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -28,8 +28,7 @@ public:
virtual const std::string GetDisplayName() const = 0;
virtual WalletDatabase& GetDatabase() = 0;
virtual bool IsWalletFlagSet(uint64_t) const = 0;
- virtual void SetWalletFlag(uint64_t) = 0;
- virtual void UnsetWalletFlagWithDB(WalletBatch&, uint64_t) = 0;
+ virtual void UnsetBlankWalletFlag(WalletBatch&) = 0;
virtual bool CanSupportFeature(enum WalletFeature) const = 0;
virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr, bool = false) = 0;
virtual bool IsLocked() const = 0;
@@ -38,6 +37,8 @@ public:
//! Default for -keypool
static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000;
+std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider);
+
/** A key from a CWallet's keypool
*
* The wallet holds one (for pre HD-split wallets) or several keypools. These
@@ -145,41 +146,68 @@ protected:
public:
ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {}
+ virtual ~ScriptPubKeyMan() {};
+ virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { return false; }
+ virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; }
+
+ virtual bool GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) { return false; }
+ virtual void KeepDestination(int64_t index) {}
+ virtual void ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) {}
+
+ virtual bool TopUp(unsigned int size = 0) { return false; }
+
+ //! Mark unused addresses as being used
+ virtual void MarkUnusedAddresses(const CScript& script) {}
+
+ /** Sets up the key generation stuff, i.e. generates new HD seeds and sets them as active.
+ * Returns false if already setup or setup fails, true if setup is successful
+ * Set force=true to make it re-setup if already setup, used for upgrades
+ */
+ virtual bool SetupGeneration(bool force = false) { return false; }
+
+ /* Returns true if HD is enabled */
+ virtual bool IsHDEnabled() const { return false; }
+
+ /* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */
+ virtual bool CanGetAddresses(bool internal = false) { return false; }
+
+ /** Upgrades the wallet to the specified version */
+ virtual bool Upgrade(int prev_version, std::string& error) { return false; }
+
+ virtual bool HavePrivateKeys() const { return false; }
+
+ //! The action to do when the DB needs rewrite
+ virtual void RewriteDB() {}
+
+ virtual int64_t GetOldestKeyPoolTime() { return GetTime(); }
+
+ virtual size_t KeypoolCountExternalKeys() { return 0; }
+ virtual unsigned int GetKeyPoolSize() const { return 0; }
+
+ virtual int64_t GetTimeFirstKey() const { return 0; }
+
+ virtual const CKeyMetadata* GetMetadata(uint160 id) const { return nullptr; }
};
class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider
{
private:
- using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
using WatchOnlySet = std::set<CScript>;
using WatchKeyMap = std::map<CKeyID, CPubKey>;
- //! will encrypt previously unencrypted keys
- bool EncryptKeys(CKeyingMaterial& vMasterKeyIn);
+ WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr;
+
+ using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore);
WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore);
WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore);
- bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
- bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey);
-
- WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr;
-
- /* the HD chain data model (external chain counters) */
- CHDChain hdChain;
-
- /* HD derive new child key (on internal or external chain) */
- void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
-
- std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_wallet);
- std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet);
- std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_wallet);
- int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0;
- std::map<CKeyID, int64_t> m_pool_key_to_index;
-
int64_t nTimeFirstKey GUARDED_BY(cs_wallet) = 0;
+ bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey);
+ bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
+
/**
* Private version of AddWatchOnly method which does not accept a
* timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if
@@ -192,26 +220,91 @@ private:
bool AddWatchOnly(const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool AddWatchOnlyInMem(const CScript &dest);
-
- /** Add a KeyOriginInfo to the wallet */
- bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info);
+ //! Adds a watch-only address to the store, and saves it to disk.
+ bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a key to the store, and saves it to disk.
bool AddKeyPubKeyWithDB(WalletBatch &batch,const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- //! Adds a watch-only address to the store, and saves it to disk.
- bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
-
void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch);
//! Adds a script to the store and saves it to disk
bool AddCScriptWithDB(WalletBatch& batch, const CScript& script);
- public:
+ /** Add a KeyOriginInfo to the wallet */
+ bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info);
+
+ /* the HD chain data model (external chain counters) */
+ CHDChain hdChain;
+
+ /* HD derive new child key (on internal or external chain) */
+ void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_wallet);
+ std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet);
+ std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_wallet);
+ int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0;
+ std::map<CKeyID, int64_t> m_pool_key_to_index;
+
//! Fetches a key from the keypool
bool GetKeyFromPool(CPubKey &key, bool internal = false);
- void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ /**
+ * Reserves a key from the keypool and sets nIndex to its index
+ *
+ * @param[out] nIndex the index of the key in keypool
+ * @param[out] keypool the keypool the key was drawn from, which could be the
+ * the pre-split pool if present, or the internal or external pool
+ * @param fRequestedInternal true if the caller would like the key drawn
+ * from the internal keypool, false if external is preferred
+ *
+ * @return true if succeeded, false if failed due to empty keypool
+ * @throws std::runtime_error if keypool read failed, key was invalid,
+ * was not found in the wallet, or was misclassified in the internal
+ * or external keypool
+ */
+ bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal);
+
+ void KeepKey(int64_t nIndex);
+ void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey);
+
+public:
+ bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) override;
+ isminetype IsMine(const CScript& script) const override;
+
+ //! will encrypt previously unencrypted keys
+ bool EncryptKeys(CKeyingMaterial& vMasterKeyIn);
+
+ bool GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) override;
+ void KeepDestination(int64_t index) override;
+ void ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) override;
+
+ bool TopUp(unsigned int size = 0) override;
+
+ void MarkUnusedAddresses(const CScript& script) override;
+
+ //! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
+ void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ bool IsHDEnabled() const override;
+
+ bool SetupGeneration(bool force = false) override;
+
+ bool Upgrade(int prev_version, std::string& error) override;
+
+ bool HavePrivateKeys() const override;
+
+ void RewriteDB() override;
+
+ int64_t GetOldestKeyPoolTime() override;
+ size_t KeypoolCountExternalKeys() override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ unsigned int GetKeyPoolSize() const override;
+
+ int64_t GetTimeFirstKey() const override;
+
+ const CKeyMetadata* GetMetadata(uint160 id) const override;
+
+ bool CanGetAddresses(bool internal = false) override;
// Map from Key ID to key metadata.
std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_wallet);
@@ -219,94 +312,61 @@ private:
// Map from Script ID to key metadata (for watch-only keys).
std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet);
- /**
- * keystore implementation
- * Generate a new key
- */
- CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a key to the store, and saves it to disk.
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a key to the store, without saving it to disk (used by LoadWallet)
- bool LoadKey(const CKey& key, const CPubKey &pubkey) { return AddKeyPubKeyInner(key, pubkey); }
- //! Load metadata (used by LoadWallet)
- void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- //! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
- void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
-
+ bool LoadKey(const CKey& key, const CPubKey &pubkey);
//! Adds an encrypted key to the store, and saves it to disk.
bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
//! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet)
bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
- bool GetKey(const CKeyID &address, CKey& keyOut) const override;
- bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override;
- bool HaveKey(const CKeyID &address) const override;
- std::set<CKeyID> GetKeys() const override;
- bool AddCScript(const CScript& redeemScript) override;
+ void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ //! Adds a CScript to the store
bool LoadCScript(const CScript& redeemScript);
+ //! Load metadata (used by LoadWallet)
+ void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ //! Generate a new key
+ CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ /* Set the HD chain model (chain child index counters) */
+ void SetHDChain(const CHDChain& chain, bool memonly);
+ const CHDChain& GetHDChain() const { return hdChain; }
- //! Adds a watch-only address to the store, and saves it to disk.
- bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool RemoveWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
bool LoadWatchOnly(const CScript &dest);
//! Returns whether the watch-only script is in the wallet
bool HaveWatchOnly(const CScript &dest) const;
//! Returns whether there are any watch-only things in the wallet
bool HaveWatchOnly() const;
+ //! Remove a watch only script from the keystore
+ bool RemoveWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
//! Fetches a pubkey from mapWatchKeys if it exists there
bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const;
- bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ /* SigningProvider overrides */
+ bool HaveKey(const CKeyID &address) const override;
+ bool GetKey(const CKeyID &address, CKey& keyOut) const override;
+ bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override;
+ bool AddCScript(const CScript& redeemScript) override;
+ bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
- bool NewKeyPool();
- size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ //! Load a keypool entry
+ void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool TopUpKeyPool(unsigned int kpSize = 0);
+ bool NewKeyPool();
+ void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- /**
- * Reserves a key from the keypool and sets nIndex to its index
- *
- * @param[out] nIndex the index of the key in keypool
- * @param[out] keypool the keypool the key was drawn from, which could be the
- * the pre-split pool if present, or the internal or external pool
- * @param fRequestedInternal true if the caller would like the key drawn
- * from the internal keypool, false if external is preferred
- *
- * @return true if succeeded, false if failed due to empty keypool
- * @throws std::runtime_error if keypool read failed, key was invalid,
- * was not found in the wallet, or was misclassified in the internal
- * or external keypool
- */
- bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal);
- void KeepKey(int64_t nIndex);
- void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey);
- int64_t GetOldestKeyPoolTime();
- /**
- * Marks all keys in the keypool up to and including reserve_key as used.
- */
- void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; }
- bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error);
-
- isminetype IsMine(const CScript& script) const;
-
- /* Set the HD chain model (chain child index counters) */
- void SetHDChain(const CHDChain& chain, bool memonly);
- const CHDChain& GetHDChain() const { return hdChain; }
-
- /* Returns true if HD is enabled */
- bool IsHDEnabled() const;
+ bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool ImportScriptPubKeys(const std::set<CScript>& script_pub_keys, const bool have_solving_data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/* Returns true if the wallet can generate new keys */
bool CanGenerateKeys();
- /* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */
- bool CanGetAddresses(bool internal = false);
-
/* Generates a new HD seed (will not be activated) */
CPubKey GenerateNewSeed();
@@ -333,9 +393,13 @@ private:
*/
void LearnAllRelatedScripts(const CPubKey& key);
- /** Implement lookup of key origin information through wallet key metadata. */
- bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
+ /**
+ * Marks all keys in the keypool up to and including reserve_key as used.
+ */
+ void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; }
+ std::set<CKeyID> GetKeys() const override;
// Temporary CWallet accessors and aliases.
friend class CWallet;
friend class ReserveDestination;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 069ae57878..0b7dc256ad 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -210,9 +210,14 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString&
}
// Set a seed for the wallet
- CPubKey master_pub_key = wallet->m_spk_man->GenerateNewSeed();
- wallet->m_spk_man->SetHDSeed(master_pub_key);
- wallet->m_spk_man->NewKeyPool();
+ {
+ if (auto spk_man = wallet->m_spk_man.get()) {
+ if (!spk_man->SetupGeneration()) {
+ error = "Unable to generate initial keys";
+ return WalletCreationStatus::CREATION_FAILED;
+ }
+ }
+ }
// Relock the wallet
wallet->Lock();
@@ -236,8 +241,6 @@ std::string COutput::ToString() const
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
}
-std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider);
-
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
{
LOCK(cs_wallet);
@@ -249,10 +252,15 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
void CWallet::UpgradeKeyMetadata()
{
+ if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) {
+ return;
+ }
+
if (m_spk_man) {
AssertLockHeld(m_spk_man->cs_wallet);
m_spk_man->UpgradeKeyMetadata();
}
+ SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
}
bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys)
@@ -562,11 +570,11 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
Unlock(strWalletPassphrase);
// if we are using HD, replace the HD seed with a new one
- if (m_spk_man->IsHDEnabled()) {
- m_spk_man->SetHDSeed(m_spk_man->GenerateNewSeed());
+ if (auto spk_man = m_spk_man.get()) {
+ if (spk_man->IsHDEnabled()) {
+ spk_man->SetupGeneration(true);
+ }
}
-
- m_spk_man->NewKeyPool();
Lock();
// Need to completely rewrite the wallet file; if we don't, bdb might keep
@@ -871,17 +879,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::St
// loop though all outputs
for (const CTxOut& txout: tx.vout) {
- // extract addresses and check if they match with an unused keypool key
- for (const auto& keyid : GetAffectedKeys(txout.scriptPubKey, *m_spk_man)) {
- std::map<CKeyID, int64_t>::const_iterator mi = m_spk_man->m_pool_key_to_index.find(keyid);
- if (mi != m_spk_man->m_pool_key_to_index.end()) {
- WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__);
- MarkReserveKeysAsUsed(mi->second);
-
- if (!m_spk_man->TopUpKeyPool()) {
- WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
- }
- }
+ if (auto spk_man = m_spk_man.get()) {
+ spk_man->MarkUnusedAddresses(txout.scriptPubKey);
}
}
@@ -1304,6 +1303,11 @@ void CWallet::UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag)
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
+void CWallet::UnsetBlankWalletFlag(WalletBatch& batch)
+{
+ UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
+}
+
bool CWallet::IsWalletFlagSet(uint64_t flag) const
{
return (m_wallet_flags & flag);
@@ -1400,9 +1404,19 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri
return false;
}
AssertLockHeld(spk_man->cs_wallet);
- if (!spk_man->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, apply_label, timestamp)) {
+ if (!spk_man->ImportScriptPubKeys(script_pub_keys, have_solving_data, timestamp)) {
return false;
}
+ if (apply_label) {
+ WalletBatch batch(*database);
+ for (const CScript& script : script_pub_keys) {
+ CTxDestination dest;
+ ExtractDestination(script, dest);
+ if (IsValidDestination(dest)) {
+ SetAddressBookWithDB(batch, dest, label, "receive");
+ }
+ }
+ }
return true;
}
@@ -1836,32 +1850,37 @@ bool CWalletTx::InMempool() const
bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain) const
{
+ std::set<uint256> s;
+ return IsTrusted(locked_chain, s);
+}
+
+bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain, std::set<uint256>& trusted_parents) const
+{
// Quick answer in most cases
- if (!locked_chain.checkFinalTx(*tx)) {
- return false;
- }
+ if (!locked_chain.checkFinalTx(*tx)) return false;
int nDepth = GetDepthInMainChain(locked_chain);
- if (nDepth >= 1)
- return true;
- if (nDepth < 0)
- return false;
- if (!pwallet->m_spend_zero_conf_change || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
- return false;
+ if (nDepth >= 1) return true;
+ if (nDepth < 0) return false;
+ // using wtx's cached debit
+ if (!pwallet->m_spend_zero_conf_change || !IsFromMe(ISMINE_ALL)) return false;
// Don't trust unconfirmed transactions from us unless they are in the mempool.
- if (!InMempool())
- return false;
+ if (!InMempool()) return false;
// Trusted if all inputs are from us and are in the mempool:
for (const CTxIn& txin : tx->vin)
{
// Transactions not sent by us: not trusted
const CWalletTx* parent = pwallet->GetWalletTx(txin.prevout.hash);
- if (parent == nullptr)
- return false;
+ if (parent == nullptr) return false;
const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
- if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE)
- return false;
+ // Check that this specific input being spent is trusted
+ if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) return false;
+ // If we've already trusted this parent, continue
+ if (trusted_parents.count(parent->GetHash())) continue;
+ // Recurse to check that the parent is also trusted
+ if (!parent->IsTrusted(locked_chain, trusted_parents)) return false;
+ trusted_parents.insert(parent->GetHash());
}
return true;
}
@@ -1947,10 +1966,11 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons
{
auto locked_chain = chain().lock();
LOCK(cs_wallet);
+ std::set<uint256> trusted_parents;
for (const auto& entry : mapWallet)
{
const CWalletTx& wtx = entry.second;
- const bool is_trusted{wtx.IsTrusted(*locked_chain)};
+ const bool is_trusted{wtx.IsTrusted(*locked_chain, trusted_parents)};
const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)};
const CAmount tx_credit_mine{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
@@ -1997,6 +2017,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH};
const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH};
+ std::set<uint256> trusted_parents;
for (const auto& entry : mapWallet)
{
const uint256& wtxid = entry.first;
@@ -2018,7 +2039,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
if (nDepth == 0 && !wtx.InMempool())
continue;
- bool safeTx = wtx.IsTrusted(locked_chain);
+ bool safeTx = wtx.IsTrusted(locked_chain, trusted_parents);
// We should not consider coins from transactions that are replacing
// other transactions.
@@ -2889,12 +2910,9 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
if (database->Rewrite("\x04pool"))
{
- setInternalKeyPool.clear();
- setExternalKeyPool.clear();
- m_spk_man->m_pool_key_to_index.clear();
- // Note: can't top-up keypool here, because wallet is locked.
- // User will be prompted to unlock wallet the next operation
- // that requires a new key.
+ if (auto spk_man = m_spk_man.get()) {
+ spk_man->RewriteDB();
+ }
}
}
@@ -2926,12 +2944,9 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
{
if (database->Rewrite("\x04pool"))
{
- setInternalKeyPool.clear();
- setExternalKeyPool.clear();
- m_spk_man->m_pool_key_to_index.clear();
- // Note: can't top-up keypool here, because wallet is locked.
- // User will be prompted to unlock wallet the next operation
- // that requires a new key.
+ if (auto spk_man = m_spk_man.get()) {
+ spk_man->RewriteDB();
+ }
}
}
@@ -2950,13 +2965,9 @@ DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
{
if (database->Rewrite("\x04pool"))
{
- LOCK(cs_wallet);
- setInternalKeyPool.clear();
- setExternalKeyPool.clear();
- m_spk_man->m_pool_key_to_index.clear();
- // Note: can't top-up keypool here, because wallet is locked.
- // User will be prompted to unlock wallet the next operation
- // that requires a new key.
+ if (auto spk_man = m_spk_man.get()) {
+ spk_man->RewriteDB();
+ }
}
}
@@ -3023,23 +3034,39 @@ size_t CWallet::KeypoolCountExternalKeys()
return count;
}
+unsigned int CWallet::GetKeyPoolSize() const
+{
+ AssertLockHeld(cs_wallet);
+
+ unsigned int count = 0;
+ if (auto spk_man = m_spk_man.get()) {
+ count += spk_man->GetKeyPoolSize();
+ }
+ return count;
+}
+
bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
bool res = true;
if (auto spk_man = m_spk_man.get()) {
- res &= spk_man->TopUpKeyPool(kpSize);
+ res &= spk_man->TopUp(kpSize);
}
return res;
}
bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error)
{
+ LOCK(cs_wallet);
error.clear();
bool result = false;
auto spk_man = m_spk_man.get();
if (spk_man) {
- result = spk_man->GetNewDestination(type, label, dest, error);
+ result = spk_man->GetNewDestination(type, dest, error);
+ }
+ if (result) {
+ SetAddressBook(dest, label, "receive");
}
+
return result;
}
@@ -3047,7 +3074,7 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des
{
error.clear();
- m_spk_man->TopUpKeyPool();
+ m_spk_man->TopUp();
ReserveDestination reservedest(this);
if (!reservedest.GetReservedDestination(type, dest, true)) {
@@ -3074,11 +3101,12 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain:
{
LOCK(cs_wallet);
+ std::set<uint256> trusted_parents;
for (const auto& walletEntry : mapWallet)
{
const CWalletTx& wtx = walletEntry.second;
- if (!wtx.IsTrusted(locked_chain))
+ if (!wtx.IsTrusted(locked_chain, trusted_parents))
continue;
if (wtx.IsImmatureCoinBase(locked_chain))
@@ -3229,7 +3257,7 @@ bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestin
if (nIndex == -1)
{
CKeyPool keypool;
- if (!m_spk_man->ReserveKeyFromKeyPool(nIndex, keypool, internal)) {
+ if (!m_spk_man->GetReservedDestination(type, internal, nIndex, keypool)) {
return false;
}
vchPubKey = keypool.vchPubKey;
@@ -3245,7 +3273,7 @@ bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestin
void ReserveDestination::KeepDestination()
{
if (nIndex != -1)
- m_spk_man->KeepKey(nIndex);
+ m_spk_man->KeepDestination(nIndex);
nIndex = -1;
vchPubKey = CPubKey();
address = CNoDestination();
@@ -3254,7 +3282,7 @@ void ReserveDestination::KeepDestination()
void ReserveDestination::ReturnDestination()
{
if (nIndex != -1) {
- m_spk_man->ReturnKey(nIndex, fInternal, vchPubKey);
+ m_spk_man->ReturnDestination(nIndex, fInternal, vchPubKey);
}
nIndex = -1;
vchPubKey = CPubKey();
@@ -3600,31 +3628,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
return nullptr;
}
- bool hd_upgrade = false;
- bool split_upgrade = false;
- if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->m_spk_man->IsHDEnabled()) {
- walletInstance->WalletLogPrintf("Upgrading wallet to HD\n");
- walletInstance->SetMinVersion(FEATURE_HD);
-
- // generate a new master key
- CPubKey masterPubKey = walletInstance->m_spk_man->GenerateNewSeed();
- walletInstance->m_spk_man->SetHDSeed(masterPubKey);
- hd_upgrade = true;
- }
- // Upgrade to HD chain split if necessary
- if (walletInstance->CanSupportFeature(FEATURE_HD_SPLIT)) {
- walletInstance->WalletLogPrintf("Upgrading wallet to use HD chain split\n");
- walletInstance->SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL);
- split_upgrade = FEATURE_HD_SPLIT > prev_version;
- }
- // Mark all keys currently in the keypool as pre-split
- if (split_upgrade) {
- walletInstance->MarkPreSplitKeys();
- }
- // Regenerate the keypool if upgraded to HD
- if (hd_upgrade) {
- if (!walletInstance->m_spk_man->TopUpKeyPool()) {
- error = _("Unable to generate keys").translated;
+ if (auto spk_man = walletInstance->m_spk_man.get()) {
+ std::string error;
+ if (!spk_man->Upgrade(prev_version, error)) {
+ chain.initError(error);
return nullptr;
}
}
@@ -3637,15 +3644,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
walletInstance->SetWalletFlags(wallet_creation_flags, false);
if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
- // generate a new seed
- CPubKey seed = walletInstance->m_spk_man->GenerateNewSeed();
- walletInstance->m_spk_man->SetHDSeed(seed);
- }
-
- // Top up the keypool
- if (walletInstance->m_spk_man->CanGenerateKeys() && !walletInstance->m_spk_man->TopUpKeyPool()) {
- error = _("Unable to generate initial keys").translated;
- return nullptr;
+ if (auto spk_man = walletInstance->m_spk_man.get()) {
+ if (!spk_man->SetupGeneration()) {
+ error = _("Unable to generate initial keys").translated;
+ return nullptr;
+ }
+ }
}
auto locked_chain = chain.lock();
@@ -3655,9 +3659,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
error = strprintf(_("Error loading %s: Private keys can only be disabled during creation").translated, walletFile);
return NULL;
} else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
- LOCK(walletInstance->cs_KeyStore);
- if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) {
- warnings.push_back(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys").translated, walletFile));
+ if (walletInstance->m_spk_man) {
+ if (walletInstance->m_spk_man->HavePrivateKeys()) {
+ warnings.push_back(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys").translated, walletFile));
+ }
}
}
@@ -3807,8 +3812,13 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
// No need to read and scan block if block was created before
// our wallet birthday (as adjusted for block time variability)
- if (walletInstance->nTimeFirstKey) {
- if (Optional<int> first_block = locked_chain->findFirstBlockWithTimeAndHeight(walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW, rescan_height, nullptr)) {
+ Optional<int64_t> time_first_key;
+ if (auto spk_man = walletInstance->m_spk_man.get()) {
+ int64_t time = spk_man->GetTimeFirstKey();
+ if (!time_first_key || time < *time_first_key) time_first_key = time;
+ }
+ if (time_first_key) {
+ if (Optional<int> first_block = locked_chain->findFirstBlockWithTimeAndHeight(*time_first_key - TIMESTAMP_WINDOW, rescan_height, nullptr)) {
rescan_height = *first_block;
}
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index f3b791441c..6c9b3f40ab 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -476,6 +476,7 @@ public:
bool InMempool() const;
bool IsTrusted(interfaces::Chain::Lock& locked_chain) const;
+ bool IsTrusted(interfaces::Chain::Lock& locked_chain, std::set<uint256>& trusted_parents) const;
int64_t GetTxTime() const;
@@ -660,7 +661,10 @@ private:
bool SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::string& strPurpose);
//! Unsets a wallet flag and saves it to disk
- void UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag) override;
+ void UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag);
+
+ //! Unset the blank wallet flag and saves it to disk
+ void UnsetBlankWalletFlag(WalletBatch& batch) override;
/** Interface for accessing chain state. */
interfaces::Chain* m_chain;
@@ -989,11 +993,7 @@ public:
bool DelAddressBook(const CTxDestination& address);
- unsigned int GetKeyPoolSize() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
- {
- AssertLockHeld(cs_wallet);
- return setInternalKeyPool.size() + setExternalKeyPool.size();
- }
+ unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower
void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr, bool fExplicit = false) override;
@@ -1090,7 +1090,7 @@ public:
void BlockUntilSyncedToCurrentChain() LOCKS_EXCLUDED(cs_main, cs_wallet);
/** set a single wallet flag */
- void SetWalletFlag(uint64_t flags) override;
+ void SetWalletFlag(uint64_t flags);
/** Unsets a single wallet flag */
void UnsetWalletFlag(uint64_t flag);
@@ -1128,13 +1128,6 @@ public:
LegacyScriptPubKeyMan::WatchOnlySet& setWatchOnly GUARDED_BY(cs_KeyStore) = m_spk_man->setWatchOnly;
LegacyScriptPubKeyMan::WatchKeyMap& mapWatchKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapWatchKeys;
WalletBatch*& encrypted_batch GUARDED_BY(cs_wallet) = m_spk_man->encrypted_batch;
- std::set<int64_t>& setInternalKeyPool GUARDED_BY(cs_wallet) = m_spk_man->setInternalKeyPool;
- std::set<int64_t>& setExternalKeyPool GUARDED_BY(cs_wallet) = m_spk_man->setExternalKeyPool;
- int64_t& nTimeFirstKey GUARDED_BY(cs_wallet) = m_spk_man->nTimeFirstKey;
- std::map<CKeyID, CKeyMetadata>& mapKeyMetadata GUARDED_BY(cs_wallet) = m_spk_man->mapKeyMetadata;
- std::map<CScriptID, CKeyMetadata>& m_script_metadata GUARDED_BY(cs_wallet) = m_spk_man->m_script_metadata;
- void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(m_spk_man->cs_wallet); m_spk_man->MarkPreSplitKeys(); }
- void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(m_spk_man->cs_wallet); m_spk_man->MarkReserveKeysAsUsed(keypool_id); }
using CryptedKeyMap = LegacyScriptPubKeyMan::CryptedKeyMap;
};
diff --git a/test/functional/README.md b/test/functional/README.md
index a9b83076eb..77a9ce9acb 100644
--- a/test/functional/README.md
+++ b/test/functional/README.md
@@ -99,6 +99,16 @@ P2PInterface object and override the callback methods.
Examples tests are [p2p_unrequested_blocks.py](p2p_unrequested_blocks.py),
[p2p_compactblocks.py](p2p_compactblocks.py).
+#### Prototyping tests
+
+The [`TestShell`](test-shell.md) class exposes the BitcoinTestFramework
+functionality to interactive Python3 environments and can be used to prototype
+tests. This may be especially useful in a REPL environment with session logging
+utilities, such as
+[IPython](https://ipython.readthedocs.io/en/stable/interactive/reference.html#session-logging-and-restoring).
+The logs of such interactive sessions can later be adapted into permanent test
+cases.
+
### Test framework modules
The following are useful modules for test developers. They are located in
[test/functional/test_framework/](test_framework).
diff --git a/test/functional/test-shell.md b/test/functional/test-shell.md
new file mode 100644
index 0000000000..f6ea9ef682
--- /dev/null
+++ b/test/functional/test-shell.md
@@ -0,0 +1,186 @@
+Test Shell for Interactive Environments
+=========================================
+
+This document describes how to use the `TestShell` submodule in the functional
+test suite.
+
+The `TestShell` submodule extends the `BitcoinTestFramework` functionality to
+external interactive environments for prototyping and educational purposes. Just
+like `BitcoinTestFramework`, the `TestShell` allows the user to:
+
+* Manage regtest bitcoind subprocesses.
+* Access RPC interfaces of the underlying bitcoind instances.
+* Log events to the functional test logging utility.
+
+The `TestShell` can be useful in interactive environments where it is necessary
+to extend the object lifetime of the underlying `BitcoinTestFramework` between
+user inputs. Such environments include the Python3 command line interpreter or
+[Jupyter](https://jupyter.org/) notebooks running a Python3 kernel.
+
+## 1. Requirements
+
+* Python3
+* `bitcoind` built in the same repository as the `TestShell`.
+
+## 2. Importing `TestShell` from the Bitcoin Core repository
+
+We can import the `TestShell` by adding the path of the Bitcoin Core
+`test_framework` module to the beginning of the PATH variable, and then
+importing the `TestShell` class from the `test_shell` sub-package.
+
+```
+>>> import sys
+>>> sys.path.insert(0, "/path/to/bitcoin/test/functional")
+>>> from test_framework.test_shell import TestShell
+```
+
+The following `TestShell` methods manage the lifetime of the underlying bitcoind
+processes and logging utilities.
+
+* `TestShell.setup()`
+* `TestShell.shutdown()`
+
+The `TestShell` inherits all `BitcoinTestFramework` members and methods, such
+as:
+* `TestShell.nodes[index].rpc_method()`
+* `TestShell.log.info("Custom log message")`
+
+The following sections demonstrate how to initialize, run, and shut down a
+`TestShell` object.
+
+## 3. Initializing a `TestShell` object
+
+```
+>>> test = TestShell().setup(num_nodes=2, setup_clean_chain=True)
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Initializing test directory /path/to/bitcoin_func_test_XXXXXXX
+```
+The `TestShell` forwards all functional test parameters of the parent
+`BitcoinTestFramework` object. The full set of argument keywords which can be
+used to initialize the `TestShell` can be found in [section
+#6](#custom-testshell-parameters) of this document.
+
+**Note: Running multiple instances of `TestShell` is not allowed.** Running a
+single process also ensures that logging remains consolidated in the same
+temporary folder. If you need more bitcoind nodes than set by default (1),
+simply increase the `num_nodes` parameter during setup.
+
+```
+>>> test2 = TestShell().setup()
+TestShell is already running!
+```
+
+## 4. Interacting with the `TestShell`
+
+Unlike the `BitcoinTestFramework` class, the `TestShell` keeps the underlying
+Bitcoind subprocesses (nodes) and logging utilities running until the user
+explicitly shuts down the `TestShell` object.
+
+During the time between the `setup` and `shutdown` calls, all `bitcoind` node
+processes and `BitcoinTestFramework` convenience methods can be accessed
+interactively.
+
+**Example: Mining a regtest chain**
+
+By default, the `TestShell` nodes are initialized with a clean chain. This means
+that each node of the `TestShell` is initialized with a block height of 0.
+
+```
+>>> test.nodes[0].getblockchaininfo()["blocks"]
+0
+```
+
+We now let the first node generate 101 regtest blocks, and direct the coinbase
+rewards to a wallet address owned by the mining node.
+
+```
+>>> address = test.nodes[0].getnewaddress()
+>>> test.nodes[0].generatetoaddress(101, address)
+['2b98dd0044aae6f1cca7f88a0acf366a4bfe053c7f7b00da3c0d115f03d67efb', ...
+```
+Since the two nodes are both initialized by default to establish an outbound
+connection to each other during `setup`, the second node's chain will include
+the mined blocks as soon as they propagate.
+
+```
+>>> test.nodes[1].getblockchaininfo()["blocks"]
+101
+```
+The block rewards from the first block are now spendable by the wallet of the
+first node.
+
+```
+>>> test.nodes[0].getbalance()
+Decimal('50.00000000')
+```
+
+We can also log custom events to the logger.
+
+```
+>>> test.nodes[0].log.info("Successfully mined regtest chain!")
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework.node0 (INFO): Successfully mined regtest chain!
+```
+
+**Note: Please also consider the functional test
+[readme](../test/functional/README.md), which provides an overview of the
+test-framework**. Modules such as
+[key.py](../test/functional/test_framework/key.py),
+[script.py](../test/functional/test_framework/script.py) and
+[messages.py](../test/functional/test_framework/messages.py) are particularly
+useful in constructing objects which can be passed to the bitcoind nodes managed
+by a running `TestShell` object.
+
+## 5. Shutting the `TestShell` down
+
+Shutting down the `TestShell` will safely tear down all running bitcoind
+instances and remove all temporary data and logging directories.
+
+```
+>>> test.shutdown()
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Stopping nodes
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Cleaning up /path/to/bitcoin_func_test_XXXXXXX on exit
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Tests successful
+```
+To prevent the logs from being removed after a shutdown, simply set the
+`TestShell.options.nocleanup` member to `True`.
+```
+>>> test.options.nocleanup = True
+>>> test.shutdown()
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Stopping nodes
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Not cleaning up dir /path/to/bitcoin_func_test_XXXXXXX on exit
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Tests successful
+```
+
+The following utility consolidates logs from the bitcoind nodes and the
+underlying `BitcoinTestFramework`:
+
+* `/path/to/bitcoin/test/functional/combine_logs.py
+ '/path/to/bitcoin_func_test_XXXXXXX'`
+
+## 6. Custom `TestShell` parameters
+
+The `TestShell` object initializes with the default settings inherited from the
+`BitcoinTestFramework` class. The user can override these in
+`TestShell.setup(key=value)`.
+
+**Note:** `TestShell.reset()` will reset test parameters to default values and
+can be called after the TestShell is shut down.
+
+| Test parameter key | Default Value | Description |
+|---|---|---|
+| `bind_to_localhost_only` | `True` | Binds bitcoind RPC services to `127.0.0.1` if set to `True`.|
+| `cachedir` | `"/path/to/bitcoin/test/cache"` | Sets the bitcoind datadir directory. |
+| `chain` | `"regtest"` | Sets the chain-type for the underlying test bitcoind processes. |
+| `configfile` | `"/path/to/bitcoin/test/config.ini"` | Sets the location of the test framework config file. |
+| `coveragedir` | `None` | Records bitcoind RPC test coverage into this directory if set. |
+| `loglevel` | `INFO` | Logs events at this level and higher. Can be set to `DEBUG`, `INFO`, `WARNING`, `ERROR` or `CRITICAL`. |
+| `nocleanup` | `False` | Cleans up temporary test directory if set to `True` during `shutdown`. |
+| `noshutdown` | `False` | Does not stop bitcoind instances after `shutdown` if set to `True`. |
+| `num_nodes` | `1` | Sets the number of initialized bitcoind processes. |
+| `perf` | False | Profiles running nodes with `perf` for the duration of the test if set to `True`. |
+| `rpc_timeout` | `60` | Sets the RPC server timeout for the underlying bitcoind processes. |
+| `setup_clean_chain` | `False` | Initializes an empty blockchain by default. A 199-block-long chain is initialized if set to `True`. |
+| `randomseed` | Random Integer | `TestShell.options.randomseed` is a member of `TestShell` which can be accessed during a test to seed a random generator. User can override default with a constant value for reproducible test runs. |
+| `supports_cli` | `False` | Whether the bitcoin-cli utility is compiled and available for the test. |
+| `tmpdir` | `"/var/folders/.../"` | Sets directory for test logs. Will be deleted upon a successful test run unless `nocleanup` is set to `True` |
+| `trace_rpc` | `False` | Logs all RPC calls if set to `True`. |
+| `usecli` | `False` | Uses the bitcoin-cli interface for all bitcoind commands instead of directly calling the RPC server. Requires `supports_cli`. |
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index f95c158a68..a9e669fea9 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -478,7 +478,8 @@ class NetworkThread(threading.Thread):
wait_until(lambda: not self.network_event_loop.is_running(), timeout=timeout)
self.network_event_loop.close()
self.join(timeout)
-
+ # Safe to remove event loop.
+ NetworkThread.network_event_loop = None
class P2PDataStore(P2PInterface):
"""A P2P data store class.
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 780aa5fe03..c56c0d06ff 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -99,12 +99,39 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.supports_cli = False
self.bind_to_localhost_only = True
self.set_test_params()
-
- assert hasattr(self, "num_nodes"), "Test must set self.num_nodes in set_test_params()"
+ self.parse_args()
def main(self):
"""Main function. This should not be overridden by the subclass test scripts."""
+ assert hasattr(self, "num_nodes"), "Test must set self.num_nodes in set_test_params()"
+
+ try:
+ self.setup()
+ self.run_test()
+ except JSONRPCException:
+ self.log.exception("JSONRPC error")
+ self.success = TestStatus.FAILED
+ except SkipTest as e:
+ self.log.warning("Test Skipped: %s" % e.message)
+ self.success = TestStatus.SKIPPED
+ except AssertionError:
+ self.log.exception("Assertion failed")
+ self.success = TestStatus.FAILED
+ except KeyError:
+ self.log.exception("Key error")
+ self.success = TestStatus.FAILED
+ except Exception:
+ self.log.exception("Unexpected exception caught during testing")
+ self.success = TestStatus.FAILED
+ except KeyboardInterrupt:
+ self.log.warning("Exiting after keyboard interrupt")
+ self.success = TestStatus.FAILED
+ finally:
+ exit_code = self.shutdown()
+ sys.exit(exit_code)
+
+ def parse_args(self):
parser = argparse.ArgumentParser(usage="%(prog)s [options]")
parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true",
help="Leave bitcoinds and test.* datadir on exit or error")
@@ -135,6 +162,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.add_options(parser)
self.options = parser.parse_args()
+ def setup(self):
+ """Call this method to start up the test framework object with options set."""
+
PortSeed.n = self.options.port_seed
check_json_precision()
@@ -181,33 +211,20 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.network_thread = NetworkThread()
self.network_thread.start()
- success = TestStatus.FAILED
+ if self.options.usecli:
+ if not self.supports_cli:
+ raise SkipTest("--usecli specified but test does not support using CLI")
+ self.skip_if_no_cli()
+ self.skip_test_if_missing_module()
+ self.setup_chain()
+ self.setup_network()
- try:
- if self.options.usecli:
- if not self.supports_cli:
- raise SkipTest("--usecli specified but test does not support using CLI")
- self.skip_if_no_cli()
- self.skip_test_if_missing_module()
- self.setup_chain()
- self.setup_network()
- self.run_test()
- success = TestStatus.PASSED
- except JSONRPCException:
- self.log.exception("JSONRPC error")
- except SkipTest as e:
- self.log.warning("Test Skipped: %s" % e.message)
- success = TestStatus.SKIPPED
- except AssertionError:
- self.log.exception("Assertion failed")
- except KeyError:
- self.log.exception("Key error")
- except Exception:
- self.log.exception("Unexpected exception caught during testing")
- except KeyboardInterrupt:
- self.log.warning("Exiting after keyboard interrupt")
+ self.success = TestStatus.PASSED
- if success == TestStatus.FAILED and self.options.pdbonfailure:
+ def shutdown(self):
+ """Call this method to shut down the test framework object."""
+
+ if self.success == TestStatus.FAILED and self.options.pdbonfailure:
print("Testcase failed. Attaching python debugger. Enter ? for help")
pdb.set_trace()
@@ -225,7 +242,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
should_clean_up = (
not self.options.nocleanup and
not self.options.noshutdown and
- success != TestStatus.FAILED and
+ self.success != TestStatus.FAILED and
not self.options.perf
)
if should_clean_up:
@@ -238,20 +255,33 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.log.warning("Not cleaning up dir {}".format(self.options.tmpdir))
cleanup_tree_on_exit = False
- if success == TestStatus.PASSED:
+ if self.success == TestStatus.PASSED:
self.log.info("Tests successful")
exit_code = TEST_EXIT_PASSED
- elif success == TestStatus.SKIPPED:
+ elif self.success == TestStatus.SKIPPED:
self.log.info("Test skipped")
exit_code = TEST_EXIT_SKIPPED
else:
self.log.error("Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir)
self.log.error("Hint: Call {} '{}' to consolidate all logs".format(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../combine_logs.py"), self.options.tmpdir))
exit_code = TEST_EXIT_FAILED
- logging.shutdown()
+ # Logging.shutdown will not remove stream- and filehandlers, so we must
+ # do it explicitly. Handlers are removed so the next test run can apply
+ # different log handler settings.
+ # See: https://docs.python.org/3/library/logging.html#logging.shutdown
+ for h in list(self.log.handlers):
+ h.flush()
+ h.close()
+ self.log.removeHandler(h)
+ rpc_logger = logging.getLogger("BitcoinRPC")
+ for h in list(rpc_logger.handlers):
+ h.flush()
+ rpc_logger.removeHandler(h)
if cleanup_tree_on_exit:
shutil.rmtree(self.options.tmpdir)
- sys.exit(exit_code)
+
+ self.nodes.clear()
+ return exit_code
# Methods to override in subclass test scripts.
def set_test_params(self):
diff --git a/test/functional/test_framework/test_shell.py b/test/functional/test_framework/test_shell.py
new file mode 100644
index 0000000000..26df128f1f
--- /dev/null
+++ b/test/functional/test_framework/test_shell.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import BitcoinTestFramework
+
+class TestShell:
+ """Wrapper Class for BitcoinTestFramework.
+
+ The TestShell class extends the BitcoinTestFramework
+ rpc & daemon process management functionality to external
+ python environments.
+
+ It is a singleton class, which ensures that users only
+ start a single TestShell at a time."""
+
+ class __TestShell(BitcoinTestFramework):
+ def set_test_params(self):
+ pass
+
+ def run_test(self):
+ pass
+
+ def setup(self, **kwargs):
+ if self.running:
+ print("TestShell is already running!")
+ return
+
+ # Num_nodes parameter must be set
+ # by BitcoinTestFramework child class.
+ self.num_nodes = 1
+
+ # User parameters override default values.
+ for key, value in kwargs.items():
+ if hasattr(self, key):
+ setattr(self, key, value)
+ elif hasattr(self.options, key):
+ setattr(self.options, key, value)
+ else:
+ raise KeyError(key + " not a valid parameter key!")
+
+ super().setup()
+ self.running = True
+ return self
+
+ def shutdown(self):
+ if not self.running:
+ print("TestShell is not running!")
+ else:
+ super().shutdown()
+ self.running = False
+
+ def reset(self):
+ if self.running:
+ print("Shutdown TestShell before resetting!")
+ else:
+ self.num_nodes = None
+ super().__init__()
+
+ instance = None
+
+ def __new__(cls):
+ # This implementation enforces singleton pattern, and will return the
+ # previously initialized instance if available
+ if not TestShell.instance:
+ TestShell.instance = TestShell.__TestShell()
+ TestShell.instance.running = False
+ return TestShell.instance
+
+ def __getattr__(self, name):
+ return getattr(self.instance, name)
+
+ def __setattr__(self, name, value):
+ return setattr(self.instance, name, value)
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index c50dcd987a..a5f9a047ed 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -109,13 +109,51 @@ class WalletTest(BitcoinTestFramework):
self.log.info("Test getbalance and getunconfirmedbalance with unconfirmed inputs")
+ # Before `test_balance()`, we have had two nodes with a balance of 50
+ # each and then we:
+ #
+ # 1) Sent 40 from node A to node B with fee 0.01
+ # 2) Sent 60 from node B to node A with fee 0.01
+ #
+ # Then we check the balances:
+ #
+ # 1) As is
+ # 2) With transaction 2 from above with 2x the fee
+ #
+ # Prior to #16766, in this situation, the node would immediately report
+ # a balance of 30 on node B as unconfirmed and trusted.
+ #
+ # After #16766, we show that balance as unconfirmed.
+ #
+ # The balance is indeed "trusted" and "confirmed" insofar as removing
+ # the mempool transactions would return at least that much money. But
+ # the algorithm after #16766 marks it as unconfirmed because the 'taint'
+ # tracking of transaction trust for summing balances doesn't consider
+ # which inputs belong to a user. In this case, the change output in
+ # question could be "destroyed" by replace the 1st transaction above.
+ #
+ # The post #16766 behavior is correct; we shouldn't be treating those
+ # funds as confirmed. If you want to rely on that specific UTXO existing
+ # which has given you that balance, you cannot, as a third party
+ # spending the other input would destroy that unconfirmed.
+ #
+ # For example, if the test transactions were:
+ #
+ # 1) Sent 40 from node A to node B with fee 0.01
+ # 2) Sent 10 from node B to node A with fee 0.01
+ #
+ # Then our node would report a confirmed balance of 40 + 50 - 10 = 80
+ # BTC, which is more than would be available if transaction 1 were
+ # replaced.
+
+
def test_balances(*, fee_node_1=0):
# getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send
- assert_equal(self.nodes[1].getbalance(), Decimal('30') - fee_node_1) # change from node 1's send
+ assert_equal(self.nodes[1].getbalance(), Decimal('0')) # node 1's send had an unsafe input
# Same with minconf=0
assert_equal(self.nodes[0].getbalance(minconf=0), Decimal('9.99'))
- assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('30') - fee_node_1)
+ assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('0'))
# getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago
# TODO: fix getbalance tracking of coin spentness depth
assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0'))
@@ -125,9 +163,9 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('60'))
assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60'))
- assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent
- assert_equal(self.nodes[1].getbalances()['mine']['untrusted_pending'], Decimal('0'))
- assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0'))
+ assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('30') - fee_node_1) # Doesn't include output of node 0's send since it was spent
+ assert_equal(self.nodes[1].getbalances()['mine']['untrusted_pending'], Decimal('30') - fee_node_1)
+ assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('30') - fee_node_1)
test_balances(fee_node_1=Decimal('0.01'))
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 0948d47653..95d51adebb 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -38,7 +38,7 @@ class BumpFeeTest(BitcoinTestFramework):
"-walletrbf={}".format(i),
"-mintxfee=0.00002",
"-deprecatedrpc=totalFee",
- "-addresstype=p2sh-segwit", # TODO update constants in test and remove
+ "-addresstype=bech32",
] for i in range(self.num_nodes)]
def skip_test_if_missing_module(self):
@@ -246,10 +246,8 @@ def test_dust_to_fee(rbf_node, dest_address):
# the bumped tx sets fee=49,900, but it converts to 50,000
rbfid = spend_one_input(rbf_node, dest_address)
fulltx = rbf_node.getrawtransaction(rbfid, 1)
- # (32-byte p2sh-pwpkh output size + 148 p2pkh spend estimate) * 10k(discard_rate) / 1000 = 1800
- # P2SH outputs are slightly "over-discarding" due to the IsDust calculation assuming it will
- # be spent as a P2PKH.
- bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 50000 - 1800})
+ # (31-vbyte p2wpkh output size + 67-vbyte p2wpkh spend estimate) * 10k(discard_rate) / 1000 = 980
+ bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 50000 - 980})
full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1)
assert_equal(bumped_tx["fee"], Decimal("0.00050000"))
assert_equal(len(fulltx["vout"]), 2)
@@ -272,7 +270,9 @@ def test_settxfee(rbf_node, dest_address):
def test_maxtxfee_fails(test, rbf_node, dest_address):
- test.restart_node(1, ['-maxtxfee=0.00003'] + test.extra_args[1])
+ # size of bumped transaction (p2wpkh, 1 input, 2 outputs): 141 vbytes
+ # expected bumping feerate of 20 sats/vbyte => 141*20 sats = 0.00002820 btc
+ test.restart_node(1, ['-maxtxfee=0.000025'] + test.extra_args[1])
rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
rbfid = spend_one_input(rbf_node, dest_address)
assert_raises_rpc_error(-4, "Unable to create transaction: Fee exceeds maximum configured by -maxtxfee", rbf_node.bumpfee, rbfid)
diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py
index 4aeb393255..455e89e310 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -5,6 +5,7 @@
"""Test the listsincelast RPC."""
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.messages import BIP125_SEQUENCE_NUMBER
from test_framework.util import (
assert_array_result,
assert_equal,
@@ -12,6 +13,7 @@ from test_framework.util import (
connect_nodes,
)
+from decimal import Decimal
class ListSinceBlockTest(BitcoinTestFramework):
def set_test_params(self):
@@ -33,6 +35,7 @@ class ListSinceBlockTest(BitcoinTestFramework):
self.test_reorg()
self.test_double_spend()
self.test_double_send()
+ self.double_spends_filtered()
def test_no_blockhash(self):
txid = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1)
@@ -291,5 +294,51 @@ class ListSinceBlockTest(BitcoinTestFramework):
if tx['txid'] == txid1:
assert_equal(tx['confirmations'], 2)
+ def double_spends_filtered(self):
+ '''
+ `listsinceblock` was returning conflicted transactions even if they
+ occurred before the specified cutoff blockhash
+ '''
+ spending_node = self.nodes[2]
+ dest_address = spending_node.getnewaddress()
+
+ tx_input = dict(
+ sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in spending_node.listunspent()))
+ rawtx = spending_node.createrawtransaction(
+ [tx_input], {dest_address: tx_input["amount"] - Decimal("0.00051000"),
+ spending_node.getrawchangeaddress(): Decimal("0.00050000")})
+ signedtx = spending_node.signrawtransactionwithwallet(rawtx)
+ orig_tx_id = spending_node.sendrawtransaction(signedtx["hex"])
+ original_tx = spending_node.gettransaction(orig_tx_id)
+
+ double_tx = spending_node.bumpfee(orig_tx_id)
+
+ # check that both transactions exist
+ block_hash = spending_node.listsinceblock(
+ spending_node.getblockhash(spending_node.getblockcount()))
+ original_found = False
+ double_found = False
+ for tx in block_hash['transactions']:
+ if tx['txid'] == original_tx['txid']:
+ original_found = True
+ if tx['txid'] == double_tx['txid']:
+ double_found = True
+ assert_equal(original_found, True)
+ assert_equal(double_found, True)
+
+ lastblockhash = spending_node.generate(1)[0]
+
+ # check that neither transaction exists
+ block_hash = spending_node.listsinceblock(lastblockhash)
+ original_found = False
+ double_found = False
+ for tx in block_hash['transactions']:
+ if tx['txid'] == original_tx['txid']:
+ original_found = True
+ if tx['txid'] == double_tx['txid']:
+ double_found = True
+ assert_equal(original_found, False)
+ assert_equal(double_found, False)
+
if __name__ == '__main__':
ListSinceBlockTest().main()
diff --git a/test/lint/lint-assertions.sh b/test/lint/lint-assertions.sh
index 5bbcae79eb..a4c6f0a8d4 100755
--- a/test/lint/lint-assertions.sh
+++ b/test/lint/lint-assertions.sh
@@ -20,4 +20,15 @@ if [[ ${OUTPUT} != "" ]]; then
EXIT_CODE=1
fi
+# Macro CHECK_NONFATAL(condition) should be used instead of assert for RPC code, where it
+# is undesirable to crash the whole program. See: src/util/check.h
+# src/rpc/server.cpp is excluded from this check since it's mostly meta-code.
+OUTPUT=$(git grep -nE 'assert *\(.*\);' -- "src/rpc/" "src/wallet/rpc*" ":(exclude)src/rpc/server.cpp")
+if [[ ${OUTPUT} != "" ]]; then
+ echo "CHECK_NONFATAL(condition) should be used instead of assert for RPC code."
+ echo
+ echo "${OUTPUT}"
+ EXIT_CODE=1
+fi
+
exit ${EXIT_CODE}