From ac7e03e93330ad4226fe9f624feff000b84ef388 Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 5 Jun 2025 13:59:19 -0500 Subject: [PATCH 001/244] Enable BIP157 block filters index and serving by default This change enables compact block filters (BIP157) functionality by default: - Sets DEFAULT_BLOCKFILTERINDEX to "basic" instead of "0" - Sets DEFAULT_PEERBLOCKFILTERS to true instead of false This improves privacy for light clients and enables better pruned node support. --- src/net_processing.h | 2 +- src/validation.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/net_processing.h b/src/net_processing.h index be19716117bd..833a029af68e 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -33,7 +33,7 @@ static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS_SIZE = 10; // this all /** Default number of orphan+recently-replaced txn to keep around for block reconstruction */ static const unsigned int DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN = 100; static const bool DEFAULT_PEERBLOOMFILTERS = true; -static const bool DEFAULT_PEERBLOCKFILTERS = false; +static const bool DEFAULT_PEERBLOCKFILTERS = true; /** Threshold for marking a node to be discouraged, e.g. disconnected and added to the discouragement filter. */ static const int DISCOURAGEMENT_THRESHOLD{100}; diff --git a/src/validation.h b/src/validation.h index 814a75d842aa..9d9a87f966b9 100644 --- a/src/validation.h +++ b/src/validation.h @@ -79,7 +79,7 @@ static const int64_t DEFAULT_MAX_TIP_AGE = 6 * 60 * 60; // ~144 blocks behind -> static const bool DEFAULT_CHECKPOINTS_ENABLED = true; static const bool DEFAULT_TXINDEX = true; static constexpr bool DEFAULT_COINSTATSINDEX{false}; -static const char* const DEFAULT_BLOCKFILTERINDEX = "0"; +static const char* const DEFAULT_BLOCKFILTERINDEX = "basic"; /** Default for -persistmempool */ static const bool DEFAULT_PERSIST_MEMPOOL = true; /** Default for -syncmempool */ From 0274cd2db4380f30572314eaa923b00f2cfa3356 Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 5 Jun 2025 14:01:11 -0500 Subject: [PATCH 002/244] docs: Add release notes for PR #6708 Documents the change to enable BIP157 block filters by default. --- doc/release-notes-6708.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 doc/release-notes-6708.md diff --git a/doc/release-notes-6708.md b/doc/release-notes-6708.md new file mode 100644 index 000000000000..e38714843784 --- /dev/null +++ b/doc/release-notes-6708.md @@ -0,0 +1,6 @@ +Updated settings +---------------- + +- BIP157 compact block filters are now enabled by default. This improves privacy for light clients + and enables better support for pruned nodes. The `-blockfilterindex` option now defaults to `basic` + instead of `0` (disabled), and the `-peerblockfilters` option now defaults to `true` instead of `false` (#6708). \ No newline at end of file From fbde721657a3e06c06ead8966a7229f1aa9935ce Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 5 Jun 2025 14:08:14 -0500 Subject: [PATCH 003/244] docs: Fix PR number in release notes - rename to 6711 Updates the release notes file name and PR reference from 6708 to 6711. --- doc/{release-notes-6708.md => release-notes-6711.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename doc/{release-notes-6708.md => release-notes-6711.md} (85%) diff --git a/doc/release-notes-6708.md b/doc/release-notes-6711.md similarity index 85% rename from doc/release-notes-6708.md rename to doc/release-notes-6711.md index e38714843784..3c4f12fe3877 100644 --- a/doc/release-notes-6708.md +++ b/doc/release-notes-6711.md @@ -3,4 +3,4 @@ Updated settings - BIP157 compact block filters are now enabled by default. This improves privacy for light clients and enables better support for pruned nodes. The `-blockfilterindex` option now defaults to `basic` - instead of `0` (disabled), and the `-peerblockfilters` option now defaults to `true` instead of `false` (#6708). \ No newline at end of file + instead of `0` (disabled), and the `-peerblockfilters` option now defaults to `true` instead of `false` (#6711). From ef2a1e3d2afbe6b4c291a0f2c2e75a927e89f36a Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 5 Jun 2025 15:03:11 -0500 Subject: [PATCH 004/244] Fix initialization error when using default blockfilterindex value When blockfilterindex defaults to "basic" but no explicit command line argument is provided, GetArgs("-blockfilterindex") returns an empty vector. This caused the validation logic to fail with "Cannot set -peerblockfilters without -blockfilterindex" error. Add check for empty names vector and use the default value when needed. --- src/init.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index 19f89b47c8db..a4de222857d7 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1143,7 +1143,11 @@ bool AppInitParameterInteraction(const ArgsManager& args) if (blockfilterindex_value == "" || blockfilterindex_value == "1") { g_enabled_filter_types = AllBlockFilterTypes(); } else if (blockfilterindex_value != "0") { - const std::vector names = args.GetArgs("-blockfilterindex"); + std::vector names = args.GetArgs("-blockfilterindex"); + if (names.empty()) { + // Use default value when no explicit -blockfilterindex was provided + names.push_back(DEFAULT_BLOCKFILTERINDEX); + } for (const auto& name : names) { BlockFilterType filter_type; if (!BlockFilterTypeByName(name, filter_type)) { From e6e9ff8c9d80bbfca22fbc8c43bdc56da524cdc9 Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 16 Jul 2025 02:20:53 -0500 Subject: [PATCH 005/244] test: Fix functional tests to work with new blockfilterindex defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three tests needed updates to work with the new defaults where -blockfilterindex=basic and -peerblockfilters=true: - p2p_blockfilters.py: Explicitly disable peerblockfilters for node 1 which doesn't serve compact filters - rpc_getblockfilter.py: Explicitly disable both options for nodes that should not have block filter index - rpc_misc.py: Explicitly disable both options when testing the absence of indices 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- test/functional/p2p_blockfilters.py | 4 ++-- test/functional/rpc_getblockfilter.py | 4 ++-- test/functional/rpc_misc.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py index 48c4881950bd..682084c14c3c 100755 --- a/test/functional/p2p_blockfilters.py +++ b/test/functional/p2p_blockfilters.py @@ -47,7 +47,7 @@ def set_test_params(self): self.num_nodes = 2 self.extra_args = [ ["-blockfilterindex", "-peerblockfilters"], - ["-blockfilterindex"], + ["-blockfilterindex", "-peerblockfilters=0"], ] def run_test(self): @@ -264,7 +264,7 @@ def run_test(self): self.log.info("Test -peerblockfilters without -blockfilterindex raises an error") self.stop_node(0) - self.nodes[0].extra_args = ["-peerblockfilters"] + self.nodes[0].extra_args = ["-peerblockfilters", "-blockfilterindex=0"] msg = "Error: Cannot set -peerblockfilters without -blockfilterindex." self.nodes[0].assert_start_raises_init_error(expected_msg=msg) diff --git a/test/functional/rpc_getblockfilter.py b/test/functional/rpc_getblockfilter.py index 1c456a5b82ba..70baa0983b4a 100755 --- a/test/functional/rpc_getblockfilter.py +++ b/test/functional/rpc_getblockfilter.py @@ -15,7 +15,7 @@ class GetBlockFilterTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [["-blockfilterindex"], []] + self.extra_args = [["-blockfilterindex"], ["-blockfilterindex=0", "-peerblockfilters=0"]] def run_test(self): # Create two chains by disconnecting nodes 0 & 1, mining, then reconnecting @@ -55,7 +55,7 @@ def run_test(self): assert_raises_rpc_error(-5, "Unknown filtertype", self.nodes[0].getblockfilter, genesis_hash, "unknown") # Test getblockfilter fails on node without compact block filter index - self.restart_node(0, extra_args=["-blockfilterindex=0"]) + self.restart_node(0, extra_args=["-blockfilterindex=0", "-peerblockfilters=0"]) for filter_type in FILTER_TYPES: assert_raises_rpc_error(-1, "Index is not enabled for filtertype {}".format(filter_type), self.nodes[0].getblockfilter, genesis_hash, filter_type) diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py index 4df181468088..e3f706edcf89 100755 --- a/test/functional/rpc_misc.py +++ b/test/functional/rpc_misc.py @@ -81,7 +81,7 @@ def run_test(self): assert_equal(node.echoipc("hello"), "hello") self.log.info("test getindexinfo") - self.restart_node(0, ["-txindex=0"]) + self.restart_node(0, ["-txindex=0", "-blockfilterindex=0", "-peerblockfilters=0"]) # Without any indices running the RPC returns an empty object assert_equal(node.getindexinfo(), {}) From 4412ced8ca33c585088aa1ba7f4b6d7a57c9acf4 Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 16 Jul 2025 14:18:47 -0500 Subject: [PATCH 006/244] test: Fix feature_index_prune.py to work with new blockfilterindex defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Node 1 was configured with only -coinstatsindex=1, but with the new defaults it was also getting -blockfilterindex=basic enabled. The test expects node 1 to only have coinstatsindex, so we need to explicitly disable blockfilterindex and peerblockfilters. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- test/functional/feature_index_prune.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py index a699990c45e2..40c0466cbe64 100755 --- a/test/functional/feature_index_prune.py +++ b/test/functional/feature_index_prune.py @@ -22,7 +22,7 @@ def set_test_params(self): self.num_nodes = 4 self.extra_args = [ ["-fastprune", "-prune=1", "-blockfilterindex=1"] + DEPLOYMENT_ARGS, - ["-fastprune", "-prune=1", "-coinstatsindex=1"] + DEPLOYMENT_ARGS, + ["-fastprune", "-prune=1", "-coinstatsindex=1", "-blockfilterindex=0", "-peerblockfilters=0"] + DEPLOYMENT_ARGS, ["-fastprune", "-prune=1", "-blockfilterindex=1", "-coinstatsindex=1"] + DEPLOYMENT_ARGS, [] + DEPLOYMENT_ARGS, ] From 2a88db9fb77ac591b943688dc011593dee5df887 Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 17 Jul 2025 11:17:29 -0500 Subject: [PATCH 007/244] test: Fix feature_index_prune.py to explicitly disable indices When BIP157 filters are enabled by default, the restart_without_indices function needs to explicitly disable both blockfilterindex and coinstatsindex to avoid conflicts with pruning. --- test/functional/feature_index_prune.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py index 40c0466cbe64..af54defc8530 100755 --- a/test/functional/feature_index_prune.py +++ b/test/functional/feature_index_prune.py @@ -54,7 +54,7 @@ def mine_batches(self, blocks): def restart_without_indices(self): for i in range(3): - self.restart_node(i, extra_args=["-fastprune", "-prune=1"] + DEPLOYMENT_ARGS, expected_stderr=EXPECTED_STDERR_NO_GOV_PRUNE) + self.restart_node(i, extra_args=["-fastprune", "-prune=1", "-blockfilterindex=0", "-coinstatsindex=0"] + DEPLOYMENT_ARGS, expected_stderr=EXPECTED_STDERR_NO_GOV_PRUNE) self.reconnect_nodes() def run_test(self): From d6c11f02efb875121ffc8cb8793c45023d6467e8 Mon Sep 17 00:00:00 2001 From: pasta Date: Fri, 18 Jul 2025 11:10:32 -0500 Subject: [PATCH 008/244] test: fix feature_index_prune.py for new peerblockfilters default With peerblockfilters now enabled by default, the test must explicitly disable it when disabling blockfilterindex to avoid initialization errors. Co-Authored-By: Claude --- test/functional/feature_index_prune.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py index af54defc8530..62c329170963 100755 --- a/test/functional/feature_index_prune.py +++ b/test/functional/feature_index_prune.py @@ -54,7 +54,7 @@ def mine_batches(self, blocks): def restart_without_indices(self): for i in range(3): - self.restart_node(i, extra_args=["-fastprune", "-prune=1", "-blockfilterindex=0", "-coinstatsindex=0"] + DEPLOYMENT_ARGS, expected_stderr=EXPECTED_STDERR_NO_GOV_PRUNE) + self.restart_node(i, extra_args=["-fastprune", "-prune=1", "-blockfilterindex=0", "-coinstatsindex=0", "-peerblockfilters=0"] + DEPLOYMENT_ARGS, expected_stderr=EXPECTED_STDERR_NO_GOV_PRUNE) self.reconnect_nodes() def run_test(self): From 195a1a90b37aeff6cbc49a3f0851ea4986895773 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sat, 19 Jul 2025 01:25:30 +0300 Subject: [PATCH 009/244] refactor: group all `CheckCbTx*` calls and their timers under one `if (opt_cbTx.has_value())` --- src/evo/specialtxman.cpp | 43 ++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 07481112a3d4..cee7c256bb79 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -601,11 +601,9 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CB } } - int64_t nTime5_1 = GetTimeMicros(); - nTimeDMN += nTime5_1 - nTime5; - - LogPrint(BCLog::BENCHMARK, " - m_dmnman: %.2fms [%.2fs]\n", 0.001 * (nTime5_1 - nTime5), - nTimeDMN * 0.000001); + int64_t nTime6 = GetTimeMicros(); + nTimeDMN += nTime6 - nTime5; + LogPrint(BCLog::BENCHMARK, " - m_dmnman: %.2fms [%.2fs]\n", 0.001 * (nTime6 - nTime5), nTimeDMN * 0.000001); if (opt_cbTx.has_value()) { uint256 calculatedMerkleRoot; @@ -617,46 +615,43 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CB return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-mnmerkleroot"); } - int64_t nTime5_2 = GetTimeMicros(); - nTimeMerkleMNL += nTime5_2 - nTime5_1; + int64_t nTime6_1 = GetTimeMicros(); + nTimeMerkleMNL += nTime6_1 - nTime6; LogPrint(BCLog::BENCHMARK, " - CalcCbTxMerkleRootMNList: %.2fms [%.2fs]\n", - 0.001 * (nTime5_2 - nTime5_1), nTimeMerkleMNL * 0.000001); - } + 0.001 * (nTime6_1 - nTime6), nTimeMerkleMNL * 0.000001); - int64_t nTime6 = GetTimeMicros(); - if (opt_cbTx.has_value()) { if (!CheckCbTxMerkleRoots(block, *opt_cbTx, pindex, m_qblockman, state)) { // pass the state returned by the function above return false; } - } - int64_t nTime7 = GetTimeMicros(); - nTimeMerkle += nTime7 - nTime6; + int64_t nTime6_2 = GetTimeMicros(); + nTimeMerkle += nTime6_2 - nTime6_1; - LogPrint(BCLog::BENCHMARK, " - CheckCbTxMerkleRoots: %.2fms [%.2fs]\n", 0.001 * (nTime7 - nTime6), - nTimeMerkle * 0.000001); + LogPrint(BCLog::BENCHMARK, " - CheckCbTxMerkleRoots: %.2fms [%.2fs]\n", 0.001 * (nTime6_2 - nTime6_1), + nTimeMerkle * 0.000001); - if (opt_cbTx.has_value()) { if (!CheckCbTxBestChainlock(*opt_cbTx, pindex, m_clhandler, state)) { // pass the state returned by the function above return false; } + + int64_t nTime6_3 = GetTimeMicros(); + nTimeCbTxCL += nTime6_3 - nTime6_2; + LogPrint(BCLog::BENCHMARK, " - CheckCbTxBestChainlock: %.2fms [%.2fs]\n", + 0.001 * (nTime6_3 - nTime6_2), nTimeCbTxCL * 0.000001); } - int64_t nTime8 = GetTimeMicros(); - nTimeCbTxCL += nTime8 - nTime7; - LogPrint(BCLog::BENCHMARK, " - CheckCbTxBestChainlock: %.2fms [%.2fs]\n", 0.001 * (nTime8 - nTime7), - nTimeCbTxCL * 0.000001); + int64_t nTime7 = GetTimeMicros(); if (!m_mnhfman.ProcessBlock(block, pindex, fJustCheck, state)) { // pass the state returned by the function above return false; } - int64_t nTime9 = GetTimeMicros(); - nTimeMnehf += nTime9 - nTime8; - LogPrint(BCLog::BENCHMARK, " - m_mnhfman: %.2fms [%.2fs]\n", 0.001 * (nTime9 - nTime8), nTimeMnehf * 0.000001); + int64_t nTime8 = GetTimeMicros(); + nTimeMnehf += nTime8 - nTime7; + LogPrint(BCLog::BENCHMARK, " - m_mnhfman: %.2fms [%.2fs]\n", 0.001 * (nTime8 - nTime7), nTimeMnehf * 0.000001); if (DeploymentActiveAfter(pindex, m_consensus_params, Consensus::DEPLOYMENT_V19) && bls::bls_legacy_scheme.load()) { // NOTE: The block next to the activation is the one that is using new rules. From fc331e3df14209867992b0af2989aba9c716c489 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 22 Jul 2025 17:54:47 +0300 Subject: [PATCH 010/244] refactor: drop confusing `CheckCbTxMerkleRoots`, move its logic into `ProcessSpecialTxsInBlock` --- src/evo/cbtx.cpp | 17 ----------------- src/evo/cbtx.h | 3 --- src/evo/specialtxman.cpp | 26 ++++++++++++++++---------- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/src/evo/cbtx.cpp b/src/evo/cbtx.cpp index 0ea054809917..af8ea39deb0a 100644 --- a/src/evo/cbtx.cpp +++ b/src/evo/cbtx.cpp @@ -43,23 +43,6 @@ bool CheckCbTx(const CCbTx& cbTx, const CBlockIndex* pindexPrev, TxValidationSta return true; } -bool CheckCbTxMerkleRoots(const CBlock& block, const CCbTx& cbTx, const CBlockIndex* pindex, - const llmq::CQuorumBlockProcessor& quorum_block_processor, BlockValidationState& state) -{ - if (pindex && cbTx.nVersion >= CCbTx::Version::MERKLE_ROOT_QUORUMS) { - uint256 calculatedMerkleRoot; - if (!CalcCbTxMerkleRootQuorums(block, pindex->pprev, quorum_block_processor, calculatedMerkleRoot, state)) { - // pass the state returned by the function above - return false; - } - if (calculatedMerkleRoot != cbTx.merkleRootQuorums) { - return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-quorummerkleroot"); - } - } - - return true; -} - using QcHashMap = std::map>; using QcIndexedHashMap = std::map>; diff --git a/src/evo/cbtx.h b/src/evo/cbtx.h index c9087f5866b0..482d880dd522 100644 --- a/src/evo/cbtx.h +++ b/src/evo/cbtx.h @@ -65,9 +65,6 @@ template<> struct is_serializable_enum : std::true_type {}; bool CheckCbTx(const CCbTx& cbTx, const CBlockIndex* pindexPrev, TxValidationState& state); -// This can only be done after the block has been fully processed, as otherwise we won't have the finished MN list -bool CheckCbTxMerkleRoots(const CBlock& block, const CCbTx& cbTx, const CBlockIndex* pindex, - const llmq::CQuorumBlockProcessor& quorum_block_processor, BlockValidationState& state); bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPrev, const llmq::CQuorumBlockProcessor& quorum_block_processor, uint256& merkleRootRet, BlockValidationState& state); diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index cee7c256bb79..bf5be2a05a4f 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -499,7 +499,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CB static int64_t nTimeQuorum = 0; static int64_t nTimeDMN = 0; static int64_t nTimeMerkleMNL = 0; - static int64_t nTimeMerkle = 0; + static int64_t nTimeMerkleQuorums = 0; static int64_t nTimeCbTxCL = 0; static int64_t nTimeMnehf = 0; static int64_t nTimePayload = 0; @@ -606,12 +606,12 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CB LogPrint(BCLog::BENCHMARK, " - m_dmnman: %.2fms [%.2fs]\n", 0.001 * (nTime6 - nTime5), nTimeDMN * 0.000001); if (opt_cbTx.has_value()) { - uint256 calculatedMerkleRoot; - if (!CalcCbTxMerkleRootMNList(calculatedMerkleRoot, mn_list.to_sml(), state)) { + uint256 calculatedMerkleRootMNL; + if (!CalcCbTxMerkleRootMNList(calculatedMerkleRootMNL, mn_list.to_sml(), state)) { // pass the state returned by the function above return false; } - if (calculatedMerkleRoot != opt_cbTx->merkleRootMNList) { + if (calculatedMerkleRootMNL != opt_cbTx->merkleRootMNList) { return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-mnmerkleroot"); } @@ -620,16 +620,22 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CB LogPrint(BCLog::BENCHMARK, " - CalcCbTxMerkleRootMNList: %.2fms [%.2fs]\n", 0.001 * (nTime6_1 - nTime6), nTimeMerkleMNL * 0.000001); - if (!CheckCbTxMerkleRoots(block, *opt_cbTx, pindex, m_qblockman, state)) { - // pass the state returned by the function above - return false; + if (opt_cbTx->nVersion >= CCbTx::Version::MERKLE_ROOT_QUORUMS) { + uint256 calculatedMerkleRootQuorums; + if (!CalcCbTxMerkleRootQuorums(block, pindex->pprev, m_qblockman, calculatedMerkleRootQuorums, state)) { + // pass the state returned by the function above + return false; + } + if (calculatedMerkleRootQuorums != opt_cbTx->merkleRootQuorums) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-quorummerkleroot"); + } } int64_t nTime6_2 = GetTimeMicros(); - nTimeMerkle += nTime6_2 - nTime6_1; + nTimeMerkleQuorums += nTime6_2 - nTime6_1; - LogPrint(BCLog::BENCHMARK, " - CheckCbTxMerkleRoots: %.2fms [%.2fs]\n", 0.001 * (nTime6_2 - nTime6_1), - nTimeMerkle * 0.000001); + LogPrint(BCLog::BENCHMARK, " - CalcCbTxMerkleRootQuorums: %.2fms [%.2fs]\n", + 0.001 * (nTime6_2 - nTime6_1), nTimeMerkleQuorums * 0.000001); if (!CheckCbTxBestChainlock(*opt_cbTx, pindex, m_clhandler, state)) { // pass the state returned by the function above From 40f3d98f97766115909c9d87efffd13638f2ebfc Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 2 Jul 2025 13:33:12 +0700 Subject: [PATCH 011/244] refactor: simplify GetLastMinedCommitmentsPerQuorumIndexUntilBlock interface --- src/llmq/blockprocessor.cpp | 21 ++++++++++----------- src/llmq/blockprocessor.h | 4 +++- src/llmq/snapshot.cpp | 7 ++++--- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/llmq/blockprocessor.cpp b/src/llmq/blockprocessor.cpp index e554948a7429..cd0f1b19370d 100644 --- a/src/llmq/blockprocessor.cpp +++ b/src/llmq/blockprocessor.cpp @@ -572,16 +572,17 @@ std::optional CQuorumBlockProcessor::GetLastMinedCommitments return std::nullopt; } -std::vector> CQuorumBlockProcessor::GetLastMinedCommitmentsPerQuorumIndexUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t cycle) const +std::vector CQuorumBlockProcessor::GetLastMinedCommitmentsPerQuorumIndexUntilBlock( + Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t cycle) const { const auto& llmq_params_opt = Params().GetLLMQ(llmqType); assert(llmq_params_opt.has_value()); - std::vector> ret; + std::vector ret; for (const auto quorumIndex : irange::range(llmq_params_opt->signingActiveQuorumCount)) { std::optional q = GetLastMinedCommitmentsByQuorumIndexUntilBlock(llmqType, pindex, quorumIndex, cycle); if (q.has_value()) { - ret.emplace_back(quorumIndex, q.value()); + ret.emplace_back(q.value()); } } @@ -595,17 +596,15 @@ std::vector CQuorumBlockProcessor::GetMinedCommitmentsIndexe size_t cycle = 0; while (ret.size() < maxCount) { - std::vector> cycleRet = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(llmqType, pindex, cycle); + std::vector cycleRet = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(llmqType, pindex, cycle); if (cycleRet.empty()) { return ret; } std::vector cycleRetTransformed; - std::transform(cycleRet.begin(), - cycleRet.end(), - std::back_inserter(cycleRetTransformed), - [](const std::pair& p) { return p.second; }); + std::transform(cycleRet.begin(), cycleRet.end(), std::back_inserter(cycleRetTransformed), + [](const CBlockIndex* pindex) { return pindex; }); size_t needToCopy = maxCount - ret.size(); @@ -627,9 +626,9 @@ std::map> CQuorumBlockProce auto& v = ret[params.type]; v.reserve(params.signingActiveQuorumCount); if (IsQuorumRotationEnabled(params, pindex)) { - std::vector> commitments = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(params.type, pindex, 0); - std::transform(commitments.begin(), commitments.end(), std::back_inserter(v), - [](const std::pair& p) { return p.second; }); + std::vector commitments = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(params.type, + pindex, 0); + std::copy(commitments.begin(), commitments.end(), std::back_inserter(v)); } else { auto commitments = GetMinedCommitmentsUntilBlock(params.type, pindex, params.signingActiveQuorumCount); std::copy(commitments.begin(), commitments.end(), std::back_inserter(v)); diff --git a/src/llmq/blockprocessor.h b/src/llmq/blockprocessor.h index 170e0007f742..705fe8e332b5 100644 --- a/src/llmq/blockprocessor.h +++ b/src/llmq/blockprocessor.h @@ -71,7 +71,9 @@ class CQuorumBlockProcessor std::map> GetMinedAndActiveCommitmentsUntilBlock(gsl::not_null pindex) const; std::vector GetMinedCommitmentsIndexedUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t maxCount) const; - std::vector> GetLastMinedCommitmentsPerQuorumIndexUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t cycle) const; + std::vector GetLastMinedCommitmentsPerQuorumIndexUntilBlock(Consensus::LLMQType llmqType, + const CBlockIndex* pindex, + size_t cycle) const; std::optional GetLastMinedCommitmentsByQuorumIndexUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, int quorumIndex, size_t cycle) const; private: static bool GetCommitmentsFromBlock(const CBlock& block, gsl::not_null pindex, std::multimap& ret, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); diff --git a/src/llmq/snapshot.cpp b/src/llmq/snapshot.cpp index b0097798e409..03f2baba1c0e 100644 --- a/src/llmq/snapshot.cpp +++ b/src/llmq/snapshot.cpp @@ -294,17 +294,18 @@ bool BuildQuorumRotationInfo(CDeterministicMNManager& dmnman, CQuorumSnapshotMan std::set snapshotHeightsNeeded; - std::vector> qdata = qblockman.GetLastMinedCommitmentsPerQuorumIndexUntilBlock(llmqType, blockIndex, 0); + std::vector qdata = qblockman.GetLastMinedCommitmentsPerQuorumIndexUntilBlock(llmqType, + blockIndex, 0); for (const auto& obj : qdata) { uint256 minedBlockHash; - llmq::CFinalCommitmentPtr qc = qblockman.GetMinedCommitment(llmqType, obj.second->GetBlockHash(), minedBlockHash); + llmq::CFinalCommitmentPtr qc = qblockman.GetMinedCommitment(llmqType, obj->GetBlockHash(), minedBlockHash); if (qc == nullptr) { return false; } response.lastCommitmentPerIndex.push_back(*qc); - int quorumCycleStartHeight = obj.second->nHeight - (obj.second->nHeight % llmq_params_opt->dkgInterval); + int quorumCycleStartHeight = obj->nHeight - (obj->nHeight % llmq_params_opt->dkgInterval); snapshotHeightsNeeded.insert(quorumCycleStartHeight - cycleLength); snapshotHeightsNeeded.insert(quorumCycleStartHeight - 2 * cycleLength); snapshotHeightsNeeded.insert(quorumCycleStartHeight - 3 * cycleLength); From 6d9018946d0542a984946ac72dd674117a07a62b Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 2 Jul 2025 13:35:36 +0700 Subject: [PATCH 012/244] refactor: futher simplification of GetMinedAndActiveCommitmentsUntilBlock and related code --- src/llmq/blockprocessor.cpp | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/llmq/blockprocessor.cpp b/src/llmq/blockprocessor.cpp index cd0f1b19370d..b3ae63677d08 100644 --- a/src/llmq/blockprocessor.cpp +++ b/src/llmq/blockprocessor.cpp @@ -602,15 +602,8 @@ std::vector CQuorumBlockProcessor::GetMinedCommitmentsIndexe return ret; } - std::vector cycleRetTransformed; - std::transform(cycleRet.begin(), cycleRet.end(), std::back_inserter(cycleRetTransformed), - [](const CBlockIndex* pindex) { return pindex; }); - size_t needToCopy = maxCount - ret.size(); - - std::copy_n(cycleRetTransformed.begin(), - std::min(needToCopy, cycleRetTransformed.size()), - std::back_inserter(ret)); + std::copy_n(cycleRet.begin(), std::min(needToCopy, cycleRet.size()), std::back_inserter(ret)); cycle++; } @@ -623,15 +616,11 @@ std::map> CQuorumBlockProce std::map> ret; for (const auto& params : Params().GetConsensus().llmqs) { - auto& v = ret[params.type]; - v.reserve(params.signingActiveQuorumCount); + auto& commitments = ret[params.type]; if (IsQuorumRotationEnabled(params, pindex)) { - std::vector commitments = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(params.type, - pindex, 0); - std::copy(commitments.begin(), commitments.end(), std::back_inserter(v)); + commitments = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(params.type, pindex, 0); } else { - auto commitments = GetMinedCommitmentsUntilBlock(params.type, pindex, params.signingActiveQuorumCount); - std::copy(commitments.begin(), commitments.end(), std::back_inserter(v)); + commitments = GetMinedCommitmentsUntilBlock(params.type, pindex, params.signingActiveQuorumCount); } } From d78fb0203f42d0b29be7ada0b11bc49b951ea852 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 15 Jul 2025 02:20:46 +0700 Subject: [PATCH 013/244] perf: replace re-creation of CFinalCommitment and extra unique_ptr to RVO CFinalCommitment is a heavy object to create and copy --- src/evo/cbtx.cpp | 9 ++++----- src/llmq/blockprocessor.cpp | 11 +++++++++++ src/llmq/blockprocessor.h | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/evo/cbtx.cpp b/src/evo/cbtx.cpp index 0ea054809917..0cf579c97eee 100644 --- a/src/evo/cbtx.cpp +++ b/src/evo/cbtx.cpp @@ -96,15 +96,14 @@ auto CachedGetQcHashesQcIndexedHashes(const CBlockIndex* pindexPrev, const llmq: vec_hashes.reserve(vecBlockIndexes.size()); auto& map_indexed_hashes = qcIndexedHashes_cached[llmqType]; for (const auto& blockIndex : vecBlockIndexes) { - uint256 dummyHash; - llmq::CFinalCommitmentPtr pqc = quorum_block_processor.GetMinedCommitment(llmqType, blockIndex->GetBlockHash(), dummyHash); - if (pqc == nullptr) { + const auto [pqc, dummyHash] = quorum_block_processor.GetMinedCommitment(llmqType, blockIndex->GetBlockHash()); + if (dummyHash == uint256::ZERO) { // this should never happen return std::nullopt; } - auto qcHash = ::SerializeHash(*pqc); + auto qcHash = ::SerializeHash(pqc); if (rotation_enabled) { - map_indexed_hashes[pqc->quorumIndex] = qcHash; + map_indexed_hashes[pqc.quorumIndex] = qcHash; } else { vec_hashes.emplace_back(qcHash); } diff --git a/src/llmq/blockprocessor.cpp b/src/llmq/blockprocessor.cpp index b3ae63677d08..12ab73f3ad82 100644 --- a/src/llmq/blockprocessor.cpp +++ b/src/llmq/blockprocessor.cpp @@ -479,6 +479,17 @@ CFinalCommitmentPtr CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQTyp return std::make_unique(p.first); } +std::pair CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType, + const uint256& quorumHash) const +{ + auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash)); + std::pair ret; + if (!m_evoDb.Read(key, ret)) { + return {CFinalCommitment{}, uint256::ZERO}; + } + return ret; +} + // The returned quorums are in reversed order, so the most recent one is at index 0 std::vector CQuorumBlockProcessor::GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, gsl::not_null pindex, size_t maxCount) const { diff --git a/src/llmq/blockprocessor.h b/src/llmq/blockprocessor.h index 705fe8e332b5..068c8cd1ad5c 100644 --- a/src/llmq/blockprocessor.h +++ b/src/llmq/blockprocessor.h @@ -66,6 +66,7 @@ class CQuorumBlockProcessor bool GetMineableCommitmentsTx(const Consensus::LLMQParams& llmqParams, int nHeight, std::vector& ret) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const; CFinalCommitmentPtr GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, uint256& retMinedBlockHash) const; + std::pair GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const; std::vector GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, gsl::not_null pindex, size_t maxCount) const; std::map> GetMinedAndActiveCommitmentsUntilBlock(gsl::not_null pindex) const; From f2b2479ec938b2cbd18c9945bf2102dd2ac3d3da Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 15 Jul 2025 02:33:22 +0700 Subject: [PATCH 014/244] refactor: avoid code duplication due to 2 implementation of GetMinedCommitment --- src/evo/smldiff.cpp | 7 +++---- src/llmq/blockprocessor.cpp | 11 ----------- src/llmq/blockprocessor.h | 3 --- src/llmq/quorums.cpp | 12 ++++++------ src/llmq/snapshot.cpp | 7 +++---- 5 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/evo/smldiff.cpp b/src/evo/smldiff.cpp index e0e9bf6d5a17..5ba1e06358f3 100644 --- a/src/evo/smldiff.cpp +++ b/src/evo/smldiff.cpp @@ -54,12 +54,11 @@ bool CSimplifiedMNListDiff::BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, for (const auto& p : quorumHashes) { const auto& [llmqType, hash] = p; if (!baseQuorumHashes.count(p)) { - uint256 minedBlockHash; - llmq::CFinalCommitmentPtr qc = quorum_block_processor.GetMinedCommitment(llmqType, hash, minedBlockHash); - if (qc == nullptr) { + auto [qc, minedBlockHash] = quorum_block_processor.GetMinedCommitment(llmqType, hash); + if (minedBlockHash == uint256::ZERO) { return false; } - newQuorums.emplace_back(*qc); + newQuorums.emplace_back(std::move(qc)); } } diff --git a/src/llmq/blockprocessor.cpp b/src/llmq/blockprocessor.cpp index 12ab73f3ad82..739601afb980 100644 --- a/src/llmq/blockprocessor.cpp +++ b/src/llmq/blockprocessor.cpp @@ -468,17 +468,6 @@ bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, con return fExists; } -CFinalCommitmentPtr CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, uint256& retMinedBlockHash) const -{ - auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash)); - std::pair p; - if (!m_evoDb.Read(key, p)) { - return nullptr; - } - retMinedBlockHash = p.second; - return std::make_unique(p.first); -} - std::pair CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const { diff --git a/src/llmq/blockprocessor.h b/src/llmq/blockprocessor.h index 068c8cd1ad5c..f8f195fcd73b 100644 --- a/src/llmq/blockprocessor.h +++ b/src/llmq/blockprocessor.h @@ -33,8 +33,6 @@ namespace llmq class CFinalCommitment; class CQuorumSnapshotManager; -using CFinalCommitmentPtr = std::unique_ptr; - class CQuorumBlockProcessor { private: @@ -65,7 +63,6 @@ class CQuorumBlockProcessor std::optional> GetMineableCommitments(const Consensus::LLMQParams& llmqParams, int nHeight) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool GetMineableCommitmentsTx(const Consensus::LLMQParams& llmqParams, int nHeight, std::vector& ret) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const; - CFinalCommitmentPtr GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, uint256& retMinedBlockHash) const; std::pair GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const; std::vector GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, gsl::not_null pindex, size_t maxCount) const; diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp index de1b3815c59b..d7eeef7ecd1b 100644 --- a/src/llmq/quorums.cpp +++ b/src/llmq/quorums.cpp @@ -399,20 +399,20 @@ void CQuorumManager::CheckQuorumConnections(CConnman& connman, const Consensus:: CQuorumPtr CQuorumManager::BuildQuorumFromCommitment(const Consensus::LLMQType llmqType, gsl::not_null pQuorumBaseBlockIndex, bool populate_cache) const { const uint256& quorumHash{pQuorumBaseBlockIndex->GetBlockHash()}; - uint256 minedBlockHash; - CFinalCommitmentPtr qc = quorumBlockProcessor.GetMinedCommitment(llmqType, quorumHash, minedBlockHash); - if (qc == nullptr) { + + auto [qc, minedBlockHash] = quorumBlockProcessor.GetMinedCommitment(llmqType, quorumHash); + if (minedBlockHash == uint256::ZERO) { LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- No mined commitment for llmqType[%d] nHeight[%d] quorumHash[%s]\n", __func__, ToUnderlying(llmqType), pQuorumBaseBlockIndex->nHeight, pQuorumBaseBlockIndex->GetBlockHash().ToString()); return nullptr; } - assert(qc->quorumHash == pQuorumBaseBlockIndex->GetBlockHash()); + assert(qc.quorumHash == pQuorumBaseBlockIndex->GetBlockHash()); const auto& llmq_params_opt = Params().GetLLMQ(llmqType); assert(llmq_params_opt.has_value()); auto quorum = std::make_shared(llmq_params_opt.value(), blsWorker); - auto members = utils::GetAllQuorumMembers(qc->llmqType, m_dmnman, m_qsnapman, pQuorumBaseBlockIndex); + auto members = utils::GetAllQuorumMembers(qc.llmqType, m_dmnman, m_qsnapman, pQuorumBaseBlockIndex); - quorum->Init(std::move(qc), pQuorumBaseBlockIndex, minedBlockHash, members); + quorum->Init(std::make_unique(std::move(qc)), pQuorumBaseBlockIndex, minedBlockHash, members); if (populate_cache && llmq_params_opt->size == 1) { WITH_LOCK(cs_map_quorums, mapQuorumsCache[llmqType].insert(quorumHash, quorum)); diff --git a/src/llmq/snapshot.cpp b/src/llmq/snapshot.cpp index 03f2baba1c0e..a0e6cf35958a 100644 --- a/src/llmq/snapshot.cpp +++ b/src/llmq/snapshot.cpp @@ -298,12 +298,11 @@ bool BuildQuorumRotationInfo(CDeterministicMNManager& dmnman, CQuorumSnapshotMan blockIndex, 0); for (const auto& obj : qdata) { - uint256 minedBlockHash; - llmq::CFinalCommitmentPtr qc = qblockman.GetMinedCommitment(llmqType, obj->GetBlockHash(), minedBlockHash); - if (qc == nullptr) { + auto [qc, minedBlockHash] = qblockman.GetMinedCommitment(llmqType, obj->GetBlockHash()); + if (minedBlockHash == uint256::ZERO) { return false; } - response.lastCommitmentPerIndex.push_back(*qc); + response.lastCommitmentPerIndex.emplace_back(std::move(qc)); int quorumCycleStartHeight = obj->nHeight - (obj->nHeight % llmq_params_opt->dkgInterval); snapshotHeightsNeeded.insert(quorumCycleStartHeight - cycleLength); From 89581527f28904d9c2bfbf1aa1a276cbf69bdd65 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 4 Jul 2025 23:18:37 +0700 Subject: [PATCH 015/244] perf: cache mined commitment for quorum merkle root calculation --- src/evo/cbtx.cpp | 28 ++++++++++++++++++++-------- src/llmq/utils.cpp | 4 ++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/evo/cbtx.cpp b/src/evo/cbtx.cpp index 0cf579c97eee..6af96c26dc72 100644 --- a/src/evo/cbtx.cpp +++ b/src/evo/cbtx.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -74,11 +75,12 @@ auto CachedGetQcHashesQcIndexedHashes(const CBlockIndex* pindexPrev, const llmq: static Mutex cs_cache; static std::map> quorums_cached GUARDED_BY(cs_cache); + static std::map, StaticSaltedHasher>> qc_hashes_cached + GUARDED_BY(cs_cache); static QcHashMap qcHashes_cached GUARDED_BY(cs_cache); static QcIndexedHashMap qcIndexedHashes_cached GUARDED_BY(cs_cache); LOCK(cs_cache); - if (quorums == quorums_cached) { return std::make_pair(qcHashes_cached, qcIndexedHashes_cached); } @@ -87,6 +89,9 @@ auto CachedGetQcHashesQcIndexedHashes(const CBlockIndex* pindexPrev, const llmq: quorums_cached.clear(); qcHashes_cached.clear(); qcIndexedHashes_cached.clear(); + if (qc_hashes_cached.empty()) { + llmq::utils::InitQuorumsCache(qc_hashes_cached); + } for (const auto& [llmqType, vecBlockIndexes] : quorums) { const auto& llmq_params_opt = Params().GetLLMQ(llmqType); @@ -96,16 +101,23 @@ auto CachedGetQcHashesQcIndexedHashes(const CBlockIndex* pindexPrev, const llmq: vec_hashes.reserve(vecBlockIndexes.size()); auto& map_indexed_hashes = qcIndexedHashes_cached[llmqType]; for (const auto& blockIndex : vecBlockIndexes) { - const auto [pqc, dummyHash] = quorum_block_processor.GetMinedCommitment(llmqType, blockIndex->GetBlockHash()); - if (dummyHash == uint256::ZERO) { - // this should never happen - return std::nullopt; + uint256 block_hash{blockIndex->GetBlockHash()}; + + std::pair qc_hash; + if (!qc_hashes_cached[llmqType].get(block_hash, qc_hash)) { + auto [pqc, dummy_hash] = quorum_block_processor.GetMinedCommitment(llmqType, block_hash); + if (dummy_hash == uint256::ZERO) { + // this should never happen + return std::nullopt; + } + qc_hash.first = ::SerializeHash(pqc); + qc_hash.second = rotation_enabled ? pqc.quorumIndex : 0; + qc_hashes_cached[llmqType].insert(block_hash, qc_hash); } - auto qcHash = ::SerializeHash(pqc); if (rotation_enabled) { - map_indexed_hashes[pqc.quorumIndex] = qcHash; + map_indexed_hashes[qc_hash.second] = qc_hash.first; } else { - vec_hashes.emplace_back(qcHash); + vec_hashes.emplace_back(qc_hash.first); } } } diff --git a/src/llmq/utils.cpp b/src/llmq/utils.cpp index b64b573c88a7..d8ff21d5ca69 100644 --- a/src/llmq/utils.cpp +++ b/src/llmq/utils.cpp @@ -953,5 +953,9 @@ template void InitQuorumsCache, StaticSaltedHasher, 0ul, 0ul>, std::less, std::allocator, StaticSaltedHasher, 0ul, 0ul>>>>>(std::map, StaticSaltedHasher, 0ul, 0ul>, std::less, std::allocator, StaticSaltedHasher, 0ul, 0ul>>>>&cache, bool limit_by_connections); template void InitQuorumsCache>>(std::map>& cache, bool limit_by_connections); template void InitQuorumsCache>>(std::map>& cache, bool limit_by_connections); +template void +InitQuorumsCache, StaticSaltedHasher>>>( + std::map, StaticSaltedHasher>>& cache, + bool limit_by_connections); } // namespace utils } // namespace llmq From 8254e064856b11940eee88c1b271df5beba98b7a Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 22 Jul 2025 22:09:07 +0700 Subject: [PATCH 016/244] refactor: resolve freshly introduced circular dependency --- src/evo/smldiff.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/evo/smldiff.cpp b/src/evo/smldiff.cpp index 5ba1e06358f3..0de5434fb194 100644 --- a/src/evo/smldiff.cpp +++ b/src/evo/smldiff.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -23,6 +22,9 @@ using node::ReadBlockFromDisk; +// Forward declaration +std::optional> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex); + CSimplifiedMNListDiff::CSimplifiedMNListDiff() = default; CSimplifiedMNListDiff::~CSimplifiedMNListDiff() = default; From eeb577b3e7c8334585620513059fa7707edbce48 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 4 Jul 2025 23:28:16 +0700 Subject: [PATCH 017/244] perf: swap arrays instead assigments to save allocation --- src/evo/cbtx.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evo/cbtx.cpp b/src/evo/cbtx.cpp index 6af96c26dc72..714548f0f813 100644 --- a/src/evo/cbtx.cpp +++ b/src/evo/cbtx.cpp @@ -121,7 +121,7 @@ auto CachedGetQcHashesQcIndexedHashes(const CBlockIndex* pindexPrev, const llmq: } } } - quorums_cached = quorums; + std::swap(quorums_cached, quorums); return std::make_pair(qcHashes_cached, qcIndexedHashes_cached); } From 6a53aa221fe5b788b41ff55635089c29e6523964 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sun, 1 Jun 2025 02:05:01 +0700 Subject: [PATCH 018/244] feat: stop logging 'remove mapSendableNodes' and 'remove mapReceivableNodes' which triggers on each send / receive bytes --- src/net.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 659841ee5dbd..f135905ca5fa 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2539,7 +2539,6 @@ void CConnman::SocketHandlerConnected(const Sock::EventsPerSock& events_per_sock // (even if there are pending messages to be sent) for (auto it = mapSendableNodes.begin(); it != mapSendableNodes.end(); ) { if (!it->second->fCanSendData) { - LogPrint(BCLog::NET, "%s -- remove mapSendableNodes, peer=%d\n", __func__, it->second->GetId()); it = mapSendableNodes.erase(it); } else { ++it; @@ -2548,7 +2547,6 @@ void CConnman::SocketHandlerConnected(const Sock::EventsPerSock& events_per_sock // clean up mapReceivableNodes from nodes that were receivable in the last iteration but aren't anymore for (auto it = mapReceivableNodes.begin(); it != mapReceivableNodes.end(); ) { if (!it->second->fHasRecvData) { - LogPrint(BCLog::NET, "%s -- remove mapReceivableNodes, peer=%d\n", __func__, it->second->GetId()); it = mapReceivableNodes.erase(it); } else { ++it; From 1abe75394505910ca4ded80be1117e09663e82e0 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sun, 1 Jun 2025 01:59:42 +0700 Subject: [PATCH 019/244] refactor: remove useless logs from CQuorumBlockProcessor and make its member `IsMiningPhase` be anonymous function --- src/llmq/blockprocessor.cpp | 53 ++++++++++++++++--------------------- src/llmq/blockprocessor.h | 1 - 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/llmq/blockprocessor.cpp b/src/llmq/blockprocessor.cpp index e554948a7429..fe565ee1de79 100644 --- a/src/llmq/blockprocessor.cpp +++ b/src/llmq/blockprocessor.cpp @@ -220,20 +220,34 @@ static std::tuple BuildInversed return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT_Q_INDEXED, llmqType, quorumIndex, htobe32_internal(std::numeric_limits::max() - nMinedHeight)); } +static bool IsMiningPhase(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + AssertLockHeld(cs_main); + + // Note: This function can be called for new blocks + assert(nHeight <= active_chain.Height() + 1); + + int quorumCycleStartHeight = nHeight - (nHeight % llmqParams.dkgInterval); + int quorumCycleMiningStartHeight = quorumCycleStartHeight + llmqParams.dkgMiningWindowStart; + int quorumCycleMiningEndHeight = quorumCycleStartHeight + llmqParams.dkgMiningWindowEnd; + + return nHeight >= quorumCycleMiningStartHeight && nHeight <= quorumCycleMiningEndHeight; +} + bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc, BlockValidationState& state, bool fJustCheck, bool fBLSChecks) { AssertLockHeld(::cs_main); const auto& llmq_params_opt = Params().GetLLMQ(qc.llmqType); if (!llmq_params_opt.has_value()) { - LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- invalid commitment type %d\n", __func__, ToUnderlying(qc.llmqType)); + LogPrint(BCLog::LLMQ, "%s -- invalid commitment type %d\n", __func__, ToUnderlying(qc.llmqType)); return false; } const auto& llmq_params = llmq_params_opt.value(); uint256 quorumHash = GetQuorumBlockHash(llmq_params, m_chainstate.m_chain, nHeight, qc.quorumIndex); - LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s fJustCheck[%d] processing commitment from block.\n", __func__, + LogPrint(BCLog::LLMQ, "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s fJustCheck[%d] processing commitment from block.\n", __func__, nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString(), fJustCheck); // skip `bad-qc-block` checks below when replaying blocks after the crash @@ -242,19 +256,19 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH } if (quorumHash.IsNull()) { - LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s quorumHash is null.\n", __func__, + LogPrint(BCLog::LLMQ, "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s quorumHash is null.\n", __func__, nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-block"); } if (quorumHash != qc.quorumHash) { - LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d, type=%d, quorumIndex=%d, quorumHash=%s, qc.quorumHash=%s signers=%s, validMembers=%d, quorumPublicKey=%s non equal quorumHash.\n", __func__, + LogPrint(BCLog::LLMQ, "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, qc.quorumHash=%s signers=%s, validMembers=%d, quorumPublicKey=%s non equal quorumHash.\n", __func__, nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-block"); } if (qc.IsNull()) { if (!qc.VerifyNull()) { - LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%dqc verifynull failed.\n", __func__, + LogPrint(BCLog::LLMQ, "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%dqc verifynull failed.\n", __func__, nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-invalid-null"); } @@ -274,7 +288,7 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH const auto* pQuorumBaseBlockIndex = m_chainstate.m_blockman.LookupBlockIndex(qc.quorumHash); if (!qc.Verify(m_dmnman, m_qsnapman, pQuorumBaseBlockIndex, /*checkSigs=*/fBLSChecks)) { - LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s qc verify failed.\n", __func__, + LogPrint(BCLog::LLMQ, "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s qc verify failed.\n", __func__, nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-invalid"); } @@ -286,7 +300,7 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH bool rotation_enabled = IsQuorumRotationEnabled(llmq_params, pQuorumBaseBlockIndex); if (rotation_enabled) { - LogPrint(BCLog::LLMQ, "[ProcessCommitment] height[%d] pQuorumBaseBlockIndex[%d] quorumIndex[%d] qversion[%d] Built\n", + LogPrint(BCLog::LLMQ, "%s -- height[%d] pQuorumBaseBlockIndex[%d] quorumIndex[%d] qversion[%d] Built\n", __func__, nHeight, pQuorumBaseBlockIndex->nHeight, qc.quorumIndex, qc.nVersion); } @@ -307,8 +321,8 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH minableCommitments.erase(::SerializeHash(qc)); } - LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- processed commitment from block. type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s\n", __func__, - ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); + LogPrint(BCLog::LLMQ, "%s -- processed commitment from block. type=%d, quorumIndex=%d, quorumHash=%s\n", __func__, + ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString()); return true; } @@ -394,26 +408,6 @@ bool CQuorumBlockProcessor::GetCommitmentsFromBlock(const CBlock& block, gsl::no return true; } -bool CQuorumBlockProcessor::IsMiningPhase(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight) -{ - AssertLockHeld(::cs_main); - - // Note: This function can be called for new blocks - assert(nHeight <= active_chain.Height() + 1); - - int quorumCycleStartHeight = nHeight - (nHeight % llmqParams.dkgInterval); - int quorumCycleMiningStartHeight = quorumCycleStartHeight + llmqParams.dkgMiningWindowStart; - int quorumCycleMiningEndHeight = quorumCycleStartHeight + llmqParams.dkgMiningWindowEnd; - - if (nHeight >= quorumCycleMiningStartHeight && nHeight <= quorumCycleMiningEndHeight) { - LogPrint(BCLog::LLMQ, "[IsMiningPhase] nHeight[%d] llmqType[%d] quorumCycleStartHeight[%d] -- mining[%d-%d]\n", nHeight, ToUnderlying(llmqParams.type), quorumCycleStartHeight, quorumCycleMiningStartHeight, quorumCycleMiningEndHeight); - return true; - } - LogPrint(BCLog::LLMQ, "[IsMiningPhase] nHeight[%d] llmqType[%d] quorumCycleStartHeight[%d] -- NOT mining[%d-%d]\n", nHeight, ToUnderlying(llmqParams.type), quorumCycleStartHeight, quorumCycleMiningStartHeight, quorumCycleMiningEndHeight); - - return false; -} - size_t CQuorumBlockProcessor::GetNumCommitmentsRequired(const Consensus::LLMQParams& llmqParams, int nHeight) const { AssertLockHeld(::cs_main); @@ -449,7 +443,6 @@ uint256 CQuorumBlockProcessor::GetQuorumBlockHash(const Consensus::LLMQParams& l return {}; } - LogPrint(BCLog::LLMQ, "[GetQuorumBlockHash] llmqType[%d] h[%d] qi[%d] quorumStartHeight[%d] quorumHash[%s]\n", ToUnderlying(llmqParams.type), nHeight, quorumIndex, quorumStartHeight, quorumBlockHash.ToString()); return quorumBlockHash; } diff --git a/src/llmq/blockprocessor.h b/src/llmq/blockprocessor.h index 170e0007f742..0b56e0387c48 100644 --- a/src/llmq/blockprocessor.h +++ b/src/llmq/blockprocessor.h @@ -76,7 +76,6 @@ class CQuorumBlockProcessor private: static bool GetCommitmentsFromBlock(const CBlock& block, gsl::not_null pindex, std::multimap& ret, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc, BlockValidationState& state, bool fJustCheck, bool fBLSChecks) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - static bool IsMiningPhase(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); size_t GetNumCommitmentsRequired(const Consensus::LLMQParams& llmqParams, int nHeight) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); static uint256 GetQuorumBlockHash(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight, int quorumIndex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); }; From 3458ceba90aa0cd93132e6bd2898d484af97293e Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 2 Jun 2025 15:22:34 +0700 Subject: [PATCH 020/244] feat: remove useless checks after UpdateMN Firstly, production code (even in debug mode) should not operate as unit test by calling heavy GetMN just to be sure that implementation is not broken. Secondly, since mn_rr hard-fork is activated it doesn't affect any new blocks; but it is used for validation of old blocks only. Thirdly, it removes useless log record (especially now when no more 4x payments for Evo Nodes). 2025-05-31T19:40:51.578629Z (mocktime: 2014-12-04T17:29:28Z) [httpworker] [evo/deterministicmns.cpp:1023] [BuildNewListFromBlock] [mnpayments] CDeterministicMNManager::BuildNewListFromBlock -- MN ae67835776b01b4ad9b153c829bf30fe70e8c3699a09ddcebe431712c38cee63, nConsecutivePayments=0 --- src/evo/specialtxman.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 162446a14d99..c07b42183850 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -457,14 +457,6 @@ bool CSpecialTxProcessor::BuildNewListFromBlock(const CBlock& block, gsl::not_nu } } newList.UpdateMN(payee->proTxHash, newState); - if (debugLogs) { - dmn = newList.GetMN(payee->proTxHash); - // Since the previous GetMN query returned a value, after an update, querying the same - // hash *must* give us a result. If it doesn't, that would be a potential logic bug. - assert(dmn); - LogPrint(BCLog::MNPAYMENTS, "%s -- MN %s, nConsecutivePayments=%d\n", __func__, dmn->proTxHash.ToString(), - dmn->pdmnState->nConsecutivePayments); - } } // reset nConsecutivePayments on non-paid EvoNodes From a04e831fc0e063e6ef9513231bd3ceb80695d584 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 2 Jun 2025 15:51:38 +0700 Subject: [PATCH 021/244] feat: use seconds since 1970 for mocktime in logs Firstly, it's easier to spot changes in mocktime and calculate how long if time is logged in seconds. Secondly, the start time `2014-12-04T17:15:37Z` (genesis block) is not round number which makes calculation of "how much exactly time pass" even more difficult. --- src/logging.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging.cpp b/src/logging.cpp index 2d98525a3a2a..1655206166fa 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -401,7 +401,7 @@ std::string BCLog::Logger::LogTimestampStr(const std::string& str) } std::chrono::seconds mocktime = GetMockTime(); if (mocktime > 0s) { - strStamped += " (mocktime: " + FormatISO8601DateTime(count_seconds(mocktime)) + ")"; + strStamped += strprintf(" (mocktime: %d)", count_seconds(mocktime)); } strStamped += ' ' + str; } else From f9536e81340be6e301826a9ba5ee3c5a74e9c6d8 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sat, 12 Jul 2025 00:10:25 +0700 Subject: [PATCH 022/244] fmt: applyed clang format for blockprocessor --- src/llmq/blockprocessor.cpp | 43 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/llmq/blockprocessor.cpp b/src/llmq/blockprocessor.cpp index fe565ee1de79..9b2d9f24e005 100644 --- a/src/llmq/blockprocessor.cpp +++ b/src/llmq/blockprocessor.cpp @@ -220,7 +220,8 @@ static std::tuple BuildInversed return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT_Q_INDEXED, llmqType, quorumIndex, htobe32_internal(std::numeric_limits::max() - nMinedHeight)); } -static bool IsMiningPhase(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static bool IsMiningPhase(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight) + EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); @@ -247,8 +248,12 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH uint256 quorumHash = GetQuorumBlockHash(llmq_params, m_chainstate.m_chain, nHeight, qc.quorumIndex); - LogPrint(BCLog::LLMQ, "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s fJustCheck[%d] processing commitment from block.\n", __func__, - nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString(), fJustCheck); + LogPrint(BCLog::LLMQ, /* Continued */ + "%s -- processing commitment for block height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, " + "validMembers=%d, quorumPublicKey=%s " + "fJustCheck[%d] processing commitment from block.\n", + __func__, nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), + qc.CountValidMembers(), qc.quorumPublicKey.ToString(), fJustCheck); // skip `bad-qc-block` checks below when replaying blocks after the crash if (m_chainstate.m_chain.Tip() == nullptr) { @@ -256,20 +261,29 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH } if (quorumHash.IsNull()) { - LogPrint(BCLog::LLMQ, "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s quorumHash is null.\n", __func__, - nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); + LogPrint(BCLog::LLMQ, /* Continued */ + "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, " + "quorumPublicKey=%s quorumHash is null.\n", + __func__, nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), + qc.CountValidMembers(), qc.quorumPublicKey.ToString()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-block"); } if (quorumHash != qc.quorumHash) { - LogPrint(BCLog::LLMQ, "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, qc.quorumHash=%s signers=%s, validMembers=%d, quorumPublicKey=%s non equal quorumHash.\n", __func__, - nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); + LogPrint(BCLog::LLMQ, /* Continued */ + "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, qc.quorumHash=%s signers=%s, " + "validMembers=%d, quorumPublicKey=%s non equal quorumHash.\n", + __func__, nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), + qc.quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-block"); } if (qc.IsNull()) { if (!qc.VerifyNull()) { - LogPrint(BCLog::LLMQ, "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%dqc verifynull failed.\n", __func__, - nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers()); + LogPrint(BCLog::LLMQ, /* Continued */ + "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%dqc " + "verifynull failed.\n", + __func__, nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), + qc.CountSigners(), qc.CountValidMembers()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-invalid-null"); } return true; @@ -288,8 +302,11 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH const auto* pQuorumBaseBlockIndex = m_chainstate.m_blockman.LookupBlockIndex(qc.quorumHash); if (!qc.Verify(m_dmnman, m_qsnapman, pQuorumBaseBlockIndex, /*checkSigs=*/fBLSChecks)) { - LogPrint(BCLog::LLMQ, "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s qc verify failed.\n", __func__, - nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); + LogPrint(BCLog::LLMQ, /* Continued */ + "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, " + "quorumPublicKey=%s qc verify failed.\n", + __func__, nHeight, ToUnderlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), + qc.CountValidMembers(), qc.quorumPublicKey.ToString()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-invalid"); } @@ -300,8 +317,8 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH bool rotation_enabled = IsQuorumRotationEnabled(llmq_params, pQuorumBaseBlockIndex); if (rotation_enabled) { - LogPrint(BCLog::LLMQ, "%s -- height[%d] pQuorumBaseBlockIndex[%d] quorumIndex[%d] qversion[%d] Built\n", __func__, - nHeight, pQuorumBaseBlockIndex->nHeight, qc.quorumIndex, qc.nVersion); + LogPrint(BCLog::LLMQ, "%s -- height[%d] pQuorumBaseBlockIndex[%d] quorumIndex[%d] qversion[%d] Built\n", + __func__, nHeight, pQuorumBaseBlockIndex->nHeight, qc.quorumIndex, qc.nVersion); } // Store commitment in DB From 1e3d0fe2bd62e15d1fa64d0b12d5a86331d00e9d Mon Sep 17 00:00:00 2001 From: PastaBot <156604295+DashCoreAutoGuix@users.noreply.github.com> Date: Sat, 26 Jul 2025 17:00:24 -0500 Subject: [PATCH 023/244] Merge bitcoin/bitcoin#23897: refactor: Move calculation logic out from `CheckSequenceLocksAtTip()` Co-authored-by: glozow --- src/test/miner_tests.cpp | 4 +- src/validation.cpp | 177 ++++++++++++++++++++++----------------- src/validation.h | 36 +++++--- 3 files changed, 129 insertions(+), 88 deletions(-) diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 8447a5e927da..bcd497bb320b 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -39,7 +39,9 @@ struct MinerTestingSetup : public TestingSetup { bool TestSequenceLocks(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs) { CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool); - return CheckSequenceLocksAtTip(m_node.chainman->ActiveChain().Tip(), view_mempool, tx); + CBlockIndex* tip{m_node.chainman->ActiveChain().Tip()}; + const std::optional lock_points{CalculateLockPointsAtTip(tip, view_mempool, tx)}; + return lock_points.has_value() && CheckSequenceLocksAtTip(tip, *lock_points); } BlockAssembler AssemblerForTest(const CChainParams& params); }; diff --git a/src/validation.cpp b/src/validation.cpp index 823c2ca5d354..69c24d278480 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -185,11 +185,89 @@ bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& return IsFinalTx(tx, nBlockHeight, nBlockTime); } +namespace { +/** + * A helper which calculates heights of inputs of a given transaction. + * + * @param[in] tip The current chain tip. If an input belongs to a mempool + * transaction, we assume it will be confirmed in the next block. + * @param[in] coins Any CCoinsView that provides access to the relevant coins. + * @param[in] tx The transaction being evaluated. + * + * @returns A vector of input heights or nullopt, in case of an error. + */ +std::optional> CalculatePrevHeights( + const CBlockIndex& tip, + const CCoinsView& coins, + const CTransaction& tx) +{ + std::vector prev_heights; + prev_heights.resize(tx.vin.size()); + for (size_t i = 0; i < tx.vin.size(); ++i) { + const CTxIn& txin = tx.vin[i]; + Coin coin; + if (!coins.GetCoin(txin.prevout, coin)) { + LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, i, tx.GetHash().GetHex()); + return std::nullopt; + } + if (coin.nHeight == MEMPOOL_HEIGHT) { + // Assume all mempool transaction confirm in the next block. + prev_heights[i] = tip.nHeight + 1; + } else { + prev_heights[i] = coin.nHeight; + } + } + return prev_heights; +} +} // namespace + +std::optional CalculateLockPointsAtTip( + CBlockIndex* tip, + const CCoinsView& coins_view, + const CTransaction& tx) +{ + assert(tip); + + auto prev_heights{CalculatePrevHeights(*tip, coins_view, tx)}; + if (!prev_heights.has_value()) return std::nullopt; + + CBlockIndex next_tip; + next_tip.pprev = tip; + // 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 active_chainstate.m_chain.Height() + next_tip.nHeight = tip->nHeight + 1; + const auto [min_height, min_time] = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prev_heights.value(), next_tip); + + // Also store the hash of the block with the highest height of + // all the blocks which have sequence locked prevouts. + // This hash needs to still be on the chain + // for these LockPoint calculations to be valid + // Note: It is impossible to correctly calculate a maxInputBlock + // if any of the sequence locked inputs depend on unconfirmed txs, + // except in the special case where the relative lock time/height + // is 0, which is equivalent to no sequence lock. Since we assume + // input height of tip+1 for mempool txs and test the resulting + // min_height and min_time from CalculateSequenceLocks against tip+1. + int max_input_height{0}; + for (const int height : prev_heights.value()) { + // Can ignore mempool inputs since we'll fail if they had non-zero locks + if (height != next_tip.nHeight) { + max_input_height = std::max(max_input_height, height); + } + } + + // tip->GetAncestor(max_input_height) should never return a nullptr + // because max_input_height is always less than the tip height. + // It would, however, be a bad bug to continue execution, since a + // LockPoints object with the maxInputBlock member set to nullptr + // signifies no relative lock time. + return LockPoints{min_height, min_time, Assert(tip->GetAncestor(max_input_height))}; +} + bool CheckSequenceLocksAtTip(CBlockIndex* tip, - const CCoinsView& coins_view, - const CTransaction& tx, - LockPoints* lp, - bool useExistingLockPoints) + const LockPoints& lock_points) { assert(tip != nullptr); @@ -203,61 +281,7 @@ bool CheckSequenceLocksAtTip(CBlockIndex* tip, // *next* block, we need to use one more than active_chainstate.m_chain.Height() index.nHeight = tip->nHeight + 1; - std::pair lockPair; - if (useExistingLockPoints) { - assert(lp); - lockPair.first = lp->height; - lockPair.second = lp->time; - } - else { - std::vector prevheights; - prevheights.resize(tx.vin.size()); - for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) { - const CTxIn& txin = tx.vin[txinIndex]; - Coin coin; - if (!coins_view.GetCoin(txin.prevout, coin)) { - return error("%s: Missing input", __func__); - } - if (coin.nHeight == MEMPOOL_HEIGHT) { - // Assume all mempool transaction confirm in the next block - prevheights[txinIndex] = tip->nHeight + 1; - } else { - prevheights[txinIndex] = coin.nHeight; - } - } - lockPair = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prevheights, index); - if (lp) { - lp->height = lockPair.first; - lp->time = lockPair.second; - // Also store the hash of the block with the highest height of - // all the blocks which have sequence locked prevouts. - // This hash needs to still be on the chain - // for these LockPoint calculations to be valid - // Note: It is impossible to correctly calculate a maxInputBlock - // if any of the sequence locked inputs depend on unconfirmed txs, - // except in the special case where the relative lock time/height - // is 0, which is equivalent to no sequence lock. Since we assume - // input height of tip+1 for mempool txs and test the resulting - // lockPair from CalculateSequenceLocks against tip+1. We know - // EvaluateSequenceLocks will fail if there was a non-zero sequence - // lock on a mempool input, so we can use the return value of - // CheckSequenceLocksAtTip to indicate the LockPoints validity - int maxInputHeight = 0; - for (const int height : prevheights) { - // Can ignore mempool inputs since we'll fail if they had non-zero locks - if (height != tip->nHeight+1) { - maxInputHeight = std::max(maxInputHeight, height); - } - } - // tip->GetAncestor(maxInputHeight) should never return a nullptr - // because maxInputHeight is always less than the tip height. - // It would, however, be a bad bug to continue execution, since a - // LockPoints object with the maxInputBlock member set to nullptr - // signifies no relative lock time. - lp->maxInputBlock = Assert(tip->GetAncestor(maxInputHeight)); - } - } - return EvaluateSequenceLocks(index, lockPair); + return EvaluateSequenceLocks(index, {lock_points.height, lock_points.time}); } bool GetUTXOCoin(CChainState& active_chainstate, const COutPoint& outpoint, Coin& coin) @@ -405,20 +429,23 @@ void CChainState::MaybeUpdateMempoolForReorg( // The transaction must be final. if (!CheckFinalTxAtTip(*Assert(m_chain.Tip()), tx)) return true; - LockPoints lp = it->GetLockPoints(); - const bool validLP{TestLockPointValidity(m_chain, lp)}; - CCoinsViewMemPool view_mempool(&CoinsTip(), *m_mempool); + + const LockPoints& lp = it->GetLockPoints(); // CheckSequenceLocksAtTip checks if the transaction will be final in the next block to be - // created on top of the new chain. We use useExistingLockPoints=false so that, instead of - // using the information in lp (which might now refer to a block that no longer exists in - // the chain), it will update lp to contain LockPoints relevant to the new chain. - if (!CheckSequenceLocksAtTip(m_chain.Tip(), view_mempool, tx, &lp, validLP)) { - // If CheckSequenceLocksAtTip fails, remove the tx and don't depend on the LockPoints. - return true; - } else if (!validLP) { - // If CheckSequenceLocksAtTip succeeded, it also updated the LockPoints. - // Now update the mempool entry lockpoints as well. - m_mempool->mapTx.modify(it, [&lp](CTxMemPoolEntry& e) { e.UpdateLockPoints(lp); }); + // created on top of the new chain. + if (TestLockPointValidity(m_chain, lp)) { + if (!CheckSequenceLocksAtTip(m_chain.Tip(), lp)) { + return true; + } + } else { + const CCoinsViewMemPool view_mempool{&CoinsTip(), *m_mempool}; + const std::optional new_lock_points{CalculateLockPointsAtTip(m_chain.Tip(), view_mempool, tx)}; + if (new_lock_points.has_value() && CheckSequenceLocksAtTip(m_chain.Tip(), *new_lock_points)) { + // Now update the mempool entry lockpoints as well. + m_mempool->mapTx.modify(it, [&new_lock_points](CTxMemPoolEntry& e) { e.UpdateLockPoints(*new_lock_points); }); + } else { + return true; + } } // If the transaction spends any coinbase outputs, it must be mature. @@ -781,7 +808,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) } } } - LockPoints lp; m_view.SetBackend(m_viewmempool); const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip(); @@ -823,7 +849,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // be mined yet. // Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's // backend was removed, it no longer pulls coins from the mempool. - if (!CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx, &lp)) { + const std::optional lock_points{CalculateLockPointsAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx)}; + if (!lock_points.has_value() || !CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), *lock_points)) { return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final"); } @@ -854,7 +881,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) } entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), - fSpendsCoinbase, nSigOps, lp)); + fSpendsCoinbase, nSigOps, lock_points.value())); ws.m_vsize = entry->GetTxSize(); if (nSigOps > MAX_STANDARD_TX_SIGOPS) diff --git a/src/validation.h b/src/validation.h index 814a75d842aa..6eb7608491f3 100644 --- a/src/validation.h +++ b/src/validation.h @@ -281,27 +281,39 @@ int GetUTXOConfirmations(CChainState& active_chainstate, const COutPoint& outpoi bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); /** - * Check if transaction will be BIP68 final in the next block to be created on top of tip. - * @param[in] tip Chain tip to check tx sequence locks against. For example, - * the tip of the current active chain. + * Calculate LockPoints required to check if transaction will be BIP68 final in the next block + * to be created on top of tip. + * + * @param[in] tip Chain tip for which tx sequence locks are calculated. For + * example, the tip of the current active chain. * @param[in] coins_view Any CCoinsView that provides access to the relevant coins for * checking sequence locks. For example, it can be a CCoinsViewCache * that isn't connected to anything but contains all the relevant * coins, or a CCoinsViewMemPool that is connected to the - * mempool and chainstate UTXO set. In the latter case, the caller is - * responsible for holding the appropriate locks to ensure that + * mempool and chainstate UTXO set. In the latter case, the caller + * is responsible for holding the appropriate locks to ensure that * calls to GetCoin() return correct coins. + * @param[in] tx The transaction being evaluated. + * + * @returns The resulting height and time calculated and the hash of the block needed for + * calculation, or std::nullopt if there is an error. + */ +std::optional CalculateLockPointsAtTip( + CBlockIndex* tip, + const CCoinsView& coins_view, + const CTransaction& tx); + +/** + * Check if transaction will be BIP68 final in the next block to be created on top of tip. + * @param[in] tip Chain tip to check tx sequence locks against. For example, + * the tip of the current active chain. + * @param[in] lock_points LockPoints containing the height and time at which this + * transaction is final. * Simulates calling SequenceLocks() with data from the tip passed in. - * Optionally stores in LockPoints the resulting height and time calculated and the hash - * of the block needed for calculation or skips the calculation and uses the LockPoints - * passed in for evaluation. * The LockPoints should not be considered valid if CheckSequenceLocksAtTip returns false. */ bool CheckSequenceLocksAtTip(CBlockIndex* tip, - const CCoinsView& coins_view, - const CTransaction& tx, - LockPoints* lp = nullptr, - bool useExistingLockPoints = false); + const LockPoints& lock_points); /** * Closure representing one script verification From 3f8f85df4a5d18437f8ebc60ade8e21ecc2d62f4 Mon Sep 17 00:00:00 2001 From: PastaBot <156604295+DashCoreAutoGuix@users.noreply.github.com> Date: Sat, 26 Jul 2025 17:09:21 -0500 Subject: [PATCH 024/244] Merge bitcoin/bitcoin#27221: test: Default timeout factor to 4 under --valgrind Co-authored-by: fanquake --- test/functional/test_framework/test_framework.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 8b325b480bd4..452b3929f5bd 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -148,8 +148,6 @@ def __init__(self): print("DEPRECATED: --timeoutscale option is no longer available, please use --timeout-factor instead") if self.options.timeout_factor == 1: self.options.timeout_factor = self.options.timeout_scale - if self.options.timeout_factor == 0 : - self.options.timeout_factor = 99999 self.rpc_timeout = int(self.rpc_timeout * self.options.timeout_factor) # optionally, increase timeout by a factor def main(self): @@ -223,7 +221,7 @@ def parse_args(self): help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown, valgrind 3.14 or later required. Does not apply to previous release binaries.") parser.add_argument("--randomseed", type=int, help="set a random seed for deterministically reproducing a previous test run") - parser.add_argument('--timeout-factor', dest="timeout_factor", type=float, default=1.0, help='adjust test timeouts by a factor. Setting it to 0 disables all timeouts') + parser.add_argument("--timeout-factor", dest="timeout_factor", type=float, help="adjust test timeouts by a factor. Setting it to 0 disables all timeouts") parser.add_argument("--v2transport", dest="v2transport", default=False, action="store_true", help="use BIP324 v2 connections between all nodes by default") parser.add_argument("--v1transport", dest="v1transport", default=False, action="store_true", @@ -237,6 +235,9 @@ def parse_args(self): self.add_options(parser) self.options = parser.parse_args() + if self.options.timeout_factor == 0: + self.options.timeout_factor = 99999 + self.options.timeout_factor = self.options.timeout_factor or (4 if self.options.valgrind else 1) self.options.previous_releases_path = previous_releases_path config = configparser.ConfigParser() From 4a8c861c33267323967f2ba9ee4a77ec9dd6f4a6 Mon Sep 17 00:00:00 2001 From: fanquake Date: Sun, 19 Mar 2023 12:12:17 +0000 Subject: [PATCH 025/244] Merge bitcoin/bitcoin#25666: refactor: wallet, do not translate init options names e43a547a3674a31504a60ede9b4912e014a54139 refactor: wallet, do not translate init arguments names (furszy) Pull request description: Simple, and not interesting, refactor that someone has to do sooner or later. We are translating some init arguments names when those shouldn't be translated. ACKs for top commit: achow101: ACK e43a547a3674a31504a60ede9b4912e014a54139 MarcoFalke: lgtm ACK e43a547a3674a31504a60ede9b4912e014a54139 ryanofsky: Code review ACK e43a547a3674a31504a60ede9b4912e014a54139. Just rebased since last review. Tree-SHA512: c6eca98fd66d54d5510de03ab4e63c00ba2838af4237d2bb135d01c47f8ad8ca9aa7ae1e45cf668afcfb9dd958b075a1756cc887b3beef2cb494933d4d83eab0 --- src/wallet/spend.cpp | 2 +- src/wallet/wallet.cpp | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 71ed77e58de4..5aa4e1f65e78 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -778,7 +778,7 @@ static std::optional CreateTransactionInternal( } if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) { // eventually allow a fallback fee - error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); + error = strprintf(_("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable %s."), "-fallbackfee"); return std::nullopt; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6979c5d49fae..78efc16f35e8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2971,7 +2971,7 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri if (args.IsArgSet("-fallbackfee")) { std::optional fallback_fee = ParseMoney(args.GetArg("-fallbackfee", "")); if (!fallback_fee) { - error = strprintf(_("Invalid amount for -fallbackfee=: '%s'"), args.GetArg("-fallbackfee", "")); + error = strprintf(_("Invalid amount for %s=: '%s'"), "-fallbackfee", args.GetArg("-fallbackfee", "")); return nullptr; } else if (fallback_fee.value() > HIGH_TX_FEE_PER_KB) { warnings.push_back(AmountHighWarn("-fallbackfee") + Untranslated(" ") + @@ -2985,7 +2985,7 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri if (args.IsArgSet("-discardfee")) { std::optional discard_fee = ParseMoney(args.GetArg("-discardfee", "")); if (!discard_fee) { - error = strprintf(_("Invalid amount for -discardfee=: '%s'"), args.GetArg("-discardfee", "")); + error = strprintf(_("Invalid amount for %s=: '%s'"), "-discardfee", args.GetArg("-discardfee", "")); return nullptr; } else if (discard_fee.value() > HIGH_TX_FEE_PER_KB) { warnings.push_back(AmountHighWarn("-discardfee") + Untranslated(" ") + @@ -3005,8 +3005,8 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri } walletInstance->m_pay_tx_fee = CFeeRate{pay_tx_fee.value(), 1000}; if (chain && walletInstance->m_pay_tx_fee < chain->relayMinFee()) { - error = strprintf(_("Invalid amount for -paytxfee=: '%s' (must be at least %s)"), - args.GetArg("-paytxfee", ""), chain->relayMinFee().ToString()); + error = strprintf(_("Invalid amount for %s=: '%s' (must be at least %s)"), + "-paytxfee", args.GetArg("-paytxfee", ""), chain->relayMinFee().ToString()); return nullptr; } } @@ -3017,11 +3017,11 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri error = AmountErrMsg("maxtxfee", args.GetArg("-maxtxfee", "")); return nullptr; } else if (max_fee.value() > HIGH_MAX_TX_FEE) { - warnings.push_back(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction.")); + warnings.push_back(strprintf(_("%s is set very high! Fees this large could be paid on a single transaction."), "-maxtxfee")); } if (chain && CFeeRate{max_fee.value(), 1000} < chain->relayMinFee()) { - error = strprintf(_("Invalid amount for -maxtxfee=: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"), - args.GetArg("-maxtxfee", ""), chain->relayMinFee().ToString()); + error = strprintf(_("Invalid amount for %s=: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"), + "-maxtxfee", args.GetArg("-maxtxfee", ""), chain->relayMinFee().ToString()); return nullptr; } From f1c060db14fb4b11fa007526f2ed68b101b870a7 Mon Sep 17 00:00:00 2001 From: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> Date: Sat, 26 Jul 2025 18:27:59 -0500 Subject: [PATCH 026/244] Merge bitcoin/bitcoin#27902: fuzz: wallet, add target for `CoinControl` Co-authored-by: PastaBot <156604295+DashCoreAutoGuix@users.noreply.github.com> Co-authored-by: Andrew Chow --- src/Makefile.test.include | 1 + src/wallet/test/fuzz/coincontrol.cpp | 89 ++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/wallet/test/fuzz/coincontrol.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index bed7081dd6d4..6e9ecedffe44 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -222,6 +222,7 @@ BITCOIN_TESTS += wallet/test/db_tests.cpp endif FUZZ_WALLET_SRC = \ + wallet/test/fuzz/coincontrol.cpp \ wallet/test/fuzz/coinselection.cpp if USE_SQLITE diff --git a/src/wallet/test/fuzz/coincontrol.cpp b/src/wallet/test/fuzz/coincontrol.cpp new file mode 100644 index 000000000000..4a47b33f71c9 --- /dev/null +++ b/src/wallet/test/fuzz/coincontrol.cpp @@ -0,0 +1,89 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include + +namespace wallet { +namespace { + +const TestingSetup* g_setup; + +void initialize_coincontrol() +{ + static const auto testing_setup = MakeNoLogFileContext(); + g_setup = testing_setup.get(); +} + +FUZZ_TARGET(coincontrol, .init = initialize_coincontrol) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const auto& node = g_setup->m_node; + ArgsManager& args = *node.args; + + // for GetBoolArg to return true sometimes + args.ForceSetArg("-avoidpartialspends", fuzzed_data_provider.ConsumeBool()?"1":"0"); + + CCoinControl coin_control; + COutPoint out_point; + + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) + { + CallOneOf( + fuzzed_data_provider, + [&] { + std::optional optional_out_point = ConsumeDeserializable(fuzzed_data_provider); + if (!optional_out_point) { + return; + } + out_point = *optional_out_point; + }, + [&] { + (void)coin_control.HasSelected(); + }, + [&] { + (void)coin_control.IsSelected(out_point); + }, + [&] { + (void)coin_control.IsExternalSelected(out_point); + }, + [&] { + CTxOut txout; + (void)coin_control.GetExternalOutput(out_point, txout); + }, + [&] { + (void)coin_control.Select(out_point); + }, + [&] { + const CTxOut tx_out{ConsumeMoney(fuzzed_data_provider), ConsumeScript(fuzzed_data_provider)}; + (void)coin_control.SelectExternal(out_point, tx_out); + }, + [&] { + (void)coin_control.UnSelect(out_point); + }, + [&] { + (void)coin_control.UnSelectAll(); + }, + [&] { + std::vector selected; + coin_control.ListSelected(selected); + }, + [&] { + int64_t weight{fuzzed_data_provider.ConsumeIntegral()}; + (void)coin_control.SetInputWeight(out_point, weight); + }, + [&] { + // Condition to avoid the assertion in GetInputWeight + if (coin_control.HasInputWeight(out_point)) { + (void)coin_control.GetInputWeight(out_point); + } + }); + } +} +} // namespace +} // namespace wallet From 587294fddee5f14e51859d289ba89ab3ec4ab384 Mon Sep 17 00:00:00 2001 From: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> Date: Sat, 26 Jul 2025 18:33:36 -0500 Subject: [PATCH 027/244] Merge bitcoin/bitcoin#24851: init: ignore BIP-30 verification in DisconnectBlock for problematic blocks Co-authored-by: Andrew Chow --- src/validation.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/validation.cpp b/src/validation.cpp index 69c24d278480..29ce4c2184eb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1957,11 +1957,21 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI return DISCONNECT_FAILED; } + // Ignore blocks that contain transactions which are 'overwritten' by later transactions, + // unless those are already completely spent. + // See https://github.com/bitcoin/bitcoin/issues/22596 for additional information. + // Note: the blocks specified here are different than the ones used in ConnectBlock because DisconnectBlock + // unwinds the blocks in reverse. As a result, the inconsistency is not discovered until the earlier + // blocks with the duplicate coinbase transactions are disconnected. + bool fEnforceBIP30 = !((pindex->nHeight==91722 && pindex->GetBlockHash() == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) || + (pindex->nHeight==91812 && pindex->GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f"))); + // undo transactions in reverse order for (int i = block.vtx.size() - 1; i >= 0; i--) { const CTransaction &tx = *(block.vtx[i]); uint256 hash = tx.GetHash(); bool is_coinbase = tx.IsCoinBase(); + bool is_bip30_exception = (is_coinbase && !fEnforceBIP30); if (fAddressIndex) { for (unsigned int k = tx.vout.size(); k-- > 0;) { @@ -1990,7 +2000,9 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI Coin coin; bool is_spent = view.SpendCoin(out, &coin); if (!is_spent || tx.vout[o] != coin.out || pindex->nHeight != coin.nHeight || is_coinbase != coin.fCoinBase) { - fClean = false; // transaction output mismatch + if (!is_bip30_exception) { + fClean = false; // transaction output mismatch + } } } } From 2d27f12c7397466169fa6df4acb00ef093a72083 Mon Sep 17 00:00:00 2001 From: PastaBot <156604295+DashCoreAutoGuix@users.noreply.github.com> Date: Sat, 26 Jul 2025 18:34:26 -0500 Subject: [PATCH 028/244] Merge bitcoin/bitcoin#26321: Adjust `.tx/config` for new Transifex CLI Co-authored-by: fanquake --- .tx/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tx/config b/.tx/config index 7699aee5fffe..a9939bf4f9cd 100644 --- a/.tx/config +++ b/.tx/config @@ -1,7 +1,7 @@ [main] host = https://www.transifex.com -[dash.dash_ents] +[o:dash:p:dash:r:dash_ents] file_filter = src/qt/locale/dash_.ts source_file = src/qt/locale/dash_en.xlf source_lang = en From 9ccf84340ef4457127bcb1cc0d0318ba8e6cd48f Mon Sep 17 00:00:00 2001 From: PastaBot <156604295+DashCoreAutoGuix@users.noreply.github.com> Date: Sat, 26 Jul 2025 18:39:16 -0500 Subject: [PATCH 029/244] Merge bitcoin/bitcoin#26259: test: Test year 2106 block timestamps Co-authored-by: fanquake --- test/functional/mining_basic.py | 2 +- test/functional/rpc_blockchain.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index daf634b16125..a9720eea30c3 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -179,7 +179,7 @@ def assert_submitblock(block, result_str_1, result_str_2=None): self.log.info("getblocktemplate: Test bad timestamps") bad_block = copy.deepcopy(block) - bad_block.nTime = 2**31 - 1 + bad_block.nTime = 2**32 - 1 assert_template(node, bad_block, 'time-too-new') assert_submitblock(bad_block, 'time-too-new', 'time-too-new') bad_block.nTime = 0 diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index b933d359f58d..d18c366d0476 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -92,6 +92,7 @@ def run_test(self): self._test_stopatheight() self._test_waitforblockheight() self._test_getblock() + self._test_y2106() assert self.nodes[0].verifychain(4, 0) def mine_chain(self): @@ -247,6 +248,14 @@ def _test_getblockchaininfo(self): 'active': False}, }) + def _test_y2106(self): + self.log.info("Check that block timestamps work until year 2106") + self.generate(self.nodes[0], 8)[-1] + time_2106 = 2**32 - 1 + self.nodes[0].setmocktime(time_2106) + last = self.generate(self.nodes[0], 6)[-1] + assert_equal(self.nodes[0].getblockheader(last)["mediantime"], time_2106) + def _test_getchaintxstats(self): self.log.info("Test getchaintxstats") From a8c09bcf2b728608d6f7c1046464f4ab1ad9f7ac Mon Sep 17 00:00:00 2001 From: PastaBot <156604295+DashCoreAutoGuix@users.noreply.github.com> Date: Tue, 29 Jul 2025 13:37:08 -0500 Subject: [PATCH 030/244] Merge bitcoin/bitcoin#25491: wallet: use Mutex for g_sqlite_mutex instead of GlobalMutex Co-authored-by: Andrew Chow Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> --- src/wallet/sqlite.cpp | 8 +++++--- src/wallet/sqlite.h | 12 +++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp index fc0fe8ff9e67..2e31688712c2 100644 --- a/src/wallet/sqlite.cpp +++ b/src/wallet/sqlite.cpp @@ -23,9 +23,6 @@ namespace wallet { static constexpr int32_t WALLET_SCHEMA_VERSION = 0; -static Mutex g_sqlite_mutex; -static int g_sqlite_count GUARDED_BY(g_sqlite_mutex) = 0; - static Span SpanFromBlob(sqlite3_stmt* stmt, int col) { return {reinterpret_cast(sqlite3_column_blob(stmt, col)), @@ -104,6 +101,9 @@ static void SetPragma(sqlite3* db, const std::string& key, const std::string& va } } +Mutex SQLiteDatabase::g_sqlite_mutex; +int SQLiteDatabase::g_sqlite_count = 0; + SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock) : WalletDatabase(), m_mock(mock), m_dir_path(fs::PathToString(dir_path)), m_file_path(fs::PathToString(file_path)), m_use_unsafe_sync(options.use_unsafe_sync) { @@ -167,6 +167,8 @@ SQLiteDatabase::~SQLiteDatabase() void SQLiteDatabase::Cleanup() noexcept { + AssertLockNotHeld(g_sqlite_mutex); + Close(); LOCK(g_sqlite_mutex); diff --git a/src/wallet/sqlite.h b/src/wallet/sqlite.h index 47b7ebb0ecde..d7135679ff8a 100644 --- a/src/wallet/sqlite.h +++ b/src/wallet/sqlite.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_WALLET_SQLITE_H #define BITCOIN_WALLET_SQLITE_H +#include #include #include @@ -63,7 +64,16 @@ class SQLiteDatabase : public WalletDatabase const std::string m_file_path; - void Cleanup() noexcept; + /** + * This mutex protects SQLite initialization and shutdown. + * sqlite3_config() and sqlite3_shutdown() are not thread-safe (sqlite3_initialize() is). + * Concurrent threads that execute SQLiteDatabase::SQLiteDatabase() should have just one + * of them do the init and the rest wait for it to complete before all can proceed. + */ + static Mutex g_sqlite_mutex; + static int g_sqlite_count GUARDED_BY(g_sqlite_mutex); + + void Cleanup() noexcept EXCLUSIVE_LOCKS_REQUIRED(!g_sqlite_mutex); public: SQLiteDatabase() = delete; From fc68188e3227dc0912ef3c508f6d45ff1d7614ee Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Wed, 22 Jun 2022 20:44:43 +0800 Subject: [PATCH 031/244] merge bitcoin#25447: add low-level target for txorphanage --- ci/dash/lint-tidy.sh | 1 + src/Makefile.test.include | 1 + src/test/fuzz/txorphan.cpp | 146 +++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 src/test/fuzz/txorphan.cpp diff --git a/ci/dash/lint-tidy.sh b/ci/dash/lint-tidy.sh index 7786a58f7ef9..f6fcaf19e06d 100755 --- a/ci/dash/lint-tidy.sh +++ b/ci/dash/lint-tidy.sh @@ -20,6 +20,7 @@ iwyu_tool.py \ "src/init" \ "src/rpc/fees.cpp" \ "src/rpc/signmessage.cpp" \ + "src/test/fuzz/txorphan.cpp" \ "src/util/bip32.cpp" \ "src/util/bytevectorhash.cpp" \ "src/util/error.cpp" \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index bed7081dd6d4..010fdfeb35bc 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -363,6 +363,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/tx_in.cpp \ test/fuzz/tx_out.cpp \ test/fuzz/tx_pool.cpp \ + test/fuzz/txorphan.cpp \ test/fuzz/utxo_snapshot.cpp \ test/fuzz/validation_load_mempool.cpp \ test/fuzz/versionbits.cpp diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp new file mode 100644 index 000000000000..47e6dbfc825e --- /dev/null +++ b/src/test/fuzz/txorphan.cpp @@ -0,0 +1,146 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include