-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathtest_dash_node_reception_wire.cpp
More file actions
261 lines (228 loc) · 12.9 KB
/
Copy pathtest_dash_node_reception_wire.cpp
File metadata and controls
261 lines (228 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
// SPDX-License-Identifier: AGPL-3.0-or-later
/// Phase C-TEMPLATE step 9 -- reception-wire KAT (leg 1: mempool relay).
///
/// #672..#685 landed the node-held embedded coin-state bundle + its async
/// CoinStateMaintainer, and test_dash_coin_state_maintainer / _node_embedded_wire
/// proved that DRIVING the maintainer's on_*() methods flips select_work() to
/// the embedded arm. But every one of those suites poked the maintainer DIRECTLY
/// -- nothing subscribed a live interfaces::Node's reception events to it, so in
/// a running node the arm could never flip on its own. wire_mempool_ingest()
/// (src/impl/dash/coin/mempool_ingest.hpp) closes the FIRST of the four legs:
/// interfaces::Node::new_tx -> CoinStateMaintainer::on_mempool_tx. This suite
/// proves that subscription end-to-end, off the real Event, with no direct poke:
///
/// * a new_tx relay fired on the interface FOLDS into the maintainer's mempool
/// (size 0 -> 1) -- the reception path, not a test poke, drives the state;
/// * disposing the returned handle tears the subscription down: a later relay
/// is NOT folded (size stays put) -- teardown is honoured;
/// * a relayed tx reaches the assembled embedded template once MN+tip arm the
/// bundle (select_work -> WorkSource::Embedded, tx present in m_tx_hashes),
/// and an on_invalidate() reorg demotes back to the retained dashd fallback.
///
/// Seeding mirrors test_dash_coin_state_maintainer.cpp exactly so the suites pin
/// the SAME projection. Scope-honest: only the new_tx leg is wired here; the
/// on_block_connected / on_new_tip / on_mn_list_update legs need payload the
/// interface does not yet carry (block height / tip params / a mnlistdiff event)
/// and land in their own slices -- their maintainer methods are still exercised
/// directly here only to arm the bundle for the template-reach assertion.
#include <gtest/gtest.h>
#include <impl/dash/coin/node_interface.hpp> // dash::interfaces::Node
#include <impl/dash/coin/mempool_ingest.hpp> // c2pool::dash::wire_mempool_ingest
#include <impl/dash/coin/tip_ingest.hpp> // c2pool::dash::wire_tip_ingest
#include <impl/dash/coin/coin_state_maintainer.hpp>
#include <impl/dash/coin/node_coin_state.hpp>
#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/utxo_adapter.hpp>
#include <impl/dash/coin/rpc_data.hpp>
#include <impl/dash/coin/transaction.hpp>
#include <core/uint256.hpp>
#include <core/events.hpp>
#include <array>
#include <cstdint>
#include <cstring>
#include <memory>
#include <utility>
#include <vector>
using c2pool::dash::wire_mempool_ingest;
using c2pool::dash::wire_tip_ingest;
using dash::coin::CoinStateMaintainer;
using dash::coin::NodeCoinState;
using dash::coin::WorkSource;
using dash::coin::WorkSelection;
using dash::coin::MNState;
using dash::coin::MutableTransaction;
using dash::coin::Transaction;
using ::core::coin::UTXOViewCache;
using ::core::coin::Outpoint;
using ::core::coin::Coin;
using ::bitcoin_family::coin::TxIn;
using ::bitcoin_family::coin::TxOut;
static constexpr uint8_t DASH_PUBKEY_VER = 76;
static constexpr uint8_t DASH_P2SH_VER = 16;
static constexpr uint32_t H = 2'400'000; // past MN_RR: platform burn active
static uint256 raw256(uint8_t base) {
uint256 h;
std::array<uint8_t, 32> p{};
for (size_t i = 0; i < 32; ++i) p[i] = static_cast<uint8_t>(base + i);
std::memcpy(h.data(), p.data(), 32);
return h;
}
static std::vector<unsigned char> p2pkh_script(uint8_t hashseed) {
std::vector<unsigned char> s{0x76, 0xa9, 0x14};
for (int i = 0; i < 20; ++i) s.push_back(static_cast<unsigned char>(hashseed + i));
s.push_back(0x88); s.push_back(0xac);
return s;
}
static MutableTransaction make_spend(const uint256& prev, uint32_t idx,
int64_t out_value, uint32_t salt) {
MutableTransaction tx;
tx.version = 1; tx.type = 0; tx.locktime = salt;
TxIn in; in.prevout.hash = prev; in.prevout.index = idx;
in.sequence = 0xffffffffu;
tx.vin.push_back(in);
TxOut o; o.value = out_value;
tx.vout.push_back(o);
return tx;
}
static std::vector<std::pair<uint256, MNState>> single_mn(const std::vector<unsigned char>& payout) {
MNState s;
s.isValid = true;
s.nRegisteredHeight = 2'300'000;
s.nLastPaidHeight = 0;
s.scriptPayout.m_data = payout;
return std::vector<std::pair<uint256, MNState>>{{raw256(0x01), s}};
}
static const uint256 PREV_HASH = raw256(0xAB);
static const uint32_t BITS = 0x1b104be3u;
static const uint32_t MTP = 1'700'000'000u;
static const uint32_t CURTIME = 1'700'000'123u;
static const uint32_t VERSION = 0x20000000u;
// ════════════════════════════════════════════════════════════════════════
// Leg 1: a new_tx relay fired on the interface folds through the maintainer.
// No direct on_mempool_tx() poke -- the Event drives it.
// ════════════════════════════════════════════════════════════════════════
TEST(DashReceptionWire, NewTxRelayFoldsThroughMaintainer) {
UTXOViewCache utxo(nullptr);
const uint256 prev = raw256(0x77);
utxo.add_coin(Outpoint(prev, 0), Coin(100'000, {}, 1, false));
dash::interfaces::Node node;
NodeCoinState st;
st.mempool().set_utxo(&utxo);
CoinStateMaintainer m(st);
auto sub = wire_mempool_ingest(node, m);
ASSERT_TRUE(sub) << "wire must return a live subscription handle";
ASSERT_EQ(st.mempool().size(), 0u);
// Fire the reception event (NOT a direct maintainer poke).
node.new_tx.happened(Transaction(make_spend(prev, 0, 90'000, /*salt=*/1)));
EXPECT_EQ(st.mempool().size(), 1u) << "new_tx relay must fold into the mempool";
}
// ════════════════════════════════════════════════════════════════════════
// Disposing the handle tears the subscription down: later relays are dropped.
// ════════════════════════════════════════════════════════════════════════
TEST(DashReceptionWire, DisposeStopsIngest) {
UTXOViewCache utxo(nullptr);
const uint256 a = raw256(0x77);
const uint256 b = raw256(0x66);
utxo.add_coin(Outpoint(a, 0), Coin(100'000, {}, 1, false));
utxo.add_coin(Outpoint(b, 0), Coin(100'000, {}, 1, false));
dash::interfaces::Node node;
NodeCoinState st;
st.mempool().set_utxo(&utxo);
CoinStateMaintainer m(st);
auto sub = wire_mempool_ingest(node, m);
node.new_tx.happened(Transaction(make_spend(a, 0, 90'000, /*salt=*/1)));
ASSERT_EQ(st.mempool().size(), 1u);
sub->dispose();
node.new_tx.happened(Transaction(make_spend(b, 0, 90'000, /*salt=*/2)));
EXPECT_EQ(st.mempool().size(), 1u) << "after dispose, no further relay may fold";
}
// ════════════════════════════════════════════════════════════════════════
// A relayed tx reaches the assembled embedded template once MN+tip arm the
// bundle; an on_invalidate() reorg demotes back to the retained dashd fallback.
// (MN/tip legs are still poked directly -- only new_tx is wired in this slice.)
// ════════════════════════════════════════════════════════════════════════
TEST(DashReceptionWire, RelayedTxReachesEmbeddedTemplateThenInvalidateDemotes) {
UTXOViewCache utxo(nullptr);
const uint256 prev = raw256(0x77);
utxo.add_coin(Outpoint(prev, 0), Coin(100'000, {}, 1, false));
dash::interfaces::Node node;
NodeCoinState st;
st.mempool().set_utxo(&utxo);
CoinStateMaintainer m(st);
auto sub = wire_mempool_ingest(node, m);
// Reception leg feeds the mempool; MN + tip arm the bundle.
node.new_tx.happened(Transaction(make_spend(prev, 0, 90'000, /*salt=*/1))); // fee 10'000
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());
bool fb = false;
WorkSelection sel = st.select_work([&]() { fb = true; return dash::coin::DashWorkData{}; });
EXPECT_EQ(sel.source, WorkSource::Embedded);
EXPECT_FALSE(fb) << "embedded arm must not invoke the dashd fallback";
EXPECT_EQ(sel.work.m_height, H);
ASSERT_EQ(st.mempool().size(), 1u);
EXPECT_EQ(sel.work.m_tx_hashes.size(), 1u)
<< "the RELAYED tx must reach the assembled embedded template";
// Reorg: on_invalidate drops the tip -> next get_work falls back to dashd.
m.on_invalidate();
EXPECT_FALSE(m.live());
bool fb2 = false;
WorkSelection sel2 = st.select_work([&]() { fb2 = true; return dash::coin::DashWorkData{}; });
EXPECT_EQ(sel2.source, WorkSource::DashdFallback);
EXPECT_TRUE(fb2) << "after invalidate, the retained dashd fallback must run";
}
// ════════════════════════════════════════════════════════════════════════
// Leg 2: a new_tip advance fired on the interface arms the maintainer tip-
// readiness THROUGH THE WIRE -- no direct on_new_tip() poke. A tip arriving
// before the first mnlistdiff must NOT go live (MN list absent); once the MN
// list seeds the other prerequisite the bundle arms and select_work flips to
// the embedded arm, and the WIRED tip params reach the assembled template.
// ════════════════════════════════════════════════════════════════════════
TEST(DashReceptionWire, NewTipRelayArmsBundleOnceMnSeeded) {
UTXOViewCache utxo(nullptr);
dash::interfaces::Node node;
NodeCoinState st;
st.mempool().set_utxo(&utxo);
CoinStateMaintainer m(st);
auto sub = wire_tip_ingest(node, m);
ASSERT_TRUE(sub) << "wire must return a live subscription handle";
// Tip arrives first (reception is async): tip-readiness is set via the
// wire, but the MN list is still empty, so the bundle stays on dashd.
dash::interfaces::TipAdvance t;
t.prev_height = H - 1; t.prev_hash = PREV_HASH; t.bits_for_next = BITS;
t.mtp_at_tip = MTP; t.address_version = DASH_PUBKEY_VER;
t.address_p2sh_version = DASH_P2SH_VER; t.curtime = CURTIME; t.version = VERSION;
node.new_tip.happened(t);
EXPECT_FALSE(m.live()) << "tip alone must not arm the bundle -- MN list absent";
// MN list seeds the second prerequisite -> bundle arms, embedded arm wins.
m.on_mn_list_update(single_mn(p2pkh_script(0x30)));
ASSERT_TRUE(m.live()) << "tip (via wire) + MN must arm the embedded bundle";
bool fb = false;
WorkSelection sel = st.select_work([&]() { fb = true; return dash::coin::DashWorkData{}; });
EXPECT_EQ(sel.source, WorkSource::Embedded);
EXPECT_FALSE(fb) << "embedded arm must not invoke the dashd fallback";
EXPECT_EQ(sel.work.m_height, H) << "the WIRED tip params must reach the template";
}
// ════════════════════════════════════════════════════════════════════════
// Disposing the tip handle tears the subscription down: a later tip advance is
// not applied, so a post-reorg bundle cannot silently re-arm off a stale feed.
// ════════════════════════════════════════════════════════════════════════
TEST(DashReceptionWire, DisposeStopsTipIngest) {
UTXOViewCache utxo(nullptr);
dash::interfaces::Node node;
NodeCoinState st;
st.mempool().set_utxo(&utxo);
CoinStateMaintainer m(st);
// MN present up front; the tip is the gating event under test.
m.on_mn_list_update(single_mn(p2pkh_script(0x30)));
auto sub = wire_tip_ingest(node, m);
sub->dispose();
dash::interfaces::TipAdvance t;
t.prev_height = H - 1; t.prev_hash = PREV_HASH; t.bits_for_next = BITS;
t.mtp_at_tip = MTP; t.address_version = DASH_PUBKEY_VER;
t.address_p2sh_version = DASH_P2SH_VER; t.curtime = CURTIME; t.version = VERSION;
node.new_tip.happened(t);
EXPECT_FALSE(m.live()) << "after dispose, a tip advance must not arm the bundle";
}