|
32 | 32 | #include <impl/dash/coin/mempool_ingest.hpp> // c2pool::dash::wire_mempool_ingest |
33 | 33 | #include <impl/dash/coin/tip_ingest.hpp> // c2pool::dash::wire_tip_ingest |
34 | 34 | #include <impl/dash/coin/block_connect_ingest.hpp> // c2pool::dash::wire_block_connect_ingest |
| 35 | +#include <impl/dash/coin/mn_list_ingest.hpp> // c2pool::dash::wire_mn_list_ingest |
35 | 36 | #include <impl/dash/coin/coin_state_maintainer.hpp> |
36 | 37 | #include <impl/dash/coin/node_coin_state.hpp> |
37 | 38 | #include <impl/dash/coin/embedded_gbt.hpp> |
|
55 | 56 | using c2pool::dash::wire_mempool_ingest; |
56 | 57 | using c2pool::dash::wire_tip_ingest; |
57 | 58 | using c2pool::dash::wire_block_connect_ingest; |
| 59 | +using c2pool::dash::wire_mn_list_ingest; |
58 | 60 | using dash::coin::CoinStateMaintainer; |
59 | 61 | using dash::coin::NodeCoinState; |
60 | 62 | using dash::coin::WorkSource; |
@@ -377,3 +379,96 @@ TEST(DashReceptionWire, DisposeStopsBlockConnectIngest) { |
377 | 379 | EXPECT_EQ(st.mnstates().size(), 1u) << "after dispose, a connected block must not be applied"; |
378 | 380 | EXPECT_TRUE(m.live()) << "after dispose, the armed bundle must stay live"; |
379 | 381 | } |
| 382 | + |
| 383 | +// ======================================================================== |
| 384 | +// Leg 4: an mn_list_update snapshot fired on the interface RESYNCS the DMN set |
| 385 | +// THROUGH THE WIRE -- no direct on_mn_list_update() poke. With a tip already |
| 386 | +// present, the wired mnlistdiff snapshot arms MN-readiness and select_work |
| 387 | +// flips to the embedded arm. This is the authoritative bulk feed (leg 3's |
| 388 | +// block_connected only folds per-block deltas between these snapshots). |
| 389 | +// ======================================================================== |
| 390 | +TEST(DashReceptionWire, MnListUpdateRelaySeedsMnSetThroughWire) { |
| 391 | + UTXOViewCache utxo(nullptr); |
| 392 | + dash::interfaces::Node node; |
| 393 | + NodeCoinState st; |
| 394 | + st.mempool().set_utxo(&utxo); |
| 395 | + CoinStateMaintainer m(st); |
| 396 | + |
| 397 | + auto sub = wire_mn_list_ingest(node, m); |
| 398 | + ASSERT_TRUE(sub) << "wire must return a live subscription handle"; |
| 399 | + |
| 400 | + // Tip arrives first (reception is async); the MN list is the gating event. |
| 401 | + m.on_new_tip(H - 1, PREV_HASH, BITS, MTP, DASH_PUBKEY_VER, DASH_P2SH_VER, CURTIME, VERSION); |
| 402 | + EXPECT_FALSE(m.live()) << "tip alone must not arm the bundle -- MN list absent"; |
| 403 | + ASSERT_EQ(st.mnstates().size(), 0u); |
| 404 | + |
| 405 | + // mnlistdiff snapshot fired on the interface (NOT a direct maintainer poke). |
| 406 | + dash::interfaces::MnListUpdate u; |
| 407 | + u.mnstates = single_mn(p2pkh_script(0x30)); |
| 408 | + node.mn_list_update.happened(u); |
| 409 | + |
| 410 | + EXPECT_EQ(st.mnstates().size(), 1u) << "mnlistdiff relay (via wire) must seed the DMN set"; |
| 411 | + ASSERT_TRUE(m.live()) << "tip + MN (via wire) must arm the embedded bundle"; |
| 412 | + |
| 413 | + bool fb = false; |
| 414 | + WorkSelection sel = st.select_work([&]() { fb = true; return dash::coin::DashWorkData{}; }); |
| 415 | + EXPECT_EQ(sel.source, WorkSource::Embedded); |
| 416 | + EXPECT_FALSE(fb) << "embedded arm must not invoke the dashd fallback"; |
| 417 | +} |
| 418 | + |
| 419 | +// ======================================================================== |
| 420 | +// An EMPTY mnlistdiff snapshot fired on the wire is a set-gap: on_mn_list_update |
| 421 | +// clears MN-readiness and demotes an armed bundle to the retained dashd |
| 422 | +// fallback rather than backing a template with no masternode payee. |
| 423 | +// ======================================================================== |
| 424 | +TEST(DashReceptionWire, EmptyMnListRelayDemotesThroughWire) { |
| 425 | + UTXOViewCache utxo(nullptr); |
| 426 | + dash::interfaces::Node node; |
| 427 | + NodeCoinState st; |
| 428 | + st.mempool().set_utxo(&utxo); |
| 429 | + CoinStateMaintainer m(st); |
| 430 | + |
| 431 | + auto sub = wire_mn_list_ingest(node, m); |
| 432 | + |
| 433 | + // Arm the bundle: tip + a non-empty snapshot delivered on the wire. |
| 434 | + m.on_new_tip(H - 1, PREV_HASH, BITS, MTP, DASH_PUBKEY_VER, DASH_P2SH_VER, CURTIME, VERSION); |
| 435 | + dash::interfaces::MnListUpdate seed; |
| 436 | + seed.mnstates = single_mn(p2pkh_script(0x30)); |
| 437 | + node.mn_list_update.happened(seed); |
| 438 | + ASSERT_TRUE(m.live()); |
| 439 | + ASSERT_EQ(st.mnstates().size(), 1u); |
| 440 | + |
| 441 | + // An empty snapshot fired on the wire -> set-gap -> demote. |
| 442 | + dash::interfaces::MnListUpdate empty; // empty.mnstates == {} |
| 443 | + node.mn_list_update.happened(empty); |
| 444 | + |
| 445 | + EXPECT_EQ(st.mnstates().size(), 0u) << "empty snapshot must clear the DMN set"; |
| 446 | + EXPECT_FALSE(m.live()) << "empty mnlistdiff (set-gap) must demote the live bundle"; |
| 447 | + |
| 448 | + bool fb = false; |
| 449 | + WorkSelection sel = st.select_work([&]() { fb = true; return dash::coin::DashWorkData{}; }); |
| 450 | + EXPECT_EQ(sel.source, WorkSource::DashdFallback); |
| 451 | + EXPECT_TRUE(fb) << "after an empty snapshot, get_work must fall back to dashd"; |
| 452 | +} |
| 453 | + |
| 454 | +// ======================================================================== |
| 455 | +// Disposing the mn-list handle tears the subscription down: a later snapshot is |
| 456 | +// not applied, so a stale mnlistdiff feed cannot silently re-seed the DMN set. |
| 457 | +// ======================================================================== |
| 458 | +TEST(DashReceptionWire, DisposeStopsMnListIngest) { |
| 459 | + UTXOViewCache utxo(nullptr); |
| 460 | + dash::interfaces::Node node; |
| 461 | + NodeCoinState st; |
| 462 | + st.mempool().set_utxo(&utxo); |
| 463 | + CoinStateMaintainer m(st); |
| 464 | + |
| 465 | + auto sub = wire_mn_list_ingest(node, m); |
| 466 | + sub->dispose(); |
| 467 | + |
| 468 | + dash::interfaces::MnListUpdate u; |
| 469 | + u.mnstates = single_mn(p2pkh_script(0x30)); |
| 470 | + node.mn_list_update.happened(u); |
| 471 | + |
| 472 | + EXPECT_EQ(st.mnstates().size(), 0u) |
| 473 | + << "after dispose, an mnlistdiff snapshot must not seed the set"; |
| 474 | +} |
0 commit comments