Created
May 11, 2026 21:29
-
-
Save ZenGround0/00a0fa704922f51dfa5395f15ddf8380 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Exercise the SectorStatusChecker EVM contract against a live miner+sector. | |
| # | |
| # Usage: | |
| # check-sector-status.sh <contract_eth_addr> <miner_addr> <sector_number> | |
| # | |
| # - <contract_eth_addr>: 0x... address of deployed SectorStatusChecker | |
| # - <miner_addr>: Filecoin miner address (e.g. t0180698 or f0...) | |
| # - <sector_number>: u64 sector number | |
| # | |
| # Env overrides: | |
| # ETH_RPC default http://127.0.0.1:4444/rpc/v1 | |
| # LOTUS default "lotus" (in PATH) | |
| # | |
| # Steps: | |
| # 1. Calls GenerateSectorLocation off-chain (lotus state call) to get | |
| # {status, aux_data} for the sector. Aux_data is the CBOR-encoded | |
| # SectorLocation = [deadline:i64, partition:i64]; sentinel (-1, -1) | |
| # means "NO_DEADLINE/NO_PARTITION" (sector not tracked anywhere — | |
| # either compacted-terminated or never committed). | |
| # 2. Calls validateSectorStatus on the contract via eth_call, asserting | |
| # the contract agrees the sector is in the reported status. | |
| # 3. Calls getNominalSectorExpiration on the contract via eth_call, and | |
| # compares against the expiration in `lotus state sector` output. | |
| # Skipped when status is Dead (sector is not in the Sectors AMT, so | |
| # no nominal expiration exists by design). | |
| set -euo pipefail | |
| ETH_RPC="${ETH_RPC:-http://127.0.0.1:4444/rpc/v1}" | |
| LOTUS="${LOTUS:-lotus}" | |
| if [[ $# -ne 3 ]]; then | |
| echo "usage: $0 <contract_eth_addr> <miner_addr> <sector_number>" >&2 | |
| exit 2 | |
| fi | |
| CONTRACT="$1" | |
| MINER="$2" | |
| SECTOR="$3" | |
| # FRC-42 method number for miner GenerateSectorLocation. | |
| GENERATE_SECTOR_LOCATION_METHOD=1321604665 | |
| # CBOR encoding of SectorLocation { deadline: -1, partition: -1 }, base64-encoded: | |
| # 0x82 (array of 2) 0x20 (i64 -1) 0x20 (i64 -1) ==> base64 "giAg" | |
| AUX_NONE_B64="giAg" | |
| AUX_NONE_HEX="0x822020" | |
| # Sanity check args. | |
| [[ "$CONTRACT" =~ ^0x[0-9a-fA-F]{40}$ ]] || { echo "contract must be 0x-prefixed eth address" >&2; exit 2; } | |
| [[ "$SECTOR" =~ ^[0-9]+$ ]] || { echo "sector must be a non-negative integer" >&2; exit 2; } | |
| # Resolve miner to its f0/t0 ID form (uint64 form) for the contract. | |
| # `lotus state lookup <addr>` works for both ID (t0..) and robust (t2..) forms. | |
| MINER_ID_ADDR="$("$LOTUS" state lookup "$MINER" 2>/dev/null || true)" | |
| [[ -n "$MINER_ID_ADDR" ]] || { echo "could not resolve miner to ID address: $MINER" >&2; exit 1; } | |
| MINER_ID="${MINER_ID_ADDR#?0}" # strip leading 't0' or 'f0' | |
| [[ "$MINER_ID" =~ ^[0-9]+$ ]] || { echo "unexpected ID address shape: $MINER_ID_ADDR" >&2; exit 1; } | |
| echo "== Inputs ==" | |
| echo " contract: $CONTRACT" | |
| echo " miner: $MINER (id=$MINER_ID, resolved=$MINER_ID_ADDR)" | |
| echo " sector: $SECTOR" | |
| echo " eth-rpc: $ETH_RPC" | |
| echo | |
| # Tiny pure-stdlib CBOR decoder for our SectorLocation = [i64, i64] case. | |
| # Reads aux_data base64 from stdin and prints "<deadline> <partition>". | |
| decode_aux() { | |
| python3 - "$1" <<'PY' | |
| import sys, base64 | |
| b = base64.b64decode(sys.argv[1]) | |
| def rd(b, i): | |
| major, info = b[i] >> 5, b[i] & 0x1f | |
| i += 1 | |
| if info < 24: | |
| v = info | |
| elif info == 24: | |
| v = b[i]; i += 1 | |
| elif info == 25: | |
| v = int.from_bytes(b[i:i+2], 'big'); i += 2 | |
| elif info == 26: | |
| v = int.from_bytes(b[i:i+4], 'big'); i += 4 | |
| elif info == 27: | |
| v = int.from_bytes(b[i:i+8], 'big'); i += 8 | |
| else: | |
| raise ValueError(f"unsupported additional info {info}") | |
| if major == 1: # negative integer | |
| v = -1 - v | |
| elif major != 0: # unsigned integer | |
| raise ValueError(f"unexpected CBOR major {major}") | |
| return v, i | |
| if not b or b[0] != 0x82: | |
| raise SystemExit(f"expected CBOR array(2), got 0x{b[0]:02x}" if b else "empty aux_data") | |
| i = 1 | |
| d, i = rd(b, i) | |
| p, i = rd(b, i) | |
| print(f"{d} {p}") | |
| PY | |
| } | |
| # Pretty-print the location based on (deadline, partition). | |
| fmt_location() { | |
| local d="$1" p="$2" | |
| if [[ "$d" == "-1" && "$p" == "-1" ]]; then | |
| echo "NO_DEADLINE NO_PARTITION" | |
| else | |
| echo "deadline=$d partition=$p" | |
| fi | |
| } | |
| # Pretty-print status code. | |
| fmt_status() { | |
| case "$1" in | |
| 0) echo "Dead";; | |
| 1) echo "Active";; | |
| 2) echo "Faulty";; | |
| *) echo "Unknown($1)";; | |
| esac | |
| } | |
| # ---- 1. Off-chain GenerateSectorLocation via lotus state call ---- | |
| echo "== 1. GenerateSectorLocation (off-chain) ==" | |
| PARAMS_B64="$("$LOTUS" chain encode params "$MINER_ID_ADDR" "$GENERATE_SECTOR_LOCATION_METHOD" "{\"SectorNumber\":$SECTOR}")" | |
| # Run the call; capture stdout *and* allow a failure (e.g. older actor version | |
| # might raise instead of returning Dead/(-1,-1) for a missing sector). | |
| if CALL_OUT="$("$LOTUS" state call --encoding base64 "$MINER_ID_ADDR" "$GENERATE_SECTOR_LOCATION_METHOD" "$PARAMS_B64" 2>&1)"; then | |
| CALL_OK=1 | |
| else | |
| CALL_OK=0 | |
| fi | |
| # Parse "Exit code: X" if present. | |
| EXIT_CODE="$(grep -E '^Exit code:' <<<"$CALL_OUT" | awk '{print $3}')" | |
| EXIT_CODE="${EXIT_CODE:-?}" | |
| if [[ "$CALL_OK" != "1" || "$EXIT_CODE" != "0" ]]; then | |
| # Look for the actor-side "sector ... not found" signal in either the lotus | |
| # error text or the call output. Treat it as Dead with NO_DEADLINE/NO_PARTITION. | |
| if grep -qE "sector .* not found|not_found|USR_NOT_FOUND" <<<"$CALL_OUT"; then | |
| echo " WARN: GenerateSectorLocation reported sector not found; falling back to status=Dead, location=NO_DEADLINE/NO_PARTITION" | |
| STATUS=0 | |
| AUX_B64="$AUX_NONE_B64" | |
| AUX_HEX="$AUX_NONE_HEX" | |
| else | |
| echo "GenerateSectorLocation failed (exit=$EXIT_CODE):" >&2 | |
| echo "$CALL_OUT" >&2 | |
| exit 1 | |
| fi | |
| else | |
| # Extract Status + AuxData from the JSON Return block. | |
| RETURN_JSON="$(awk '/^Return:/{flag=1; next} flag' <<<"$CALL_OUT")" | |
| if command -v jq >/dev/null 2>&1; then | |
| STATUS=$(echo "$RETURN_JSON" | jq -r '.Status') | |
| AUX_B64=$(echo "$RETURN_JSON" | jq -r '.AuxData') | |
| else | |
| STATUS=$(echo "$RETURN_JSON" | sed -n 's/.*"Status": *\([0-9]*\).*/\1/p') | |
| AUX_B64=$(echo "$RETURN_JSON" | sed -n 's/.*"AuxData": *"\([^"]*\)".*/\1/p') | |
| fi | |
| AUX_HEX="0x$(echo "$AUX_B64" | base64 -d | od -An -tx1 -v | tr -d ' \n')" | |
| fi | |
| # Decode + pretty-print. | |
| read -r LOC_DEADLINE LOC_PARTITION < <(decode_aux "$AUX_B64") | |
| echo " status: $STATUS ($(fmt_status "$STATUS"))" | |
| echo " location: $(fmt_location "$LOC_DEADLINE" "$LOC_PARTITION")" | |
| echo " aux_data: $AUX_B64 ($AUX_HEX)" | |
| echo | |
| # ---- 2. validateSectorStatus via contract eth_call ---- | |
| echo "== 2. validateSectorStatus (contract) ==" | |
| VS_CALL=$(cast calldata "validateSectorStatus(uint64,uint64,uint8,bytes)" "$MINER_ID" "$SECTOR" "$STATUS" "$AUX_HEX") | |
| VS_RET=$(cast call --rpc-url "$ETH_RPC" "$CONTRACT" "$VS_CALL") | |
| VS_VALID=$(cast --to-dec "$VS_RET") | |
| echo " raw: $VS_RET" | |
| echo " valid: $VS_VALID" | |
| if [[ "$VS_VALID" != "1" ]]; then | |
| echo "FAIL: contract reports sector is NOT in status=$STATUS ($(fmt_status "$STATUS"))" >&2 | |
| exit 1 | |
| fi | |
| echo " PASS: contract agrees sector is $(fmt_status "$STATUS")" | |
| echo | |
| # ---- 3. getNominalSectorExpiration via contract eth_call ---- | |
| echo "== 3. getNominalSectorExpiration (contract) ==" | |
| if [[ "$STATUS" == "0" ]]; then | |
| echo " SKIP: sector is Dead — not in Sectors AMT, no nominal expiration to compare" | |
| echo | |
| echo "All checks passed." | |
| exit 0 | |
| fi | |
| NE_CALL=$(cast calldata "getNominalSectorExpiration(uint64,uint64)" "$MINER_ID" "$SECTOR") | |
| NE_RET=$(cast call --rpc-url "$ETH_RPC" "$CONTRACT" "$NE_CALL") | |
| CONTRACT_EXP=$(cast --to-dec "$NE_RET") | |
| echo " contract expiration: $CONTRACT_EXP" | |
| # Compare against lotus state sector. | |
| LOTUS_EXP=$("$LOTUS" state sector "$MINER_ID_ADDR" "$SECTOR" 2>/dev/null \ | |
| | awk '/^Expiration:/ {print $2; exit}') | |
| echo " lotus expiration: ${LOTUS_EXP:-<none>}" | |
| if [[ -z "$LOTUS_EXP" ]]; then | |
| echo " WARN: could not read expiration from 'lotus state sector' (sector may not be in on-chain SectorInfo array — e.g. terminated/compacted)." | |
| elif [[ "$CONTRACT_EXP" == "$LOTUS_EXP" ]]; then | |
| echo " PASS: expirations match" | |
| else | |
| echo " FAIL: expirations differ" >&2 | |
| exit 1 | |
| fi | |
| echo | |
| echo "All checks passed." |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Termianted and uncompacted sector