Skip to content

Commit 47ce84f

Browse files
VijitSingh97claude
andcommitted
test(config): backfill CI-tier coverage for control gate, allowlist round-trip, validators (#521, #522, #523)
Adds tier-1 stack tests in tests/stack/run.sh: - #521: telegram.control fails closed on each of its three legs (dashboard.control off, telegram.commands off, empty allowed_ids) and validates when fully configured. - #522: data-driven preview->commit round-trip through the approval gate for every CONTROL_DASHBOARD_EDITABLE_KEYS key (scalars + the 24 allowlisted TELEGRAM_EVENT_* toggles), asserting each lands in config.json. - #523: negative paths for tari.payout_scan_birthday (non-integer, >65535) and a malformed-but-present tari.spend_public_key, plus reflect assertions for monero.rpc_lan_access -> MONERO_RPC_BIND and monero.prep_blocks_threads -> MONERO_PREP_THREADS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d4af133 commit 47ce84f

1 file changed

Lines changed: 124 additions & 0 deletions

File tree

tests/stack/run.sh

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2638,6 +2638,51 @@ printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","n
26382638
out="$(cd "$V" && PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
26392639
assert_rc "malformed tari view key rejected" "$?" "1"
26402640
assert_contains "malformed tari view-key message" "$out" "64-character hex"
2641+
# (5) A spend key PRESENT but malformed (not 64 hex) is rejected — the require-both check keys off
2642+
# the same 64-hex shape as the view key, so a garbage spend key fails just like a missing one (#523).
2643+
seed_env
2644+
printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p"}, "tari":{"wallet_address":"T","view_key":"%s","spend_public_key":"deadbeef"}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" "$TVIEW" >"$V/config.json"
2645+
out="$(cd "$V" && PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
2646+
assert_rc "malformed tari spend key rejected" "$?" "1"
2647+
assert_contains "malformed spend-key message names spend_public_key" "$out" "tari.spend_public_key"
2648+
2649+
echo "== black-box: tari.payout_scan_birthday validation (#523) =="
2650+
# The restore-point birthday is validated only on the view-key path (it feeds the tari-wallet). It
2651+
# is "auto" or a u16 days-since-epoch (0–65535) — a block height or an out-of-range value is a
2652+
# common mistake that must fail at apply, not silently mis-restore the wallet. Keys are valid so
2653+
# only the birthday is under test.
2654+
# (1) A non-integer birthday (a block height, say) is refused.
2655+
seed_env
2656+
printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p"}, "tari":{"wallet_address":"T","view_key":"%s","spend_public_key":"%s","payout_scan_birthday":"height-3200000"}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" "$TVIEW" "$TSPEND" >"$V/config.json"
2657+
out="$(cd "$V" && PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
2658+
assert_rc "non-integer birthday rejected" "$?" "1"
2659+
assert_contains "non-integer birthday message names the field" "$out" "tari.payout_scan_birthday"
2660+
# (2) An in-range-looking but too-large birthday (> 65535, e.g. a block height) is refused.
2661+
seed_env
2662+
printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p"}, "tari":{"wallet_address":"T","view_key":"%s","spend_public_key":"%s","payout_scan_birthday":"99999"}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" "$TVIEW" "$TSPEND" >"$V/config.json"
2663+
out="$(cd "$V" && PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
2664+
assert_rc "out-of-range birthday rejected" "$?" "1"
2665+
assert_contains "out-of-range birthday message names the u16 ceiling" "$out" "65535"
2666+
# (3) A valid u16 birthday applies and reflects verbatim into .env.
2667+
seed_env
2668+
printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p"}, "tari":{"wallet_address":"T","view_key":"%s","spend_public_key":"%s","payout_scan_birthday":"1000"}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" "$TVIEW" "$TSPEND" >"$V/config.json"
2669+
out="$(cd "$V" && PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
2670+
assert_rc "valid birthday accepted" "$?" "0"
2671+
assert_eq "valid birthday reflected into .env" "$(run_sourced "$V" env_get_file "$V/.env" TARI_WALLET_BIRTHDAY)" "1000"
2672+
2673+
echo "== black-box: monero.rpc_lan_access + prep_blocks_threads reflect into .env (#523) =="
2674+
# The rendered .env must match the config input. rpc_lan_access gates the monerod RPC bind: default
2675+
# (unset) keeps it localhost-only; true opens it to the LAN. prep_blocks_threads overrides the
2676+
# host-core-derived block-verification thread count verbatim when it is an integer.
2677+
seed_env
2678+
printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p"}, "tari":{"wallet_address":"T"}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" >"$V/config.json"
2679+
out="$(cd "$V" && PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
2680+
assert_eq "rpc_lan_access default binds monerod RPC to localhost" "$(run_sourced "$V" env_get_file "$V/.env" MONERO_RPC_BIND)" "127.0.0.1"
2681+
seed_env
2682+
printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p","rpc_lan_access":true,"prep_blocks_threads":6}, "tari":{"wallet_address":"T"}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" >"$V/config.json"
2683+
out="$(cd "$V" && PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
2684+
assert_eq "rpc_lan_access true binds monerod RPC to all interfaces" "$(run_sourced "$V" env_get_file "$V/.env" MONERO_RPC_BIND)" "0.0.0.0"
2685+
assert_eq "prep_blocks_threads override reflected verbatim" "$(run_sourced "$V" env_get_file "$V/.env" MONERO_PREP_THREADS)" "6"
26412686

26422687
echo "== black-box: tor.auto_heal renders to .env (#424) =="
26432688
# The dashboard's healer reads TOR_AUTO_HEAL from .env. Key absent -> off (the stack never
@@ -4030,6 +4075,44 @@ control_config main
40304075
out="$(cd "$C" && DOCKER_LOG="$CTRL_LOG" PATH="$C/bin:$PATH" ./pithead apply --dry-run --porcelain 2>&1)"
40314076
assert_rc "control without an onion is allowed" "$?" "0"
40324077

4078+
echo "== black-box: telegram.control fails closed on each leg (#521) =="
4079+
# telegram.control (the #338 remote /restart /apply surface) is a remotely-reachable host-control
4080+
# channel, so it refuses to enable unless the whole chain is present: dashboard.control on (the #33
4081+
# spool it rides), telegram.commands on (the bot that answers it), and at least one allow-listed
4082+
# operator id (or every command is refused and the feature is inert). Each leg must fail closed.
4083+
tg_control_config() { # <dashboard.control.enabled> <telegram.commands.enabled> <allowed_ids-json>
4084+
jq -n --arg w "$WALLET" --argjson ctl "$1" --argjson cmds "$2" --argjson ids "$3" \
4085+
'{monero:{mode:"local",wallet_address:$w,node_username:"u",node_password:"p"},
4086+
tari:{wallet_address:"T"}, p2pool:{pool:"main"},
4087+
telegram:{enabled:true,bot_token:"123456:legit-ABC_def",chat_id:"1111",
4088+
commands:{enabled:$cmds}, control:{enabled:true,allowed_ids:$ids}},
4089+
dashboard:{secure:true,host:"box.lan",auth:{username:"admin",password:"a control passphrase"},
4090+
control:{enabled:$ctl}}}' >"$C/config.json"
4091+
}
4092+
# Leg 1: dashboard.control off — the spool the commands ride does not exist.
4093+
tg_control_config false true '[123456]'
4094+
out="$(cd "$C" && DOCKER_LOG="$CTRL_LOG" PATH="$C/bin:$PATH" ./pithead apply --dry-run --porcelain 2>&1)"
4095+
assert_rc "telegram.control without dashboard.control is refused" "$?" "1"
4096+
assert_contains "refusal names dashboard.control.enabled" "$out" "dashboard.control.enabled is false"
4097+
# Leg 2: telegram.commands off — no bot is polling for the commands.
4098+
tg_control_config true false '[123456]'
4099+
out="$(cd "$C" && DOCKER_LOG="$CTRL_LOG" PATH="$C/bin:$PATH" ./pithead apply --dry-run --porcelain 2>&1)"
4100+
assert_rc "telegram.control without telegram.commands is refused" "$?" "1"
4101+
assert_contains "refusal names telegram.commands.enabled" "$out" "telegram.commands.enabled is false"
4102+
# Leg 3: allowed_ids empty — no operator could ever confirm, the feature is inert.
4103+
tg_control_config true true '[]'
4104+
out="$(cd "$C" && DOCKER_LOG="$CTRL_LOG" PATH="$C/bin:$PATH" ./pithead apply --dry-run --porcelain 2>&1)"
4105+
assert_rc "telegram.control with an empty allowed_ids is refused" "$?" "1"
4106+
assert_contains "refusal names allowed_ids" "$out" "telegram.control.allowed_ids is empty"
4107+
# All three legs present — validates.
4108+
tg_control_config true true '[123456]'
4109+
out="$(cd "$C" && DOCKER_LOG="$CTRL_LOG" PATH="$C/bin:$PATH" ./pithead apply --dry-run --porcelain 2>&1)"
4110+
assert_rc "fully-configured telegram.control validates" "$?" "0"
4111+
assert_not_contains "fully-configured control raises no telegram.control error" "$out" "telegram.control.enabled is true but"
4112+
# Restore the section baseline (control on, no telegram) for the tests that follow.
4113+
control_config main
4114+
(cd "$C" && DOCKER_LOG="$CTRL_LOG" PATH="$C/bin:$PATH" ./pithead apply -y >/dev/null 2>&1)
4115+
40334116
echo "== black-box: approval gate fails closed on a destructive commit (#33 / #338) =="
40344117
UUID3="33333333-3333-4333-8333-333333333333"
40354118
# Clean baseline: pool mini, clearnet off, applied.
@@ -4185,6 +4268,47 @@ gate_try "$C/cand.json"
41854268
assert_eq "non-security change on a security-laden config still applies" "$(jq -r '.status' "$RESULTS/$UUID5.json" 2>/dev/null)" "applied"
41864269
assert_eq "pool tier change landed in config.json" "$(jq -r '.p2pool.pool' "$C/config.json")" "mini"
41874270

4271+
echo "== black-box: editable-allowlist commit round-trip, every key (#522) =="
4272+
# Every key on CONTROL_DASHBOARD_EDITABLE_KEYS must actually round-trip a real preview->commit
4273+
# through the approval gate and land in config.json — not just pass a describe_change unit check.
4274+
# Fresh baseline with each tunable at a known value so every row below is a genuine single-key
4275+
# env diff (pool flips P2POOL_FLAGS + P2POOL_PORT, both allowlisted).
4276+
jq -n --arg w "$WALLET" '{
4277+
monero:{mode:"local",wallet_address:$w,node_username:"u",node_password:"p",mem_limit:"4g",prep_blocks_threads:4},
4278+
tari:{wallet_address:"T",mem_limit:"3g"}, p2pool:{pool:"main"},
4279+
xvb:{enabled:true,donation_level:"donor"}, telegram:{daily_summary_time:"08:00"},
4280+
dashboard:{secure:true,host:"box.lan",tari_required:true,check_for_updates:true,timezone:"UTC",
4281+
hashrate_drop_threshold:50,hashrate_drop_minutes:10,
4282+
auth:{username:"admin",password:"a control passphrase"},control:{enabled:true}}}' >"$C/config.json"
4283+
(cd "$C" && DOCKER_LOG="$CTRL_LOG" PATH="$C/bin:$PATH" ./pithead apply -y >/dev/null 2>&1)
4284+
roundtrip_key() { # <label> <jq-set> <jq-read> <expected>
4285+
jq "$2" "$C/config.json" >"$C/cand.json"
4286+
gate_try "$C/cand.json"
4287+
assert_eq "$1 commit applies through the gate" "$(jq -r '.status' "$RESULTS/$UUID5.json" 2>/dev/null)" "applied"
4288+
assert_eq "$1 landed in config.json" "$(jq -r "$3" "$C/config.json")" "$4"
4289+
}
4290+
roundtrip_key "XVB_ENABLED" '.xvb.enabled=false' '.xvb.enabled' "false"
4291+
roundtrip_key "XVB_DONATION_LEVEL" '.xvb.donation_level="whale"' '.xvb.donation_level' "whale"
4292+
roundtrip_key "TARI_REQUIRED" '.dashboard.tari_required=false' '.dashboard.tari_required' "false"
4293+
roundtrip_key "DASHBOARD_CHECK_UPDATES" '.dashboard.check_for_updates=false' '.dashboard.check_for_updates' "false"
4294+
roundtrip_key "DASHBOARD_TZ" '.dashboard.timezone="Europe/Paris"' '.dashboard.timezone' "Europe/Paris"
4295+
roundtrip_key "MONERO_MEM_LIMIT" '.monero.mem_limit="5g"' '.monero.mem_limit' "5g"
4296+
roundtrip_key "TARI_MEM_LIMIT" '.tari.mem_limit="2g"' '.tari.mem_limit' "2g"
4297+
roundtrip_key "MONERO_PREP_THREADS" '.monero.prep_blocks_threads=8' '.monero.prep_blocks_threads' "8"
4298+
roundtrip_key "HASHRATE_DROP_THRESHOLD_PCT" '.dashboard.hashrate_drop_threshold=40' '.dashboard.hashrate_drop_threshold' "40"
4299+
roundtrip_key "HASHRATE_DROP_MINUTES" '.dashboard.hashrate_drop_minutes=15' '.dashboard.hashrate_drop_minutes' "15"
4300+
roundtrip_key "TELEGRAM_DAILY_SUMMARY_TIME" '.telegram.daily_summary_time="09:30"' '.telegram.daily_summary_time' "09:30"
4301+
roundtrip_key "P2POOL_FLAGS/P2POOL_PORT" '.p2pool.pool="mini"' '.p2pool.pool' "mini"
4302+
# The 24 allowlisted TELEGRAM_EVENT_* toggles. wallet_changed + clearnet_exposed are deliberately
4303+
# NOT on the allowlist (tamper-evidence alarms; their refusal is asserted above), so they are
4304+
# excluded here. Each flips true->false as a single-key diff.
4305+
for ev in node_down node_recovered worker_offline worker_recovered worker_joined worker_left \
4306+
sync_finished disk_space db_unhealthy db_reset xvb_no_share xvb_registration new_release \
4307+
stack_online daily_summary hashrate_low hashrate_loss hugepages low_ram high_reject_rate \
4308+
block_found payout_found payout_confirmed container_unhealthy; do
4309+
roundtrip_key "TELEGRAM_EVENT ${ev}" ".telegram.events.${ev}=false" ".telegram.events.${ev}" "false"
4310+
done
4311+
41884312
echo "== black-box: per-worker token mask + host-side restore (#172) =="
41894313
# dashboard.workers[].token is a per-rig credential living in a VARIABLE-LENGTH array — out of the
41904314
# fixed CONTROL_SECRET_PATHS walk. The masked prefill copy must sentinel each set token (extends

0 commit comments

Comments
 (0)