Skip to content

Commit 1941f77

Browse files
authored
Merge pull request #679 from frstrtr/ltc-doge/g3a-populated-block-regtest
G3a: LTC+DOGE populated-block production harness (test-only, regime-independent)
2 parents 12de90f + 5b0860a commit 1941f77

2 files changed

Lines changed: 594 additions & 0 deletions

File tree

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
#!/usr/bin/env bash
2+
# ltc_g2_crossing_staged_migration_harness.sh
3+
# -----------------------------------------------------------------------------
4+
# Greenlight gate G2 -- LTC (+DOGE aux) V35->V36 CROSSING staged-migration
5+
# harness. This is THE contabo LTC-pool prod-cutover gate (operator 2026-06-17
6+
# re-scope): the staged v35->v36 miner-reappointment soak. DOGE rides LTC as
7+
# merged-mining aux and is validated coupled, not separately.
8+
#
9+
# Gate sequence: G1 (differential byte-parity KAT) -> G2 (this: three-tier
10+
# crossing) -> G3a/G3b (regtest/testnet block production).
11+
#
12+
# WHY "RE-CUT" (integrator 2026-07-12, UID2071): stage the harness with the
13+
# 3301 accept-floor + the PR#95 8-byte sharechain identifier + --fresh-ratchet
14+
# so it LANDS the moment the swarm (VM119/120/121) has a target. The build must
15+
# not be the thing we wait on when the operator start-tap fires.
16+
#
17+
# THREE-TIER WIRE-COMPAT (operator 2026-07-03): the v35-phase swarm runs THREE
18+
# node tiers on ONE sharechain (identifier below), the axis under test being the
19+
# share VERSION and the wire bytes, NOT the namespace:
20+
#
21+
# T-A jtoomim v35-only : OLD raw-tx litecoin p2pool. Accepts OUR v35
22+
# shares BYTE-FOR-BYTE and SEES the v36 vote via
23+
# legacy desired_version / get_desired_version_
24+
# counts -- WITHOUT ever gaining v36 features.
25+
# T-B p2pool-merged-v36 : our python reference (frstrtr/p2pool-merged-v36,
26+
# (python) the v36 DESTINATION shape); the parity oracle.
27+
# T-C c2pool-ltc (C++) : AutoRatchet VOTING->ACTIVATED->CONFIRMED; embedded
28+
# LTC + FULLY-embedded DOGE aux (no external
29+
# dogecoind on the crossing path).
30+
#
31+
# HARD invariant: ZERO v36-byte leak into v35 shares. addr-autoconvert and
32+
# merged-rewards are GATED on v36-active; while VOTING, T-C mints a v35-faithful
33+
# share T-A accepts byte-for-byte, and only ADVERTISES desired_version=36.
34+
#
35+
# STAGING (operator 2026-06-18): forward natural-fill -> ratchet cross -> reverse
36+
# FWD natural-fill : T-A/T-B mint v35, T-C VOTING+v35-faithful; chain fills.
37+
# CROSS ratchet : integrator repoints the R1-LTC cgminers (.37/.38/.39,
38+
# ALL 3 on the flip per operator) v35-node -> v36-node one
39+
# by one; work-weight crosses 60%, count crosses 95%,
40+
# sustained 2*CHAIN_LENGTH -> CONFIRMED.
41+
# REV reverse : pre-CONFIRMED revert posture (drop below 50% =
42+
# DEACTIVATION_THRESHOLD -> ACTIVATED->VOTING) is exercised
43+
# so a stalled crossing rolls back cleanly, not wedges.
44+
#
45+
# BUCKET-1 ISOLATION (operator 2026-06-17 3-bucket rule): PREFIX / IDENTIFIER are
46+
# the per-coin/per-instance isolation boundary -- KEPT, never "standardized".
47+
# This harness pins the LTC identifier explicitly; a private crossing swarm may
48+
# override it (--identifier) to isolate from live prod while keeping the SAME
49+
# version-axis semantics. Self-service RPC creds; no secret on any card.
50+
#
51+
# NATURAL-VOTE HARD GATE (memory contabo-cutover-natural-vote-hardgate): a GREEN
52+
# crossing here authorises DEPLOY of the v36-capable build to contabo ONLY.
53+
# v36_active stays false/VOTING until the REAL network crosses 95% naturally.
54+
# This harness NEVER force-activates and NEVER puts dev rigs on the prod vote.
55+
#
56+
# PER-COIN ISOLATION: LTC (+DOGE aux) only. Touches no other coin tree.
57+
set -euo pipefail
58+
59+
# ---- binaries (self-provision; sudo is operator-only) -----------------------
60+
LTC_DAEMON="${LTC_DAEMON:-litecoind}" # Litecoin Core (RPC fallback; embedded is primary)
61+
LTC_CLI="${LTC_CLI:-litecoin-cli}"
62+
C2POOL_LTC="${C2POOL_LTC:-c2pool-ltc}" # T-C: v36 C++ pool, DOGE aux embedded
63+
ORACLE_MERGED_PY="${ORACLE_MERGED_PY:-$HOME/Github/p2pool-merged-v36/run_p2pool.py}" # T-B
64+
ORACLE_JT_PY="${ORACLE_JT_PY:-}" # T-A: LTC jtoomim v35-only p2pool (NOT staged; see gate)
65+
66+
# ---- sharechain identity: PR#95 8-BYTE id (bucket-1 isolation primitive) -----
67+
# LTC mainnet (src/impl/ltc/params.hpp:79-80): id e037d5b8c6923410 / prefix 7208c1a53ef629b0
68+
# LTC testnet (params.hpp:81-82): id cca5e24ec6408b1e / prefix ad9614f6466a39cf
69+
NET="${LTC_NET:-mainnet}" # crossing exercises the real prod-path semantics
70+
SHARECHAIN_ID="${LTC_SHARECHAIN_ID:-e037d5b8c6923410}" # 8-byte; --identifier overrides for private swarm
71+
SHARECHAIN_PREFIX="${LTC_SHARECHAIN_PREFIX:-7208c1a53ef629b0}"
72+
73+
# ---- protocol floor: 3301 accept-floor (params.hpp:65) ----------------------
74+
# minimum_protocol_version=3301 accepts v35 (3502) peers so T-A/T-B and T-C peer
75+
# across the crossing window; advertised 3600 announces v36 capability (the vote).
76+
MIN_PROTO="${LTC_MIN_PROTO:-3301}"
77+
ADV_PROTO="${LTC_ADV_PROTO:-3600}"
78+
79+
# node/pool ports -- T-A v35-only, T-B merged-v36 python, T-C c2pool-ltc
80+
A_P2P=${A_P2P:-9346}; A_STRATUM=${A_STRATUM:-9327} # jtoomim v35-only
81+
B_P2P=${B_P2P:-9348}; B_STRATUM=${B_STRATUM:-9328} # merged-v36 python
82+
C_P2P=${C_P2P:-9350}; C_STRATUM=${C_STRATUM:-19327} # c2pool-ltc (embedded DOGE aux)
83+
84+
# parent daemon (RPC fallback; embedded primary -- never removed)
85+
RPCPORT=${LTC_RPCPORT:-19332}
86+
P2PPORT=${LTC_P2PPORT:-19333}
87+
DATADIR="${LTC_DATADIR:-$HOME/.litecoin-g2}"
88+
RATCHET_STATE="${LTC_RATCHET_STATE:-$HOME/.c2pool-ltc-g2/ratchet.json}"
89+
90+
# ---- flags ------------------------------------------------------------------
91+
SIM_VOTES=0; FRESH_RATCHET=0
92+
for a in "$@"; do case "$a" in
93+
--sim-votes) SIM_VOTES=1;;
94+
--fresh-ratchet) FRESH_RATCHET=1;; # wipe ratchet state for a clean crossing run
95+
esac; done
96+
97+
log() { echo "[ltc-g2 $(printf "%(%H:%M:%S)T")] $*" >&2; }
98+
die() { echo "[ltc-g2 FAIL] $*" >&2; exit 1; }
99+
need() { command -v "$1" >/dev/null 2>&1 || die "missing binary: $1 (self-provision; sudo is operator-only)"; }
100+
gated(){ echo "[ltc-g2 GATED: $1] ${*:2}" >&2; }
101+
102+
cli() { "$LTC_CLI" -datadir="$DATADIR" -rpcport=$RPCPORT "$@"; }
103+
104+
# ---- self-service RPC creds (never written to a coordination card) ----------
105+
gen_creds() {
106+
mkdir -p "$DATADIR"
107+
if [ ! -f "$DATADIR/.rpccreds" ]; then
108+
local u p
109+
u="ltcg2_$(printf "%(%s)T" -1 | tail -c 6)"
110+
p="$(head -c18 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24)"
111+
printf 'rpcuser=%s\nrpcpassword=%s\n' "$u" "$p" > "$DATADIR/.rpccreds"
112+
chmod 600 "$DATADIR/.rpccreds"
113+
log "generated isolated RPC creds at $DATADIR/.rpccreds (600)"
114+
fi
115+
}
116+
117+
fresh_ratchet() {
118+
[ "$FRESH_RATCHET" -eq 1 ] || return 0
119+
mkdir -p "$(dirname "$RATCHET_STATE")"
120+
if [ -f "$RATCHET_STATE" ]; then
121+
mv -f "$RATCHET_STATE" "$RATCHET_STATE.pre-$(printf "%(%s)T" -1)" # preserve, never destroy history
122+
log "--fresh-ratchet: archived prior ratchet state; T-C starts VOTING from bootstrap"
123+
else
124+
log "--fresh-ratchet: no prior state; T-C starts VOTING from bootstrap"
125+
fi
126+
}
127+
128+
# ---- substrate: parent litecoind (RPC fallback path) ------------------------
129+
start_daemon() {
130+
need "$LTC_DAEMON"; need "$LTC_CLI"
131+
gen_creds
132+
cat "$DATADIR/.rpccreds" > "$DATADIR/litecoin.conf"
133+
cat >> "$DATADIR/litecoin.conf" <<CONF
134+
server=1
135+
listen=1
136+
rpcbind=127.0.0.1
137+
rpcport=$RPCPORT
138+
port=$P2PPORT
139+
CONF
140+
"$LTC_DAEMON" -datadir="$DATADIR" -daemon
141+
for i in $(seq 1 30); do cli getblockchaininfo >/dev/null 2>&1 && break; sleep 1; done
142+
cli getblockchaininfo >/dev/null 2>&1 || die "litecoind did not come up"
143+
log "parent litecoind up: $(cli getblockchaininfo | grep -E 'chain|blocks' | tr -d ' ,\"')"
144+
}
145+
146+
# ---- the three tiers ---------------------------------------------------------
147+
start_tier_a_jtoomim() { # v35-only raw-tx oracle
148+
if [ -z "$ORACLE_JT_PY" ] || [ ! -f "$ORACLE_JT_PY" ]; then
149+
gated ltc-jtoomim-checkout "T-A (jtoomim v35-only litecoin p2pool) not staged on workstation \
150+
(only p2pool-btc-jtoomim exists) -- C1 byte-for-byte + C2 vote-visibility against the v35-ONLY tier \
151+
DEFERRED until the LTC jtoomim checkout is provisioned. Set ORACLE_JT_PY to arm."
152+
return 0
153+
fi
154+
log "starting T-A (jtoomim v35-only) p2p=$A_P2P stratum=$A_STRATUM id=$SHARECHAIN_ID"
155+
nohup python3 "$ORACLE_JT_PY" --net litecoin \
156+
--p2pool-port $A_P2P --worker-port $A_STRATUM \
157+
--bitcoind-rpc-port $RPCPORT --give-author 0 \
158+
>"$HOME/.c2pool-ltc-g2/tierA-jtoomim.log" 2>&1 &
159+
echo $! > "$HOME/.c2pool-ltc-g2/tierA.pid"
160+
}
161+
162+
start_tier_b_merged() { # merged-v36 python (the v36 DESTINATION / parity oracle)
163+
[ -f "$ORACLE_MERGED_PY" ] || { gated merged-oracle "T-B $ORACLE_MERGED_PY not staged"; return 0; }
164+
log "starting T-B (p2pool-merged-v36 python) p2p=$B_P2P stratum=$B_STRATUM id=$SHARECHAIN_ID"
165+
nohup python3 "$ORACLE_MERGED_PY" --net litecoin \
166+
--p2pool-port $B_P2P --worker-port $B_STRATUM \
167+
--bitcoind-rpc-port $RPCPORT --give-author 0 \
168+
>"$HOME/.c2pool-ltc-g2/tierB-merged.log" 2>&1 &
169+
echo $! > "$HOME/.c2pool-ltc-g2/tierB.pid"
170+
}
171+
172+
start_tier_c_c2pool() { # c2pool-ltc C++ (AutoRatchet; DOGE aux embedded)
173+
need "$C2POOL_LTC"
174+
fresh_ratchet
175+
mkdir -p "$(dirname "$RATCHET_STATE")"
176+
log "starting T-C (c2pool-ltc) p2p=$C_P2P stratum=$C_STRATUM min-proto=$MIN_PROTO adv=$ADV_PROTO"
177+
# Peers T-A/T-B on the shared 8-byte identifier; VOTING mints v35-faithful,
178+
# advertises desired_version=36. DOGE aux is embedded (merged-mining), coupled.
179+
nohup "$C2POOL_LTC" --run --"$NET" \
180+
--coin-rpc 127.0.0.1:$RPCPORT --coin-rpc-auth "$DATADIR/litecoin.conf" \
181+
--identifier "$SHARECHAIN_ID" --prefix "$SHARECHAIN_PREFIX" \
182+
--sharechain-port $C_P2P --stratum "0.0.0.0:$C_STRATUM" \
183+
--addnode 127.0.0.1:$A_P2P --addnode 127.0.0.1:$B_P2P \
184+
--ratchet-state "$RATCHET_STATE" \
185+
>"$HOME/.c2pool-ltc-g2/tierC-c2pool.log" 2>&1 &
186+
echo $! > "$HOME/.c2pool-ltc-g2/tierC.pid"
187+
}
188+
189+
# ---- the 5 checks ------------------------------------------------------------
190+
# SIM mode (--sim-votes) proves the staged-gate + vote-visibility LOGIC now,
191+
# rig-independent, via the LTC AutoRatchet KATs. LIVE rows (real work-weighted
192+
# R1-LTC hashrate .37/.38/.39) stay GATED until the crossing start-tap fires and
193+
# the rigs are freed onto the v36 tier.
194+
#
195+
# !! DEPENDENCY GAP (found 2026-07-12): the LTC test target `share_test`
196+
# (src/impl/ltc/test/CMakeLists.txt) has NO AutoRatchet sim KATs, though DGB and
197+
# BTC do (src/impl/{dgb,btc}/test/auto_ratchet_sim_test.cpp,
198+
# auto_ratchet_tail_guard_test.cpp, desired_version_tally_test.cpp) -- and those
199+
# port headers state the state machine is "identical shape to ltc::AutoRatchet".
200+
# LTC being the REFERENCE yet lacking the KATs is a coverage inversion. Until the
201+
# LTC ports land, the C2/C3/C4 sim rows are [GATED: ltc-ratchet-kats]. Authoring
202+
# src/impl/ltc/test/auto_ratchet_{sim,tail_guard,desired_version_tally}_test.cpp
203+
# (mirroring dgb, base_version=target-1 not the DGB explicit-35, single Scrypt
204+
# algo so no multi-algo C5 leg) is the immediate next milestone that arms sim.
205+
LTC_RATCHET_TESTDIR="${LTC_RATCHET_TESTDIR:-build_ltc}"
206+
207+
check_c1_threetier_cohabit() {
208+
log "C1 THREE-TIER COHABIT: T-A/T-B/T-C peer on ONE sharechain id=$SHARECHAIN_ID; T-C accepts v35 byte-for-byte"
209+
gated rigs "C1 live-peer + no-split assertion needs all three tiers up on the crossing net"
210+
}
211+
212+
check_c2_vote_visibility() {
213+
log "C2 VOTE VISIBILITY: T-A(v35-only) SEES desired_version=36 via legacy get_desired_version_counts; ZERO v36-byte leak into v35 shares"
214+
if [ "$SIM_VOTES" -eq 1 ]; then
215+
if ctest --test-dir "$LTC_RATCHET_TESTDIR" -N -R 'LTC_AutoRatchetSim|LTC_DesiredVersionTally' 2>/dev/null | grep -q 'Test #'; then
216+
ctest --test-dir "$LTC_RATCHET_TESTDIR" -R 'LTC_AutoRatchetSim|LTC_DesiredVersionTally' \
217+
--output-on-failure || die "C2 sim (vote-visibility / VOTING mints v35-faithful) failed"
218+
else
219+
gated ltc-ratchet-kats "C2 sim needs LTC_AutoRatchetSim + LTC_DesiredVersionTally KATs (not yet authored; port from dgb)"
220+
fi
221+
else
222+
gated rigs "C2 live vote-visibility needs the v35-only tier fed real work"
223+
fi
224+
}
225+
226+
check_c3_staged_accept_gate() {
227+
log "C3 STAGED ACCEPT GATE (#288): flat-95%-count desired GATED behind 60%-by-WORK accept; mint cannot outrun accept"
228+
if [ "$SIM_VOTES" -eq 1 ]; then
229+
if ctest --test-dir "$LTC_RATCHET_TESTDIR" -N -R 'LTC_AutoRatchetTailGuard' 2>/dev/null | grep -q 'Test #'; then
230+
ctest --test-dir "$LTC_RATCHET_TESTDIR" -R 'LTC_AutoRatchetTailGuard' \
231+
--output-on-failure || die "C3 sim (#288 tail-guard) failed -- mint outran accept"
232+
else
233+
gated ltc-ratchet-kats "C3 sim needs LTC_AutoRatchetTailGuard KAT (not yet authored; port from dgb)"
234+
fi
235+
else
236+
gated rigs "C3 live mint-cannot-outrun-accept needs rigs to move work weights"
237+
fi
238+
}
239+
240+
check_c4_ratchet_persist_reverse() {
241+
log "C4 RATCHET + PERSIST + REVERSE: VOTING->ACTIVATED->CONFIRMED on 60%-work+95%/2*CL; survives restart; pre-CONFIRMED drop<50% reverts cleanly"
242+
if [ "$SIM_VOTES" -eq 1 ]; then
243+
if ctest --test-dir "$LTC_RATCHET_TESTDIR" -N -R 'LTC_AutoRatchetSim' 2>/dev/null | grep -q 'Test #'; then
244+
ctest --test-dir "$LTC_RATCHET_TESTDIR" -R 'LTC_AutoRatchetSim' \
245+
--output-on-failure || die "C4 sim (state-machine/restart/reverse KAT) failed"
246+
else
247+
gated ltc-ratchet-kats "C4 sim needs LTC_AutoRatchetSim KAT (not yet authored; port from dgb)"
248+
fi
249+
else
250+
gated rigs "C4 live CONFIRMED requires sustained 2*CHAIN_LENGTH of rig-fed 95%/60% shares; reverse leg needs a controlled drop"
251+
fi
252+
}
253+
254+
check_c5_byte_compat_differential() {
255+
log "C5 BYTE-COMPAT DIFFERENTIAL: c2pool v35 share serialization == jtoomim/merged-v36 python byte-for-byte (G1 + jtoomim-byte-compat)"
256+
# The merged wire-compat runtime KAT (PR#665) is the standing byte-parity gate.
257+
if ctest --test-dir "$LTC_RATCHET_TESTDIR" -N -R 'wirecompat|WireCompat' 2>/dev/null | grep -q 'Test #'; then
258+
ctest --test-dir "$LTC_RATCHET_TESTDIR" -R 'wirecompat|WireCompat' \
259+
--output-on-failure || die "C5 (v35 byte-parity differential) failed -- v36 byte leaked into a v35 share"
260+
else
261+
gated ltc-ratchet-kats "C5 differential needs the wirecompat runtime KAT built in $LTC_RATCHET_TESTDIR (PR#665)"
262+
fi
263+
}
264+
265+
# ---- driver ------------------------------------------------------------------
266+
main() {
267+
mkdir -p "$HOME/.c2pool-ltc-g2"
268+
case "${1:-run}" in
269+
provision) start_daemon; start_tier_a_jtoomim; start_tier_b_merged; start_tier_c_c2pool ;;
270+
checks)
271+
check_c1_threetier_cohabit
272+
check_c2_vote_visibility
273+
check_c3_staged_accept_gate
274+
check_c4_ratchet_persist_reverse
275+
check_c5_byte_compat_differential
276+
log "G2 crossing check pass complete (sim=$SIM_VOTES fresh-ratchet=$FRESH_RATCHET). Fill scripts/ltc_g2_evidence_template.md."
277+
;;
278+
run|*)
279+
log "G2 crossing harness: provision three-tier substrate, then run 5 checks."
280+
log " staged with 3301 floor + PR#95 8-byte id ($SHARECHAIN_ID) + --fresh-ratchet=$FRESH_RATCHET."
281+
log " rig-bound + live rows GATED until the operator start-tap frees R1-LTC .37/.38/.39 onto the v36 tier."
282+
start_daemon || true
283+
start_tier_a_jtoomim || true
284+
start_tier_b_merged || true
285+
start_tier_c_c2pool || true
286+
check_c1_threetier_cohabit
287+
check_c2_vote_visibility
288+
check_c3_staged_accept_gate
289+
check_c4_ratchet_persist_reverse
290+
check_c5_byte_compat_differential
291+
;;
292+
esac
293+
}
294+
main "$@"

0 commit comments

Comments
 (0)