Skip to content

Commit d11e94d

Browse files
author
integrator
committed
dash(embedded/E2a): wire live coin-P2P feed into maintainer -> populate NodeCoinState
E1 (#743) landed a coin-network P2P client that fires the raw dash::interfaces:: Node wire events; E2b (#745) landed the UTXO/fee lane. Nothing joined them into a live populate loop. E2a is that join, entirely behind --coin-p2p-connect (+ --embedded-utxo): with no flag, run_node is byte-unchanged. What got wired (src/impl/dash/coin/live_feed.hpp + main_dash.cpp): - new_headers -> HeaderChain::add_headers (X11 + DarkGravityWave validated) -- the tip authority; HeaderChain::set_on_tip_changed derives a TipAdvance (next-work bits, median-time-past, address versions) and fires Node::new_tip -> wire_tip_ingest -> CoinStateMaintainer::on_new_tip (tip readiness). - new_tx -> wire_mempool_ingest -> maintainer.on_mempool_tx. - full_block -> (X11 hash -> header-chain height) -> Node::block_connected, driving BOTH wire_block_connect_ingest (MnStateMachine::apply_block) AND the E2b UTXO lane connect_block + fee recompute from one fired event. - new_block(inv) -> request_block (getdata pull); new_chainlock -> best-CL tracker. - mn_list_update leg wired for completeness (the payout-bearing DMN set source). - set_on_handshake_complete kicks initial sync (getheaders + mempool); header self-propel requests the next batch off the updated locator. - UTXO bootstrap request-by-height seam bound to the coin-P2P full-block pull. BlockType parser (src/impl/dash/coin/block.hpp): closes the S5 header-only deferral -- BlockType now (de)serializes the standard Bitcoin block body (CompactSize tx-count + txs), mirroring the DGB sibling minus witness. This lets a live dashd `block` body deserialize into the tx set the ingest legs consume, and makes the multi-entry `headers` message round-trip (each entry = 80B header + CompactSize(0)). HeaderChain gains median_time_past() (BIP113). work_generation_ + notify_all() sessions (event-driven stale-work notify). KATs: block-body tx-set round-trip + 81-byte empty-body pin (test_dash_p2p_ messages); live-feed bridge translation (test_dash_live_feed_bridge in the test_dash_node_reception_wire target). All affected dash targets green; --selftest green.
1 parent 65e447e commit d11e94d

8 files changed

Lines changed: 545 additions & 17 deletions

File tree

src/c2pool/main_dash.cpp

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@
5757
#include <impl/dash/coin/p2p_client.hpp> // dash::coin::p2p::CoinClient — OPT-IN coin-network dial (E1, --coin-p2p-connect)
5858
#include <impl/dash/coin/node_coin_state.hpp> // dash::coin::NodeCoinState (embedded work bundle)
5959
#include <impl/dash/coin/utxo_lane.hpp> // dash::coin::UtxoLane — embedded UTXO/fee lane (E2b, #738)
60+
#include <impl/dash/coin/header_chain.hpp> // dash::coin::HeaderChain — SPV header/tip authority (E2a)
61+
#include <impl/dash/coin/coin_state_maintainer.hpp> // dash::coin::CoinStateMaintainer — populate ordering gate (E2a)
62+
#include <impl/dash/coin/live_feed.hpp> // E2a live-feed bridge (raw wire events -> derived ingest events)
63+
#include <impl/dash/coin/mempool_ingest.hpp> // wire_mempool_ingest (leg 1)
64+
#include <impl/dash/coin/tip_ingest.hpp> // wire_tip_ingest (leg 2)
65+
#include <impl/dash/coin/block_connect_ingest.hpp> // wire_block_connect_ingest (leg 3)
66+
#include <impl/dash/coin/mn_list_ingest.hpp> // wire_mn_list_ingest (leg 4)
6067
#include <impl/dash/node.hpp> // dash::Node — sharechain pool-node (NodeBridge<NodeImpl,Legacy,Actual>)
6168
#include <impl/dash/config.hpp> // dash::Config (PoolConfig/CoinConfig)
6269
#include <impl/dash/config_pool.hpp> // dash::SharechainConfig — P2P_PORT / PREFIX / min-proto SSOT
@@ -892,6 +899,156 @@ int run_node(bool testnet, const std::string& rpc_endpoint,
892899
think_timer->expires_after(std::chrono::seconds(15));
893900
think_timer->async_wait(*think_tick);
894901

902+
// ── E2a: wire the LIVE coin-P2P feed into the maintainer -> populate ──
903+
// GUARANTEE: this whole block is gated on `coin_p2p` (i.e. --coin-p2p-connect
904+
// was supplied). With NO flag, coin_p2p is null, none of the header chain /
905+
// maintainer / ingest legs below are constructed, and run_node is byte-
906+
// identical to the released dashd-fallback prod path. The subscriptions are
907+
// REGISTERED here (before ioc.run()); no wire event can fire until the loop
908+
// below pumps the socket I/O, so wiring after the E1 connect() is race-free.
909+
//
910+
// These locals are declared LAST in run_node's scope so they are destroyed
911+
// FIRST at return (after ioc.run() has stopped and no further events fire):
912+
// subscription handles -> maintainer -> header_chain, all torn down before
913+
// node_coin_state / coin_state (declared earlier) they reference.
914+
std::unique_ptr<dash::coin::HeaderChain> header_chain;
915+
std::unique_ptr<dash::coin::CoinStateMaintainer> maintainer;
916+
std::vector<std::shared_ptr<EventDisposable>> coin_feed_subs;
917+
if (coin_p2p) {
918+
const auto dash_params = testnet
919+
? dash::coin::make_dash_chain_params_testnet()
920+
: dash::coin::make_dash_chain_params_mainnet();
921+
const auto hdr_db = (core::filesystem::config_path()
922+
/ net_subdir / "dash_headers").string();
923+
header_chain = std::make_unique<dash::coin::HeaderChain>(dash_params, hdr_db);
924+
header_chain->init();
925+
926+
maintainer = std::make_unique<dash::coin::CoinStateMaintainer>(node_coin_state);
927+
928+
// Coin address versions for the embedded coinbase-payee encoding (the
929+
// TipAdvance carries them so build_embedded_workdata can encode the MN
930+
// payee); sourced from the oracle CoinParams, testnet/mainnet-aware.
931+
const core::CoinParams coin_params = dash::make_coin_params(testnet);
932+
const uint8_t addr_ver = coin_params.address_version;
933+
const uint8_t p2sh_ver = coin_params.address_p2sh_version;
934+
935+
// Leg 1 (mempool relay): new_tx -> maintainer.on_mempool_tx. Optional
936+
// for viability; enriches the assembled template.
937+
coin_feed_subs.push_back(
938+
c2pool::dash::wire_mempool_ingest(coin_state, *maintainer));
939+
// Leg 2 (tip advance): Node::new_tip -> maintainer.on_new_tip. The
940+
// new_tip event is FIRED by the tip-changed callback below (off the
941+
// header chain), NOT the raw wire.
942+
coin_feed_subs.push_back(
943+
c2pool::dash::wire_tip_ingest(coin_state, *maintainer));
944+
// Leg 3 (block connect): Node::block_connected -> maintainer
945+
// .on_block_connected (MnStateMachine::apply_block, folds DIP3 special
946+
// txs into the DMN set). block_connected is fired by the live-feed
947+
// bridge (full_block -> height lookup). The E2b UTXO lane is ALSO
948+
// subscribed to the same event (its connect_block + fee recompute).
949+
coin_feed_subs.push_back(
950+
c2pool::dash::wire_block_connect_ingest(coin_state, *maintainer));
951+
// Leg 4 (mnlistdiff RESYNC): Node::mn_list_update -> maintainer
952+
// .on_mn_list_update. Wired for completeness; a payee-complete DMN-set
953+
// source over P2P is the known follow-on (see PR — Dash's Simplified MN
954+
// List omits scriptPayout, so the authoritative payout-bearing set is
955+
// built by leg 3's apply_block over connected block bodies).
956+
coin_feed_subs.push_back(
957+
c2pool::dash::wire_mn_list_ingest(coin_state, *maintainer));
958+
959+
// Bridge: new_headers -> HeaderChain::add_headers (X11 PoW + DGW
960+
// validated). The tip authority for the embedded template.
961+
coin_feed_subs.push_back(
962+
dash::coin::wire_header_ingest(coin_state, *header_chain));
963+
// Bridge: full_block -> (X11 hash -> header-chain height) ->
964+
// Node::block_connected, driving leg 3 + the E2b UTXO lane.
965+
coin_feed_subs.push_back(
966+
dash::coin::wire_full_block_ingest(coin_state, *header_chain));
967+
968+
// new_block(inv hash) -> pull the full block from the peer (getdata).
969+
// Closes the loop so live tip blocks arrive as full_block -> connect.
970+
coin_feed_subs.push_back(
971+
coin_state.new_block.subscribe(
972+
[cp = coin_p2p.get()](const uint256& hash) {
973+
cp->request_block(hash);
974+
}));
975+
976+
// new_chainlock -> record into the best-chainlock tracker (finalization
977+
// signal the block-find submit path can consult).
978+
coin_feed_subs.push_back(
979+
coin_state.new_chainlock.subscribe(
980+
[&coin_state](const std::pair<uint256, int32_t>& cl) {
981+
coin_state.chainlocked_blocks[cl.first] = cl.second;
982+
}));
983+
984+
// Header self-propel: after each accepted headers batch, request the
985+
// next batch off the updated locator so the chain catches up to tip.
986+
// Registered AFTER wire_header_ingest so add_headers runs first and the
987+
// locator reflects the new tip.
988+
coin_feed_subs.push_back(
989+
coin_state.new_headers.subscribe(
990+
[cp = coin_p2p.get(), hc = header_chain.get()]
991+
(const std::vector<dash::coin::BlockHeaderType>& batch) {
992+
if (batch.empty()) return;
993+
cp->send_getheaders(70230, hc->get_locator(), uint256::ZERO);
994+
}));
995+
996+
// Tip-changed callback: (a) fire Node::new_tip (leg 2 arms tip-readiness
997+
// -> the maintainer republishes once the MN list is ALSO seeded ->
998+
// populated() flips), and (b) #739 idle-notify: bump work-generation +
999+
// notify sessions on a real tip change so idle miners are not wedged on
1000+
// stale work between job-push timer firings (event-driven notify).
1001+
header_chain->set_on_tip_changed(
1002+
[&coin_state, &stratum_server, hc = header_chain.get(),
1003+
addr_ver, p2sh_ver, ws = work_source.get()]
1004+
(const uint256&, uint32_t, const uint256& new_tip, uint32_t new_height) {
1005+
auto ta = dash::coin::tip_advance_from_chain(
1006+
*hc, addr_ver, p2sh_ver);
1007+
if (ta) {
1008+
coin_state.new_tip.happened(*ta);
1009+
LOG_INFO << "[EMB-DASH] tip advanced h=" << new_height
1010+
<< " " << new_tip.GetHex().substr(0, 16)
1011+
<< " bits=0x" << std::hex << ta->bits_for_next << std::dec
1012+
<< " -> new_tip fired (maintainer arm)";
1013+
}
1014+
// #739: event-driven stale-work notify.
1015+
if (ws) ws->bump_work_generation();
1016+
if (stratum_server) stratum_server->notify_all();
1017+
});
1018+
1019+
// E2b UTXO bootstrap window-refill seam: request historical block
1020+
// bodies BY HEIGHT (header-chain hash lookup) so the UTXO view + the
1021+
// MnStateMachine (apply_block) fill forward from the live feed.
1022+
if (embedded_utxo && utxo_lane.live()) {
1023+
utxo_lane.set_request_block_fn(
1024+
[cp = coin_p2p.get(), hc = header_chain.get()](uint32_t h) {
1025+
auto e = hc->get_header_by_height(h);
1026+
if (e) cp->request_block(e->hash);
1027+
});
1028+
}
1029+
1030+
// Peer's reported chain height -> header-chain sync-progress gauge.
1031+
coin_p2p->set_on_peer_height(
1032+
[hc = header_chain.get()](uint32_t h) { hc->set_peer_tip_height(h); });
1033+
1034+
// Kick the initial sync once the version/verack handshake completes:
1035+
// getheaders off our current locator + a mempool prime.
1036+
coin_p2p->set_on_handshake_complete(
1037+
[cp = coin_p2p.get(), hc = header_chain.get()]() {
1038+
LOG_INFO << "[EMB-DASH] handshake complete -> initial sync:"
1039+
" getheaders + mempool";
1040+
cp->send_getheaders(70230, hc->get_locator(), uint256::ZERO);
1041+
cp->send_mempool();
1042+
});
1043+
1044+
std::cout << "[run] E2a live-feed wired: header-chain(" << hdr_db
1045+
<< ") + CoinStateMaintainer + 6 ingest subscriptions;"
1046+
" populate flips get_work to the EMBEDDED arm once the tip"
1047+
" (headers) AND the DMN set (block-connect apply_block) are"
1048+
" present" << (embedded_utxo ? " + UTXO maturity>=106" : "")
1049+
<< "\n";
1050+
}
1051+
8951052
std::cout << "[run] run-loop up (Ctrl-C to stop); won blocks relay via the\n"
8961053
"[run] dashd-RPC submitblock fallback + the embedded sharechain P2P leg.\n";
8971054
ioc.run();

src/impl/dash/coin/block.hpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@
77
// NOTE: the `m_txs` block-body member below was ADVANCED FROM S5 (block-replay)
88
// into the S4-pre foundation (branch dash/pr0-foundation-s4) to close
99
// credit_pool.apply_block(), which walks block.m_txs for asset-lock/unlock
10-
// accounting. This is the SINGLE canonical declaration of the member: S5 builds
11-
// on it (adding the transaction-aware (de)serialization of m_txs) rather than
12-
// re-authoring it, to avoid a double-add collision on this struct.
10+
// accounting. This is the SINGLE canonical declaration of the member.
1311
//
14-
// S3/S4-pre serialization stays HEADER-ONLY here; transaction-aware
15-
// (de)serialization of m_txs remains deferred to S5 along with the wire format.
12+
// E2a (#738) closes the S5 deferral: BlockType now (de)serializes m_txs as the
13+
// standard Bitcoin block body (CompactSize tx-count + txs), mirroring the DGB
14+
// sibling (src/impl/dgb/coin/block.hpp) minus witness (Dash is non-segwit, non-
15+
// MWEB). This is what a live dashd `block` message body needs to deserialize
16+
// into the tx set the embedded ingest legs consume, and it makes the multi-entry
17+
// `headers` message round-trip too: on the wire each `headers` entry is an 80-
18+
// byte header followed by a CompactSize(0) tx-count, which now deserializes as a
19+
// header + empty m_txs. The E1 coin-P2P client explicitly deferred this parser
20+
// to E2a.
1621

1722
#include <impl/bitcoin_family/coin/base_block.hpp>
1823
#include <impl/dash/coin/transaction.hpp> // complete MutableTransaction for m_txs member
@@ -29,18 +34,22 @@ using bitcoin_family::coin::BlockHeaderType;
2934

3035
struct BlockType : BlockHeaderType
3136
{
32-
// Advanced from S5 to close credit_pool.apply_block(). Transaction-aware
33-
// (de)serialization of this member lands in S5; not serialized here.
37+
// Block body. E2a: transaction-aware (de)serialization below. Dash is
38+
// non-segwit / non-MWEB, so the tx vector is the plain CompactSize-prefixed
39+
// MutableTransaction sequence (MutableTransaction carries its own Dash
40+
// version|type<<16 + extra_payload codec) — no TX_WITH_WITNESS wrapper.
3441
std::vector<MutableTransaction> m_txs;
3542

3643
template <typename Stream>
3744
void Serialize(Stream& s) const {
3845
BlockHeaderType::Serialize(s);
46+
::Serialize(s, m_txs);
3947
}
4048

4149
template <typename Stream>
4250
void Unserialize(Stream& s) {
4351
BlockHeaderType::Unserialize(s);
52+
::Unserialize(s, m_txs);
4453
}
4554

4655
BlockType() : BlockHeaderType() { }

src/impl/dash/coin/header_chain.hpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <core/pack.hpp>
1616
#include <core/hash.hpp>
1717

18+
#include <algorithm>
1819
#include <atomic>
1920
#include <chrono>
2021
#include <functional>
@@ -317,6 +318,31 @@ class HeaderChain {
317318
return m_headers.size();
318319
}
319320

321+
// Median-time-past of the tip: median of the last 11 block timestamps
322+
// (dashcore GetMedianTimePast / BIP113). build_embedded_workdata() uses
323+
// mtp_at_tip+1 as the template mintime, so the embedded arm needs a real
324+
// MTP off the header chain to match dashd's GBT. Falls back to the tip
325+
// timestamp when fewer than 11 headers are indexed (cold start).
326+
uint32_t median_time_past() const {
327+
std::lock_guard<std::mutex> lock(m_mutex);
328+
if (m_tip.IsNull()) return 0;
329+
static constexpr int MEDIAN_SPAN = 11;
330+
std::vector<uint32_t> times;
331+
times.reserve(MEDIAN_SPAN);
332+
int64_t h = static_cast<int64_t>(m_tip_height);
333+
for (int i = 0; i < MEDIAN_SPAN && h >= 0; ++i, --h) {
334+
auto e = get_header_by_height_internal(static_cast<uint32_t>(h));
335+
if (!e) break;
336+
times.push_back(e->header.m_timestamp);
337+
}
338+
if (times.empty()) {
339+
auto it = m_headers.find(m_tip);
340+
return it != m_headers.end() ? it->second.header.m_timestamp : 0;
341+
}
342+
std::sort(times.begin(), times.end());
343+
return times[times.size() / 2];
344+
}
345+
320346
bool has_header(const uint256& hash) const {
321347
std::lock_guard<std::mutex> lock(m_mutex);
322348
return m_headers.count(hash) > 0;

0 commit comments

Comments
 (0)