aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xci/lint/04_install.sh8
-rwxr-xr-xci/test/00_setup_env_native_asan.sh2
-rwxr-xr-xci/test/00_setup_env_native_tidy.sh2
-rwxr-xr-xci/test/00_setup_env_native_tsan.sh2
-rwxr-xr-xcontrib/install_db4.sh15
-rwxr-xr-xcontrib/linearize/linearize-hashes.py2
-rwxr-xr-xcontrib/seeds/generate-seeds.py4
-rw-r--r--src/Makefile.test.include1
-rw-r--r--src/node/interfaces.cpp5
-rw-r--r--src/node/miner.cpp4
-rw-r--r--src/policy/rbf.cpp8
-rw-r--r--src/qt/walletmodel.cpp7
-rw-r--r--src/rpc/mempool.cpp10
-rw-r--r--src/test/mempool_tests.cpp13
-rw-r--r--src/test/txvalidationcache_tests.cpp8
-rw-r--r--src/txmempool.cpp124
-rw-r--r--src/txmempool.h65
-rw-r--r--src/validation.cpp40
-rw-r--r--src/wallet/coinselection.cpp16
-rw-r--r--src/wallet/coinselection.h14
-rw-r--r--src/wallet/rpc/spend.cpp18
-rw-r--r--src/wallet/rpc/transactions.cpp1
-rw-r--r--src/wallet/rpc/wallet.cpp8
-rw-r--r--src/wallet/spend.cpp133
-rw-r--r--src/wallet/spend.h26
-rw-r--r--src/wallet/test/availablecoins_tests.cpp107
-rw-r--r--src/wallet/test/coinselector_tests.cpp2
-rw-r--r--src/wallet/test/wallet_tests.cpp40
-rw-r--r--src/wallet/walletdb.cpp11
-rw-r--r--src/wallet/walletdb.h2
-rwxr-xr-xtest/functional/feature_coinstatsindex.py6
-rwxr-xr-xtest/functional/feature_index_prune.py6
-rwxr-xr-xtest/functional/interface_usdt_utxocache.py4
-rwxr-xr-xtest/functional/interface_usdt_validation.py2
-rwxr-xr-xtest/functional/mocks/invalid_signer.py2
-rwxr-xr-xtest/functional/mocks/signer.py2
-rwxr-xr-xtest/functional/p2p_addr_relay.py2
-rwxr-xr-xtest/functional/p2p_blocksonly.py2
-rwxr-xr-xtest/functional/p2p_getaddr_caching.py14
-rwxr-xr-xtest/functional/p2p_headers_sync_with_minchainwork.py6
-rwxr-xr-xtest/functional/p2p_message_capture.py4
-rwxr-xr-xtest/functional/rpc_blockchain.py2
-rwxr-xr-xtest/functional/rpc_psbt.py4
-rwxr-xr-xtest/functional/rpc_rawtransaction.py14
-rwxr-xr-xtest/functional/rpc_scanblocks.py30
-rw-r--r--test/functional/test_framework/key.py14
-rw-r--r--test/functional/test_framework/script.py4
-rw-r--r--test/functional/test_framework/siphash.py4
-rwxr-xr-xtest/functional/wallet_avoidreuse.py2
-rwxr-xr-xtest/functional/wallet_backwards_compatibility.py8
-rwxr-xr-xtest/functional/wallet_bumpfee.py2
-rwxr-xr-xtest/functional/wallet_fundrawtransaction.py20
-rwxr-xr-xtest/functional/wallet_implicitsegwit.py2
-rwxr-xr-xtest/functional/wallet_importdescriptors.py4
-rwxr-xr-xtest/functional/wallet_multiwallet.py8
-rwxr-xr-xtest/functional/wallet_reorgsrestore.py4
-rwxr-xr-xtest/functional/wallet_send.py10
-rwxr-xr-xtest/functional/wallet_signer.py8
-rwxr-xr-xtest/functional/wallet_taproot.py28
-rw-r--r--test/sanitizer_suppressions/tsan3
60 files changed, 451 insertions, 468 deletions
diff --git a/ci/lint/04_install.sh b/ci/lint/04_install.sh
index 9ccb1d53d1..b459b321f1 100755
--- a/ci/lint/04_install.sh
+++ b/ci/lint/04_install.sh
@@ -17,10 +17,10 @@ ${CI_RETRY_EXE} apt-get install -y python3-pip curl git gawk jq
)
${CI_RETRY_EXE} pip3 install codespell==2.2.1
-${CI_RETRY_EXE} pip3 install flake8==4.0.1
-${CI_RETRY_EXE} pip3 install mypy==0.942
-${CI_RETRY_EXE} pip3 install pyzmq==22.3.0
-${CI_RETRY_EXE} pip3 install vulture==2.3
+${CI_RETRY_EXE} pip3 install flake8==5.0.4
+${CI_RETRY_EXE} pip3 install mypy==0.971
+${CI_RETRY_EXE} pip3 install pyzmq==24.0.1
+${CI_RETRY_EXE} pip3 install vulture==2.6
SHELLCHECK_VERSION=v0.8.0
curl -sL "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar --xz -xf - --directory /tmp/
diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh
index 7f3421269c..66bbdb8cb5 100755
--- a/ci/test/00_setup_env_native_asan.sh
+++ b/ci/test/00_setup_env_native_asan.sh
@@ -23,4 +23,4 @@ export PACKAGES="systemtap-sdt-dev clang llvm python3-zmq qtbase5-dev qttools5-d
export DOCKER_NAME_TAG=ubuntu:22.04
export NO_DEPENDS=1
export GOAL="install"
-export BITCOIN_CONFIG="--enable-usdt --enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++"
+export BITCOIN_CONFIG="--enable-c++20 --enable-usdt --enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++"
diff --git a/ci/test/00_setup_env_native_tidy.sh b/ci/test/00_setup_env_native_tidy.sh
index 11a12e336a..e4d3468473 100755
--- a/ci/test/00_setup_env_native_tidy.sh
+++ b/ci/test/00_setup_env_native_tidy.sh
@@ -15,5 +15,5 @@ export RUN_FUNCTIONAL_TESTS=false
export RUN_FUZZ_TESTS=false
export RUN_TIDY=true
export GOAL="install"
-export BITCOIN_CONFIG="CC=clang CXX=clang++ --enable-c++20 --with-incompatible-bdb --disable-hardening CFLAGS='-O0 -g0' CXXFLAGS='-O0 -g0'"
+export BITCOIN_CONFIG="CC=clang CXX=clang++ --with-incompatible-bdb --disable-hardening CFLAGS='-O0 -g0' CXXFLAGS='-O0 -g0'"
export CCACHE_SIZE=200M
diff --git a/ci/test/00_setup_env_native_tsan.sh b/ci/test/00_setup_env_native_tsan.sh
index 66d18b974c..0e0a4b8f18 100755
--- a/ci/test/00_setup_env_native_tsan.sh
+++ b/ci/test/00_setup_env_native_tsan.sh
@@ -11,4 +11,4 @@ export DOCKER_NAME_TAG=ubuntu:22.04
export PACKAGES="clang-13 llvm-13 libc++abi-13-dev libc++-13-dev python3-zmq"
export DEP_OPTS="CC=clang-13 CXX='clang++-13 -stdlib=libc++'"
export GOAL="install"
-export BITCOIN_CONFIG="--enable-zmq CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER -DDEBUG_LOCKCONTENTION' CXXFLAGS='-g' --with-sanitizers=thread CC=clang-13 CXX='clang++-13 -stdlib=libc++'"
+export BITCOIN_CONFIG="--enable-zmq CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER -DDEBUG_LOCKCONTENTION' CXXFLAGS='-g' --with-sanitizers=thread"
diff --git a/contrib/install_db4.sh b/contrib/install_db4.sh
index 2850c4b993..c7d39f5b99 100755
--- a/contrib/install_db4.sh
+++ b/contrib/install_db4.sh
@@ -32,16 +32,15 @@ check_exists() {
sha256_check() {
# Args: <sha256_hash> <filename>
#
- if check_exists sha256sum; then
- echo "${1} ${2}" | sha256sum -c
+ if [ "$(uname)" = "FreeBSD" ]; then
+ # sha256sum exists on FreeBSD, but takes different arguments than the GNU version
+ sha256 -c "${1}" "${2}"
+ elif check_exists sha256sum; then
+ echo "${1} ${2}" | sha256sum -c
elif check_exists sha256; then
- if [ "$(uname)" = "FreeBSD" ]; then
- sha256 -c "${1}" "${2}"
- else
- echo "${1} ${2}" | sha256 -c
- fi
+ echo "${1} ${2}" | sha256 -c
else
- echo "${1} ${2}" | shasum -a 256 -c
+ echo "${1} ${2}" | shasum -a 256 -c
fi
}
diff --git a/contrib/linearize/linearize-hashes.py b/contrib/linearize/linearize-hashes.py
index 804ab34d65..695bafad34 100755
--- a/contrib/linearize/linearize-hashes.py
+++ b/contrib/linearize/linearize-hashes.py
@@ -78,7 +78,7 @@ def get_block_hashes(settings, max_blocks_per_call=10000):
if rpc.response_is_error(resp_obj):
print('JSON-RPC: error at height', height+x, ': ', resp_obj['error'], file=sys.stderr)
sys.exit(1)
- assert(resp_obj['id'] == x) # assume replies are in-sequence
+ assert resp_obj['id'] == x # assume replies are in-sequence
if settings['rev_hash_bytes'] == 'true':
resp_obj['result'] = bytes.fromhex(resp_obj['result'])[::-1].hex()
print(resp_obj['result'])
diff --git a/contrib/seeds/generate-seeds.py b/contrib/seeds/generate-seeds.py
index 44345e3987..a6f435af0d 100755
--- a/contrib/seeds/generate-seeds.py
+++ b/contrib/seeds/generate-seeds.py
@@ -70,13 +70,13 @@ def name_to_bip155(addr):
if i == 0 or i == (len(addr)-1): # skip empty component at beginning or end
continue
x += 1 # :: skips to suffix
- assert(x < 2)
+ assert x < 2
else: # two bytes per component
val = int(comp, 16)
sub[x].append(val >> 8)
sub[x].append(val & 0xff)
nullbytes = 16 - len(sub[0]) - len(sub[1])
- assert((x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0))
+ assert (x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0)
addr_bytes = bytes(sub[0] + ([0] * nullbytes) + sub[1])
if addr_bytes[0] == 0xfc:
# Assume that seeds with fc00::/8 addresses belong to CJDNS,
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index d34e6e0545..1a29e9a47a 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -174,7 +174,6 @@ BITCOIN_TESTS += \
wallet/test/wallet_crypto_tests.cpp \
wallet/test/wallet_transaction_tests.cpp \
wallet/test/coinselector_tests.cpp \
- wallet/test/availablecoins_tests.cpp \
wallet/test/init_tests.cpp \
wallet/test/ismine_tests.cpp \
wallet/test/rpc_util_tests.cpp \
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 7fd5ffd6a6..980506f8dc 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -672,12 +672,9 @@ public:
if (!m_node.mempool) return true;
LockPoints lp;
CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp);
- CTxMemPool::setEntries ancestors;
const CTxMemPool::Limits& limits{m_node.mempool->m_limits};
- std::string unused_error_string;
LOCK(m_node.mempool->cs);
- return m_node.mempool->CalculateMemPoolAncestors(
- entry, ancestors, limits, unused_error_string);
+ return m_node.mempool->CalculateMemPoolAncestors(entry, limits).has_value();
}
CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override
{
diff --git a/src/node/miner.cpp b/src/node/miner.cpp
index 7179d1a341..dc6849e0d2 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -394,9 +394,7 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele
continue;
}
- CTxMemPool::setEntries ancestors;
- std::string dummy;
- mempool.CalculateMemPoolAncestors(*iter, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false);
+ auto ancestors{mempool.AssumeCalculateMemPoolAncestors(__func__, *iter, CTxMemPool::Limits::NoLimits(), /*fSearchForParents=*/false)};
onlyUnconfirmed(ancestors);
ancestors.insert(iter);
diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp
index 10f85b9510..d032b74008 100644
--- a/src/policy/rbf.cpp
+++ b/src/policy/rbf.cpp
@@ -22,8 +22,6 @@ RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool)
{
AssertLockHeld(pool.cs);
- CTxMemPool::setEntries ancestors;
-
// First check the transaction itself.
if (SignalsOptInRBF(tx)) {
return RBFTransactionState::REPLACEABLE_BIP125;
@@ -37,9 +35,9 @@ RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool)
// If all the inputs have nSequence >= maxint-1, it still might be
// signaled for RBF if any unconfirmed parents have signaled.
- std::string dummy;
- CTxMemPoolEntry entry = *pool.mapTx.find(tx.GetHash());
- pool.CalculateMemPoolAncestors(entry, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false);
+ const CTxMemPoolEntry entry{*pool.mapTx.find(tx.GetHash())};
+ auto ancestors{pool.AssumeCalculateMemPoolAncestors(__func__, entry, CTxMemPool::Limits::NoLimits(),
+ /*fSearchForParents=*/false)};
for (CTxMemPool::txiter it : ancestors) {
if (SignalsOptInRBF(it->GetTx())) {
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index 1f36af31f4..097b2b0364 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -212,7 +212,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
return AmountExceedsBalance;
}
- {
+ try {
CAmount nFeeRequired = 0;
int nChangePosRet = -1;
@@ -240,6 +240,11 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) {
return AbsurdFee;
}
+ } catch (const std::runtime_error& err) {
+ // Something unexpected happened, instruct user to report this bug.
+ Q_EMIT message(tr("Send Coins"), QString::fromStdString(err.what()),
+ CClientUIInterface::MSG_ERROR);
+ return TransactionCreationFailed;
}
return SendCoinsReturn(OK);
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index 7a0c361ae0..58e71a0604 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -23,6 +23,8 @@
#include <util/moneystr.h>
#include <util/time.h>
+#include <utility>
+
using kernel::DumpMempool;
using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
@@ -449,19 +451,17 @@ static RPCHelpMan getmempoolancestors()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
}
- CTxMemPool::setEntries setAncestors;
- std::string dummy;
- mempool.CalculateMemPoolAncestors(*it, setAncestors, CTxMemPool::Limits::NoLimits(), dummy, false);
+ auto ancestors{mempool.AssumeCalculateMemPoolAncestors(__func__, *it, CTxMemPool::Limits::NoLimits(), /*fSearchForParents=*/false)};
if (!fVerbose) {
UniValue o(UniValue::VARR);
- for (CTxMemPool::txiter ancestorIt : setAncestors) {
+ for (CTxMemPool::txiter ancestorIt : ancestors) {
o.push_back(ancestorIt->GetTx().GetHash().ToString());
}
return o;
} else {
UniValue o(UniValue::VOBJ);
- for (CTxMemPool::txiter ancestorIt : setAncestors) {
+ for (CTxMemPool::txiter ancestorIt : ancestors) {
const CTxMemPoolEntry &e = *ancestorIt;
const uint256& _hash = e.GetTx().GetHash();
UniValue info(UniValue::VOBJ);
diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp
index 1a24448569..7e9079743e 100644
--- a/src/test/mempool_tests.cpp
+++ b/src/test/mempool_tests.cpp
@@ -202,10 +202,9 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
tx7.vout[1].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx7.vout[1].nValue = 1 * COIN;
- CTxMemPool::setEntries setAncestorsCalculated;
- std::string dummy;
- BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(2'000'000LL).FromTx(tx7), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true);
- BOOST_CHECK(setAncestorsCalculated == setAncestors);
+ auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), CTxMemPool::Limits::NoLimits())};
+ BOOST_REQUIRE(ancestors_calculated.has_value());
+ BOOST_CHECK(*ancestors_calculated == setAncestors);
pool.addUnchecked(entry.FromTx(tx7), setAncestors);
BOOST_CHECK_EQUAL(pool.size(), 7U);
@@ -261,9 +260,9 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
tx10.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx10.vout[0].nValue = 10 * COIN;
- setAncestorsCalculated.clear();
- BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200'000LL).Time(NodeSeconds{4s}).FromTx(tx10), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true);
- BOOST_CHECK(setAncestorsCalculated == setAncestors);
+ ancestors_calculated = pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(NodeSeconds{4s}).FromTx(tx10), CTxMemPool::Limits::NoLimits());
+ BOOST_REQUIRE(ancestors_calculated);
+ BOOST_CHECK(*ancestors_calculated == setAncestors);
pool.addUnchecked(entry.FromTx(tx10), setAncestors);
diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp
index 9a9ba8ea51..52c0b1d938 100644
--- a/src/test/txvalidationcache_tests.cpp
+++ b/src/test/txvalidationcache_tests.cpp
@@ -77,7 +77,9 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, Dersig100Setup)
LOCK(cs_main);
BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash());
}
- m_node.mempool->clear();
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), 1U);
+ WITH_LOCK(m_node.mempool->cs, m_node.mempool->removeRecursive(CTransaction{spends[0]}, MemPoolRemovalReason::CONFLICT));
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), 0U);
// Test 3: ... and should be rejected if spend2 is in the memory pool
BOOST_CHECK(ToMemPool(spends[1]));
@@ -86,7 +88,9 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, Dersig100Setup)
LOCK(cs_main);
BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash());
}
- m_node.mempool->clear();
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), 1U);
+ WITH_LOCK(m_node.mempool->cs, m_node.mempool->removeRecursive(CTransaction{spends[1]}, MemPoolRemovalReason::CONFLICT));
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), 0U);
// Final sanity test: first spend in *m_node.mempool, second in block, that's OK:
std::vector<CMutableTransaction> oneSpend;
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index 83020117a7..c2a8ed0e27 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -17,12 +17,16 @@
#include <util/check.h>
#include <util/moneystr.h>
#include <util/overflow.h>
+#include <util/result.h>
#include <util/system.h>
#include <util/time.h>
+#include <util/translation.h>
#include <validationinterface.h>
#include <cmath>
#include <optional>
+#include <string_view>
+#include <utility>
bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp)
{
@@ -147,32 +151,29 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256>& vHashes
}
}
-bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size,
- size_t entry_count,
- setEntries& setAncestors,
- CTxMemPoolEntry::Parents& staged_ancestors,
- const Limits& limits,
- std::string &errString) const
+util::Result<CTxMemPool::setEntries> CTxMemPool::CalculateAncestorsAndCheckLimits(
+ size_t entry_size,
+ size_t entry_count,
+ CTxMemPoolEntry::Parents& staged_ancestors,
+ const Limits& limits) const
{
size_t totalSizeWithAncestors = entry_size;
+ setEntries ancestors;
while (!staged_ancestors.empty()) {
const CTxMemPoolEntry& stage = staged_ancestors.begin()->get();
txiter stageit = mapTx.iterator_to(stage);
- setAncestors.insert(stageit);
+ ancestors.insert(stageit);
staged_ancestors.erase(stage);
totalSizeWithAncestors += stageit->GetTxSize();
if (stageit->GetSizeWithDescendants() + entry_size > static_cast<uint64_t>(limits.descendant_size_vbytes)) {
- errString = strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_size_vbytes);
- return false;
+ return util::Error{Untranslated(strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_size_vbytes))};
} else if (stageit->GetCountWithDescendants() + entry_count > static_cast<uint64_t>(limits.descendant_count)) {
- errString = strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_count);
- return false;
+ return util::Error{Untranslated(strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_count))};
} else if (totalSizeWithAncestors > static_cast<uint64_t>(limits.ancestor_size_vbytes)) {
- errString = strprintf("exceeds ancestor size limit [limit: %u]", limits.ancestor_size_vbytes);
- return false;
+ return util::Error{Untranslated(strprintf("exceeds ancestor size limit [limit: %u]", limits.ancestor_size_vbytes))};
}
const CTxMemPoolEntry::Parents& parents = stageit->GetMemPoolParentsConst();
@@ -180,17 +181,16 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size,
txiter parent_it = mapTx.iterator_to(parent);
// If this is a new ancestor, add it.
- if (setAncestors.count(parent_it) == 0) {
+ if (ancestors.count(parent_it) == 0) {
staged_ancestors.insert(parent);
}
- if (staged_ancestors.size() + setAncestors.size() + entry_count > static_cast<uint64_t>(limits.ancestor_count)) {
- errString = strprintf("too many unconfirmed ancestors [limit: %u]", limits.ancestor_count);
- return false;
+ if (staged_ancestors.size() + ancestors.size() + entry_count > static_cast<uint64_t>(limits.ancestor_count)) {
+ return util::Error{Untranslated(strprintf("too many unconfirmed ancestors [limit: %u]", limits.ancestor_count))};
}
}
}
- return true;
+ return ancestors;
}
bool CTxMemPool::CheckPackageLimits(const Package& package,
@@ -215,20 +215,17 @@ bool CTxMemPool::CheckPackageLimits(const Package& package,
// When multiple transactions are passed in, the ancestors and descendants of all transactions
// considered together must be within limits even if they are not interdependent. This may be
// stricter than the limits for each individual transaction.
- setEntries setAncestors;
- const auto ret = CalculateAncestorsAndCheckLimits(total_size, package.size(),
- setAncestors, staged_ancestors,
- limits, errString);
+ const auto ancestors{CalculateAncestorsAndCheckLimits(total_size, package.size(),
+ staged_ancestors, limits)};
// It's possible to overestimate the ancestor/descendant totals.
- if (!ret) errString.insert(0, "possibly ");
- return ret;
+ if (!ancestors.has_value()) errString = "possibly " + util::ErrorString(ancestors).original;
+ return ancestors.has_value();
}
-bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
- setEntries &setAncestors,
- const Limits& limits,
- std::string &errString,
- bool fSearchForParents /* = true */) const
+util::Result<CTxMemPool::setEntries> CTxMemPool::CalculateMemPoolAncestors(
+ const CTxMemPoolEntry &entry,
+ const Limits& limits,
+ bool fSearchForParents /* = true */) const
{
CTxMemPoolEntry::Parents staged_ancestors;
const CTransaction &tx = entry.GetTx();
@@ -242,8 +239,7 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
if (piter) {
staged_ancestors.insert(**piter);
if (staged_ancestors.size() + 1 > static_cast<uint64_t>(limits.ancestor_count)) {
- errString = strprintf("too many unconfirmed parents [limit: %u]", limits.ancestor_count);
- return false;
+ return util::Error{Untranslated(strprintf("too many unconfirmed parents [limit: %u]", limits.ancestor_count))};
}
}
}
@@ -254,9 +250,22 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
staged_ancestors = it->GetMemPoolParentsConst();
}
- return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /*entry_count=*/1,
- setAncestors, staged_ancestors,
- limits, errString);
+ return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /*entry_count=*/1, staged_ancestors,
+ limits);
+}
+
+CTxMemPool::setEntries CTxMemPool::AssumeCalculateMemPoolAncestors(
+ std::string_view calling_fn_name,
+ const CTxMemPoolEntry &entry,
+ const Limits& limits,
+ bool fSearchForParents /* = true */) const
+{
+ auto result{Assume(CalculateMemPoolAncestors(entry, limits, fSearchForParents))};
+ if (!result) {
+ LogPrintLevel(BCLog::MEMPOOL, BCLog::Level::Error, "%s: CalculateMemPoolAncestors failed unexpectedly, continuing with empty ancestor set (%s)\n",
+ calling_fn_name, util::ErrorString(result).original);
+ }
+ return std::move(result).value_or(CTxMemPool::setEntries{});
}
void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors)
@@ -320,9 +329,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b
}
}
for (txiter removeIt : entriesToRemove) {
- setEntries setAncestors;
const CTxMemPoolEntry &entry = *removeIt;
- std::string dummy;
// Since this is a tx that is already in the mempool, we can call CMPA
// with fSearchForParents = false. If the mempool is in a consistent
// state, then using true or false should both be correct, though false
@@ -342,10 +349,10 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b
// mempool parents we'd calculate by searching, and it's important that
// we use the cached notion of ancestor transactions as the set of
// things to update for removal.
- CalculateMemPoolAncestors(entry, setAncestors, Limits::NoLimits(), dummy, false);
+ auto ancestors{AssumeCalculateMemPoolAncestors(__func__, entry, Limits::NoLimits(), /*fSearchForParents=*/false)};
// Note that UpdateAncestorsOf severs the child links that point to
// removeIt in the entries for the parents of removeIt.
- UpdateAncestorsOf(false, removeIt, setAncestors);
+ UpdateAncestorsOf(false, removeIt, ancestors);
}
// After updating all the ancestor sizes, we can now sever the link between each
// transaction being removed and any mempool children (ie, update CTxMemPoolEntry::m_parents
@@ -389,7 +396,6 @@ CTxMemPool::CTxMemPool(const Options& opts)
m_full_rbf{opts.full_rbf},
m_limits{opts.limits}
{
- _clear(); //lock free clear
}
bool CTxMemPool::isSpent(const COutPoint& outpoint) const
@@ -627,26 +633,6 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
blockSinceLastRollingFeeBump = true;
}
-void CTxMemPool::_clear()
-{
- vTxHashes.clear();
- mapTx.clear();
- mapNextTx.clear();
- totalTxSize = 0;
- m_total_fee = 0;
- cachedInnerUsage = 0;
- lastRollingFeeUpdate = GetTime();
- blockSinceLastRollingFeeBump = false;
- rollingMinimumFeeRate = 0;
- ++nTransactionsUpdated;
-}
-
-void CTxMemPool::clear()
-{
- LOCK(cs);
- _clear();
-}
-
void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) const
{
if (m_check_ratio == 0) return;
@@ -695,15 +681,13 @@ void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendhei
assert(setParentCheck.size() == it->GetMemPoolParentsConst().size());
assert(std::equal(setParentCheck.begin(), setParentCheck.end(), it->GetMemPoolParentsConst().begin(), comp));
// Verify ancestor state is correct.
- setEntries setAncestors;
- std::string dummy;
- CalculateMemPoolAncestors(*it, setAncestors, Limits::NoLimits(), dummy);
- uint64_t nCountCheck = setAncestors.size() + 1;
+ auto ancestors{AssumeCalculateMemPoolAncestors(__func__, *it, Limits::NoLimits())};
+ uint64_t nCountCheck = ancestors.size() + 1;
uint64_t nSizeCheck = it->GetTxSize();
CAmount nFeesCheck = it->GetModifiedFee();
int64_t nSigOpCheck = it->GetSigOpCost();
- for (txiter ancestorIt : setAncestors) {
+ for (txiter ancestorIt : ancestors) {
nSizeCheck += ancestorIt->GetTxSize();
nFeesCheck += ancestorIt->GetModifiedFee();
nSigOpCheck += ancestorIt->GetSigOpCost();
@@ -858,10 +842,8 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD
if (it != mapTx.end()) {
mapTx.modify(it, [&nFeeDelta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(nFeeDelta); });
// Now update all ancestors' modified fees with descendants
- setEntries setAncestors;
- std::string dummy;
- CalculateMemPoolAncestors(*it, setAncestors, Limits::NoLimits(), dummy, false);
- for (txiter ancestorIt : setAncestors) {
+ auto ancestors{AssumeCalculateMemPoolAncestors(__func__, *it, Limits::NoLimits(), /*fSearchForParents=*/false)};
+ for (txiter ancestorIt : ancestors) {
mapTx.modify(ancestorIt, [=](CTxMemPoolEntry& e){ e.UpdateDescendantState(0, nFeeDelta, 0);});
}
// Now update all descendants' modified fees with ancestors
@@ -998,10 +980,8 @@ int CTxMemPool::Expire(std::chrono::seconds time)
void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, bool validFeeEstimate)
{
- setEntries setAncestors;
- std::string dummy;
- CalculateMemPoolAncestors(entry, setAncestors, Limits::NoLimits(), dummy);
- return addUnchecked(entry, setAncestors, validFeeEstimate);
+ auto ancestors{AssumeCalculateMemPoolAncestors(__func__, entry, Limits::NoLimits())};
+ return addUnchecked(entry, ancestors, validFeeEstimate);
}
void CTxMemPool::UpdateChild(txiter entry, txiter child, bool add)
diff --git a/src/txmempool.h b/src/txmempool.h
index bd62a09a4c..c3bc86cc04 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -11,6 +11,7 @@
#include <optional>
#include <set>
#include <string>
+#include <string_view>
#include <utility>
#include <vector>
@@ -28,6 +29,7 @@
#include <sync.h>
#include <util/epochguard.h>
#include <util/hasher.h>
+#include <util/result.h>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
@@ -317,14 +319,14 @@ protected:
std::atomic<unsigned int> nTransactionsUpdated{0}; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation
CBlockPolicyEstimator* const minerPolicyEstimator;
- uint64_t totalTxSize GUARDED_BY(cs); //!< sum of all mempool tx's virtual sizes. Differs from serialized tx size since witness data is discounted. Defined in BIP 141.
- CAmount m_total_fee GUARDED_BY(cs); //!< sum of all mempool tx's fees (NOT modified fee)
- uint64_t cachedInnerUsage GUARDED_BY(cs); //!< sum of dynamic memory usage of all the map elements (NOT the maps themselves)
+ uint64_t totalTxSize GUARDED_BY(cs){0}; //!< sum of all mempool tx's virtual sizes. Differs from serialized tx size since witness data is discounted. Defined in BIP 141.
+ CAmount m_total_fee GUARDED_BY(cs){0}; //!< sum of all mempool tx's fees (NOT modified fee)
+ uint64_t cachedInnerUsage GUARDED_BY(cs){0}; //!< sum of dynamic memory usage of all the map elements (NOT the maps themselves)
- mutable int64_t lastRollingFeeUpdate GUARDED_BY(cs);
- mutable bool blockSinceLastRollingFeeBump GUARDED_BY(cs);
- mutable double rollingMinimumFeeRate GUARDED_BY(cs); //!< minimum fee to get into the pool, decreases exponentially
- mutable Epoch m_epoch GUARDED_BY(cs);
+ mutable int64_t lastRollingFeeUpdate GUARDED_BY(cs){GetTime()};
+ mutable bool blockSinceLastRollingFeeBump GUARDED_BY(cs){false};
+ mutable double rollingMinimumFeeRate GUARDED_BY(cs){0}; //!< minimum fee to get into the pool, decreases exponentially
+ mutable Epoch m_epoch GUARDED_BY(cs){};
// In-memory counter for external mempool tracking purposes.
// This number is incremented once every time a transaction
@@ -428,24 +430,20 @@ private:
/**
* Helper function to calculate all in-mempool ancestors of staged_ancestors and apply ancestor
- * and descendant limits (including staged_ancestors thsemselves, entry_size and entry_count).
+ * and descendant limits (including staged_ancestors themselves, entry_size and entry_count).
*
* @param[in] entry_size Virtual size to include in the limits.
* @param[in] entry_count How many entries to include in the limits.
- * @param[out] setAncestors Will be populated with all mempool ancestors.
* @param[in] staged_ancestors Should contain entries in the mempool.
* @param[in] limits Maximum number and size of ancestors and descendants
- * @param[out] errString Populated with error reason if any limits are hit
*
- * @return true if no limits were hit and all in-mempool ancestors were calculated, false
- * otherwise
+ * @return all in-mempool ancestors, or an error if any ancestor or descendant limits were hit
*/
- bool CalculateAncestorsAndCheckLimits(size_t entry_size,
- size_t entry_count,
- setEntries& setAncestors,
- CTxMemPoolEntry::Parents &staged_ancestors,
- const Limits& limits,
- std::string &errString) const EXCLUSIVE_LOCKS_REQUIRED(cs);
+ util::Result<setEntries> CalculateAncestorsAndCheckLimits(size_t entry_size,
+ size_t entry_count,
+ CTxMemPoolEntry::Parents &staged_ancestors,
+ const Limits& limits
+ ) const EXCLUSIVE_LOCKS_REQUIRED(cs);
public:
indirectmap<COutPoint, const CTransaction*> mapNextTx GUARDED_BY(cs);
@@ -502,8 +500,6 @@ public:
void removeConflicts(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs);
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs);
- void clear();
- void _clear() EXCLUSIVE_LOCKS_REQUIRED(cs); //lock free
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb, bool wtxid=false);
void queryHashes(std::vector<uint256>& vtxid) const;
bool isSpent(const COutPoint& outpoint) const;
@@ -558,22 +554,37 @@ public:
* (these are all calculated including the tx itself)
*
* @param[in] entry CTxMemPoolEntry of which all in-mempool ancestors are calculated
- * @param[out] setAncestors Will be populated with all mempool ancestors.
* @param[in] limits Maximum number and size of ancestors and descendants
- * @param[out] errString Populated with error reason if any limits are hit
* @param[in] fSearchForParents Whether to search a tx's vin for in-mempool parents, or look
* up parents from mapLinks. Must be true for entries not in
* the mempool
*
- * @return true if no limits were hit and all in-mempool ancestors were calculated, false
- * otherwise
+ * @return all in-mempool ancestors, or an error if any ancestor or descendant limits were hit
*/
- bool CalculateMemPoolAncestors(const CTxMemPoolEntry& entry,
- setEntries& setAncestors,
+ util::Result<setEntries> CalculateMemPoolAncestors(const CTxMemPoolEntry& entry,
const Limits& limits,
- std::string& errString,
bool fSearchForParents = true) const EXCLUSIVE_LOCKS_REQUIRED(cs);
+ /**
+ * Same as CalculateMemPoolAncestors, but always returns a (non-optional) setEntries.
+ * Should only be used when it is assumed CalculateMemPoolAncestors would not fail. If
+ * CalculateMemPoolAncestors does unexpectedly fail, an empty setEntries is returned and the
+ * error is logged to BCLog::MEMPOOL with level BCLog::Level::Error. In debug builds, failure
+ * of CalculateMemPoolAncestors will lead to shutdown due to assertion failure.
+ *
+ * @param[in] calling_fn_name Name of calling function so we can properly log the call site
+ *
+ * @return a setEntries corresponding to the result of CalculateMemPoolAncestors or an empty
+ * setEntries if it failed
+ *
+ * @see CTXMemPool::CalculateMemPoolAncestors()
+ */
+ setEntries AssumeCalculateMemPoolAncestors(
+ std::string_view calling_fn_name,
+ const CTxMemPoolEntry &entry,
+ const Limits& limits,
+ bool fSearchForParents = true) const EXCLUSIVE_LOCKS_REQUIRED(cs);
+
/** Calculate all in-mempool ancestors of a set of transactions not already in the mempool and
* check ancestor and descendant limits. Heuristics are used to estimate the ancestor and
* descendant count of all entries if the package were to be added to the mempool. The limits
diff --git a/src/validation.cpp b/src/validation.cpp
index 33b0cf3f7a..e24d39170e 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -64,6 +64,7 @@
#include <numeric>
#include <optional>
#include <string>
+#include <utility>
using kernel::CCoinsStats;
using kernel::CoinStatsHashType;
@@ -867,11 +868,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
m_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants();
}
- std::string errString;
- if (!m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, m_limits, errString)) {
- ws.m_ancestors.clear();
+ auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, m_limits)};
+ if (!ancestors) {
// If CalculateMemPoolAncestors fails second time, we want the original error string.
- std::string dummy_err_string;
// Contracting/payment channels CPFP carve-out:
// If the new transaction is relatively small (up to 40k weight)
// and has at most one ancestor (ie ancestor limit of 2, including
@@ -889,14 +888,16 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
.descendant_count = m_limits.descendant_count + 1,
.descendant_size_vbytes = m_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT,
};
- if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT ||
- !m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors,
- cpfp_carve_out_limits,
- dummy_err_string)) {
- return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", errString);
+ const auto error_message{util::ErrorString(ancestors).original};
+ if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT) {
+ return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
}
+ ancestors = m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits);
+ if (!ancestors) return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
}
+ ws.m_ancestors = *ancestors;
+
// A transaction that spends outputs that would be replaced by it is invalid. Now
// that we have the set of all ancestors we can detect this
// pathological case by making sure ws.m_conflicts and ws.m_ancestors don't
@@ -1111,15 +1112,18 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
// Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the
// last calculation done in PreChecks, since package ancestors have already been submitted.
- std::string unused_err_string;
- if(!m_pool.CalculateMemPoolAncestors(*ws.m_entry, ws.m_ancestors, m_limits, unused_err_string)) {
- results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
- // Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail.
- Assume(false);
- all_submitted = false;
- package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
- strprintf("BUG! Mempool ancestors or descendants were underestimated: %s",
- ws.m_ptx->GetHash().ToString()));
+ {
+ auto ancestors{m_pool.CalculateMemPoolAncestors(*ws.m_entry, m_limits)};
+ if(!ancestors) {
+ results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
+ // Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail.
+ Assume(false);
+ all_submitted = false;
+ package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
+ strprintf("BUG! Mempool ancestors or descendants were underestimated: %s",
+ ws.m_ptx->GetHash().ToString()));
+ }
+ ws.m_ancestors = std::move(ancestors).value_or(ws.m_ancestors);
}
// If we call LimitMempoolSize() for each individual Finalize(), the mempool will not take
// the transaction's descendant feerate into account because it hasn't seen them yet. Also,
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 4fba567b7f..e6ba89627c 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -88,13 +88,15 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
std::vector<size_t> best_selection;
CAmount best_waste = MAX_MONEY;
+ bool is_feerate_high = utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee;
+
// Depth First search loop for choosing the UTXOs
for (size_t curr_try = 0, utxo_pool_index = 0; curr_try < TOTAL_TRIES; ++curr_try, ++utxo_pool_index) {
// Conditions for starting a backtrack
bool backtrack = false;
if (curr_value + curr_available_value < selection_target || // Cannot possibly reach target with the amount remaining in the curr_available_value.
curr_value > selection_target + cost_of_change || // Selected value is out of range, go back and try other branch
- (curr_waste > best_waste && (utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > 0)) { // Don't select things which we know will be more wasteful if the waste is increasing
+ (curr_waste > best_waste && is_feerate_high)) { // Don't select things which we know will be more wasteful if the waste is increasing
backtrack = true;
} else if (curr_value >= selection_target) { // Selected value is within range
curr_waste += (curr_value - selection_target); // This is the excess value which is added to the waste for the below comparison
@@ -446,7 +448,8 @@ void SelectionResult::Clear()
void SelectionResult::AddInput(const OutputGroup& group)
{
- util::insert(m_selected_inputs, group.m_outputs);
+ // As it can fail, combine inputs first
+ InsertInputs(group.m_outputs);
m_use_effective = !group.m_subtract_fee_outputs;
m_weight += group.m_weight;
@@ -454,7 +457,8 @@ void SelectionResult::AddInput(const OutputGroup& group)
void SelectionResult::AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs)
{
- util::insert(m_selected_inputs, inputs);
+ // As it can fail, combine inputs first
+ InsertInputs(inputs);
m_use_effective = !subtract_fee_outputs;
m_weight += std::accumulate(inputs.cbegin(), inputs.cend(), 0, [](int sum, const auto& coin) {
@@ -464,16 +468,14 @@ void SelectionResult::AddInputs(const std::set<COutput>& inputs, bool subtract_f
void SelectionResult::Merge(const SelectionResult& other)
{
- // Obtain the expected selected inputs count after the merge (for now, duplicates are not allowed)
- const size_t expected_count = m_selected_inputs.size() + other.m_selected_inputs.size();
+ // As it can fail, combine inputs first
+ InsertInputs(other.m_selected_inputs);
m_target += other.m_target;
m_use_effective |= other.m_use_effective;
if (m_algo == SelectionAlgorithm::MANUAL) {
m_algo = other.m_algo;
}
- util::insert(m_selected_inputs, other.m_selected_inputs);
- assert(m_selected_inputs.size() == expected_count);
m_weight += other.m_weight;
}
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 2efeb8476d..9ff2011ce3 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -10,6 +10,8 @@
#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <random.h>
+#include <util/system.h>
+#include <util/check.h>
#include <optional>
@@ -186,6 +188,7 @@ struct CoinEligibilityFilter
/** When avoid_reuse=true and there are full groups (OUTPUT_GROUP_MAX_ENTRIES), whether or not to use any partial groups.*/
const bool m_include_partial_groups{false};
+ CoinEligibilityFilter() = delete;
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_ancestors) {}
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants) {}
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants, bool include_partial) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants), m_include_partial_groups(include_partial) {}
@@ -301,6 +304,17 @@ private:
/** Total weight of the selected inputs */
int m_weight{0};
+ template<typename T>
+ void InsertInputs(const T& inputs)
+ {
+ // Store sum of combined input sets to check that the results have no shared UTXOs
+ const size_t expected_count = m_selected_inputs.size() + inputs.size();
+ util::insert(m_selected_inputs, inputs);
+ if (m_selected_inputs.size() != expected_count) {
+ throw std::runtime_error(STR_INTERNAL_BUG("Shared UTXOs among selection results"));
+ }
+ }
+
public:
explicit SelectionResult(const CAmount target, SelectionAlgorithm algo)
: m_target(target), m_algo(algo) {}
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index 5f31c1d72c..be1ad37592 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -763,13 +763,17 @@ RPCHelpMan fundrawtransaction()
},
{"input_weights", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "Inputs and their corresponding weights",
{
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
- {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output index"},
- {"weight", RPCArg::Type::NUM, RPCArg::Optional::NO, "The maximum weight for this input, "
- "including the weight of the outpoint and sequence number. "
- "Note that serialized signature sizes are not guaranteed to be consistent, "
- "so the maximum DER signatures size of 73 bytes should be used when considering ECDSA signatures."
- "Remember to convert serialized sizes to weight units when necessary."},
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
+ {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output index"},
+ {"weight", RPCArg::Type::NUM, RPCArg::Optional::NO, "The maximum weight for this input, "
+ "including the weight of the outpoint and sequence number. "
+ "Note that serialized signature sizes are not guaranteed to be consistent, "
+ "so the maximum DER signatures size of 73 bytes should be used when considering ECDSA signatures."
+ "Remember to convert serialized sizes to weight units when necessary."},
+ },
+ },
},
},
},
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index cff371c55b..c257af13c4 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -416,7 +416,6 @@ static const std::vector<RPCResult> TransactionDescriptionString()
}},
{RPCResult::Type::STR_HEX, "replaced_by_txid", /*optional=*/true, "The txid if this tx was replaced."},
{RPCResult::Type::STR_HEX, "replaces_txid", /*optional=*/true, "The txid if the tx replaces one."},
- {RPCResult::Type::STR, "comment", /*optional=*/true, ""},
{RPCResult::Type::STR, "to", /*optional=*/true, "If a comment to is associated with the transaction."},
{RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
{RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."},
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index f0ecd13f26..e6e444dbd5 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -226,6 +226,14 @@ static RPCHelpMan loadwallet()
bilingual_str error;
std::vector<bilingual_str> warnings;
std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
+
+ {
+ LOCK(context.wallets_mutex);
+ if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) {
+ throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded.");
+ }
+ }
+
std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
HandleWalletError(wallet, status, error);
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 3bb3c17119..0ec9e5411a 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -287,7 +287,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
if (coinControl && coinControl->HasSelected() && coinControl->IsSelected(outpoint))
continue;
- if (wallet.IsLockedCoin(outpoint))
+ if (wallet.IsLockedCoin(outpoint) && params.skip_locked)
continue;
if (wallet.IsSpent(outpoint))
@@ -510,15 +510,20 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
return groups_out;
}
-std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins,
+// Returns true if the result contains an error and the message is not empty
+static bool HasErrorMsg(const util::Result<SelectionResult>& res) { return !util::ErrorString(res).empty(); }
+
+util::Result<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins,
const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types)
{
// Run coin selection on each OutputType and compute the Waste Metric
std::vector<SelectionResult> results;
for (const auto& it : available_coins.coins) {
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, it.second, coin_selection_params)}) {
- results.push_back(*result);
- }
+ auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, it.second, coin_selection_params)};
+ // If any specific error message appears here, then something particularly wrong happened.
+ if (HasErrorMsg(result)) return result; // So let's return the specific error.
+ // Append the favorable result.
+ if (result) results.push_back(*result);
}
// If we have at least one solution for funding the transaction without mixing, choose the minimum one according to waste metric
// and return the result
@@ -528,16 +533,14 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
// over all available coins, which would allow mixing.
// If TypesCount() <= 1, there is nothing to mix.
if (allow_mixed_output_types && available_coins.TypesCount() > 1) {
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.All(), coin_selection_params)}) {
- return result;
- }
+ return ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.All(), coin_selection_params);
}
// Either mixing is not allowed and we couldn't find a solution from any single OutputType, or mixing was allowed and we still couldn't
// find a solution using all available coins
- return std::nullopt;
+ return util::Error();
};
-std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins, const CoinSelectionParams& coin_selection_params)
+util::Result<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins, const CoinSelectionParams& coin_selection_params)
{
// Vector of results. We will choose the best one based on waste.
std::vector<SelectionResult> results;
@@ -561,7 +564,7 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons
if (results.empty()) {
// No solution found
- return std::nullopt;
+ return util::Error();
}
std::vector<SelectionResult> eligible_results;
@@ -571,7 +574,8 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons
});
if (eligible_results.empty()) {
- return std::nullopt;
+ return util::Error{_("The inputs size exceeds the maximum weight. "
+ "Please try sending a smaller amount or manually consolidating your wallet's UTXOs")};
}
// Choose the result with the least waste
@@ -580,15 +584,18 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons
return best_result;
}
-std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const PreSelectedInputs& pre_set_inputs,
- const CAmount& nTargetValue, const CCoinControl& coin_control,
- const CoinSelectionParams& coin_selection_params)
+util::Result<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const PreSelectedInputs& pre_set_inputs,
+ const CAmount& nTargetValue, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params)
{
// Deduct preset inputs amount from the search target
CAmount selection_target = nTargetValue - pre_set_inputs.total_amount;
// Return if automatic coin selection is disabled, and we don't cover the selection target
- if (!coin_control.m_allow_other_inputs && selection_target > 0) return std::nullopt;
+ if (!coin_control.m_allow_other_inputs && selection_target > 0) {
+ return util::Error{_("The preselected coins total amount does not cover the transaction target. "
+ "Please allow other inputs to be automatically selected or include more coins manually")};
+ }
// Return if we can cover the target only with the preset inputs
if (selection_target <= 0) {
@@ -603,7 +610,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
CAmount available_coins_total_amount = coin_selection_params.m_subtract_fee_outputs ? available_coins.GetTotalAmount() :
(available_coins.GetEffectiveTotalAmount().has_value() ? *available_coins.GetEffectiveTotalAmount() : 0);
if (selection_target > available_coins_total_amount) {
- return std::nullopt; // Insufficient funds
+ return util::Error(); // Insufficient funds
}
// Start wallet Coin Selection procedure
@@ -622,7 +629,12 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
return op_selection_result;
}
-std::optional<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& value_to_select, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
+struct SelectionFilter {
+ CoinEligibilityFilter filter;
+ bool allow_mixed_output_types{true};
+};
+
+util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& value_to_select, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
{
unsigned int limit_ancestor_count = 0;
unsigned int limit_descendant_count = 0;
@@ -643,54 +655,56 @@ std::optional<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coi
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
// transaction at a target feerate. If an attempt fails, more attempts may be made using a more
// permissive CoinEligibilityFilter.
- std::optional<SelectionResult> res = [&] {
- // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
- // confirmations on outputs received from other wallets and only spend confirmed change.
- if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/false)}) return r1;
- // Allow mixing only if no solution from any single output type can be found
- if (auto r2{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) return r2;
-
+ util::Result<SelectionResult> res = [&] {
+ // Place coins eligibility filters on a scope increasing order.
+ std::vector<SelectionFilter> ordered_filters{
+ // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
+ // confirmations on outputs received from other wallets and only spend confirmed change.
+ {CoinEligibilityFilter(1, 6, 0), /*allow_mixed_output_types=*/false},
+ {CoinEligibilityFilter(1, 1, 0)},
+ };
// Fall back to using zero confirmation change (but with as few ancestors in the mempool as
// possible) if we cannot fund the transaction otherwise.
if (wallet.m_spend_zero_conf_change) {
- if (auto r3{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) return r3;
- if (auto r4{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)),
- available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
- return r4;
- }
- if (auto r5{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2),
- available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
- return r5;
- }
+ ordered_filters.push_back({CoinEligibilityFilter(0, 1, 2)});
+ ordered_filters.push_back({CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3))});
+ ordered_filters.push_back({CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2)});
// If partial groups are allowed, relax the requirement of spending OutputGroups (groups
// of UTXOs sent to the same address, which are obviously controlled by a single wallet)
// in their entirety.
- if (auto r6{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, /*include_partial=*/true),
- available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
- return r6;
- }
+ ordered_filters.push_back({CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, /*include_partial=*/true)});
// Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
// received from other wallets.
if (coin_control.m_include_unsafe_inputs) {
- if (auto r7{AttemptSelection(wallet, value_to_select,
- CoinEligibilityFilter(/*conf_mine=*/0, /*conf_theirs=*/0, max_ancestors-1, max_descendants-1, /*include_partial=*/true),
- available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
- return r7;
- }
+ ordered_filters.push_back({CoinEligibilityFilter(/*conf_mine=*/0, /*conf_theirs*/0, max_ancestors-1, max_descendants-1, /*include_partial=*/true)});
}
// Try with unlimited ancestors/descendants. The transaction will still need to meet
// mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but
// OutputGroups use heuristics that may overestimate ancestor/descendant counts.
if (!fRejectLongChains) {
- if (auto r8{AttemptSelection(wallet, value_to_select,
- CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), /*include_partial=*/true),
- available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
- return r8;
- }
+ ordered_filters.push_back({CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(),
+ std::numeric_limits<uint64_t>::max(),
+ /*include_partial=*/true)});
+ }
+ }
+
+ // Walk-through the filters until the solution gets found.
+ // If no solution is found, return the first detailed error (if any).
+ // future: add "error level" so the worst one can be picked instead.
+ std::vector<util::Result<SelectionResult>> res_detailed_errors;
+ for (const auto& select_filter : ordered_filters) {
+ if (auto res{AttemptSelection(wallet, value_to_select, select_filter.filter, available_coins,
+ coin_selection_params, select_filter.allow_mixed_output_types)}) {
+ return res; // result found
+ } else {
+ // If any specific error message appears here, then something particularly wrong might have happened.
+ // Save the error and continue the selection process. So if no solutions gets found, we can return
+ // the detailed error to the upper layers.
+ if (HasErrorMsg(res)) res_detailed_errors.emplace_back(res);
}
}
// Coin Selection failed.
- return std::optional<SelectionResult>();
+ return res_detailed_errors.empty() ? util::Result<SelectionResult>(util::Error()) : res_detailed_errors.front();
}();
return res;
@@ -917,13 +931,16 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
}
// Choose coins to use
- std::optional<SelectionResult> result = SelectCoins(wallet, available_coins, preset_inputs, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
- if (!result) {
- return util::Error{_("Insufficient funds")};
+ auto select_coins_res = SelectCoins(wallet, available_coins, preset_inputs, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
+ if (!select_coins_res) {
+ // 'SelectCoins' either returns a specific error message or, if empty, means a general "Insufficient funds".
+ const bilingual_str& err = util::ErrorString(select_coins_res);
+ return util::Error{err.empty() ?_("Insufficient funds") : err};
}
- TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->GetAlgo()).c_str(), result->GetTarget(), result->GetWaste(), result->GetSelectedValue());
+ const SelectionResult& result = *select_coins_res;
+ TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result.GetAlgo()).c_str(), result.GetTarget(), result.GetWaste(), result.GetSelectedValue());
- const CAmount change_amount = result->GetChange(coin_selection_params.min_viable_change, coin_selection_params.m_change_fee);
+ const CAmount change_amount = result.GetChange(coin_selection_params.min_viable_change, coin_selection_params.m_change_fee);
if (change_amount > 0) {
CTxOut newTxOut(change_amount, scriptChange);
if (nChangePosInOut == -1) {
@@ -938,7 +955,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
}
// Shuffle selected coins and fill in final vin
- std::vector<COutput> selected_coins = result->GetShuffledInputVector();
+ std::vector<COutput> selected_coins = result.GetShuffledInputVector();
// The sequence number is set to non-maxint so that DiscourageFeeSniping
// works.
@@ -963,7 +980,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
const CAmount output_value = CalculateOutputValue(txNew);
Assume(recipients_sum + change_amount == output_value);
- CAmount current_fee = result->GetSelectedValue() - output_value;
+ CAmount current_fee = result.GetSelectedValue() - output_value;
// Sanity check that the fee cannot be negative as that means we have more output value than input value
if (current_fee < 0) {
@@ -974,7 +991,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
if (nChangePosInOut != -1 && fee_needed < current_fee) {
auto& change = txNew.vout.at(nChangePosInOut);
change.nValue += current_fee - fee_needed;
- current_fee = result->GetSelectedValue() - CalculateOutputValue(txNew);
+ current_fee = result.GetSelectedValue() - CalculateOutputValue(txNew);
if (fee_needed != current_fee) {
return util::Error{Untranslated(STR_INTERNAL_BUG("Change adjustment: Fee needed != fee paid"))};
}
@@ -1013,7 +1030,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
}
++i;
}
- current_fee = result->GetSelectedValue() - CalculateOutputValue(txNew);
+ current_fee = result.GetSelectedValue() - CalculateOutputValue(txNew);
if (fee_needed != current_fee) {
return util::Error{Untranslated(STR_INTERNAL_BUG("SFFO: Fee needed != fee paid"))};
}
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index c612ed105e..5ffdd11813 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -76,6 +76,8 @@ struct CoinFilterParams {
bool only_spendable{true};
// By default, do not include immature coinbase outputs
bool include_immature_coinbase{false};
+ // By default, skip locked UTXOs
+ bool skip_locked{true};
};
/**
@@ -119,9 +121,11 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
* param@[in] coin_selection_params Parameters for the coin selection
* param@[in] allow_mixed_output_types Relax restriction that SelectionResults must be of the same OutputType
* returns If successful, a SelectionResult containing the input set
- * If failed, a nullopt
+ * If failed, returns (1) an empty error message if the target was not reached (general "Insufficient funds")
+ * or (2) an specific error message if there was something particularly wrong (e.g. a selection
+ * result that surpassed the tx max weight size).
*/
-std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins,
+util::Result<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins,
const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types);
/**
@@ -135,9 +139,11 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
* param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering
* param@[in] coin_selection_params Parameters for the coin selection
* returns If successful, a SelectionResult containing the input set
- * If failed, a nullopt
+ * If failed, returns (1) an empty error message if the target was not reached (general "Insufficient funds")
+ * or (2) an specific error message if there was something particularly wrong (e.g. a selection
+ * result that surpassed the tx max weight size).
*/
-std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins,
+util::Result<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins,
const CoinSelectionParams& coin_selection_params);
// User manually selected inputs that must be part of the transaction
@@ -175,18 +181,20 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
* param@[in] coin_selection_params Parameters for this coin selection such as feerates, whether to avoid partial spends,
* and whether to subtract the fee from the outputs.
* returns If successful, a SelectionResult containing the selected coins
- * If failed, a nullopt.
+ * If failed, returns (1) an empty error message if the target was not reached (general "Insufficient funds")
+ * or (2) an specific error message if there was something particularly wrong (e.g. a selection
+ * result that surpassed the tx max weight size).
*/
-std::optional<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control,
+util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control,
const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
/**
* Select all coins from coin_control, and if coin_control 'm_allow_other_inputs=true', call 'AutomaticCoinSelection' to
* select a set of coins such that nTargetValue - pre_set_inputs.total_amount is met.
*/
-std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const PreSelectedInputs& pre_set_inputs,
- const CAmount& nTargetValue, const CCoinControl& coin_control,
- const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+util::Result<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const PreSelectedInputs& pre_set_inputs,
+ const CAmount& nTargetValue, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
struct CreatedTransactionResult
{
diff --git a/src/wallet/test/availablecoins_tests.cpp b/src/wallet/test/availablecoins_tests.cpp
deleted file mode 100644
index 2427a343d5..0000000000
--- a/src/wallet/test/availablecoins_tests.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) 2022 The Bitcoin Core developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or https://www.opensource.org/licenses/mit-license.php.
-
-#include <validation.h>
-#include <wallet/coincontrol.h>
-#include <wallet/spend.h>
-#include <wallet/test/util.h>
-#include <wallet/test/wallet_test_fixture.h>
-
-#include <boost/test/unit_test.hpp>
-
-namespace wallet {
-BOOST_FIXTURE_TEST_SUITE(availablecoins_tests, WalletTestingSetup)
-class AvailableCoinsTestingSetup : public TestChain100Setup
-{
-public:
- AvailableCoinsTestingSetup()
- {
- CreateAndProcessBlock({}, {});
- wallet = CreateSyncedWallet(*m_node.chain, m_node.chainman->ActiveChain(), m_args, coinbaseKey);
- }
-
- ~AvailableCoinsTestingSetup()
- {
- wallet.reset();
- }
- CWalletTx& AddTx(CRecipient recipient)
- {
- CTransactionRef tx;
- CCoinControl dummy;
- {
- constexpr int RANDOM_CHANGE_POSITION = -1;
- auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, dummy);
- BOOST_CHECK(res);
- tx = res->tx;
- }
- wallet->CommitTransaction(tx, {}, {});
- CMutableTransaction blocktx;
- {
- LOCK(wallet->cs_wallet);
- blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetHash()).tx);
- }
- CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
-
- LOCK(wallet->cs_wallet);
- LOCK(m_node.chainman->GetMutex());
- wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
- auto it = wallet->mapWallet.find(tx->GetHash());
- BOOST_CHECK(it != wallet->mapWallet.end());
- it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/1};
- return it->second;
- }
-
- std::unique_ptr<CWallet> wallet;
-};
-
-BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, AvailableCoinsTestingSetup)
-{
- CoinsResult available_coins;
- util::Result<CTxDestination> dest{util::Error{}};
- LOCK(wallet->cs_wallet);
-
- // Verify our wallet has one usable coinbase UTXO before starting
- // This UTXO is a P2PK, so it should show up in the Other bucket
- available_coins = AvailableCoins(*wallet);
- BOOST_CHECK_EQUAL(available_coins.Size(), 1U);
- BOOST_CHECK_EQUAL(available_coins.coins[OutputType::UNKNOWN].size(), 1U);
-
- // We will create a self transfer for each of the OutputTypes and
- // verify it is put in the correct bucket after running GetAvailablecoins
- //
- // For each OutputType, We expect 2 UTXOs in our wallet following the self transfer:
- // 1. One UTXO as the recipient
- // 2. One UTXO from the change, due to payment address matching logic
-
- // Bech32m
- dest = wallet->GetNewDestination(OutputType::BECH32M, "");
- BOOST_ASSERT(dest);
- AddTx(CRecipient{{GetScriptForDestination(*dest)}, 1 * COIN, /*fSubtractFeeFromAmount=*/true});
- available_coins = AvailableCoins(*wallet);
- BOOST_CHECK_EQUAL(available_coins.coins[OutputType::BECH32M].size(), 2U);
-
- // Bech32
- dest = wallet->GetNewDestination(OutputType::BECH32, "");
- BOOST_ASSERT(dest);
- AddTx(CRecipient{{GetScriptForDestination(*dest)}, 2 * COIN, /*fSubtractFeeFromAmount=*/true});
- available_coins = AvailableCoins(*wallet);
- BOOST_CHECK_EQUAL(available_coins.coins[OutputType::BECH32].size(), 2U);
-
- // P2SH-SEGWIT
- dest = wallet->GetNewDestination(OutputType::P2SH_SEGWIT, "");
- BOOST_ASSERT(dest);
- AddTx(CRecipient{{GetScriptForDestination(*dest)}, 3 * COIN, /*fSubtractFeeFromAmount=*/true});
- available_coins = AvailableCoins(*wallet);
- BOOST_CHECK_EQUAL(available_coins.coins[OutputType::P2SH_SEGWIT].size(), 2U);
-
- // Legacy (P2PKH)
- dest = wallet->GetNewDestination(OutputType::LEGACY, "");
- BOOST_ASSERT(dest);
- AddTx(CRecipient{{GetScriptForDestination(*dest)}, 4 * COIN, /*fSubtractFeeFromAmount=*/true});
- available_coins = AvailableCoins(*wallet);
- BOOST_CHECK_EQUAL(available_coins.coins[OutputType::LEGACY].size(), 2U);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-} // namespace wallet
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index c30209002d..ae6d1deb8f 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -931,7 +931,7 @@ BOOST_AUTO_TEST_CASE(effective_value_test)
BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
}
-static std::optional<SelectionResult> select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function<CoinsResult(CWallet&)> coin_setup, interfaces::Chain* chain, const ArgsManager& args)
+static util::Result<SelectionResult> select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function<CoinsResult(CWallet&)> coin_setup, interfaces::Chain* chain, const ArgsManager& args)
{
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(chain, "", args, CreateMockWalletDatabase());
wallet->LoadWallet();
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 7ae10c23ca..c7c0a637de 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -622,6 +622,46 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);
}
+void TestCoinsResult(ListCoinsTest& context, OutputType out_type, CAmount amount,
+ std::map<OutputType, size_t>& expected_coins_sizes)
+{
+ LOCK(context.wallet->cs_wallet);
+ util::Result<CTxDestination> dest = Assert(context.wallet->GetNewDestination(out_type, ""));
+ CWalletTx& wtx = context.AddTx(CRecipient{{GetScriptForDestination(*dest)}, amount, /*fSubtractFeeFromAmount=*/true});
+ CoinFilterParams filter;
+ filter.skip_locked = false;
+ CoinsResult available_coins = AvailableCoins(*context.wallet, nullptr, std::nullopt, filter);
+ // Lock outputs so they are not spent in follow-up transactions
+ for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) context.wallet->LockCoin({wtx.GetHash(), i});
+ for (const auto& [type, size] : expected_coins_sizes) BOOST_CHECK_EQUAL(size, available_coins.coins[type].size());
+}
+
+BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, ListCoinsTest)
+{
+ std::map<OutputType, size_t> expected_coins_sizes;
+ for (const auto& out_type : OUTPUT_TYPES) { expected_coins_sizes[out_type] = 0U; }
+
+ // Verify our wallet has one usable coinbase UTXO before starting
+ // This UTXO is a P2PK, so it should show up in the Other bucket
+ expected_coins_sizes[OutputType::UNKNOWN] = 1U;
+ CoinsResult available_coins = WITH_LOCK(wallet->cs_wallet, return AvailableCoins(*wallet));
+ BOOST_CHECK_EQUAL(available_coins.Size(), expected_coins_sizes[OutputType::UNKNOWN]);
+ BOOST_CHECK_EQUAL(available_coins.coins[OutputType::UNKNOWN].size(), expected_coins_sizes[OutputType::UNKNOWN]);
+
+ // We will create a self transfer for each of the OutputTypes and
+ // verify it is put in the correct bucket after running GetAvailablecoins
+ //
+ // For each OutputType, We expect 2 UTXOs in our wallet following the self transfer:
+ // 1. One UTXO as the recipient
+ // 2. One UTXO from the change, due to payment address matching logic
+
+ for (const auto& out_type : OUTPUT_TYPES) {
+ if (out_type == OutputType::UNKNOWN) continue;
+ expected_coins_sizes[out_type] = 2U;
+ TestCoinsResult(*this, out_type, 1 * COIN, expected_coins_sizes);
+ }
+}
+
BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
{
{
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index d560de863a..b393c35112 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -974,7 +974,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
return result;
}
-DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWalletTx>& vWtx)
+DBErrors WalletBatch::FindWalletTxHashes(std::vector<uint256>& tx_hashes)
{
DBErrors result = DBErrors::LOAD_OK;
@@ -1012,9 +1012,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal
if (strType == DBKeys::TX) {
uint256 hash;
ssKey >> hash;
- vTxHash.push_back(hash);
- vWtx.emplace_back(/*tx=*/nullptr, TxStateInactive{});
- ssValue >> vWtx.back();
+ tx_hashes.push_back(hash);
}
}
} catch (...) {
@@ -1027,10 +1025,9 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal
DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<uint256>& vTxHashOut)
{
- // build list of wallet TXs and hashes
+ // build list of wallet TX hashes
std::vector<uint256> vTxHash;
- std::list<CWalletTx> vWtx;
- DBErrors err = FindWalletTx(vTxHash, vWtx);
+ DBErrors err = FindWalletTxHashes(vTxHash);
if (err != DBErrors::LOAD_OK) {
return err;
}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index c0d9de8bb3..97e8fad278 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -273,7 +273,7 @@ public:
bool EraseActiveScriptPubKeyMan(uint8_t type, bool internal);
DBErrors LoadWallet(CWallet* pwallet);
- DBErrors FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWalletTx>& vWtx);
+ DBErrors FindWalletTxHashes(std::vector<uint256>& tx_hashes);
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
/* Function to determine if a certain KV/key-type is a key (cryptographical key) type */
static bool IsKeyType(const std::string& strType);
diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py
index 30cf559c43..eff4d9b149 100755
--- a/test/functional/feature_coinstatsindex.py
+++ b/test/functional/feature_coinstatsindex.py
@@ -221,7 +221,7 @@ class CoinStatsIndexTest(BitcoinTestFramework):
self.generate(index_node, 1, sync_fun=self.no_op)
res10 = index_node.gettxoutsetinfo('muhash')
- assert(res8['txouts'] < res10['txouts'])
+ assert res8['txouts'] < res10['txouts']
self.log.info("Test that the index works with -reindex")
@@ -268,12 +268,12 @@ class CoinStatsIndexTest(BitcoinTestFramework):
res2 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112)
assert_equal(res["bestblock"], block)
assert_equal(res["muhash"], res2["muhash"])
- assert(res["muhash"] != res_invalid["muhash"])
+ assert res["muhash"] != res_invalid["muhash"]
# Test that requesting reorged out block by hash is still returning correct results
res_invalid2 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=reorg_block)
assert_equal(res_invalid2["muhash"], res_invalid["muhash"])
- assert(res["muhash"] != res_invalid2["muhash"])
+ assert res["muhash"] != res_invalid2["muhash"]
# Add another block, so we don't depend on reconsiderblock remembering which
# blocks were touched by invalidateblock
diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py
index d5baa080bb..77a056346a 100755
--- a/test/functional/feature_index_prune.py
+++ b/test/functional/feature_index_prune.py
@@ -62,7 +62,7 @@ class FeatureIndexPruneTest(BitcoinTestFramework):
for node in filter_nodes:
assert_greater_than(len(node.getblockfilter(tip)['filter']), 0)
for node in stats_nodes:
- assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash'])
+ assert node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash']
self.mine_batches(500)
self.sync_index(height=700)
@@ -80,14 +80,14 @@ class FeatureIndexPruneTest(BitcoinTestFramework):
for node in filter_nodes:
assert_greater_than(len(node.getblockfilter(tip)['filter']), 0)
for node in stats_nodes:
- assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash'])
+ assert node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash']
self.log.info("check if we can access the blockfilter and coinstats of a pruned block")
height_hash = self.nodes[0].getblockhash(2)
for node in filter_nodes:
assert_greater_than(len(node.getblockfilter(height_hash)['filter']), 0)
for node in stats_nodes:
- assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=height_hash)['muhash'])
+ assert node.gettxoutsetinfo(hash_type="muhash", hash_or_height=height_hash)['muhash']
# mine and sync index up to a height that will later be the pruneheight
self.generate(self.nodes[0], 51)
diff --git a/test/functional/interface_usdt_utxocache.py b/test/functional/interface_usdt_utxocache.py
index 2280de1479..e3b0b32f0d 100755
--- a/test/functional/interface_usdt_utxocache.py
+++ b/test/functional/interface_usdt_utxocache.py
@@ -357,8 +357,8 @@ class UTXOCacheTracepointTest(BitcoinTestFramework):
"size": event.size
})
# sanity checks only
- assert(event.memory > 0)
- assert(event.duration > 0)
+ assert event.memory > 0
+ assert event.duration > 0
handle_flush_succeeds += 1
bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush)
diff --git a/test/functional/interface_usdt_validation.py b/test/functional/interface_usdt_validation.py
index 8953dd023b..4323aef771 100755
--- a/test/functional/interface_usdt_validation.py
+++ b/test/functional/interface_usdt_validation.py
@@ -112,7 +112,7 @@ class ValidationTracepointTest(BitcoinTestFramework):
assert_equal(len([tx["vin"] for tx in block["tx"]]), event.inputs)
assert_equal(0, event.sigops) # no sigops in coinbase tx
# only plausibility checks
- assert(event.duration > 0)
+ assert event.duration > 0
del expected_blocks[block_hash]
blocks_checked += 1
diff --git a/test/functional/mocks/invalid_signer.py b/test/functional/mocks/invalid_signer.py
index 1d77e75215..7bfa9051ac 100755
--- a/test/functional/mocks/invalid_signer.py
+++ b/test/functional/mocks/invalid_signer.py
@@ -10,7 +10,7 @@ import json
def perform_pre_checks():
mock_result_path = os.path.join(os.getcwd(), "mock_result")
- if(os.path.isfile(mock_result_path)):
+ if os.path.isfile(mock_result_path):
with open(mock_result_path, "r", encoding="utf8") as f:
mock_result = f.read()
if mock_result[0]:
diff --git a/test/functional/mocks/signer.py b/test/functional/mocks/signer.py
index 41ea541f7b..5f4fad6380 100755
--- a/test/functional/mocks/signer.py
+++ b/test/functional/mocks/signer.py
@@ -10,7 +10,7 @@ import json
def perform_pre_checks():
mock_result_path = os.path.join(os.getcwd(), "mock_result")
- if(os.path.isfile(mock_result_path)):
+ if os.path.isfile(mock_result_path):
with open(mock_result_path, "r", encoding="utf8") as f:
mock_result = f.read()
if mock_result[0]:
diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py
index e2e9b6dcb2..e002a520c6 100755
--- a/test/functional/p2p_addr_relay.py
+++ b/test/functional/p2p_addr_relay.py
@@ -49,7 +49,7 @@ class AddrReceiver(P2PInterface):
def on_addr(self, message):
for addr in message.addrs:
self.num_ipv4_received += 1
- if(self.test_addr_contents):
+ if self.test_addr_contents:
# relay_tests checks the content of the addr messages match
# expectations based on the message creation in setup_addr_msg
assert_equal(addr.nServices, 9)
diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py
index c7297374b9..fa9ddf7ebe 100755
--- a/test/functional/p2p_blocksonly.py
+++ b/test/functional/p2p_blocksonly.py
@@ -104,7 +104,7 @@ class P2PBlocksOnly(BitcoinTestFramework):
self.nodes[0].setmocktime(int(time.time()) + 60)
conn.sync_send_with_ping()
- assert(int(txid, 16) not in conn.get_invs())
+ assert int(txid, 16) not in conn.get_invs()
def check_p2p_inv_violation(self, peer):
self.log.info("Check that tx-invs from P2P are rejected and result in disconnect")
diff --git a/test/functional/p2p_getaddr_caching.py b/test/functional/p2p_getaddr_caching.py
index 050183f19a..1c9ad7289b 100755
--- a/test/functional/p2p_getaddr_caching.py
+++ b/test/functional/p2p_getaddr_caching.py
@@ -60,7 +60,7 @@ class AddrTest(BitcoinTestFramework):
# Need to make sure we hit MAX_ADDR_TO_SEND records in the addr response later because
# only a fraction of all known addresses can be cached and returned.
- assert(len(self.nodes[0].getnodeaddresses(0)) > int(MAX_ADDR_TO_SEND / (MAX_PCT_ADDR_TO_SEND / 100)))
+ assert len(self.nodes[0].getnodeaddresses(0)) > int(MAX_ADDR_TO_SEND / (MAX_PCT_ADDR_TO_SEND / 100))
last_response_on_local_bind = None
last_response_on_onion_bind1 = None
@@ -85,9 +85,9 @@ class AddrTest(BitcoinTestFramework):
if i > 0:
# Responses from different binds should be unique
- assert(last_response_on_local_bind != addr_receiver_onion1.get_received_addrs())
- assert(last_response_on_local_bind != addr_receiver_onion2.get_received_addrs())
- assert(last_response_on_onion_bind1 != addr_receiver_onion2.get_received_addrs())
+ assert last_response_on_local_bind != addr_receiver_onion1.get_received_addrs()
+ assert last_response_on_local_bind != addr_receiver_onion2.get_received_addrs()
+ assert last_response_on_onion_bind1 != addr_receiver_onion2.get_received_addrs()
# Responses on from the same bind should be the same
assert_equal(last_response_on_local_bind, addr_receiver_local.get_received_addrs())
assert_equal(last_response_on_onion_bind1, addr_receiver_onion1.get_received_addrs())
@@ -119,9 +119,9 @@ class AddrTest(BitcoinTestFramework):
addr_receiver_onion2.wait_until(addr_receiver_onion2.addr_received)
# new response is different
- assert(set(last_response_on_local_bind) != set(addr_receiver_local.get_received_addrs()))
- assert(set(last_response_on_onion_bind1) != set(addr_receiver_onion1.get_received_addrs()))
- assert(set(last_response_on_onion_bind2) != set(addr_receiver_onion2.get_received_addrs()))
+ assert set(last_response_on_local_bind) != set(addr_receiver_local.get_received_addrs())
+ assert set(last_response_on_onion_bind1) != set(addr_receiver_onion1.get_received_addrs())
+ assert set(last_response_on_onion_bind2) != set(addr_receiver_onion2.get_received_addrs())
if __name__ == '__main__':
AddrTest().main()
diff --git a/test/functional/p2p_headers_sync_with_minchainwork.py b/test/functional/p2p_headers_sync_with_minchainwork.py
index a69e7b5710..b07077c668 100755
--- a/test/functional/p2p_headers_sync_with_minchainwork.py
+++ b/test/functional/p2p_headers_sync_with_minchainwork.py
@@ -57,7 +57,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
def check_node3_chaintips(num_tips, tip_hash, height):
node3_chaintips = self.nodes[3].getchaintips()
- assert(len(node3_chaintips) == num_tips)
+ assert len(node3_chaintips) == num_tips
assert {
'height': height,
'hash': tip_hash,
@@ -69,7 +69,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
for node in self.nodes[1:3]:
chaintips = node.getchaintips()
- assert(len(chaintips) == 1)
+ assert len(chaintips) == 1
assert {
'height': 0,
'hash': '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206',
@@ -89,7 +89,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
'status': 'active',
} in self.nodes[2].getchaintips()
- assert(len(self.nodes[2].getchaintips()) == 1)
+ assert len(self.nodes[2].getchaintips()) == 1
self.log.info("Check that node3 accepted these headers as well")
check_node3_chaintips(2, self.nodes[0].getbestblockhash(), NODE1_BLOCKS_REQUIRED)
diff --git a/test/functional/p2p_message_capture.py b/test/functional/p2p_message_capture.py
index 495e9bf1da..3ab0b79ba2 100755
--- a/test/functional/p2p_message_capture.py
+++ b/test/functional/p2p_message_capture.py
@@ -36,7 +36,7 @@ def mini_parser(dat_file):
"""
with open(dat_file, 'rb') as f_in:
# This should have at least one message in it
- assert(os.fstat(f_in.fileno()).st_size >= TIME_SIZE + LENGTH_SIZE + MSGTYPE_SIZE)
+ assert os.fstat(f_in.fileno()).st_size >= TIME_SIZE + LENGTH_SIZE + MSGTYPE_SIZE
while True:
tmp_header_raw = f_in.read(TIME_SIZE + LENGTH_SIZE + MSGTYPE_SIZE)
if not tmp_header_raw:
@@ -44,7 +44,7 @@ def mini_parser(dat_file):
tmp_header = BytesIO(tmp_header_raw)
tmp_header.read(TIME_SIZE) # skip the timestamp field
msgtype = tmp_header.read(MSGTYPE_SIZE).rstrip(b'\x00')
- assert(msgtype in MESSAGEMAP)
+ assert msgtype in MESSAGEMAP
length: int = int.from_bytes(tmp_header.read(LENGTH_SIZE), "little")
data = f_in.read(length)
assert_equal(len(data), length)
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index e9c4abe2a8..19c73eebf0 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -370,7 +370,7 @@ class BlockchainTest(BitcoinTestFramework):
# hash_type muhash should return a different UTXO set hash.
res6 = node.gettxoutsetinfo(hash_type='muhash')
assert 'muhash' in res6
- assert(res['hash_serialized_2'] != res6['muhash'])
+ assert res['hash_serialized_2'] != res6['muhash']
# muhash should not be returned unless requested.
for r in [res, res2, res3, res4, res5]:
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 2a91e9d0de..a50e0fb244 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -120,7 +120,9 @@ class PSBTTest(BitcoinTestFramework):
# If inputs are specified, do not automatically add more:
utxo1 = self.nodes[0].listunspent()[0]
- assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[0].walletcreatefundedpsbt, [{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90})
+ assert_raises_rpc_error(-4, "The preselected coins total amount does not cover the transaction target. "
+ "Please allow other inputs to be automatically selected or include more coins manually",
+ self.nodes[0].walletcreatefundedpsbt, [{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90})
psbtx1 = self.nodes[0].walletcreatefundedpsbt([{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90}, 0, {"add_inputs": True})['psbt']
assert_equal(len(self.nodes[0].decodepsbt(psbtx1)['tx']['vin']), 2)
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index c0a0e7ee17..1c91ab6f5f 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -216,13 +216,13 @@ class RawTransactionsTest(BitcoinTestFramework):
if missing_fields:
raise AssertionError(f"fields {', '.join(missing_fields)} are not in transaction")
- assert(len(gottx['vin']) > 0)
+ assert len(gottx['vin']) > 0
if v == 1:
- assert('fee' not in gottx)
- assert('prevout' not in gottx['vin'][0])
+ assert 'fee' not in gottx
+ assert 'prevout' not in gottx['vin'][0]
if v == 2:
- assert(isinstance(gottx['fee'], Decimal))
- assert('prevout' in gottx['vin'][0])
+ assert isinstance(gottx['fee'], Decimal)
+ assert 'prevout' in gottx['vin'][0]
prevout = gottx['vin'][0]['prevout']
script_pub_key = prevout['scriptPubKey']
@@ -235,11 +235,11 @@ class RawTransactionsTest(BitcoinTestFramework):
raise AssertionError(f"fields {', '.join(missing_fields)} are not in transaction")
# check verbosity 2 without blockhash but with txindex
- assert('fee' in self.nodes[0].getrawtransaction(txid=tx, verbosity=2))
+ assert 'fee' in self.nodes[0].getrawtransaction(txid=tx, verbosity=2)
# check that coinbase has no fee or does not throw any errors for verbosity 2
coin_base = self.nodes[1].getblock(block1)['tx'][0]
gottx = self.nodes[1].getrawtransaction(txid=coin_base, verbosity=2, blockhash=block1)
- assert('fee' not in gottx)
+ assert 'fee' not in gottx
def createrawtransaction_tests(self):
self.log.info("Test createrawtransaction")
diff --git a/test/functional/rpc_scanblocks.py b/test/functional/rpc_scanblocks.py
index b449875c95..9a00518150 100755
--- a/test/functional/rpc_scanblocks.py
+++ b/test/functional/rpc_scanblocks.py
@@ -46,7 +46,7 @@ class ScanblocksTest(BitcoinTestFramework):
self.wait_until(lambda: all(i["synced"] for i in node.getindexinfo().values()))
out = node.scanblocks("start", [f"addr({addr_1})"])
- assert(blockhash in out['relevant_blocks'])
+ assert blockhash in out['relevant_blocks']
assert_equal(height, out['to_height'])
assert_equal(0, out['from_height'])
@@ -56,24 +56,24 @@ class ScanblocksTest(BitcoinTestFramework):
# make sure the blockhash is not in the filter result if we set the start_height
# to the just mined block (unlikely to hit a false positive)
- assert(blockhash not in node.scanblocks(
- "start", [f"addr({addr_1})"], height_new)['relevant_blocks'])
+ assert blockhash not in node.scanblocks(
+ "start", [f"addr({addr_1})"], height_new)['relevant_blocks']
# make sure the blockhash is present when using the first mined block as start_height
- assert(blockhash in node.scanblocks(
- "start", [f"addr({addr_1})"], height)['relevant_blocks'])
+ assert blockhash in node.scanblocks(
+ "start", [f"addr({addr_1})"], height)['relevant_blocks']
# also test the stop height
- assert(blockhash in node.scanblocks(
- "start", [f"addr({addr_1})"], height, height)['relevant_blocks'])
+ assert blockhash in node.scanblocks(
+ "start", [f"addr({addr_1})"], height, height)['relevant_blocks']
# use the stop_height to exclude the relevant block
- assert(blockhash not in node.scanblocks(
- "start", [f"addr({addr_1})"], 0, height - 1)['relevant_blocks'])
+ assert blockhash not in node.scanblocks(
+ "start", [f"addr({addr_1})"], 0, height - 1)['relevant_blocks']
# make sure the blockhash is present when using the first mined block as start_height
- assert(blockhash in node.scanblocks(
- "start", [{"desc": f"pkh({parent_key}/*)", "range": [0, 100]}], height)['relevant_blocks'])
+ assert blockhash in node.scanblocks(
+ "start", [{"desc": f"pkh({parent_key}/*)", "range": [0, 100]}], height)['relevant_blocks']
# check that false-positives are included in the result now; note that
# finding a false-positive at runtime would take too long, hence we simply
@@ -89,10 +89,10 @@ class ScanblocksTest(BitcoinTestFramework):
false_positive_hash = bip158_basic_element_hash(false_positive_spk, 1, genesis_blockhash)
assert_equal(genesis_coinbase_hash, false_positive_hash)
- assert(genesis_blockhash in node.scanblocks(
- "start", [{"desc": f"raw({genesis_coinbase_spk.hex()})"}], 0, 0)['relevant_blocks'])
- assert(genesis_blockhash in node.scanblocks(
- "start", [{"desc": f"raw({false_positive_spk.hex()})"}], 0, 0)['relevant_blocks'])
+ assert genesis_blockhash in node.scanblocks(
+ "start", [{"desc": f"raw({genesis_coinbase_spk.hex()})"}], 0, 0)['relevant_blocks']
+ assert genesis_blockhash in node.scanblocks(
+ "start", [{"desc": f"raw({false_positive_spk.hex()})"}], 0, 0)['relevant_blocks']
# TODO: after an "accurate" mode for scanblocks is implemented (e.g. PR #26325)
# check here that it filters out the false-positive
diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py
index 68afc1383d..ad305ce1ef 100644
--- a/test/functional/test_framework/key.py
+++ b/test/functional/test_framework/key.py
@@ -139,7 +139,7 @@ class EllipticCurve:
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)"""
x1, y1, z1 = p1
x2, y2, z2 = p2
- assert(z2 == 1)
+ assert z2 == 1
# Adding to the point at infinity is a no-op
if z1 == 0:
return p2
@@ -262,7 +262,7 @@ class ECPubKey():
return self.valid
def get_bytes(self):
- assert(self.valid)
+ assert self.valid
p = SECP256K1.affine(self.p)
if p is None:
return None
@@ -276,7 +276,7 @@ class ECPubKey():
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
ECDSA verifier algorithm"""
- assert(self.valid)
+ assert self.valid
# Extract r and s from the DER formatted signature. Return false for
# any DER encoding errors.
@@ -349,7 +349,7 @@ class ECKey():
def set(self, secret, compressed):
"""Construct a private key object with given 32-byte secret and compressed flag."""
- assert(len(secret) == 32)
+ assert len(secret) == 32
secret = int.from_bytes(secret, 'big')
self.valid = (secret > 0 and secret < SECP256K1_ORDER)
if self.valid:
@@ -362,7 +362,7 @@ class ECKey():
def get_bytes(self):
"""Retrieve the 32-byte representation of this key."""
- assert(self.valid)
+ assert self.valid
return self.secret.to_bytes(32, 'big')
@property
@@ -375,7 +375,7 @@ class ECKey():
def get_pubkey(self):
"""Compute an ECPubKey object for this secret key."""
- assert(self.valid)
+ assert self.valid
ret = ECPubKey()
p = SECP256K1.mul([(SECP256K1_G, self.secret)])
ret.p = p
@@ -388,7 +388,7 @@ class ECKey():
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
ECDSA signer algorithm."""
- assert(self.valid)
+ assert self.valid
z = int.from_bytes(msg, 'big')
# Note: no RFC6979 by default, but a simple random nonce (some tests rely on distinct transactions for the same operation)
if rfc6979:
diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py
index ebff849cc9..f345bf02db 100644
--- a/test/functional/test_framework/script.py
+++ b/test/functional/test_framework/script.py
@@ -824,10 +824,10 @@ def taproot_tree_helper(scripts):
if len(scripts) == 1:
# One entry: treat as a leaf
script = scripts[0]
- assert(not callable(script))
+ assert not callable(script)
if isinstance(script, list):
return taproot_tree_helper(script)
- assert(isinstance(script, tuple))
+ assert isinstance(script, tuple)
version = LEAF_VERSION_TAPSCRIPT
name = script[0]
code = script[1]
diff --git a/test/functional/test_framework/siphash.py b/test/functional/test_framework/siphash.py
index 5ad245cf1b..884dbcab46 100644
--- a/test/functional/test_framework/siphash.py
+++ b/test/functional/test_framework/siphash.py
@@ -31,7 +31,7 @@ def siphash_round(v0, v1, v2, v3):
def siphash(k0, k1, data):
- assert(type(data) == bytes)
+ assert type(data) == bytes
v0 = 0x736f6d6570736575 ^ k0
v1 = 0x646f72616e646f6d ^ k1
v2 = 0x6c7967656e657261 ^ k0
@@ -61,5 +61,5 @@ def siphash(k0, k1, data):
def siphash256(k0, k1, num):
- assert(type(num) == int)
+ assert type(num) == int
return siphash(k0, k1, num.to_bytes(32, 'little'))
diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py
index 4fb0559a33..5601d81227 100755
--- a/test/functional/wallet_avoidreuse.py
+++ b/test/functional/wallet_avoidreuse.py
@@ -195,7 +195,7 @@ class AvoidReuseTest(BitcoinTestFramework):
# getbalances should show no used, 10 btc trusted
assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10})
# node 0 should not show a used entry, as it does not enable avoid_reuse
- assert("used" not in self.nodes[0].getbalances()["mine"])
+ assert "used" not in self.nodes[0].getbalances()["mine"]
self.nodes[1].sendtoaddress(retaddr, 5)
self.generate(self.nodes[0], 1)
diff --git a/test/functional/wallet_backwards_compatibility.py b/test/functional/wallet_backwards_compatibility.py
index ffe3c359a9..f55a3758ce 100755
--- a/test/functional/wallet_backwards_compatibility.py
+++ b/test/functional/wallet_backwards_compatibility.py
@@ -193,18 +193,18 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
assert_equal(txs[1]["txid"], tx1_id)
assert_equal(txs[2]["walletconflicts"], [tx1_id])
assert_equal(txs[1]["replaced_by_txid"], tx2_id)
- assert not(txs[1]["abandoned"])
+ assert not txs[1]["abandoned"]
assert_equal(txs[1]["confirmations"], -1)
assert_equal(txs[2]["blockindex"], 1)
assert txs[3]["abandoned"]
assert_equal(txs[4]["walletconflicts"], [tx3_id])
assert_equal(txs[3]["replaced_by_txid"], tx4_id)
- assert not(hasattr(txs[3], "blockindex"))
+ assert not hasattr(txs[3], "blockindex")
elif wallet_name == "w2":
- assert(info['private_keys_enabled'] == False)
+ assert info['private_keys_enabled'] == False
assert info['keypoolsize'] == 0
else:
- assert(info['private_keys_enabled'] == True)
+ assert info['private_keys_enabled'] == True
assert info['keypoolsize'] == 0
else:
for node in legacy_nodes:
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 2160408a55..1cb3f434e8 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -557,7 +557,7 @@ def test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address):
def test_bumpfee_metadata(self, rbf_node, dest_address):
self.log.info('Test that bumped txn metadata persists to new txn record')
- assert(rbf_node.getbalance() < 49)
+ assert rbf_node.getbalance() < 49
self.generatetoaddress(rbf_node, 101, rbf_node.getnewaddress())
rbfid = rbf_node.sendtoaddress(dest_address, 49, "comment value", "to value")
bumped_tx = rbf_node.bumpfee(rbfid)
diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py
index 038002d2dc..98b0f70b01 100755
--- a/test/functional/wallet_fundrawtransaction.py
+++ b/test/functional/wallet_fundrawtransaction.py
@@ -27,6 +27,8 @@ from test_framework.util import (
)
from test_framework.wallet_util import bytes_to_wif
+ERR_NOT_ENOUGH_PRESET_INPUTS = "The preselected coins total amount does not cover the transaction target. " \
+ "Please allow other inputs to be automatically selected or include more coins manually"
def get_unspent(listunspent, amount):
for utx in listunspent:
@@ -328,7 +330,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex'])
# Should fail without add_inputs:
- assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx, {"add_inputs": False})
+ assert_raises_rpc_error(-4, ERR_NOT_ENOUGH_PRESET_INPUTS, self.nodes[2].fundrawtransaction, rawtx, {"add_inputs": False})
# add_inputs is enabled by default
rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
@@ -360,7 +362,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
# Should fail without add_inputs:
- assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx, {"add_inputs": False})
+ assert_raises_rpc_error(-4, ERR_NOT_ENOUGH_PRESET_INPUTS, self.nodes[2].fundrawtransaction, rawtx, {"add_inputs": False})
rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {"add_inputs": True})
dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
@@ -394,7 +396,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
# Should fail without add_inputs:
- assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx, {"add_inputs": False})
+ assert_raises_rpc_error(-4, ERR_NOT_ENOUGH_PRESET_INPUTS, self.nodes[2].fundrawtransaction, rawtx, {"add_inputs": False})
rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {"add_inputs": True})
dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
@@ -989,7 +991,9 @@ class RawTransactionsTest(BitcoinTestFramework):
outputs[recipient.getnewaddress()] = 0.1
wallet.sendmany("", outputs)
self.generate(self.nodes[0], 10)
- assert_raises_rpc_error(-4, "Insufficient funds", recipient.fundrawtransaction, rawtx)
+ assert_raises_rpc_error(-4, "The inputs size exceeds the maximum weight. "
+ "Please try sending a smaller amount or manually consolidating your wallet's UTXOs",
+ recipient.fundrawtransaction, rawtx)
self.nodes[0].unloadwallet("large")
def test_external_inputs(self):
@@ -1130,7 +1134,7 @@ class RawTransactionsTest(BitcoinTestFramework):
}
]
}
- assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{addr1: 8}], options=options)
+ assert_raises_rpc_error(-4, ERR_NOT_ENOUGH_PRESET_INPUTS, wallet.send, outputs=[{addr1: 8}], options=options)
# Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount)
options["add_inputs"] = True
@@ -1158,7 +1162,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# 6. Explicit add_inputs=false, no preset inputs:
options = {"add_inputs": False}
- assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{addr1: 3}], options=options)
+ assert_raises_rpc_error(-4, ERR_NOT_ENOUGH_PRESET_INPUTS, wallet.send, outputs=[{addr1: 3}], options=options)
################################################
@@ -1175,7 +1179,7 @@ class RawTransactionsTest(BitcoinTestFramework):
"vout": 1 # change position was hardcoded to index 0
}]
outputs = {self.nodes[1].getnewaddress(): 8}
- assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, inputs=inputs, outputs=outputs)
+ assert_raises_rpc_error(-4, ERR_NOT_ENOUGH_PRESET_INPUTS, wallet.walletcreatefundedpsbt, inputs=inputs, outputs=outputs)
# Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount)
options["add_inputs"] = True
@@ -1202,7 +1206,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# Case (6). Explicit add_inputs=false, no preset inputs:
options = {"add_inputs": False}
- assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, inputs=[], outputs=outputs, options=options)
+ assert_raises_rpc_error(-4, ERR_NOT_ENOUGH_PRESET_INPUTS, wallet.walletcreatefundedpsbt, inputs=[], outputs=outputs, options=options)
self.nodes[2].unloadwallet("test_preset_inputs")
diff --git a/test/functional/wallet_implicitsegwit.py b/test/functional/wallet_implicitsegwit.py
index 3ac0bbc17f..baa9bafb00 100755
--- a/test/functional/wallet_implicitsegwit.py
+++ b/test/functional/wallet_implicitsegwit.py
@@ -36,7 +36,7 @@ def check_implicit_transactions(implicit_keys, implicit_node):
pubkey = implicit_keys[a]
for b in address_types:
b_address = key_to_address(pubkey, b)
- assert(('receive', b_address) in tuple((tx['category'], tx['address']) for tx in txs))
+ assert ('receive', b_address) in tuple((tx['category'], tx['address']) for tx in txs)
class ImplicitSegwitTest(BitcoinTestFramework):
def add_options(self, parser):
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index 03b285e254..9e813166c5 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -487,8 +487,8 @@ class ImportDescriptorsTest(BitcoinTestFramework):
assert_equal(addr, 'bcrt1qp8s25ckjl7gr6x2q3dx3tn2pytwp05upkjztk6ey857tt50r5aeqn6mvr9') # Derived at m/84'/0'/0'/1
change_addr = wmulti_pub.getrawchangeaddress('bech32')
assert_equal(change_addr, 'bcrt1qzxl0qz2t88kljdnkzg4n4gapr6kte26390gttrg79x66nt4p04fssj53nl')
- assert(send_txid in self.nodes[0].getrawmempool(True))
- assert(send_txid in (x['txid'] for x in wmulti_pub.listunspent(0)))
+ assert send_txid in self.nodes[0].getrawmempool(True)
+ assert send_txid in (x['txid'] for x in wmulti_pub.listunspent(0))
assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999)
# generate some utxos for next tests
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index a4ffb8df63..2faf6cad8b 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -303,12 +303,8 @@ class MultiWalletTest(BitcoinTestFramework):
assert_raises_rpc_error(-18, "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path), self.nodes[0].loadwallet, 'wallets')
# Fail to load duplicate wallets
- path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "w1", "wallet.dat")
- if self.options.descriptors:
- assert_raises_rpc_error(-4, f"Wallet file verification failed. SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another instance of {self.config['environment']['PACKAGE_NAME']}?", self.nodes[0].loadwallet, wallet_names[0])
- else:
- assert_raises_rpc_error(-35, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0])
-
+ assert_raises_rpc_error(-35, "Wallet \"w1\" is already loaded.", self.nodes[0].loadwallet, wallet_names[0])
+ if not self.options.descriptors:
# This tests the default wallet that BDB makes, so SQLite wallet doesn't need to test this
# Fail to load duplicate wallets by different ways (directory and filepath)
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "wallet.dat")
diff --git a/test/functional/wallet_reorgsrestore.py b/test/functional/wallet_reorgsrestore.py
index 2969da4059..1c79c6816c 100755
--- a/test/functional/wallet_reorgsrestore.py
+++ b/test/functional/wallet_reorgsrestore.py
@@ -94,11 +94,11 @@ class ReorgsRestoreTest(BitcoinTestFramework):
tx_after_reorg = self.nodes[1].gettransaction(txid)
# Check that normal confirmed tx is confirmed again but with different blockhash
assert_equal(tx_after_reorg["confirmations"], 2)
- assert(tx_before_reorg["blockhash"] != tx_after_reorg["blockhash"])
+ assert tx_before_reorg["blockhash"] != tx_after_reorg["blockhash"]
conflicted_after_reorg = self.nodes[1].gettransaction(conflicted_txid)
# Check that conflicted tx is confirmed again with blockhash different than previously conflicting tx
assert_equal(conflicted_after_reorg["confirmations"], 1)
- assert(conflicting["blockhash"] != conflicted_after_reorg["blockhash"])
+ assert conflicting["blockhash"] != conflicted_after_reorg["blockhash"]
if __name__ == '__main__':
ReorgsRestoreTest().main()
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index 7c83b42769..424834323f 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -279,11 +279,11 @@ class WalletSendTest(BitcoinTestFramework):
self.log.info("Don't broadcast...")
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False)
- assert(res["hex"])
+ assert res["hex"]
self.log.info("Return PSBT...")
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, psbt=True)
- assert(res["psbt"])
+ assert res["psbt"]
self.log.info("Create transaction that spends to address, but don't broadcast...")
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False)
@@ -412,10 +412,12 @@ class WalletSendTest(BitcoinTestFramework):
assert res["complete"]
utxo1 = w0.listunspent()[0]
assert_equal(utxo1["amount"], 50)
+ ERR_NOT_ENOUGH_PRESET_INPUTS = "The preselected coins total amount does not cover the transaction target. " \
+ "Please allow other inputs to be automatically selected or include more coins manually"
self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[utxo1],
- expect_error=(-4, "Insufficient funds"))
+ expect_error=(-4, ERR_NOT_ENOUGH_PRESET_INPUTS))
self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[utxo1], add_inputs=False,
- expect_error=(-4, "Insufficient funds"))
+ expect_error=(-4, ERR_NOT_ENOUGH_PRESET_INPUTS))
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[utxo1], add_inputs=True, add_to_wallet=False)
assert res["complete"]
diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py
index 01cd4f28fc..8d25044e43 100755
--- a/test/functional/wallet_signer.py
+++ b/test/functional/wallet_signer.py
@@ -174,7 +174,7 @@ class WalletSignerTest(BitcoinTestFramework):
mock_psbt_signed = mock_wallet.walletprocesspsbt(psbt=mock_psbt, sign=True, sighashtype="ALL", bip32derivs=True)
mock_psbt_final = mock_wallet.finalizepsbt(mock_psbt_signed["psbt"])
mock_tx = mock_psbt_final["hex"]
- assert(mock_wallet.testmempoolaccept([mock_tx])[0]["allowed"])
+ assert mock_wallet.testmempoolaccept([mock_tx])[0]["allowed"]
# # Create a new wallet and populate with specific public keys, in order
# # to work with the mock signed PSBT.
@@ -203,7 +203,7 @@ class WalletSignerTest(BitcoinTestFramework):
# assert_equal(result[1], {'success': True})
assert_equal(hww.getwalletinfo()["txcount"], 1)
- assert(hww.testmempoolaccept([mock_tx])[0]["allowed"])
+ assert hww.testmempoolaccept([mock_tx])[0]["allowed"]
with open(os.path.join(self.nodes[1].cwd, "mock_psbt"), "w", encoding="utf8") as f:
f.write(mock_psbt_signed["psbt"])
@@ -212,13 +212,13 @@ class WalletSignerTest(BitcoinTestFramework):
# Don't broadcast transaction yet so the RPC returns the raw hex
res = hww.send(outputs={dest:0.5},options={"add_to_wallet": False})
- assert(res["complete"])
+ assert res["complete"]
assert_equal(res["hex"], mock_tx)
self.log.info('Test sendall using hww1')
res = hww.sendall(recipients=[{dest:0.5}, hww.getrawchangeaddress()],options={"add_to_wallet": False})
- assert(res["complete"])
+ assert res["complete"]
assert_equal(res["hex"], mock_tx)
# Broadcast transaction so we can bump the fee
hww.sendrawtransaction(res["hex"])
diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py
index d95ad9e01f..b52892704f 100755
--- a/test/functional/wallet_taproot.py
+++ b/test/functional/wallet_taproot.py
@@ -244,7 +244,7 @@ class WalletTaprootTest(BitcoinTestFramework):
desc_pub = self.make_desc(pattern, privmap, keys, True)
assert_equal(self.nodes[0].getdescriptorinfo(desc)['descriptor'], desc_pub)
result = addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}])
- assert(result[0]['success'])
+ assert result[0]['success']
address_type = "bech32m" if "tr" in pattern else "bech32"
for i in range(4):
addr_g = addr_gen.getnewaddress(address_type=address_type)
@@ -260,9 +260,9 @@ class WalletTaprootTest(BitcoinTestFramework):
# tr descriptors can be imported
result = privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}])
- assert(result[0]["success"])
+ assert result[0]['success']
result = pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}])
- assert(result[0]["success"])
+ assert result[0]["success"]
# Cleanup
privs_tr_enabled.unloadwallet()
@@ -284,9 +284,9 @@ class WalletTaprootTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub)
assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub)
result = rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}])
- assert(result[0]['success'])
+ assert result[0]['success']
result = rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}])
- assert(result[0]['success'])
+ assert result[0]['success']
address_type = "bech32m" if "tr" in pattern else "bech32"
for i in range(4):
addr_g = rpc_online.getnewaddress(address_type=address_type)
@@ -302,12 +302,12 @@ class WalletTaprootTest(BitcoinTestFramework):
# Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends.
res = rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True, fee_rate=200)
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
- assert(rpc_online.gettransaction(res)["confirmations"] > 0)
+ assert rpc_online.gettransaction(res)["confirmations"] > 0
# Cleanup
txid = rpc_online.sendall(recipients=[self.boring.getnewaddress()])["txid"]
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
- assert(rpc_online.gettransaction(txid)["confirmations"] > 0)
+ assert rpc_online.gettransaction(txid)["confirmations"] > 0
rpc_online.unloadwallet()
def do_test_psbt(self, comment, pattern, privmap, treefn, keys_pay, keys_change):
@@ -329,16 +329,16 @@ class WalletTaprootTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub)
assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub)
result = psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}])
- assert(result[0]['success'])
+ assert result[0]['success']
result = psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}])
- assert(result[0]['success'])
+ assert result[0]['success']
result = psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}])
- assert(result[0]['success'])
+ assert result[0]['success']
result = psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}])
- assert(result[0]['success'])
+ assert result[0]['success']
for key in keys_pay + keys_change:
result = key_only_wallet.importdescriptors([{"desc": descsum_create(f"wpkh({key['xprv']}/*)"), "timestamp":"now"}])
- assert(result[0]["success"])
+ assert result[0]["success"]
address_type = "bech32m" if "tr" in pattern else "bech32"
for i in range(4):
addr_g = psbt_online.getnewaddress(address_type=address_type)
@@ -375,7 +375,7 @@ class WalletTaprootTest(BitcoinTestFramework):
txid = self.nodes[0].sendrawtransaction(rawtx)
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
- assert(psbt_online.gettransaction(txid)['confirmations'] > 0)
+ assert psbt_online.gettransaction(txid)['confirmations'] > 0
# Cleanup
psbt = psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"]
@@ -383,7 +383,7 @@ class WalletTaprootTest(BitcoinTestFramework):
rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex']
txid = self.nodes[0].sendrawtransaction(rawtx)
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
- assert(psbt_online.gettransaction(txid)['confirmations'] > 0)
+ assert psbt_online.gettransaction(txid)['confirmations'] > 0
psbt_online.unloadwallet()
psbt_offline.unloadwallet()
diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan
index 0e6a429f35..d331991273 100644
--- a/test/sanitizer_suppressions/tsan
+++ b/test/sanitizer_suppressions/tsan
@@ -35,5 +35,8 @@ race:libzmq
# https://github.com/bitcoin/bitcoin/issues/20618
race:CZMQAbstractPublishNotifier::SendZmqMessage
+# https://github.com/bitcoin/bitcoin/pull/20218, https://github.com/bitcoin/bitcoin/pull/20745
+race:epoll_ctl
+
# https://github.com/bitcoin/bitcoin/issues/23366
race:std::__1::ios_base::*