aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xci/test/06_script_b.sh2
-rw-r--r--depends/packages/expat.mk9
-rw-r--r--depends/packages/qt.mk1
-rw-r--r--src/.clang-tidy2
-rw-r--r--src/Makefile.test.include2
-rw-r--r--src/addrman.cpp149
-rw-r--r--src/addrman.h25
-rw-r--r--src/addrman_impl.h42
-rw-r--r--src/bench/addrman.cpp2
-rw-r--r--src/bench/coin_selection.cpp6
-rw-r--r--src/blockfilter.cpp2
-rw-r--r--src/chain.cpp7
-rw-r--r--src/chain.h24
-rw-r--r--src/external_signer.cpp2
-rw-r--r--src/net.cpp29
-rw-r--r--src/net_processing.cpp17
-rw-r--r--src/node/interfaces.cpp1
-rw-r--r--src/primitives/transaction.cpp7
-rw-r--r--src/primitives/transaction.h14
-rw-r--r--src/protocol.h11
-rw-r--r--src/psbt.h3
-rw-r--r--src/random.h17
-rw-r--r--src/rpc/blockchain.cpp25
-rw-r--r--src/rpc/mining.cpp4
-rw-r--r--src/rpc/net.cpp24
-rw-r--r--src/rpc/node.cpp6
-rw-r--r--src/rpc/rawtransaction.cpp32
-rw-r--r--src/serialize.h23
-rw-r--r--src/test/addrman_tests.cpp26
-rw-r--r--src/test/fuzz/addrman.cpp18
-rw-r--r--src/test/fuzz/chain.cpp3
-rw-r--r--src/test/fuzz/parse_univalue.cpp2
-rw-r--r--src/test/fuzz/tx_pool.cpp10
-rw-r--r--src/test/fuzz/txorphan.cpp2
-rw-r--r--src/test/fuzz/util.cpp5
-rw-r--r--src/test/fuzz/util.h5
-rw-r--r--src/test/netbase_tests.cpp18
-rw-r--r--src/test/random_tests.cpp10
-rw-r--r--src/test/rbf_tests.cpp230
-rw-r--r--src/test/script_tests.cpp24
-rw-r--r--src/test/system_tests.cpp5
-rw-r--r--src/threadinterrupt.cpp12
-rw-r--r--src/threadinterrupt.h5
-rw-r--r--src/timedata.h8
-rw-r--r--src/txdb.cpp2
-rw-r--r--src/univalue/include/univalue.h40
-rw-r--r--src/util/system.cpp2
-rw-r--r--src/util/time.h1
-rw-r--r--src/validation.cpp1
-rw-r--r--src/wallet/rpc/addresses.cpp28
-rw-r--r--src/wallet/rpc/backup.cpp40
-rw-r--r--src/wallet/rpc/coins.cpp18
-rw-r--r--src/wallet/rpc/encrypt.cpp14
-rw-r--r--src/wallet/rpc/signmessage.cpp2
-rw-r--r--src/wallet/rpc/spend.cpp22
-rw-r--r--src/wallet/rpc/transactions.cpp18
-rw-r--r--src/wallet/rpc/wallet.cpp14
-rw-r--r--src/wallet/spend.cpp164
-rw-r--r--src/wallet/spend.h69
-rw-r--r--src/wallet/test/availablecoins_tests.cpp105
-rw-r--r--src/wallet/test/coinselector_tests.cpp264
-rw-r--r--src/wallet/test/wallet_tests.cpp6
-rwxr-xr-xtest/functional/rpc_psbt.py10
-rw-r--r--test/functional/test_framework/psbt.py8
-rwxr-xr-xtest/functional/test_runner.py1
-rwxr-xr-xtest/functional/wallet_address_types.py28
-rwxr-xr-xtest/functional/wallet_avoid_mixing_output_types.py176
67 files changed, 1306 insertions, 598 deletions
diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh
index b60c9b6d30..84b7ebe3fd 100755
--- a/ci/test/06_script_b.sh
+++ b/ci/test/06_script_b.sh
@@ -47,9 +47,11 @@ if [ "${RUN_TIDY}" = "true" ]; then
" src/policy/feerate.cpp"\
" src/policy/packages.cpp"\
" src/policy/settings.cpp"\
+ " src/primitives/transaction.cpp"\
" src/rpc/fees.cpp"\
" src/rpc/signmessage.cpp"\
" src/test/fuzz/txorphan.cpp"\
+ " src/threadinterrupt.cpp"\
" src/util/bip32.cpp"\
" src/util/bytevectorhash.cpp"\
" src/util/error.cpp"\
diff --git a/depends/packages/expat.mk b/depends/packages/expat.mk
index 349319138e..bb203d06f8 100644
--- a/depends/packages/expat.mk
+++ b/depends/packages/expat.mk
@@ -1,15 +1,18 @@
package=expat
-$(package)_version=2.4.1
+$(package)_version=2.4.8
$(package)_download_path=https://github.com/libexpat/libexpat/releases/download/R_$(subst .,_,$($(package)_version))/
$(package)_file_name=$(package)-$($(package)_version).tar.xz
-$(package)_sha256_hash=cf032d0dba9b928636548e32b327a2d66b1aab63c4f4a13dd132c2d1d2f2fb6a
+$(package)_sha256_hash=f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25
+# -D_DEFAULT_SOURCE defines __USE_MISC, which exposes additional
+# definitions in endian.h, which are required for a working
+# endianess check in configure when building with -flto.
define $(package)_set_vars
$(package)_config_opts=--disable-shared --without-docbook --without-tests --without-examples
$(package)_config_opts += --disable-dependency-tracking --enable-option-checking
$(package)_config_opts += --without-xmlwf
$(package)_config_opts_linux=--with-pic
- $(package)_cflags += -fno-lto
+ $(package)_cppflags += -D_DEFAULT_SOURCE
endef
define $(package)_config_cmds
diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk
index 25f483808c..bddc9b4871 100644
--- a/depends/packages/qt.mk
+++ b/depends/packages/qt.mk
@@ -177,6 +177,7 @@ $(package)_config_opts_mingw32 += "QMAKE_CFLAGS = '$($(package)_cflags) $($(pack
$(package)_config_opts_mingw32 += "QMAKE_CXX = '$($(package)_cxx)'"
$(package)_config_opts_mingw32 += "QMAKE_CXXFLAGS = '$($(package)_cxxflags) $($(package)_cppflags)'"
$(package)_config_opts_mingw32 += "QMAKE_LFLAGS = '$($(package)_ldflags)'"
+$(package)_config_opts_mingw32 += "QMAKE_LIB = '$($(package)_ar) rc'"
$(package)_config_opts_mingw32 += -device-option CROSS_COMPILE="$(host)-"
$(package)_config_opts_mingw32 += -pch
diff --git a/src/.clang-tidy b/src/.clang-tidy
index df2a080075..b9371b147b 100644
--- a/src/.clang-tidy
+++ b/src/.clang-tidy
@@ -5,6 +5,7 @@ misc-unused-using-decls,
modernize-use-default-member-init,
modernize-use-nullptr,
readability-redundant-declaration,
+readability-redundant-string-init,
'
WarningsAsErrors: '
bugprone-argument-comment,
@@ -12,4 +13,5 @@ misc-unused-using-decls,
modernize-use-default-member-init,
modernize-use-nullptr,
readability-redundant-declaration,
+readability-redundant-string-init,
'
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index d9195ad32e..f52964b033 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -117,6 +117,7 @@ BITCOIN_TESTS =\
test/prevector_tests.cpp \
test/raii_event_tests.cpp \
test/random_tests.cpp \
+ test/rbf_tests.cpp \
test/rest_tests.cpp \
test/reverselock_tests.cpp \
test/rpc_tests.cpp \
@@ -167,6 +168,7 @@ 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/scriptpubkeyman_tests.cpp
diff --git a/src/addrman.cpp b/src/addrman.cpp
index 204bb544c5..857ad73b2f 100644
--- a/src/addrman.cpp
+++ b/src/addrman.cpp
@@ -29,19 +29,19 @@ static constexpr uint32_t ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP{64};
/** Maximum number of times an address can occur in the new table */
static constexpr int32_t ADDRMAN_NEW_BUCKETS_PER_ADDRESS{8};
/** How old addresses can maximally be */
-static constexpr int64_t ADDRMAN_HORIZON_DAYS{30};
+static constexpr auto ADDRMAN_HORIZON{30 * 24h};
/** After how many failed attempts we give up on a new node */
static constexpr int32_t ADDRMAN_RETRIES{3};
/** How many successive failures are allowed ... */
static constexpr int32_t ADDRMAN_MAX_FAILURES{10};
-/** ... in at least this many days */
-static constexpr int64_t ADDRMAN_MIN_FAIL_DAYS{7};
+/** ... in at least this duration */
+static constexpr auto ADDRMAN_MIN_FAIL{7 * 24h};
/** How recent a successful connection should be before we allow an address to be evicted from tried */
-static constexpr int64_t ADDRMAN_REPLACEMENT_HOURS{4};
+static constexpr auto ADDRMAN_REPLACEMENT{4h};
/** The maximum number of tried addr collisions to store */
static constexpr size_t ADDRMAN_SET_TRIED_COLLISION_SIZE{10};
-/** The maximum time we'll spend trying to resolve a tried table collision, in seconds */
-static constexpr int64_t ADDRMAN_TEST_WINDOW{40*60}; // 40 minutes
+/** The maximum time we'll spend trying to resolve a tried table collision */
+static constexpr auto ADDRMAN_TEST_WINDOW{40min};
int AddrInfo::GetTriedBucket(const uint256& nKey, const NetGroupManager& netgroupman) const
{
@@ -64,36 +64,39 @@ int AddrInfo::GetBucketPosition(const uint256& nKey, bool fNew, int nBucket) con
return hash1 % ADDRMAN_BUCKET_SIZE;
}
-bool AddrInfo::IsTerrible(int64_t nNow) const
+bool AddrInfo::IsTerrible(NodeSeconds now) const
{
- if (nNow - nLastTry <= 60) { // never remove things tried in the last minute
+ if (now - m_last_try <= 1min) { // never remove things tried in the last minute
return false;
}
- if (nTime > nNow + 10 * 60) // came in a flying DeLorean
+ if (nTime > now + 10min) { // came in a flying DeLorean
return true;
+ }
- if (nNow - nTime > ADDRMAN_HORIZON_DAYS * 24 * 60 * 60) { // not seen in recent history
+ if (now - nTime > ADDRMAN_HORIZON) { // not seen in recent history
return true;
}
- if (nLastSuccess == 0 && nAttempts >= ADDRMAN_RETRIES) // tried N times and never a success
+ if (TicksSinceEpoch<std::chrono::seconds>(m_last_success) == 0 && nAttempts >= ADDRMAN_RETRIES) { // tried N times and never a success
return true;
+ }
- if (nNow - nLastSuccess > ADDRMAN_MIN_FAIL_DAYS * 24 * 60 * 60 && nAttempts >= ADDRMAN_MAX_FAILURES) // N successive failures in the last week
+ if (now - m_last_success > ADDRMAN_MIN_FAIL && nAttempts >= ADDRMAN_MAX_FAILURES) { // N successive failures in the last week
return true;
+ }
return false;
}
-double AddrInfo::GetChance(int64_t nNow) const
+double AddrInfo::GetChance(NodeSeconds now) const
{
double fChance = 1.0;
- int64_t nSinceLastTry = std::max<int64_t>(nNow - nLastTry, 0);
// deprioritize very recent attempts away
- if (nSinceLastTry < 60 * 10)
+ if (now - m_last_try < 10min) {
fChance *= 0.01;
+ }
// deprioritize 66% after each failed attempt, but at most 1/28th to avoid the search taking forever or overly penalizing outages.
fChance *= pow(0.66, std::min(nAttempts, 8));
@@ -540,7 +543,7 @@ void AddrManImpl::MakeTried(AddrInfo& info, int nId)
info.fInTried = true;
}
-bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty)
+bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, std::chrono::seconds time_penalty)
{
AssertLockHeld(cs);
@@ -552,15 +555,15 @@ bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, int64_
// Do not set a penalty for a source's self-announcement
if (addr == source) {
- nTimePenalty = 0;
+ time_penalty = 0s;
}
if (pinfo) {
// periodically update nTime
- bool fCurrentlyOnline = (GetAdjustedTime() - addr.nTime < 24 * 60 * 60);
- int64_t nUpdateInterval = (fCurrentlyOnline ? 60 * 60 : 24 * 60 * 60);
- if (pinfo->nTime < addr.nTime - nUpdateInterval - nTimePenalty) {
- pinfo->nTime = std::max((int64_t)0, addr.nTime - nTimePenalty);
+ const bool currently_online{AdjustedTime() - addr.nTime < 24h};
+ const auto update_interval{currently_online ? 1h : 24h};
+ if (pinfo->nTime < addr.nTime - update_interval - time_penalty) {
+ pinfo->nTime = std::max(NodeSeconds{0s}, addr.nTime - time_penalty);
}
// add services
@@ -587,7 +590,7 @@ bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, int64_
return false;
} else {
pinfo = Create(addr, source, &nId);
- pinfo->nTime = std::max((int64_t)0, (int64_t)pinfo->nTime - nTimePenalty);
+ pinfo->nTime = std::max(NodeSeconds{0s}, pinfo->nTime - time_penalty);
nNew++;
}
@@ -617,13 +620,13 @@ bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, int64_
return fInsert;
}
-bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, int64_t nTime)
+bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, NodeSeconds time)
{
AssertLockHeld(cs);
int nId;
- nLastGood = nTime;
+ m_last_good = time;
AddrInfo* pinfo = Find(addr, &nId);
@@ -633,8 +636,8 @@ bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, int64_t nT
AddrInfo& info = *pinfo;
// update info
- info.nLastSuccess = nTime;
- info.nLastTry = nTime;
+ info.m_last_success = time;
+ info.m_last_try = time;
info.nAttempts = 0;
// nTime is not updated here, to avoid leaking information about
// currently-connected peers.
@@ -671,11 +674,11 @@ bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, int64_t nT
}
}
-bool AddrManImpl::Add_(const std::vector<CAddress> &vAddr, const CNetAddr& source, int64_t nTimePenalty)
+bool AddrManImpl::Add_(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty)
{
int added{0};
for (std::vector<CAddress>::const_iterator it = vAddr.begin(); it != vAddr.end(); it++) {
- added += AddSingle(*it, source, nTimePenalty) ? 1 : 0;
+ added += AddSingle(*it, source, time_penalty) ? 1 : 0;
}
if (added > 0) {
LogPrint(BCLog::ADDRMAN, "Added %i addresses (of %i) from %s: %i tried, %i new\n", added, vAddr.size(), source.ToString(), nTried, nNew);
@@ -683,7 +686,7 @@ bool AddrManImpl::Add_(const std::vector<CAddress> &vAddr, const CNetAddr& sourc
return added > 0;
}
-void AddrManImpl::Attempt_(const CService& addr, bool fCountFailure, int64_t nTime)
+void AddrManImpl::Attempt_(const CService& addr, bool fCountFailure, NodeSeconds time)
{
AssertLockHeld(cs);
@@ -696,14 +699,14 @@ void AddrManImpl::Attempt_(const CService& addr, bool fCountFailure, int64_t nTi
AddrInfo& info = *pinfo;
// update info
- info.nLastTry = nTime;
- if (fCountFailure && info.nLastCountAttempt < nLastGood) {
- info.nLastCountAttempt = nTime;
+ info.m_last_try = time;
+ if (fCountFailure && info.m_last_count_attempt < m_last_good) {
+ info.m_last_count_attempt = time;
info.nAttempts++;
}
}
-std::pair<CAddress, int64_t> AddrManImpl::Select_(bool newOnly) const
+std::pair<CAddress, NodeSeconds> AddrManImpl::Select_(bool newOnly) const
{
AssertLockHeld(cs);
@@ -736,7 +739,7 @@ std::pair<CAddress, int64_t> AddrManImpl::Select_(bool newOnly) const
// With probability GetChance() * fChanceFactor, return the entry.
if (insecure_rand.randbits(30) < fChanceFactor * info.GetChance() * (1 << 30)) {
LogPrint(BCLog::ADDRMAN, "Selected %s from tried\n", info.ToString());
- return {info, info.nLastTry};
+ return {info, info.m_last_try};
}
// Otherwise start over with a (likely) different bucket, and increased chance factor.
fChanceFactor *= 1.2;
@@ -764,7 +767,7 @@ std::pair<CAddress, int64_t> AddrManImpl::Select_(bool newOnly) const
// With probability GetChance() * fChanceFactor, return the entry.
if (insecure_rand.randbits(30) < fChanceFactor * info.GetChance() * (1 << 30)) {
LogPrint(BCLog::ADDRMAN, "Selected %s from new\n", info.ToString());
- return {info, info.nLastTry};
+ return {info, info.m_last_try};
}
// Otherwise start over with a (likely) different bucket, and increased chance factor.
fChanceFactor *= 1.2;
@@ -785,7 +788,7 @@ std::vector<CAddress> AddrManImpl::GetAddr_(size_t max_addresses, size_t max_pct
}
// gather a list of random nodes, skipping those of low quality
- const int64_t now{GetAdjustedTime()};
+ const auto now{AdjustedTime()};
std::vector<CAddress> addresses;
for (unsigned int n = 0; n < vRandom.size(); n++) {
if (addresses.size() >= nNodes)
@@ -810,7 +813,7 @@ std::vector<CAddress> AddrManImpl::GetAddr_(size_t max_addresses, size_t max_pct
return addresses;
}
-void AddrManImpl::Connected_(const CService& addr, int64_t nTime)
+void AddrManImpl::Connected_(const CService& addr, NodeSeconds time)
{
AssertLockHeld(cs);
@@ -823,9 +826,10 @@ void AddrManImpl::Connected_(const CService& addr, int64_t nTime)
AddrInfo& info = *pinfo;
// update info
- int64_t nUpdateInterval = 20 * 60;
- if (nTime - info.nTime > nUpdateInterval)
- info.nTime = nTime;
+ const auto update_interval{20min};
+ if (time - info.nTime > update_interval) {
+ info.nTime = time;
+ }
}
void AddrManImpl::SetServices_(const CService& addr, ServiceFlags nServices)
@@ -870,22 +874,22 @@ void AddrManImpl::ResolveCollisions_()
int id_old = vvTried[tried_bucket][tried_bucket_pos];
AddrInfo& info_old = mapInfo[id_old];
- const auto current_time{GetAdjustedTime()};
+ const auto current_time{AdjustedTime()};
// Has successfully connected in last X hours
- if (current_time - info_old.nLastSuccess < ADDRMAN_REPLACEMENT_HOURS*(60*60)) {
+ if (current_time - info_old.m_last_success < ADDRMAN_REPLACEMENT) {
erase_collision = true;
- } else if (current_time - info_old.nLastTry < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { // attempted to connect and failed in last X hours
+ } else if (current_time - info_old.m_last_try < ADDRMAN_REPLACEMENT) { // attempted to connect and failed in last X hours
// Give address at least 60 seconds to successfully connect
- if (current_time - info_old.nLastTry > 60) {
+ if (current_time - info_old.m_last_try > 60s) {
LogPrint(BCLog::ADDRMAN, "Replacing %s with %s in tried table\n", info_old.ToString(), info_new.ToString());
// Replaces an existing address already in the tried table with the new address
Good_(info_new, false, current_time);
erase_collision = true;
}
- } else if (current_time - info_new.nLastSuccess > ADDRMAN_TEST_WINDOW) {
+ } else if (current_time - info_new.m_last_success > ADDRMAN_TEST_WINDOW) {
// If the collision hasn't resolved in some reasonable amount of time,
// just evict the old entry -- we must not be able to
// connect to it for some reason.
@@ -894,7 +898,7 @@ void AddrManImpl::ResolveCollisions_()
erase_collision = true;
}
} else { // Collision is not actually a collision anymore
- Good_(info_new, false, GetAdjustedTime());
+ Good_(info_new, false, AdjustedTime());
erase_collision = true;
}
}
@@ -907,7 +911,7 @@ void AddrManImpl::ResolveCollisions_()
}
}
-std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision_()
+std::pair<CAddress, NodeSeconds> AddrManImpl::SelectTriedCollision_()
{
AssertLockHeld(cs);
@@ -932,7 +936,7 @@ std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision_()
int tried_bucket_pos = newInfo.GetBucketPosition(nKey, false, tried_bucket);
const AddrInfo& info_old = mapInfo[vvTried[tried_bucket][tried_bucket_pos]];
- return {info_old, info_old.nLastTry};
+ return {info_old, info_old.m_last_try};
}
std::optional<AddressPosition> AddrManImpl::FindAddressEntry_(const CAddress& addr)
@@ -990,8 +994,9 @@ int AddrManImpl::CheckAddrman() const
int n = entry.first;
const AddrInfo& info = entry.second;
if (info.fInTried) {
- if (!info.nLastSuccess)
+ if (!TicksSinceEpoch<std::chrono::seconds>(info.m_last_success)) {
return -1;
+ }
if (info.nRefCount)
return -2;
setTried.insert(n);
@@ -1008,10 +1013,12 @@ int AddrManImpl::CheckAddrman() const
}
if (info.nRandomPos < 0 || (size_t)info.nRandomPos >= vRandom.size() || vRandom[info.nRandomPos] != n)
return -14;
- if (info.nLastTry < 0)
+ if (info.m_last_try < NodeSeconds{0s}) {
return -6;
- if (info.nLastSuccess < 0)
+ }
+ if (info.m_last_success < NodeSeconds{0s}) {
return -8;
+ }
}
if (setTried.size() != (size_t)nTried)
@@ -1067,29 +1074,29 @@ size_t AddrManImpl::size() const
return vRandom.size();
}
-bool AddrManImpl::Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, int64_t nTimePenalty)
+bool AddrManImpl::Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty)
{
LOCK(cs);
Check();
- auto ret = Add_(vAddr, source, nTimePenalty);
+ auto ret = Add_(vAddr, source, time_penalty);
Check();
return ret;
}
-bool AddrManImpl::Good(const CService& addr, int64_t nTime)
+bool AddrManImpl::Good(const CService& addr, NodeSeconds time)
{
LOCK(cs);
Check();
- auto ret = Good_(addr, /*test_before_evict=*/true, nTime);
+ auto ret = Good_(addr, /*test_before_evict=*/true, time);
Check();
return ret;
}
-void AddrManImpl::Attempt(const CService& addr, bool fCountFailure, int64_t nTime)
+void AddrManImpl::Attempt(const CService& addr, bool fCountFailure, NodeSeconds time)
{
LOCK(cs);
Check();
- Attempt_(addr, fCountFailure, nTime);
+ Attempt_(addr, fCountFailure, time);
Check();
}
@@ -1101,7 +1108,7 @@ void AddrManImpl::ResolveCollisions()
Check();
}
-std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision()
+std::pair<CAddress, NodeSeconds> AddrManImpl::SelectTriedCollision()
{
LOCK(cs);
Check();
@@ -1110,7 +1117,7 @@ std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision()
return ret;
}
-std::pair<CAddress, int64_t> AddrManImpl::Select(bool newOnly) const
+std::pair<CAddress, NodeSeconds> AddrManImpl::Select(bool newOnly) const
{
LOCK(cs);
Check();
@@ -1128,11 +1135,11 @@ std::vector<CAddress> AddrManImpl::GetAddr(size_t max_addresses, size_t max_pct,
return addresses;
}
-void AddrManImpl::Connected(const CService& addr, int64_t nTime)
+void AddrManImpl::Connected(const CService& addr, NodeSeconds time)
{
LOCK(cs);
Check();
- Connected_(addr, nTime);
+ Connected_(addr, time);
Check();
}
@@ -1184,19 +1191,19 @@ size_t AddrMan::size() const
return m_impl->size();
}
-bool AddrMan::Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, int64_t nTimePenalty)
+bool AddrMan::Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty)
{
- return m_impl->Add(vAddr, source, nTimePenalty);
+ return m_impl->Add(vAddr, source, time_penalty);
}
-bool AddrMan::Good(const CService& addr, int64_t nTime)
+bool AddrMan::Good(const CService& addr, NodeSeconds time)
{
- return m_impl->Good(addr, nTime);
+ return m_impl->Good(addr, time);
}
-void AddrMan::Attempt(const CService& addr, bool fCountFailure, int64_t nTime)
+void AddrMan::Attempt(const CService& addr, bool fCountFailure, NodeSeconds time)
{
- m_impl->Attempt(addr, fCountFailure, nTime);
+ m_impl->Attempt(addr, fCountFailure, time);
}
void AddrMan::ResolveCollisions()
@@ -1204,12 +1211,12 @@ void AddrMan::ResolveCollisions()
m_impl->ResolveCollisions();
}
-std::pair<CAddress, int64_t> AddrMan::SelectTriedCollision()
+std::pair<CAddress, NodeSeconds> AddrMan::SelectTriedCollision()
{
return m_impl->SelectTriedCollision();
}
-std::pair<CAddress, int64_t> AddrMan::Select(bool newOnly) const
+std::pair<CAddress, NodeSeconds> AddrMan::Select(bool newOnly) const
{
return m_impl->Select(newOnly);
}
@@ -1219,9 +1226,9 @@ std::vector<CAddress> AddrMan::GetAddr(size_t max_addresses, size_t max_pct, std
return m_impl->GetAddr(max_addresses, max_pct, network);
}
-void AddrMan::Connected(const CService& addr, int64_t nTime)
+void AddrMan::Connected(const CService& addr, NodeSeconds time)
{
- m_impl->Connected(addr, nTime);
+ m_impl->Connected(addr, time);
}
void AddrMan::SetServices(const CService& addr, ServiceFlags nServices)
diff --git a/src/addrman.h b/src/addrman.h
index a0063e8a9c..b70c6a48ad 100644
--- a/src/addrman.h
+++ b/src/addrman.h
@@ -11,6 +11,7 @@
#include <protocol.h>
#include <streams.h>
#include <timedata.h>
+#include <util/time.h>
#include <cstdint>
#include <memory>
@@ -107,23 +108,23 @@ public:
*
* @param[in] vAddr Address records to attempt to add.
* @param[in] source The address of the node that sent us these addr records.
- * @param[in] nTimePenalty A "time penalty" to apply to the address record's nTime. If a peer
+ * @param[in] time_penalty A "time penalty" to apply to the address record's nTime. If a peer
* sends us an address record with nTime=n, then we'll add it to our
- * addrman with nTime=(n - nTimePenalty).
+ * addrman with nTime=(n - time_penalty).
* @return true if at least one address is successfully added. */
- bool Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, int64_t nTimePenalty = 0);
+ bool Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty = 0s);
/**
* Mark an address record as accessible and attempt to move it to addrman's tried table.
*
* @param[in] addr Address record to attempt to move to tried table.
- * @param[in] nTime The time that we were last connected to this peer.
+ * @param[in] time The time that we were last connected to this peer.
* @return true if the address is successfully moved from the new table to the tried table.
*/
- bool Good(const CService& addr, int64_t nTime = GetAdjustedTime());
+ bool Good(const CService& addr, NodeSeconds time = AdjustedTime());
//! Mark an entry as connection attempted to.
- void Attempt(const CService& addr, bool fCountFailure, int64_t nTime = GetAdjustedTime());
+ void Attempt(const CService& addr, bool fCountFailure, NodeSeconds time = AdjustedTime());
//! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions.
void ResolveCollisions();
@@ -133,18 +134,18 @@ public:
* attempting to evict.
*
* @return CAddress The record for the selected tried peer.
- * int64_t The last time we attempted to connect to that peer.
+ * seconds The last time we attempted to connect to that peer.
*/
- std::pair<CAddress, int64_t> SelectTriedCollision();
+ std::pair<CAddress, NodeSeconds> SelectTriedCollision();
/**
* Choose an address to connect to.
*
* @param[in] newOnly Whether to only select addresses from the new table.
* @return CAddress The record for the selected peer.
- * int64_t The last time we attempted to connect to that peer.
+ * seconds The last time we attempted to connect to that peer.
*/
- std::pair<CAddress, int64_t> Select(bool newOnly = false) const;
+ std::pair<CAddress, NodeSeconds> Select(bool newOnly = false) const;
/**
* Return all or many randomly selected addresses, optionally by network.
@@ -166,9 +167,9 @@ public:
* not leak information about currently connected peers.
*
* @param[in] addr The address of the peer we were connected to
- * @param[in] nTime The time that we were last connected to this peer
+ * @param[in] time The time that we were last connected to this peer
*/
- void Connected(const CService& addr, int64_t nTime = GetAdjustedTime());
+ void Connected(const CService& addr, NodeSeconds time = AdjustedTime());
//! Update an entry's service bits.
void SetServices(const CService& addr, ServiceFlags nServices);
diff --git a/src/addrman_impl.h b/src/addrman_impl.h
index 9d98cdde54..a73a026940 100644
--- a/src/addrman_impl.h
+++ b/src/addrman_impl.h
@@ -11,7 +11,9 @@
#include <protocol.h>
#include <serialize.h>
#include <sync.h>
+#include <timedata.h>
#include <uint256.h>
+#include <util/time.h>
#include <cstdint>
#include <optional>
@@ -38,16 +40,16 @@ class AddrInfo : public CAddress
{
public:
//! last try whatsoever by us (memory only)
- int64_t nLastTry{0};
+ NodeSeconds m_last_try{0s};
//! last counted attempt (memory only)
- int64_t nLastCountAttempt{0};
+ NodeSeconds m_last_count_attempt{0s};
//! where knowledge about this address first came from
CNetAddr source;
//! last successful connection by us
- int64_t nLastSuccess{0};
+ NodeSeconds m_last_success{0s};
//! connection attempts since last successful attempt
int nAttempts{0};
@@ -64,7 +66,7 @@ public:
SERIALIZE_METHODS(AddrInfo, obj)
{
READWRITEAS(CAddress, obj);
- READWRITE(obj.source, obj.nLastSuccess, obj.nAttempts);
+ READWRITE(obj.source, Using<ChronoFormatter<int64_t>>(obj.m_last_success), obj.nAttempts);
}
AddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn), source(addrSource)
@@ -91,10 +93,10 @@ public:
int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const;
//! Determine whether the statistics about this entry are bad enough so that it can just be deleted
- bool IsTerrible(int64_t nNow = GetAdjustedTime()) const;
+ bool IsTerrible(NodeSeconds now = AdjustedTime()) const;
//! Calculate the relative chance this entry should be given when selecting nodes to connect to
- double GetChance(int64_t nNow = GetAdjustedTime()) const;
+ double GetChance(NodeSeconds now = AdjustedTime()) const;
};
class AddrManImpl
@@ -112,26 +114,26 @@ public:
size_t size() const EXCLUSIVE_LOCKS_REQUIRED(!cs);
- bool Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, int64_t nTimePenalty)
+ bool Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty)
EXCLUSIVE_LOCKS_REQUIRED(!cs);
- bool Good(const CService& addr, int64_t nTime)
+ bool Good(const CService& addr, NodeSeconds time)
EXCLUSIVE_LOCKS_REQUIRED(!cs);
- void Attempt(const CService& addr, bool fCountFailure, int64_t nTime)
+ void Attempt(const CService& addr, bool fCountFailure, NodeSeconds time)
EXCLUSIVE_LOCKS_REQUIRED(!cs);
void ResolveCollisions() EXCLUSIVE_LOCKS_REQUIRED(!cs);
- std::pair<CAddress, int64_t> SelectTriedCollision() EXCLUSIVE_LOCKS_REQUIRED(!cs);
+ std::pair<CAddress, NodeSeconds> SelectTriedCollision() EXCLUSIVE_LOCKS_REQUIRED(!cs);
- std::pair<CAddress, int64_t> Select(bool newOnly) const
+ std::pair<CAddress, NodeSeconds> Select(bool newOnly) const
EXCLUSIVE_LOCKS_REQUIRED(!cs);
std::vector<CAddress> GetAddr(size_t max_addresses, size_t max_pct, std::optional<Network> network) const
EXCLUSIVE_LOCKS_REQUIRED(!cs);
- void Connected(const CService& addr, int64_t nTime)
+ void Connected(const CService& addr, NodeSeconds time)
EXCLUSIVE_LOCKS_REQUIRED(!cs);
void SetServices(const CService& addr, ServiceFlags nServices)
@@ -202,7 +204,7 @@ private:
int vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE] GUARDED_BY(cs);
//! last time Good was called (memory only). Initially set to 1 so that "never" is strictly worse.
- int64_t nLastGood GUARDED_BY(cs){1};
+ NodeSeconds m_last_good GUARDED_BY(cs){1s};
//! Holds addrs inserted into tried table that collide with existing entries. Test-before-evict discipline used to resolve these collisions.
std::set<int> m_tried_collisions;
@@ -233,25 +235,25 @@ private:
/** Attempt to add a single address to addrman's new table.
* @see AddrMan::Add() for parameters. */
- bool AddSingle(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty) EXCLUSIVE_LOCKS_REQUIRED(cs);
+ bool AddSingle(const CAddress& addr, const CNetAddr& source, std::chrono::seconds time_penalty) EXCLUSIVE_LOCKS_REQUIRED(cs);
- bool Good_(const CService& addr, bool test_before_evict, int64_t time) EXCLUSIVE_LOCKS_REQUIRED(cs);
+ bool Good_(const CService& addr, bool test_before_evict, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs);
- bool Add_(const std::vector<CAddress> &vAddr, const CNetAddr& source, int64_t nTimePenalty) EXCLUSIVE_LOCKS_REQUIRED(cs);
+ bool Add_(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty) EXCLUSIVE_LOCKS_REQUIRED(cs);
- void Attempt_(const CService& addr, bool fCountFailure, int64_t nTime) EXCLUSIVE_LOCKS_REQUIRED(cs);
+ void Attempt_(const CService& addr, bool fCountFailure, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs);
- std::pair<CAddress, int64_t> Select_(bool newOnly) const EXCLUSIVE_LOCKS_REQUIRED(cs);
+ std::pair<CAddress, NodeSeconds> Select_(bool newOnly) const EXCLUSIVE_LOCKS_REQUIRED(cs);
std::vector<CAddress> GetAddr_(size_t max_addresses, size_t max_pct, std::optional<Network> network) const EXCLUSIVE_LOCKS_REQUIRED(cs);
- void Connected_(const CService& addr, int64_t nTime) EXCLUSIVE_LOCKS_REQUIRED(cs);
+ void Connected_(const CService& addr, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs);
void SetServices_(const CService& addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs);
void ResolveCollisions_() EXCLUSIVE_LOCKS_REQUIRED(cs);
- std::pair<CAddress, int64_t> SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs);
+ std::pair<CAddress, NodeSeconds> SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs);
std::optional<AddressPosition> FindAddressEntry_(const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(cs);
diff --git a/src/bench/addrman.cpp b/src/bench/addrman.cpp
index 76300f4db8..2600b03022 100644
--- a/src/bench/addrman.cpp
+++ b/src/bench/addrman.cpp
@@ -43,7 +43,7 @@ static void CreateAddresses()
CAddress ret(CService(addr, port), NODE_NETWORK);
- ret.nTime = GetAdjustedTime();
+ ret.nTime = AdjustedTime();
return ret;
};
diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp
index eaefb9b63a..a80ec3703c 100644
--- a/src/bench/coin_selection.cpp
+++ b/src/bench/coin_selection.cpp
@@ -56,10 +56,10 @@ static void CoinSelection(benchmark::Bench& bench)
addCoin(3 * COIN, wallet, wtxs);
// Create coins
- std::vector<COutput> coins;
+ wallet::CoinsResult available_coins;
for (const auto& wtx : wtxs) {
const auto txout = wtx->tx->vout.at(0);
- coins.emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
+ available_coins.bech32.emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
}
const CoinEligibilityFilter filter_standard(1, 6, 0);
@@ -76,7 +76,7 @@ static void CoinSelection(benchmark::Bench& bench)
/*avoid_partial=*/ false,
};
bench.run([&] {
- auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, coin_selection_params);
+ auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, available_coins, coin_selection_params, /*allow_mixed_output_types=*/true);
assert(result);
assert(result->GetSelectedValue() == 1003 * COIN);
assert(result->GetInputSet().size() == 2);
diff --git a/src/blockfilter.cpp b/src/blockfilter.cpp
index 1ad6872143..0ff79bb3ca 100644
--- a/src/blockfilter.cpp
+++ b/src/blockfilter.cpp
@@ -148,7 +148,7 @@ bool GCSFilter::MatchAny(const ElementSet& elements) const
const std::string& BlockFilterTypeName(BlockFilterType filter_type)
{
- static std::string unknown_retval = "";
+ static std::string unknown_retval;
auto it = g_filter_types.find(filter_type);
return it != g_filter_types.end() ? it->second : unknown_retval;
}
diff --git a/src/chain.cpp b/src/chain.cpp
index b8158f7b0b..0f898bafd5 100644
--- a/src/chain.cpp
+++ b/src/chain.cpp
@@ -4,6 +4,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chain.h>
+#include <tinyformat.h>
#include <util/time.h>
std::string CBlockFileInfo::ToString() const
@@ -11,6 +12,12 @@ std::string CBlockFileInfo::ToString() const
return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, FormatISO8601Date(nTimeFirst), FormatISO8601Date(nTimeLast));
}
+std::string CBlockIndex::ToString() const
+{
+ return strprintf("CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)",
+ pprev, nHeight, hashMerkleRoot.ToString(), GetBlockHash().ToString());
+}
+
void CChain::SetTip(CBlockIndex *pindex) {
if (pindex == nullptr) {
vChain.clear();
diff --git a/src/chain.h b/src/chain.h
index ecc2ae732f..627a3dfab2 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -11,7 +11,6 @@
#include <flatfile.h>
#include <primitives/block.h>
#include <sync.h>
-#include <tinyformat.h>
#include <uint256.h>
#include <vector>
@@ -263,6 +262,7 @@ public:
uint256 GetBlockHash() const
{
+ assert(phashBlock != nullptr);
return *phashBlock;
}
@@ -301,13 +301,7 @@ public:
return pbegin[(pend - pbegin) / 2];
}
- std::string ToString() const
- {
- return strprintf("CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)",
- pprev, nHeight,
- hashMerkleRoot.ToString(),
- GetBlockHash().ToString());
- }
+ std::string ToString() const;
//! Check whether this block index entry is valid up to the passed validity level.
bool IsValid(enum BlockStatus nUpTo = BLOCK_VALID_TRANSACTIONS) const
@@ -402,7 +396,7 @@ public:
READWRITE(obj.nNonce);
}
- uint256 GetBlockHash() const
+ uint256 ConstructBlockHash() const
{
CBlockHeader block;
block.nVersion = nVersion;
@@ -414,16 +408,8 @@ public:
return block.GetHash();
}
-
- std::string ToString() const
- {
- std::string str = "CDiskBlockIndex(";
- str += CBlockIndex::ToString();
- str += strprintf("\n hashBlock=%s, hashPrev=%s)",
- GetBlockHash().ToString(),
- hashPrev.ToString());
- return str;
- }
+ uint256 GetBlockHash() = delete;
+ std::string ToString() = delete;
};
/** An in-memory indexed chain of blocks. */
diff --git a/src/external_signer.cpp b/src/external_signer.cpp
index d125fe479b..6bab0a856c 100644
--- a/src/external_signer.cpp
+++ b/src/external_signer.cpp
@@ -49,7 +49,7 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS
if (signer.m_fingerprint.compare(fingerprintStr) == 0) duplicate = true;
}
if (duplicate) break;
- std::string name = "";
+ std::string name;
const UniValue& model_field = find_value(signer, "model");
if (model_field.isStr() && model_field.getValStr() != "") {
name += model_field.getValStr();
diff --git a/src/net.cpp b/src/net.cpp
index e9aa7ee43b..c4aaac4986 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -85,8 +85,8 @@ static constexpr int DNSSEEDS_DELAY_PEER_THRESHOLD = 1000; // "many" vs "few" pe
/** The default timeframe for -maxuploadtarget. 1 day. */
static constexpr std::chrono::seconds MAX_UPLOAD_TIMEFRAME{60 * 60 * 24};
-// We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization.
-#define FEELER_SLEEP_WINDOW 1
+// A random time period (0 to 1 seconds) is added to feeler connections to prevent synchronization.
+static constexpr auto FEELER_SLEEP_WINDOW{1s};
/** Used to pass flags to the Bind() function */
enum BindFlags {
@@ -187,7 +187,7 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn)
// it'll get a pile of addresses with newer timestamps.
// Seed nodes are given a random 'last seen time' of between one and two
// weeks ago.
- const int64_t nOneWeek = 7*24*60*60;
+ const auto one_week{7 * 24h};
std::vector<CAddress> vSeedsOut;
FastRandomContext rng;
CDataStream s(vSeedsIn, SER_NETWORK, PROTOCOL_VERSION | ADDRV2_FORMAT);
@@ -195,7 +195,7 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn)
CService endpoint;
s >> endpoint;
CAddress addr{endpoint, GetDesirableServiceFlags(NODE_NONE)};
- addr.nTime = GetTime() - rng.randrange(nOneWeek) - nOneWeek;
+ addr.nTime = rng.rand_uniform_delay(Now<NodeSeconds>() - one_week, -one_week);
LogPrint(BCLog::NET, "Added hardcoded seed: %s\n", addr.ToString());
vSeedsOut.push_back(addr);
}
@@ -452,10 +452,9 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
}
}
- /// debug print
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "trying connection %s lastseen=%.1fhrs\n",
- pszDest ? pszDest : addrConnect.ToString(),
- pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime) / 3600.0);
+ pszDest ? pszDest : addrConnect.ToString(),
+ Ticks<HoursDouble>(pszDest ? 0h : AdjustedTime() - addrConnect.nTime));
// Resolve
const uint16_t default_port{pszDest != nullptr ? Params().GetDefaultPort(pszDest) :
@@ -1469,9 +1468,8 @@ void CConnman::ThreadDNSAddressSeed()
unsigned int nMaxIPs = 256; // Limits number of IPs learned from a DNS seed
if (LookupHost(host, vIPs, nMaxIPs, true)) {
for (const CNetAddr& ip : vIPs) {
- int nOneDay = 24*3600;
CAddress addr = CAddress(CService(ip, Params().GetDefaultPort()), requiredServiceBits);
- addr.nTime = GetTime() - 3*nOneDay - rng.randrange(4*nOneDay); // use a random age between 3 and 7 days old
+ addr.nTime = rng.rand_uniform_delay(Now<NodeSeconds>() - 3 * 24h, -4 * 24h); // use a random age between 3 and 7 days old
vAdd.push_back(addr);
found++;
}
@@ -1568,6 +1566,7 @@ int CConnman::GetExtraBlockRelayCount() const
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET_OPEN_CONNECTION);
+ FastRandomContext rng;
// Connect to specific addresses
if (!connect.empty())
{
@@ -1736,7 +1735,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
addrman.ResolveCollisions();
- int64_t nANow = GetAdjustedTime();
+ const auto nANow{AdjustedTime()};
int nTries = 0;
while (!interruptNet)
{
@@ -1759,7 +1758,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
break;
CAddress addr;
- int64_t addr_last_try{0};
+ NodeSeconds addr_last_try{0s};
if (fFeeler) {
// First, try to get a tried table collision address. This returns
@@ -1799,8 +1798,9 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
continue;
// only consider very recently tried nodes after 30 failed attempts
- if (nANow - addr_last_try < 600 && nTries < 30)
+ if (nANow - addr_last_try < 10min && nTries < 30) {
continue;
+ }
// for non-feelers, require all the services we'll want,
// for feelers, only require they be a full node (only because most
@@ -1821,12 +1821,11 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
}
if (addrConnect.IsValid()) {
-
if (fFeeler) {
// Add small amount of random noise before connection to avoid synchronization.
- int randsleep = GetRand<int>(FEELER_SLEEP_WINDOW * 1000);
- if (!interruptNet.sleep_for(std::chrono::milliseconds(randsleep)))
+ if (!interruptNet.sleep_for(rng.rand_uniform_duration<CThreadInterrupt::Clock>(FEELER_SLEEP_WINDOW))) {
return;
+ }
LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString());
}
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index a845be9e62..0e10fa5f9d 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -29,6 +29,7 @@
#include <scheduler.h>
#include <streams.h>
#include <sync.h>
+#include <timedata.h>
#include <tinyformat.h>
#include <txmempool.h>
#include <txorphanage.h>
@@ -2929,7 +2930,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// indicate to the peer that we will participate in addr relay.
if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload())
{
- CAddress addr{GetLocalAddress(pfrom.addr), peer->m_our_services, (uint32_t)GetAdjustedTime()};
+ CAddress addr{GetLocalAddress(pfrom.addr), peer->m_our_services, AdjustedTime()};
FastRandomContext insecure_rand;
if (addr.IsRoutable())
{
@@ -3134,8 +3135,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// Store the new addresses
std::vector<CAddress> vAddrOk;
- int64_t nNow = GetAdjustedTime();
- int64_t nSince = nNow - 10 * 60;
+ const auto current_a_time{AdjustedTime()};
// Update/increment addr rate limiting bucket.
const auto current_time{GetTime<std::chrono::microseconds>()};
@@ -3171,8 +3171,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (!MayHaveUsefulAddressDB(addr.nServices) && !HasAllDesirableServiceFlags(addr.nServices))
continue;
- if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60)
- addr.nTime = nNow - 5 * 24 * 60 * 60;
+ if (addr.nTime <= NodeSeconds{100000000s} || addr.nTime > current_a_time + 10min) {
+ addr.nTime = current_a_time - 5 * 24h;
+ }
AddAddressKnown(*peer, addr);
if (m_banman && (m_banman->IsDiscouraged(addr) || m_banman->IsBanned(addr))) {
// Do not process banned/discouraged addresses beyond remembering we received them
@@ -3180,7 +3181,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}
++num_proc;
bool fReachable = IsReachable(addr);
- if (addr.nTime > nSince && !peer->m_getaddr_sent && vAddr.size() <= 10 && addr.IsRoutable()) {
+ if (addr.nTime > current_a_time - 10min && !peer->m_getaddr_sent && vAddr.size() <= 10 && addr.IsRoutable()) {
// Relay to a limited number of other nodes
RelayAddress(pfrom.GetId(), addr, fReachable);
}
@@ -3193,7 +3194,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
LogPrint(BCLog::NET, "Received addr: %u addresses (%u processed, %u rate-limited) from peer=%d\n",
vAddr.size(), num_proc, num_rate_limit, pfrom.GetId());
- m_addrman.Add(vAddrOk, pfrom.addr, 2 * 60 * 60);
+ m_addrman.Add(vAddrOk, pfrom.addr, 2h);
if (vAddr.size() < 1000) peer->m_getaddr_sent = false;
// AddrFetch: Require multiple addresses to avoid disconnecting on self-announcements
@@ -4682,7 +4683,7 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros
peer.m_addr_known->reset();
}
if (std::optional<CService> local_service = GetLocalAddrForPeer(node)) {
- CAddress local_addr{*local_service, peer.m_our_services, (uint32_t)GetAdjustedTime()};
+ CAddress local_addr{*local_service, peer.m_our_services, AdjustedTime()};
FastRandomContext insecure_rand;
PushAddress(peer, local_addr, insecure_rand);
}
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index be7e3992f0..46d45377fa 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -36,7 +36,6 @@
#include <shutdown.h>
#include <support/allocators/secure.h>
#include <sync.h>
-#include <timedata.h>
#include <txmempool.h>
#include <uint256.h>
#include <univalue.h>
diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp
index f7f6ae4480..ec48194ee9 100644
--- a/src/primitives/transaction.cpp
+++ b/src/primitives/transaction.cpp
@@ -7,10 +7,15 @@
#include <consensus/amount.h>
#include <hash.h>
+#include <script/script.h>
+#include <serialize.h>
#include <tinyformat.h>
+#include <uint256.h>
#include <util/strencodings.h>
+#include <version.h>
-#include <assert.h>
+#include <cassert>
+#include <stdexcept>
std::string COutPoint::ToString() const
{
diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h
index fb98fb6868..f496ea022e 100644
--- a/src/primitives/transaction.h
+++ b/src/primitives/transaction.h
@@ -6,13 +6,21 @@
#ifndef BITCOIN_PRIMITIVES_TRANSACTION_H
#define BITCOIN_PRIMITIVES_TRANSACTION_H
-#include <stdint.h>
#include <consensus/amount.h>
+#include <prevector.h>
#include <script/script.h>
#include <serialize.h>
#include <uint256.h>
+#include <cstddef>
+#include <cstdint>
+#include <ios>
+#include <limits>
+#include <memory>
+#include <string>
#include <tuple>
+#include <utility>
+#include <vector>
/**
* A flag that is ORed into the protocol version to designate that a transaction
@@ -303,7 +311,7 @@ private:
public:
/** Convert a CMutableTransaction into a CTransaction. */
explicit CTransaction(const CMutableTransaction& tx);
- CTransaction(CMutableTransaction&& tx);
+ explicit CTransaction(CMutableTransaction&& tx);
template <typename Stream>
inline void Serialize(Stream& s) const {
@@ -368,7 +376,7 @@ struct CMutableTransaction
int32_t nVersion;
uint32_t nLockTime;
- CMutableTransaction();
+ explicit CMutableTransaction();
explicit CMutableTransaction(const CTransaction& tx);
template <typename Stream>
diff --git a/src/protocol.h b/src/protocol.h
index da2d24aff3..b85dc0d820 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -11,6 +11,7 @@
#include <serialize.h>
#include <streams.h>
#include <uint256.h>
+#include <util/time.h>
#include <cstdint>
#include <limits>
@@ -352,7 +353,7 @@ static inline bool MayHaveUsefulAddressDB(ServiceFlags services)
/** A CService with information about it as peer */
class CAddress : public CService
{
- static constexpr uint32_t TIME_INIT{100000000};
+ static constexpr std::chrono::seconds TIME_INIT{100000000};
/** Historically, CAddress disk serialization stored the CLIENT_VERSION, optionally OR'ed with
* the ADDRV2_FORMAT flag to indicate V2 serialization. The first field has since been
@@ -382,7 +383,7 @@ class CAddress : public CService
public:
CAddress() : CService{} {};
CAddress(CService ipIn, ServiceFlags nServicesIn) : CService{ipIn}, nServices{nServicesIn} {};
- CAddress(CService ipIn, ServiceFlags nServicesIn, uint32_t nTimeIn) : CService{ipIn}, nTime{nTimeIn}, nServices{nServicesIn} {};
+ CAddress(CService ipIn, ServiceFlags nServicesIn, NodeSeconds time) : CService{ipIn}, nTime{time}, nServices{nServicesIn} {};
SERIALIZE_METHODS(CAddress, obj)
{
@@ -415,7 +416,7 @@ public:
use_v2 = s.GetVersion() & ADDRV2_FORMAT;
}
- READWRITE(obj.nTime);
+ READWRITE(Using<LossyChronoFormatter<uint32_t>>(obj.nTime));
// nServices is serialized as CompactSize in V2; as uint64_t in V1.
if (use_v2) {
uint64_t services_tmp;
@@ -430,8 +431,8 @@ public:
SerReadWriteMany(os, ser_action, ReadWriteAsHelper<CService>(obj));
}
- //! Always included in serialization.
- uint32_t nTime{TIME_INIT};
+ //! Always included in serialization. The behavior is unspecified if the value is not representable as uint32_t.
+ NodeSeconds nTime{TIME_INIT};
//! Serialized as uint64_t in V1, and as CompactSize in V2.
ServiceFlags nServices{NODE_NONE};
diff --git a/src/psbt.h b/src/psbt.h
index c390bb67d3..eef7d7dd3b 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -893,6 +893,9 @@ struct PSBTOutput
s >> leaf_hashes;
size_t after_hashes = s.size();
size_t hashes_len = before_hashes - after_hashes;
+ if (hashes_len > value_len) {
+ throw std::ios_base::failure("Output Taproot BIP32 keypath has an invalid length");
+ }
size_t origin_len = value_len - hashes_len;
m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len)));
break;
diff --git a/src/random.h b/src/random.h
index b92c29f0be..5fe20c5f76 100644
--- a/src/random.h
+++ b/src/random.h
@@ -11,6 +11,7 @@
#include <span.h>
#include <uint256.h>
+#include <cassert>
#include <chrono>
#include <cstdint>
#include <limits>
@@ -236,13 +237,19 @@ public:
template <typename Tp>
Tp rand_uniform_delay(const Tp& time, typename Tp::duration range)
{
- using Dur = typename Tp::duration;
- Dur dur{range.count() > 0 ? /* interval [0..range) */ Dur{randrange(range.count())} :
- range.count() < 0 ? /* interval (range..0] */ -Dur{randrange(-range.count())} :
- /* interval [0..0] */ Dur{0}};
- return time + dur;
+ return time + rand_uniform_duration<Tp>(range);
}
+ /** Generate a uniform random duration in the range from 0 (inclusive) to range (exclusive). */
+ template <typename Chrono>
+ typename Chrono::duration rand_uniform_duration(typename Chrono::duration range) noexcept
+ {
+ using Dur = typename Chrono::duration;
+ return range.count() > 0 ? /* interval [0..range) */ Dur{randrange(range.count())} :
+ range.count() < 0 ? /* interval (range..0] */ -Dur{randrange(-range.count())} :
+ /* interval [0..0] */ Dur{0};
+ };
+
// Compatibility with the C++11 UniformRandomBitGenerator concept
typedef uint64_t result_type;
static constexpr uint64_t min() { return 0; }
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 1635851afb..8f116a05ef 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -397,7 +397,7 @@ static RPCHelpMan syncwithvalidationinterfacequeue()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
SyncWithValidationInterfaceQueue();
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -614,11 +614,12 @@ const RPCResult getblock_vin{
{
{RPCResult::Type::BOOL, "generated", "Coinbase or not"},
{RPCResult::Type::NUM, "height", "The height of the prevout"},
- {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT},
+ {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
- {RPCResult::Type::STR, "asm", "The asm"},
- {RPCResult::Type::STR_HEX, "hex", "The hex"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
}},
@@ -1015,9 +1016,9 @@ static RPCHelpMan gettxout()
{RPCResult::Type::NUM, "confirmations", "The number of confirmations"},
{RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "", {
- {RPCResult::Type::STR, "asm", "The asm"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", "The hex"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg pubkeyhash"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
}},
@@ -1055,11 +1056,11 @@ static RPCHelpMan gettxout()
LOCK(mempool.cs);
CCoinsViewMemPool view(coins_view, mempool);
if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {
- return NullUniValue;
+ return UniValue::VNULL;
}
} else {
if (!coins_view->GetCoin(out, coin)) {
- return NullUniValue;
+ return UniValue::VNULL;
}
}
@@ -1497,7 +1498,7 @@ static RPCHelpMan preciousblock()
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -1538,7 +1539,7 @@ static RPCHelpMan invalidateblock()
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -1578,7 +1579,7 @@ static RPCHelpMan reconsiderblock()
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -2097,7 +2098,7 @@ static RPCHelpMan scantxoutset()
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// no scan in progress
- return NullUniValue;
+ return UniValue::VNULL;
}
result.pushKV("progress", g_scan_progress.load());
return result;
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index ea6db1e9a0..2902b35865 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -476,7 +476,7 @@ static RPCHelpMan prioritisetransaction()
static UniValue BIP22ValidationResult(const BlockValidationState& state)
{
if (state.IsValid())
- return NullUniValue;
+ return UniValue::VNULL;
if (state.IsError())
throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString());
@@ -1040,7 +1040,7 @@ static RPCHelpMan submitheader()
BlockValidationState state;
chainman.ProcessNewBlockHeaders({h}, state);
- if (state.IsValid()) return NullUniValue;
+ if (state.IsValid()) return UniValue::VNULL;
if (state.IsError()) {
throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString());
}
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 27eea824bc..059be61bb4 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -60,7 +60,7 @@ static RPCHelpMan getconnectioncount()
NodeContext& node = EnsureAnyNodeContext(request.context);
const CConnman& connman = EnsureConnman(node);
- return (int)connman.GetNodeCount(ConnectionDirection::Both);
+ return connman.GetNodeCount(ConnectionDirection::Both);
},
};
}
@@ -84,7 +84,7 @@ static RPCHelpMan ping()
// Request that each node send a ping during next message processing pass
peerman.SendPings();
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -304,7 +304,7 @@ static RPCHelpMan addnode()
{
CAddress addr;
connman.OpenNetworkConnection(addr, false, nullptr, strNode.c_str(), ConnectionType::MANUAL);
- return NullUniValue;
+ return UniValue::VNULL;
}
if (strCommand == "add")
@@ -320,7 +320,7 @@ static RPCHelpMan addnode()
}
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -423,7 +423,7 @@ static RPCHelpMan disconnectnode()
throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED, "Node not found in connected nodes");
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -640,9 +640,9 @@ static RPCHelpMan getnetworkinfo()
obj.pushKV("timeoffset", GetTimeOffset());
if (node.connman) {
obj.pushKV("networkactive", node.connman->GetNetworkActive());
- obj.pushKV("connections", (int)node.connman->GetNodeCount(ConnectionDirection::Both));
- obj.pushKV("connections_in", (int)node.connman->GetNodeCount(ConnectionDirection::In));
- obj.pushKV("connections_out", (int)node.connman->GetNodeCount(ConnectionDirection::Out));
+ obj.pushKV("connections", node.connman->GetNodeCount(ConnectionDirection::Both));
+ obj.pushKV("connections_in", node.connman->GetNodeCount(ConnectionDirection::In));
+ obj.pushKV("connections_out", node.connman->GetNodeCount(ConnectionDirection::Out));
}
obj.pushKV("networks", GetNetworksInfo());
obj.pushKV("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
@@ -745,7 +745,7 @@ static RPCHelpMan setban()
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Unban failed. Requested address/subnet was not previously manually banned.");
}
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -819,7 +819,7 @@ static RPCHelpMan clearbanned()
node.banman->ClearBanned();
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -894,7 +894,7 @@ static RPCHelpMan getnodeaddresses()
for (const CAddress& addr : vAddr) {
UniValue obj(UniValue::VOBJ);
- obj.pushKV("time", (int)addr.nTime);
+ obj.pushKV("time", int64_t{TicksSinceEpoch<std::chrono::seconds>(addr.nTime)});
obj.pushKV("services", (uint64_t)addr.nServices);
obj.pushKV("address", addr.ToStringIP());
obj.pushKV("port", addr.GetPort());
@@ -942,7 +942,7 @@ static RPCHelpMan addpeeraddress()
if (LookupHost(addr_string, net_addr, false)) {
CAddress address{{net_addr, port}, ServiceFlags{NODE_NETWORK | NODE_WITNESS}};
- address.nTime = GetAdjustedTime();
+ address.nTime = AdjustedTime();
// The source address is set equal to the address. This is equivalent to the peer
// announcing itself.
if (node.addrman->Add({address}, address)) {
diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp
index 5475662b82..605ebc15a7 100644
--- a/src/rpc/node.cpp
+++ b/src/rpc/node.cpp
@@ -65,7 +65,7 @@ static RPCHelpMan setmocktime()
}
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -85,7 +85,7 @@ static RPCHelpMan invokedisallowedsyscall()
throw std::runtime_error("invokedisallowedsyscall is used for testing only.");
}
TestDisallowedSandboxCall();
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -118,7 +118,7 @@ static RPCHelpMan mockscheduler()
CHECK_NONFATAL(node_context->scheduler);
node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds));
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 16105a85d5..9ec506b0fb 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -96,8 +96,8 @@ static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc)
{RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number (if not coinbase transaction)"},
{RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script (if not coinbase transaction)",
{
- {RPCResult::Type::STR, "asm", "asm"},
- {RPCResult::Type::STR_HEX, "hex", "hex"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the signature script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw signature script bytes, hex-encoded"},
}},
{RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
{
@@ -114,9 +114,9 @@ static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc)
{RPCResult::Type::NUM, "n", "index"},
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
- {RPCResult::Type::STR, "asm", "the asm"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", "the hex"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
}},
@@ -692,9 +692,9 @@ const RPCResult decodepsbt_inputs{
{RPCResult::Type::NUM, "amount", "The value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
- {RPCResult::Type::STR, "asm", "The asm"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", "The hex"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
}},
@@ -706,14 +706,14 @@ const RPCResult decodepsbt_inputs{
{RPCResult::Type::STR, "sighash", /*optional=*/true, "The sighash type to be used"},
{RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "",
{
- {RPCResult::Type::STR, "asm", "The asm"},
- {RPCResult::Type::STR_HEX, "hex", "The hex"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the redeem script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw redeem script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "",
{
- {RPCResult::Type::STR, "asm", "The asm"},
- {RPCResult::Type::STR_HEX, "hex", "The hex"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the witness script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw witness script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "",
@@ -727,8 +727,8 @@ const RPCResult decodepsbt_inputs{
}},
{RPCResult::Type::OBJ, "final_scriptSig", /*optional=*/true, "",
{
- {RPCResult::Type::STR, "asm", "The asm"},
- {RPCResult::Type::STR_HEX, "hex", "The hex"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the final signature script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw final signature script bytes, hex-encoded"},
}},
{RPCResult::Type::ARR, "final_scriptwitness", /*optional=*/true, "",
{
@@ -812,14 +812,14 @@ const RPCResult decodepsbt_outputs{
{
{RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "",
{
- {RPCResult::Type::STR, "asm", "The asm"},
- {RPCResult::Type::STR_HEX, "hex", "The hex"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the redeem script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw redeem script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "",
{
- {RPCResult::Type::STR, "asm", "The asm"},
- {RPCResult::Type::STR_HEX, "hex", "The hex"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the witness script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw witness script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "",
diff --git a/src/serialize.h b/src/serialize.h
index a1cce78451..89a9f32240 100644
--- a/src/serialize.h
+++ b/src/serialize.h
@@ -520,6 +520,29 @@ struct CompactSizeFormatter
}
};
+template <typename U, bool LOSSY = false>
+struct ChronoFormatter {
+ template <typename Stream, typename Tp>
+ void Unser(Stream& s, Tp& tp)
+ {
+ U u;
+ s >> u;
+ // Lossy deserialization does not make sense, so force Wnarrowing
+ tp = Tp{typename Tp::duration{typename Tp::duration::rep{u}}};
+ }
+ template <typename Stream, typename Tp>
+ void Ser(Stream& s, Tp tp)
+ {
+ if constexpr (LOSSY) {
+ s << U(tp.time_since_epoch().count());
+ } else {
+ s << U{tp.time_since_epoch().count()};
+ }
+ }
+};
+template <typename U>
+using LossyChronoFormatter = ChronoFormatter<U, true>;
+
class CompactSizeWriter
{
protected:
diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp
index 12cf1176a6..b1372a3e98 100644
--- a/src/test/addrman_tests.cpp
+++ b/src/test/addrman_tests.cpp
@@ -225,7 +225,7 @@ BOOST_AUTO_TEST_CASE(addrman_new_multiplicity)
{
auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node));
CAddress addr{CAddress(ResolveService("253.3.3.3", 8333), NODE_NONE)};
- int64_t start_time{GetAdjustedTime()};
+ const auto start_time{AdjustedTime()};
addr.nTime = start_time;
// test that multiplicity stays at 1 if nTime doesn't increase
@@ -244,7 +244,7 @@ BOOST_AUTO_TEST_CASE(addrman_new_multiplicity)
for (unsigned int i = 1; i < 400; ++i) {
std::string addr_ip{ToString(i % 256) + "." + ToString(i >> 8 % 256) + ".1.1"};
CNetAddr source{ResolveIP(addr_ip)};
- addr.nTime = start_time + i;
+ addr.nTime = start_time + std::chrono::seconds{i};
addrman->Add({addr}, source);
}
AddressPosition addr_pos_multi = addrman->FindAddressEntry(addr).value();
@@ -295,15 +295,15 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr)
BOOST_CHECK_EQUAL(vAddr1.size(), 0U);
CAddress addr1 = CAddress(ResolveService("250.250.2.1", 8333), NODE_NONE);
- addr1.nTime = GetAdjustedTime(); // Set time so isTerrible = false
+ addr1.nTime = AdjustedTime(); // Set time so isTerrible = false
CAddress addr2 = CAddress(ResolveService("250.251.2.2", 9999), NODE_NONE);
- addr2.nTime = GetAdjustedTime();
+ addr2.nTime = AdjustedTime();
CAddress addr3 = CAddress(ResolveService("251.252.2.3", 8333), NODE_NONE);
- addr3.nTime = GetAdjustedTime();
+ addr3.nTime = AdjustedTime();
CAddress addr4 = CAddress(ResolveService("252.253.3.4", 8333), NODE_NONE);
- addr4.nTime = GetAdjustedTime();
+ addr4.nTime = AdjustedTime();
CAddress addr5 = CAddress(ResolveService("252.254.4.5", 8333), NODE_NONE);
- addr5.nTime = GetAdjustedTime();
+ addr5.nTime = AdjustedTime();
CNetAddr source1 = ResolveIP("250.1.2.1");
CNetAddr source2 = ResolveIP("250.2.3.3");
@@ -329,7 +329,7 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr)
CAddress addr = CAddress(ResolveService(strAddr), NODE_NONE);
// Ensure that for all addrs in addrman, isTerrible == false.
- addr.nTime = GetAdjustedTime();
+ addr.nTime = AdjustedTime();
addrman->Add({addr}, ResolveIP(strAddr));
if (i % 8 == 0)
addrman->Good(addr);
@@ -821,8 +821,8 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks)
// Ensure test of address fails, so that it is evicted.
// Update entry in tried by setting last good connection in the deep past.
- BOOST_CHECK(!addrman->Good(info, /*nTime=*/1));
- addrman->Attempt(info, /*fCountFailure=*/false, /*nTime=*/GetAdjustedTime() - 61);
+ BOOST_CHECK(!addrman->Good(info, NodeSeconds{1s}));
+ addrman->Attempt(info, /*fCountFailure=*/false, AdjustedTime() - 61s);
// Should swap 36 for 19.
addrman->ResolveCollisions();
@@ -966,7 +966,7 @@ BOOST_AUTO_TEST_CASE(addrman_update_address)
CNetAddr source{ResolveIP("252.2.2.2")};
CAddress addr{CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE)};
- int64_t start_time{GetAdjustedTime() - 10000};
+ const auto start_time{AdjustedTime() - 10000s};
addr.nTime = start_time;
BOOST_CHECK(addrman->Add({addr}, source));
BOOST_CHECK_EQUAL(addrman->size(), 1U);
@@ -978,7 +978,7 @@ BOOST_AUTO_TEST_CASE(addrman_update_address)
addrman->SetServices(addr_diff_port, NODE_NETWORK_LIMITED);
std::vector<CAddress> vAddr1{addrman->GetAddr(/*max_addresses=*/0, /*max_pct=*/0, /*network=*/std::nullopt)};
BOOST_CHECK_EQUAL(vAddr1.size(), 1U);
- BOOST_CHECK_EQUAL(vAddr1.at(0).nTime, start_time);
+ BOOST_CHECK(vAddr1.at(0).nTime == start_time);
BOOST_CHECK_EQUAL(vAddr1.at(0).nServices, NODE_NONE);
// Updating an addrman entry with the correct port is successful
@@ -986,7 +986,7 @@ BOOST_AUTO_TEST_CASE(addrman_update_address)
addrman->SetServices(addr, NODE_NETWORK_LIMITED);
std::vector<CAddress> vAddr2 = addrman->GetAddr(/*max_addresses=*/0, /*max_pct=*/0, /*network=*/std::nullopt);
BOOST_CHECK_EQUAL(vAddr2.size(), 1U);
- BOOST_CHECK(vAddr2.at(0).nTime >= start_time + 10000);
+ BOOST_CHECK(vAddr2.at(0).nTime >= start_time + 10000s);
BOOST_CHECK_EQUAL(vAddr2.at(0).nServices, NODE_NETWORK_LIMITED);
}
diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp
index af7a282781..7668940cbc 100644
--- a/src/test/fuzz/addrman.cpp
+++ b/src/test/fuzz/addrman.cpp
@@ -113,11 +113,11 @@ void FillAddrman(AddrMan& addrman, FuzzedDataProvider& fuzzed_data_provider)
for (size_t j = 0; j < num_addresses; ++j) {
const auto addr = CAddress{CService{RandAddr(fuzzed_data_provider, fast_random_context), 8333}, NODE_NETWORK};
- const auto time_penalty = fast_random_context.randrange(100000001);
+ const std::chrono::seconds time_penalty{fast_random_context.randrange(100000001)};
addrman.Add({addr}, source, time_penalty);
if (n > 0 && addrman.size() % n == 0) {
- addrman.Good(addr, GetTime());
+ addrman.Good(addr, Now<NodeSeconds>());
}
// Add 10% of the addresses from more than one source.
@@ -161,7 +161,7 @@ public:
CSipHasher hasher(0, 0);
auto addr_key = a.GetKey();
auto source_key = a.source.GetAddrBytes();
- hasher.Write(a.nLastSuccess);
+ hasher.Write(TicksSinceEpoch<std::chrono::seconds>(a.m_last_success));
hasher.Write(a.nAttempts);
hasher.Write(a.nRefCount);
hasher.Write(a.fInTried);
@@ -175,8 +175,8 @@ public:
};
auto addrinfo_eq = [](const AddrInfo& lhs, const AddrInfo& rhs) {
- return std::tie(static_cast<const CService&>(lhs), lhs.source, lhs.nLastSuccess, lhs.nAttempts, lhs.nRefCount, lhs.fInTried) ==
- std::tie(static_cast<const CService&>(rhs), rhs.source, rhs.nLastSuccess, rhs.nAttempts, rhs.nRefCount, rhs.fInTried);
+ return std::tie(static_cast<const CService&>(lhs), lhs.source, lhs.m_last_success, lhs.nAttempts, lhs.nRefCount, lhs.fInTried) ==
+ std::tie(static_cast<const CService&>(rhs), rhs.source, rhs.m_last_success, rhs.nAttempts, rhs.nRefCount, rhs.fInTried);
};
using Addresses = std::unordered_set<AddrInfo, decltype(addrinfo_hasher), decltype(addrinfo_eq)>;
@@ -269,25 +269,25 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman)
}
const std::optional<CNetAddr> opt_net_addr = ConsumeDeserializable<CNetAddr>(fuzzed_data_provider);
if (opt_net_addr) {
- addr_man.Add(addresses, *opt_net_addr, fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 100000000));
+ addr_man.Add(addresses, *opt_net_addr, std::chrono::seconds{ConsumeTime(fuzzed_data_provider, 0, 100000000)});
}
},
[&] {
const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider);
if (opt_service) {
- addr_man.Good(*opt_service, ConsumeTime(fuzzed_data_provider));
+ addr_man.Good(*opt_service, NodeSeconds{std::chrono::seconds{ConsumeTime(fuzzed_data_provider)}});
}
},
[&] {
const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider);
if (opt_service) {
- addr_man.Attempt(*opt_service, fuzzed_data_provider.ConsumeBool(), ConsumeTime(fuzzed_data_provider));
+ addr_man.Attempt(*opt_service, fuzzed_data_provider.ConsumeBool(), NodeSeconds{std::chrono::seconds{ConsumeTime(fuzzed_data_provider)}});
}
},
[&] {
const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider);
if (opt_service) {
- addr_man.Connected(*opt_service, ConsumeTime(fuzzed_data_provider));
+ addr_man.Connected(*opt_service, NodeSeconds{std::chrono::seconds{ConsumeTime(fuzzed_data_provider)}});
}
},
[&] {
diff --git a/src/test/fuzz/chain.cpp b/src/test/fuzz/chain.cpp
index 8c0ed32d51..01edb06138 100644
--- a/src/test/fuzz/chain.cpp
+++ b/src/test/fuzz/chain.cpp
@@ -23,7 +23,7 @@ FUZZ_TARGET(chain)
disk_block_index->phashBlock = &zero;
{
LOCK(::cs_main);
- (void)disk_block_index->GetBlockHash();
+ (void)disk_block_index->ConstructBlockHash();
(void)disk_block_index->GetBlockPos();
(void)disk_block_index->GetBlockTime();
(void)disk_block_index->GetBlockTimeMax();
@@ -31,7 +31,6 @@ FUZZ_TARGET(chain)
(void)disk_block_index->GetUndoPos();
(void)disk_block_index->HaveTxsDownloaded();
(void)disk_block_index->IsValid();
- (void)disk_block_index->ToString();
}
const CBlockHeader block_header = disk_block_index->GetBlockHeader();
diff --git a/src/test/fuzz/parse_univalue.cpp b/src/test/fuzz/parse_univalue.cpp
index c7a76aa52f..0cc210f26f 100644
--- a/src/test/fuzz/parse_univalue.cpp
+++ b/src/test/fuzz/parse_univalue.cpp
@@ -26,7 +26,7 @@ FUZZ_TARGET_INIT(parse_univalue, initialize_parse_univalue)
return ParseNonRFCJSONValue(random_string);
} catch (const std::runtime_error&) {
valid = false;
- return NullUniValue;
+ return UniValue{};
}
}();
if (!valid) {
diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
index 63fbf0516a..cfb112879a 100644
--- a/src/test/fuzz/tx_pool.cpp
+++ b/src/test/fuzz/tx_pool.cpp
@@ -136,7 +136,6 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
MockTime(fuzzed_data_provider, chainstate);
- SetMempoolConstraints(*node.args, fuzzed_data_provider);
// All RBF-spendable outpoints
std::set<COutPoint> outpoints_rbf;
@@ -150,6 +149,7 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
// The sum of the values of all spendable outpoints
constexpr CAmount SUPPLY_TOTAL{COINBASE_MATURITY * 50 * COIN};
+ SetMempoolConstraints(*node.args, fuzzed_data_provider);
CTxMemPool tx_pool_{MakeMempool(node)};
MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
@@ -221,9 +221,6 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
MockTime(fuzzed_data_provider, chainstate);
}
if (fuzzed_data_provider.ConsumeBool()) {
- SetMempoolConstraints(*node.args, fuzzed_data_provider);
- }
- if (fuzzed_data_provider.ConsumeBool()) {
tx_pool.RollingFeeUpdate();
}
if (fuzzed_data_provider.ConsumeBool()) {
@@ -316,7 +313,6 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
auto& chainstate = node.chainman->ActiveChainstate();
MockTime(fuzzed_data_provider, chainstate);
- SetMempoolConstraints(*node.args, fuzzed_data_provider);
std::vector<uint256> txids;
for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
@@ -328,6 +324,7 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
txids.push_back(ConsumeUInt256(fuzzed_data_provider));
}
+ SetMempoolConstraints(*node.args, fuzzed_data_provider);
CTxMemPool tx_pool_{MakeMempool(node)};
MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
@@ -339,9 +336,6 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
MockTime(fuzzed_data_provider, chainstate);
}
if (fuzzed_data_provider.ConsumeBool()) {
- SetMempoolConstraints(*node.args, fuzzed_data_provider);
- }
- if (fuzzed_data_provider.ConsumeBool()) {
tx_pool.RollingFeeUpdate();
}
if (fuzzed_data_provider.ConsumeBool()) {
diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp
index 9278f03423..7580651371 100644
--- a/src/test/fuzz/txorphan.cpp
+++ b/src/test/fuzz/txorphan.cpp
@@ -67,7 +67,7 @@ FUZZ_TARGET_INIT(txorphan, initialize_orphanage)
for (uint32_t i = 0; i < num_out; i++) {
tx_mut.vout.emplace_back(CAmount{0}, CScript{});
}
- // restore previously poped outpoints
+ // restore previously popped outpoints
for (auto& in : tx_mut.vin) {
outpoints.push_back(in.prevout);
}
diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp
index fabcea22c3..ba1a634e41 100644
--- a/src/test/fuzz/util.cpp
+++ b/src/test/fuzz/util.cpp
@@ -527,6 +527,11 @@ CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept
return net_addr;
}
+CAddress ConsumeAddress(FuzzedDataProvider& fuzzed_data_provider) noexcept
+{
+ return {ConsumeService(fuzzed_data_provider), ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS), NodeSeconds{std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<uint32_t>()}}};
+}
+
FILE* FuzzedFileProvider::open()
{
SetFuzzedErrNo(m_fuzzed_data_provider);
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index 60e2875953..33d9ab3cc3 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -287,10 +287,7 @@ inline CService ConsumeService(FuzzedDataProvider& fuzzed_data_provider) noexcep
return {ConsumeNetAddr(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<uint16_t>()};
}
-inline CAddress ConsumeAddress(FuzzedDataProvider& fuzzed_data_provider) noexcept
-{
- return {ConsumeService(fuzzed_data_provider), ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS), fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
-}
+CAddress ConsumeAddress(FuzzedDataProvider& fuzzed_data_provider) noexcept;
template <bool ReturnUniquePtr = false>
auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<NodeId>& node_id_in = std::nullopt) noexcept
diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp
index 224dc88d0f..c2d2fa37b4 100644
--- a/src/test/netbase_tests.cpp
+++ b/src/test/netbase_tests.cpp
@@ -480,21 +480,21 @@ BOOST_AUTO_TEST_CASE(netbase_dont_resolve_strings_with_embedded_nul_characters)
// try a few edge cases for port, service flags and time.
static const std::vector<CAddress> fixture_addresses({
- CAddress(
+ CAddress{
CService(CNetAddr(in6_addr(IN6ADDR_LOOPBACK_INIT)), 0 /* port */),
NODE_NONE,
- 0x4966bc61U /* Fri Jan 9 02:54:25 UTC 2009 */
- ),
- CAddress(
+ NodeSeconds{0x4966bc61s}, /* Fri Jan 9 02:54:25 UTC 2009 */
+ },
+ CAddress{
CService(CNetAddr(in6_addr(IN6ADDR_LOOPBACK_INIT)), 0x00f1 /* port */),
NODE_NETWORK,
- 0x83766279U /* Tue Nov 22 11:22:33 UTC 2039 */
- ),
- CAddress(
+ NodeSeconds{0x83766279s}, /* Tue Nov 22 11:22:33 UTC 2039 */
+ },
+ CAddress{
CService(CNetAddr(in6_addr(IN6ADDR_LOOPBACK_INIT)), 0xf1f2 /* port */),
static_cast<ServiceFlags>(NODE_WITNESS | NODE_COMPACT_FILTERS | NODE_NETWORK_LIMITED),
- 0xffffffffU /* Sun Feb 7 06:28:15 UTC 2106 */
- )
+ NodeSeconds{0xffffffffs}, /* Sun Feb 7 06:28:15 UTC 2106 */
+ },
});
// fixture_addresses should equal to this when serialized in V1 format.
diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp
index 9b2760fd1c..96fb28dc9f 100644
--- a/src/test/random_tests.cpp
+++ b/src/test/random_tests.cpp
@@ -53,6 +53,16 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests)
BOOST_CHECK_EQUAL(ctx1.randbits(3), ctx2.randbits(3));
BOOST_CHECK(ctx1.rand256() == ctx2.rand256());
BOOST_CHECK(ctx1.randbytes(50) == ctx2.randbytes(50));
+ {
+ struct MicroClock {
+ using duration = std::chrono::microseconds;
+ };
+ FastRandomContext ctx{true};
+ // Check with clock type
+ BOOST_CHECK_EQUAL(47222, ctx.rand_uniform_duration<MicroClock>(1s).count());
+ // Check with time-point type
+ BOOST_CHECK_EQUAL(2782, ctx.rand_uniform_duration<SteadySeconds>(9h).count());
+ }
// Check that a nondeterministic ones are not
g_mock_deterministic_tests = false;
diff --git a/src/test/rbf_tests.cpp b/src/test/rbf_tests.cpp
new file mode 100644
index 0000000000..e597081afd
--- /dev/null
+++ b/src/test/rbf_tests.cpp
@@ -0,0 +1,230 @@
+// Copyright (c) 2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <policy/rbf.h>
+#include <random.h>
+#include <txmempool.h>
+#include <util/system.h>
+#include <util/time.h>
+
+#include <test/util/setup_common.h>
+
+#include <boost/test/unit_test.hpp>
+#include <optional>
+#include <vector>
+
+BOOST_FIXTURE_TEST_SUITE(rbf_tests, TestingSetup)
+
+static inline CTransactionRef make_tx(const std::vector<CTransactionRef>& inputs,
+ const std::vector<CAmount>& output_values)
+{
+ CMutableTransaction tx = CMutableTransaction();
+ tx.vin.resize(inputs.size());
+ tx.vout.resize(output_values.size());
+ for (size_t i = 0; i < inputs.size(); ++i) {
+ tx.vin[i].prevout.hash = inputs[i]->GetHash();
+ tx.vin[i].prevout.n = 0;
+ // Add a witness so wtxid != txid
+ CScriptWitness witness;
+ witness.stack.push_back(std::vector<unsigned char>(i + 10));
+ tx.vin[i].scriptWitness = witness;
+ }
+ for (size_t i = 0; i < output_values.size(); ++i) {
+ tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
+ tx.vout[i].nValue = output_values[i];
+ }
+ return MakeTransactionRef(tx);
+}
+
+static void add_descendants(const CTransactionRef& tx, int32_t num_descendants, CTxMemPool& pool)
+ EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs)
+{
+ AssertLockHeld(::cs_main);
+ AssertLockHeld(pool.cs);
+ TestMemPoolEntryHelper entry;
+ // Assumes this isn't already spent in mempool
+ auto tx_to_spend = tx;
+ for (int32_t i{0}; i < num_descendants; ++i) {
+ auto next_tx = make_tx(/*inputs=*/{tx_to_spend}, /*output_values=*/{(50 - i) * CENT});
+ pool.addUnchecked(entry.FromTx(next_tx));
+ tx_to_spend = next_tx;
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
+{
+ CTxMemPool& pool = *Assert(m_node.mempool);
+ LOCK2(::cs_main, pool.cs);
+ TestMemPoolEntryHelper entry;
+
+ const CAmount low_fee{CENT/100};
+ const CAmount normal_fee{CENT/10};
+ const CAmount high_fee{CENT};
+
+ // Create a parent tx1 and child tx2 with normal fees:
+ const auto tx1 = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
+ pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx1));
+ const auto tx2 = make_tx(/*inputs=*/ {tx1}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx2));
+
+ // Create a low-feerate parent tx3 and high-feerate child tx4 (cpfp)
+ const auto tx3 = make_tx(/*inputs=*/ {m_coinbase_txns[1]}, /*output_values=*/ {1099 * CENT});
+ pool.addUnchecked(entry.Fee(low_fee).FromTx(tx3));
+ const auto tx4 = make_tx(/*inputs=*/ {tx3}, /*output_values=*/ {999 * CENT});
+ pool.addUnchecked(entry.Fee(high_fee).FromTx(tx4));
+
+ // Create a parent tx5 and child tx6 where both have very low fees
+ const auto tx5 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {1099 * CENT});
+ pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
+ const auto tx6 = make_tx(/*inputs=*/ {tx3}, /*output_values=*/ {1098 * CENT});
+ pool.addUnchecked(entry.Fee(low_fee).FromTx(tx6));
+ // Make tx6's modified fee much higher than its base fee. This should cause it to pass
+ // the fee-related checks despite being low-feerate.
+ pool.PrioritiseTransaction(tx6->GetHash(), 1 * COIN);
+
+ // Two independent high-feerate transactions, tx7 and tx8
+ const auto tx7 = make_tx(/*inputs=*/ {m_coinbase_txns[3]}, /*output_values=*/ {999 * CENT});
+ pool.addUnchecked(entry.Fee(high_fee).FromTx(tx7));
+ const auto tx8 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {999 * CENT});
+ pool.addUnchecked(entry.Fee(high_fee).FromTx(tx8));
+
+ const auto entry1 = pool.GetIter(tx1->GetHash()).value();
+ const auto entry2 = pool.GetIter(tx2->GetHash()).value();
+ const auto entry3 = pool.GetIter(tx3->GetHash()).value();
+ const auto entry4 = pool.GetIter(tx4->GetHash()).value();
+ const auto entry5 = pool.GetIter(tx5->GetHash()).value();
+ const auto entry6 = pool.GetIter(tx6->GetHash()).value();
+ const auto entry7 = pool.GetIter(tx7->GetHash()).value();
+ const auto entry8 = pool.GetIter(tx8->GetHash()).value();
+
+ BOOST_CHECK_EQUAL(entry1->GetFee(), normal_fee);
+ BOOST_CHECK_EQUAL(entry2->GetFee(), normal_fee);
+ BOOST_CHECK_EQUAL(entry3->GetFee(), low_fee);
+ BOOST_CHECK_EQUAL(entry4->GetFee(), high_fee);
+ BOOST_CHECK_EQUAL(entry5->GetFee(), low_fee);
+ BOOST_CHECK_EQUAL(entry6->GetFee(), low_fee);
+ BOOST_CHECK_EQUAL(entry7->GetFee(), high_fee);
+ BOOST_CHECK_EQUAL(entry8->GetFee(), high_fee);
+
+ CTxMemPool::setEntries set_12_normal{entry1, entry2};
+ CTxMemPool::setEntries set_34_cpfp{entry3, entry4};
+ CTxMemPool::setEntries set_56_low{entry5, entry6};
+ CTxMemPool::setEntries all_entries{entry1, entry2, entry3, entry4, entry5, entry6, entry7, entry8};
+ CTxMemPool::setEntries empty_set;
+
+ const auto unused_txid{GetRandHash()};
+
+ // Tests for PaysMoreThanConflicts
+ // These tests use feerate, not absolute fee.
+ BOOST_CHECK(PaysMoreThanConflicts(/*iters_conflicting=*/set_12_normal,
+ /*replacement_feerate=*/CFeeRate(entry1->GetModifiedFee() + 1, entry1->GetTxSize() + 2),
+ /*txid=*/unused_txid).has_value());
+ // Replacement must be strictly greater than the originals.
+ BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1->GetModifiedFee(), entry1->GetTxSize()), unused_txid).has_value());
+ BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1->GetModifiedFee() + 1, entry1->GetTxSize()), unused_txid) == std::nullopt);
+ // These tests use modified fees (including prioritisation), not base fees.
+ BOOST_CHECK(PaysMoreThanConflicts({entry5}, CFeeRate(entry5->GetModifiedFee() + 1, entry5->GetTxSize()), unused_txid) == std::nullopt);
+ BOOST_CHECK(PaysMoreThanConflicts({entry6}, CFeeRate(entry6->GetFee() + 1, entry6->GetTxSize()), unused_txid).has_value());
+ BOOST_CHECK(PaysMoreThanConflicts({entry6}, CFeeRate(entry6->GetModifiedFee() + 1, entry6->GetTxSize()), unused_txid) == std::nullopt);
+ // PaysMoreThanConflicts checks individual feerate, not ancestor feerate. This test compares
+ // replacement_feerate and entry4's feerate, which are the same. The replacement_feerate is
+ // considered too low even though entry4 has a low ancestor feerate.
+ BOOST_CHECK(PaysMoreThanConflicts(set_34_cpfp, CFeeRate(entry4->GetModifiedFee(), entry4->GetTxSize()), unused_txid).has_value());
+
+ // Tests for EntriesAndTxidsDisjoint
+ BOOST_CHECK(EntriesAndTxidsDisjoint(empty_set, {tx1->GetHash()}, unused_txid) == std::nullopt);
+ BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx3->GetHash()}, unused_txid) == std::nullopt);
+ // EntriesAndTxidsDisjoint uses txids, not wtxids.
+ BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx2->GetWitnessHash()}, unused_txid) == std::nullopt);
+ BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx2->GetHash()}, unused_txid).has_value());
+ BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx1->GetHash()}, unused_txid).has_value());
+ BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx2->GetHash()}, unused_txid).has_value());
+ // EntriesAndTxidsDisjoint does not calculate descendants of iters_conflicting; it uses whatever
+ // the caller passed in. As such, no error is returned even though entry2 is a descendant of tx1.
+ BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx1->GetHash()}, unused_txid) == std::nullopt);
+
+ // Tests for PaysForRBF
+ const CFeeRate incremental_relay_feerate{DEFAULT_INCREMENTAL_RELAY_FEE};
+ const CFeeRate higher_relay_feerate{2 * DEFAULT_INCREMENTAL_RELAY_FEE};
+ // Must pay at least as much as the original.
+ BOOST_CHECK(PaysForRBF(/*original_fees=*/high_fee,
+ /*replacement_fees=*/high_fee,
+ /*replacement_vsize=*/1,
+ /*relay_fee=*/CFeeRate(0),
+ /*txid=*/unused_txid)
+ == std::nullopt);
+ BOOST_CHECK(PaysForRBF(high_fee, high_fee - 1, 1, CFeeRate(0), unused_txid).has_value());
+ BOOST_CHECK(PaysForRBF(high_fee + 1, high_fee, 1, CFeeRate(0), unused_txid).has_value());
+ // Additional fees must cover the replacement's vsize at incremental relay fee
+ BOOST_CHECK(PaysForRBF(high_fee, high_fee + 1, 2, incremental_relay_feerate, unused_txid).has_value());
+ BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 2, incremental_relay_feerate, unused_txid) == std::nullopt);
+ BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 2, higher_relay_feerate, unused_txid).has_value());
+ BOOST_CHECK(PaysForRBF(high_fee, high_fee + 4, 2, higher_relay_feerate, unused_txid) == std::nullopt);
+ BOOST_CHECK(PaysForRBF(low_fee, high_fee, 99999999, incremental_relay_feerate, unused_txid).has_value());
+ BOOST_CHECK(PaysForRBF(low_fee, high_fee + 99999999, 99999999, incremental_relay_feerate, unused_txid) == std::nullopt);
+
+ // Tests for GetEntriesForConflicts
+ CTxMemPool::setEntries all_parents{entry1, entry3, entry5, entry7, entry8};
+ CTxMemPool::setEntries all_children{entry2, entry4, entry6};
+ const std::vector<CTransactionRef> parent_inputs({m_coinbase_txns[0], m_coinbase_txns[1], m_coinbase_txns[2],
+ m_coinbase_txns[3], m_coinbase_txns[4]});
+ const auto conflicts_with_parents = make_tx(parent_inputs, {50 * CENT});
+ CTxMemPool::setEntries all_conflicts;
+ BOOST_CHECK(GetEntriesForConflicts(/*tx=*/ *conflicts_with_parents.get(),
+ /*pool=*/ pool,
+ /*iters_conflicting=*/ all_parents,
+ /*all_conflicts=*/ all_conflicts) == std::nullopt);
+ BOOST_CHECK(all_conflicts == all_entries);
+ auto conflicts_size = all_conflicts.size();
+ all_conflicts.clear();
+
+ add_descendants(tx2, 23, pool);
+ BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
+ conflicts_size += 23;
+ BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
+ all_conflicts.clear();
+
+ add_descendants(tx4, 23, pool);
+ BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
+ conflicts_size += 23;
+ BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
+ all_conflicts.clear();
+
+ add_descendants(tx6, 23, pool);
+ BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
+ conflicts_size += 23;
+ BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
+ all_conflicts.clear();
+
+ add_descendants(tx7, 23, pool);
+ BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
+ conflicts_size += 23;
+ BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
+ BOOST_CHECK_EQUAL(all_conflicts.size(), 100);
+ all_conflicts.clear();
+
+ // Exceeds maximum number of conflicts.
+ add_descendants(tx8, 1, pool);
+ BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts).has_value());
+
+ // Tests for HasNoNewUnconfirmed
+ const auto spends_unconfirmed = make_tx({tx1}, {36 * CENT});
+ for (const auto& input : spends_unconfirmed->vin) {
+ // Spends unconfirmed inputs.
+ BOOST_CHECK(pool.exists(GenTxid::Txid(input.prevout.hash)));
+ }
+ BOOST_CHECK(HasNoNewUnconfirmed(/*tx=*/ *spends_unconfirmed.get(),
+ /*pool=*/ pool,
+ /*iters_conflicting=*/ all_entries) == std::nullopt);
+ BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, {entry2}) == std::nullopt);
+ BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, empty_set).has_value());
+
+ const auto spends_new_unconfirmed = make_tx({tx1, tx8}, {36 * CENT});
+ BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, {entry2}).has_value());
+ BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, all_entries).has_value());
+
+ const auto spends_conflicting_confirmed = make_tx({m_coinbase_txns[0], m_coinbase_txns[1]}, {45 * CENT});
+ BOOST_CHECK(HasNoNewUnconfirmed(*spends_conflicting_confirmed.get(), pool, {entry1, entry3}) == std::nullopt);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp
index fbcf1f14ef..9e7a376d6b 100644
--- a/src/test/script_tests.cpp
+++ b/src/test/script_tests.cpp
@@ -1514,8 +1514,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_returns_true)
CScriptWitness wit;
scriptPubKey << OP_1;
- CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1);
- CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx);
+ CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
+ CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
stream << spendTx;
@@ -1537,8 +1537,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_index_err)
CScriptWitness wit;
scriptPubKey << OP_EQUAL;
- CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1);
- CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx);
+ CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
+ CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
stream << spendTx;
@@ -1560,8 +1560,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_size)
CScriptWitness wit;
scriptPubKey << OP_EQUAL;
- CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1);
- CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx);
+ CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
+ CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
stream << spendTx;
@@ -1583,8 +1583,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_serialization)
CScriptWitness wit;
scriptPubKey << OP_EQUAL;
- CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1);
- CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx);
+ CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
+ CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
stream << 0xffffffff;
@@ -1606,8 +1606,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_amount_required_err)
CScriptWitness wit;
scriptPubKey << OP_EQUAL;
- CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1);
- CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx);
+ CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
+ CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
stream << spendTx;
@@ -1629,8 +1629,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_invalid_flags)
CScriptWitness wit;
scriptPubKey << OP_EQUAL;
- CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1);
- CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx);
+ CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
+ CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
stream << spendTx;
diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp
index 3f5353b5a2..f160bb08a5 100644
--- a/src/test/system_tests.cpp
+++ b/src/test/system_tests.cpp
@@ -7,11 +7,6 @@
#include <univalue.h>
#ifdef ENABLE_EXTERNAL_SIGNER
-#if defined(WIN32) && !defined(__kernel_entry)
-// A workaround for boost 1.71 incompatibility with mingw-w64 compiler.
-// For details see https://github.com/bitcoin/bitcoin/pull/22348.
-#define __kernel_entry
-#endif
#if defined(__GNUC__)
// Boost 1.78 requires the following workaround.
// See: https://github.com/boostorg/process/issues/235
diff --git a/src/threadinterrupt.cpp b/src/threadinterrupt.cpp
index 340106ed99..e28b447c1d 100644
--- a/src/threadinterrupt.cpp
+++ b/src/threadinterrupt.cpp
@@ -28,18 +28,8 @@ void CThreadInterrupt::operator()()
cond.notify_all();
}
-bool CThreadInterrupt::sleep_for(std::chrono::milliseconds rel_time)
+bool CThreadInterrupt::sleep_for(Clock::duration rel_time)
{
WAIT_LOCK(mut, lock);
return !cond.wait_for(lock, rel_time, [this]() { return flag.load(std::memory_order_acquire); });
}
-
-bool CThreadInterrupt::sleep_for(std::chrono::seconds rel_time)
-{
- return sleep_for(std::chrono::duration_cast<std::chrono::milliseconds>(rel_time));
-}
-
-bool CThreadInterrupt::sleep_for(std::chrono::minutes rel_time)
-{
- return sleep_for(std::chrono::duration_cast<std::chrono::milliseconds>(rel_time));
-}
diff --git a/src/threadinterrupt.h b/src/threadinterrupt.h
index 992016b4f6..363aab39ce 100644
--- a/src/threadinterrupt.h
+++ b/src/threadinterrupt.h
@@ -19,13 +19,12 @@
class CThreadInterrupt
{
public:
+ using Clock = std::chrono::steady_clock;
CThreadInterrupt();
explicit operator bool() const;
void operator()() EXCLUSIVE_LOCKS_REQUIRED(!mut);
void reset();
- bool sleep_for(std::chrono::milliseconds rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut);
- bool sleep_for(std::chrono::seconds rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut);
- bool sleep_for(std::chrono::minutes rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut);
+ bool sleep_for(Clock::duration rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut);
private:
std::condition_variable cond;
diff --git a/src/timedata.h b/src/timedata.h
index 2f039d5465..ed2d8639f7 100644
--- a/src/timedata.h
+++ b/src/timedata.h
@@ -5,9 +5,12 @@
#ifndef BITCOIN_TIMEDATA_H
#define BITCOIN_TIMEDATA_H
+#include <util/time.h>
+
#include <algorithm>
-#include <assert.h>
-#include <stdint.h>
+#include <cassert>
+#include <chrono>
+#include <cstdint>
#include <vector>
static const int64_t DEFAULT_MAX_TIME_ADJUSTMENT = 70 * 60;
@@ -73,6 +76,7 @@ public:
/** Functions to keep track of adjusted P2P time */
int64_t GetTimeOffset();
int64_t GetAdjustedTime();
+inline NodeSeconds AdjustedTime() { return Now<NodeSeconds>() + std::chrono::seconds{GetTimeOffset()}; }
void AddTimeData(const CNetAddr& ip, int64_t nTime);
/**
diff --git a/src/txdb.cpp b/src/txdb.cpp
index c048c2d92a..bad3bb80a9 100644
--- a/src/txdb.cpp
+++ b/src/txdb.cpp
@@ -310,7 +310,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams,
CDiskBlockIndex diskindex;
if (pcursor->GetValue(diskindex)) {
// Construct block index object
- CBlockIndex* pindexNew = insertBlockIndex(diskindex.GetBlockHash());
+ CBlockIndex* pindexNew = insertBlockIndex(diskindex.ConstructBlockHash());
pindexNew->pprev = insertBlockIndex(diskindex.hashPrev);
pindexNew->nHeight = diskindex.nHeight;
pindexNew->nFile = diskindex.nFile;
diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h
index dff544f96f..230a5fd096 100644
--- a/src/univalue/include/univalue.h
+++ b/src/univalue/include/univalue.h
@@ -24,27 +24,25 @@ public:
typ = initialType;
val = initialStr;
}
- UniValue(uint64_t val_) {
- setInt(val_);
- }
- UniValue(int64_t val_) {
- setInt(val_);
- }
- UniValue(bool val_) {
- setBool(val_);
- }
- UniValue(int val_) {
- setInt(val_);
- }
- UniValue(double val_) {
- setFloat(val_);
- }
- UniValue(const std::string& val_) {
- setStr(val_);
- }
- UniValue(const char *val_) {
- std::string s(val_);
- setStr(s);
+ template <typename Ref, typename T = std::remove_cv_t<std::remove_reference_t<Ref>>,
+ std::enable_if_t<std::is_floating_point_v<T> || // setFloat
+ std::is_same_v<bool, T> || // setBool
+ std::is_signed_v<T> || std::is_unsigned_v<T> || // setInt
+ std::is_constructible_v<std::string, T>, // setStr
+ bool> = true>
+ UniValue(Ref&& val)
+ {
+ if constexpr (std::is_floating_point_v<T>) {
+ setFloat(val);
+ } else if constexpr (std::is_same_v<bool, T>) {
+ setBool(val);
+ } else if constexpr (std::is_signed_v<T>) {
+ setInt(int64_t{val});
+ } else if constexpr (std::is_unsigned_v<T>) {
+ setInt(uint64_t{val});
+ } else {
+ setStr(std::string{std::forward<Ref>(val)});
+ }
}
void clear();
diff --git a/src/util/system.cpp b/src/util/system.cpp
index f6f2828fc8..ce45fb2ed4 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -740,7 +740,7 @@ std::string ArgsManager::GetHelpMessage() const
{
const bool show_debug = GetBoolArg("-help-debug", false);
- std::string usage = "";
+ std::string usage;
LOCK(cs_args);
for (const auto& arg_map : m_available_args) {
switch(arg_map.first) {
diff --git a/src/util/time.h b/src/util/time.h
index c75b1e94ed..4f9bde5d56 100644
--- a/src/util/time.h
+++ b/src/util/time.h
@@ -55,6 +55,7 @@ constexpr int64_t count_seconds(std::chrono::seconds t) { return t.count(); }
constexpr int64_t count_milliseconds(std::chrono::milliseconds t) { return t.count(); }
constexpr int64_t count_microseconds(std::chrono::microseconds t) { return t.count(); }
+using HoursDouble = std::chrono::duration<double, std::chrono::hours::period>;
using SecondsDouble = std::chrono::duration<double, std::chrono::seconds::period>;
/**
diff --git a/src/validation.cpp b/src/validation.cpp
index 9b39350fed..d64ef4df0b 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2266,7 +2266,6 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
m_blockman.m_dirty_blockindex.insert(pindex);
}
- assert(pindex->phashBlock);
// add this block to the view's block chain
view.SetBestBlock(pindex->GetBlockHash());
diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
index 9428d049de..2a6eb96871 100644
--- a/src/wallet/rpc/addresses.cpp
+++ b/src/wallet/rpc/addresses.cpp
@@ -34,7 +34,7 @@ RPCHelpMan getnewaddress()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -86,7 +86,7 @@ RPCHelpMan getrawchangeaddress()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -131,7 +131,7 @@ RPCHelpMan setlabel()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -148,7 +148,7 @@ RPCHelpMan setlabel()
pwallet->SetAddressBook(dest, label, "send");
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -181,7 +181,7 @@ RPCHelpMan listaddressgroupings()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -252,7 +252,7 @@ RPCHelpMan addmultisigaddress()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet);
@@ -327,7 +327,7 @@ RPCHelpMan keypoolrefill()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
if (pwallet->IsLegacy() && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
@@ -350,7 +350,7 @@ RPCHelpMan keypoolrefill()
throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool.");
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -374,14 +374,14 @@ RPCHelpMan newkeypool()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
spk_man.NewKeyPool();
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -548,7 +548,7 @@ RPCHelpMan getaddressinfo()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -658,7 +658,7 @@ RPCHelpMan getaddressesbylabel()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -721,7 +721,7 @@ RPCHelpMan listlabels()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -763,7 +763,7 @@ RPCHelpMan walletdisplayaddress()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
- if (!wallet) return NullUniValue;
+ if (!wallet) return UniValue::VNULL;
CWallet* const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
index 62aba4f3f2..306053fd0c 100644
--- a/src/wallet/rpc/backup.cpp
+++ b/src/wallet/rpc/backup.cpp
@@ -124,7 +124,7 @@ RPCHelpMan importprivkey()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
@@ -140,7 +140,7 @@ RPCHelpMan importprivkey()
EnsureWalletIsUnlocked(*pwallet);
std::string strSecret = request.params[0].get_str();
- std::string strLabel = "";
+ std::string strLabel;
if (!request.params[1].isNull())
strLabel = request.params[1].get_str();
@@ -192,7 +192,7 @@ RPCHelpMan importprivkey()
RescanWallet(*pwallet, reserver);
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -229,7 +229,7 @@ RPCHelpMan importaddress()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
EnsureLegacyScriptPubKeyMan(*pwallet, true);
@@ -299,7 +299,7 @@ RPCHelpMan importaddress()
}
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -317,7 +317,7 @@ RPCHelpMan importprunedfunds()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
CMutableTransaction tx;
if (!DecodeHexTx(tx, request.params[0].get_str())) {
@@ -352,7 +352,7 @@ RPCHelpMan importprunedfunds()
CTransactionRef tx_ref = MakeTransactionRef(tx);
if (pwallet->IsMine(*tx_ref)) {
pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)});
- return NullUniValue;
+ return UniValue::VNULL;
}
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction");
@@ -376,7 +376,7 @@ RPCHelpMan removeprunedfunds()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -393,7 +393,7 @@ RPCHelpMan removeprunedfunds()
throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet.");
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -425,7 +425,7 @@ RPCHelpMan importpubkey()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
EnsureLegacyScriptPubKeyMan(*pwallet, true);
@@ -480,7 +480,7 @@ RPCHelpMan importpubkey()
}
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -506,7 +506,7 @@ RPCHelpMan importwallet()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
EnsureLegacyScriptPubKeyMan(*pwallet, true);
@@ -637,7 +637,7 @@ RPCHelpMan importwallet()
if (!fGood)
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet");
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -661,7 +661,7 @@ RPCHelpMan dumpprivkey()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(*pwallet);
@@ -711,7 +711,7 @@ RPCHelpMan dumpwallet()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
const CWallet& wallet = *pwallet;
const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(wallet);
@@ -1328,7 +1328,7 @@ RPCHelpMan importmulti()
[&](const RPCHelpMan& self, const JSONRPCRequest& mainRequest) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(mainRequest);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
CWallet& wallet{*pwallet};
// Make sure the results are valid at least up to the most recent block
@@ -1636,7 +1636,7 @@ RPCHelpMan importdescriptors()
[&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
CWallet& wallet{*pwallet};
// Make sure the results are valid at least up to the most recent block
@@ -1771,7 +1771,7 @@ RPCHelpMan listdescriptors()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
- if (!wallet) return NullUniValue;
+ if (!wallet) return UniValue::VNULL;
if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets");
@@ -1840,7 +1840,7 @@ RPCHelpMan backupwallet()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -1853,7 +1853,7 @@ RPCHelpMan backupwallet()
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp
index a9fff95882..d40d800f28 100644
--- a/src/wallet/rpc/coins.cpp
+++ b/src/wallet/rpc/coins.cpp
@@ -103,7 +103,7 @@ RPCHelpMan getreceivedbyaddress()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -144,7 +144,7 @@ RPCHelpMan getreceivedbylabel()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -184,7 +184,7 @@ RPCHelpMan getbalance()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -223,7 +223,7 @@ RPCHelpMan getunconfirmedbalance()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -282,7 +282,7 @@ RPCHelpMan lockunspent()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -407,7 +407,7 @@ RPCHelpMan listlockunspent()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -459,7 +459,7 @@ RPCHelpMan getbalances()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
- if (!rpc_wallet) return NullUniValue;
+ if (!rpc_wallet) return UniValue::VNULL;
const CWallet& wallet = *rpc_wallet;
// Make sure the results are valid at least up to the most recent block
@@ -559,7 +559,7 @@ RPCHelpMan listunspent()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
int nMinDepth = 1;
if (!request.params[0].isNull()) {
@@ -638,7 +638,7 @@ RPCHelpMan listunspent()
cctl.m_max_depth = nMaxDepth;
cctl.m_include_unsafe_inputs = include_unsafe;
LOCK(pwallet->cs_wallet);
- vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).coins;
+ vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).all();
}
LOCK(pwallet->cs_wallet);
diff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp
index 931c64ca06..a68f52a718 100644
--- a/src/wallet/rpc/encrypt.cpp
+++ b/src/wallet/rpc/encrypt.cpp
@@ -32,7 +32,7 @@ RPCHelpMan walletpassphrase()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
- if (!wallet) return NullUniValue;
+ if (!wallet) return UniValue::VNULL;
CWallet* const pwallet = wallet.get();
int64_t nSleepTime;
@@ -98,7 +98,7 @@ RPCHelpMan walletpassphrase()
}
}, nSleepTime);
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -120,7 +120,7 @@ RPCHelpMan walletpassphrasechange()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -146,7 +146,7 @@ RPCHelpMan walletpassphrasechange()
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -173,7 +173,7 @@ RPCHelpMan walletlock()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -184,7 +184,7 @@ RPCHelpMan walletlock()
pwallet->Lock();
pwallet->nRelockTime = 0;
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -217,7 +217,7 @@ RPCHelpMan encryptwallet()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
diff --git a/src/wallet/rpc/signmessage.cpp b/src/wallet/rpc/signmessage.cpp
index 438d290030..ae4bd4fbc5 100644
--- a/src/wallet/rpc/signmessage.cpp
+++ b/src/wallet/rpc/signmessage.cpp
@@ -36,7 +36,7 @@ RPCHelpMan signmessage()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index 83e23497cb..e27cb1139e 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -261,7 +261,7 @@ RPCHelpMan sendtoaddress()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -367,7 +367,7 @@ RPCHelpMan sendmany()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -422,7 +422,7 @@ RPCHelpMan settxfee()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LOCK(pwallet->cs_wallet);
@@ -805,7 +805,7 @@ RPCHelpMan fundrawtransaction()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL});
@@ -895,7 +895,7 @@ RPCHelpMan signrawtransactionwithwallet()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true);
@@ -992,7 +992,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
[want_psbt](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !want_psbt) {
throw JSONRPCError(RPC_WALLET_ERROR, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead.");
@@ -1209,7 +1209,7 @@ RPCHelpMan send()
);
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
UniValue options{request.params[4].isNull() ? UniValue::VOBJ : request.params[4]};
InterpretFeeEstimationInstructions(/*conf_target=*/request.params[1], /*estimate_mode=*/request.params[2], /*fee_rate=*/request.params[3], options);
@@ -1314,7 +1314,7 @@ RPCHelpMan sendall()
);
std::shared_ptr<CWallet> const pwallet{GetWalletForJSONRPCRequest(request)};
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
@@ -1382,7 +1382,7 @@ RPCHelpMan sendall()
total_input_value += tx->tx->vout[input.prevout.n].nValue;
}
} else {
- for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, /*nMinimumAmount=*/0).coins) {
+ for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, /*nMinimumAmount=*/0).all()) {
CHECK_NONFATAL(output.input_bytes > 0);
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
continue;
@@ -1490,7 +1490,7 @@ RPCHelpMan walletprocesspsbt()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
const CWallet& wallet{*pwallet};
// Make sure the results are valid at least up to the most recent block
@@ -1617,7 +1617,7 @@ RPCHelpMan walletcreatefundedpsbt()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
CWallet& wallet{*pwallet};
// Make sure the results are valid at least up to the most recent block
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index 0b52dcc001..9c8003d6d7 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -236,7 +236,7 @@ RPCHelpMan listreceivedbyaddress()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -281,7 +281,7 @@ RPCHelpMan listreceivedbylabel()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -471,7 +471,7 @@ RPCHelpMan listtransactions()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -586,7 +586,7 @@ RPCHelpMan listsinceblock()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
const CWallet& wallet = *pwallet;
// Make sure the results are valid at least up to the most recent block
@@ -727,7 +727,7 @@ RPCHelpMan gettransaction()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -800,7 +800,7 @@ RPCHelpMan abandontransaction()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -817,7 +817,7 @@ RPCHelpMan abandontransaction()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -845,7 +845,7 @@ RPCHelpMan rescanblockchain()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
CWallet& wallet{*pwallet};
// Make sure the results are valid at least up to the most recent block
@@ -925,7 +925,7 @@ RPCHelpMan abortrescan()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;
pwallet->AbortRescan();
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index 4c44c333a6..0fe8871152 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -68,7 +68,7 @@ static RPCHelpMan getwalletinfo()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@@ -241,7 +241,7 @@ static RPCHelpMan loadwallet()
static RPCHelpMan setwalletflag()
{
- std::string flags = "";
+ std::string flags;
for (auto& it : WALLET_FLAG_MAP)
if (it.second & MUTABLE_WALLET_FLAGS)
flags += (flags == "" ? "" : ", ") + it.first;
@@ -267,7 +267,7 @@ static RPCHelpMan setwalletflag()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
std::string flag_str = request.params[0].get_str();
bool value = request.params[1].isNull() || request.params[1].get_bool();
@@ -480,7 +480,7 @@ static RPCHelpMan sethdseed()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
@@ -521,7 +521,7 @@ static RPCHelpMan sethdseed()
spk_man.SetHDSeed(master_pub_key);
if (flush_key_pool) spk_man.NewKeyPool();
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -532,7 +532,7 @@ static RPCHelpMan upgradewallet()
"\nUpgrade the wallet. Upgrades to the latest version if no version number is specified.\n"
"New keys may be generated and a new wallet backup will need to be made.",
{
- {"version", RPCArg::Type::NUM, RPCArg::Default{FEATURE_LATEST}, "The version number to upgrade to. Default is the latest wallet version."}
+ {"version", RPCArg::Type::NUM, RPCArg::Default{int{FEATURE_LATEST}}, "The version number to upgrade to. Default is the latest wallet version."}
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -551,7 +551,7 @@ static RPCHelpMan upgradewallet()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
- if (!pwallet) return NullUniValue;
+ if (!pwallet) return UniValue::VNULL;
RPCTypeCheck(request.params, {UniValue::VNUM}, true);
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 9be29c4709..c00a2cd023 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -79,6 +79,32 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
}
+uint64_t CoinsResult::size() const
+{
+ return bech32m.size() + bech32.size() + P2SH_segwit.size() + legacy.size() + other.size();
+}
+
+std::vector<COutput> CoinsResult::all() const
+{
+ std::vector<COutput> all;
+ all.reserve(this->size());
+ all.insert(all.end(), bech32m.begin(), bech32m.end());
+ all.insert(all.end(), bech32.begin(), bech32.end());
+ all.insert(all.end(), P2SH_segwit.begin(), P2SH_segwit.end());
+ all.insert(all.end(), legacy.begin(), legacy.end());
+ all.insert(all.end(), other.begin(), other.end());
+ return all;
+}
+
+void CoinsResult::clear()
+{
+ bech32m.clear();
+ bech32.clear();
+ P2SH_segwit.clear();
+ legacy.clear();
+ other.clear();
+}
+
CoinsResult AvailableCoins(const CWallet& wallet,
const CCoinControl* coinControl,
std::optional<CFeeRate> feerate,
@@ -193,10 +219,55 @@ CoinsResult AvailableCoins(const CWallet& wallet,
// Filter by spendable outputs only
if (!spendable && only_spendable) continue;
+ // When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script)
+ // We don't need those here, so we are leaving them in return_values_unused
+ std::vector<std::vector<uint8_t>> return_values_unused;
+ TxoutType type;
+ bool is_from_p2sh{false};
+
+ // If the Output is P2SH and spendable, we want to know if it is
+ // a P2SH (legacy) or one of P2SH-P2WPKH, P2SH-P2WSH (P2SH-Segwit). We can determine
+ // this from the redeemScript. If the Output is not spendable, it will be classified
+ // as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript
+ if (output.scriptPubKey.IsPayToScriptHash() && solvable) {
+ CScript redeemScript;
+ CTxDestination destination;
+ if (!ExtractDestination(output.scriptPubKey, destination))
+ continue;
+ const CScriptID& hash = CScriptID(std::get<ScriptHash>(destination));
+ if (!provider->GetCScript(hash, redeemScript))
+ continue;
+ type = Solver(redeemScript, return_values_unused);
+ is_from_p2sh = true;
+ } else {
+ type = Solver(output.scriptPubKey, return_values_unused);
+ }
+
int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl);
- result.coins.emplace_back(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
+ COutput coin(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
+ switch (type) {
+ case TxoutType::WITNESS_UNKNOWN:
+ case TxoutType::WITNESS_V1_TAPROOT:
+ result.bech32m.push_back(coin);
+ break;
+ case TxoutType::WITNESS_V0_KEYHASH:
+ case TxoutType::WITNESS_V0_SCRIPTHASH:
+ if (is_from_p2sh) {
+ result.P2SH_segwit.push_back(coin);
+ break;
+ }
+ result.bech32.push_back(coin);
+ break;
+ case TxoutType::SCRIPTHASH:
+ case TxoutType::PUBKEYHASH:
+ result.legacy.push_back(coin);
+ break;
+ default:
+ result.other.push_back(coin);
+ };
+
+ // Cache total amount as we go
result.total_amount += output.nValue;
-
// Checks the sum amount of all UTXO's.
if (nMinimumSumAmount != MAX_MONEY) {
if (result.total_amount >= nMinimumSumAmount) {
@@ -205,7 +276,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
}
// Checks the maximum number of UTXO's.
- if (nMaximumCount > 0 && result.coins.size() >= nMaximumCount) {
+ if (nMaximumCount > 0 && result.size() >= nMaximumCount) {
return result;
}
}
@@ -261,7 +332,7 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
std::map<CTxDestination, std::vector<COutput>> result;
- for (const COutput& coin : AvailableCoinsListUnspent(wallet).coins) {
+ for (const COutput& coin : AvailableCoinsListUnspent(wallet).all()) {
CTxDestination address;
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
@@ -379,20 +450,51 @@ 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, std::vector<COutput> coins,
- const CoinSelectionParams& coin_selection_params)
+std::optional<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;
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.legacy, coin_selection_params)}) {
+ results.push_back(*result);
+ }
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.P2SH_segwit, coin_selection_params)}) {
+ results.push_back(*result);
+ }
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.bech32, coin_selection_params)}) {
+ results.push_back(*result);
+ }
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.bech32m, coin_selection_params)}) {
+ results.push_back(*result);
+ }
+
+ // If we can't fund the transaction from any individual OutputType, run coin selection
+ // over all available coins, else pick the best solution from the results
+ if (results.size() == 0) {
+ if (allow_mixed_output_types) {
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.all(), coin_selection_params)}) {
+ return result;
+ }
+ }
+ return std::optional<SelectionResult>();
+ };
+ std::optional<SelectionResult> result{*std::min_element(results.begin(), results.end())};
+ return result;
+};
+
+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)
{
// Vector of results. We will choose the best one based on waste.
std::vector<SelectionResult> results;
// Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output.
- std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */);
+ std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, true /* positive_only */);
if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) {
results.push_back(*bnb_result);
}
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
- std::vector<OutputGroup> all_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, false /* positive_only */);
+ std::vector<OutputGroup> all_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, false /* positive_only */);
CAmount target_with_change = nTargetValue;
// While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output.
// So we need to include that for KnapsackSolver and SRD as well, as we are expecting to create a change output.
@@ -425,9 +527,8 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
return best_result;
}
-std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
+std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
{
- std::vector<COutput> vCoins(vAvailableCoins);
CAmount value_to_select = nTargetValue;
OutputGroup preset_inputs(coin_selection_params);
@@ -487,13 +588,13 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
return result;
}
- // remove preset inputs from vCoins so that Coin Selection doesn't pick them.
- for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
- {
- if (preset_coins.count(it->outpoint))
- it = vCoins.erase(it);
- else
- ++it;
+ // remove preset inputs from coins so that Coin Selection doesn't pick them.
+ if (coin_control.HasSelected()) {
+ available_coins.legacy.erase(remove_if(available_coins.legacy.begin(), available_coins.legacy.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.legacy.end());
+ available_coins.P2SH_segwit.erase(remove_if(available_coins.P2SH_segwit.begin(), available_coins.P2SH_segwit.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.P2SH_segwit.end());
+ available_coins.bech32.erase(remove_if(available_coins.bech32.begin(), available_coins.bech32.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.bech32.end());
+ available_coins.bech32m.erase(remove_if(available_coins.bech32m.begin(), available_coins.bech32m.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.bech32m.end());
+ available_coins.other.erase(remove_if(available_coins.other.begin(), available_coins.other.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.other.end());
}
unsigned int limit_ancestor_count = 0;
@@ -505,11 +606,15 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
// form groups from remaining coins; note that preset coins will not
// automatically have their associated (same address) coins included
- if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
+ if (coin_control.m_avoid_partial_spends && available_coins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
// Cases where we have 101+ outputs all pointing to the same destination may result in
// privacy leaks as they will potentially be deterministically sorted. We solve that by
// explicitly shuffling the outputs before processing
- Shuffle(vCoins.begin(), vCoins.end(), coin_selection_params.rng_fast);
+ Shuffle(available_coins.legacy.begin(), available_coins.legacy.end(), coin_selection_params.rng_fast);
+ Shuffle(available_coins.P2SH_segwit.begin(), available_coins.P2SH_segwit.end(), coin_selection_params.rng_fast);
+ Shuffle(available_coins.bech32.begin(), available_coins.bech32.end(), coin_selection_params.rng_fast);
+ Shuffle(available_coins.bech32m.begin(), available_coins.bech32m.end(), coin_selection_params.rng_fast);
+ Shuffle(available_coins.other.begin(), available_coins.other.end(), coin_selection_params.rng_fast);
}
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
@@ -521,26 +626,27 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
// 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), vCoins, coin_selection_params)}) return r1;
- if (auto r2{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, coin_selection_params)}) return r2;
+ 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;
// 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), vCoins, coin_selection_params)}) return r3;
+ 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)),
- vCoins, coin_selection_params)}) {
+ 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),
- vCoins, coin_selection_params)}) {
+ available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
return r5;
}
// 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, true /* include_partial_groups */),
- vCoins, coin_selection_params)}) {
+ available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
return r6;
}
// Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
@@ -548,7 +654,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
if (coin_control.m_include_unsafe_inputs) {
if (auto r7{AttemptSelection(wallet, value_to_select,
CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
- vCoins, coin_selection_params)}) {
+ available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
return r7;
}
}
@@ -558,7 +664,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
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(), true /* include_partial_groups */),
- vCoins, coin_selection_params)}) {
+ available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
return r8;
}
}
@@ -778,7 +884,7 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
CAmount selection_target = recipients_sum + not_input_fees;
// Get available coins
- auto res_available_coins = AvailableCoins(wallet,
+ auto available_coins = AvailableCoins(wallet,
&coin_control,
coin_selection_params.m_effective_feerate,
1, /*nMinimumAmount*/
@@ -787,7 +893,7 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
0); /*nMaximumCount*/
// Choose coins to use
- std::optional<SelectionResult> result = SelectCoins(wallet, res_available_coins.coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
+ std::optional<SelectionResult> result = SelectCoins(wallet, available_coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
if (!result) {
return _("Insufficient funds");
}
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index ab0ff1ee58..96ecd690be 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -29,14 +29,38 @@ struct TxSize {
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr);
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
+/**
+ * COutputs available for spending, stored by OutputType.
+ * This struct is really just a wrapper around OutputType vectors with a convenient
+ * method for concatenating and returning all COutputs as one vector.
+ *
+ * clear(), size() methods are implemented so that one can interact with
+ * the CoinsResult struct as if it was a vector
+ */
struct CoinsResult {
- std::vector<COutput> coins;
- // Sum of all the coins amounts
+ /** Vectors for each OutputType */
+ std::vector<COutput> legacy;
+ std::vector<COutput> P2SH_segwit;
+ std::vector<COutput> bech32;
+ std::vector<COutput> bech32m;
+
+ /** Other is a catch-all for anything that doesn't match the known OutputTypes */
+ std::vector<COutput> other;
+
+ /** Concatenate and return all COutputs as one vector */
+ std::vector<COutput> all() const;
+
+ /** The following methods are provided so that CoinsResult can mimic a vector,
+ * i.e., methods can work with individual OutputType vectors or on the entire object */
+ uint64_t size() const;
+ void clear();
+
+ /** Sum of all available coins */
CAmount total_amount{0};
};
+
/**
- * Return vector of available COutputs.
- * By default, returns only the spendable coins.
+ * Populate the CoinsResult struct with vectors of available COutputs, organized by OutputType.
*/
CoinsResult AvailableCoins(const CWallet& wallet,
const CCoinControl* coinControl = nullptr,
@@ -67,35 +91,52 @@ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint&
std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only);
+/**
+ * Attempt to find a valid input set that preserves privacy by not mixing OutputTypes.
+ * `ChooseSelectionResult()` will be called on each OutputType individually and the best
+ * the solution (according to the waste metric) will be chosen. If a valid input cannot be found from any
+ * single OutputType, fallback to running `ChooseSelectionResult()` over all available coins.
+ *
+ * param@[in] wallet The wallet which provides solving data for the coins
+ * param@[in] nTargetValue The target value
+ * param@[in] eligilibity_filter A filter containing rules for which coins are allowed to be included in this selection
+ * 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
+ * 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
+ */
+std::optional<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);
/**
* Attempt to find a valid input set that meets the provided eligibility filter and target.
* Multiple coin selection algorithms will be run and the input set that produces the least waste
* (according to the waste metric) will be chosen.
*
- * param@[in] wallet The wallet which provides solving data for the coins
- * param@[in] nTargetValue The target value
- * param@[in] eligilibity_filter A filter containing rules for which coins are allowed to be included in this selection
- * param@[in] coins The vector of coins 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
+ * param@[in] wallet The wallet which provides solving data for the coins
+ * param@[in] nTargetValue The target value
+ * param@[in] eligilibity_filter A filter containing rules for which coins are allowed to be included in this selection
+ * 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
*/
-std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
+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);
/**
* Select a set of coins such that nTargetValue is met and at least
* all coins from coin_control are selected; never select unconfirmed coins if they are not ours
* param@[in] wallet The wallet which provides data necessary to spend the selected coins
- * param@[in] vAvailableCoins The vector of coins available to be spent
+ * param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering
* param@[in] nTargetValue The target value
* 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.
*/
-std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, const CCoinControl& coin_control,
+std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, 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
new file mode 100644
index 0000000000..01d24da981
--- /dev/null
+++ b/src/wallet/test/availablecoins_tests.cpp
@@ -0,0 +1,105 @@
+// 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.GetObj().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);
+ 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;
+ BResult<CTxDestination> dest;
+ 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.other.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.HasRes());
+ AddTx(CRecipient{{GetScriptForDestination(dest.GetObj())}, 1 * COIN, /*fSubtractFeeFromAmount=*/true});
+ available_coins = AvailableCoins(*wallet);
+ BOOST_CHECK_EQUAL(available_coins.bech32m.size(), 2U);
+
+ // Bech32
+ dest = wallet->GetNewDestination(OutputType::BECH32, "");
+ BOOST_ASSERT(dest.HasRes());
+ AddTx(CRecipient{{GetScriptForDestination(dest.GetObj())}, 2 * COIN, /*fSubtractFeeFromAmount=*/true});
+ available_coins = AvailableCoins(*wallet);
+ BOOST_CHECK_EQUAL(available_coins.bech32.size(), 2U);
+
+ // P2SH-SEGWIT
+ dest = wallet->GetNewDestination(OutputType::P2SH_SEGWIT, "");
+ AddTx(CRecipient{{GetScriptForDestination(dest.GetObj())}, 3 * COIN, /*fSubtractFeeFromAmount=*/true});
+ available_coins = AvailableCoins(*wallet);
+ BOOST_CHECK_EQUAL(available_coins.P2SH_segwit.size(), 2U);
+
+ // Legacy (P2PKH)
+ dest = wallet->GetNewDestination(OutputType::LEGACY, "");
+ BOOST_ASSERT(dest.HasRes());
+ AddTx(CRecipient{{GetScriptForDestination(dest.GetObj())}, 4 * COIN, /*fSubtractFeeFromAmount=*/true});
+ available_coins = AvailableCoins(*wallet);
+ BOOST_CHECK_EQUAL(available_coins.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 a418105ee1..cd7fd3f4dd 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -67,7 +67,7 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe
set.insert(coin);
}
-static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false)
+static void add_coin(CoinsResult& available_coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput =0, bool spendable = false)
{
CMutableTransaction tx;
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
@@ -85,7 +85,7 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
assert(ret.second);
CWalletTx& wtx = (*ret.first).second;
const auto& txout = wtx.tx->vout.at(nInput);
- coins.emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
+ available_coins.bech32.emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
}
/** Check if SelectionResult a is equivalent to SelectionResult b.
@@ -129,18 +129,18 @@ static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool)
return target;
}
-inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
+inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& available_coins)
{
static std::vector<OutputGroup> static_groups;
static_groups.clear();
- for (auto& coin : coins) {
+ for (auto& coin : available_coins) {
static_groups.emplace_back();
static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
return static_groups;
}
-inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& coins, CWallet& wallet, const CoinEligibilityFilter& filter)
+inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& available_coins, CWallet& wallet, const CoinEligibilityFilter& filter)
{
FastRandomContext rand{};
CoinSelectionParams coin_selection_params{
@@ -155,7 +155,7 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>
/*avoid_partial=*/ false,
};
static std::vector<OutputGroup> static_groups;
- static_groups = GroupOutputs(wallet, coins, coin_selection_params, filter, /*positive_only=*/false);
+ static_groups = GroupOutputs(wallet, available_coins, coin_selection_params, filter, /*positive_only=*/false);
return static_groups;
}
@@ -307,18 +307,18 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet->SetupDescriptorScriptPubKeyMans();
- std::vector<COutput> coins;
+ CoinsResult available_coins;
- add_coin(coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate);
- coins.at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
- BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
+ add_coin(available_coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate);
+ available_coins.all().at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
+ BOOST_CHECK(!SelectCoinsBnB(GroupCoins(available_coins.all()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
// Test fees subtracted from output:
- coins.clear();
- add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate);
- coins.at(0).input_bytes = 40;
+ available_coins.clear();
+ add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate);
+ available_coins.all().at(0).input_bytes = 40;
coin_selection_params_bnb.m_subtract_fee_outputs = true;
- const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
+ const auto result9 = SelectCoinsBnB(GroupCoins(available_coins.all()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
BOOST_CHECK(result9);
BOOST_CHECK_EQUAL(result9->GetSelectedValue(), 1 * CENT);
}
@@ -330,16 +330,16 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet->SetupDescriptorScriptPubKeyMans();
- std::vector<COutput> coins;
+ CoinsResult available_coins;
- add_coin(coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
- add_coin(coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
- add_coin(coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
CCoinControl coin_control;
coin_control.m_allow_other_inputs = true;
- coin_control.Select(coins.at(0).outpoint);
+ coin_control.Select(available_coins.all().at(0).outpoint);
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
- const auto result10 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ const auto result10 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(result10);
}
{
@@ -349,52 +349,52 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet->SetupDescriptorScriptPubKeyMans();
- std::vector<COutput> coins;
+ CoinsResult available_coins;
// single coin should be selected when effective fee > long term fee
coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
- add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
- add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
- add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
expected_result.Clear();
add_coin(10 * CENT, 2, expected_result);
CCoinControl coin_control;
- const auto result11 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ const auto result11 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(EquivalentResult(expected_result, *result11));
- coins.clear();
+ available_coins.clear();
// more coins should be selected when effective fee < long term fee
coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000);
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(5000);
- add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
- add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
- add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
expected_result.Clear();
add_coin(9 * CENT, 2, expected_result);
add_coin(1 * CENT, 2, expected_result);
- const auto result12 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ const auto result12 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(EquivalentResult(expected_result, *result12));
- coins.clear();
+ available_coins.clear();
// pre selected coin should be selected even if disadvantageous
coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
- add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
- add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
- add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
+ add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
expected_result.Clear();
add_coin(9 * CENT, 2, expected_result);
add_coin(1 * CENT, 2, expected_result);
coin_control.m_allow_other_inputs = true;
- coin_control.Select(coins.at(1).outpoint); // pre select 9 coin
- const auto result13 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ coin_control.Select(available_coins.all().at(1).outpoint); // pre select 9 coin
+ const auto result13 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(EquivalentResult(expected_result, *result13));
}
}
@@ -410,175 +410,175 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet->SetupDescriptorScriptPubKeyMans();
- std::vector<COutput> coins;
+ CoinsResult available_coins;
// test multiple times to allow for differences in the shuffle order
for (int i = 0; i < RUN_TESTS; i++)
{
- coins.clear();
+ available_coins.clear();
// with an empty wallet we can't even pay one cent
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1 * CENT, CENT));
- add_coin(coins, *wallet, 1*CENT, CFeeRate(0), 4); // add a new 1 cent coin
+ add_coin(available_coins, *wallet, 1*CENT, CFeeRate(0), 4); // add a new 1 cent coin
// with a new 1 cent coin, we still can't find a mature 1 cent
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1 * CENT, CENT));
// but we can find a new 1 cent
- const auto result1 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
+ const auto result1 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result1);
BOOST_CHECK_EQUAL(result1->GetSelectedValue(), 1 * CENT);
- add_coin(coins, *wallet, 2*CENT); // add a mature 2 cent coin
+ add_coin(available_coins, *wallet, 2*CENT); // add a mature 2 cent coin
// we can't make 3 cents of mature coins
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 3 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 3 * CENT, CENT));
// we can make 3 cents of new coins
- const auto result2 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 3 * CENT, CENT);
+ const auto result2 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 3 * CENT, CENT);
BOOST_CHECK(result2);
BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT);
- add_coin(coins, *wallet, 5*CENT); // add a mature 5 cent coin,
- add_coin(coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses
- add_coin(coins, *wallet, 20*CENT); // and a mature 20 cent coin
+ add_coin(available_coins, *wallet, 5*CENT); // add a mature 5 cent coin,
+ add_coin(available_coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses
+ add_coin(available_coins, *wallet, 20*CENT); // and a mature 20 cent coin
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
// we can't make 38 cents only if we disallow new coins:
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 38 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 38 * CENT, CENT));
// we can't even make 37 cents if we don't allow new coins even if they're from us
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard_extra), 38 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard_extra), 38 * CENT, CENT));
// but we can make 37 cents if we accept new coins from ourself
- const auto result3 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 37 * CENT, CENT);
+ const auto result3 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 37 * CENT, CENT);
BOOST_CHECK(result3);
BOOST_CHECK_EQUAL(result3->GetSelectedValue(), 37 * CENT);
// and we can make 38 cents if we accept all new coins
- const auto result4 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 38 * CENT, CENT);
+ const auto result4 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 38 * CENT, CENT);
BOOST_CHECK(result4);
BOOST_CHECK_EQUAL(result4->GetSelectedValue(), 38 * CENT);
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
- const auto result5 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 34 * CENT, CENT);
+ const auto result5 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 34 * CENT, CENT);
BOOST_CHECK(result5);
BOOST_CHECK_EQUAL(result5->GetSelectedValue(), 35 * CENT); // but 35 cents is closest
BOOST_CHECK_EQUAL(result5->GetInputSet().size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
- const auto result6 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 7 * CENT, CENT);
+ const auto result6 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 7 * CENT, CENT);
BOOST_CHECK(result6);
BOOST_CHECK_EQUAL(result6->GetSelectedValue(), 7 * CENT);
BOOST_CHECK_EQUAL(result6->GetInputSet().size(), 2U);
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
- const auto result7 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 8 * CENT, CENT);
+ const auto result7 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 8 * CENT, CENT);
BOOST_CHECK(result7);
BOOST_CHECK(result7->GetSelectedValue() == 8 * CENT);
BOOST_CHECK_EQUAL(result7->GetInputSet().size(), 3U);
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
- const auto result8 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 9 * CENT, CENT);
+ const auto result8 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 9 * CENT, CENT);
BOOST_CHECK(result8);
BOOST_CHECK_EQUAL(result8->GetSelectedValue(), 10 * CENT);
BOOST_CHECK_EQUAL(result8->GetInputSet().size(), 1U);
// now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
- coins.clear();
+ available_coins.clear();
- add_coin(coins, *wallet, 6*CENT);
- add_coin(coins, *wallet, 7*CENT);
- add_coin(coins, *wallet, 8*CENT);
- add_coin(coins, *wallet, 20*CENT);
- add_coin(coins, *wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total
+ add_coin(available_coins, *wallet, 6*CENT);
+ add_coin(available_coins, *wallet, 7*CENT);
+ add_coin(available_coins, *wallet, 8*CENT);
+ add_coin(available_coins, *wallet, 20*CENT);
+ add_coin(available_coins, *wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total
// check that we have 71 and not 72
- const auto result9 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 71 * CENT, CENT);
+ const auto result9 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 71 * CENT, CENT);
BOOST_CHECK(result9);
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 72 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 72 * CENT, CENT));
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
- const auto result10 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT);
+ const auto result10 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
BOOST_CHECK(result10);
BOOST_CHECK_EQUAL(result10->GetSelectedValue(), 20 * CENT); // we should get 20 in one coin
BOOST_CHECK_EQUAL(result10->GetInputSet().size(), 1U);
- add_coin(coins, *wallet, 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
+ add_coin(available_coins, *wallet, 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
- const auto result11 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT);
+ const auto result11 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
BOOST_CHECK(result11);
BOOST_CHECK_EQUAL(result11->GetSelectedValue(), 18 * CENT); // we should get 18 in 3 coins
BOOST_CHECK_EQUAL(result11->GetInputSet().size(), 3U);
- add_coin(coins, *wallet, 18*CENT); // now we have 5+6+7+8+18+20+30
+ add_coin(available_coins, *wallet, 18*CENT); // now we have 5+6+7+8+18+20+30
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
- const auto result12 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT);
+ const auto result12 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
BOOST_CHECK(result12);
BOOST_CHECK_EQUAL(result12->GetSelectedValue(), 18 * CENT); // we should get 18 in 1 coin
BOOST_CHECK_EQUAL(result12->GetInputSet().size(), 1U); // because in the event of a tie, the biggest coin wins
// now try making 11 cents. we should get 5+6
- const auto result13 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 11 * CENT, CENT);
+ const auto result13 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 11 * CENT, CENT);
BOOST_CHECK(result13);
BOOST_CHECK_EQUAL(result13->GetSelectedValue(), 11 * CENT);
BOOST_CHECK_EQUAL(result13->GetInputSet().size(), 2U);
// check that the smallest bigger coin is used
- add_coin(coins, *wallet, 1*COIN);
- add_coin(coins, *wallet, 2*COIN);
- add_coin(coins, *wallet, 3*COIN);
- add_coin(coins, *wallet, 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
- const auto result14 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 95 * CENT, CENT);
+ add_coin(available_coins, *wallet, 1*COIN);
+ add_coin(available_coins, *wallet, 2*COIN);
+ add_coin(available_coins, *wallet, 3*COIN);
+ add_coin(available_coins, *wallet, 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
+ const auto result14 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 95 * CENT, CENT);
BOOST_CHECK(result14);
BOOST_CHECK_EQUAL(result14->GetSelectedValue(), 1 * COIN); // we should get 1 BTC in 1 coin
BOOST_CHECK_EQUAL(result14->GetInputSet().size(), 1U);
- const auto result15 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 195 * CENT, CENT);
+ const auto result15 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 195 * CENT, CENT);
BOOST_CHECK(result15);
BOOST_CHECK_EQUAL(result15->GetSelectedValue(), 2 * COIN); // we should get 2 BTC in 1 coin
BOOST_CHECK_EQUAL(result15->GetInputSet().size(), 1U);
// empty the wallet and start again, now with fractions of a cent, to test small change avoidance
- coins.clear();
- add_coin(coins, *wallet, CENT * 1 / 10);
- add_coin(coins, *wallet, CENT * 2 / 10);
- add_coin(coins, *wallet, CENT * 3 / 10);
- add_coin(coins, *wallet, CENT * 4 / 10);
- add_coin(coins, *wallet, CENT * 5 / 10);
+ available_coins.clear();
+ add_coin(available_coins, *wallet, CENT * 1 / 10);
+ add_coin(available_coins, *wallet, CENT * 2 / 10);
+ add_coin(available_coins, *wallet, CENT * 3 / 10);
+ add_coin(available_coins, *wallet, CENT * 4 / 10);
+ add_coin(available_coins, *wallet, CENT * 5 / 10);
// try making 1 * CENT from the 1.5 * CENT
// we'll get change smaller than CENT whatever happens, so can expect CENT exactly
- const auto result16 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT, CENT);
+ const auto result16 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT, CENT);
BOOST_CHECK(result16);
BOOST_CHECK_EQUAL(result16->GetSelectedValue(), CENT);
// but if we add a bigger coin, small change is avoided
- add_coin(coins, *wallet, 1111*CENT);
+ add_coin(available_coins, *wallet, 1111*CENT);
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
- const auto result17 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
+ const auto result17 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result17);
BOOST_CHECK_EQUAL(result17->GetSelectedValue(), 1 * CENT); // we should get the exact amount
// if we add more small coins:
- add_coin(coins, *wallet, CENT * 6 / 10);
- add_coin(coins, *wallet, CENT * 7 / 10);
+ add_coin(available_coins, *wallet, CENT * 6 / 10);
+ add_coin(available_coins, *wallet, CENT * 7 / 10);
// and try again to make 1.0 * CENT
- const auto result18 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
+ const auto result18 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result18);
BOOST_CHECK_EQUAL(result18->GetSelectedValue(), 1 * CENT); // we should get the exact amount
// run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
// they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
- coins.clear();
+ available_coins.clear();
for (int j = 0; j < 20; j++)
- add_coin(coins, *wallet, 50000 * COIN);
+ add_coin(available_coins, *wallet, 50000 * COIN);
- const auto result19 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 500000 * COIN, CENT);
+ const auto result19 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 500000 * COIN, CENT);
BOOST_CHECK(result19);
BOOST_CHECK_EQUAL(result19->GetSelectedValue(), 500000 * COIN); // we should get the exact amount
BOOST_CHECK_EQUAL(result19->GetInputSet().size(), 10U); // in ten coins
@@ -587,41 +587,41 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// we need to try finding an exact subset anyway
// sometimes it will fail, and so we use the next biggest coin:
- coins.clear();
- add_coin(coins, *wallet, CENT * 5 / 10);
- add_coin(coins, *wallet, CENT * 6 / 10);
- add_coin(coins, *wallet, CENT * 7 / 10);
- add_coin(coins, *wallet, 1111 * CENT);
- const auto result20 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
+ available_coins.clear();
+ add_coin(available_coins, *wallet, CENT * 5 / 10);
+ add_coin(available_coins, *wallet, CENT * 6 / 10);
+ add_coin(available_coins, *wallet, CENT * 7 / 10);
+ add_coin(available_coins, *wallet, 1111 * CENT);
+ const auto result20 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result20);
BOOST_CHECK_EQUAL(result20->GetSelectedValue(), 1111 * CENT); // we get the bigger coin
BOOST_CHECK_EQUAL(result20->GetInputSet().size(), 1U);
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
- coins.clear();
- add_coin(coins, *wallet, CENT * 4 / 10);
- add_coin(coins, *wallet, CENT * 6 / 10);
- add_coin(coins, *wallet, CENT * 8 / 10);
- add_coin(coins, *wallet, 1111 * CENT);
- const auto result21 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT, CENT);
+ available_coins.clear();
+ add_coin(available_coins, *wallet, CENT * 4 / 10);
+ add_coin(available_coins, *wallet, CENT * 6 / 10);
+ add_coin(available_coins, *wallet, CENT * 8 / 10);
+ add_coin(available_coins, *wallet, 1111 * CENT);
+ const auto result21 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT, CENT);
BOOST_CHECK(result21);
BOOST_CHECK_EQUAL(result21->GetSelectedValue(), CENT); // we should get the exact amount
BOOST_CHECK_EQUAL(result21->GetInputSet().size(), 2U); // in two coins 0.4+0.6
// test avoiding small change
- coins.clear();
- add_coin(coins, *wallet, CENT * 5 / 100);
- add_coin(coins, *wallet, CENT * 1);
- add_coin(coins, *wallet, CENT * 100);
+ available_coins.clear();
+ add_coin(available_coins, *wallet, CENT * 5 / 100);
+ add_coin(available_coins, *wallet, CENT * 1);
+ add_coin(available_coins, *wallet, CENT * 100);
// trying to make 100.01 from these three coins
- const auto result22 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT * 10001 / 100, CENT);
+ const auto result22 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT * 10001 / 100, CENT);
BOOST_CHECK(result22);
BOOST_CHECK_EQUAL(result22->GetSelectedValue(), CENT * 10105 / 100); // we should get all coins
BOOST_CHECK_EQUAL(result22->GetInputSet().size(), 3U);
// but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
- const auto result23 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT * 9990 / 100, CENT);
+ const auto result23 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT * 9990 / 100, CENT);
BOOST_CHECK(result23);
BOOST_CHECK_EQUAL(result23->GetSelectedValue(), 101 * CENT);
BOOST_CHECK_EQUAL(result23->GetInputSet().size(), 2U);
@@ -629,14 +629,14 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// test with many inputs
for (CAmount amt=1500; amt < COIN; amt*=10) {
- coins.clear();
+ available_coins.clear();
// Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 bytes per input)
for (uint16_t j = 0; j < 676; j++)
- add_coin(coins, *wallet, amt);
+ add_coin(available_coins, *wallet, amt);
// We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
for (int i = 0; i < RUN_TESTS; i++) {
- const auto result24 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 2000, CENT);
+ const auto result24 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 2000, CENT);
BOOST_CHECK(result24);
if (amt - 2000 < CENT) {
@@ -655,17 +655,17 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// test randomness
{
- coins.clear();
+ available_coins.clear();
for (int i2 = 0; i2 < 100; i2++)
- add_coin(coins, *wallet, COIN);
+ add_coin(available_coins, *wallet, COIN);
// Again, we only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
for (int i = 0; i < RUN_TESTS; i++) {
// picking 50 from 100 coins doesn't depend on the shuffle,
// but does depend on randomness in the stochastic approximation code
- const auto result25 = KnapsackSolver(GroupCoins(coins), 50 * COIN, CENT);
+ const auto result25 = KnapsackSolver(GroupCoins(available_coins.all()), 50 * COIN, CENT);
BOOST_CHECK(result25);
- const auto result26 = KnapsackSolver(GroupCoins(coins), 50 * COIN, CENT);
+ const auto result26 = KnapsackSolver(GroupCoins(available_coins.all()), 50 * COIN, CENT);
BOOST_CHECK(result26);
BOOST_CHECK(!EqualResult(*result25, *result26));
@@ -676,9 +676,9 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// When choosing 1 from 100 identical coins, 1% of the time, this test will choose the same coin twice
// which will cause it to fail.
// To avoid that issue, run the test RANDOM_REPEATS times and only complain if all of them fail
- const auto result27 = KnapsackSolver(GroupCoins(coins), COIN, CENT);
+ const auto result27 = KnapsackSolver(GroupCoins(available_coins.all()), COIN, CENT);
BOOST_CHECK(result27);
- const auto result28 = KnapsackSolver(GroupCoins(coins), COIN, CENT);
+ const auto result28 = KnapsackSolver(GroupCoins(available_coins.all()), COIN, CENT);
BOOST_CHECK(result28);
if (EqualResult(*result27, *result28))
fails++;
@@ -689,19 +689,19 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// add 75 cents in small change. not enough to make 90 cents,
// then try making 90 cents. there are multiple competing "smallest bigger" coins,
// one of which should be picked at random
- add_coin(coins, *wallet, 5 * CENT);
- add_coin(coins, *wallet, 10 * CENT);
- add_coin(coins, *wallet, 15 * CENT);
- add_coin(coins, *wallet, 20 * CENT);
- add_coin(coins, *wallet, 25 * CENT);
+ add_coin(available_coins, *wallet, 5 * CENT);
+ add_coin(available_coins, *wallet, 10 * CENT);
+ add_coin(available_coins, *wallet, 15 * CENT);
+ add_coin(available_coins, *wallet, 20 * CENT);
+ add_coin(available_coins, *wallet, 25 * CENT);
for (int i = 0; i < RUN_TESTS; i++) {
int fails = 0;
for (int j = 0; j < RANDOM_REPEATS; j++)
{
- const auto result29 = KnapsackSolver(GroupCoins(coins), 90 * CENT, CENT);
+ const auto result29 = KnapsackSolver(GroupCoins(available_coins.all()), 90 * CENT, CENT);
BOOST_CHECK(result29);
- const auto result30 = KnapsackSolver(GroupCoins(coins), 90 * CENT, CENT);
+ const auto result30 = KnapsackSolver(GroupCoins(available_coins.all()), 90 * CENT, CENT);
BOOST_CHECK(result30);
if (EqualResult(*result29, *result30))
fails++;
@@ -720,14 +720,14 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet->SetupDescriptorScriptPubKeyMans();
- std::vector<COutput> coins;
+ CoinsResult available_coins;
// Test vValue sort order
for (int i = 0; i < 1000; i++)
- add_coin(coins, *wallet, 1000 * COIN);
- add_coin(coins, *wallet, 3 * COIN);
+ add_coin(available_coins, *wallet, 1000 * COIN);
+ add_coin(available_coins, *wallet, 3 * COIN);
- const auto result = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1003 * COIN, CENT, rand);
+ const auto result = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1003 * COIN, CENT, rand);
BOOST_CHECK(result);
BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN);
BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U);
@@ -750,14 +750,14 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
// Run this test 100 times
for (int i = 0; i < 100; ++i)
{
- std::vector<COutput> coins;
+ CoinsResult available_coins;
CAmount balance{0};
// Make a wallet with 1000 exponentially distributed random inputs
for (int j = 0; j < 1000; ++j)
{
CAmount val = distribution(generator)*10000000;
- add_coin(coins, *wallet, val);
+ add_coin(available_coins, *wallet, val);
balance += val;
}
@@ -780,7 +780,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
/*avoid_partial=*/ false,
};
CCoinControl cc;
- const auto result = SelectCoins(*wallet, coins, target, cc, cs_params);
+ const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params);
BOOST_CHECK(result);
BOOST_CHECK_GE(result->GetSelectedValue(), target);
}
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index aa5df695de..81183aa738 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -591,7 +591,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
// Lock both coins. Confirm number of available coins drops to 0.
{
LOCK(wallet->cs_wallet);
- BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).coins.size(), 2U);
+ BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).size(), 2U);
}
for (const auto& group : list) {
for (const auto& coin : group.second) {
@@ -601,7 +601,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
}
{
LOCK(wallet->cs_wallet);
- BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).coins.size(), 0U);
+ BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).size(), 0U);
}
// Confirm ListCoins still returns same result as before, despite coins
// being locked.
@@ -779,7 +779,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
promise.set_value();
SyncWithValidationInterfaceQueue();
// AddToWallet events for block_tx and mempool_tx events are counted a
- // second time as the notificaiton queue is processed
+ // second time as the notification queue is processed
BOOST_CHECK_EQUAL(addtx_count, 4);
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index a6566f152f..23a581469e 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -820,6 +820,16 @@ class PSBTTest(BitcoinTestFramework):
assert hash.hex() in res_input[preimage_key]
assert_equal(res_input[preimage_key][hash.hex()], preimage.hex())
+ self.log.info("Test that combining PSBTs with different transactions fails")
+ tx = CTransaction()
+ tx.vin = [CTxIn(outpoint=COutPoint(hash=int('aa' * 32, 16), n=0), scriptSig=b"")]
+ tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")]
+ psbt1 = PSBT(g=PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()}), i=[PSBTMap()], o=[PSBTMap()]).to_base64()
+ tx.vout[0].nValue += 1 # slightly modify tx
+ psbt2 = PSBT(g=PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()}), i=[PSBTMap()], o=[PSBTMap()]).to_base64()
+ assert_raises_rpc_error(-8, "PSBTs not compatible (different transactions)", self.nodes[0].combinepsbt, [psbt1, psbt2])
+ assert_equal(self.nodes[0].combinepsbt([psbt1, psbt1]), psbt1)
+
if __name__ == '__main__':
PSBTTest().main()
diff --git a/test/functional/test_framework/psbt.py b/test/functional/test_framework/psbt.py
index ad3fe29b62..68945e7e84 100644
--- a/test/functional/test_framework/psbt.py
+++ b/test/functional/test_framework/psbt.py
@@ -96,10 +96,10 @@ class PSBTMap:
class PSBT:
"""Class for serializing and deserializing PSBTs"""
- def __init__(self):
- self.g = PSBTMap()
- self.i = []
- self.o = []
+ def __init__(self, *, g=None, i=None, o=None):
+ self.g = g if g is not None else PSBTMap()
+ self.i = i if i is not None else []
+ self.o = o if o is not None else []
self.tx = None
def deserialize(self, f):
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index c5a69afa6e..e5784eb614 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -156,6 +156,7 @@ BASE_SCRIPTS = [
'mempool_spend_coinbase.py',
'wallet_avoidreuse.py --legacy-wallet',
'wallet_avoidreuse.py --descriptors',
+ 'wallet_avoid_mixing_output_types.py --descriptors',
'mempool_reorg.py',
'mempool_persist.py',
'p2p_block_sync.py',
diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py
index f7c80f805c..5b836f693f 100755
--- a/test/functional/wallet_address_types.py
+++ b/test/functional/wallet_address_types.py
@@ -345,31 +345,19 @@ class AddressTypeTest(BitcoinTestFramework):
self.log.info("Nodes with addresstype=legacy never use a P2WPKH change output (unless changetype is set otherwise):")
self.test_change_output_type(0, [to_address_bech32_1], 'legacy')
- if self.options.descriptors:
- self.log.info("Nodes with addresstype=p2sh-segwit match the change output")
- self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit')
- self.test_change_output_type(1, [to_address_bech32_1], 'bech32')
- self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32')
- self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32')
- else:
- self.log.info("Nodes with addresstype=p2sh-segwit match the change output")
- self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit')
- self.test_change_output_type(1, [to_address_bech32_1], 'bech32')
- self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32')
- self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32')
+ self.log.info("Nodes with addresstype=p2sh-segwit match the change output")
+ self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit')
+ self.test_change_output_type(1, [to_address_bech32_1], 'bech32')
+ self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32')
+ self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32')
self.log.info("Nodes with change_type=bech32 always use a P2WPKH change output:")
self.test_change_output_type(2, [to_address_bech32_1], 'bech32')
self.test_change_output_type(2, [to_address_p2sh], 'bech32')
- if self.options.descriptors:
- self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):")
- self.test_change_output_type(3, [to_address_bech32_1], 'bech32')
- self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit')
- else:
- self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):")
- self.test_change_output_type(3, [to_address_bech32_1], 'bech32')
- self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit')
+ self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):")
+ self.test_change_output_type(3, [to_address_bech32_1], 'bech32')
+ self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit')
self.log.info('getrawchangeaddress defaults to addresstype if -changetype is not set and argument is absent')
self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32')
diff --git a/test/functional/wallet_avoid_mixing_output_types.py b/test/functional/wallet_avoid_mixing_output_types.py
new file mode 100755
index 0000000000..46f41d9c22
--- /dev/null
+++ b/test/functional/wallet_avoid_mixing_output_types.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python3
+# 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.
+"""Test output type mixing during coin selection
+
+A wallet may have different types of UTXOs to choose from during coin selection,
+where output type is one of the following:
+ - BECH32M
+ - BECH32
+ - P2SH-SEGWIT
+ - LEGACY
+
+This test verifies that mixing different output types is avoided unless
+absolutely necessary. Both wallets start with zero funds. Alice mines
+enough blocks to have spendable coinbase outputs. Alice sends three
+random value payments which sum to 10BTC for each output type to Bob,
+for a total of 40BTC in Bob's wallet.
+
+Bob then sends random valued payments back to Alice, some of which need
+unconfirmed change, and we verify that none of these payments contain mixed
+inputs. Finally, Bob sends the remainder of his funds, which requires mixing.
+
+The payment values are random, but chosen such that they sum up to a specified
+total. This ensures we are not relying on specific values for the UTXOs,
+but still know when to expect mixing due to the wallet being close to empty.
+
+"""
+
+import random
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.blocktools import COINBASE_MATURITY
+
+ADDRESS_TYPES = [
+ "bech32m",
+ "bech32",
+ "p2sh-segwit",
+ "legacy",
+]
+
+
+def is_bech32_address(node, addr):
+ """Check if an address contains a bech32 output."""
+ addr_info = node.getaddressinfo(addr)
+ return addr_info['desc'].startswith('wpkh(')
+
+
+def is_bech32m_address(node, addr):
+ """Check if an address contains a bech32m output."""
+ addr_info = node.getaddressinfo(addr)
+ return addr_info['desc'].startswith('tr(')
+
+
+def is_p2sh_segwit_address(node, addr):
+ """Check if an address contains a P2SH-Segwit output.
+ Note: this function does not actually determine the type
+ of P2SH output, but is sufficient for this test in that
+ we are only generating P2SH-Segwit outputs.
+ """
+ addr_info = node.getaddressinfo(addr)
+ return addr_info['desc'].startswith('sh(wpkh(')
+
+
+def is_legacy_address(node, addr):
+ """Check if an address contains a legacy output."""
+ addr_info = node.getaddressinfo(addr)
+ return addr_info['desc'].startswith('pkh(')
+
+
+def is_same_type(node, tx):
+ """Check that all inputs are of the same OutputType"""
+ vins = node.getrawtransaction(tx, True)['vin']
+ inputs = []
+ for vin in vins:
+ prev_tx, n = vin['txid'], vin['vout']
+ inputs.append(
+ node.getrawtransaction(
+ prev_tx,
+ True,
+ )['vout'][n]['scriptPubKey']['address']
+ )
+ has_legacy = False
+ has_p2sh = False
+ has_bech32 = False
+ has_bech32m = False
+
+ for addr in inputs:
+ if is_legacy_address(node, addr):
+ has_legacy = True
+ if is_p2sh_segwit_address(node, addr):
+ has_p2sh = True
+ if is_bech32_address(node, addr):
+ has_bech32 = True
+ if is_bech32m_address(node, addr):
+ has_bech32m = True
+
+ return (sum([has_legacy, has_p2sh, has_bech32, has_bech32m]) == 1)
+
+
+def generate_payment_values(n, m):
+ """Return a randomly chosen list of n positive integers summing to m.
+ Each such list is equally likely to occur."""
+
+ dividers = sorted(random.sample(range(1, m), n - 1))
+ return [a - b for a, b in zip(dividers + [m], [0] + dividers)]
+
+
+class AddressInputTypeGrouping(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+ self.extra_args = [
+ [
+ "-addresstype=bech32",
+ "-whitelist=noban@127.0.0.1",
+ "-txindex",
+ ],
+ [
+ "-addresstype=p2sh-segwit",
+ "-whitelist=noban@127.0.0.1",
+ "-txindex",
+ ],
+ ]
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def make_payment(self, A, B, v, addr_type):
+ fee_rate = random.randint(1, 20)
+ self.log.debug(f"Making payment of {v} BTC at fee_rate {fee_rate}")
+ tx = B.sendtoaddress(
+ address=A.getnewaddress(address_type=addr_type),
+ amount=v,
+ fee_rate=fee_rate,
+ )
+ return tx
+
+ def run_test(self):
+
+ # alias self.nodes[i] to A, B for readability
+ A, B = self.nodes[0], self.nodes[1]
+ self.generate(A, COINBASE_MATURITY + 5)
+
+ self.log.info("Creating mixed UTXOs in B's wallet")
+ for v in generate_payment_values(3, 10):
+ self.log.debug(f"Making payment of {v} BTC to legacy")
+ A.sendtoaddress(B.getnewaddress(address_type="legacy"), v)
+
+ for v in generate_payment_values(3, 10):
+ self.log.debug(f"Making payment of {v} BTC to p2sh")
+ A.sendtoaddress(B.getnewaddress(address_type="p2sh-segwit"), v)
+
+ for v in generate_payment_values(3, 10):
+ self.log.debug(f"Making payment of {v} BTC to bech32")
+ A.sendtoaddress(B.getnewaddress(address_type="bech32"), v)
+
+ for v in generate_payment_values(3, 10):
+ self.log.debug(f"Making payment of {v} BTC to bech32m")
+ A.sendtoaddress(B.getnewaddress(address_type="bech32m"), v)
+
+ self.generate(A, 1)
+
+ self.log.info("Sending payments from B to A")
+ for v in generate_payment_values(5, 9):
+ tx = self.make_payment(
+ A, B, v, random.choice(ADDRESS_TYPES)
+ )
+ self.generate(A, 1)
+ assert is_same_type(B, tx)
+
+ tx = self.make_payment(A, B, 30.99, random.choice(ADDRESS_TYPES))
+ assert not is_same_type(B, tx)
+
+
+if __name__ == '__main__':
+ AddressInputTypeGrouping().main()