Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/impl/dash/coin/coin_state_maintainer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include <impl/dash/coin/node_coin_state.hpp> // NodeCoinState
#include <impl/dash/coin/mn_state_machine.hpp> // MNState
#include <impl/dash/coin/block.hpp> // BlockType
#include <impl/dash/coin/transaction.hpp> // MutableTransaction

#include <core/uint256.hpp>
Expand Down Expand Up @@ -93,6 +94,26 @@ class CoinStateMaintainer {
republish();
}

/// Header / think path (block connect): fold a newly-connected block's
/// special txs into the DMN list incrementally, mirroring dashcore's
/// RebuildListFromBlock (MnStateMachine::apply_block). This is the LIVE
/// driver that keeps the masternode set the embedded coinbase pays current
/// BETWEEN full mnlistdiff snapshots -- on_mn_list_update() stays the
/// authoritative resync and is UNCHANGED. MN-readiness is refreshed from
/// the post-apply list size: a block that empties the set (all collateral
/// spent) drops the bundle to the dashd fallback rather than backing a
/// template with a phantom payee. Returns apply_block's ApplyResult.
MnStateMachine::ApplyResult
on_block_connected(const dash::coin::BlockType& block, uint32_t height) {
auto r = m_state.mnstates().apply_block(block, height);
m_have_mn = m_state.mnstates().size() != 0;
if (!m_have_mn)
demote();
else
republish();
return r;
}

/// Reorg / MN-list gap / mempool flush: invalidate the live bundle so the
/// next get_work falls back to dashd until a fresh tip rebuilds it. The
/// stashed tip params are dropped -- a reorg means the old prev_hash is no
Expand Down
67 changes: 67 additions & 0 deletions test/test_dash_coin_state_maintainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <impl/dash/coin/embedded_gbt.hpp>
#include <impl/dash/coin/mn_state_machine.hpp>
#include <impl/dash/coin/mempool.hpp>
#include <impl/dash/coin/block.hpp>
#include <impl/dash/coin/utxo_adapter.hpp>
#include <impl/dash/coin/rpc_data.hpp>
#include <impl/dash/coin/transaction.hpp>
Expand All @@ -45,6 +46,7 @@ using dash::coin::WorkSelection;
using dash::coin::MNState;
using dash::coin::Mempool;
using dash::coin::MutableTransaction;
using dash::coin::BlockType;
using dash::coin::build_embedded_workdata;
using ::core::coin::UTXOViewCache;
using ::core::coin::Outpoint;
Expand Down Expand Up @@ -223,3 +225,68 @@ TEST(DashCoinStateMaintainer, InvalidateReorgRequiresFreshTipToReArm) {
EXPECT_FALSE(fb);
EXPECT_EQ(sel.work.m_height, H + 1);
}

// ========================================================================
// on_block_connected() -- incremental MnStateMachine::apply_block live-wire.
// #674 populated the DMN set only via the bulk mnlistdiff snapshot
// (on_mn_list_update). This slice drives apply_block per connected block so
// the set the embedded coinbase pays auto-maintains between snapshots, while
// the dashd RPC arm stays the fallback when the set can no longer back a payee.
// ========================================================================
static std::vector<std::pair<uint256, MNState>>
single_mn_coll(const std::vector<unsigned char>& payout,
const uint256& coll_hash, uint32_t coll_idx) {
MNState s;
s.isValid = true;
s.nRegisteredHeight = 2300000;
s.nLastPaidHeight = 0;
s.scriptPayout.m_data = payout;
s.collateralOutpoint.hash = coll_hash;
s.collateralOutpoint.index = coll_idx;
return std::vector<std::pair<uint256, MNState>>{{raw256(0x01), s}};
}

// A block with no special txs touches no DMN records: apply_block registers
// nothing, the set is unchanged, and the embedded bundle stays live.
TEST(DashCoinStateMaintainer, BlockConnectNoSpecialTxPreservesReadiness) {
NodeCoinState st;
CoinStateMaintainer m(st);
m.on_mn_list_update(single_mn(p2pkh_script(0x30)));
m.on_new_tip(H - 1, PREV_HASH, BITS, MTP, DASH_PUBKEY_VER, DASH_P2SH_VER, CURTIME, VERSION);
ASSERT_TRUE(m.live());

BlockType blk;
blk.m_txs.push_back(make_spend(raw256(0x90), 0, 500000000, 1)); // cb (idx 0, skipped)
blk.m_txs.push_back(make_spend(raw256(0x91), 0, 400000000, 2)); // plain spend, no collateral match
auto r = m.on_block_connected(blk, H);

EXPECT_EQ(r.registered, 0u);
EXPECT_EQ(st.mnstates().size(), 1u);
EXPECT_TRUE(m.live()) << "no-op block must not drop the live bundle";
}

// A block whose non-coinbase tx spends the sole MN's collateral removes it
// (apply_block pass 2). The now-empty set cannot back a masternode payee, so
// the maintainer drops the embedded bundle and get_work falls back to dashd.
TEST(DashCoinStateMaintainer, BlockConnectCollateralSpendDropsToFallback) {
NodeCoinState st;
CoinStateMaintainer m(st);
const uint256 coll = raw256(0x55);
m.on_mn_list_update(single_mn_coll(p2pkh_script(0x30), coll, 3));
m.on_new_tip(H - 1, PREV_HASH, BITS, MTP, DASH_PUBKEY_VER, DASH_P2SH_VER, CURTIME, VERSION);
ASSERT_TRUE(m.live());
ASSERT_EQ(st.mnstates().size(), 1u);

BlockType blk;
blk.m_txs.push_back(make_spend(raw256(0x90), 0, 500000000, 1)); // cb (idx 0, skipped)
blk.m_txs.push_back(make_spend(coll, 3, 400000000, 2)); // spends the MN collateral
m.on_block_connected(blk, H);

EXPECT_EQ(st.mnstates().size(), 0u);
EXPECT_FALSE(m.live()) << "collateral spend emptied the DMN set; bundle must drop to fallback";

bool fb = false;
WorkSelection sel = st.select_work([&]() { fb = true; return DashWorkData{}; });
EXPECT_EQ(sel.source, WorkSource::DashdFallback);
EXPECT_TRUE(fb) << "empty DMN set must route get_work to the dashd RPC fallback";
}
Loading