|
| 1 | +#pragma once |
| 2 | + |
| 3 | +// DASH v36-native MINT-side auto-ratchet of the P2P accept floor. |
| 4 | +// |
| 5 | +// FENCED / ADDITIVE / UNWIRED. This header carries NO src/ call site — the live |
| 6 | +// consumer (node.cpp, analogous to dgb::NodeImpl min-proto-ratchet) rides the S8 |
| 7 | +// pool-node wire-up and is consensus-bearing, so it is surfaced separately for an |
| 8 | +// integrator merge tap. Landing the skeleton here de-risks that wire-up and closes |
| 9 | +// the single gap the #357 AutoRatchet mint-gate weighting audit found: DASH already |
| 10 | +// defines the WEIGHTED desired-version tally (version_negotiation.hpp |
| 11 | +// get_desired_version_weights) and the 60%/95% accept gates consume it, but there |
| 12 | +// was NO mint-side consumer that lifts MINIMUM_PROTOCOL_VERSION off the same window. |
| 13 | +// |
| 14 | +// v36-STANDARDIZATION (operator 3-bucket rule, bucket 2 = v36-NATIVE SHARED |
| 15 | +// STRUCTURE): the auto-ratchet is standardized cross-coin toward the v37 shape. It |
| 16 | +// mirrors dgb::apply_min_protocol_ratchet_decision byte-for-byte in decision logic; |
| 17 | +// only the floor constants differ per coin (the ISOLATION invariant stays per-coin). |
| 18 | +// |
| 19 | +// ORACLE NOTE (honest, surfaced to integrator): the DASH oracle frstrtr/p2pool-dash |
| 20 | +// (older-than-v35) has NO update_min_protocol_version and NO NEW_MINIMUM_PROTOCOL_VERSION |
| 21 | +// — MINIMUM_PROTOCOL_VERSION is the static 1700 net constant (dash.py:23). The |
| 22 | +// dgb-scrypt oracle DOES ratchet (data.py:715 newminpver = NEW_MINIMUM_PROTOCOL_VERSION, |
| 23 | +// 1400 -> 3500). So the DASH ratchet TARGET floor is a c2pool v36-native choice, not |
| 24 | +// an oracle-derived number. The decision MATH below is oracle-faithful to the |
| 25 | +// dgb-scrypt update_min_protocol_version (floor-divided 95% work-weighted gate); the |
| 26 | +// concrete target value is a wire-up-time decision and is intentionally NOT baked in |
| 27 | +// here — every function takes current_floor / target_floor as parameters. |
| 28 | + |
| 29 | +#include <core/uint256.hpp> // uint288 |
| 30 | + |
| 31 | +#include <cstdint> |
| 32 | +#include <map> |
| 33 | +#include <vector> |
| 34 | + |
| 35 | +namespace dash { |
| 36 | + |
| 37 | +// A single share's contribution to a desired-version tally: the version it desires |
| 38 | +// and its work weight (target_to_average_attempts of its target, == ShareIndex::work, |
| 39 | +// the same metric get_desired_version_weights caches -- version_negotiation.hpp D5). |
| 40 | +struct VersionWork { |
| 41 | + int64_t desired_version; |
| 42 | + uint288 work; |
| 43 | +}; |
| 44 | + |
| 45 | +// WORK-WEIGHTED accumulation: sum each version's work over the window. This is the |
| 46 | +// map get_desired_version_weights produces from a real chain window; exposed here so |
| 47 | +// the KAT can exercise the exact weighting divergence without a live ShareChain. |
| 48 | +inline std::map<uint64_t, uint288> |
| 49 | +accumulate_version_weights(const std::vector<VersionWork>& window) |
| 50 | +{ |
| 51 | + std::map<uint64_t, uint288> res; |
| 52 | + for (const auto& vw : window) |
| 53 | + res[static_cast<uint64_t>(vw.desired_version)] += vw.work; |
| 54 | + return res; |
| 55 | +} |
| 56 | + |
| 57 | +// FLAT (plain-count) accumulation: one vote per share, work ignored. This is the |
| 58 | +// count map the ratchet must NOT consume -- it exists only to PROVE, in the KAT, |
| 59 | +// that the weighted decision diverges from the plain-count decision on a crafted |
| 60 | +// window (matching the btc/dgb work-weighted-not-flat-count divergence assert). |
| 61 | +inline std::map<uint64_t, uint288> |
| 62 | +accumulate_version_counts(const std::vector<VersionWork>& window) |
| 63 | +{ |
| 64 | + std::map<uint64_t, uint288> res; |
| 65 | + for (const auto& vw : window) |
| 66 | + res[static_cast<uint64_t>(vw.desired_version)] += uint288(1); |
| 67 | + return res; |
| 68 | +} |
| 69 | + |
| 70 | +// Pure ratchet -- mirrors the dgb-scrypt oracle update_min_protocol_version dict |
| 71 | +// branch (data.py:715-719): |
| 72 | +// minpver = getattr(share.net, 'MINIMUM_PROTOCOL_VERSION', 1700) |
| 73 | +// newminpver = getattr(share.net, 'NEW_MINIMUM_PROTOCOL_VERSION', minpver) |
| 74 | +// if (counts is not None) and (minpver < newminpver): |
| 75 | +// if counts.get(share.VERSION, 0) >= sum(counts.itervalues())*95//100: |
| 76 | +// share.net.MINIMUM_PROTOCOL_VERSION = newminpver |
| 77 | +// |
| 78 | +// counts is the WORK-WEIGHTED map (get_desired_version_weights), keyed by |
| 79 | +// desired_version and looked up by the best share's VERSION. Pure: no I/O, inputs |
| 80 | +// unmutated; returns the (possibly lifted) floor. |
| 81 | +// |
| 82 | +// Integer math is the oracle's exactly: ratchet iff |
| 83 | +// best_weight >= floor(total_weight * 95 / 100). |
| 84 | +// We floor-divide (total*95)/100 rather than the cross-multiplied |
| 85 | +// best*100 >= total*95 idiom -- the two differ at the boundary when |
| 86 | +// (total*95) % 100 != 0 (floor-div accepts best == floor(...), cross-mult rejects), |
| 87 | +// and the oracle floor-divides. uint288 throughout: no IEEE-double, no overflow at |
| 88 | +// consensus (2^256-scale) weights. |
| 89 | +inline uint32_t ratchet_min_protocol_version( |
| 90 | + const std::map<uint64_t, uint288>& version_weights, |
| 91 | + int64_t best_version, |
| 92 | + uint32_t current_floor, |
| 93 | + uint32_t target_floor) |
| 94 | +{ |
| 95 | + if (current_floor >= target_floor) // oracle: minpver < newminpver guard |
| 96 | + return current_floor; |
| 97 | + |
| 98 | + uint288 best_weight; |
| 99 | + uint288 total_weight; |
| 100 | + for (const auto& [ver, w] : version_weights) { |
| 101 | + total_weight = total_weight + w; |
| 102 | + if (static_cast<int64_t>(ver) == best_version) |
| 103 | + best_weight = best_weight + w; |
| 104 | + } |
| 105 | + // oracle: counts.get(share.VERSION, 0) >= sum(counts.itervalues())*95//100 |
| 106 | + if (best_weight >= (total_weight * uint32_t(95)) / uint32_t(100)) |
| 107 | + return target_floor; |
| 108 | + return current_floor; |
| 109 | +} |
| 110 | + |
| 111 | +// Runtime WIRING decision -- the call-site guard the pure ratchet deliberately omits. |
| 112 | +// ratchet_min_protocol_version mirrors ONLY the oracle dict-branch: over an empty or |
| 113 | +// partial window it ratchets (0 >= 0), because the oracle's full-window guard lives |
| 114 | +// at the CALL SITE (main.py: only update when len(shares) > CHAIN_LENGTH, sampling |
| 115 | +// get_desired_version over [nth_parent(prev, CHAIN_LENGTH*9/10), CHAIN_LENGTH/10] -- |
| 116 | +// the SAME window the 60% version-switch gate reads, version_negotiation.hpp). Without |
| 117 | +// this guard a fresh node (parent_height < CHAIN_LENGTH -> partial window) would |
| 118 | +// spuriously lift the floor and reject every legitimate peer. This function is that |
| 119 | +// guard: no lift until a FULL window exists behind the best share's parent, then |
| 120 | +// delegate to the pure ratchet over the window the caller sampled. |
| 121 | +inline uint32_t apply_min_protocol_ratchet_decision( |
| 122 | + int32_t parent_height, |
| 123 | + int32_t chain_length, |
| 124 | + const std::map<uint64_t, uint288>& window_weights, |
| 125 | + int64_t best_version, |
| 126 | + uint32_t current_floor, |
| 127 | + uint32_t target_floor) |
| 128 | +{ |
| 129 | + if (current_floor >= target_floor) // oracle guard: minpver < newminpver |
| 130 | + return current_floor; |
| 131 | + if (parent_height < chain_length) // oracle: len(shares) > CHAIN_LENGTH |
| 132 | + return current_floor; // no full window yet -> never lift |
| 133 | + return ratchet_min_protocol_version( |
| 134 | + window_weights, best_version, current_floor, target_floor); |
| 135 | +} |
| 136 | + |
| 137 | +} // namespace dash |
0 commit comments