#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$ROOT_DIR" usage() { cat <<'USAGE' Usage: run_multi_rir_ccr_replay_verify.sh --rir [--mode snapshot|delta|both] [--keep-db] Options: --rir Comma-separated RIR list, e.g. apnic or apnic,ripe --mode snapshot | delta | both (default: both) --keep-db Keep per-run RocksDB directories (default: remove after verify) --bundle-root

Override bundle root --out-root

Override output root (default: rpki/target/replay) --run-tag Override timestamp suffix for all RIR runs USAGE } MODE="both" KEEP_DB=0 RIR_LIST="" BUNDLE_ROOT="${BUNDLE_ROOT:-/home/yuyr/dev/rust_playground/routinator/bench/multi_rir_demo/runs/20260316-112341-multi-final3}" OUT_ROOT="${OUT_ROOT:-$ROOT_DIR/target/replay}" RUN_TAG="${RUN_TAG:-$(date -u +%Y%m%dT%H%M%SZ)}" while [[ $# -gt 0 ]]; do case "$1" in --rir) shift RIR_LIST="${1:-}" ;; --mode) shift MODE="${1:-}" ;; --keep-db) KEEP_DB=1 ;; --bundle-root) shift BUNDLE_ROOT="${1:-}" ;; --out-root) shift OUT_ROOT="${1:-}" ;; --run-tag) shift RUN_TAG="${1:-}" ;; -h|--help) usage exit 0 ;; *) echo "unknown argument: $1" >&2 usage >&2 exit 2 ;; esac shift || true done if [[ -z "$RIR_LIST" ]]; then echo "--rir is required" >&2 usage >&2 exit 2 fi case "$MODE" in snapshot|delta|both) ;; *) echo "invalid --mode: $MODE" >&2 usage >&2 exit 2 ;; esac CASE_INFO_SCRIPT="$ROOT_DIR/scripts/payload_replay/multi_rir_case_info.py" mkdir -p "$OUT_ROOT" RUN_ROOT="$OUT_ROOT/$RUN_TAG" mkdir -p "$RUN_ROOT" cargo build --release --bin rpki --bin ccr_to_routinator_csv --bin ccr_verify >/dev/null summary_md="$RUN_ROOT/multi_rir_ccr_replay_verify_${RUN_TAG}_summary.md" summary_json="$RUN_ROOT/multi_rir_ccr_replay_verify_${RUN_TAG}_summary.json" python3 - <<'PY' >/dev/null PY summary_json_tmp="$(mktemp)" printf '[]' > "$summary_json_tmp" run_one_mode() { local rir="$1" local mode="$2" local run_dir="$3" local trust_anchor="$4" local tal_path="$5" local ta_path="$6" local base_archive="$7" local base_locks="$8" local base_csv="$9" local delta_archive="${10}" local delta_locks="${11}" local delta_csv="${12}" local snapshot_validation_time="${13}" local delta_validation_time="${14}" local db_dir="$run_dir/${rir}_${mode}_db" local report_json="$run_dir/${rir}_${mode}_report.json" local run_log="$run_dir/${rir}_${mode}_run.log" local ccr_path="$run_dir/${rir}_${mode}.ccr" local csv_path="$run_dir/${rir}_${mode}_ccr_vrps.csv" local compare_md="$run_dir/${rir}_${mode}_ccr_compare_summary.md" local only_ours="$run_dir/${rir}_${mode}_ccr_only_in_ours.csv" local only_record="$run_dir/${rir}_${mode}_ccr_only_in_record.csv" local verify_json="$run_dir/${rir}_${mode}_ccr_verify.json" local meta_json="$run_dir/${rir}_${mode}_meta.json" rm -rf "$db_dir" local -a cmd=(target/release/rpki --db "$db_dir" --tal-path "$tal_path" --ta-path "$ta_path") if [[ "$mode" == "snapshot" ]]; then cmd+=(--payload-replay-archive "$base_archive" --payload-replay-locks "$base_locks" --validation-time "$snapshot_validation_time") else cmd+=( --payload-base-archive "$base_archive" --payload-base-locks "$base_locks" --payload-base-validation-time "$snapshot_validation_time" --payload-delta-archive "$delta_archive" --payload-delta-locks "$delta_locks" --validation-time "$delta_validation_time" ) fi cmd+=(--report-json "$report_json" --ccr-out "$ccr_path") local start_s end_s duration_s start_s="$(date +%s)" ( echo "# ${rir} ${mode} command:" printf '%q ' "${cmd[@]}" echo echo "${cmd[@]}" ) 2>&1 | tee "$run_log" >/dev/null end_s="$(date +%s)" duration_s="$((end_s - start_s))" target/release/ccr_to_routinator_csv \ --ccr "$ccr_path" \ --out "$csv_path" \ --trust-anchor "$trust_anchor" >/dev/null local record_csv if [[ "$mode" == "snapshot" ]]; then record_csv="$base_csv" else record_csv="$delta_csv" fi ./scripts/payload_replay/compare_with_routinator_record.sh \ "$csv_path" \ "$record_csv" \ "$compare_md" \ "$only_ours" \ "$only_record" >/dev/null target/release/ccr_verify \ --ccr "$ccr_path" \ --db "$db_dir" > "$verify_json" python3 - "$report_json" "$meta_json" "$mode" "$duration_s" <<'PY' import json, sys from pathlib import Path report = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8')) meta = { 'mode': sys.argv[3], 'duration_seconds': int(sys.argv[4]), 'validation_time': report.get('validation_time_rfc3339_utc'), 'publication_points_processed': report['tree']['instances_processed'], 'publication_points_failed': report['tree']['instances_failed'], 'vrps': len(report['vrps']), 'aspas': len(report['aspas']), } Path(sys.argv[2]).write_text(json.dumps(meta, ensure_ascii=False, indent=2)+'\n', encoding='utf-8') PY if [[ "$KEEP_DB" -eq 0 ]]; then rm -rf "$db_dir" fi } IFS=',' read -r -a RIRS <<< "$RIR_LIST" for rir in "${RIRS[@]}"; do rir="$(echo "$rir" | xargs)" [[ -n "$rir" ]] || continue eval "$(python3 "$CASE_INFO_SCRIPT" --bundle-root "$BUNDLE_ROOT" --rir "$rir" --format env)" run_dir="$RUN_ROOT/${rir}_ccr_replay_${RUN_TAG}" mkdir -p "$run_dir" if [[ "$MODE" == "snapshot" || "$MODE" == "both" ]]; then run_one_mode \ "$rir" snapshot "$run_dir" "$TRUST_ANCHOR" "$TAL_PATH" "$TA_PATH" \ "$PAYLOAD_REPLAY_ARCHIVE" "$PAYLOAD_REPLAY_LOCKS" "$ROUTINATOR_BASE_RECORD_CSV" \ "$PAYLOAD_DELTA_ARCHIVE" "$PAYLOAD_DELTA_LOCKS" "$ROUTINATOR_DELTA_RECORD_CSV" \ "$SNAPSHOT_VALIDATION_TIME" "$DELTA_VALIDATION_TIME" fi if [[ "$MODE" == "delta" || "$MODE" == "both" ]]; then run_one_mode \ "$rir" delta "$run_dir" "$TRUST_ANCHOR" "$TAL_PATH" "$TA_PATH" \ "$PAYLOAD_REPLAY_ARCHIVE" "$PAYLOAD_REPLAY_LOCKS" "$ROUTINATOR_BASE_RECORD_CSV" \ "$PAYLOAD_DELTA_ARCHIVE" "$PAYLOAD_DELTA_LOCKS" "$ROUTINATOR_DELTA_RECORD_CSV" \ "$SNAPSHOT_VALIDATION_TIME" "$DELTA_VALIDATION_TIME" fi python3 - "$summary_json_tmp" "$run_dir" "$rir" "$MODE" <<'PY' import json, sys from pathlib import Path summary_path = Path(sys.argv[1]) run_dir = Path(sys.argv[2]) rir = sys.argv[3] mode = sys.argv[4] rows = json.loads(summary_path.read_text(encoding='utf-8')) for submode in ['snapshot','delta']: if mode not in ('both', submode): continue compare = run_dir / f'{rir}_{submode}_ccr_compare_summary.md' meta = run_dir / f'{rir}_{submode}_meta.json' verify = run_dir / f'{rir}_{submode}_ccr_verify.json' if not compare.exists() or not meta.exists() or not verify.exists(): continue compare_text = compare.read_text(encoding='utf-8') meta_obj = json.loads(meta.read_text(encoding='utf-8')) verify_obj = json.loads(verify.read_text(encoding='utf-8')) def metric(name): prefix = f'| {name} | ' for line in compare_text.splitlines(): if line.startswith(prefix): return int(line.split('|')[2].strip()) raise SystemExit(f'missing metric {name} in {compare}') rows.append({ 'rir': rir, 'mode': submode, 'run_dir': str(run_dir), 'duration_seconds': meta_obj['duration_seconds'], 'vrps': meta_obj['vrps'], 'aspas': meta_obj['aspas'], 'only_in_ours': metric('only_in_ours'), 'only_in_record': metric('only_in_record'), 'intersection': metric('intersection'), 'state_hashes_ok': verify_obj.get('state_hashes_ok'), }) summary_path.write_text(json.dumps(rows, ensure_ascii=False, indent=2)+'\n', encoding='utf-8') PY done python3 - "$summary_json_tmp" "$summary_json" "$summary_md" "$RUN_TAG" <<'PY' import json, sys from pathlib import Path rows = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8')) out_json = Path(sys.argv[2]) out_md = Path(sys.argv[3]) run_tag = sys.argv[4] out_json.write_text(json.dumps(rows, ensure_ascii=False, indent=2)+'\n', encoding='utf-8') parts = [] parts.append('# Multi-RIR CCR Replay Verify Summary\n\n') parts.append(f'- run_tag: `{run_tag}`\n\n') parts.append('| rir | mode | duration_s | vrps | aspas | only_in_ours | only_in_record | state_hashes_ok |\n') parts.append('|---|---|---:|---:|---:|---:|---:|---|\n') for row in rows: parts.append(f"| {row['rir']} | {row['mode']} | {row['duration_seconds']} | {row['vrps']} | {row['aspas']} | {row['only_in_ours']} | {row['only_in_record']} | {row['state_hashes_ok']} |\n") out_md.write_text(''.join(parts), encoding='utf-8') PY rm -f "$summary_json_tmp" echo "== multi-rir replay verify complete ==" >&2 echo "- summary: $summary_md" >&2 echo "- summary json: $summary_json" >&2