aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.clang-format3
-rw-r--r--src/Makefile.test_util.include1
-rw-r--r--src/base58.cpp2
-rw-r--r--src/bench/block_assemble.cpp2
-rw-r--r--src/bench/data.cpp2
-rw-r--r--src/bitcoin-cli.cpp3
-rw-r--r--src/chain.h13
-rw-r--r--src/chainparams.cpp33
-rw-r--r--src/chainparams.h26
-rw-r--r--src/coins.cpp8
-rw-r--r--src/coins.h12
-rw-r--r--src/index/base.cpp43
-rw-r--r--src/init.cpp10
-rw-r--r--src/logging.cpp13
-rw-r--r--src/logging.h10
-rw-r--r--src/net.cpp42
-rw-r--r--src/net.h3
-rw-r--r--src/net_processing.cpp29
-rw-r--r--src/node/coinstats.cpp12
-rw-r--r--src/node/interfaces.cpp2
-rw-r--r--src/node/transaction.cpp4
-rw-r--r--src/protocol.cpp3
-rw-r--r--src/qt/forms/debugwindow.ui110
-rw-r--r--src/qt/guiutil.cpp132
-rw-r--r--src/qt/guiutil.h41
-rw-r--r--src/qt/networkstyle.cpp13
-rw-r--r--src/qt/peertablemodel.cpp5
-rw-r--r--src/qt/peertablemodel.h15
-rw-r--r--src/qt/platformstyle.cpp15
-rw-r--r--src/qt/receivecoinsdialog.cpp40
-rw-r--r--src/qt/receivecoinsdialog.h2
-rw-r--r--src/qt/rpcconsole.cpp17
-rw-r--r--src/qt/transactionview.cpp122
-rw-r--r--src/qt/transactionview.h6
-rw-r--r--src/random.cpp3
-rw-r--r--src/rest.cpp20
-rw-r--r--src/rpc/blockchain.cpp24
-rw-r--r--src/rpc/blockchain.h8
-rw-r--r--src/rpc/rawtransaction.cpp2
-rw-r--r--src/script/descriptor.cpp93
-rw-r--r--src/script/descriptor.h3
-rw-r--r--src/test/base32_tests.cpp2
-rw-r--r--src/test/base64_tests.cpp2
-rw-r--r--src/test/descriptor_tests.cpp82
-rw-r--r--src/test/fuzz/fuzz.cpp2
-rw-r--r--src/test/fuzz/string.cpp1
-rw-r--r--src/test/hash_tests.cpp4
-rw-r--r--src/test/miner_tests.cpp24
-rw-r--r--src/test/net_tests.cpp2
-rw-r--r--src/test/sanity_tests.cpp2
-rw-r--r--src/test/scriptnum_tests.cpp8
-rw-r--r--src/test/sighash_tests.cpp2
-rw-r--r--src/test/sock_tests.cpp2
-rw-r--r--src/test/txvalidation_tests.cpp2
-rw-r--r--src/test/txvalidationcache_tests.cpp2
-rw-r--r--src/test/util/script.h21
-rw-r--r--src/test/util/setup_common.cpp88
-rw-r--r--src/test/util/setup_common.h29
-rw-r--r--src/test/util_tests.cpp13
-rw-r--r--src/test/validation_block_tests.cpp21
-rw-r--r--src/test/validation_chainstatemanager_tests.cpp187
-rw-r--r--src/test/validation_tests.cpp24
-rw-r--r--src/txdb.cpp14
-rw-r--r--src/txmempool.cpp10
-rw-r--r--src/txmempool.h3
-rw-r--r--src/util/strencodings.h2
-rw-r--r--src/util/string.h8
-rw-r--r--src/util/time.cpp72
-rw-r--r--src/util/time.h16
-rw-r--r--src/validation.cpp440
-rw-r--r--src/validation.h63
-rw-r--r--src/wallet/rpcwallet.cpp17
-rw-r--r--src/wallet/scriptpubkeyman.cpp13
-rw-r--r--src/wallet/scriptpubkeyman.h2
74 files changed, 1531 insertions, 596 deletions
diff --git a/src/.clang-format b/src/.clang-format
index ef7a0ef5c7..a69c57f3e0 100644
--- a/src/.clang-format
+++ b/src/.clang-format
@@ -11,7 +11,8 @@ AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true
-BinPackParameters: false
+BinPackArguments: true
+BinPackParameters: true
BreakBeforeBinaryOperators: false
BreakBeforeBraces: Custom
BraceWrapping:
diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include
index 0621da8ddf..1abfb667a0 100644
--- a/src/Makefile.test_util.include
+++ b/src/Makefile.test_util.include
@@ -12,6 +12,7 @@ TEST_UTIL_H = \
test/util/logging.h \
test/util/mining.h \
test/util/net.h \
+ test/util/script.h \
test/util/setup_common.h \
test/util/str.h \
test/util/transaction_utils.h \
diff --git a/src/base58.cpp b/src/base58.cpp
index 65e373283c..fb04673c5c 100644
--- a/src/base58.cpp
+++ b/src/base58.cpp
@@ -52,7 +52,7 @@ static const int8_t mapBase58[256] = {
int size = strlen(psz) * 733 /1000 + 1; // log(58) / log(256), rounded up.
std::vector<unsigned char> b256(size);
// Process the characters.
- static_assert(sizeof(mapBase58)/sizeof(mapBase58[0]) == 256, "mapBase58.size() should be 256"); // guarantee not out of range
+ static_assert(std::size(mapBase58) == 256, "mapBase58.size() should be 256"); // guarantee not out of range
while (*psz && !IsSpace(*psz)) {
// Decode base58 character
int carry = mapBase58[(uint8_t)*psz];
diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp
index 9f7d582e8a..8f656c44d9 100644
--- a/src/bench/block_assemble.cpp
+++ b/src/bench/block_assemble.cpp
@@ -48,7 +48,7 @@ static void AssembleBlock(benchmark::Bench& bench)
LOCK(::cs_main); // Required for ::AcceptToMemoryPool.
for (const auto& txr : txs) {
- const MempoolAcceptResult res = ::AcceptToMemoryPool(*test_setup.m_node.mempool, txr, false /* bypass_limits */);
+ const MempoolAcceptResult res = ::AcceptToMemoryPool(::ChainstateActive(), *test_setup.m_node.mempool, txr, false /* bypass_limits */);
assert(res.m_result_type == MempoolAcceptResult::ResultType::VALID);
}
}
diff --git a/src/bench/data.cpp b/src/bench/data.cpp
index 0ae4c7cad4..481e372105 100644
--- a/src/bench/data.cpp
+++ b/src/bench/data.cpp
@@ -8,7 +8,7 @@ namespace benchmark {
namespace data {
#include <bench/data/block413567.raw.h>
-const std::vector<uint8_t> block413567{block413567_raw, block413567_raw + sizeof(block413567_raw) / sizeof(block413567_raw[0])};
+const std::vector<uint8_t> block413567{std::begin(block413567_raw), std::end(block413567_raw)};
} // namespace data
} // namespace benchmark
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index a9a0ec6f5d..0830cb54cb 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -302,6 +302,7 @@ private:
static constexpr int8_t UNKNOWN_NETWORK{-1};
static constexpr int8_t NET_I2P{3}; // pos of "i2p" in m_networks
static constexpr uint8_t m_networks_size{4};
+ static constexpr uint8_t MAX_DETAIL_LEVEL{4};
const std::array<std::string, m_networks_size> m_networks{{"ipv4", "ipv6", "onion", "i2p"}};
std::array<std::array<uint16_t, m_networks_size + 1>, 3> m_counts{{{}}}; //!< Peer counts by (in/out/total, networks/total)
uint8_t m_block_relay_peers_count{0};
@@ -439,7 +440,7 @@ public:
if (!args.empty()) {
uint8_t n{0};
if (ParseUInt8(args.at(0), &n)) {
- m_details_level = n;
+ m_details_level = std::min(n, MAX_DETAIL_LEVEL);
} else if (args.at(0) == "help") {
m_is_help_requested = true;
} else {
diff --git a/src/chain.h b/src/chain.h
index 43e8a39f36..04a5db5a17 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -163,14 +163,27 @@ public:
//! Number of transactions in this block.
//! Note: in a potential headers-first mode, this number cannot be relied upon
+ //! Note: this value is faked during UTXO snapshot load to ensure that
+ //! LoadBlockIndex() will load index entries for blocks that we lack data for.
+ //! @sa ActivateSnapshot
unsigned int nTx{0};
//! (memory only) Number of transactions in the chain up to and including this block.
//! This value will be non-zero only if and only if transactions for this block and all its parents are available.
//! Change to 64-bit type when necessary; won't happen before 2030
+ //!
+ //! Note: this value is faked during use of a UTXO snapshot because we don't
+ //! have the underlying block data available during snapshot load.
+ //! @sa AssumeutxoData
+ //! @sa ActivateSnapshot
unsigned int nChainTx{0};
//! Verification status of this block. See enum BlockStatus
+ //!
+ //! Note: this value is modified to show BLOCK_OPT_WITNESS during UTXO snapshot
+ //! load to avoid the block index being spuriously rewound.
+ //! @sa RewindBlockIndex
+ //! @sa ActivateSnapshot
uint32_t nStatus{0};
//! block header
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index 88cf5ef0a8..16efffa6f0 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -8,9 +8,7 @@
#include <chainparamsseeds.h>
#include <consensus/merkle.h>
#include <hash.h> // for signet block challenge hash
-#include <tinyformat.h>
#include <util/system.h>
-#include <util/strencodings.h>
#include <versionbitsinfo.h>
#include <assert.h>
@@ -136,7 +134,7 @@ public:
bech32_hrp = "bc";
- vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main));
+ vFixedSeeds = std::vector<SeedSpec6>(std::begin(pnSeed6_main), std::end(pnSeed6_main));
fDefaultConsistencyChecks = false;
fRequireStandard = true;
@@ -161,6 +159,10 @@ public:
}
};
+ m_assumeutxo_data = MapAssumeutxo{
+ // TODO to be specified in a future patch.
+ };
+
chainTxData = ChainTxData{
// Data from RPC: getchaintxstats 4096 0000000000000000000b9d2ec5a352ecba0592946514a92f14319dc2b367fc72
/* nTime */ 1603995752,
@@ -237,7 +239,7 @@ public:
bech32_hrp = "tb";
- vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test));
+ vFixedSeeds = std::vector<SeedSpec6>(std::begin(pnSeed6_test), std::end(pnSeed6_test));
fDefaultConsistencyChecks = false;
fRequireStandard = false;
@@ -250,6 +252,10 @@ public:
}
};
+ m_assumeutxo_data = MapAssumeutxo{
+ // TODO to be specified in a future patch.
+ };
+
chainTxData = ChainTxData{
// Data from RPC: getchaintxstats 4096 000000000000006433d1efec504c53ca332b64963c425395515b01977bd7b3b0
/* nTime */ 1603359686,
@@ -406,7 +412,7 @@ public:
pchMessageStart[2] = 0xb5;
pchMessageStart[3] = 0xda;
nDefaultPort = 18444;
- nPruneAfterHeight = 1000;
+ nPruneAfterHeight = gArgs.GetBoolArg("-fastprune", false) ? 100 : 1000;
m_assumed_blockchain_size = 0;
m_assumed_chain_state_size = 0;
@@ -431,6 +437,17 @@ public:
}
};
+ m_assumeutxo_data = MapAssumeutxo{
+ {
+ 110,
+ {uint256S("0x76fd7334ac7c1baf57ddc0c626f073a655a35d98a4258cd1382c8cc2b8392e10"), 110},
+ },
+ {
+ 210,
+ {uint256S("0x9c5ed99ef98544b34f8920b6d1802f72ac28ae6e2bd2bd4c316ff10c230df3f2"), 210},
+ },
+ };
+
chainTxData = ChainTxData{
0,
0,
@@ -526,3 +543,9 @@ void SelectParams(const std::string& network)
SelectBaseParams(network);
globalChainParams = CreateChainParams(gArgs, network);
}
+
+std::ostream& operator<<(std::ostream& o, const AssumeutxoData& aud)
+{
+ o << strprintf("AssumeutxoData(%s, %s)", aud.hash_serialized.ToString(), aud.nChainTx);
+ return o;
+}
diff --git a/src/chainparams.h b/src/chainparams.h
index d8b25c7220..4d24dcdb7c 100644
--- a/src/chainparams.h
+++ b/src/chainparams.h
@@ -31,6 +31,26 @@ struct CCheckpointData {
};
/**
+ * Holds configuration for use during UTXO snapshot load and validation. The contents
+ * here are security critical, since they dictate which UTXO snapshots are recognized
+ * as valid.
+ */
+struct AssumeutxoData {
+ //! The expected hash of the deserialized UTXO set.
+ const uint256 hash_serialized;
+
+ //! Used to populate the nChainTx value, which is used during BlockManager::LoadBlockIndex().
+ //!
+ //! We need to hardcode the value here because this is computed cumulatively using block data,
+ //! which we do not necessarily have at the time of snapshot load.
+ const unsigned int nChainTx;
+};
+
+std::ostream& operator<<(std::ostream& o, const AssumeutxoData& aud);
+
+using MapAssumeutxo = std::map<int, const AssumeutxoData>;
+
+/**
* Holds various statistics on transactions within a chain. Used to estimate
* verification progress during chain sync.
*
@@ -90,6 +110,11 @@ public:
const std::string& Bech32HRP() const { return bech32_hrp; }
const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
const CCheckpointData& Checkpoints() const { return checkpointData; }
+
+ //! Get allowed assumeutxo configuration.
+ //! @see ChainstateManager
+ const MapAssumeutxo& Assumeutxo() const { return m_assumeutxo_data; }
+
const ChainTxData& TxData() const { return chainTxData; }
protected:
CChainParams() {}
@@ -111,6 +136,7 @@ protected:
bool m_is_test_chain;
bool m_is_mockable_chain;
CCheckpointData checkpointData;
+ MapAssumeutxo m_assumeutxo_data;
ChainTxData chainTxData;
};
diff --git a/src/coins.cpp b/src/coins.cpp
index dd84e720e7..d52851cadd 100644
--- a/src/coins.cpp
+++ b/src/coins.cpp
@@ -97,6 +97,14 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
cachedCoinsUsage += it->second.coin.DynamicMemoryUsage();
}
+void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin) {
+ cachedCoinsUsage += coin.DynamicMemoryUsage();
+ cacheCoins.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(std::move(outpoint)),
+ std::forward_as_tuple(std::move(coin), CCoinsCacheEntry::DIRTY));
+}
+
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check_for_overwrite) {
bool fCoinbase = tx.IsCoinBase();
const uint256& txid = tx.GetHash();
diff --git a/src/coins.h b/src/coins.h
index d2eb42d8cf..feb441fd6a 100644
--- a/src/coins.h
+++ b/src/coins.h
@@ -20,6 +20,8 @@
#include <functional>
#include <unordered_map>
+class ChainstateManager;
+
/**
* A UTXO entry.
*
@@ -125,6 +127,7 @@ struct CCoinsCacheEntry
CCoinsCacheEntry() : flags(0) {}
explicit CCoinsCacheEntry(Coin&& coin_) : coin(std::move(coin_)), flags(0) {}
+ CCoinsCacheEntry(Coin&& coin_, unsigned char flag) : coin(std::move(coin_)), flags(flag) {}
};
typedef std::unordered_map<COutPoint, CCoinsCacheEntry, SaltedOutpointHasher> CCoinsMap;
@@ -263,6 +266,15 @@ public:
void AddCoin(const COutPoint& outpoint, Coin&& coin, bool possible_overwrite);
/**
+ * Emplace a coin into cacheCoins without performing any checks, marking
+ * the emplaced coin as dirty.
+ *
+ * NOT FOR GENERAL USE. Used only when loading coins from a UTXO snapshot.
+ * @sa ChainstateManager::PopulateAndValidateSnapshot()
+ */
+ void EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin);
+
+ /**
* Spend a coin. Pass moveto in order to get the deleted data.
* If no unspent output exists for the passed outpoint, this call
* has no effect.
diff --git a/src/index/base.cpp b/src/index/base.cpp
index 3d3dda95b1..25644c3b41 100644
--- a/src/index/base.cpp
+++ b/src/index/base.cpp
@@ -65,6 +65,43 @@ bool BaseIndex::Init()
m_best_block_index = g_chainman.m_blockman.FindForkInGlobalIndex(::ChainActive(), locator);
}
m_synced = m_best_block_index.load() == ::ChainActive().Tip();
+ if (!m_synced) {
+ bool prune_violation = false;
+ if (!m_best_block_index) {
+ // index is not built yet
+ // make sure we have all block data back to the genesis
+ const CBlockIndex* block = ::ChainActive().Tip();
+ while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
+ block = block->pprev;
+ }
+ prune_violation = block != ::ChainActive().Genesis();
+ }
+ // in case the index has a best block set and is not fully synced
+ // check if we have the required blocks to continue building the index
+ else {
+ const CBlockIndex* block_to_test = m_best_block_index.load();
+ if (!ChainActive().Contains(block_to_test)) {
+ // if the bestblock is not part of the mainchain, find the fork
+ // and make sure we have all data down to the fork
+ block_to_test = ::ChainActive().FindFork(block_to_test);
+ }
+ const CBlockIndex* block = ::ChainActive().Tip();
+ prune_violation = true;
+ // check backwards from the tip if we have all block data until we reach the indexes bestblock
+ while (block_to_test && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
+ if (block_to_test == block) {
+ prune_violation = false;
+ break;
+ }
+ block = block->pprev;
+ }
+ }
+ if (prune_violation) {
+ // throw error and graceful shutdown if we can't build the index
+ FatalError("%s: %s best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)", __func__, GetName());
+ return false;
+ }
+ }
return true;
}
@@ -177,6 +214,10 @@ bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_ti
assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
// In the case of a reorg, ensure persisted block locator is not stale.
+ // Pruning has a minimum of 288 blocks-to-keep and getting the index
+ // out of sync may be possible but a users fault.
+ // In case we reorg beyond the pruned depth, ReadBlockFromDisk would
+ // throw and lead to a graceful shutdown
m_best_block_index = new_tip;
if (!Commit()) {
// If commit fails, revert the best block index to avoid corruption.
@@ -325,6 +366,6 @@ IndexSummary BaseIndex::GetSummary() const
IndexSummary summary{};
summary.name = GetName();
summary.synced = m_synced;
- summary.best_block_height = m_best_block_index.load()->nHeight;
+ summary.best_block_height = m_best_block_index ? m_best_block_index.load()->nHeight : 0;
return summary;
}
diff --git a/src/init.cpp b/src/init.cpp
index 96fb32ce2a..befba2eb2d 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -386,6 +386,7 @@ void SetupServerArgs(NodeContext& node)
#endif
argsman.AddArg("-assumevalid=<hex>", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s, signet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex(), signetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-blocksdir=<dir>", "Specify directory to hold blocks subdirectory for *.dat files (default: <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ argsman.AddArg("-fastprune", "Use smaller block files and lower minimum prune height for testing purposes", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
#if HAVE_SYSTEM
argsman.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#endif
@@ -532,6 +533,7 @@ void SetupServerArgs(NodeContext& node)
#else
hidden_args.emplace_back("-logthreadnames");
#endif
+ argsman.AddArg("-logsourcelocations", strprintf("Prepend debug output with name of the originating source location (source file, line number and function name) (default: %u)", DEFAULT_LOGSOURCELOCATIONS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-logtimemicros", strprintf("Add microsecond precision to debug timestamps (default: %u)", DEFAULT_LOGTIMEMICROS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
@@ -773,6 +775,10 @@ static bool InitSanityCheck()
return InitError(Untranslated("OS cryptographic RNG sanity check failure. Aborting."));
}
+ if (!ChronoSanityCheck()) {
+ return InitError(Untranslated("Clock epoch mismatch. Aborting."));
+ }
+
return true;
}
@@ -878,6 +884,7 @@ void InitLogging(const ArgsManager& args)
#ifdef HAVE_THREAD_LOCAL
LogInstance().m_log_threadnames = args.GetBoolArg("-logthreadnames", DEFAULT_LOGTHREADNAMES);
#endif
+ LogInstance().m_log_sourcelocations = args.GetBoolArg("-logsourcelocations", DEFAULT_LOGSOURCELOCATIONS);
fLogIPs = args.GetBoolArg("-logips", DEFAULT_LOGIPS);
@@ -1024,9 +1031,6 @@ bool AppInitParameterInteraction(const ArgsManager& args)
if (args.GetArg("-prune", 0)) {
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX))
return InitError(_("Prune mode is incompatible with -txindex."));
- if (!g_enabled_filter_types.empty()) {
- return InitError(_("Prune mode is incompatible with -blockfilterindex."));
- }
}
// -bind and -whitebind can't be set when not listening
diff --git a/src/logging.cpp b/src/logging.cpp
index 4ddcf1d930..e82f2c2810 100644
--- a/src/logging.cpp
+++ b/src/logging.cpp
@@ -5,6 +5,7 @@
#include <logging.h>
#include <util/threadnames.h>
+#include <util/string.h>
#include <util/time.h>
#include <mutex>
@@ -203,9 +204,9 @@ std::string BCLog::Logger::LogTimestampStr(const std::string& str)
strStamped.pop_back();
strStamped += strprintf(".%06dZ", nTimeMicros%1000000);
}
- int64_t mocktime = GetMockTime();
- if (mocktime) {
- strStamped += " (mocktime: " + FormatISO8601DateTime(mocktime) + ")";
+ std::chrono::seconds mocktime = GetMockTime();
+ if (mocktime > 0s) {
+ strStamped += " (mocktime: " + FormatISO8601DateTime(count_seconds(mocktime)) + ")";
}
strStamped += ' ' + str;
} else
@@ -236,11 +237,15 @@ namespace BCLog {
}
}
-void BCLog::Logger::LogPrintStr(const std::string& str)
+void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line)
{
StdLockGuard scoped_lock(m_cs);
std::string str_prefixed = LogEscapeMessage(str);
+ if (m_log_sourcelocations && m_started_new_line) {
+ str_prefixed.insert(0, "[" + RemovePrefix(source_file, "./") + ":" + ToString(source_line) + "] [" + logging_function + "] ");
+ }
+
if (m_log_threadnames && m_started_new_line) {
str_prefixed.insert(0, "[" + util::ThreadGetInternalName() + "] ");
}
diff --git a/src/logging.h b/src/logging.h
index 9efecc7c12..4ece8f5e3a 100644
--- a/src/logging.h
+++ b/src/logging.h
@@ -22,6 +22,7 @@ static const bool DEFAULT_LOGTIMEMICROS = false;
static const bool DEFAULT_LOGIPS = false;
static const bool DEFAULT_LOGTIMESTAMPS = true;
static const bool DEFAULT_LOGTHREADNAMES = false;
+static const bool DEFAULT_LOGSOURCELOCATIONS = false;
extern const char * const DEFAULT_DEBUGLOGFILE;
extern bool fLogIPs;
@@ -90,12 +91,13 @@ namespace BCLog {
bool m_log_timestamps = DEFAULT_LOGTIMESTAMPS;
bool m_log_time_micros = DEFAULT_LOGTIMEMICROS;
bool m_log_threadnames = DEFAULT_LOGTHREADNAMES;
+ bool m_log_sourcelocations = DEFAULT_LOGSOURCELOCATIONS;
fs::path m_file_path;
std::atomic<bool> m_reopen_file{false};
/** Send a string to the log output */
- void LogPrintStr(const std::string& str);
+ void LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line);
/** Returns whether logs will be written to any output */
bool Enabled() const
@@ -163,7 +165,7 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str);
// peer can fill up a user's disk with debug.log entries.
template <typename... Args>
-static inline void LogPrintf(const char* fmt, const Args&... args)
+static inline void LogPrintf_(const std::string& logging_function, const std::string& source_file, const int source_line, const char* fmt, const Args&... args)
{
if (LogInstance().Enabled()) {
std::string log_msg;
@@ -173,10 +175,12 @@ static inline void LogPrintf(const char* fmt, const Args&... args)
/* Original format string will have newline so don't add one here */
log_msg = "Error \"" + std::string(fmterr.what()) + "\" while formatting log message: " + fmt;
}
- LogInstance().LogPrintStr(log_msg);
+ LogInstance().LogPrintStr(log_msg, logging_function, source_file, source_line);
}
}
+#define LogPrintf(...) LogPrintf_(__func__, __FILE__, __LINE__, __VA_ARGS__)
+
// Use a macro instead of a function for conditional logging to prevent
// evaluating arguments when logging for the category is not enabled.
#define LogPrint(category, ...) \
diff --git a/src/net.cpp b/src/net.cpp
index 81176785a2..533815b755 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -201,31 +201,29 @@ bool IsPeerAddrLocalGood(CNode *pnode)
IsReachable(addrLocal.GetNetwork());
}
-// pushes our own address to a peer
-void AdvertiseLocal(CNode *pnode)
+Optional<CAddress> GetLocalAddrForPeer(CNode *pnode)
{
- if (fListen && pnode->fSuccessfullyConnected)
+ CAddress addrLocal = GetLocalAddress(&pnode->addr, pnode->GetLocalServices());
+ if (gArgs.GetBoolArg("-addrmantest", false)) {
+ // use IPv4 loopback during addrmantest
+ addrLocal = CAddress(CService(LookupNumeric("127.0.0.1", GetListenPort())), pnode->GetLocalServices());
+ }
+ // If discovery is enabled, sometimes give our peer the address it
+ // tells us that it sees us as in case it has a better idea of our
+ // address than we do.
+ FastRandomContext rng;
+ if (IsPeerAddrLocalGood(pnode) && (!addrLocal.IsRoutable() ||
+ rng.randbits((GetnScore(addrLocal) > LOCAL_MANUAL) ? 3 : 1) == 0))
{
- CAddress addrLocal = GetLocalAddress(&pnode->addr, pnode->GetLocalServices());
- if (gArgs.GetBoolArg("-addrmantest", false)) {
- // use IPv4 loopback during addrmantest
- addrLocal = CAddress(CService(LookupNumeric("127.0.0.1", GetListenPort())), pnode->GetLocalServices());
- }
- // If discovery is enabled, sometimes give our peer the address it
- // tells us that it sees us as in case it has a better idea of our
- // address than we do.
- FastRandomContext rng;
- if (IsPeerAddrLocalGood(pnode) && (!addrLocal.IsRoutable() ||
- rng.randbits((GetnScore(addrLocal) > LOCAL_MANUAL) ? 3 : 1) == 0))
- {
- addrLocal.SetIP(pnode->GetAddrLocal());
- }
- if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false))
- {
- LogPrint(BCLog::NET, "AdvertiseLocal: advertising address %s\n", addrLocal.ToString());
- pnode->PushAddress(addrLocal, rng);
- }
+ addrLocal.SetIP(pnode->GetAddrLocal());
+ }
+ if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false))
+ {
+ LogPrint(BCLog::NET, "Advertising address %s to peer=%d\n", addrLocal.ToString(), pnode->GetId());
+ return addrLocal;
}
+ // Address is unroutable. Don't advertise.
+ return nullopt;
}
// learn a new local address
diff --git a/src/net.h b/src/net.h
index 682150d65a..67d1cf0e55 100644
--- a/src/net.h
+++ b/src/net.h
@@ -197,7 +197,8 @@ enum
};
bool IsPeerAddrLocalGood(CNode *pnode);
-void AdvertiseLocal(CNode *pnode);
+/** Returns a local address that we should advertise to this peer */
+Optional<CAddress> GetLocalAddrForPeer(CNode *pnode);
/**
* Mark a network as reachable or unreachable (no automatic connects to it)
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 22d9fe5ba6..c97f7ced46 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -659,7 +659,7 @@ static void UpdatePreferredDownload(const CNode& node, CNodeState* state) EXCLUS
nPreferredDownload += state->fPreferredDownload;
}
-bool PeerManagerImpl::MarkBlockAsReceived(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+bool PeerManagerImpl::MarkBlockAsReceived(const uint256& hash)
{
std::map<uint256, std::pair<NodeId, std::list<QueuedBlock>::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash);
if (itInFlight != mapBlocksInFlight.end()) {
@@ -683,7 +683,7 @@ bool PeerManagerImpl::MarkBlockAsReceived(const uint256& hash) EXCLUSIVE_LOCKS_R
return false;
}
-bool PeerManagerImpl::MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex, std::list<QueuedBlock>::iterator** pit) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+bool PeerManagerImpl::MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex, std::list<QueuedBlock>::iterator** pit)
{
CNodeState *state = State(nodeid);
assert(state != nullptr);
@@ -752,7 +752,7 @@ static void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) EXCLUSIV
}
}
-void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid)
{
AssertLockHeld(cs_main);
CNodeState* nodestate = State(nodeid);
@@ -791,7 +791,7 @@ void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid) EXCLUS
}
}
-bool PeerManagerImpl::TipMayBeStale() EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+bool PeerManagerImpl::TipMayBeStale()
{
AssertLockHeld(cs_main);
const Consensus::Params& consensusParams = m_chainparams.GetConsensus();
@@ -815,7 +815,7 @@ static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) EXCLUSIV
return false;
}
-void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller)
{
if (count == 0)
return;
@@ -1616,7 +1616,7 @@ void PeerManagerImpl::BlockChecked(const CBlock& block, const BlockValidationSta
//
-bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid)
{
assert(recentRejects);
if (::ChainActive().Tip()->GetBlockHash() != hashRecentRejectsChainTip) {
@@ -1884,7 +1884,7 @@ void static ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& ch
}
}
-CTransactionRef PeerManagerImpl::FindTxForGetData(const CNode& peer, const GenTxid& gtxid, const std::chrono::seconds mempool_req, const std::chrono::seconds now) LOCKS_EXCLUDED(cs_main)
+CTransactionRef PeerManagerImpl::FindTxForGetData(const CNode& peer, const GenTxid& gtxid, const std::chrono::seconds mempool_req, const std::chrono::seconds now)
{
auto txinfo = m_mempool.info(gtxid);
if (txinfo.tx) {
@@ -1911,7 +1911,7 @@ CTransactionRef PeerManagerImpl::FindTxForGetData(const CNode& peer, const GenTx
return {};
}
-void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(!cs_main, peer.m_getdata_requests_mutex)
+void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc)
{
AssertLockNotHeld(cs_main);
@@ -2236,7 +2236,7 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
if (orphan_it == mapOrphanTransactions.end()) continue;
const CTransactionRef porphanTx = orphan_it->second.tx;
- const MempoolAcceptResult result = AcceptToMemoryPool(m_mempool, porphanTx, false /* bypass_limits */);
+ const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), m_mempool, porphanTx, false /* bypass_limits */);
const TxValidationState& state = result.m_state;
if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
@@ -3258,7 +3258,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
}
- const MempoolAcceptResult result = AcceptToMemoryPool(m_mempool, ptx, false /* bypass_limits */);
+ const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), m_mempool, ptx, false /* bypass_limits */);
const TxValidationState& state = result.m_state;
if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
@@ -4417,7 +4417,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
// Address refresh broadcast
auto current_time = GetTime<std::chrono::microseconds>();
- if (pto->RelayAddrsWithConn() && !::ChainstateActive().IsInitialBlockDownload() && pto->m_next_local_addr_send < current_time) {
+ if (fListen && pto->RelayAddrsWithConn() &&
+ !::ChainstateActive().IsInitialBlockDownload() &&
+ pto->m_next_local_addr_send < current_time) {
// If we've sent before, clear the bloom filter for the peer, so that our
// self-announcement will actually go out.
// This might be unnecessary if the bloom filter has already rolled
@@ -4427,7 +4429,10 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
if (pto->m_next_local_addr_send != 0us) {
pto->m_addr_known->reset();
}
- AdvertiseLocal(pto);
+ if (Optional<CAddress> local_addr = GetLocalAddrForPeer(pto)) {
+ FastRandomContext insecure_rand;
+ pto->PushAddress(*local_addr, insecure_rand);
+ }
pto->m_next_local_addr_send = PoissonNextSend(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
}
diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp
index b994e79391..06fcc33725 100644
--- a/src/node/coinstats.cpp
+++ b/src/node/coinstats.cpp
@@ -55,6 +55,18 @@ static void ApplyHash(CCoinsStats& stats, MuHash3072& muhash, const uint256& has
muhash.Insert(MakeUCharSpan(ss));
}
+//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
+//! validation commitments are reliant on the hash constructed by this
+//! function.
+//!
+//! If the construction of this hash is changed, it will invalidate
+//! existing UTXO snapshots. This will not result in any kind of consensus
+//! failure, but it will force clients that were expecting to make use of
+//! assumeutxo to do traditional IBD instead.
+//!
+//! It is also possible, though very unlikely, that a change in this
+//! construction could cause a previously invalid (and potentially malicious)
+//! UTXO snapshot to be considered valid.
template <typename T>
static void ApplyStats(CCoinsStats& stats, T& hash_obj, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
{
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index b7efd68cfd..ec976fe9bf 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -441,7 +441,7 @@ public:
bool checkFinalTx(const CTransaction& tx) override
{
LOCK(cs_main);
- return CheckFinalTx(tx);
+ return CheckFinalTx(::ChainActive().Tip(), tx);
}
Optional<int> findLocatorFork(const CBlockLocator& locator) override
{
diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp
index 6f5acf41e3..3b3fab7b6b 100644
--- a/src/node/transaction.cpp
+++ b/src/node/transaction.cpp
@@ -53,7 +53,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
if (max_tx_fee > 0) {
// First, call ATMP with test_accept and check the fee. If ATMP
// fails here, return error immediately.
- const MempoolAcceptResult result = AcceptToMemoryPool(*node.mempool, tx, false /* bypass_limits */,
+ const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *node.mempool, tx, false /* bypass_limits */,
true /* test_accept */);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string);
@@ -62,7 +62,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
}
}
// Try to submit the transaction to the mempool.
- const MempoolAcceptResult result = AcceptToMemoryPool(*node.mempool, tx, false /* bypass_limits */,
+ const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *node.mempool, tx, false /* bypass_limits */,
false /* test_accept */);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string);
diff --git a/src/protocol.cpp b/src/protocol.cpp
index 56e738eaa8..0b893b9272 100644
--- a/src/protocol.cpp
+++ b/src/protocol.cpp
@@ -5,7 +5,6 @@
#include <protocol.h>
-#include <util/strencodings.h>
#include <util/system.h>
static std::atomic<bool> g_initial_block_download_completed(false);
@@ -86,7 +85,7 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::CFCHECKPT,
NetMsgType::WTXIDRELAY,
};
-const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));
+const static std::vector<std::string> allNetMessageTypesVec(std::begin(allNetMessageTypes), std::end(allNetMessageTypes));
CMessageHeader::CMessageHeader()
{
diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui
index 3831852185..9e828ce0a6 100644
--- a/src/qt/forms/debugwindow.ui
+++ b/src/qt/forms/debugwindow.ui
@@ -988,7 +988,7 @@
</item>
</layout>
</widget>
- <widget class="QWidget" name="widget_2" native="true">
+ <widget class="QWidget" name="peersTabRightPanel" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -1079,10 +1079,10 @@
<item row="1" column="0">
<widget class="QLabel" name="peerConnectionTypeLabel">
<property name="toolTip">
- <string>The type of peer connection: %1</string>
+ <string>The direction and type of peer connection: %1</string>
</property>
<property name="text">
- <string>Connection Type</string>
+ <string>Direction/Type</string>
</property>
</widget>
</item>
@@ -1198,13 +1198,65 @@
</widget>
</item>
<item row="6" column="0">
+ <widget class="QLabel" name="peerRelayTxesLabel">
+ <property name="toolTip">
+ <string>Whether the peer requested us to relay transactions.</string>
+ </property>
+ <property name="text">
+ <string>Wants Tx Relay</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QLabel" name="peerRelayTxes">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>N/A</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QLabel" name="peerHighBandwidthLabel">
+ <property name="toolTip">
+ <string>High bandwidth BIP152 compact block relay: %1</string>
+ </property>
+ <property name="text">
+ <string>High Bandwidth</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QLabel" name="peerHighBandwidth">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>N/A</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
<widget class="QLabel" name="label_29">
<property name="text">
<string>Starting Block</string>
</property>
</widget>
</item>
- <item row="6" column="1">
+ <item row="8" column="1">
<widget class="QLabel" name="peerHeight">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1220,14 +1272,14 @@
</property>
</widget>
</item>
- <item row="7" column="0">
+ <item row="9" column="0">
<widget class="QLabel" name="label_27">
<property name="text">
<string>Synced Headers</string>
</property>
</widget>
</item>
- <item row="7" column="1">
+ <item row="9" column="1">
<widget class="QLabel" name="peerSyncHeight">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1243,14 +1295,14 @@
</property>
</widget>
</item>
- <item row="8" column="0">
+ <item row="10" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Synced Blocks</string>
</property>
</widget>
</item>
- <item row="8" column="1">
+ <item row="10" column="1">
<widget class="QLabel" name="peerCommonHeight">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1266,14 +1318,14 @@
</property>
</widget>
</item>
- <item row="9" column="0">
+ <item row="11" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Connection Time</string>
</property>
</widget>
</item>
- <item row="9" column="1">
+ <item row="11" column="1">
<widget class="QLabel" name="peerConnTime">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1289,14 +1341,14 @@
</property>
</widget>
</item>
- <item row="10" column="0">
+ <item row="12" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Last Send</string>
</property>
</widget>
</item>
- <item row="10" column="1">
+ <item row="12" column="1">
<widget class="QLabel" name="peerLastSend">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1312,14 +1364,14 @@
</property>
</widget>
</item>
- <item row="11" column="0">
+ <item row="13" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Last Receive</string>
</property>
</widget>
</item>
- <item row="11" column="1">
+ <item row="13" column="1">
<widget class="QLabel" name="peerLastRecv">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1335,14 +1387,14 @@
</property>
</widget>
</item>
- <item row="12" column="0">
+ <item row="14" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Sent</string>
</property>
</widget>
</item>
- <item row="12" column="1">
+ <item row="14" column="1">
<widget class="QLabel" name="peerBytesSent">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1358,14 +1410,14 @@
</property>
</widget>
</item>
- <item row="13" column="0">
+ <item row="15" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Received</string>
</property>
</widget>
</item>
- <item row="13" column="1">
+ <item row="15" column="1">
<widget class="QLabel" name="peerBytesRecv">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1381,14 +1433,14 @@
</property>
</widget>
</item>
- <item row="14" column="0">
+ <item row="16" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Ping Time</string>
</property>
</widget>
</item>
- <item row="14" column="1">
+ <item row="16" column="1">
<widget class="QLabel" name="peerPingTime">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1404,7 +1456,7 @@
</property>
</widget>
</item>
- <item row="15" column="0">
+ <item row="17" column="0">
<widget class="QLabel" name="peerPingWaitLabel">
<property name="toolTip">
<string>The duration of a currently outstanding ping.</string>
@@ -1414,7 +1466,7 @@
</property>
</widget>
</item>
- <item row="15" column="1">
+ <item row="17" column="1">
<widget class="QLabel" name="peerPingWait">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1430,14 +1482,14 @@
</property>
</widget>
</item>
- <item row="16" column="0">
+ <item row="18" column="0">
<widget class="QLabel" name="peerMinPingLabel">
<property name="text">
<string>Min Ping</string>
</property>
</widget>
</item>
- <item row="16" column="1">
+ <item row="18" column="1">
<widget class="QLabel" name="peerMinPing">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1453,14 +1505,14 @@
</property>
</widget>
</item>
- <item row="17" column="0">
+ <item row="19" column="0">
<widget class="QLabel" name="label_timeoffset">
<property name="text">
<string>Time Offset</string>
</property>
</widget>
</item>
- <item row="17" column="1">
+ <item row="19" column="1">
<widget class="QLabel" name="timeoffset">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1476,7 +1528,7 @@
</property>
</widget>
</item>
- <item row="18" column="0">
+ <item row="20" column="0">
<widget class="QLabel" name="peerMappedASLabel">
<property name="toolTip">
<string>The mapped Autonomous System used for diversifying peer selection.</string>
@@ -1486,7 +1538,7 @@
</property>
</widget>
</item>
- <item row="18" column="1">
+ <item row="20" column="1">
<widget class="QLabel" name="peerMappedAS">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1502,7 +1554,7 @@
</property>
</widget>
</item>
- <item row="19" column="0">
+ <item row="21" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 0c2bbe9516..d72ed0c3ff 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -470,120 +470,6 @@ bool LabelOutOfFocusEventFilter::eventFilter(QObject* watched, QEvent* event)
return QObject::eventFilter(watched, event);
}
-void TableViewLastColumnResizingFixer::connectViewHeadersSignals()
-{
- connect(tableView->horizontalHeader(), &QHeaderView::sectionResized, this, &TableViewLastColumnResizingFixer::on_sectionResized);
- connect(tableView->horizontalHeader(), &QHeaderView::geometriesChanged, this, &TableViewLastColumnResizingFixer::on_geometriesChanged);
-}
-
-// We need to disconnect these while handling the resize events, otherwise we can enter infinite loops.
-void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals()
-{
- disconnect(tableView->horizontalHeader(), &QHeaderView::sectionResized, this, &TableViewLastColumnResizingFixer::on_sectionResized);
- disconnect(tableView->horizontalHeader(), &QHeaderView::geometriesChanged, this, &TableViewLastColumnResizingFixer::on_geometriesChanged);
-}
-
-// Setup the resize mode, handles compatibility for Qt5 and below as the method signatures changed.
-// Refactored here for readability.
-void TableViewLastColumnResizingFixer::setViewHeaderResizeMode(int logicalIndex, QHeaderView::ResizeMode resizeMode)
-{
- tableView->horizontalHeader()->setSectionResizeMode(logicalIndex, resizeMode);
-}
-
-void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex, int width)
-{
- tableView->setColumnWidth(nColumnIndex, width);
- tableView->horizontalHeader()->resizeSection(nColumnIndex, width);
-}
-
-int TableViewLastColumnResizingFixer::getColumnsWidth()
-{
- int nColumnsWidthSum = 0;
- for (int i = 0; i < columnCount; i++)
- {
- nColumnsWidthSum += tableView->horizontalHeader()->sectionSize(i);
- }
- return nColumnsWidthSum;
-}
-
-int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column)
-{
- int nResult = lastColumnMinimumWidth;
- int nTableWidth = tableView->horizontalHeader()->width();
-
- if (nTableWidth > 0)
- {
- int nOtherColsWidth = getColumnsWidth() - tableView->horizontalHeader()->sectionSize(column);
- nResult = std::max(nResult, nTableWidth - nOtherColsWidth);
- }
-
- return nResult;
-}
-
-// Make sure we don't make the columns wider than the table's viewport width.
-void TableViewLastColumnResizingFixer::adjustTableColumnsWidth()
-{
- disconnectViewHeadersSignals();
- resizeColumn(lastColumnIndex, getAvailableWidthForColumn(lastColumnIndex));
- connectViewHeadersSignals();
-
- int nTableWidth = tableView->horizontalHeader()->width();
- int nColsWidth = getColumnsWidth();
- if (nColsWidth > nTableWidth)
- {
- resizeColumn(secondToLastColumnIndex,getAvailableWidthForColumn(secondToLastColumnIndex));
- }
-}
-
-// Make column use all the space available, useful during window resizing.
-void TableViewLastColumnResizingFixer::stretchColumnWidth(int column)
-{
- disconnectViewHeadersSignals();
- resizeColumn(column, getAvailableWidthForColumn(column));
- connectViewHeadersSignals();
-}
-
-// When a section is resized this is a slot-proxy for ajustAmountColumnWidth().
-void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex, int oldSize, int newSize)
-{
- adjustTableColumnsWidth();
- int remainingWidth = getAvailableWidthForColumn(logicalIndex);
- if (newSize > remainingWidth)
- {
- resizeColumn(logicalIndex, remainingWidth);
- }
-}
-
-// When the table's geometry is ready, we manually perform the stretch of the "Message" column,
-// as the "Stretch" resize mode does not allow for interactive resizing.
-void TableViewLastColumnResizingFixer::on_geometriesChanged()
-{
- if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) != 0)
- {
- disconnectViewHeadersSignals();
- resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex));
- connectViewHeadersSignals();
- }
-}
-
-/**
- * Initializes all internal variables and prepares the
- * the resize modes of the last 2 columns of the table and
- */
-TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer(QTableView* table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent) :
- QObject(parent),
- tableView(table),
- lastColumnMinimumWidth(lastColMinimumWidth),
- allColumnsMinimumWidth(allColsMinimumWidth)
-{
- columnCount = tableView->horizontalHeader()->count();
- lastColumnIndex = columnCount - 1;
- secondToLastColumnIndex = columnCount - 2;
- tableView->horizontalHeader()->setMinimumSectionSize(allColumnsMinimumWidth);
- setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive);
- setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive);
-}
-
#ifdef WIN32
fs::path static StartupShortcutPath()
{
@@ -766,15 +652,19 @@ QString NetworkToQString(Network net)
assert(false);
}
-QString ConnectionTypeToQString(ConnectionType conn_type)
+QString ConnectionTypeToQString(ConnectionType conn_type, bool prepend_direction)
{
+ QString prefix;
+ if (prepend_direction) {
+ prefix = (conn_type == ConnectionType::INBOUND) ? QObject::tr("Inbound") : QObject::tr("Outbound") + " ";
+ }
switch (conn_type) {
- case ConnectionType::INBOUND: return QObject::tr("Inbound");
- case ConnectionType::OUTBOUND_FULL_RELAY: return QObject::tr("Outbound Full Relay");
- case ConnectionType::BLOCK_RELAY: return QObject::tr("Outbound Block Relay");
- case ConnectionType::MANUAL: return QObject::tr("Outbound Manual");
- case ConnectionType::FEELER: return QObject::tr("Outbound Feeler");
- case ConnectionType::ADDR_FETCH: return QObject::tr("Outbound Address Fetch");
+ case ConnectionType::INBOUND: return prefix;
+ case ConnectionType::OUTBOUND_FULL_RELAY: return prefix + QObject::tr("Full Relay");
+ case ConnectionType::BLOCK_RELAY: return prefix + QObject::tr("Block Relay");
+ case ConnectionType::MANUAL: return prefix + QObject::tr("Manual");
+ case ConnectionType::FEELER: return prefix + QObject::tr("Feeler");
+ case ConnectionType::ADDR_FETCH: return prefix + QObject::tr("Address Fetch");
} // no default case, so the compiler can warn about missing cases
assert(false);
}
diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h
index 9fb108dff9..3134c98429 100644
--- a/src/qt/guiutil.h
+++ b/src/qt/guiutil.h
@@ -181,45 +181,6 @@ namespace GUIUtil
bool eventFilter(QObject* watched, QEvent* event) override;
};
- /**
- * Makes a QTableView last column feel as if it was being resized from its left border.
- * Also makes sure the column widths are never larger than the table's viewport.
- * In Qt, all columns are resizable from the right, but it's not intuitive resizing the last column from the right.
- * Usually our second to last columns behave as if stretched, and when on stretch mode, columns aren't resizable
- * interactively or programmatically.
- *
- * This helper object takes care of this issue.
- *
- */
- class TableViewLastColumnResizingFixer: public QObject
- {
- Q_OBJECT
-
- public:
- TableViewLastColumnResizingFixer(QTableView* table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent);
- void stretchColumnWidth(int column);
-
- private:
- QTableView* tableView;
- int lastColumnMinimumWidth;
- int allColumnsMinimumWidth;
- int lastColumnIndex;
- int columnCount;
- int secondToLastColumnIndex;
-
- void adjustTableColumnsWidth();
- int getAvailableWidthForColumn(int column);
- int getColumnsWidth();
- void connectViewHeadersSignals();
- void disconnectViewHeadersSignals();
- void setViewHeaderResizeMode(int logicalIndex, QHeaderView::ResizeMode resizeMode);
- void resizeColumn(int nColumnIndex, int width);
-
- private Q_SLOTS:
- void on_sectionResized(int logicalIndex, int oldSize, int newSize);
- void on_geometriesChanged();
- };
-
bool GetStartOnSystemStartup();
bool SetStartOnSystemStartup(bool fAutoStart);
@@ -233,7 +194,7 @@ namespace GUIUtil
QString NetworkToQString(Network net);
/** Convert enum ConnectionType to QString */
- QString ConnectionTypeToQString(ConnectionType conn_type);
+ QString ConnectionTypeToQString(ConnectionType conn_type, bool prepend_direction);
/** Convert seconds into a QString with days, hours, mins, secs */
QString formatDurationStr(int secs);
diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp
index b1081f6aee..ee70c1bc30 100644
--- a/src/qt/networkstyle.cpp
+++ b/src/qt/networkstyle.cpp
@@ -22,7 +22,6 @@ static const struct {
{"signet", QAPP_APP_NAME_SIGNET, 35, 15},
{"regtest", QAPP_APP_NAME_REGTEST, 160, 30},
};
-static const unsigned network_styles_count = sizeof(network_styles)/sizeof(*network_styles);
// titleAddText needs to be const char* for tr()
NetworkStyle::NetworkStyle(const QString &_appName, const int iconColorHueShift, const int iconColorSaturationReduction, const char *_titleAddText):
@@ -81,14 +80,12 @@ NetworkStyle::NetworkStyle(const QString &_appName, const int iconColorHueShift,
const NetworkStyle* NetworkStyle::instantiate(const std::string& networkId)
{
std::string titleAddText = networkId == CBaseChainParams::MAIN ? "" : strprintf("[%s]", networkId);
- for (unsigned x=0; x<network_styles_count; ++x)
- {
- if (networkId == network_styles[x].networkId)
- {
+ for (const auto& network_style : network_styles) {
+ if (networkId == network_style.networkId) {
return new NetworkStyle(
- network_styles[x].appName,
- network_styles[x].iconColorHueShift,
- network_styles[x].iconColorSaturationReduction,
+ network_style.appName,
+ network_style.iconColorHueShift,
+ network_style.iconColorSaturationReduction,
titleAddText.c_str());
}
}
diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp
index bad81d894c..5f518a67cd 100644
--- a/src/qt/peertablemodel.cpp
+++ b/src/qt/peertablemodel.cpp
@@ -29,6 +29,8 @@ bool NodeLessThan::operator()(const CNodeCombinedStats &left, const CNodeCombine
return pLeft->nodeid < pRight->nodeid;
case PeerTableModel::Address:
return pLeft->addrName.compare(pRight->addrName) < 0;
+ case PeerTableModel::ConnectionType:
+ return pLeft->m_conn_type < pRight->m_conn_type;
case PeerTableModel::Network:
return pLeft->m_network < pRight->m_network;
case PeerTableModel::Ping:
@@ -163,6 +165,8 @@ QVariant PeerTableModel::data(const QModelIndex &index, int role) const
case Address:
// prepend to peer address down-arrow symbol for inbound connection and up-arrow for outbound connection
return QString(rec->nodeStats.fInbound ? "↓ " : "↑ ") + QString::fromStdString(rec->nodeStats.addrName);
+ case ConnectionType:
+ return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /* prepend_direction */ false);
case Network:
return GUIUtil::NetworkToQString(rec->nodeStats.m_network);
case Ping:
@@ -176,6 +180,7 @@ QVariant PeerTableModel::data(const QModelIndex &index, int role) const
}
} else if (role == Qt::TextAlignmentRole) {
switch (index.column()) {
+ case ConnectionType:
case Network:
return QVariant(Qt::AlignCenter);
case Ping:
diff --git a/src/qt/peertablemodel.h b/src/qt/peertablemodel.h
index 7bff239507..0823235ec0 100644
--- a/src/qt/peertablemodel.h
+++ b/src/qt/peertablemodel.h
@@ -59,12 +59,13 @@ public:
enum ColumnIndex {
NetNodeId = 0,
- Address = 1,
- Network = 2,
- Ping = 3,
- Sent = 4,
- Received = 5,
- Subversion = 6
+ Address,
+ ConnectionType,
+ Network,
+ Ping,
+ Sent,
+ Received,
+ Subversion
};
enum {
@@ -87,7 +88,7 @@ public Q_SLOTS:
private:
interfaces::Node& m_node;
- const QStringList columns{tr("Peer Id"), tr("Address"), tr("Network"), tr("Ping"), tr("Sent"), tr("Received"), tr("User Agent")};
+ const QStringList columns{tr("Peer Id"), tr("Address"), tr("Type"), tr("Network"), tr("Ping"), tr("Sent"), tr("Received"), tr("User Agent")};
std::unique_ptr<PeerTablePriv> priv;
QTimer *timer;
};
diff --git a/src/qt/platformstyle.cpp b/src/qt/platformstyle.cpp
index c6b80fd340..aab8d8e4af 100644
--- a/src/qt/platformstyle.cpp
+++ b/src/qt/platformstyle.cpp
@@ -23,7 +23,6 @@ static const struct {
/* Other: linux, unix, ... */
{"other", true, true, false}
};
-static const unsigned platform_styles_count = sizeof(platform_styles)/sizeof(*platform_styles);
namespace {
/* Local functions for colorizing single-color images */
@@ -121,15 +120,13 @@ QIcon PlatformStyle::TextColorIcon(const QIcon& icon) const
const PlatformStyle *PlatformStyle::instantiate(const QString &platformId)
{
- for (unsigned x=0; x<platform_styles_count; ++x)
- {
- if (platformId == platform_styles[x].platformId)
- {
+ for (const auto& platform_style : platform_styles) {
+ if (platformId == platform_style.platformId) {
return new PlatformStyle(
- platform_styles[x].platformId,
- platform_styles[x].imagesOnButtons,
- platform_styles[x].colorizeIcons,
- platform_styles[x].useExtraSpacing);
+ platform_style.platformId,
+ platform_style.imagesOnButtons,
+ platform_style.colorizeIcons,
+ platform_style.useExtraSpacing);
}
}
return nullptr;
diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp
index 0aea920c86..49725a0d33 100644
--- a/src/qt/receivecoinsdialog.cpp
+++ b/src/qt/receivecoinsdialog.cpp
@@ -19,12 +19,12 @@
#include <QCursor>
#include <QMessageBox>
#include <QScrollBar>
+#include <QSettings>
#include <QTextDocument>
ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::ReceiveCoinsDialog),
- columnResizingFixer(nullptr),
model(nullptr),
platformStyle(_platformStyle)
{
@@ -63,6 +63,22 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid
connect(copyAmountAction, &QAction::triggered, this, &ReceiveCoinsDialog::copyAmount);
connect(ui->clearButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::clear);
+
+ QTableView* tableView = ui->recentRequestsView;
+ tableView->verticalHeader()->hide();
+ tableView->setAlternatingRowColors(true);
+ tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ tableView->setSelectionMode(QAbstractItemView::ContiguousSelection);
+
+ QSettings settings;
+ if (!tableView->horizontalHeader()->restoreState(settings.value("RecentRequestsViewHeaderState").toByteArray())) {
+ tableView->setColumnWidth(RecentRequestsTableModel::Date, DATE_COLUMN_WIDTH);
+ tableView->setColumnWidth(RecentRequestsTableModel::Label, LABEL_COLUMN_WIDTH);
+ tableView->setColumnWidth(RecentRequestsTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
+ tableView->horizontalHeader()->setMinimumSectionSize(MINIMUM_COLUMN_WIDTH);
+ tableView->horizontalHeader()->setStretchLastSection(true);
+ }
+ tableView->horizontalHeader()->setSortIndicator(RecentRequestsTableModel::Date, Qt::DescendingOrder);
}
void ReceiveCoinsDialog::setModel(WalletModel *_model)
@@ -76,22 +92,10 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model)
updateDisplayUnit();
QTableView* tableView = ui->recentRequestsView;
-
- tableView->verticalHeader()->hide();
- tableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
tableView->setModel(_model->getRecentRequestsTableModel());
- tableView->setAlternatingRowColors(true);
- tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
- tableView->setSelectionMode(QAbstractItemView::ContiguousSelection);
- tableView->setColumnWidth(RecentRequestsTableModel::Date, DATE_COLUMN_WIDTH);
- tableView->setColumnWidth(RecentRequestsTableModel::Label, LABEL_COLUMN_WIDTH);
- tableView->setColumnWidth(RecentRequestsTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
-
connect(tableView->selectionModel(),
&QItemSelectionModel::selectionChanged, this,
&ReceiveCoinsDialog::recentRequestsView_selectionChanged);
- // Last 2 columns are set by the columnResizingFixer, when the table geometry is ready.
- columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this);
if (model->wallet().getDefaultAddressType() == OutputType::BECH32) {
ui->useBech32->setCheckState(Qt::Checked);
@@ -111,6 +115,8 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model)
ReceiveCoinsDialog::~ReceiveCoinsDialog()
{
+ QSettings settings;
+ settings.setValue("RecentRequestsViewHeaderState", ui->recentRequestsView->horizontalHeader()->saveState());
delete ui;
}
@@ -235,14 +241,6 @@ void ReceiveCoinsDialog::on_removeRequestButton_clicked()
model->getRecentRequestsTableModel()->removeRows(firstIndex.row(), selection.length(), firstIndex.parent());
}
-// We override the virtual resizeEvent of the QWidget to adjust tables column
-// sizes as the tables width is proportional to the dialogs width.
-void ReceiveCoinsDialog::resizeEvent(QResizeEvent *event)
-{
- QWidget::resizeEvent(event);
- columnResizingFixer->stretchColumnWidth(RecentRequestsTableModel::Message);
-}
-
QModelIndex ReceiveCoinsDialog::selectedRow()
{
if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
diff --git a/src/qt/receivecoinsdialog.h b/src/qt/receivecoinsdialog.h
index 9b89bd6a8b..1ef84640f2 100644
--- a/src/qt/receivecoinsdialog.h
+++ b/src/qt/receivecoinsdialog.h
@@ -51,14 +51,12 @@ public Q_SLOTS:
private:
Ui::ReceiveCoinsDialog *ui;
- GUIUtil::TableViewLastColumnResizingFixer *columnResizingFixer;
WalletModel *model;
QMenu *contextMenu;
const PlatformStyle *platformStyle;
QModelIndex selectedRow();
void copyColumnToClipboard(int column);
- virtual void resizeEvent(QResizeEvent *event) override;
private Q_SLOTS:
void on_receiveButton_clicked();
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index ea12ce1583..4a4b557acc 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -473,6 +473,11 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty
tr("Outbound Address Fetch: short-lived, for soliciting addresses")};
const QString list{"<ul><li>" + Join(CONNECTION_TYPE_DOC, QString("</li><li>")) + "</li></ul>"};
ui->peerConnectionTypeLabel->setToolTip(ui->peerConnectionTypeLabel->toolTip().arg(list));
+ const QString hb_list{"<ul><li>\""
+ + tr("To") + "\" – " + tr("we selected the peer for high bandwidth relay") + "</li><li>\""
+ + tr("From") + "\" – " + tr("the peer selected us for high bandwidth relay") + "</li><li>\""
+ + tr("No") + "\" – " + tr("no high bandwidth relay selected") + "</li></ul>"};
+ ui->peerHighBandwidthLabel->setToolTip(ui->peerHighBandwidthLabel->toolTip().arg(hb_list));
ui->dataDir->setToolTip(ui->dataDir->toolTip().arg(QString(nonbreaking_hyphen) + "datadir"));
ui->blocksDir->setToolTip(ui->blocksDir->toolTip().arg(QString(nonbreaking_hyphen) + "blocksdir"));
ui->openDebugLogfileButton->setToolTip(ui->openDebugLogfileButton->toolTip().arg(PACKAGE_NAME));
@@ -1097,7 +1102,7 @@ void RPCConsole::updateDetailWidget()
{
const QList<QModelIndex> selected_peers = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
if (!clientModel || !clientModel->getPeerTableModel() || selected_peers.size() != 1) {
- ui->detailWidget->hide();
+ ui->peersTabRightPanel->hide();
ui->peerHeading->setText(tr("Select a peer to view detailed information."));
return;
}
@@ -1109,6 +1114,12 @@ void RPCConsole::updateDetailWidget()
peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal));
ui->peerHeading->setText(peerAddrDetails);
ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStats.nServices));
+ ui->peerRelayTxes->setText(stats->nodeStats.fRelayTxes ? "Yes" : "No");
+ QString bip152_hb_settings;
+ if (stats->nodeStats.m_bip152_highbandwidth_to) bip152_hb_settings += "To";
+ if (stats->nodeStats.m_bip152_highbandwidth_from) bip152_hb_settings += (bip152_hb_settings == "" ? "From" : "/From");
+ if (bip152_hb_settings == "") bip152_hb_settings = "No";
+ ui->peerHighBandwidth->setText(bip152_hb_settings);
ui->peerLastSend->setText(stats->nodeStats.nLastSend ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastSend) : tr("never"));
ui->peerLastRecv->setText(stats->nodeStats.nLastRecv ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastRecv) : tr("never"));
ui->peerBytesSent->setText(GUIUtil::formatBytes(stats->nodeStats.nSendBytes));
@@ -1119,7 +1130,7 @@ void RPCConsole::updateDetailWidget()
ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset));
ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion));
ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer));
- ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type));
+ ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, /* prepend_direction */ true));
ui->peerNetwork->setText(GUIUtil::NetworkToQString(stats->nodeStats.m_network));
if (stats->nodeStats.m_permissionFlags == PF_NONE) {
ui->peerPermissions->setText(tr("N/A"));
@@ -1151,7 +1162,7 @@ void RPCConsole::updateDetailWidget()
ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStateStats.m_ping_wait_usec));
}
- ui->detailWidget->show();
+ ui->peersTabRightPanel->show();
}
void RPCConsole::resizeEvent(QResizeEvent *event)
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index 0cf6480b82..b568f41158 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -31,6 +31,7 @@
#include <QMenu>
#include <QPoint>
#include <QScrollBar>
+#include <QSettings>
#include <QTableView>
#include <QTimer>
#include <QUrl>
@@ -126,27 +127,40 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
vlayout->setContentsMargins(0,0,0,0);
vlayout->setSpacing(0);
- QTableView *view = new QTableView(this);
+ transactionView = new QTableView(this);
+ transactionView->setObjectName("transactionView");
vlayout->addLayout(hlayout);
vlayout->addWidget(createDateRangeWidget());
- vlayout->addWidget(view);
+ vlayout->addWidget(transactionView);
vlayout->setSpacing(0);
- int width = view->verticalScrollBar()->sizeHint().width();
+ int width = transactionView->verticalScrollBar()->sizeHint().width();
// Cover scroll bar width with spacing
if (platformStyle->getUseExtraSpacing()) {
hlayout->addSpacing(width+2);
} else {
hlayout->addSpacing(width);
}
- // Always show scroll bar
- view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
- view->setTabKeyNavigation(false);
- view->setContextMenuPolicy(Qt::CustomContextMenu);
-
- view->installEventFilter(this);
-
- transactionView = view;
- transactionView->setObjectName("transactionView");
+ transactionView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ transactionView->setTabKeyNavigation(false);
+ transactionView->setContextMenuPolicy(Qt::CustomContextMenu);
+ transactionView->installEventFilter(this);
+ transactionView->setAlternatingRowColors(true);
+ transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ transactionView->setSortingEnabled(true);
+ transactionView->verticalHeader()->hide();
+
+ QSettings settings;
+ if (!transactionView->horizontalHeader()->restoreState(settings.value("TransactionViewHeaderState").toByteArray())) {
+ transactionView->setColumnWidth(TransactionTableModel::Status, STATUS_COLUMN_WIDTH);
+ transactionView->setColumnWidth(TransactionTableModel::Watchonly, WATCHONLY_COLUMN_WIDTH);
+ transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH);
+ transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH);
+ transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
+ transactionView->horizontalHeader()->setMinimumSectionSize(MINIMUM_COLUMN_WIDTH);
+ transactionView->horizontalHeader()->setStretchLastSection(true);
+ }
+ transactionView->horizontalHeader()->setSortIndicator(TransactionTableModel::Date, Qt::DescendingOrder);
// Actions
abandonAction = new QAction(tr("Abandon transaction"), this);
@@ -158,7 +172,6 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this);
QAction *copyTxHexAction = new QAction(tr("Copy raw transaction"), this);
QAction *copyTxPlainText = new QAction(tr("Copy full transaction details"), this);
- QAction *editLabelAction = new QAction(tr("Edit label"), this);
QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);
contextMenu = new QMenu(this);
@@ -173,7 +186,6 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
contextMenu->addSeparator();
contextMenu->addAction(bumpFeeAction);
contextMenu->addAction(abandonAction);
- contextMenu->addAction(editLabelAction);
connect(dateWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseDate);
connect(typeWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseType);
@@ -183,8 +195,8 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
connect(search_widget, &QLineEdit::textChanged, prefix_typing_delay, static_cast<void (QTimer::*)()>(&QTimer::start));
connect(prefix_typing_delay, &QTimer::timeout, this, &TransactionView::changedSearch);
- connect(view, &QTableView::doubleClicked, this, &TransactionView::doubleClicked);
- connect(view, &QTableView::customContextMenuRequested, this, &TransactionView::contextualMenu);
+ connect(transactionView, &QTableView::doubleClicked, this, &TransactionView::doubleClicked);
+ connect(transactionView, &QTableView::customContextMenuRequested, this, &TransactionView::contextualMenu);
connect(bumpFeeAction, &QAction::triggered, this, &TransactionView::bumpFee);
connect(abandonAction, &QAction::triggered, this, &TransactionView::abandonTx);
@@ -194,7 +206,6 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
connect(copyTxIDAction, &QAction::triggered, this, &TransactionView::copyTxID);
connect(copyTxHexAction, &QAction::triggered, this, &TransactionView::copyTxHex);
connect(copyTxPlainText, &QAction::triggered, this, &TransactionView::copyTxPlainText);
- connect(editLabelAction, &QAction::triggered, this, &TransactionView::editLabel);
connect(showDetailsAction, &QAction::triggered, this, &TransactionView::showDetails);
// Double-clicking on a transaction on the transaction history page shows details
connect(this, &TransactionView::doubleClicked, this, &TransactionView::showDetails);
@@ -204,6 +215,12 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
});
}
+TransactionView::~TransactionView()
+{
+ QSettings settings;
+ settings.setValue("TransactionViewHeaderState", transactionView->horizontalHeader()->saveState());
+}
+
void TransactionView::setModel(WalletModel *_model)
{
this->model = _model;
@@ -214,25 +231,8 @@ void TransactionView::setModel(WalletModel *_model)
transactionProxyModel->setDynamicSortFilter(true);
transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
-
transactionProxyModel->setSortRole(Qt::EditRole);
-
- transactionView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
transactionView->setModel(transactionProxyModel);
- transactionView->setAlternatingRowColors(true);
- transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
- transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
- transactionView->horizontalHeader()->setSortIndicator(TransactionTableModel::Date, Qt::DescendingOrder);
- transactionView->setSortingEnabled(true);
- transactionView->verticalHeader()->hide();
-
- transactionView->setColumnWidth(TransactionTableModel::Status, STATUS_COLUMN_WIDTH);
- transactionView->setColumnWidth(TransactionTableModel::Watchonly, WATCHONLY_COLUMN_WIDTH);
- transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH);
- transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH);
- transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
-
- columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(transactionView, AMOUNT_MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH, this);
if (_model->getOptionsModel())
{
@@ -474,52 +474,6 @@ void TransactionView::copyTxPlainText()
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole);
}
-void TransactionView::editLabel()
-{
- if(!transactionView->selectionModel() ||!model)
- return;
- QModelIndexList selection = transactionView->selectionModel()->selectedRows();
- if(!selection.isEmpty())
- {
- AddressTableModel *addressBook = model->getAddressTableModel();
- if(!addressBook)
- return;
- QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString();
- if(address.isEmpty())
- {
- // If this transaction has no associated address, exit
- return;
- }
- // Is address in address book? Address book can miss address when a transaction is
- // sent from outside the UI.
- int idx = addressBook->lookupAddress(address);
- if(idx != -1)
- {
- // Edit sending / receiving address
- QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex());
- // Determine type of address, launch appropriate editor dialog type
- QString type = modelIdx.data(AddressTableModel::TypeRole).toString();
-
- EditAddressDialog dlg(
- type == AddressTableModel::Receive
- ? EditAddressDialog::EditReceivingAddress
- : EditAddressDialog::EditSendingAddress, this);
- dlg.setModel(addressBook);
- dlg.loadRow(idx);
- dlg.exec();
- }
- else
- {
- // Add sending address
- EditAddressDialog dlg(EditAddressDialog::NewSendingAddress,
- this);
- dlg.setModel(addressBook);
- dlg.setAddress(address);
- dlg.exec();
- }
- }
-}
-
void TransactionView::showDetails()
{
if(!transactionView->selectionModel())
@@ -623,14 +577,6 @@ void TransactionView::focusTransaction(const uint256& txid)
}
}
-// We override the virtual resizeEvent of the QWidget to adjust tables column
-// sizes as the tables width is proportional to the dialogs width.
-void TransactionView::resizeEvent(QResizeEvent* event)
-{
- QWidget::resizeEvent(event);
- columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress);
-}
-
// Need to override default Ctrl+C action for amount as default behaviour is just to copy DisplayRole text
bool TransactionView::eventFilter(QObject *obj, QEvent *event)
{
diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h
index b268823066..cd40813461 100644
--- a/src/qt/transactionview.h
+++ b/src/qt/transactionview.h
@@ -35,6 +35,7 @@ class TransactionView : public QWidget
public:
explicit TransactionView(const PlatformStyle *platformStyle, QWidget *parent = nullptr);
+ ~TransactionView();
void setModel(WalletModel *model);
@@ -82,10 +83,6 @@ private:
QWidget *createDateRangeWidget();
- GUIUtil::TableViewLastColumnResizingFixer *columnResizingFixer{nullptr};
-
- virtual void resizeEvent(QResizeEvent* event) override;
-
bool eventFilter(QObject *obj, QEvent *event) override;
private Q_SLOTS:
@@ -93,7 +90,6 @@ private Q_SLOTS:
void dateRangeChanged();
void showDetails();
void copyAddress();
- void editLabel();
void copyLabel();
void copyAmount();
void copyTxID();
diff --git a/src/random.cpp b/src/random.cpp
index af9504e0ce..9900825abb 100644
--- a/src/random.cpp
+++ b/src/random.cpp
@@ -38,7 +38,6 @@
#include <sys/random.h>
#endif
#ifdef HAVE_SYSCTL_ARND
-#include <util/strencodings.h> // for ARRAYLEN
#include <sys/sysctl.h>
#endif
@@ -333,7 +332,7 @@ void GetOSRand(unsigned char *ent32)
int have = 0;
do {
size_t len = NUM_OS_RANDOM_BYTES - have;
- if (sysctl(name, ARRAYLEN(name), ent32 + have, &len, nullptr, 0) != 0) {
+ if (sysctl(name, std::size(name), ent32 + have, &len, nullptr, 0) != 0) {
RandFailure();
}
have += len;
diff --git a/src/rest.cpp b/src/rest.cpp
index f872f6e59d..71426a4dc4 100644
--- a/src/rest.cpp
+++ b/src/rest.cpp
@@ -19,7 +19,6 @@
#include <txmempool.h>
#include <util/check.h>
#include <util/ref.h>
-#include <util/strencodings.h>
#include <validation.h>
#include <version.h>
@@ -117,9 +116,10 @@ static RetFormat ParseDataFormat(std::string& param, const std::string& strReq)
param = strReq.substr(0, pos);
const std::string suff(strReq, pos + 1);
- for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++)
- if (suff == rf_names[i].name)
- return rf_names[i].rf;
+ for (const auto& rf_name : rf_names) {
+ if (suff == rf_name.name)
+ return rf_name.rf;
+ }
/* If no suffix is found, return original string. */
param = strReq;
@@ -129,12 +129,13 @@ static RetFormat ParseDataFormat(std::string& param, const std::string& strReq)
static std::string AvailableDataFormatsString()
{
std::string formats;
- for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++)
- if (strlen(rf_names[i].name) > 0) {
+ for (const auto& rf_name : rf_names) {
+ if (strlen(rf_name.name) > 0) {
formats.append(".");
- formats.append(rf_names[i].name);
+ formats.append(rf_name.name);
formats.append(", ");
}
+ }
if (formats.length() > 0)
return formats.substr(0, formats.length() - 2);
@@ -695,6 +696,7 @@ void InterruptREST()
void StopREST()
{
- for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++)
- UnregisterHTTPHandler(uri_prefixes[i].prefix, false);
+ for (const auto& up : uri_prefixes) {
+ UnregisterHTTPHandler(up.prefix, false);
+ }
}
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 5be02b1e4e..5dc33d7a98 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -2411,10 +2411,21 @@ static RPCHelpMan dumptxoutset()
FILE* file{fsbridge::fopen(temppath, "wb")};
CAutoFile afile{file, SER_DISK, CLIENT_VERSION};
+ NodeContext& node = EnsureNodeContext(request.context);
+ UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), afile);
+ fs::rename(temppath, path);
+
+ result.pushKV("path", path.string());
+ return result;
+},
+ };
+}
+
+UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile)
+{
std::unique_ptr<CCoinsViewCursor> pcursor;
CCoinsStats stats;
CBlockIndex* tip;
- NodeContext& node = EnsureNodeContext(request.context);
{
// We need to lock cs_main to ensure that the coinsdb isn't written to
@@ -2431,13 +2442,13 @@ static RPCHelpMan dumptxoutset()
//
LOCK(::cs_main);
- ::ChainstateActive().ForceFlushStateToDisk();
+ chainstate.ForceFlushStateToDisk();
- if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) {
+ if (!GetUTXOStats(&chainstate.CoinsDB(), stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
- pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor());
+ pcursor = std::unique_ptr<CCoinsViewCursor>(chainstate.CoinsDB().Cursor());
tip = g_chainman.m_blockman.LookupBlockIndex(stats.hashBlock);
CHECK_NONFATAL(tip);
}
@@ -2462,16 +2473,13 @@ static RPCHelpMan dumptxoutset()
}
afile.fclose();
- fs::rename(temppath, path);
UniValue result(UniValue::VOBJ);
result.pushKV("coins_written", stats.coins_count);
result.pushKV("base_hash", tip->GetBlockHash().ToString());
result.pushKV("base_height", tip->nHeight);
- result.pushKV("path", path.string());
+
return result;
-},
- };
}
void RegisterBlockchainRPCCommands(CRPCTable &t)
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index e4ce80400e..d8cae4dd24 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -6,6 +6,7 @@
#define BITCOIN_RPC_BLOCKCHAIN_H
#include <amount.h>
+#include <streams.h>
#include <sync.h>
#include <stdint.h>
@@ -16,6 +17,7 @@ extern RecursiveMutex cs_main;
class CBlock;
class CBlockIndex;
class CBlockPolicyEstimator;
+class CChainState;
class CTxMemPool;
class ChainstateManager;
class UniValue;
@@ -57,4 +59,10 @@ CTxMemPool& EnsureMemPool(const util::Ref& context);
ChainstateManager& EnsureChainman(const util::Ref& context);
CBlockPolicyEstimator& EnsureFeeEstimator(const util::Ref& context);
+/**
+ * Helper to create UTXO snapshots given a chainstate and a file handle.
+ * @return a UniValue map containing metadata about the snapshot.
+ */
+UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile);
+
#endif
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 784a53e060..47c776bbd1 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -946,7 +946,7 @@ static RPCHelpMan testmempoolaccept()
result_0.pushKV("txid", tx->GetHash().GetHex());
result_0.pushKV("wtxid", tx->GetWitnessHash().GetHex());
- const MempoolAcceptResult accept_result = WITH_LOCK(cs_main, return AcceptToMemoryPool(mempool, std::move(tx),
+ const MempoolAcceptResult accept_result = WITH_LOCK(cs_main, return AcceptToMemoryPool(::ChainstateActive(), mempool, std::move(tx),
false /* bypass_limits */, /* test_accept */ true));
// Only return the fee and vsize if the transaction would pass ATMP.
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 9e4b8a9dd6..6ab01882ac 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -179,6 +179,9 @@ public:
/** Get the descriptor string form including private data (if available in arg). */
virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0;
+ /** Get the descriptor string form with the xpub at the last hardened derivation */
+ virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const = 0;
+
/** Derive a private key, if private data is available in arg. */
virtual bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const = 0;
};
@@ -212,6 +215,21 @@ public:
ret = "[" + OriginString() + "]" + std::move(sub);
return true;
}
+ bool ToNormalizedString(const SigningProvider& arg, std::string& ret, bool priv) const override
+ {
+ std::string sub;
+ if (!m_provider->ToNormalizedString(arg, sub, priv)) return false;
+ // If m_provider is a BIP32PubkeyProvider, we may get a string formatted like a OriginPubkeyProvider
+ // In that case, we need to strip out the leading square bracket and fingerprint from the substring,
+ // and append that to our own origin string.
+ if (sub[0] == '[') {
+ sub = sub.substr(9);
+ ret = "[" + OriginString() + std::move(sub);
+ } else {
+ ret = "[" + OriginString() + "]" + std::move(sub);
+ }
+ return true;
+ }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
{
return m_provider->GetPrivKey(pos, arg, key);
@@ -243,6 +261,12 @@ public:
ret = EncodeSecret(key);
return true;
}
+ bool ToNormalizedString(const SigningProvider& arg, std::string& ret, bool priv) const override
+ {
+ if (priv) return ToPrivateString(arg, ret);
+ ret = ToString();
+ return true;
+ }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
{
return arg.GetKey(m_pubkey.GetID(), key);
@@ -386,6 +410,56 @@ public:
}
return true;
}
+ bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const override
+ {
+ // For hardened derivation type, just return the typical string, nothing to normalize
+ if (m_derive == DeriveType::HARDENED) {
+ if (priv) return ToPrivateString(arg, out);
+ out = ToString();
+ return true;
+ }
+ // Step backwards to find the last hardened step in the path
+ int i = (int)m_path.size() - 1;
+ for (; i >= 0; --i) {
+ if (m_path.at(i) >> 31) {
+ break;
+ }
+ }
+ // Either no derivation or all unhardened derivation
+ if (i == -1) {
+ if (priv) return ToPrivateString(arg, out);
+ out = ToString();
+ return true;
+ }
+ // Derive the xpub at the last hardened step
+ CExtKey xprv;
+ if (!GetExtKey(arg, xprv)) return false;
+ KeyOriginInfo origin;
+ int k = 0;
+ for (; k <= i; ++k) {
+ // Derive
+ xprv.Derive(xprv, m_path.at(k));
+ // Add to the path
+ origin.path.push_back(m_path.at(k));
+ // First derivation element, get the fingerprint for origin
+ if (k == 0) {
+ std::copy(xprv.vchFingerprint, xprv.vchFingerprint + 4, origin.fingerprint);
+ }
+ }
+ // Build the remaining path
+ KeyPath end_path;
+ for (; k < (int)m_path.size(); ++k) {
+ end_path.push_back(m_path.at(k));
+ }
+ // Build the string
+ std::string origin_str = HexStr(origin.fingerprint) + FormatHDKeypath(origin.path);
+ out = "[" + origin_str + "]" + (priv ? EncodeExtKey(xprv) : EncodeExtPubKey(xprv.Neuter())) + FormatHDKeypath(end_path);
+ if (IsRange()) {
+ out += "/*";
+ assert(m_derive == DeriveType::UNHARDENED);
+ }
+ return true;
+ }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
{
CExtKey extkey;
@@ -449,7 +523,7 @@ public:
return false;
}
- bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv) const
+ bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv, bool normalized) const
{
std::string extra = ToStringExtra();
size_t pos = extra.size() > 0 ? 1 : 0;
@@ -457,7 +531,9 @@ public:
for (const auto& pubkey : m_pubkey_args) {
if (pos++) ret += ",";
std::string tmp;
- if (priv) {
+ if (normalized) {
+ if (!pubkey->ToNormalizedString(*arg, tmp, priv)) return false;
+ } else if (priv) {
if (!pubkey->ToPrivateString(*arg, tmp)) return false;
} else {
tmp = pubkey->ToString();
@@ -467,7 +543,7 @@ public:
if (m_subdescriptor_arg) {
if (pos++) ret += ",";
std::string tmp;
- if (!m_subdescriptor_arg->ToStringHelper(arg, tmp, priv)) return false;
+ if (!m_subdescriptor_arg->ToStringHelper(arg, tmp, priv, normalized)) return false;
ret += std::move(tmp);
}
out = std::move(ret) + ")";
@@ -477,13 +553,20 @@ public:
std::string ToString() const final
{
std::string ret;
- ToStringHelper(nullptr, ret, false);
+ ToStringHelper(nullptr, ret, false, false);
return AddChecksum(ret);
}
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final
{
- bool ret = ToStringHelper(&arg, out, true);
+ bool ret = ToStringHelper(&arg, out, true, false);
+ out = AddChecksum(out);
+ return ret;
+ }
+
+ bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const override final
+ {
+ bool ret = ToStringHelper(&arg, out, priv, true);
out = AddChecksum(out);
return ret;
}
diff --git a/src/script/descriptor.h b/src/script/descriptor.h
index 17b43e7c81..46d51fa587 100644
--- a/src/script/descriptor.h
+++ b/src/script/descriptor.h
@@ -93,6 +93,9 @@ struct Descriptor {
/** Convert the descriptor to a private string. This fails if the provided provider does not have the relevant private keys. */
virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;
+ /** Convert the descriptor to a normalized string. Normalized descriptors have the xpub at the last hardened step. This fails if the provided provider does not have the private keys to derive that xpub. */
+ virtual bool ToNormalizedString(const SigningProvider& provider, std::string& out, bool priv) const = 0;
+
/** Expand a descriptor at a specified position.
*
* @param[in] pos The position at which to expand the descriptor. If IsRange() is false, this is ignored.
diff --git a/src/test/base32_tests.cpp b/src/test/base32_tests.cpp
index 3f7c5e99ee..3b44564ddb 100644
--- a/src/test/base32_tests.cpp
+++ b/src/test/base32_tests.cpp
@@ -17,7 +17,7 @@ BOOST_AUTO_TEST_CASE(base32_testvectors)
static const std::string vstrIn[] = {"","f","fo","foo","foob","fooba","foobar"};
static const std::string vstrOut[] = {"","my======","mzxq====","mzxw6===","mzxw6yq=","mzxw6ytb","mzxw6ytboi======"};
static const std::string vstrOutNoPadding[] = {"","my","mzxq","mzxw6","mzxw6yq","mzxw6ytb","mzxw6ytboi"};
- for (unsigned int i=0; i<sizeof(vstrIn)/sizeof(vstrIn[0]); i++)
+ for (unsigned int i=0; i<std::size(vstrIn); i++)
{
std::string strEnc = EncodeBase32(vstrIn[i]);
BOOST_CHECK_EQUAL(strEnc, vstrOut[i]);
diff --git a/src/test/base64_tests.cpp b/src/test/base64_tests.cpp
index bb8d102bd0..714fccffaa 100644
--- a/src/test/base64_tests.cpp
+++ b/src/test/base64_tests.cpp
@@ -16,7 +16,7 @@ BOOST_AUTO_TEST_CASE(base64_testvectors)
{
static const std::string vstrIn[] = {"","f","fo","foo","foob","fooba","foobar"};
static const std::string vstrOut[] = {"","Zg==","Zm8=","Zm9v","Zm9vYg==","Zm9vYmE=","Zm9vYmFy"};
- for (unsigned int i=0; i<sizeof(vstrIn)/sizeof(vstrIn[0]); i++)
+ for (unsigned int i=0; i<std::size(vstrIn); i++)
{
std::string strEnc = EncodeBase64(vstrIn[i]);
BOOST_CHECK_EQUAL(strEnc, vstrOut[i]);
diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index 20132d5782..acbd6a01ee 100644
--- a/src/test/descriptor_tests.cpp
+++ b/src/test/descriptor_tests.cpp
@@ -65,7 +65,7 @@ std::string UseHInsteadOfApostrophe(const std::string& desc)
const std::set<std::vector<uint32_t>> ONLY_EMPTY{{}};
-void DoCheck(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const Optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY,
+void DoCheck(const std::string& prv, const std::string& pub, const std::string& norm_prv, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const Optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY,
bool replace_apostrophe_with_h_in_prv=false, bool replace_apostrophe_with_h_in_pub=false)
{
FlatSigningProvider keys_priv, keys_pub;
@@ -112,6 +112,17 @@ void DoCheck(const std::string& prv, const std::string& pub, int flags, const st
BOOST_CHECK(EqualDescriptor(prv, prv1));
BOOST_CHECK(!parse_pub->ToPrivateString(keys_pub, prv1));
+ // Check that private can produce the normalized descriptors
+ std::string norm1;
+ BOOST_CHECK(parse_priv->ToNormalizedString(keys_priv, norm1, false));
+ BOOST_CHECK(EqualDescriptor(norm1, norm_pub));
+ BOOST_CHECK(parse_pub->ToNormalizedString(keys_priv, norm1, false));
+ BOOST_CHECK(EqualDescriptor(norm1, norm_pub));
+ BOOST_CHECK(parse_priv->ToNormalizedString(keys_priv, norm1, true));
+ BOOST_CHECK(EqualDescriptor(norm1, norm_prv));
+ BOOST_CHECK(parse_pub->ToNormalizedString(keys_priv, norm1, true));
+ BOOST_CHECK(EqualDescriptor(norm1, norm_prv));
+
// Check whether IsRange on both returns the expected result
BOOST_CHECK_EQUAL(parse_pub->IsRange(), (flags & RANGE) != 0);
BOOST_CHECK_EQUAL(parse_priv->IsRange(), (flags & RANGE) != 0);
@@ -251,29 +262,29 @@ void DoCheck(const std::string& prv, const std::string& pub, int flags, const st
BOOST_CHECK_MESSAGE(left_paths.empty(), "Not all expected key paths found: " + prv);
}
-void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const Optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY)
+void Check(const std::string& prv, const std::string& pub, const std::string& norm_prv, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const Optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY)
{
bool found_apostrophes_in_prv = false;
bool found_apostrophes_in_pub = false;
// Do not replace apostrophes with 'h' in prv and pub
- DoCheck(prv, pub, flags, scripts, type, paths);
+ DoCheck(prv, pub, norm_prv, norm_pub, flags, scripts, type, paths);
// Replace apostrophes with 'h' in prv but not in pub, if apostrophes are found in prv
if (prv.find('\'') != std::string::npos) {
found_apostrophes_in_prv = true;
- DoCheck(prv, pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false);
+ DoCheck(prv, pub, norm_prv, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false);
}
// Replace apostrophes with 'h' in pub but not in prv, if apostrophes are found in pub
if (pub.find('\'') != std::string::npos) {
found_apostrophes_in_pub = true;
- DoCheck(prv, pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true);
+ DoCheck(prv, pub, norm_prv, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true);
}
// Replace apostrophes with 'h' both in prv and in pub, if apostrophes are found in both
if (found_apostrophes_in_prv && found_apostrophes_in_pub) {
- DoCheck(prv, pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true);
+ DoCheck(prv, pub, norm_prv, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true);
}
}
@@ -284,50 +295,51 @@ BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(descriptor_test)
{
// Basic single-key compressed
- Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, nullopt);
- Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}, nullopt);
- Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, OutputType::LEGACY, {{1,0x80000002UL,3,0x80000004UL}});
- Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}, OutputType::BECH32);
- Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, OutputType::P2SH_SEGWIT);
+ Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, nullopt);
+ Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}, nullopt);
+ Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, OutputType::LEGACY, {{1,0x80000002UL,3,0x80000004UL}});
+ Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}, OutputType::BECH32);
+ Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, OutputType::P2SH_SEGWIT);
CheckUnparsable("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", "Pubkey '03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5' is invalid"); // Invalid pubkey
CheckUnparsable("pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Key origin start '[ character expected but not found, got 'd' instead"); // Missing start bracket in key origin
CheckUnparsable("pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Multiple ']' characters found for a single pubkey"); // Multiple end brackets in key origin
// Basic single-key uncompressed
- Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, nullopt);
- Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}, nullopt);
- Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, OutputType::LEGACY);
+ Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)",SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, nullopt);
+ Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}, nullopt);
+ Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, OutputType::LEGACY);
CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Uncompressed keys are not allowed"); // No uncompressed keys in witness
CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness
CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness
// Some unconventional single-key constructions
- Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}, OutputType::LEGACY);
- Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}}, OutputType::LEGACY);
- Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}}, OutputType::BECH32);
- Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}}, OutputType::BECH32);
- Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}}, OutputType::P2SH_SEGWIT);
- Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}, OutputType::P2SH_SEGWIT);
+ Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}, OutputType::LEGACY);
+ Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}}, OutputType::LEGACY);
+ Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}}, OutputType::BECH32);
+ Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}}, OutputType::BECH32);
+ Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}}, OutputType::P2SH_SEGWIT);
+ Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}, OutputType::P2SH_SEGWIT);
// Versions with BIP32 derivations
- Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}, nullopt);
- Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, nullopt, {{0}});
- Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{0xFFFFFFFFUL,0}});
- Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, OutputType::BECH32, {{0x8000000DUL, 1, 2, 0}, {0x8000000DUL, 1, 2, 1}, {0x8000000DUL, 1, 2, 2}});
- Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED | DERIVE_HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, OutputType::P2SH_SEGWIT, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}});
- Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, nullopt, {{0}, {1}});
+ Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}, nullopt);
+ Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", "pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, nullopt, {{0}});
+ Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", "pkh([bd16bee5/2147483647']xprv9vHkqa6XAPwKqSKSEJMcAB3yoCZhaSVsGZbSkFY5L3Lfjjk8sjZucbsbvEw5o3QrSA69nPfZDCgFnNnLhQ2ohpZuwummndnPasDw2Qr6dC2/0)", "pkh([bd16bee5/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{0xFFFFFFFFUL,0}});
+ Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", "wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, OutputType::BECH32, {{0x8000000DUL, 1, 2, 0}, {0x8000000DUL, 1, 2, 1}, {0x8000000DUL, 1, 2, 2}});
+ Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED | DERIVE_HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, OutputType::P2SH_SEGWIT, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}});
+ Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", "combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, nullopt, {{0}, {1}});
CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long key fingerprint
CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", "Key path value 2147483648 is out of range"); // BIP 32 path element overflow
CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", "Key path value '1aa' is not a valid uint32"); // Path is not valid uint
+ Check("pkh([01234567/10/20]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh([01234567/10/20]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", "pkh([01234567/10/20/2147483647']xprv9vHkqa6XAPwKqSKSEJMcAB3yoCZhaSVsGZbSkFY5L3Lfjjk8sjZucbsbvEw5o3QrSA69nPfZDCgFnNnLhQ2ohpZuwummndnPasDw2Qr6dC2/0)", "pkh([01234567/10/20/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{10, 20, 0xFFFFFFFFUL, 0}});
// Multisig constructions
- Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt);
- Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt);
- Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt);
- Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
- Check("sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", RANGE, {{"5221025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b62102fbd47cc8034098f0e6a94c6aeee8528abf0a2153a5d8e46d325b7284c046784652ae"}, {"52210264fd4d1f5dea8ded94c61e9641309349b62f27fbffe807291f664e286bfbe6472103f4ece6dfccfa37b211eb3d0af4d0c61dba9ef698622dc17eecdf764beeb005a652ae"}, {"5221022ccabda84c30bad578b13c89eb3b9544ce149787e5b538175b1d1ba259cbb83321024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c52ae"}}, nullopt, {{0}, {1}, {2}, {0, 0, 0}, {0, 0, 1}, {0, 0, 2}});
- Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE | DERIVE_HARDENED, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, OutputType::BECH32, {{0xFFFFFFFFUL,0}, {1,2,0}, {1,2,1}, {1,2,2}, {10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}});
- Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}, OutputType::P2SH_SEGWIT);
+ Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt);
+ Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt);
+ Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt);
+ Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
+ Check("sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", "sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", RANGE, {{"5221025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b62102fbd47cc8034098f0e6a94c6aeee8528abf0a2153a5d8e46d325b7284c046784652ae"}, {"52210264fd4d1f5dea8ded94c61e9641309349b62f27fbffe807291f664e286bfbe6472103f4ece6dfccfa37b211eb3d0af4d0c61dba9ef698622dc17eecdf764beeb005a652ae"}, {"5221022ccabda84c30bad578b13c89eb3b9544ce149787e5b538175b1d1ba259cbb83321024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c52ae"}}, nullopt, {{0}, {1}, {2}, {0, 0, 0}, {0, 0, 1}, {0, 0, 2}});
+ Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "wsh(multi(2,[bd16bee5/2147483647']xprv9vHkqa6XAPwKqSKSEJMcAB3yoCZhaSVsGZbSkFY5L3Lfjjk8sjZucbsbvEw5o3QrSA69nPfZDCgFnNnLhQ2ohpZuwummndnPasDw2Qr6dC2/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[bd16bee5/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE | DERIVE_HARDENED, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, OutputType::BECH32, {{0xFFFFFFFFUL,0}, {1,2,0}, {1,2,1}, {1,2,2}, {10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}});
+ Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", "sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}, OutputType::P2SH_SEGWIT);
CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))", "P2SH script is too large, 547 bytes is larger than 520 bytes"); // P2SH does not fit 16 compressed pubkeys in a redeemscript
CheckUnparsable("wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multiple ']' characters found for a single pubkey"); // Double key origin descriptor
CheckUnparsable("wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint 'aaagaaaa' is not hex"); // Non hex fingerprint
@@ -350,8 +362,8 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
CheckUnparsable("wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Cannot have wsh within wsh"); // Cannot embed P2WSH inside P2WSH
// Checksums
- Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
- Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
+ Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
+ Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#", "Expected 8 character checksum, not 0 characters"); // Empty checksum
CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfyq", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq", "Expected 8 character checksum, not 9 characters"); // Too long checksum
CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5", "Expected 8 character checksum, not 7 characters"); // Too short checksum
diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp
index 45151258b3..edb270d437 100644
--- a/src/test/fuzz/fuzz.cpp
+++ b/src/test/fuzz/fuzz.cpp
@@ -72,7 +72,7 @@ extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
}
#if defined(PROVIDE_MAIN_FUNCTION)
-__attribute__((weak)) int main(int argc, char** argv)
+int main(int argc, char** argv)
{
initialize();
static const auto& test_one_input = *Assert(g_test_one_input);
diff --git a/src/test/fuzz/string.cpp b/src/test/fuzz/string.cpp
index d6bbb17f63..ec8a3b23db 100644
--- a/src/test/fuzz/string.cpp
+++ b/src/test/fuzz/string.cpp
@@ -67,6 +67,7 @@ FUZZ_TARGET(string)
}
OutputType output_type;
(void)ParseOutputType(random_string_1, output_type);
+ (void)RemovePrefix(random_string_1, random_string_2);
(void)ResolveErrMsg(random_string_1, random_string_2);
try {
(void)RPCConvertNamedValues(random_string_1, random_string_vector);
diff --git a/src/test/hash_tests.cpp b/src/test/hash_tests.cpp
index 87f6470afa..41a626c0ea 100644
--- a/src/test/hash_tests.cpp
+++ b/src/test/hash_tests.cpp
@@ -107,14 +107,14 @@ BOOST_AUTO_TEST_CASE(siphash)
// Check test vectors from spec, one byte at a time
CSipHasher hasher2(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL);
- for (uint8_t x=0; x<ARRAYLEN(siphash_4_2_testvec); ++x)
+ for (uint8_t x=0; x<std::size(siphash_4_2_testvec); ++x)
{
BOOST_CHECK_EQUAL(hasher2.Finalize(), siphash_4_2_testvec[x]);
hasher2.Write(&x, 1);
}
// Check test vectors from spec, eight bytes at a time
CSipHasher hasher3(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL);
- for (uint8_t x=0; x<ARRAYLEN(siphash_4_2_testvec); x+=8)
+ for (uint8_t x=0; x<std::size(siphash_4_2_testvec); x+=8)
{
BOOST_CHECK_EQUAL(hasher3.Finalize(), siphash_4_2_testvec[x]);
hasher3.Write(uint64_t(x)|(uint64_t(x+1)<<8)|(uint64_t(x+2)<<16)|(uint64_t(x+3)<<24)|
diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp
index e967273636..aa628371e6 100644
--- a/src/test/miner_tests.cpp
+++ b/src/test/miner_tests.cpp
@@ -28,7 +28,7 @@ struct MinerTestingSetup : public TestingSetup {
void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
bool TestSequenceLocks(const CTransaction& tx, int flags) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs)
{
- return CheckSequenceLocks(*m_node.mempool, tx, flags);
+ return CheckSequenceLocks(::ChainstateActive(), *m_node.mempool, tx, flags);
}
BlockAssembler AssemblerForTest(const CChainParams& params);
};
@@ -123,6 +123,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
m_node.mempool->addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
+ BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 4U);
BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashParentTx);
BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashHighFeeTx);
BOOST_CHECK(pblocktemplate->block.vtx[3]->GetHash() == hashMediumFeeTx);
@@ -157,6 +158,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
hashLowFeeTx = tx.GetHash();
m_node.mempool->addUnchecked(entry.Fee(feeToUse+2).FromTx(tx));
pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
+ BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U);
BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashFreeTx);
BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashLowFeeTx);
@@ -191,6 +193,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee
m_node.mempool->addUnchecked(entry.Fee(10000).FromTx(tx));
pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
+ BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 9U);
BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2);
}
@@ -216,11 +219,10 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
// We can't make transactions until we have inputs
// Therefore, load 110 blocks :)
- static_assert(sizeof(blockinfo) / sizeof(*blockinfo) == 110, "Should have 110 blocks to import");
+ static_assert(std::size(blockinfo) == 110, "Should have 110 blocks to import");
int baseheight = 0;
std::vector<CTransactionRef> txFirst;
- for (unsigned int i = 0; i < sizeof(blockinfo)/sizeof(*blockinfo); ++i)
- {
+ for (const auto& bi : blockinfo) {
CBlock *pblock = &pblocktemplate->block; // pointer for convenience
{
LOCK(cs_main);
@@ -229,7 +231,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
CMutableTransaction txCoinbase(*pblock->vtx[0]);
txCoinbase.nVersion = 1;
txCoinbase.vin[0].scriptSig = CScript();
- txCoinbase.vin[0].scriptSig.push_back(blockinfo[i].extranonce);
+ txCoinbase.vin[0].scriptSig.push_back(bi.extranonce);
txCoinbase.vin[0].scriptSig.push_back(::ChainActive().Height());
txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this)
txCoinbase.vout[0].scriptPubKey = CScript();
@@ -239,7 +241,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
if (txFirst.size() < 4)
txFirst.push_back(pblock->vtx[0]);
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
- pblock->nNonce = blockinfo[i].nonce;
+ pblock->nNonce = bi.nonce;
}
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr));
@@ -435,7 +437,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.nLockTime = 0;
hash = tx.GetHash();
m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- BOOST_CHECK(CheckFinalTx(CTransaction(tx), flags)); // Locktime passes
+ BOOST_CHECK(CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime passes
BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail
BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(::ChainActive().Tip()->nHeight + 2))); // Sequence locks pass on 2nd block
@@ -445,7 +447,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
prevheights[0] = baseheight + 2;
hash = tx.GetHash();
m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx));
- BOOST_CHECK(CheckFinalTx(CTransaction(tx), flags)); // Locktime passes
+ BOOST_CHECK(CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime passes
BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail
for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++)
@@ -461,7 +463,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.nLockTime = ::ChainActive().Tip()->nHeight + 1;
hash = tx.GetHash();
m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx));
- BOOST_CHECK(!CheckFinalTx(CTransaction(tx), flags)); // Locktime fails
+ BOOST_CHECK(!CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime fails
BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass
BOOST_CHECK(IsFinalTx(CTransaction(tx), ::ChainActive().Tip()->nHeight + 2, ::ChainActive().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block
@@ -472,7 +474,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
prevheights[0] = baseheight + 4;
hash = tx.GetHash();
m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx));
- BOOST_CHECK(!CheckFinalTx(CTransaction(tx), flags)); // Locktime fails
+ BOOST_CHECK(!CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime fails
BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass
BOOST_CHECK(IsFinalTx(CTransaction(tx), ::ChainActive().Tip()->nHeight + 2, ::ChainActive().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later
@@ -481,7 +483,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
prevheights[0] = ::ChainActive().Tip()->nHeight + 1;
tx.nLockTime = 0;
tx.vin[0].nSequence = 0;
- BOOST_CHECK(CheckFinalTx(CTransaction(tx), flags)); // Locktime passes
+ BOOST_CHECK(CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime passes
BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass
tx.vin[0].nSequence = 1;
BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index f52905e1ef..1c7c35528e 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -691,7 +691,7 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test)
pnode->SetAddrLocal(addrLocal);
// before patch, this causes undefined behavior detectable with clang's -fsanitize=memory
- AdvertiseLocal(&*pnode);
+ GetLocalAddrForPeer(&*pnode);
// suppress no-checks-run warning; if this test fails, it's by triggering a sanitizer
BOOST_CHECK(1);
diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp
index 740b2c72db..3e4b963fe3 100644
--- a/src/test/sanity_tests.cpp
+++ b/src/test/sanity_tests.cpp
@@ -5,6 +5,7 @@
#include <compat/sanity.h>
#include <key.h>
#include <test/util/setup_common.h>
+#include <util/time.h>
#include <boost/test/unit_test.hpp>
@@ -15,6 +16,7 @@ BOOST_AUTO_TEST_CASE(basic_sanity)
BOOST_CHECK_MESSAGE(glibc_sanity_test() == true, "libc sanity test");
BOOST_CHECK_MESSAGE(glibcxx_sanity_test() == true, "stdlib sanity test");
BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "secp256k1 sanity test");
+ BOOST_CHECK_MESSAGE(ChronoSanityCheck() == true, "chrono epoch test");
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/scriptnum_tests.cpp b/src/test/scriptnum_tests.cpp
index 281018be9f..746d4d3c6b 100644
--- a/src/test/scriptnum_tests.cpp
+++ b/src/test/scriptnum_tests.cpp
@@ -164,9 +164,9 @@ static void RunOperators(const int64_t& num1, const int64_t& num2)
BOOST_AUTO_TEST_CASE(creation)
{
- for(size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i)
+ for(size_t i = 0; i < std::size(values); ++i)
{
- for(size_t j = 0; j < sizeof(offsets) / sizeof(offsets[0]); ++j)
+ for(size_t j = 0; j < std::size(offsets); ++j)
{
RunCreate(values[i]);
RunCreate(values[i] + offsets[j]);
@@ -177,9 +177,9 @@ BOOST_AUTO_TEST_CASE(creation)
BOOST_AUTO_TEST_CASE(operators)
{
- for(size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i)
+ for(size_t i = 0; i < std::size(values); ++i)
{
- for(size_t j = 0; j < sizeof(offsets) / sizeof(offsets[0]); ++j)
+ for(size_t j = 0; j < std::size(offsets); ++j)
{
RunOperators(values[i], values[i]);
RunOperators(values[i], -values[i]);
diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp
index bc862de78a..2eb980e8cd 100644
--- a/src/test/sighash_tests.cpp
+++ b/src/test/sighash_tests.cpp
@@ -88,7 +88,7 @@ void static RandomScript(CScript &script) {
script = CScript();
int ops = (InsecureRandRange(10));
for (int i=0; i<ops; i++)
- script << oplist[InsecureRandRange(sizeof(oplist)/sizeof(oplist[0]))];
+ script << oplist[InsecureRandRange(std::size(oplist))];
}
void static RandomTransaction(CMutableTransaction &tx, bool fSingle) {
diff --git a/src/test/sock_tests.cpp b/src/test/sock_tests.cpp
index cc0e6e7057..ed9780dfb5 100644
--- a/src/test/sock_tests.cpp
+++ b/src/test/sock_tests.cpp
@@ -95,7 +95,7 @@ static void CreateSocketPair(int s[2])
static void SendAndRecvMessage(const Sock& sender, const Sock& receiver)
{
const char* msg = "abcd";
- constexpr size_t msg_len = 4;
+ constexpr ssize_t msg_len = 4;
char recv_buf[10];
BOOST_CHECK_EQUAL(sender.Send(msg, msg_len, 0), msg_len);
diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp
index 9b0d2e3135..8d14071297 100644
--- a/src/test/txvalidation_tests.cpp
+++ b/src/test/txvalidation_tests.cpp
@@ -33,7 +33,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)
LOCK(cs_main);
unsigned int initialPoolSize = m_node.mempool->size();
- const MempoolAcceptResult result = AcceptToMemoryPool(*m_node.mempool, MakeTransactionRef(coinbaseTx),
+ const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool, MakeTransactionRef(coinbaseTx),
true /* bypass_limits */);
BOOST_CHECK(result.m_result_type == MempoolAcceptResult::ResultType::INVALID);
diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp
index af0090cc10..288f807abd 100644
--- a/src/test/txvalidationcache_tests.cpp
+++ b/src/test/txvalidationcache_tests.cpp
@@ -28,7 +28,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)
const auto ToMemPool = [this](const CMutableTransaction& tx) {
LOCK(cs_main);
- const MempoolAcceptResult result = AcceptToMemoryPool(*m_node.mempool, MakeTransactionRef(tx),
+ const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool, MakeTransactionRef(tx),
true /* bypass_limits */);
return result.m_result_type == MempoolAcceptResult::ResultType::VALID;
};
diff --git a/src/test/util/script.h b/src/test/util/script.h
new file mode 100644
index 0000000000..abd14c2067
--- /dev/null
+++ b/src/test/util/script.h
@@ -0,0 +1,21 @@
+// 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.
+
+#ifndef BITCOIN_TEST_UTIL_SCRIPT_H
+#define BITCOIN_TEST_UTIL_SCRIPT_H
+
+#include <crypto/sha256.h>
+#include <script/script.h>
+
+static const std::vector<uint8_t> WITNESS_STACK_ELEM_OP_TRUE{uint8_t{OP_TRUE}};
+static const CScript P2WSH_OP_TRUE{
+ CScript{}
+ << OP_0
+ << ToByteVector([] {
+ uint256 hash;
+ CSHA256().Write(WITNESS_STACK_ELEM_OP_TRUE.data(), WITNESS_STACK_ELEM_OP_TRUE.size()).Finalize(hash.begin());
+ return hash;
+ }())};
+
+#endif // BITCOIN_TEST_UTIL_SCRIPT_H
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index b9f3f8c955..1ffe435531 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -77,6 +77,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve
{
"dummy",
"-printtoconsole=0",
+ "-logsourcelocations",
"-logtimemicros",
"-logthreadnames",
"-debug",
@@ -199,14 +200,43 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
}
}
-TestChain100Setup::TestChain100Setup()
+TestChain100Setup::TestChain100Setup(bool deterministic)
{
+ m_deterministic = deterministic;
+
+ if (m_deterministic) {
+ SetMockTime(1598887952);
+ constexpr std::array<unsigned char, 32> vchKey = {
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+ }
+ };
+ coinbaseKey.Set(vchKey.begin(), vchKey.end(), false);
+ } else {
+ coinbaseKey.MakeNewKey(true);
+ }
+
// Generate a 100-block chain:
- coinbaseKey.MakeNewKey(true);
+ this->mineBlocks(COINBASE_MATURITY);
+
+ if (m_deterministic) {
+ LOCK(::cs_main);
+ assert(
+ m_node.chainman->ActiveChain().Tip()->GetBlockHash().ToString() ==
+ "49c95db1e470fed04496d801c9d8fbb78155d2c7f855232c918823d2c17d0cf6");
+ }
+}
+
+void TestChain100Setup::mineBlocks(int num_blocks)
+{
CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
- for (int i = 0; i < COINBASE_MATURITY; i++) {
+ for (int i = 0; i < num_blocks; i++)
+ {
std::vector<CMutableTransaction> noTxns;
CBlock b = CreateAndProcessBlock(noTxns, scriptPubKey);
+ if (m_deterministic) {
+ SetMockTime(GetTime() + 1);
+ }
m_coinbase_txns.push_back(b.vtx[0]);
}
}
@@ -231,9 +261,61 @@ CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransa
return block;
}
+
+CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(CTransactionRef input_transaction,
+ int input_vout,
+ int input_height,
+ CKey input_signing_key,
+ CScript output_destination,
+ CAmount output_amount)
+{
+ // Transaction we will submit to the mempool
+ CMutableTransaction mempool_txn;
+
+ // Create an input
+ COutPoint outpoint_to_spend(input_transaction->GetHash(), input_vout);
+ CTxIn input(outpoint_to_spend);
+ mempool_txn.vin.push_back(input);
+
+ // Create an output
+ CTxOut output(output_amount, output_destination);
+ mempool_txn.vout.push_back(output);
+
+ // Sign the transaction
+ // - Add the signing key to a keystore
+ FillableSigningProvider keystore;
+ keystore.AddKey(input_signing_key);
+ // - Populate a CoinsViewCache with the unspent output
+ CCoinsView coins_view;
+ CCoinsViewCache coins_cache(&coins_view);
+ AddCoins(coins_cache, *input_transaction.get(), input_height);
+ // - Use GetCoin to properly populate utxo_to_spend,
+ Coin utxo_to_spend;
+ assert(coins_cache.GetCoin(outpoint_to_spend, utxo_to_spend));
+ // - Then add it to a map to pass in to SignTransaction
+ std::map<COutPoint, Coin> input_coins;
+ input_coins.insert({outpoint_to_spend, utxo_to_spend});
+ // - Default signature hashing type
+ int nHashType = SIGHASH_ALL;
+ std::map<int, std::string> input_errors;
+ assert(SignTransaction(mempool_txn, &keystore, input_coins, nHashType, input_errors));
+
+ // Add transaction to the mempool
+ {
+ LOCK(cs_main);
+ const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool.get(), MakeTransactionRef(mempool_txn), /* bypass_limits */ false);
+ assert(result.m_result_type == MempoolAcceptResult::ResultType::VALID);
+ }
+
+ return mempool_txn;
+}
+
TestChain100Setup::~TestChain100Setup()
{
gArgs.ForceSetArg("-segwitheight", "0");
+ if (m_deterministic) {
+ SetMockTime(0);
+ }
}
CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const
diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h
index 331c1235cb..33f24e7c44 100644
--- a/src/test/util/setup_common.h
+++ b/src/test/util/setup_common.h
@@ -78,7 +78,6 @@ struct BasicTestingSetup {
explicit BasicTestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector<const char*>& extra_args = {});
~BasicTestingSetup();
-private:
const fs::path m_path_root;
};
@@ -112,7 +111,7 @@ class CScript;
* Testing fixture that pre-creates a 100-block REGTEST-mode block chain
*/
struct TestChain100Setup : public RegTestingSetup {
- TestChain100Setup();
+ TestChain100Setup(bool deterministic = false);
/**
* Create a new block with just given transactions, coinbase paying to
@@ -121,12 +120,38 @@ struct TestChain100Setup : public RegTestingSetup {
CBlock CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns,
const CScript& scriptPubKey);
+ //! Mine a series of new blocks on the active chain.
+ void mineBlocks(int num_blocks);
+
+ /**
+ * Create a transaction and submit to the mempool.
+ *
+ * @param input_transaction The transaction to spend
+ * @param input_vout The vout to spend from the input_transaction
+ * @param input_height The height of the block that included the input_transaction
+ * @param input_signing_key The key to spend the input_transaction
+ * @param output_destination Where to send the output
+ * @param output_amount How much to send
+ */
+ CMutableTransaction CreateValidMempoolTransaction(CTransactionRef input_transaction,
+ int input_vout,
+ int input_height,
+ CKey input_signing_key,
+ CScript output_destination,
+ CAmount output_amount = CAmount(1 * COIN));
+
~TestChain100Setup();
+ bool m_deterministic;
std::vector<CTransactionRef> m_coinbase_txns; // For convenience, coinbase transactions
CKey coinbaseKey; // private/public key needed to spend coinbase transactions
};
+
+struct TestChain100DeterministicSetup : public TestChain100Setup {
+ TestChain100DeterministicSetup() : TestChain100Setup(true) { }
+};
+
class CTxMemPoolEntry;
struct TestMemPoolEntryHelper
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index dccbf00d6f..845854bd4b 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -2201,4 +2201,17 @@ BOOST_AUTO_TEST_CASE(message_hash)
BOOST_CHECK_NE(message_hash1, signature_hash);
}
+BOOST_AUTO_TEST_CASE(remove_prefix)
+{
+ BOOST_CHECK_EQUAL(RemovePrefix("./util/system.h", "./"), "util/system.h");
+ BOOST_CHECK_EQUAL(RemovePrefix("foo", "foo"), "");
+ BOOST_CHECK_EQUAL(RemovePrefix("foo", "fo"), "o");
+ BOOST_CHECK_EQUAL(RemovePrefix("foo", "f"), "oo");
+ BOOST_CHECK_EQUAL(RemovePrefix("foo", ""), "foo");
+ BOOST_CHECK_EQUAL(RemovePrefix("fo", "foo"), "fo");
+ BOOST_CHECK_EQUAL(RemovePrefix("f", "foo"), "f");
+ BOOST_CHECK_EQUAL(RemovePrefix("", "foo"), "");
+ BOOST_CHECK_EQUAL(RemovePrefix("", ""), "");
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp
index 6c18a9e1bb..0c87c4d360 100644
--- a/src/test/validation_block_tests.cpp
+++ b/src/test/validation_block_tests.cpp
@@ -11,6 +11,7 @@
#include <pow.h>
#include <random.h>
#include <script/standard.h>
+#include <test/util/script.h>
#include <test/util/setup_common.h>
#include <util/time.h>
#include <validation.h>
@@ -18,8 +19,6 @@
#include <thread>
-static const std::vector<unsigned char> V_OP_TRUE{OP_TRUE};
-
namespace validation_block_tests {
struct MinerTestingSetup : public RegTestingSetup {
std::shared_ptr<CBlock> Block(const uint256& prev_hash);
@@ -64,27 +63,17 @@ std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash)
static int i = 0;
static uint64_t time = Params().GenesisBlock().nTime;
- CScript pubKey;
- pubKey << i++ << OP_TRUE;
-
- auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(pubKey);
+ auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(CScript{} << i++ << OP_TRUE);
auto pblock = std::make_shared<CBlock>(ptemplate->block);
pblock->hashPrevBlock = prev_hash;
pblock->nTime = ++time;
- pubKey.clear();
- {
- WitnessV0ScriptHash witness_program;
- CSHA256().Write(&V_OP_TRUE[0], V_OP_TRUE.size()).Finalize(witness_program.begin());
- pubKey << OP_0 << ToByteVector(witness_program);
- }
-
// Make the coinbase transaction with two outputs:
// One zero-value one that has a unique pubkey to make sure that blocks at the same height can have a different hash
// Another one that has the coinbase reward in a P2WSH with OP_TRUE as witness program to make it easy to spend
CMutableTransaction txCoinbase(*pblock->vtx[0]);
txCoinbase.vout.resize(2);
- txCoinbase.vout[1].scriptPubKey = pubKey;
+ txCoinbase.vout[1].scriptPubKey = P2WSH_OP_TRUE;
txCoinbase.vout[1].nValue = txCoinbase.vout[0].nValue;
txCoinbase.vout[0].nValue = 0;
txCoinbase.vin[0].scriptWitness.SetNull();
@@ -254,7 +243,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
for (int num_txs = 22; num_txs > 0; --num_txs) {
CMutableTransaction mtx;
mtx.vin.push_back(CTxIn{COutPoint{last_mined->vtx[0]->GetHash(), 1}, CScript{}});
- mtx.vin[0].scriptWitness.stack.push_back(V_OP_TRUE);
+ mtx.vin[0].scriptWitness.stack.push_back(WITNESS_STACK_ELEM_OP_TRUE);
mtx.vout.push_back(last_mined->vtx[0]->vout[1]);
mtx.vout[0].nValue -= 1000;
txs.push_back(MakeTransactionRef(mtx));
@@ -284,7 +273,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
{
LOCK(cs_main);
for (const auto& tx : txs) {
- const MempoolAcceptResult result = AcceptToMemoryPool(*m_node.mempool, tx, false /* bypass_limits */);
+ const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool, tx, false /* bypass_limits */);
BOOST_REQUIRE(result.m_result_type == MempoolAcceptResult::ResultType::VALID);
}
}
diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
index 3d8570e27c..94d4277019 100644
--- a/src/test/validation_chainstatemanager_tests.cpp
+++ b/src/test/validation_chainstatemanager_tests.cpp
@@ -4,13 +4,18 @@
//
#include <chainparams.h>
#include <consensus/validation.h>
+#include <node/utxo_snapshot.h>
#include <random.h>
+#include <rpc/blockchain.h>
#include <sync.h>
#include <test/util/setup_common.h>
#include <uint256.h>
#include <validation.h>
#include <validationinterface.h>
+#include <tinyformat.h>
+#include <univalue.h>
+
#include <vector>
#include <boost/test/unit_test.hpp>
@@ -28,6 +33,8 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
std::vector<CChainState*> chainstates;
const CChainParams& chainparams = Params();
+ BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
+
// Create a legacy (IBD) chainstate.
//
CChainState& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(mempool));
@@ -54,10 +61,17 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
auto& validated_cs = manager.ValidatedChainstate();
BOOST_CHECK_EQUAL(&validated_cs, &c1);
+ BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
+
// Create a snapshot-based chainstate.
//
- CChainState& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(mempool, GetRandHash()));
+ const uint256 snapshot_blockhash = GetRandHash();
+ CChainState& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(
+ mempool, snapshot_blockhash));
chainstates.push_back(&c2);
+
+ BOOST_CHECK_EQUAL(manager.SnapshotBlockhash().value(), snapshot_blockhash);
+
c2.InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
WITH_LOCK(::cs_main, c2.InitCoinsCache(1 << 23));
@@ -155,4 +169,175 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1);
}
+auto NoMalleation = [](CAutoFile& file, SnapshotMetadata& meta){};
+
+template<typename F = decltype(NoMalleation)>
+static bool
+CreateAndActivateUTXOSnapshot(NodeContext& node, const fs::path root, F malleation = NoMalleation)
+{
+ // Write out a snapshot to the test's tempdir.
+ //
+ int height;
+ WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight());
+ fs::path snapshot_path = root / tfm::format("test_snapshot.%d.dat", height);
+ FILE* outfile{fsbridge::fopen(snapshot_path, "wb")};
+ CAutoFile auto_outfile{outfile, SER_DISK, CLIENT_VERSION};
+
+ UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), auto_outfile);
+ BOOST_TEST_MESSAGE(
+ "Wrote UTXO snapshot to " << snapshot_path.make_preferred().string() << ": " << result.write());
+
+ // Read the written snapshot in and then activate it.
+ //
+ FILE* infile{fsbridge::fopen(snapshot_path, "rb")};
+ CAutoFile auto_infile{infile, SER_DISK, CLIENT_VERSION};
+ SnapshotMetadata metadata;
+ auto_infile >> metadata;
+
+ malleation(auto_infile, metadata);
+
+ return node.chainman->ActivateSnapshot(auto_infile, metadata, /*in_memory*/ true);
+}
+
+//! Test basic snapshot activation.
+BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100DeterministicSetup)
+{
+ ChainstateManager& chainman = *Assert(m_node.chainman);
+
+ size_t initial_size;
+ size_t initial_total_coins{100};
+
+ // Make some initial assertions about the contents of the chainstate.
+ {
+ LOCK(::cs_main);
+ CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip();
+ initial_size = ibd_coinscache.GetCacheSize();
+ size_t total_coins{0};
+
+ for (CTransactionRef& txn : m_coinbase_txns) {
+ COutPoint op{txn->GetHash(), 0};
+ BOOST_CHECK(ibd_coinscache.HaveCoin(op));
+ total_coins++;
+ }
+
+ BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
+ BOOST_CHECK_EQUAL(initial_size, initial_total_coins);
+ }
+
+ // Snapshot should refuse to load at this height.
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root));
+ BOOST_CHECK(chainman.ActiveChainstate().m_from_snapshot_blockhash.IsNull());
+ BOOST_CHECK_EQUAL(
+ chainman.ActiveChainstate().m_from_snapshot_blockhash,
+ chainman.SnapshotBlockhash().value_or(uint256()));
+
+ // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can
+ // be found.
+ mineBlocks(10);
+ initial_size += 10;
+ initial_total_coins += 10;
+
+ // Should not load malleated snapshots
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
+ m_node, m_path_root, [](CAutoFile& auto_infile, SnapshotMetadata& metadata) {
+ // A UTXO is missing but count is correct
+ metadata.m_coins_count -= 1;
+
+ COutPoint outpoint;
+ Coin coin;
+
+ auto_infile >> outpoint;
+ auto_infile >> coin;
+ }));
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
+ m_node, m_path_root, [](CAutoFile& auto_infile, SnapshotMetadata& metadata) {
+ // Coins count is larger than coins in file
+ metadata.m_coins_count += 1;
+ }));
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
+ m_node, m_path_root, [](CAutoFile& auto_infile, SnapshotMetadata& metadata) {
+ // Coins count is smaller than coins in file
+ metadata.m_coins_count -= 1;
+ }));
+
+ BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root));
+
+ // Ensure our active chain is the snapshot chainstate.
+ BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash.IsNull());
+ BOOST_CHECK_EQUAL(
+ chainman.ActiveChainstate().m_from_snapshot_blockhash,
+ *chainman.SnapshotBlockhash());
+
+ // To be checked against later when we try loading a subsequent snapshot.
+ uint256 loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()};
+
+ // Make some assertions about the both chainstates. These checks ensure the
+ // legacy chainstate hasn't changed and that the newly created chainstate
+ // reflects the expected content.
+ {
+ LOCK(::cs_main);
+ int chains_tested{0};
+
+ for (CChainState* chainstate : chainman.GetAll()) {
+ BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
+ CCoinsViewCache& coinscache = chainstate->CoinsTip();
+
+ // Both caches will be empty initially.
+ BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize());
+
+ size_t total_coins{0};
+
+ for (CTransactionRef& txn : m_coinbase_txns) {
+ COutPoint op{txn->GetHash(), 0};
+ BOOST_CHECK(coinscache.HaveCoin(op));
+ total_coins++;
+ }
+
+ BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize());
+ BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
+ chains_tested++;
+ }
+
+ BOOST_CHECK_EQUAL(chains_tested, 2);
+ }
+
+ // Mine some new blocks on top of the activated snapshot chainstate.
+ constexpr size_t new_coins{100};
+ mineBlocks(new_coins); // Defined in TestChain100Setup.
+
+ {
+ LOCK(::cs_main);
+ size_t coins_in_active{0};
+ size_t coins_in_ibd{0};
+ size_t coins_missing_ibd{0};
+
+ for (CChainState* chainstate : chainman.GetAll()) {
+ BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
+ CCoinsViewCache& coinscache = chainstate->CoinsTip();
+ bool is_ibd = chainman.IsBackgroundIBD(chainstate);
+
+ for (CTransactionRef& txn : m_coinbase_txns) {
+ COutPoint op{txn->GetHash(), 0};
+ if (coinscache.HaveCoin(op)) {
+ (is_ibd ? coins_in_ibd : coins_in_active)++;
+ } else if (is_ibd) {
+ coins_missing_ibd++;
+ }
+ }
+ }
+
+ BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins);
+ BOOST_CHECK_EQUAL(coins_in_ibd, initial_total_coins);
+ BOOST_CHECK_EQUAL(coins_missing_ibd, new_coins);
+ }
+
+ // Snapshot should refuse to load after one has already loaded.
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root));
+
+ // Snapshot blockhash should be unchanged.
+ BOOST_CHECK_EQUAL(
+ chainman.ActiveChainstate().m_from_snapshot_blockhash,
+ loaded_snapshot_blockhash);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/validation_tests.cpp b/src/test/validation_tests.cpp
index 9e37f14921..ecf9453094 100644
--- a/src/test/validation_tests.cpp
+++ b/src/test/validation_tests.cpp
@@ -5,6 +5,7 @@
#include <chainparams.h>
#include <net.h>
#include <signet.h>
+#include <uint256.h>
#include <validation.h>
#include <test/util/setup_common.h>
@@ -119,4 +120,27 @@ BOOST_AUTO_TEST_CASE(signet_parse_tests)
BOOST_CHECK(!CheckSignetBlockSolution(block, signet_params->GetConsensus()));
}
+//! Test retrieval of valid assumeutxo values.
+BOOST_AUTO_TEST_CASE(test_assumeutxo)
+{
+ const auto params = CreateChainParams(*m_node.args, CBaseChainParams::REGTEST);
+
+ // These heights don't have assumeutxo configurations associated, per the contents
+ // of chainparams.cpp.
+ std::vector<int> bad_heights{0, 100, 111, 115, 209, 211};
+
+ for (auto empty : bad_heights) {
+ const auto out = ExpectedAssumeutxo(empty, *params);
+ BOOST_CHECK(!out);
+ }
+
+ const auto out110 = *ExpectedAssumeutxo(110, *params);
+ BOOST_CHECK_EQUAL(out110.hash_serialized, uint256S("76fd7334ac7c1baf57ddc0c626f073a655a35d98a4258cd1382c8cc2b8392e10"));
+ BOOST_CHECK_EQUAL(out110.nChainTx, (unsigned int)110);
+
+ const auto out210 = *ExpectedAssumeutxo(210, *params);
+ BOOST_CHECK_EQUAL(out210.hash_serialized, uint256S("9c5ed99ef98544b34f8920b6d1802f72ac28ae6e2bd2bd4c316ff10c230df3f2"));
+ BOOST_CHECK_EQUAL(out210.nChainTx, (unsigned int)210);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/txdb.cpp b/src/txdb.cpp
index 72460e7c69..4b4766e1ba 100644
--- a/src/txdb.cpp
+++ b/src/txdb.cpp
@@ -47,11 +47,15 @@ CCoinsViewDB::CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, b
void CCoinsViewDB::ResizeCache(size_t new_cache_size)
{
- // Have to do a reset first to get the original `m_db` state to release its
- // filesystem lock.
- m_db.reset();
- m_db = MakeUnique<CDBWrapper>(
- m_ldb_path, new_cache_size, m_is_memory, /*fWipe*/ false, /*obfuscate*/ true);
+ // We can't do this operation with an in-memory DB since we'll lose all the coins upon
+ // reset.
+ if (!m_is_memory) {
+ // Have to do a reset first to get the original `m_db` state to release its
+ // filesystem lock.
+ m_db.reset();
+ m_db = MakeUnique<CDBWrapper>(
+ m_ldb_path, new_cache_size, m_is_memory, /*fWipe*/ false, /*obfuscate*/ true);
+ }
}
bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const {
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index 9fa7b4e251..899835019a 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -503,7 +503,7 @@ void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReaso
RemoveStaged(setAllRemoves, false, reason);
}
-void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags)
+void CTxMemPool::removeForReorg(CChainState& active_chainstate, int flags)
{
// Remove transactions spending a coinbase which are now immature and no-longer-final transactions
AssertLockHeld(cs);
@@ -511,8 +511,9 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
const CTransaction& tx = it->GetTx();
LockPoints lp = it->GetLockPoints();
- bool validLP = TestLockPointValidity(&lp);
- if (!CheckFinalTx(tx, flags) || !CheckSequenceLocks(*this, tx, flags, &lp, validLP)) {
+ assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
+ bool validLP = TestLockPointValidity(active_chainstate.m_chain, &lp);
+ if (!CheckFinalTx(active_chainstate.m_chain.Tip(), tx, flags) || !CheckSequenceLocks(active_chainstate, *this, tx, flags, &lp, validLP)) {
// Note if CheckSequenceLocks fails the LockPoints may still be invalid
// So it's critical that we remove the tx and not depend on the LockPoints.
txToRemove.insert(it);
@@ -521,8 +522,9 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash);
if (it2 != mapTx.end())
continue;
- const Coin &coin = pcoins->AccessCoin(txin.prevout);
+ const Coin &coin = active_chainstate.CoinsTip().AccessCoin(txin.prevout);
if (m_check_ratio != 0) assert(!coin.IsSpent());
+ unsigned int nMemPoolHeight = active_chainstate.m_chain.Tip()->nHeight + 1;
if (coin.IsSpent() || (coin.IsCoinBase() && ((signed long)nMemPoolHeight) - coin.nHeight < COINBASE_MATURITY)) {
txToRemove.insert(it);
break;
diff --git a/src/txmempool.h b/src/txmempool.h
index c0df33fe13..b8de326737 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -29,6 +29,7 @@
#include <boost/multi_index/sequenced_index.hpp>
class CBlockIndex;
+class CChainState;
extern RecursiveMutex cs_main;
/** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */
@@ -616,7 +617,7 @@ public:
void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
- void removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
+ void removeForReorg(CChainState& active_chainstate, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
void removeConflicts(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs);
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs);
diff --git a/src/util/strencodings.h b/src/util/strencodings.h
index b4a61202ef..98379e9138 100644
--- a/src/util/strencodings.h
+++ b/src/util/strencodings.h
@@ -17,8 +17,6 @@
#include <string>
#include <vector>
-#define ARRAYLEN(array) (sizeof(array)/sizeof((array)[0]))
-
/** Used by SanitizeString() */
enum SafeChars
{
diff --git a/src/util/string.h b/src/util/string.h
index 5ffdc80d88..b26facc502 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -25,6 +25,14 @@
return str.substr(front, end - front + 1);
}
+[[nodiscard]] inline std::string RemovePrefix(const std::string& str, const std::string& prefix)
+{
+ if (str.substr(0, prefix.size()) == prefix) {
+ return str.substr(prefix.size());
+ }
+ return str;
+}
+
/**
* Join a list of items
*
diff --git a/src/util/time.cpp b/src/util/time.cpp
index 295806c54a..e6f0986a39 100644
--- a/src/util/time.cpp
+++ b/src/util/time.cpp
@@ -33,6 +33,49 @@ int64_t GetTime()
return now;
}
+bool ChronoSanityCheck()
+{
+ // std::chrono::system_clock.time_since_epoch and time_t(0) are not guaranteed
+ // to use the Unix epoch timestamp, prior to C++20, but in practice they almost
+ // certainly will. Any differing behavior will be assumed to be an error, unless
+ // certain platforms prove to consistently deviate, at which point we'll cope
+ // with it by adding offsets.
+
+ // Create a new clock from time_t(0) and make sure that it represents 0
+ // seconds from the system_clock's time_since_epoch. Then convert that back
+ // to a time_t and verify that it's the same as before.
+ const time_t time_t_epoch{};
+ auto clock = std::chrono::system_clock::from_time_t(time_t_epoch);
+ if (std::chrono::duration_cast<std::chrono::seconds>(clock.time_since_epoch()).count() != 0) {
+ return false;
+ }
+
+ time_t time_val = std::chrono::system_clock::to_time_t(clock);
+ if (time_val != time_t_epoch) {
+ return false;
+ }
+
+ // Check that the above zero time is actually equal to the known unix timestamp.
+ struct tm epoch;
+#ifdef HAVE_GMTIME_R
+ if (gmtime_r(&time_val, &epoch) == nullptr) {
+#else
+ if (gmtime_s(&epoch, &time_val) != 0) {
+#endif
+ return false;
+ }
+
+ if ((epoch.tm_sec != 0) ||
+ (epoch.tm_min != 0) ||
+ (epoch.tm_hour != 0) ||
+ (epoch.tm_mday != 1) ||
+ (epoch.tm_mon != 0) ||
+ (epoch.tm_year != 70)) {
+ return false;
+ }
+ return true;
+}
+
template <typename T>
T GetTime()
{
@@ -47,36 +90,43 @@ template std::chrono::seconds GetTime();
template std::chrono::milliseconds GetTime();
template std::chrono::microseconds GetTime();
+template <typename T>
+static T GetSystemTime()
+{
+ const auto now = std::chrono::duration_cast<T>(std::chrono::system_clock::now().time_since_epoch());
+ assert(now.count() > 0);
+ return now;
+}
+
void SetMockTime(int64_t nMockTimeIn)
{
Assert(nMockTimeIn >= 0);
nMockTime.store(nMockTimeIn, std::memory_order_relaxed);
}
-int64_t GetMockTime()
+void SetMockTime(std::chrono::seconds mock_time_in)
+{
+ nMockTime.store(mock_time_in.count(), std::memory_order_relaxed);
+}
+
+std::chrono::seconds GetMockTime()
{
- return nMockTime.load(std::memory_order_relaxed);
+ return std::chrono::seconds(nMockTime.load(std::memory_order_relaxed));
}
int64_t GetTimeMillis()
{
- int64_t now = (boost::posix_time::microsec_clock::universal_time() -
- boost::posix_time::ptime(boost::gregorian::date(1970,1,1))).total_milliseconds();
- assert(now > 0);
- return now;
+ return int64_t{GetSystemTime<std::chrono::milliseconds>().count()};
}
int64_t GetTimeMicros()
{
- int64_t now = (boost::posix_time::microsec_clock::universal_time() -
- boost::posix_time::ptime(boost::gregorian::date(1970,1,1))).total_microseconds();
- assert(now > 0);
- return now;
+ return int64_t{GetSystemTime<std::chrono::microseconds>().count()};
}
int64_t GetSystemTimeInSeconds()
{
- return GetTimeMicros()/1000000;
+ return int64_t{GetSystemTime<std::chrono::seconds>().count()};
}
std::string FormatISO8601DateTime(int64_t nTime) {
diff --git a/src/util/time.h b/src/util/time.h
index 03b75b5be5..56131ce0fe 100644
--- a/src/util/time.h
+++ b/src/util/time.h
@@ -43,10 +43,19 @@ int64_t GetTimeMicros();
/** Returns the system time (not mockable) */
int64_t GetSystemTimeInSeconds(); // Like GetTime(), but not mockable
-/** For testing. Set e.g. with the setmocktime rpc, or -mocktime argument */
+/**
+ * DEPRECATED
+ * Use SetMockTime with chrono type
+ *
+ * @param[in] nMockTimeIn Time in seconds.
+ */
void SetMockTime(int64_t nMockTimeIn);
+
+/** For testing. Set e.g. with the setmocktime rpc, or -mocktime argument */
+void SetMockTime(std::chrono::seconds mock_time_in);
+
/** For testing */
-int64_t GetMockTime();
+std::chrono::seconds GetMockTime();
/** Return system time (or mocked time, if set) */
template <typename T>
@@ -70,4 +79,7 @@ struct timeval MillisToTimeval(int64_t nTimeout);
*/
struct timeval MillisToTimeval(std::chrono::milliseconds ms);
+/** Sanity check epoch match normal Unix epoch */
+bool ChronoSanityCheck();
+
#endif // BITCOIN_UTIL_TIME_H
diff --git a/src/validation.cpp b/src/validation.cpp
index 778d75ce18..1b8fb1fd45 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -17,9 +17,11 @@
#include <cuckoocache.h>
#include <flatfile.h>
#include <hash.h>
+#include <index/blockfilterindex.h>
#include <index/txindex.h>
#include <logging.h>
#include <logging/timer.h>
+#include <node/coinstats.h>
#include <node/ui_interface.h>
#include <optional.h>
#include <policy/policy.h>
@@ -202,9 +204,10 @@ static FILE* OpenUndoFile(const FlatFilePos &pos, bool fReadOnly = false);
static FlatFileSeq BlockFileSeq();
static FlatFileSeq UndoFileSeq();
-bool CheckFinalTx(const CTransaction &tx, int flags)
+bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, int flags)
{
AssertLockHeld(cs_main);
+ assert(std::addressof(*::ChainActive().Tip()) == std::addressof(*active_chain_tip));
// By convention a negative value for flags indicates that the
// current network-enforced consensus rules should be used. In
@@ -220,7 +223,7 @@ bool CheckFinalTx(const CTransaction &tx, int flags)
// evaluated is what is used. Thus if we want to know if a
// transaction can be part of the *next* block, we need to call
// IsFinalTx() with one more than ::ChainActive().Height().
- const int nBlockHeight = ::ChainActive().Height() + 1;
+ const int nBlockHeight = active_chain_tip->nHeight + 1;
// BIP113 requires that time-locked transactions have nLockTime set to
// less than the median time of the previous block they're contained in.
@@ -228,13 +231,13 @@ bool CheckFinalTx(const CTransaction &tx, int flags)
// chain tip, so we use that to calculate the median time passed to
// IsFinalTx() if LOCKTIME_MEDIAN_TIME_PAST is set.
const int64_t nBlockTime = (flags & LOCKTIME_MEDIAN_TIME_PAST)
- ? ::ChainActive().Tip()->GetMedianTimePast()
+ ? active_chain_tip->GetMedianTimePast()
: GetAdjustedTime();
return IsFinalTx(tx, nBlockHeight, nBlockTime);
}
-bool TestLockPointValidity(const LockPoints* lp)
+bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp)
{
AssertLockHeld(cs_main);
assert(lp);
@@ -243,7 +246,8 @@ bool TestLockPointValidity(const LockPoints* lp)
if (lp->maxInputBlock) {
// Check whether ::ChainActive() is an extension of the block at which the LockPoints
// calculation was valid. If not LockPoints are no longer valid
- if (!::ChainActive().Contains(lp->maxInputBlock)) {
+ assert(std::addressof(::ChainActive()) == std::addressof(active_chain));
+ if (!active_chain.Contains(lp->maxInputBlock)) {
return false;
}
}
@@ -252,22 +256,28 @@ bool TestLockPointValidity(const LockPoints* lp)
return true;
}
-bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp, bool useExistingLockPoints)
+bool CheckSequenceLocks(CChainState& active_chainstate,
+ const CTxMemPool& pool,
+ const CTransaction& tx,
+ int flags,
+ LockPoints* lp,
+ bool useExistingLockPoints)
{
AssertLockHeld(cs_main);
AssertLockHeld(pool.cs);
+ assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
- CBlockIndex* tip = ::ChainActive().Tip();
+ CBlockIndex* tip = active_chainstate.m_chain.Tip();
assert(tip != nullptr);
CBlockIndex index;
index.pprev = tip;
- // CheckSequenceLocks() uses ::ChainActive().Height()+1 to evaluate
+ // CheckSequenceLocks() uses active_chainstate.m_chain.Height()+1 to evaluate
// height based locks because when SequenceLocks() is called within
// ConnectBlock(), the height of the block *being*
// evaluated is what is used.
// Thus if we want to know if a transaction can be part of the
- // *next* block, we need to use one more than ::ChainActive().Height()
+ // *next* block, we need to use one more than active_chainstate.m_chain.Height()
index.nHeight = tip->nHeight + 1;
std::pair<int, int64_t> lockPair;
@@ -277,8 +287,8 @@ bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flag
lockPair.second = lp->time;
}
else {
- // CoinsTip() contains the UTXO set for ::ChainActive().Tip()
- CCoinsViewMemPool viewMemPool(&::ChainstateActive().CoinsTip(), pool);
+ // CoinsTip() contains the UTXO set for active_chainstate.m_chain.Tip()
+ CCoinsViewMemPool viewMemPool(&active_chainstate.CoinsTip(), pool);
std::vector<int> prevheights;
prevheights.resize(tx.vin.size());
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
@@ -327,7 +337,7 @@ bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flag
// Returns the script flags which should be checked for a given block
static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& chainparams);
-static void LimitMempoolSize(CTxMemPool& pool, size_t limit, std::chrono::seconds age)
+static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache, size_t limit, std::chrono::seconds age)
EXCLUSIVE_LOCKS_REQUIRED(pool.cs, ::cs_main)
{
int expired = pool.Expire(GetTime<std::chrono::seconds>() - age);
@@ -337,18 +347,20 @@ static void LimitMempoolSize(CTxMemPool& pool, size_t limit, std::chrono::second
std::vector<COutPoint> vNoSpendsRemaining;
pool.TrimToSize(limit, &vNoSpendsRemaining);
+ assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(coins_cache));
for (const COutPoint& removed : vNoSpendsRemaining)
- ::ChainstateActive().CoinsTip().Uncache(removed);
+ coins_cache.Uncache(removed);
}
-static bool IsCurrentForFeeEstimation() EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
AssertLockHeld(cs_main);
- if (::ChainstateActive().IsInitialBlockDownload())
+ assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
+ if (active_chainstate.IsInitialBlockDownload())
return false;
- if (::ChainActive().Tip()->GetBlockTime() < count_seconds(GetTime<std::chrono::seconds>() - MAX_FEE_ESTIMATION_TIP_AGE))
+ if (active_chainstate.m_chain.Tip()->GetBlockTime() < count_seconds(GetTime<std::chrono::seconds>() - MAX_FEE_ESTIMATION_TIP_AGE))
return false;
- if (::ChainActive().Height() < pindexBestHeader->nHeight - 1)
+ if (active_chainstate.m_chain.Height() < pindexBestHeader->nHeight - 1)
return false;
return true;
}
@@ -366,10 +378,11 @@ static bool IsCurrentForFeeEstimation() EXCLUSIVE_LOCKS_REQUIRED(cs_main)
* and instead just erase from the mempool as needed.
*/
-static void UpdateMempoolForReorg(CTxMemPool& mempool, DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, mempool.cs)
+static void UpdateMempoolForReorg(CChainState& active_chainstate, CTxMemPool& mempool, DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, mempool.cs)
{
AssertLockHeld(cs_main);
AssertLockHeld(mempool.cs);
+ assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
std::vector<uint256> vHashUpdate;
// disconnectpool's insertion_order index sorts the entries from
// oldest to newest, but the oldest entry will be the last tx from the
@@ -381,7 +394,7 @@ static void UpdateMempoolForReorg(CTxMemPool& mempool, DisconnectedBlockTransact
while (it != disconnectpool.queuedTx.get<insertion_order>().rend()) {
// ignore validation errors in resurrected transactions
if (!fAddToMempool || (*it)->IsCoinBase() ||
- AcceptToMemoryPool(mempool, *it, true /* bypass_limits */).m_result_type != MempoolAcceptResult::ResultType::VALID) {
+ AcceptToMemoryPool(active_chainstate, mempool, *it, true /* bypass_limits */).m_result_type != MempoolAcceptResult::ResultType::VALID) {
// If the transaction doesn't make it in to the mempool, remove any
// transactions that depend on it (which would now be orphans).
mempool.removeRecursive(**it, MemPoolRemovalReason::REORG);
@@ -399,9 +412,9 @@ static void UpdateMempoolForReorg(CTxMemPool& mempool, DisconnectedBlockTransact
mempool.UpdateTransactionsFromBlock(vHashUpdate);
// We also need to remove any now-immature transactions
- mempool.removeForReorg(&::ChainstateActive().CoinsTip(), ::ChainActive().Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
+ mempool.removeForReorg(active_chainstate, STANDARD_LOCKTIME_VERIFY_FLAGS);
// Re-limit mempool size, in case we added any transactions
- LimitMempoolSize(mempool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
+ LimitMempoolSize(mempool, active_chainstate.CoinsTip(), gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
}
/**
@@ -411,7 +424,7 @@ static void UpdateMempoolForReorg(CTxMemPool& mempool, DisconnectedBlockTransact
* */
static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationState& state,
const CCoinsViewCache& view, const CTxMemPool& pool,
- unsigned int flags, PrecomputedTransactionData& txdata)
+ unsigned int flags, PrecomputedTransactionData& txdata, CCoinsViewCache& coins_tip)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs)
{
AssertLockHeld(cs_main);
@@ -436,7 +449,8 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationS
assert(txFrom->vout.size() > txin.prevout.n);
assert(txFrom->vout[txin.prevout.n] == coin.out);
} else {
- const Coin& coinFromUTXOSet = ::ChainstateActive().CoinsTip().AccessCoin(txin.prevout);
+ assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(coins_tip));
+ const Coin& coinFromUTXOSet = coins_tip.AccessCoin(txin.prevout);
assert(!coinFromUTXOSet.IsSpent());
assert(coinFromUTXOSet.out == coin.out);
}
@@ -451,11 +465,13 @@ namespace {
class MemPoolAccept
{
public:
- explicit MemPoolAccept(CTxMemPool& mempool) : m_pool(mempool), m_view(&m_dummy), m_viewmempool(&::ChainstateActive().CoinsTip(), m_pool),
+ explicit MemPoolAccept(CTxMemPool& mempool, CChainState& active_chainstate) : m_pool(mempool), m_view(&m_dummy), m_viewmempool(&active_chainstate.CoinsTip(), m_pool), m_active_chainstate(active_chainstate),
m_limit_ancestors(gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT)),
m_limit_ancestor_size(gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000),
m_limit_descendants(gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)),
- m_limit_descendant_size(gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000) {}
+ m_limit_descendant_size(gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000) {
+ assert(std::addressof(::ChainstateActive()) == std::addressof(m_active_chainstate));
+ }
// We put the arguments we're handed into a struct, so we can pass them
// around easier.
@@ -540,6 +556,8 @@ private:
CCoinsViewMemPool m_viewmempool;
CCoinsView m_dummy;
+ CChainState& m_active_chainstate;
+
// The package limits in effect at the time of invocation.
const size_t m_limit_ancestors;
const size_t m_limit_ancestor_size;
@@ -594,7 +612,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Only accept nLockTime-using transactions that can be mined in the next
// block; we don't want our mempool filled up with transactions that can't
// be mined yet.
- if (!CheckFinalTx(tx, STANDARD_LOCKTIME_VERIFY_FLAGS))
+ assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain));
+ if (!CheckFinalTx(m_active_chainstate.m_chain.Tip(), tx, STANDARD_LOCKTIME_VERIFY_FLAGS))
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final");
// is it already in the memory pool?
@@ -642,7 +661,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
LockPoints lp;
m_view.SetBackend(m_viewmempool);
- const CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip();
+ assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(m_active_chainstate.CoinsTip()));
+ const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip();
// do all inputs exist?
for (const CTxIn& txin : tx.vin) {
if (!coins_cache.HaveCoinInCache(txin.prevout)) {
@@ -678,16 +698,19 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// be mined yet.
// Must keep pool.cs for this unless we change CheckSequenceLocks to take a
// CoinsViewCache instead of create its own
- if (!CheckSequenceLocks(m_pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
+ assert(std::addressof(::ChainstateActive()) == std::addressof(m_active_chainstate));
+ if (!CheckSequenceLocks(m_active_chainstate, m_pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final");
- if (!Consensus::CheckTxInputs(tx, state, m_view, g_chainman.m_blockman.GetSpendHeight(m_view), ws.m_base_fees)) {
+ assert(std::addressof(g_chainman.m_blockman) == std::addressof(m_active_chainstate.m_blockman));
+ if (!Consensus::CheckTxInputs(tx, state, m_view, m_active_chainstate.m_blockman.GetSpendHeight(m_view), ws.m_base_fees)) {
return false; // state filled in by CheckTxInputs
}
// Check for non-standard pay-to-script-hash in inputs
const auto& params = args.m_chainparams.GetConsensus();
- auto taproot_state = VersionBitsState(::ChainActive().Tip(), params, Consensus::DEPLOYMENT_TAPROOT, versionbitscache);
+ assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain));
+ auto taproot_state = VersionBitsState(m_active_chainstate.m_chain.Tip(), params, Consensus::DEPLOYMENT_TAPROOT, versionbitscache);
if (fRequireStandard && !AreInputsStandard(tx, m_view, taproot_state == ThresholdState::ACTIVE)) {
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
}
@@ -713,7 +736,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
}
}
- entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, ::ChainActive().Height(),
+ assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain));
+ entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(),
fSpendsCoinbase, nSigOpsCost, lp));
unsigned int nSize = entry->GetTxSize();
@@ -965,8 +989,10 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws, P
// There is a similar check in CreateNewBlock() to prevent creating
// invalid blocks (using TestBlockValidity), however allowing such
// transactions into the mempool can be exploited as a DoS attack.
- unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(::ChainActive().Tip(), chainparams.GetConsensus());
- if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, txdata)) {
+ assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain));
+ unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(m_active_chainstate.m_chain.Tip(), chainparams.GetConsensus());
+ assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(m_active_chainstate.CoinsTip()));
+ if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, txdata, m_active_chainstate.CoinsTip())) {
return error("%s: BUG! PLEASE REPORT THIS! CheckInputScripts failed against latest-block but not STANDARD flags %s, %s",
__func__, hash.ToString(), state.ToString());
}
@@ -1006,14 +1032,16 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
// - it's not being re-added during a reorg which bypasses typical mempool fee limits
// - the node is not behind
// - the transaction is not dependent on any other transactions in the mempool
- bool validForFeeEstimation = !fReplacementTransaction && !bypass_limits && IsCurrentForFeeEstimation() && m_pool.HasNoInputsOf(tx);
+ assert(std::addressof(::ChainstateActive()) == std::addressof(m_active_chainstate));
+ bool validForFeeEstimation = !fReplacementTransaction && !bypass_limits && IsCurrentForFeeEstimation(m_active_chainstate) && m_pool.HasNoInputsOf(tx);
// Store transaction in memory
m_pool.addUnchecked(*entry, setAncestors, validForFeeEstimation);
// trim mempool and check if tx was trimmed
if (!bypass_limits) {
- LimitMempoolSize(m_pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
+ assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(m_active_chainstate.CoinsTip()));
+ LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip(), gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
if (!m_pool.exists(hash))
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full");
}
@@ -1055,6 +1083,7 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
/** (try to) add transaction to memory pool with a specified acceptance time **/
static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool,
+ CChainState& active_chainstate,
const CTransactionRef &tx, int64_t nAcceptTime,
bool bypass_limits, bool test_accept)
EXCLUSIVE_LOCKS_REQUIRED(cs_main)
@@ -1062,7 +1091,8 @@ static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainp
std::vector<COutPoint> coins_to_uncache;
MemPoolAccept::ATMPArgs args { chainparams, nAcceptTime, bypass_limits, coins_to_uncache, test_accept };
- const MempoolAcceptResult result = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
+ assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
+ const MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransaction(tx, args);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
// Remove coins that were not present in the coins cache before calling ATMPW;
// this is to prevent memory DoS in case we receive a large number of
@@ -1070,17 +1100,19 @@ static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainp
// (`CCoinsViewCache::cacheCoins`).
for (const COutPoint& hashTx : coins_to_uncache)
- ::ChainstateActive().CoinsTip().Uncache(hashTx);
+ active_chainstate.CoinsTip().Uncache(hashTx);
}
// After we've (potentially) uncached entries, ensure our coins cache is still within its size limits
BlockValidationState state_dummy;
- ::ChainstateActive().FlushStateToDisk(chainparams, state_dummy, FlushStateMode::PERIODIC);
+ active_chainstate.FlushStateToDisk(chainparams, state_dummy, FlushStateMode::PERIODIC);
return result;
}
-MempoolAcceptResult AcceptToMemoryPool(CTxMemPool& pool, const CTransactionRef &tx, bool bypass_limits, bool test_accept)
+MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPool& pool, const CTransactionRef& tx,
+ bool bypass_limits, bool test_accept)
{
- return AcceptToMemoryPoolWithTime(Params(), pool, tx, GetTime(), bypass_limits, test_accept);
+ assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
+ return AcceptToMemoryPoolWithTime(Params(), pool, active_chainstate, tx, GetTime(), bypass_limits, test_accept);
}
CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock)
@@ -2239,17 +2271,25 @@ bool CChainState::FlushStateToDisk(
{
bool fFlushForPrune = false;
bool fDoFullFlush = false;
+
CoinsCacheSizeState cache_state = GetCoinsCacheSizeState(&m_mempool);
LOCK(cs_LastBlockFile);
if (fPruneMode && (fCheckForPruning || nManualPruneHeight > 0) && !fReindex) {
+ // make sure we don't prune above the blockfilterindexes bestblocks
+ // pruning is height-based
+ int last_prune = m_chain.Height(); // last height we can prune
+ ForEachBlockFilterIndex([&](BlockFilterIndex& index) {
+ last_prune = std::max(1, std::min(last_prune, index.GetSummary().best_block_height));
+ });
+
if (nManualPruneHeight > 0) {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH);
- m_blockman.FindFilesToPruneManual(setFilesToPrune, nManualPruneHeight, m_chain.Height());
+ m_blockman.FindFilesToPruneManual(setFilesToPrune, std::min(last_prune, nManualPruneHeight), m_chain.Height());
} else {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH);
- m_blockman.FindFilesToPrune(setFilesToPrune, chainparams.PruneAfterHeight(), m_chain.Height(), IsInitialBlockDownload());
+ m_blockman.FindFilesToPrune(setFilesToPrune, chainparams.PruneAfterHeight(), m_chain.Height(), last_prune, IsInitialBlockDownload());
fCheckForPruning = false;
}
if (!setFilesToPrune.empty()) {
@@ -2683,7 +2723,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai
if (!DisconnectTip(state, chainparams, &disconnectpool)) {
// This is likely a fatal error, but keep the mempool consistent,
// just in case. Only remove from the mempool in this case.
- UpdateMempoolForReorg(m_mempool, disconnectpool, false);
+ UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, false);
// If we're unable to disconnect a block during normal operation,
// then that is a failure of our local system -- we should abort
@@ -2727,7 +2767,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai
// A system error occurred (disk space, database error, ...).
// Make the mempool consistent with the current tip, just in case
// any observers try to use it before shutdown.
- UpdateMempoolForReorg(m_mempool, disconnectpool, false);
+ UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, false);
return false;
}
} else {
@@ -2744,7 +2784,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai
if (fBlocksDisconnected) {
// If any blocks were disconnected, disconnectpool may be non empty. Add
// any disconnected transactions back to the mempool.
- UpdateMempoolForReorg(m_mempool, disconnectpool, true);
+ UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, true);
}
m_mempool.check(&CoinsTip());
@@ -2981,7 +3021,7 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam
// transactions back to the mempool if disconnecting was successful,
// and we're not doing a very deep invalidation (in which case
// keeping the mempool up to date is probably futile anyway).
- UpdateMempoolForReorg(m_mempool, disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret);
+ UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret);
if (!ret) return false;
assert(invalid_walk_tip->pprev == m_chain.Tip());
@@ -3193,7 +3233,7 @@ static bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize, unsigned int n
bool finalize_undo = false;
if (!fKnown) {
- while (vinfoBlockFile[nFile].nSize + nAddSize >= MAX_BLOCKFILE_SIZE) {
+ while (vinfoBlockFile[nFile].nSize + nAddSize >= (gArgs.GetBoolArg("-fastprune", false) ? 0x10000 /* 64kb */ : MAX_BLOCKFILE_SIZE)) {
// when the undo file is keeping up with the block file, we want to flush it explicitly
// when it is lagging behind (more blocks arrive than are being connected), we let the
// undo block write case handle it
@@ -3924,7 +3964,7 @@ void PruneBlockFilesManual(int nManualPruneHeight)
}
}
-void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, bool is_ibd)
+void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, int prune_height, bool is_ibd)
{
LOCK2(cs_main, cs_LastBlockFile);
if (chain_tip_height < 0 || nPruneTarget == 0) {
@@ -3934,7 +3974,7 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr
return;
}
- unsigned int nLastBlockWeCanPrune = chain_tip_height - MIN_BLOCKS_TO_KEEP;
+ unsigned int nLastBlockWeCanPrune = std::min(prune_height, chain_tip_height - static_cast<int>(MIN_BLOCKS_TO_KEEP));
uint64_t nCurrentUsage = CalculateCurrentUsage();
// We don't check to prune until after we've allocated new space for files
// So we should leave a buffer under our target to account for another allocation
@@ -3985,7 +4025,7 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr
static FlatFileSeq BlockFileSeq()
{
- return FlatFileSeq(GetBlocksDir(), "blk", BLOCKFILE_CHUNK_SIZE);
+ return FlatFileSeq(GetBlocksDir(), "blk", gArgs.GetBoolArg("-fastprune", false) ? 0x4000 /* 16kb */ : BLOCKFILE_CHUNK_SIZE);
}
static FlatFileSeq UndoFileSeq()
@@ -4151,7 +4191,8 @@ bool static LoadBlockIndexDB(ChainstateManager& chainman, const CChainParams& ch
void CChainState::LoadMempool(const ArgsManager& args)
{
if (args.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
- ::LoadMempool(m_mempool);
+ assert(std::addressof(::ChainstateActive()) == std::addressof(*this));
+ ::LoadMempool(m_mempool, *this);
}
m_mempool.SetIsLoaded(!ShutdownRequested());
}
@@ -4981,7 +5022,7 @@ int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::D
static const uint64_t MEMPOOL_DUMP_VERSION = 1;
-bool LoadMempool(CTxMemPool& pool)
+bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate)
{
const CChainParams& chainparams = Params();
int64_t nExpiryTimeout = gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60;
@@ -5021,7 +5062,8 @@ bool LoadMempool(CTxMemPool& pool)
}
if (nTime > nNow - nExpiryTimeout) {
LOCK(cs_main);
- if (AcceptToMemoryPoolWithTime(chainparams, pool, tx, nTime, false /* bypass_limits */,
+ assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
+ if (AcceptToMemoryPoolWithTime(chainparams, pool, active_chainstate, tx, nTime, false /* bypass_limits */,
false /* test_accept */).m_result_type == MempoolAcceptResult::ResultType::VALID) {
++count;
} else {
@@ -5147,7 +5189,8 @@ double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex *pin
Optional<uint256> ChainstateManager::SnapshotBlockhash() const {
LOCK(::cs_main);
- if (m_active_chainstate != nullptr) {
+ if (m_active_chainstate != nullptr &&
+ !m_active_chainstate->m_from_snapshot_blockhash.IsNull()) {
// If a snapshot chainstate exists, it will always be our active.
return m_active_chainstate->m_from_snapshot_blockhash;
}
@@ -5192,6 +5235,295 @@ CChainState& ChainstateManager::InitializeChainstate(CTxMemPool& mempool, const
return *to_modify;
}
+const AssumeutxoData* ExpectedAssumeutxo(
+ const int height, const CChainParams& chainparams)
+{
+ const MapAssumeutxo& valid_assumeutxos_map = chainparams.Assumeutxo();
+ const auto assumeutxo_found = valid_assumeutxos_map.find(height);
+
+ if (assumeutxo_found != valid_assumeutxos_map.end()) {
+ return &assumeutxo_found->second;
+ }
+ return nullptr;
+}
+
+bool ChainstateManager::ActivateSnapshot(
+ CAutoFile& coins_file,
+ const SnapshotMetadata& metadata,
+ bool in_memory)
+{
+ uint256 base_blockhash = metadata.m_base_blockhash;
+
+ if (this->SnapshotBlockhash()) {
+ LogPrintf("[snapshot] can't activate a snapshot-based chainstate more than once\n");
+ return false;
+ }
+
+ int64_t current_coinsdb_cache_size{0};
+ int64_t current_coinstip_cache_size{0};
+
+ // Cache percentages to allocate to each chainstate.
+ //
+ // These particular percentages don't matter so much since they will only be
+ // relevant during snapshot activation; caches are rebalanced at the conclusion of
+ // this function. We want to give (essentially) all available cache capacity to the
+ // snapshot to aid the bulk load later in this function.
+ static constexpr double IBD_CACHE_PERC = 0.01;
+ static constexpr double SNAPSHOT_CACHE_PERC = 0.99;
+
+ {
+ LOCK(::cs_main);
+ // Resize the coins caches to ensure we're not exceeding memory limits.
+ //
+ // Allocate the majority of the cache to the incoming snapshot chainstate, since
+ // (optimistically) getting to its tip will be the top priority. We'll need to call
+ // `MaybeRebalanceCaches()` once we're done with this function to ensure
+ // the right allocation (including the possibility that no snapshot was activated
+ // and that we should restore the active chainstate caches to their original size).
+ //
+ current_coinsdb_cache_size = this->ActiveChainstate().m_coinsdb_cache_size_bytes;
+ current_coinstip_cache_size = this->ActiveChainstate().m_coinstip_cache_size_bytes;
+
+ // Temporarily resize the active coins cache to make room for the newly-created
+ // snapshot chain.
+ this->ActiveChainstate().ResizeCoinsCaches(
+ static_cast<size_t>(current_coinstip_cache_size * IBD_CACHE_PERC),
+ static_cast<size_t>(current_coinsdb_cache_size * IBD_CACHE_PERC));
+ }
+
+ auto snapshot_chainstate = WITH_LOCK(::cs_main, return MakeUnique<CChainState>(
+ this->ActiveChainstate().m_mempool, m_blockman, base_blockhash));
+
+ {
+ LOCK(::cs_main);
+ snapshot_chainstate->InitCoinsDB(
+ static_cast<size_t>(current_coinsdb_cache_size * SNAPSHOT_CACHE_PERC),
+ in_memory, false, "chainstate");
+ snapshot_chainstate->InitCoinsCache(
+ static_cast<size_t>(current_coinstip_cache_size * SNAPSHOT_CACHE_PERC));
+ }
+
+ const bool snapshot_ok = this->PopulateAndValidateSnapshot(
+ *snapshot_chainstate, coins_file, metadata);
+
+ if (!snapshot_ok) {
+ WITH_LOCK(::cs_main, this->MaybeRebalanceCaches());
+ return false;
+ }
+
+ {
+ LOCK(::cs_main);
+ assert(!m_snapshot_chainstate);
+ m_snapshot_chainstate.swap(snapshot_chainstate);
+ const bool chaintip_loaded = m_snapshot_chainstate->LoadChainTip(::Params());
+ assert(chaintip_loaded);
+
+ m_active_chainstate = m_snapshot_chainstate.get();
+
+ LogPrintf("[snapshot] successfully activated snapshot %s\n", base_blockhash.ToString());
+ LogPrintf("[snapshot] (%.2f MB)\n",
+ m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() / (1000 * 1000));
+
+ this->MaybeRebalanceCaches();
+ }
+ return true;
+}
+
+bool ChainstateManager::PopulateAndValidateSnapshot(
+ CChainState& snapshot_chainstate,
+ CAutoFile& coins_file,
+ const SnapshotMetadata& metadata)
+{
+ // It's okay to release cs_main before we're done using `coins_cache` because we know
+ // that nothing else will be referencing the newly created snapshot_chainstate yet.
+ CCoinsViewCache& coins_cache = *WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsTip());
+
+ uint256 base_blockhash = metadata.m_base_blockhash;
+
+ COutPoint outpoint;
+ Coin coin;
+ const uint64_t coins_count = metadata.m_coins_count;
+ uint64_t coins_left = metadata.m_coins_count;
+
+ LogPrintf("[snapshot] loading coins from snapshot %s\n", base_blockhash.ToString());
+ int64_t flush_now{0};
+ int64_t coins_processed{0};
+
+ while (coins_left > 0) {
+ try {
+ coins_file >> outpoint;
+ } catch (const std::ios_base::failure&) {
+ LogPrintf("[snapshot] bad snapshot - no coins left after deserializing %d coins\n",
+ coins_count - coins_left);
+ return false;
+ }
+ coins_file >> coin;
+ coins_cache.EmplaceCoinInternalDANGER(std::move(outpoint), std::move(coin));
+
+ --coins_left;
+ ++coins_processed;
+
+ if (coins_processed % 1000000 == 0) {
+ LogPrintf("[snapshot] %d coins loaded (%.2f%%, %.2f MB)\n",
+ coins_processed,
+ static_cast<float>(coins_processed) * 100 / static_cast<float>(coins_count),
+ coins_cache.DynamicMemoryUsage() / (1000 * 1000));
+ }
+
+ // Batch write and flush (if we need to) every so often.
+ //
+ // If our average Coin size is roughly 41 bytes, checking every 120,000 coins
+ // means <5MB of memory imprecision.
+ if (coins_processed % 120000 == 0) {
+ if (ShutdownRequested()) {
+ return false;
+ }
+
+ const auto snapshot_cache_state = WITH_LOCK(::cs_main,
+ return snapshot_chainstate.GetCoinsCacheSizeState(&snapshot_chainstate.m_mempool));
+
+ if (snapshot_cache_state >=
+ CoinsCacheSizeState::CRITICAL) {
+ LogPrintf("[snapshot] flushing coins cache (%.2f MB)... ", /* Continued */
+ coins_cache.DynamicMemoryUsage() / (1000 * 1000));
+ flush_now = GetTimeMillis();
+
+ // This is a hack - we don't know what the actual best block is, but that
+ // doesn't matter for the purposes of flushing the cache here. We'll set this
+ // to its correct value (`base_blockhash`) below after the coins are loaded.
+ coins_cache.SetBestBlock(GetRandHash());
+
+ coins_cache.Flush();
+ LogPrintf("done (%.2fms)\n", GetTimeMillis() - flush_now);
+ }
+ }
+ }
+
+ // Important that we set this. This and the coins_cache accesses above are
+ // sort of a layer violation, but either we reach into the innards of
+ // CCoinsViewCache here or we have to invert some of the CChainState to
+ // embed them in a snapshot-activation-specific CCoinsViewCache bulk load
+ // method.
+ coins_cache.SetBestBlock(base_blockhash);
+
+ bool out_of_coins{false};
+ try {
+ coins_file >> outpoint;
+ } catch (const std::ios_base::failure&) {
+ // We expect an exception since we should be out of coins.
+ out_of_coins = true;
+ }
+ if (!out_of_coins) {
+ LogPrintf("[snapshot] bad snapshot - coins left over after deserializing %d coins\n",
+ coins_count);
+ return false;
+ }
+
+ LogPrintf("[snapshot] loaded %d (%.2f MB) coins from snapshot %s\n",
+ coins_count,
+ coins_cache.DynamicMemoryUsage() / (1000 * 1000),
+ base_blockhash.ToString());
+
+ LogPrintf("[snapshot] flushing snapshot chainstate to disk\n");
+ // No need to acquire cs_main since this chainstate isn't being used yet.
+ coins_cache.Flush(); // TODO: if #17487 is merged, add erase=false here for better performance.
+
+ assert(coins_cache.GetBestBlock() == base_blockhash);
+
+ CCoinsStats stats;
+ auto breakpoint_fnc = [] { /* TODO insert breakpoint here? */ };
+
+ // As above, okay to immediately release cs_main here since no other context knows
+ // about the snapshot_chainstate.
+ CCoinsViewDB* snapshot_coinsdb = WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsDB());
+
+ if (!GetUTXOStats(snapshot_coinsdb, stats, CoinStatsHashType::HASH_SERIALIZED, breakpoint_fnc)) {
+ LogPrintf("[snapshot] failed to generate coins stats\n");
+ return false;
+ }
+
+ // Ensure that the base blockhash appears in the known chain of valid headers. We're willing to
+ // wait a bit here because the snapshot may have been loaded on startup, before we've
+ // received headers from the network.
+
+ int max_secs_to_wait_for_headers = 60 * 10;
+ CBlockIndex* snapshot_start_block = nullptr;
+
+ while (max_secs_to_wait_for_headers > 0) {
+ snapshot_start_block = WITH_LOCK(::cs_main,
+ return m_blockman.LookupBlockIndex(base_blockhash));
+ --max_secs_to_wait_for_headers;
+
+ if (!snapshot_start_block) {
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ } else {
+ break;
+ }
+ }
+
+ if (snapshot_start_block == nullptr) {
+ LogPrintf("[snapshot] timed out waiting for snapshot start blockheader %s\n",
+ base_blockhash.ToString());
+ return false;
+ }
+
+ // Assert that the deserialized chainstate contents match the expected assumeutxo value.
+
+ int base_height = snapshot_start_block->nHeight;
+ auto maybe_au_data = ExpectedAssumeutxo(base_height, ::Params());
+
+ if (!maybe_au_data) {
+ LogPrintf("[snapshot] assumeutxo height in snapshot metadata not recognized " /* Continued */
+ "(%d) - refusing to load snapshot\n", base_height);
+ return false;
+ }
+
+ const AssumeutxoData& au_data = *maybe_au_data;
+
+ if (stats.hashSerialized != au_data.hash_serialized) {
+ LogPrintf("[snapshot] bad snapshot content hash: expected %s, got %s\n",
+ au_data.hash_serialized.ToString(), stats.hashSerialized.ToString());
+ return false;
+ }
+
+ snapshot_chainstate.m_chain.SetTip(snapshot_start_block);
+
+ // The remainder of this function requires modifying data protected by cs_main.
+ LOCK(::cs_main);
+
+ // Fake various pieces of CBlockIndex state:
+ //
+ // - nChainTx: so that we accurately report IBD-to-tip progress
+ // - nTx: so that LoadBlockIndex() loads assumed-valid CBlockIndex entries
+ // (among other things)
+ // - nStatus & BLOCK_OPT_WITNESS: so that RewindBlockIndex() doesn't zealously
+ // unwind the assumed-valid chain.
+ //
+ CBlockIndex* index = nullptr;
+ for (int i = 0; i <= snapshot_chainstate.m_chain.Height(); ++i) {
+ index = snapshot_chainstate.m_chain[i];
+
+ if (!index->nTx) {
+ index->nTx = 1;
+ }
+ index->nChainTx = index->pprev ? index->pprev->nChainTx + index->nTx : 1;
+
+ // We need to fake this flag so that CChainState::RewindBlockIndex()
+ // won't try to rewind the entire assumed-valid chain on startup.
+ if (index->pprev && ::IsWitnessEnabled(index->pprev, ::Params().GetConsensus())) {
+ index->nStatus |= BLOCK_OPT_WITNESS;
+ }
+ }
+
+ assert(index);
+ index->nChainTx = metadata.m_nchaintx;
+ snapshot_chainstate.setBlockIndexCandidates.insert(snapshot_start_block);
+
+ LogPrintf("[snapshot] validated snapshot (%.2f MB)\n",
+ coins_cache.DynamicMemoryUsage() / (1000 * 1000));
+ return true;
+}
+
CChainState& ChainstateManager::ActiveChainstate() const
{
LOCK(::cs_main);
diff --git a/src/validation.h b/src/validation.h
index 238d6009b4..4e4bdbea54 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -11,10 +11,12 @@
#endif
#include <amount.h>
+#include <attributes.h>
#include <coins.h>
#include <consensus/validation.h>
#include <crypto/common.h> // for ReadLE64
#include <fs.h>
+#include <node/utxo_snapshot.h>
#include <optional.h>
#include <policy/feerate.h>
#include <protocol.h> // For CMessageHeader::MessageStartChars
@@ -53,6 +55,7 @@ struct ChainTxData;
struct DisconnectedBlockTransactions;
struct PrecomputedTransactionData;
struct LockPoints;
+struct AssumeutxoData;
/** Default for -minrelaytxfee, minimum relay fee for transactions */
static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000;
@@ -220,7 +223,7 @@ struct MempoolAcceptResult {
* @param[in] bypass_limits When true, don't enforce mempool fee limits.
* @param[in] test_accept When true, run validation checks but don't submit to mempool.
*/
-MempoolAcceptResult AcceptToMemoryPool(CTxMemPool& pool, const CTransactionRef& tx,
+MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPool& pool, const CTransactionRef& tx,
bool bypass_limits, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Get the BIP9 state for a given deployment at the current tip. */
@@ -245,12 +248,12 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight);
*
* See consensus/consensus.h for flag definitions.
*/
-bool CheckFinalTx(const CTransaction &tx, int flags = -1) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, int flags = -1) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Test whether the LockPoints height and time are still valid on the current chain
*/
-bool TestLockPointValidity(const LockPoints* lp) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Check if transaction will be BIP 68 final in the next block to be created.
@@ -263,7 +266,12 @@ bool TestLockPointValidity(const LockPoints* lp) EXCLUSIVE_LOCKS_REQUIRED(cs_mai
*
* See consensus/consensus.h for flag definitions.
*/
-bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs);
+bool CheckSequenceLocks(CChainState& active_chainstate,
+ const CTxMemPool& pool,
+ const CTransaction& tx,
+ int flags,
+ LockPoints* lp = nullptr,
+ bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs);
/**
* Closure representing one script verification
@@ -396,7 +404,7 @@ private:
*
* @param[out] setFilesToPrune The set of file indices that can be unlinked will be returned
*/
- void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, bool is_ibd);
+ void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, int prune_height, bool is_ibd);
public:
BlockMap m_block_index GUARDED_BY(cs_main);
@@ -830,9 +838,7 @@ private:
//! using this pointer (e.g. net_processing).
//!
//! Once this pointer is set to a corresponding chainstate, it will not
- //! be reset until init.cpp:Shutdown(). This means it is safe to acquire
- //! the contents of this pointer with ::cs_main held, release the lock,
- //! and then use the reference without concern of it being deconstructed.
+ //! be reset until init.cpp:Shutdown().
//!
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
@@ -843,9 +849,7 @@ private:
//! non-null, it is always our active chainstate.
//!
//! Once this pointer is set to a corresponding chainstate, it will not
- //! be reset until init.cpp:Shutdown(). This means it is safe to acquire
- //! the contents of this pointer with ::cs_main held, release the lock,
- //! and then use the reference without concern of it being deconstructed.
+ //! be reset until init.cpp:Shutdown().
//!
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
@@ -856,9 +860,7 @@ private:
//! most-work chain.
//!
//! Once this pointer is set to a corresponding chainstate, it will not
- //! be reset until init.cpp:Shutdown(). This means it is safe to acquire
- //! the contents of this pointer with ::cs_main held, release the lock,
- //! and then use the reference without concern of it being deconstructed.
+ //! be reset until init.cpp:Shutdown().
//!
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
@@ -869,6 +871,12 @@ private:
//! by the background validation chainstate.
bool m_snapshot_validated{false};
+ //! Internal helper for ActivateSnapshot().
+ [[nodiscard]] bool PopulateAndValidateSnapshot(
+ CChainState& snapshot_chainstate,
+ CAutoFile& coins_file,
+ const SnapshotMetadata& metadata);
+
// For access to m_active_chainstate.
friend CChainState& ChainstateActive();
friend CChain& ChainActive();
@@ -899,6 +907,22 @@ public:
//! Get all chainstates currently being used.
std::vector<CChainState*> GetAll();
+ //! Construct and activate a Chainstate on the basis of UTXO snapshot data.
+ //!
+ //! Steps:
+ //!
+ //! - Initialize an unused CChainState.
+ //! - Load its `CoinsViews` contents from `coins_file`.
+ //! - Verify that the hash of the resulting coinsdb matches the expected hash
+ //! per assumeutxo chain parameters.
+ //! - Wait for our headers chain to include the base block of the snapshot.
+ //! - "Fast forward" the tip of the new chainstate to the base of the snapshot,
+ //! faking nTx* block index data along the way.
+ //! - Move the new chainstate to `m_snapshot_chainstate` and make it our
+ //! ChainstateActive().
+ [[nodiscard]] bool ActivateSnapshot(
+ CAutoFile& coins_file, const SnapshotMetadata& metadata, bool in_memory);
+
//! The most-work chain.
CChainState& ActiveChainstate() const;
CChain& ActiveChain() const { return ActiveChainstate().m_chain; }
@@ -1005,7 +1029,7 @@ CBlockFileInfo* GetBlockFileInfo(size_t n);
bool DumpMempool(const CTxMemPool& pool);
/** Load the mempool from disk. */
-bool LoadMempool(CTxMemPool& pool);
+bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate);
//! Check whether the block associated with this index entry is pruned or not.
inline bool IsBlockPruned(const CBlockIndex* pblockindex)
@@ -1013,4 +1037,13 @@ inline bool IsBlockPruned(const CBlockIndex* pblockindex)
return (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0);
}
+/**
+ * Return the expected assumeutxo value for a given height, if one exists.
+ *
+ * @param height[in] Get the assumeutxo value for this height.
+ *
+ * @returns empty if no assumeutxo configuration exists for the given height.
+ */
+const AssumeutxoData* ExpectedAssumeutxo(const int height, const CChainParams& params);
+
#endif // BITCOIN_VALIDATION_H
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 46de273d63..53232db6bc 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -400,6 +400,12 @@ UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std
{
EnsureWalletIsUnlocked(pwallet);
+ // This function is only used by sendtoaddress and sendmany.
+ // This should always try to sign, if we don't have private keys, don't try to do anything here.
+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
+ }
+
// Shuffle recipient list
std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());
@@ -409,7 +415,7 @@ UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std
bilingual_str error;
CTransactionRef tx;
FeeCalculation fee_calc_out;
- bool fCreated = pwallet->CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ const bool fCreated = pwallet->CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true);
if (!fCreated) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
}
@@ -3787,6 +3793,7 @@ RPCHelpMan getaddressinfo()
{RPCResult::Type::BOOL, "iswatchonly", "If the address is watchonly."},
{RPCResult::Type::BOOL, "solvable", "If we know how to spend coins sent to this address, ignoring the possible lack of private keys."},
{RPCResult::Type::STR, "desc", /* optional */ true, "A descriptor for spending coins sent to this address (only when solvable)."},
+ {RPCResult::Type::STR, "parent_desc", /* optional */ true, "The descriptor used to derive this address if this is a descriptor wallet"},
{RPCResult::Type::BOOL, "isscript", "If the key is a script."},
{RPCResult::Type::BOOL, "ischange", "If the address was used for change output."},
{RPCResult::Type::BOOL, "iswitness", "If the address is a witness address."},
@@ -3862,6 +3869,14 @@ RPCHelpMan getaddressinfo()
ret.pushKV("desc", InferDescriptor(scriptPubKey, *provider)->ToString());
}
+ DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(pwallet->GetScriptPubKeyMan(scriptPubKey));
+ if (desc_spk_man) {
+ std::string desc_str;
+ if (desc_spk_man->GetDescriptorString(desc_str, false)) {
+ ret.pushKV("parent_desc", desc_str);
+ }
+ }
+
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
UniValue detail = DescribeWalletAddress(pwallet, dest);
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 55110f30d9..4630603f8e 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -2264,3 +2264,16 @@ const std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() const
}
return script_pub_keys;
}
+
+bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, bool priv) const
+{
+ LOCK(cs_desc_man);
+ if (m_storage.IsLocked()) {
+ return false;
+ }
+
+ FlatSigningProvider provider;
+ provider.keys = GetKeys();
+
+ return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, priv);
+}
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 8f6b69bc78..51283e791d 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -616,6 +616,8 @@ public:
const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
const std::vector<CScript> GetScriptPubKeys() const;
+
+ bool GetDescriptorString(std::string& out, bool priv) const;
};
#endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H