|
33 | 33 |
|
34 | 34 | #include <map> |
35 | 35 | #include <set> |
| 36 | +#include <utility> |
36 | 37 | #include <vector> |
37 | 38 |
|
38 | 39 | #include <core/uint256.hpp> |
39 | 40 | #include <impl/btc/known_txs_retention.hpp> |
| 41 | +#include <impl/btc/share.hpp> // real btc share variants + ShareType |
| 42 | +#include <impl/btc/share_tx_refs.hpp> // btc::new_tx_hashes SSOT (#880) |
40 | 43 |
|
41 | 44 | namespace { |
42 | 45 |
|
@@ -206,3 +209,105 @@ TEST(BtcBroadcastMarking, WalkStrandedWhenMarkedBeforeSend) |
206 | 209 | EXPECT_TRUE(walk(chain, marked).empty()); // now correctly de-duped |
207 | 210 | } |
208 | 211 | } |
| 212 | + |
| 213 | + |
| 214 | +// --------------------------------------------------------------------------- |
| 215 | +// #880 REGRESSION: the F3 gate above must be driven by the PRODUCTION probe, |
| 216 | +// not a hand-rolled stand-in. The FakeShare KATs exercise partition_backable's |
| 217 | +// MECHANICS with a top-level new_txs member and a bespoke refs_of -- exactly |
| 218 | +// the shape that let the real bug hide. In production send_shares probed |
| 219 | +// `requires { obj->m_new_transaction_hashes; }`, which is FALSE for every BTC |
| 220 | +// share variant (the list is nested in m_tx_info), so the gate collected empty |
| 221 | +// refs, every share was vacuously backable, and the gate silently no-op'd for |
| 222 | +// ALL versions -- a share referencing a tx the peer lacked was broadcast anyway |
| 223 | +// and tripped the canonical "referenced unknown transaction" disconnect. |
| 224 | +// |
| 225 | +// These KATs build REAL btc share types and drive them through the SAME |
| 226 | +// btc::new_tx_hashes SSOT that node.cpp's partition_backable refs_of now uses. |
| 227 | +// FAILS-BEFORE: revert btc::new_tx_hashes (or the gate) to the flat probe and |
| 228 | +// the v17/v33 expectations collapse -- the unbacked share is no longer withheld. |
| 229 | +// --------------------------------------------------------------------------- |
| 230 | +namespace { |
| 231 | + |
| 232 | +// The exact refs_of node.cpp installs on partition_backable: route the variant |
| 233 | +// through the btc::new_tx_hashes SSOT. |
| 234 | +std::vector<uint256> production_refs_of(btc::ShareType& share) |
| 235 | +{ |
| 236 | + std::vector<uint256> hashes; |
| 237 | + share.invoke([&](auto* obj) { |
| 238 | + if (const auto* new_txs = btc::new_tx_hashes(obj)) |
| 239 | + hashes.assign(new_txs->begin(), new_txs->end()); |
| 240 | + }); |
| 241 | + return hashes; |
| 242 | +} |
| 243 | + |
| 244 | +} // namespace |
| 245 | + |
| 246 | +// Accessor SSOT: a REAL v17 share nests its new-tx hashes in m_tx_info; the |
| 247 | +// production probe must surface them (the dead flat probe returned nullptr). |
| 248 | +TEST(BtcBroadcastGate, RealV17ShareRefsResolvedViaSsot) |
| 249 | +{ |
| 250 | + btc::Share s; |
| 251 | + s.m_tx_info.m_new_transaction_hashes = {TA, TC}; |
| 252 | + |
| 253 | + const auto* refs = btc::new_tx_hashes(&s); |
| 254 | + ASSERT_NE(refs, nullptr); |
| 255 | + ASSERT_EQ(refs->size(), 2u); // dead flat probe -> nullptr |
| 256 | + EXPECT_EQ((*refs)[0], TA); |
| 257 | + EXPECT_EQ((*refs)[1], TC); |
| 258 | +} |
| 259 | + |
| 260 | +// Accessor SSOT: v33 uses the same nested carrier. |
| 261 | +TEST(BtcBroadcastGate, RealV33ShareRefsResolvedViaSsot) |
| 262 | +{ |
| 263 | + btc::NewShare s; |
| 264 | + s.m_tx_info.m_new_transaction_hashes = {TC}; |
| 265 | + |
| 266 | + const auto* refs = btc::new_tx_hashes(&s); |
| 267 | + ASSERT_NE(refs, nullptr); |
| 268 | + ASSERT_EQ(refs->size(), 1u); |
| 269 | + EXPECT_EQ((*refs)[0], TC); |
| 270 | +} |
| 271 | + |
| 272 | +// Accessor SSOT: v34/v35/v36 carry no m_tx_info -> nullptr, compiled out. |
| 273 | +TEST(BtcBroadcastGate, SegwitAndMergedVariantsHaveNoRefs) |
| 274 | +{ |
| 275 | + btc::SegwitMiningShare v34; |
| 276 | + btc::PaddingBugfixShare v35; |
| 277 | + btc::MergedMiningShare v36; |
| 278 | + EXPECT_EQ(btc::new_tx_hashes(&v34), nullptr); |
| 279 | + EXPECT_EQ(btc::new_tx_hashes(&v35), nullptr); |
| 280 | + EXPECT_EQ(btc::new_tx_hashes(&v36), nullptr); |
| 281 | +} |
| 282 | + |
| 283 | +// The gate end-to-end: REAL share variants + the production refs_of through |
| 284 | +// btc::partition_backable. A v17 share referencing a tx whose bytes we do NOT |
| 285 | +// hold must be withheld; the backable ones survive, in order. Pre-fix the dead |
| 286 | +// probe made every share vacuously backable -> skipped==0, nothing withheld. |
| 287 | +TEST(BtcBroadcastGate, RealShareTxInfoRefsWithheld) |
| 288 | +{ |
| 289 | + std::vector<btc::Share*> raws; // own the heap shares for cleanup |
| 290 | + auto v17 = [&](std::vector<uint256> refs) { |
| 291 | + auto* raw = new btc::Share(); |
| 292 | + raw->m_tx_info.m_new_transaction_hashes = std::move(refs); |
| 293 | + raws.push_back(raw); |
| 294 | + btc::ShareType sv; sv = raw; return sv; |
| 295 | + }; |
| 296 | + |
| 297 | + const std::set<uint256> held{TA, TB}; // TC bytes NOT held |
| 298 | + |
| 299 | + std::vector<btc::ShareType> batch; |
| 300 | + batch.push_back(v17({TA})); // backable |
| 301 | + batch.push_back(v17({TC})); // references unheld TC -> withheld |
| 302 | + batch.push_back(v17({TA, TB})); // backable |
| 303 | + |
| 304 | + const std::size_t skipped = |
| 305 | + btc::partition_backable(batch, held, production_refs_of); |
| 306 | + |
| 307 | + EXPECT_EQ(skipped, 1u); // dead-probe regression -> 0 |
| 308 | + ASSERT_EQ(batch.size(), 2u); |
| 309 | + EXPECT_EQ(production_refs_of(batch[0]).front(), TA); // order preserved |
| 310 | + EXPECT_EQ(production_refs_of(batch[1]).front(), TA); |
| 311 | + |
| 312 | + for (auto* raw : raws) delete raw; |
| 313 | +} |
0 commit comments