|
| 1 | +#pragma once |
| 2 | + |
| 3 | +// Phase S8 — WonBlock dual-arm delivery plan + reached-network verdict (LEAF). |
| 4 | +// |
| 5 | +// THE last PURE rung before the operator-gated broadcaster_full keystone. The |
| 6 | +// binding (block_relay_binding.hpp) joins the live broadcaster pool to the |
| 7 | +// planner and yields an AnnouncePlan (one inv frame + the live slot keys). A won |
| 8 | +// block must reach the network by EITHER of two independent arms: |
| 9 | +// |
| 10 | +// [won block] --+-- embedded-P2P arm -- inv fan-out to live pool slots |
| 11 | +// +-- dashd submitblock -- raw-block RPC fallback |
| 12 | +// |
| 13 | +// This leaf decides, as INERT data, WHICH arms a given won block is armed on, |
| 14 | +// and (separately) evaluates whether the block actually REACHED the network once |
| 15 | +// the keystone has executed both arms and reported their results. It performs no |
| 16 | +// I/O: it opens no socket, drives no io_context, issues no RPC, recomputes no |
| 17 | +// hash. The keystone walks the plan, writes the inv frames onto the live slots' |
| 18 | +// sockets, calls dashd submitblock with the raw bytes, collects the per-arm |
| 19 | +// results, and hands them back to evaluate() for the won-block-reaches-network |
| 20 | +// verdict. That transmit + RPC is the keystone's operator-gated concern; the |
| 21 | +// DECISION of arms and the verdict LOGIC are pure and unit-testable here, with |
| 22 | +// zero sockets and zero live dashd, like every sibling leaf. |
| 23 | +// |
| 24 | +// SCOPE / NON-CONSENSUS: single dash tree, header-only, socket-free. The raw |
| 25 | +// block bytes are an OPAQUE, caller-supplied blob (the won block as captured for |
| 26 | +// submission, the same span the broadcaster's submit_block_raw_all / FanOutHook |
| 27 | +// already consume) -- they are carried verbatim, NOT re-serialized here, and no |
| 28 | +// consensus value is computed or altered. |
| 29 | + |
| 30 | +#include "block_relay_binding.hpp" |
| 31 | +#include "block_relay_plan.hpp" |
| 32 | + |
| 33 | +#include <core/uint256.hpp> |
| 34 | + |
| 35 | +#include <cstddef> |
| 36 | +#include <span> |
| 37 | +#include <utility> |
| 38 | +#include <vector> |
| 39 | + |
| 40 | +namespace dash |
| 41 | +{ |
| 42 | + |
| 43 | +// The dual-arm delivery plan for ONE won block: the embedded-P2P fan-out plan |
| 44 | +// plus the raw block bytes for the dashd submitblock fallback, each tagged with |
| 45 | +// whether that arm is armed. The submitblock arm is armed whenever raw bytes are |
| 46 | +// present, INDEPENDENT of live-slot count -- so a won block with zero live peers |
| 47 | +// still ships via dashd. The P2P arm is armed only when the live pool is |
| 48 | +// non-empty. Both arms are INERT: nothing is transmitted until the keystone |
| 49 | +// walks this plan. |
| 50 | +struct DualArmPlan |
| 51 | +{ |
| 52 | + AnnouncePlan p2p; // inv frame + live slot keys |
| 53 | + std::vector<unsigned char> submitblock_bytes; // raw won block (opaque) |
| 54 | + bool p2p_armed{false}; // live pool non-empty |
| 55 | + bool submitblock_armed{false}; // raw bytes present |
| 56 | + |
| 57 | + // True if the block is armed on at least one arm -- i.e. there is some path |
| 58 | + // to the network. A won block recorded with no live peers and no raw bytes is |
| 59 | + // a dead end (caller error); both arms then report false. |
| 60 | + bool any_armed() const { return p2p_armed || submitblock_armed; } |
| 61 | +}; |
| 62 | + |
| 63 | +// The post-execution verdict: did the won block actually reach the network? The |
| 64 | +// keystone fills this in AFTER it has driven both arms, from the real results: |
| 65 | +// * p2p_acks -- how many live slots accepted the inv fan-out (>=1 relayed) |
| 66 | +// * submitblock_ok -- dashd accepted the raw block via the RPC fallback |
| 67 | +// reached_network is the OR of the two arms: a single successful arm suffices. |
| 68 | +struct DeliveryVerdict |
| 69 | +{ |
| 70 | + std::size_t p2p_acks{0}; |
| 71 | + bool submitblock_ok{false}; |
| 72 | + |
| 73 | + bool reached_network() const { return p2p_acks > 0 || submitblock_ok; } |
| 74 | +}; |
| 75 | + |
| 76 | +// Builds dual-arm delivery plans over a binding (which owns the live-pool view |
| 77 | +// and the relay book). Stateless and pure: opens no socket, recomputes no hash. |
| 78 | +class DualArmPlanner |
| 79 | +{ |
| 80 | +public: |
| 81 | + explicit DualArmPlanner(WonBlockRelayBinding& binding) : m_binding(binding) {} |
| 82 | + |
| 83 | + // Record the won block (via the binding -> planner -> relay) and build its |
| 84 | + // dual-arm plan against the broadcaster's CURRENT live slots. The raw bytes |
| 85 | + // are carried verbatim for the dashd submitblock arm and copied into the |
| 86 | + // plan so it outlives the caller's buffer. The P2P arm is armed iff the live |
| 87 | + // fan-out is non-empty; the submitblock arm is armed iff raw bytes were |
| 88 | + // supplied. The block records even when BOTH would be disarmed (recording is |
| 89 | + // decoupled from delivery), so a peer connecting later can still getdata it. |
| 90 | + DualArmPlan plan(const uint256& hash, |
| 91 | + WonBlockRelay::BlockType block, |
| 92 | + std::span<const unsigned char> raw_block_bytes) |
| 93 | + { |
| 94 | + DualArmPlan out; |
| 95 | + out.p2p = m_binding.plan_announce(hash, std::move(block)); |
| 96 | + out.submitblock_bytes.assign(raw_block_bytes.begin(), raw_block_bytes.end()); |
| 97 | + out.p2p_armed = out.p2p.fanout() > 0; |
| 98 | + out.submitblock_armed = !out.submitblock_bytes.empty(); |
| 99 | + return out; |
| 100 | + } |
| 101 | + |
| 102 | + // Pure verdict evaluator: given the per-arm results the keystone observed, |
| 103 | + // decide whether the won block reached the network. Static -- it depends only |
| 104 | + // on the reported results, not on any pool state. |
| 105 | + static DeliveryVerdict evaluate(std::size_t p2p_acks, bool submitblock_ok) |
| 106 | + { |
| 107 | + DeliveryVerdict v; |
| 108 | + v.p2p_acks = p2p_acks; |
| 109 | + v.submitblock_ok = submitblock_ok; |
| 110 | + return v; |
| 111 | + } |
| 112 | + |
| 113 | +private: |
| 114 | + WonBlockRelayBinding& m_binding; |
| 115 | +}; |
| 116 | + |
| 117 | +} // namespace dash |
0 commit comments