From e9a2560961c33f3a760346cf0e7d29e6d4134c53 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 13 Jun 2026 08:23:12 -0500 Subject: [PATCH 1/3] fix: require distinct coinbase outputs for duplicate superblock payments CSuperblock::IsValid matched expected payments against txNew.vout using a forward scan that re-started at the previously matched index. Two adjacent expected payments with identical scriptPubKey and amount could both match the same coinbase output, so a superblock could shortchange payees by emitting only one of two duplicate payouts. Initialize the cursor to -1 and scan from nVoutIndex + 1 so each expected payment consumes a distinct output. Adds a unit-test regression covering both the missing-output and well-formed cases. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/Makefile.test.include | 1 + src/governance/superblock.cpp | 7 +- src/test/governance_superblock_tests.cpp | 83 ++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 src/test/governance_superblock_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 375c9940d059..b7d7cc5cde14 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -119,6 +119,7 @@ BITCOIN_TESTS =\ test/flatfile_tests.cpp \ test/fs_tests.cpp \ test/getarg_tests.cpp \ + test/governance_superblock_tests.cpp \ test/governance_validators_tests.cpp \ test/coinjoin_inouts_tests.cpp \ test/coinjoin_dstxmanager_tests.cpp \ diff --git a/src/governance/superblock.cpp b/src/governance/superblock.cpp index f50538877257..bafa13fb2776 100644 --- a/src/governance/superblock.cpp +++ b/src/governance/superblock.cpp @@ -292,7 +292,7 @@ bool CSuperblock::IsValid(const CChain& active_chain, const CTransaction& txNew, return false; } - int nVoutIndex = 0; + int nVoutIndex = -1; for (int i = 0; i < nPayments; i++) { CGovernancePayment payment; if (!GetPayment(i, payment)) { @@ -303,7 +303,10 @@ bool CSuperblock::IsValid(const CChain& active_chain, const CTransaction& txNew, bool fPaymentMatch = false; - for (int j = nVoutIndex; j < nOutputs; j++) { + // Start past the previously matched output so each expected payment + // consumes a distinct vout (two adjacent payments with the same script + // and amount must match two separate outputs, not the same one twice). + for (int j = nVoutIndex + 1; j < nOutputs; j++) { // Find superblock payment fPaymentMatch = ((payment.script == txNew.vout[j].scriptPubKey) && (payment.nAmount == txNew.vout[j].nValue)); diff --git a/src/test/governance_superblock_tests.cpp b/src/test/governance_superblock_tests.cpp new file mode 100644 index 000000000000..1fafc305eee2 --- /dev/null +++ b/src/test/governance_superblock_tests.cpp @@ -0,0 +1,83 @@ +// 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 +#include +#include +#include +#include +#include