Skip to content

Commit c5758ca

Browse files
Merge #7349: fix: deduplicate QRINFO base blocks
470ec0e refactor: clarify QRINFO base-block sorting (PastaClaw) 9f30867 fix: scope QRINFO base-block dedupe to non-legacy construction (PastaClaw) e34105f fix: deduplicate QRINFO base blocks (PastaClaw) Pull request description: ## Issue being fixed or feature implemented QRINFO request handling accepts caller-provided base block hashes and carries the validated block indexes through quorum rotation response construction. Repeated base hashes do not add useful information, so this change canonicalizes those base indexes before the builder phases use them. Provenance: thepastaclaw/tracker#1450 ## What was done? - Sort validated QRINFO base block indexes by height for both construction paths. - Remove duplicate base block index pointers before response construction. - Add focused unit coverage for repeated base block selection behavior. - Add RPC functional coverage showing repeated base hashes produce the same result as a single base hash. ## How Has This Been Tested? Environment: macOS arm64, local Dash Core worktree based on `upstream/develop`. Commands run: - `git diff --check upstream/develop..HEAD` — passed - `make -C src -j$(sysctl -n hw.ncpu) test/test_dash` — passed earlier in this worktree before the final Python-only test addition - `src/test/test_dash --run_test=llmq_snapshot_tests/get_last_base_block_hash_repeated_base_blocks_test` — passed - `test/functional/feature_llmq_rotation.py` — skipped locally because this worktree was configured with `--disable-wallet` - `code-review dashpay/dash upstream/develop fix/qrinfo-dedupe-base-hashes ...` — passed with recommendation: ship Also checked: - `gh api repos/dashpay/dash --jq '.default_branch'` — `develop` - Open PR search for duplicate QRINFO/baseBlockHashes dedupe work — no duplicate open PR found ## Breaking Changes None expected. ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [ ] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: UdjinM6: utACK 470ec0e PastaPastaPasta: utACK 470ec0e Tree-SHA512: e160f2d5ee0019854a9721d357ecd7d59b5b2294a264668862ab52686fc530c468cdd0dd5cba56958d640cb631f15356b28e5b5cd8db24e00c496278657de60e
2 parents ba2817f + 470ec0e commit c5758ca

3 files changed

Lines changed: 63 additions & 3 deletions

File tree

src/llmq/snapshot.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
#include <univalue.h>
1616

17+
#include <algorithm>
18+
1719
namespace {
1820
constexpr std::string_view DB_QUORUM_SNAPSHOT{"llmq_S"};
1921

@@ -74,9 +76,15 @@ bool BuildQuorumRotationInfo(CDeterministicMNManager& dmnman, CQuorumSnapshotMan
7476
}
7577
baseBlockIndexes.push_back(blockIndex);
7678
}
77-
if (use_legacy_construction) {
78-
std::sort(baseBlockIndexes.begin(), baseBlockIndexes.end(),
79-
[](const CBlockIndex* a, const CBlockIndex* b) { return a->nHeight < b->nHeight; });
79+
// Sort in all cases: the legacy path (served to peers < EFFICIENT_QRINFO_VERSION)
80+
// relies on the order for baseBlockIndexes.back() and GetLastBaseBlockHash().
81+
std::sort(baseBlockIndexes.begin(), baseBlockIndexes.end(),
82+
[](const CBlockIndex* a, const CBlockIndex* b) { return a->nHeight < b->nHeight; });
83+
if (!use_legacy_construction) {
84+
// Only deduplicate on the non-legacy path; leave the legacy path untouched so the
85+
// wire response to older peers stays bit-for-bit identical to the pre-fix behavior.
86+
baseBlockIndexes.erase(std::unique(baseBlockIndexes.begin(), baseBlockIndexes.end()),
87+
baseBlockIndexes.end());
8088
}
8189
}
8290

src/test/llmq_snapshot_tests.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <test/util/llmq_tests.h>
66
#include <test/util/setup_common.h>
77

8+
#include <chain.h>
89
#include <streams.h>
910
#include <univalue.h>
1011

@@ -176,6 +177,54 @@ BOOST_AUTO_TEST_CASE(quorum_rotation_info_construction_test)
176177
// Note: CQuorumRotationInfo serialization requires complex setup
177178
// This is better tested in functional tests
178179

180+
BOOST_AUTO_TEST_CASE(get_last_base_block_hash_repeated_base_blocks_test)
181+
{
182+
std::vector<CBlockIndex> blocks(4);
183+
std::vector<uint256> hashes{
184+
GetTestBlockHash(10),
185+
GetTestBlockHash(20),
186+
GetTestBlockHash(30),
187+
GetTestBlockHash(40),
188+
};
189+
for (size_t i{0}; i < blocks.size(); ++i) {
190+
blocks[i].nHeight = static_cast<int>((i + 1) * 10);
191+
blocks[i].phashBlock = &hashes[i];
192+
}
193+
194+
// Non-legacy: sorts internally, so unsorted input with duplicates is fine.
195+
std::vector<const CBlockIndex*> unsorted_repeated_base_blocks{
196+
&blocks[2],
197+
&blocks[0],
198+
&blocks[1],
199+
&blocks[1],
200+
};
201+
BOOST_CHECK(GetLastBaseBlockHash(unsorted_repeated_base_blocks, &blocks[3], false) == hashes[2]);
202+
BOOST_CHECK(GetLastBaseBlockHash(unsorted_repeated_base_blocks, &blocks[1], false) == hashes[1]);
203+
204+
// Legacy: relies on caller-supplied sort and tolerates duplicates as a no-op.
205+
// BuildQuorumRotationInfo deliberately does NOT deduplicate in the legacy path so
206+
// the wire response to older peers stays bit-for-bit identical; these checks
207+
// demonstrate that the duplicate is harmless to GetLastBaseBlockHash's output.
208+
std::vector<const CBlockIndex*> sorted_repeated_base_blocks{
209+
&blocks[0],
210+
&blocks[1],
211+
&blocks[1],
212+
&blocks[2],
213+
};
214+
std::vector<const CBlockIndex*> sorted_unique_base_blocks{
215+
&blocks[0],
216+
&blocks[1],
217+
&blocks[2],
218+
};
219+
BOOST_CHECK(GetLastBaseBlockHash(sorted_repeated_base_blocks, &blocks[3], true) == hashes[2]);
220+
BOOST_CHECK(GetLastBaseBlockHash(sorted_repeated_base_blocks, &blocks[1], true) == hashes[1]);
221+
// Legacy no-op proof: duplicate vs unique input produces the same hash.
222+
BOOST_CHECK(GetLastBaseBlockHash(sorted_repeated_base_blocks, &blocks[3], true) ==
223+
GetLastBaseBlockHash(sorted_unique_base_blocks, &blocks[3], true));
224+
BOOST_CHECK(GetLastBaseBlockHash(sorted_repeated_base_blocks, &blocks[1], true) ==
225+
GetLastBaseBlockHash(sorted_unique_base_blocks, &blocks[1], true));
226+
}
227+
179228
BOOST_AUTO_TEST_CASE(get_quorum_rotation_info_serialization_test)
180229
{
181230
CGetQuorumRotationInfo getInfo;

test/functional/feature_llmq_rotation.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ def run_test(self):
231231
hmc_base_blockhash = self.nodes[0].getblockhash(block_count - (block_count % 24) - 24 - 8)
232232
best_block_hash = self.nodes[0].getbestblockhash()
233233
rpc_qr_info = self.nodes[0].quorum("rotationinfo", best_block_hash, False, [hmc_base_blockhash])
234+
rpc_qr_info_repeated_base = self.nodes[0].quorum("rotationinfo", best_block_hash, False,
235+
[hmc_base_blockhash, hmc_base_blockhash])
236+
assert_equal(rpc_qr_info_repeated_base, rpc_qr_info)
234237
assert_equal(rpc_qr_info["mnListDiffTip"]["blockHash"], best_block_hash)
235238
assert_equal(rpc_qr_info["mnListDiffTip"]["baseBlockHash"], rpc_qr_info["mnListDiffH"]["blockHash"])
236239
assert_equal(rpc_qr_info["mnListDiffH"]["baseBlockHash"], rpc_qr_info["mnListDiffAtHMinusC"]["blockHash"])

0 commit comments

Comments
 (0)