|
| 1 | +// SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | +#pragma once |
| 3 | + |
| 4 | +/// Phase C-TEMPLATE step 7 (S8 embedded_gbt live-wire, follow-on to #673): |
| 5 | +/// the maintainer that POPULATES the node-held NodeCoinState off the |
| 6 | +/// reception + think update path, replacing the set_tip-on-demand pattern |
| 7 | +/// where a caller poked NodeCoinState::set_tip() directly. |
| 8 | +/// |
| 9 | +/// #672 landed select_dash_work() as the branch point; #673 landed |
| 10 | +/// NodeCoinState as the node-held bundle whose populated() flips the hot arm. |
| 11 | +/// But #673 left publication to whoever calls set_tip() -- fine for the KAT, |
| 12 | +/// wrong for a live node: the tip advances on the header/think path, the MN |
| 13 | +/// list advances on the mnlistdiff reception path, and mempool txs arrive on |
| 14 | +/// the relay path, all ASYNCHRONOUSLY. The bundle must only go live once the |
| 15 | +/// prerequisites the embedded template needs are actually present, else the |
| 16 | +/// selector would build a template against a stale/empty MN list. |
| 17 | +/// |
| 18 | +/// CoinStateMaintainer is that ordering gate. It owns no state of its own |
| 19 | +/// beyond readiness flags + the last tip params; it drives a NodeCoinState& |
| 20 | +/// the node owns. The reception/think slices call the on_*() event methods; |
| 21 | +/// the maintainer republishes (calls set_tip) only when BOTH the MN list has |
| 22 | +/// been seeded AND a tip has arrived. Until then, and after any invalidating |
| 23 | +/// event (reorg / MN-list gap), populated() stays false and select_work() |
| 24 | +/// routes to the retained dashd getblocktemplate fallback. |
| 25 | +/// |
| 26 | +/// STRICTLY single-coin: src/impl/dash/coin/ only, no bitcoin_family / src/core |
| 27 | +/// reach. The dashd RPC arm is NEVER removed -- it is the always-reachable |
| 28 | +/// safety path and the [GBT-XCHECK] cross-check whenever the bundle is not live. |
| 29 | + |
| 30 | +#include <impl/dash/coin/node_coin_state.hpp> // NodeCoinState |
| 31 | +#include <impl/dash/coin/mn_state_machine.hpp> // MNState |
| 32 | +#include <impl/dash/coin/transaction.hpp> // MutableTransaction |
| 33 | + |
| 34 | +#include <core/uint256.hpp> |
| 35 | + |
| 36 | +#include <cstdint> |
| 37 | +#include <utility> |
| 38 | +#include <vector> |
| 39 | + |
| 40 | +namespace dash { |
| 41 | +namespace coin { |
| 42 | + |
| 43 | +/// Drives a node-owned NodeCoinState from the async update events the running |
| 44 | +/// node observes. Non-copyable (holds a reference to the node's holder). The |
| 45 | +/// node constructs exactly one, wired to its NodeCoinState member; the |
| 46 | +/// reception (mnlistdiff / mempool) and header/think (tip) slices call the |
| 47 | +/// on_*() methods as their respective updates land. |
| 48 | +class CoinStateMaintainer { |
| 49 | +public: |
| 50 | + explicit CoinStateMaintainer(NodeCoinState& state) : m_state(state) {} |
| 51 | + CoinStateMaintainer(const CoinStateMaintainer&) = delete; |
| 52 | + CoinStateMaintainer& operator=(const CoinStateMaintainer&) = delete; |
| 53 | + |
| 54 | + /// Reception path (mnlistdiff): replace the masternode set the embedded |
| 55 | + /// coinbase pays. An EMPTY list is treated as a gap -- it cannot back a |
| 56 | + /// valid payee, so it clears MN-readiness and drops the bundle to fallback |
| 57 | + /// rather than publishing a template with no masternode payment. |
| 58 | + void on_mn_list_update(std::vector<std::pair<uint256, MNState>> mnstates) { |
| 59 | + m_have_mn = !mnstates.empty(); |
| 60 | + m_state.mnstates().load(std::move(mnstates)); |
| 61 | + if (!m_have_mn) |
| 62 | + demote(); |
| 63 | + else |
| 64 | + republish(); |
| 65 | + } |
| 66 | + |
| 67 | + /// Reception path (mempool relay): fold a relayed tx into the local |
| 68 | + /// mempool. Mempool contents are OPTIONAL for viability -- an empty |
| 69 | + /// mempool yields a valid coinbase-only template -- so this never gates |
| 70 | + /// publication; it only enriches the next assembled template. Returns the |
| 71 | + /// mempool's accept verdict (false = rejected: bad utxo ref / already in). |
| 72 | + bool on_mempool_tx(const MutableTransaction& tx) { |
| 73 | + return m_state.mempool().add_tx(tx); |
| 74 | + } |
| 75 | + |
| 76 | + /// Header / think path: the chain tip advanced. Stash the params the |
| 77 | + /// embedded template needs and mark tip-readiness, then republish if the |
| 78 | + /// MN list is also ready. curtime/version left 0 defer to |
| 79 | + /// build_embedded_workdata()'s own SAFE-ADDITIVE defaults. |
| 80 | + void on_new_tip(uint32_t prev_height, const uint256& prev_hash, |
| 81 | + uint32_t bits_for_next, uint32_t mtp_at_tip, |
| 82 | + uint8_t address_version, uint8_t address_p2sh_version, |
| 83 | + uint32_t curtime = 0, uint32_t version = 0) { |
| 84 | + m_prev_height = prev_height; |
| 85 | + m_prev_hash = prev_hash; |
| 86 | + m_bits_for_next = bits_for_next; |
| 87 | + m_mtp_at_tip = mtp_at_tip; |
| 88 | + m_address_version = address_version; |
| 89 | + m_address_p2sh_version = address_p2sh_version; |
| 90 | + m_curtime = curtime; |
| 91 | + m_version = version; |
| 92 | + m_have_tip = true; |
| 93 | + republish(); |
| 94 | + } |
| 95 | + |
| 96 | + /// Reorg / MN-list gap / mempool flush: invalidate the live bundle so the |
| 97 | + /// next get_work falls back to dashd until a fresh tip rebuilds it. The |
| 98 | + /// stashed tip params are dropped -- a reorg means the old prev_hash is no |
| 99 | + /// longer the tip, so we must NOT auto-republish it; a subsequent |
| 100 | + /// on_new_tip() re-arms tip-readiness. The MN list (which survives a mere |
| 101 | + /// reorg) is left in place. |
| 102 | + void on_invalidate() { |
| 103 | + m_have_tip = false; |
| 104 | + demote(); |
| 105 | + } |
| 106 | + |
| 107 | + /// True iff both prerequisites are met AND the holder is currently live. |
| 108 | + bool live() const { return m_state.populated(); } |
| 109 | + |
| 110 | +private: |
| 111 | + // Publish only when both prerequisites are present; otherwise leave the |
| 112 | + // holder in whatever (un)published state it is in -- callers reach demote() |
| 113 | + // explicitly for the invalidating transitions. |
| 114 | + void republish() { |
| 115 | + if (m_have_tip && m_have_mn) |
| 116 | + m_state.set_tip(m_prev_height, m_prev_hash, m_bits_for_next, |
| 117 | + m_mtp_at_tip, m_address_version, m_address_p2sh_version, |
| 118 | + m_curtime, m_version); |
| 119 | + } |
| 120 | + |
| 121 | + void demote() { |
| 122 | + if (m_state.populated()) |
| 123 | + m_state.invalidate(); |
| 124 | + } |
| 125 | + |
| 126 | + NodeCoinState& m_state; |
| 127 | + |
| 128 | + bool m_have_mn{false}; |
| 129 | + bool m_have_tip{false}; |
| 130 | + |
| 131 | + // Last observed tip params, applied on republish(). |
| 132 | + uint32_t m_prev_height{0}; |
| 133 | + uint256 m_prev_hash; |
| 134 | + uint32_t m_bits_for_next{0}; |
| 135 | + uint32_t m_mtp_at_tip{0}; |
| 136 | + uint8_t m_address_version{0}; |
| 137 | + uint8_t m_address_p2sh_version{0}; |
| 138 | + uint32_t m_curtime{0}; |
| 139 | + uint32_t m_version{0}; |
| 140 | +}; |
| 141 | + |
| 142 | +} // namespace coin |
| 143 | +} // namespace dash |
0 commit comments