From 4b5e94f7a324e9b9a4ae1dd0a1677cedd3839c20 Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 14 Oct 2026 09:29:32 -0500 Subject: [PATCH 1/5] fix(net): avoid unsigned wrap when compressing header timestamps CompressibleBlockHeader::Compress subtracted the previous header's nTime from the current one as uint32_t. A block may legitimately be timestamped earlier than its parent (only the median time past bounds it), and then the subtraction wraps. The wrapped value never fit the int16_t offset, so such headers were already sent with their full timestamp and the wire output was correct, but -fsanitize=integer builds abort on the overflow while serving headers. Only compute the offset when it cannot wrap. Headers timestamped before their parent keep being sent in full, so the encoding on the wire is unchanged and no negative offset reaches the decompressor. Co-Authored-By: Claude Opus 5.5 --- src/primitives/block.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/primitives/block.cpp b/src/primitives/block.cpp index a7ec5a0823ea..710e30cad543 100644 --- a/src/primitives/block.cpp +++ b/src/primitives/block.cpp @@ -79,10 +79,10 @@ void CompressibleBlockHeader::Compress(const std::vector::max() && time_diff >= std::numeric_limits::min()) { - time_offset = static_cast(time_diff); + // Compute compressed time diff. A header timestamped before its parent is + // sent with its full time, so the offset is never negative. + if (nTime >= last_block.nTime && nTime - last_block.nTime <= uint32_t{std::numeric_limits::max()}) { + time_offset = static_cast(nTime - last_block.nTime); bit_field.MarkAsCompressed(CompressedHeaderBitField::Flag::TIMESTAMP); } From b3304f5297159f701d822ddf0516c9bcf75681f5 Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 28 Oct 2026 22:51:32 -0500 Subject: [PATCH 2/5] test: wait for index rewinds after invalidateblock in feature_timestampindex The timestamp index removes disconnected blocks in its BlockDisconnected validation callbacks. getblockhashes calls BlockUntilSyncedToCurrentChain, which only drains the validation queue when the index is behind the tip. After invalidateblock the index is ahead of the tip, so the RPC can run before the callbacks and still return the invalidated blocks. develop's ASan job already hit this once; any other work in the same callbacks makes it more likely. Drain the validation queue before checking. Co-Authored-By: Claude Opus 5.5 --- test/functional/feature_timestampindex.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/functional/feature_timestampindex.py b/test/functional/feature_timestampindex.py index 14111a7e4719..1769bc2ac2fb 100755 --- a/test/functional/feature_timestampindex.py +++ b/test/functional/feature_timestampindex.py @@ -55,6 +55,9 @@ def run_test(self): # Invalidate the last 2 blocks on both nodes self.nodes[0].invalidateblock(blockhashes[3]) self.nodes[1].invalidateblock(blockhashes[3]) + # The index removes the blocks in its BlockDisconnected callbacks. getblockhashes only waits for an index + # that is behind the tip, not for one that is ahead of it, so wait for the callbacks here. + self.nodes[1].syncwithvalidationinterfacequeue() # Verify that invalidated blocks are no longer in the index hashes_after_invalidate = self.nodes[1].getblockhashes(high, low) From 6490d96331936806e3535dc360647cc6cc9955b1 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 13 Oct 2026 22:01:00 -0500 Subject: [PATCH 3/5] feat(index): add a governance proposal collateral index Governance looks up proposal collateral through -txindex, which can't run on a pruned node. Add GovCollateralIndex, a prune-compatible BaseIndex that keeps a small summary of every transaction burning at least the proposal fee: its block hash, whether all outputs are P2PKH or unspendable, and its OP_RETURN <32-byte hash> burns. That is everything the collateral check needs, so it keeps working after the blocks are pruned. Governance will reach the index through governance::CollateralSource, a small interface in governance/collateral.h, so that governance code doesn't include the index and the index adds no include cycles through it. The index registers its prune lock in CustomInit so that startup pruning can't delete blocks it hasn't read yet. It is not wired into init or governance yet. Co-Authored-By: Claude Opus 5.5 --- src/Makefile.am | 4 + src/Makefile.test.include | 1 + src/governance/collateral.cpp | 35 ++++++ src/governance/collateral.h | 50 ++++++++ src/governance/object.h | 2 +- src/index/govcollateralindex.cpp | 147 +++++++++++++++++++++++ src/index/govcollateralindex.h | 57 +++++++++ src/test/governance_collateral_tests.cpp | 145 ++++++++++++++++++++++ test/util/data/non-backported.txt | 2 + 9 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 src/governance/collateral.cpp create mode 100644 src/governance/collateral.h create mode 100644 src/index/govcollateralindex.cpp create mode 100644 src/index/govcollateralindex.h create mode 100644 src/test/governance_collateral_tests.cpp diff --git a/src/Makefile.am b/src/Makefile.am index ad095c971a9f..117325f569f3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -251,6 +251,7 @@ BITCOIN_CORE_H = \ external_signer.h \ flat-database.h \ flatfile.h \ + governance/collateral.h \ governance/common.h \ governance/governance.h \ governance/net_governance.h \ @@ -271,6 +272,7 @@ BITCOIN_CORE_H = \ index/blockfilterindex.h \ index/coinstatsindex.h \ index/disktxpos.h \ + index/govcollateralindex.h \ index/spentindex.h \ index/spentindex_types.h \ index/timestampindex.h \ @@ -574,6 +576,7 @@ libbitcoin_node_a_SOURCES = \ evo/specialtx_filter.cpp \ evo/specialtxman.cpp \ flatfile.cpp \ + governance/collateral.cpp \ governance/governance.cpp \ governance/net_governance.cpp \ governance/object.cpp \ @@ -590,6 +593,7 @@ libbitcoin_node_a_SOURCES = \ index/base.cpp \ index/blockfilterindex.cpp \ index/coinstatsindex.cpp \ + index/govcollateralindex.cpp \ index/spentindex.cpp \ index/timestampindex.cpp \ index/txindex.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index d59f65985291..7a180f80daa4 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -127,6 +127,7 @@ BITCOIN_TESTS =\ test/flatfile_tests.cpp \ test/fs_tests.cpp \ test/getarg_tests.cpp \ + test/governance_collateral_tests.cpp \ test/governance_inv_tests.cpp \ test/governance_superblock_tests.cpp \ test/governance_validators_tests.cpp \ diff --git a/src/governance/collateral.cpp b/src/governance/collateral.cpp new file mode 100644 index 000000000000..007275a9554f --- /dev/null +++ b/src/governance/collateral.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2026 The Dash 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