From 6e657b488428ab55bb9719806033fcbcef6462f5 Mon Sep 17 00:00:00 2001 From: pasta Date: Mon, 3 Aug 2026 13:49:13 -0500 Subject: [PATCH 1/3] refactor(utils): let FundTransaction return change to a separate script FundTransaction always paid the change back to the payout script, which only works when that script is spendable. A test funding a governance proposal fee has to burn the amount to an OP_RETURN, so the change needs its own destination. --- src/test/util/masternode.cpp | 8 +++++++- src/test/util/masternode.h | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/util/masternode.cpp b/src/test/util/masternode.cpp index a40ad701d641..0551591a2f94 100644 --- a/src/test/util/masternode.cpp +++ b/src/test/util/masternode.cpp @@ -66,6 +66,12 @@ SimpleUTXOMap BuildSimpleUtxoMap(const std::vector& txs) SimpleUTXOMap FundTransaction(const ChainstateManager& chainman, CMutableTransaction& tx, SimpleUTXOMap& utxos, const CScript& script_payout, CAmount amount) +{ + return FundTransaction(chainman, tx, utxos, script_payout, amount, /*script_change=*/script_payout); +} + +SimpleUTXOMap FundTransaction(const ChainstateManager& chainman, CMutableTransaction& tx, SimpleUTXOMap& utxos, + const CScript& script_payout, CAmount amount, const CScript& script_change) { CAmount change; auto inputs = WITH_LOCK(::cs_main, return SelectUTXOs(chainman.ActiveChain(), utxos, amount, change)); @@ -74,7 +80,7 @@ SimpleUTXOMap FundTransaction(const ChainstateManager& chainman, CMutableTransac } tx.vout.emplace_back(amount, script_payout); if (change != 0) { - tx.vout.emplace_back(change, script_payout); + tx.vout.emplace_back(change, script_change); } return inputs; } diff --git a/src/test/util/masternode.h b/src/test/util/masternode.h index 0a894e157a2c..63b715ae3d5a 100644 --- a/src/test/util/masternode.h +++ b/src/test/util/masternode.h @@ -20,6 +20,9 @@ using SimpleUTXOMap = std::map; SimpleUTXOMap BuildSimpleUtxoMap(const std::vector& txs); SimpleUTXOMap FundTransaction(const ChainstateManager& chainman, CMutableTransaction& tx, SimpleUTXOMap& utxos, const CScript& script_payout, CAmount amount); +//! Overload for payout scripts that cannot receive the change, e.g. an OP_RETURN burn. +SimpleUTXOMap FundTransaction(const ChainstateManager& chainman, CMutableTransaction& tx, SimpleUTXOMap& utxos, + const CScript& script_payout, CAmount amount, const CScript& script_change); void SignTransaction(CMutableTransaction& tx, const SimpleUTXOMap& coins, const CKey& coinbase_key); CMutableTransaction CreateProRegTx(const ChainstateManager& chainman, SimpleUTXOMap& utxos, int port, const CScript& script_payout, const CKey& coinbase_key, CKey& owner_key_ret, From b7f0cde6edd5203864cf8fde8d0c4313f77d2d36 Mon Sep 17 00:00:00 2001 From: pasta Date: Mon, 3 Aug 2026 13:49:21 -0500 Subject: [PATCH 2/3] test: cover the governance vote signature path with a chain-backed fixture The existing governance unit tests run on a fixture with no chain, so the tip masternode list is empty and every CGovernanceVote::IsValid() call short-circuits at GetMNByCollateral before any signature is verified. Nothing exercised CheckSignature, and nothing proved that a legitimately signed vote is accepted at all. Add a fixture that mines a regtest chain, registers a masternode via a real ProRegTx and keeps its voting (ECDSA) and operator (BLS) keys, so votes can be signed for real. On top of it: an orphan vote (parent object unknown) from a registered masternode is cached, requested and replayed onto the object once its fee collateral confirms and the proposal arrives; forged, unknown-masternode and future-dated votes are rejected with a peer penalty; and funding votes on a proposal are accepted only from the voting key while other signals accept the operator key. Verified by mutation: inverting a masternode/signature gate in front of the orphan cache fails the orphan test, and making CheckSignature always succeed fails the rejection tests. --- src/Makefile.test.include | 1 + src/test/governance_vote_processing_tests.cpp | 365 ++++++++++++++++++ 2 files changed, 366 insertions(+) create mode 100644 src/test/governance_vote_processing_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index faaee5aa1913..05b74d0329b9 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -123,6 +123,7 @@ BITCOIN_TESTS =\ test/governance_inv_tests.cpp \ test/governance_superblock_tests.cpp \ test/governance_validators_tests.cpp \ + test/governance_vote_processing_tests.cpp \ test/governance_vote_wire_tests.cpp \ test/coinjoin_inouts_tests.cpp \ test/coinjoin_dstxmanager_tests.cpp \ diff --git a/src/test/governance_vote_processing_tests.cpp b/src/test/governance_vote_processing_tests.cpp new file mode 100644 index 000000000000..8f33b318ae28 --- /dev/null +++ b/src/test/governance_vote_processing_tests.cpp @@ -0,0 +1,365 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include