From e3d274948f0dc0f1851564f9c9039115d8a6e325 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Thu, 2 Oct 2025 16:28:46 +0900 Subject: [PATCH 01/16] 1st version of demo: add scripts and README --- crypto-benchmarks.rs/.gitignore | 10 + crypto-benchmarks.rs/demo/README.md | 139 ++++++++++++++ .../demo/scripts/00_set_cli.sh | 33 ++++ .../demo/scripts/10_init_inputs.sh | 75 ++++++++ .../demo/scripts/20_make_registry.sh | 86 +++++++++ .../demo/scripts/30_cast_votes.sh | 132 ++++++++++++++ .../demo/scripts/40_make_certificate.sh | 52 ++++++ .../demo/scripts/50_verify_certificate.sh | 40 ++++ .../demo/scripts/60_pretty_print_cert.sh | 80 ++++++++ .../demo/scripts/70_run_one.sh | 94 ++++++++++ crypto-benchmarks.rs/demo/scripts/80_sweep.sh | 97 ++++++++++ .../demo/scripts/85_plot_sweep.sh | 172 ++++++++++++++++++ .../demo/scripts/pretty_cert.py | 81 +++++++++ 13 files changed, 1091 insertions(+) create mode 100644 crypto-benchmarks.rs/demo/README.md create mode 100755 crypto-benchmarks.rs/demo/scripts/00_set_cli.sh create mode 100755 crypto-benchmarks.rs/demo/scripts/10_init_inputs.sh create mode 100755 crypto-benchmarks.rs/demo/scripts/20_make_registry.sh create mode 100755 crypto-benchmarks.rs/demo/scripts/30_cast_votes.sh create mode 100755 crypto-benchmarks.rs/demo/scripts/40_make_certificate.sh create mode 100755 crypto-benchmarks.rs/demo/scripts/50_verify_certificate.sh create mode 100755 crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh create mode 100755 crypto-benchmarks.rs/demo/scripts/70_run_one.sh create mode 100755 crypto-benchmarks.rs/demo/scripts/80_sweep.sh create mode 100755 crypto-benchmarks.rs/demo/scripts/85_plot_sweep.sh create mode 100644 crypto-benchmarks.rs/demo/scripts/pretty_cert.py diff --git a/crypto-benchmarks.rs/.gitignore b/crypto-benchmarks.rs/.gitignore index 26f7359ea..928cb0fc5 100644 --- a/crypto-benchmarks.rs/.gitignore +++ b/crypto-benchmarks.rs/.gitignore @@ -1,3 +1,13 @@ target/ *.cbor *.txt + +# Demo artifacts +demo/**/*.pretty.json +demo/**/*.png +demo/**/*.csv +demo/scripts/.env_cli + +# Python cache +__pycache__/ +*.pyc diff --git a/crypto-benchmarks.rs/demo/README.md b/crypto-benchmarks.rs/demo/README.md new file mode 100644 index 000000000..35db94145 --- /dev/null +++ b/crypto-benchmarks.rs/demo/README.md @@ -0,0 +1,139 @@ + + +# Demo Scripts for Leios Crypto Benchmarks + +This folder contains scripts that orchestrate end-to-end demonstrations of BLS-based vote aggregation and certificate generation/verification for the Leios project. + +## Prerequisites + +- Build the CLI once from the repository root: + + ```bash + cargo build --release -p crypto-benchmarks + ``` + + The resulting binary will be at: + ``` + target/release/leios_crypto_benchmarks + ``` + +- Ensure Python 3 is available with `cbor2` and `matplotlib` installed. + For example: + + ```bash + python3 -m venv .venv + source .venv/bin/activate + pip install cbor2 matplotlib + ``` + +- Make sure all scripts are executable: + + ```bash + chmod +x scripts/*.sh + ``` + +## Workflow + +The scripts are designed to be run from the `demo/` directory. + +### Run Step by Step (Manual Mode) + +You can run each script individually to understand and control each step of the process for a given number of voters (e.g., 32). Use the `-d` option to specify the output directory (e.g., `run32`). + +#### 10_init_inputs.sh + +Initialize inputs for N voters: + +```bash +scripts/10_init_inputs.sh -d run32 +``` + +#### 20_make_registry.sh + +Build the registry from initialized inputs: + +```bash +scripts/20_make_registry.sh -d run32 -n 32 +``` + +#### 30_cast_votes.sh + +Cast votes with a specified fraction of voters voting (e.g., 1.0 means all vote): + +```bash +scripts/30_cast_votes.sh -d run32 -f 1.0 +``` + +#### 40_make_certificate.sh + +Generate the aggregated certificate: + +```bash +scripts/40_make_certificate.sh -d run32 +``` + +#### 50_verify_certificate.sh + +Verify the generated certificate: + +```bash +scripts/50_verify_certificate.sh -d run32 +``` + +#### 60_pretty_print_cert.sh + +Pretty-print key metrics and statistics of the certificate: + +```bash +scripts/60_pretty_print_cert.sh -d run32 +``` + +### Run a Single End-to-End Demo + +```bash +scripts/70_run_one.sh -d run32 -n 32 -f 1.0 +``` + +This will: + +1. Initialize inputs (`10_init_inputs.sh`) +2. Build a registry (`20_make_registry.sh`) +3. Cast votes (`30_cast_votes.sh`) +4. Make a certificate (`40_make_certificate.sh`) +5. Verify the certificate (`50_verify_certificate.sh`) +6. Pretty-print key metrics (`60_pretty_print_cert.sh`) + +All files are placed in `demo/run32/`. + +### Sweep Across Multiple N + +```bash +scripts/80_sweep.sh -d sweep1 -f 1.0 --ns "32 64 128 256 512 1024 2056 3000" +``` + +This will run the full pipeline for multiple voter sizes (`N`) and write a CSV of results: + +``` +demo/sweep1/sweep_results.csv +``` + +### Plot Sweep Results + +```bash +scripts/85_plot_sweep.sh -d sweep1 --open +``` + +This will generate a plot `gain_vs_N.png` showing compression ratio vs. number of voters. +Use `--open` to automatically open the PNG. + +## Notes + +- All scripts must be run from within the `demo/` directory. +- Directories passed via `-d` will be created automatically under `demo/`. +- Compression ratio is defined as: + + ``` + votes_bytes / certificate_bytes + ``` + +which illustrates the storage/bandwidth savings achieved by BLS aggregation. \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/00_set_cli.sh b/crypto-benchmarks.rs/demo/scripts/00_set_cli.sh new file mode 100755 index 000000000..1f830234a --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/00_set_cli.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail +# Usage: demo/scripts/00_set_cli.sh [-p /abs/path/to/leios_crypto_benchmarks] +# Writes demo/scripts/.env_cli with an absolute CLI path so other scripts can source it. + +DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENV_FILE="$DIR_SCRIPT/.env_cli" +CLI_PATH="" + +while getopts ":p:" opt; do + case "$opt" in + p) CLI_PATH="$OPTARG" ;; + *) echo "Usage: $0 [-p /abs/path/to/leios_crypto_benchmarks]"; exit 2 ;; + esac +done + +if [[ -z "${CLI_PATH}" ]]; then + read -r -p "Absolute path to leios_crypto_benchmarks binary: " CLI_PATH +fi + +if [[ ! -x "${CLI_PATH}" ]]; then + echo "Error: '${CLI_PATH}' is not an executable file." >&2 + exit 1 +fi + +mkdir -p "$DIR_SCRIPT" +cat > "$ENV_FILE" </dev/null +"$CLI" gen-eid > eid.txt +"$CLI" gen-eb-hash > ebhash.txt + +"$CLI" gen-stake \ + --pool-count "${POOLS}" \ + --total-stake "${TOTAL_STAKE}" \ + --shape-alpha "${ALPHA}" \ + --shape-beta "${BETA}" \ + --stake-file stake.cbor + +"$CLI" gen-pools \ + --stake-file stake.cbor \ + --pools-file pools.cbor + +# Pretty-print some of the generated values +echo "EID: $(cat eid.txt)" +echo "EB Hash: $(cat ebhash.txt)" + +# Print first 3 pools and their stakes from pools.cbor using cbor2 +PYTHON_EXEC="${VIRTUAL_ENV:+$VIRTUAL_ENV/bin/python}" +PYTHON_EXEC="${PYTHON_EXEC:-python3}" +"$PYTHON_EXEC" - <<'PY' +import sys, os +try: + import cbor2 +except ImportError: + print('cbor2 not installed! (pip install cbor2)', file=sys.stderr) + sys.exit(1) + +if not os.path.exists('pools.cbor'): + print('pools.cbor not found', file=sys.stderr) + sys.exit(1) + +with open('pools.cbor', 'rb') as f: + pools = cbor2.load(f) + +print('First 3 pools and their stakes:') +for i, entry in enumerate(pools[:3]): + # Expected structure: {"secret": , "reg": {"pool": "", ...}, "stake": } + reg = entry.get('reg', {}) + pool_id = reg.get('pool', '') + stake = entry.get('stake', '') + print(f' {i:>2}: pool={pool_id} stake={stake}') +PY +popd >/dev/null + +echo "Initialized inputs in ${DEMO_DIR}" \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/20_make_registry.sh b/crypto-benchmarks.rs/demo/scripts/20_make_registry.sh new file mode 100755 index 000000000..f81b0411b --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/20_make_registry.sh @@ -0,0 +1,86 @@ + + +#!/usr/bin/env bash +set -euo pipefail +# Usage: demo/scripts/20_make_registry.sh -d RUN_DIR -n N +# Example (from demo/): scripts/20_make_registry.sh -d run16 -n 16 +DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$DIR_SCRIPT/.env_cli" + +RUN_DIR="" +N="" + +while [[ $# -gt 0 ]]; do + case "$1" in + -d) RUN_DIR="$2"; shift 2;; + -n) N="$2"; shift 2;; + *) echo "Usage: $0 -d RUN_DIR -n N"; exit 2;; + esac +done + +if [[ -z "$RUN_DIR" || -z "$N" ]]; then + echo "Error: need -d RUN_DIR and -n N" >&2; exit 2 +fi + +RUN_DIR="$(cd "$DIR_SCRIPT/.."; cd "$RUN_DIR" && pwd)" +echo "== [20_make_registry] DIR=${RUN_DIR} N=${N} ==" + +pushd "$RUN_DIR" >/dev/null +"$CLI" make-registry \ + --pools-file pools.cbor \ + --voter-count "$N" \ + --registry-file registry.cbor +popd >/dev/null + +# --- Pretty-print a short registry summary for the audience --- +PYTHON_EXEC="${VIRTUAL_ENV:+$VIRTUAL_ENV/bin/python}" +PYTHON_EXEC="${PYTHON_EXEC:-python3}" +pushd "$RUN_DIR" >/dev/null +"$PYTHON_EXEC" - <<'PY' +import sys, os +try: + import cbor2 +except ImportError: + print("CBOR summary skipped (cbor2 not installed). Run: pip install cbor2", file=sys.stderr) + raise SystemExit(0) + +path = "registry.cbor" +if not os.path.exists(path): + print("CBOR summary skipped (registry.cbor missing).", file=sys.stderr) + raise SystemExit(0) + +c = cbor2.load(open(path, "rb")) + +voters = c.get("voters") +total_stake = c.get("total_stake") +persistent_pool = c.get("persistent_pool") or {} +info = c.get("info") or {} + +print("Registry summary:") +print(f" Seats requested (N): {voters}") +print(f" Persistent seats: {len(persistent_pool)}") +print(f" Total stake: {total_stake}") + +# Top 3 stakepools by stake (from .info) +tops = [] +for pool_id, rec in info.items(): + stake = rec.get("stake") + if isinstance(stake, int): + tops.append((stake, pool_id)) +tops.sort(reverse=True) +tops = tops[:3] + +if tops: + print(" Top 3 stakepools by stake:") + for i, (stake, pool) in enumerate(tops, 1): + print(f" {i}. pool={pool} stake={stake}") + +# Show up to first 3 persistent IDs → pools +if isinstance(persistent_pool, dict) and persistent_pool: + items = sorted(persistent_pool.items(), key=lambda kv: kv[0])[:3] + print(" Persistent mapping (first 3):") + for pid, pool in items: + print(f" id={pid} -> pool={pool}") +PY +popd >/dev/null +# --- End summary --- \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/30_cast_votes.sh b/crypto-benchmarks.rs/demo/scripts/30_cast_votes.sh new file mode 100755 index 000000000..928800e66 --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/30_cast_votes.sh @@ -0,0 +1,132 @@ + + +#!/usr/bin/env bash +set -euo pipefail +# Usage: demo/scripts/30_cast_votes.sh -d RUN_DIR -f FRACTION +# Example (from demo/): scripts/30_cast_votes.sh -d run16 -f 1.0 +# Requires: demo/scripts/.env_cli (set via 00_set_cli.sh) + +DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$DIR_SCRIPT/.env_cli" + +RUN_DIR="" +FRACTION="" + +# ---- arg parsing ---- +while [[ $# -gt 0 ]]; do + case "$1" in + -d) RUN_DIR="$2"; shift 2;; + -f) FRACTION="$2"; shift 2;; + *) echo "Usage: $0 -d RUN_DIR -f FRACTION"; exit 2;; + esac +done + +if [[ -z "$RUN_DIR" || -z "$FRACTION" ]]; then + echo "Error: need -d RUN_DIR and -f FRACTION" >&2 + exit 2 +fi + +# Resolve run directory relative to demo/ +RUN_DIR="$(cd "$DIR_SCRIPT/.."; cd "$RUN_DIR" && pwd)" +echo "== [30_cast_votes] DIR=${RUN_DIR} FRACTION=${FRACTION} ==" + +# ---- run cast-votes ---- +pushd "$RUN_DIR" >/dev/null + +if [[ ! -f "registry.cbor" ]]; then + echo "Error: ${RUN_DIR}/registry.cbor not found. Run scripts/20_make_registry.sh first." >&2 + exit 1 +fi +if [[ ! -f "eid.txt" || ! -f "ebhash.txt" ]]; then + echo "Error: eid.txt or ebhash.txt missing in ${RUN_DIR}. Run scripts/10_init_inputs.sh first." >&2 + exit 1 +fi + +OUT="$("$CLI" --verbose cast-votes \ + --registry-file registry.cbor \ + --eid "$(cat eid.txt)" \ + --eb-hash "$(cat ebhash.txt)" \ + --fraction-voting "$FRACTION" \ + --votes-file votes.cbor 2>&1)" + +echo "$OUT" + +# Extract and stash the "Voters: X" count (robust against spacing/CR/ANSI) +CLEAN_OUT="$(printf '%s\n' "$OUT" | tr -d '\r' | sed 's/\x1B\[[0-9;]*[A-Za-z]//g')" +if [[ "$CLEAN_OUT" =~ [Vv]oters:[[:space:]]*([0-9]+) ]]; then + VOTERS="${BASH_REMATCH[1]}" +else + VOTERS="$(printf '%s\n' "$CLEAN_OUT" | sed -n 's/.*[Vv]oters:[[:space:]]*\([0-9][0-9]*\).*/\1/p' | head -n1 || true)" +fi + +# Summarize results for the audience + +# File size (bytes) +if [[ -f "${RUN_DIR}/votes.cbor" ]]; then + BYTES=$(wc -c < "${RUN_DIR}/votes.cbor" | tr -d ' ') + echo "votes.cbor size: ${BYTES} bytes" +fi + +# Show a small sample of who voted +PYTHON_EXEC="${VIRTUAL_ENV:+$VIRTUAL_ENV/bin/python}" +PYTHON_EXEC="${PYTHON_EXEC:-python3}" +"$PYTHON_EXEC" - <<'PY' +import sys, os +try: + import cbor2 +except ImportError: + print("CBOR summary skipped (cbor2 not installed). Run: pip install cbor2", file=sys.stderr) + raise SystemExit(0) + +votes_path = "votes.cbor" +registry_path = "registry.cbor" +if not os.path.exists(votes_path): + print("CBOR summary skipped (votes.cbor missing).", file=sys.stderr) + raise SystemExit(0) + +# Load votes +votes = cbor2.load(open(votes_path, "rb")) +# Try to load registry to resolve persistent IDs to pool hashes +persistent_pool = {} +if os.path.exists(registry_path): + reg = cbor2.load(open(registry_path, "rb")) + pp = reg.get("persistent_pool") + if isinstance(pp, dict): + # keys could be ints or stringified ints + persistent_pool = {int(k): v for k, v in pp.items()} + elif isinstance(pp, list): + # Some builds might use list index mapping + persistent_pool = {i: v for i, v in enumerate(pp)} + +np_pools = [] +p_pools = [] # as 'id -> pool' + +for v in votes: + if isinstance(v, dict): + if "Nonpersistent" in v: + pool = v["Nonpersistent"].get("pool") + if isinstance(pool, str): + np_pools.append(pool) + elif "Persistent" in v: + pid = v["Persistent"].get("persistent") + if isinstance(pid, int): + pool = persistent_pool.get(pid, None) + if pool is not None: + p_pools.append((pid, pool)) + else: + p_pools.append((pid, "")) + if len(np_pools) >= 3 and len(p_pools) >= 3: + break + +print("Sample voters:") +if p_pools: + print(" Persistent (first up to 3):") + for pid, pool in p_pools[:3]: + print(f" id={pid} -> pool={pool}") +if np_pools: + print(" Nonpersistent pools (first up to 3):") + for pool in np_pools[:3]: + print(f" {pool}") +PY + +popd >/dev/null diff --git a/crypto-benchmarks.rs/demo/scripts/40_make_certificate.sh b/crypto-benchmarks.rs/demo/scripts/40_make_certificate.sh new file mode 100755 index 000000000..835119875 --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/40_make_certificate.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail +# Usage: demo/scripts/40_make_certificate.sh -d RUN_DIR +# Example (from demo/): scripts/40_make_certificate.sh -d run16 +# Requires: demo/scripts/.env_cli (set via 00_set_cli.sh) + +DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$DIR_SCRIPT/.env_cli" + +RUN_DIR="" + +# ---- arg parsing ---- +while [[ $# -gt 0 ]]; do + case "$1" in + -d) RUN_DIR="$2"; shift 2;; + *) echo "Usage: $0 -d RUN_DIR"; exit 2;; + esac +done + +if [[ -z "$RUN_DIR" ]]; then + echo "Error: need -d RUN_DIR" >&2 + exit 2 +fi + +# Resolve run directory relative to demo/ +RUN_DIR="$(cd "$DIR_SCRIPT/.."; cd "$RUN_DIR" && pwd)" +echo "== [40_make_certificate] DIR=${RUN_DIR} ==" + +# ---- preflight ---- +if [[ ! -f "${RUN_DIR}/registry.cbor" ]]; then + echo "Error: ${RUN_DIR}/registry.cbor not found. Run scripts/20_make_registry.sh first." >&2 + exit 1 +fi +if [[ ! -f "${RUN_DIR}/votes.cbor" ]]; then + echo "Error: ${RUN_DIR}/votes.cbor not found. Run scripts/30_cast_votes.sh first." >&2 + exit 1 +fi + +# ---- make certificate ---- +pushd "$RUN_DIR" >/dev/null +"$CLI" make-certificate \ + --registry-file registry.cbor \ + --votes-file votes.cbor \ + --certificate-file certificate.cbor +popd >/dev/null + + +# ---- summary output ---- +echo +echo "== Certificate creation summary ==" +BYTES_CERT="$(wc -c < "${RUN_DIR}/certificate.cbor" 2>/dev/null || echo "?")" +echo "Certificate size (bytes): ${BYTES_CERT}" diff --git a/crypto-benchmarks.rs/demo/scripts/50_verify_certificate.sh b/crypto-benchmarks.rs/demo/scripts/50_verify_certificate.sh new file mode 100755 index 000000000..3cc1dd200 --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/50_verify_certificate.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail +DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$DIR_SCRIPT/.env_cli" + +usage() { + cat < + +Verify the cryptographic validity of a certificate. + +Options: + -d, --dir Run directory that contains registry.cbor and certificate.cbor +USAGE + exit 1 +} + +DIR="" +while [[ $# -gt 0 ]]; do + case "$1" in + -d|--dir) DIR="$2"; shift 2;; + -h|--help) usage;; + *) echo "Unknown argument: $1"; usage;; + esac +done + +[[ -z "${DIR}" ]] && usage +DIR="$(cd "$DIR" 2>/dev/null && pwd || true)" +[[ -z "${DIR}" || ! -d "${DIR}" ]] && { echo "Run directory not found: ${DIR}"; exit 1; } + +REG="${DIR}/registry.cbor" +CERT="${DIR}/certificate.cbor" + +[[ -f "${REG}" ]] || { echo "Missing ${REG}"; exit 1; } +[[ -f "${CERT}" ]] || { echo "Missing ${CERT}"; exit 1; } + +echo "== [50_verify_certificate] DIR=${DIR} ==" +"$CLI" --verbose verify-certificate \ + --registry-file "${REG}" \ + --certificate-file "${CERT}" \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh b/crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh new file mode 100755 index 000000000..5ae008f86 --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# 60_pretty_print_cert.sh +# Wrapper that pretty-prints certificate & votes using an embedded Python helper. +# Usage: ./60_pretty_print_cert.sh -d RUN_DIR +set -euo pipefail + +usage() { echo "Usage: $0 -d RUN_DIR | --dir RUN_DIR"; exit 2; } + + +# Defaults +DIR="" + +# Parse short options +while getopts "d:" opt; do + case "$opt" in + d) DIR="$OPTARG" ;; + *) usage ;; + esac +done +shift $((OPTIND-1)) + +# Parse long options (e.g., --dir RUN_DIR) +while (( "$#" )); do + case "$1" in + --dir) + DIR="${2:-}" + shift 2 ;; + --) + shift; break ;; + -*) + usage ;; + *) + # ignore positional args + shift ;; + esac +done + +if [[ -z "${DIR}" ]]; then + usage +fi + +# Resolve absolute paths +DIR_ABS="$(cd "${DIR}" 2>/dev/null && pwd || true)" +if [[ -z "${DIR_ABS}" || ! -d "${DIR_ABS}" ]]; then + echo "Error: RUN_DIR not found: ${DIR}" >&2 + exit 1 +fi + +echo "== [60_pretty_print_cert] DIR=${DIR_ABS} ==" + +CERT="${DIR_ABS}/certificate.cbor" +VOTES="${DIR_ABS}/votes.cbor" + +if [[ ! -f "${CERT}" ]]; then + echo "{ \"error\": \"certificate not found: ${CERT}\" }" + exit 1 +fi + +# Prefer virtualenv python in demo/.venv if present, then $PYTHON, then python3, then python +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +DEMO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +if [[ -x "${DEMO_ROOT}/.venv/bin/python" ]]; then + PY_BIN="${DEMO_ROOT}/.venv/bin/python" +elif [[ -n "${PYTHON:-}" && -x "${PYTHON}" ]]; then + PY_BIN="${PYTHON}" +elif command -v python3 >/dev/null 2>&1; then + PY_BIN="$(command -v python3)" +elif command -v python >/dev/null 2>&1; then + PY_BIN="$(command -v python)" +else + echo '{ "error": "No Python interpreter found. Install python3 or create .venv." }' + exit 2 +fi + +# Run the Python helper, print to console, and also save to RUN_DIR/certificate.pretty.json +OUTPUT="$("${PY_BIN}" "${SCRIPT_DIR}/pretty_cert.py" "${CERT}" "${VOTES}")" + +# Print to console and save to file +echo "${OUTPUT}" | tee "${DIR_ABS}/certificate.pretty.json" >/dev/null +echo "${OUTPUT}" \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/70_run_one.sh b/crypto-benchmarks.rs/demo/scripts/70_run_one.sh new file mode 100755 index 000000000..5be4a650e --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/70_run_one.sh @@ -0,0 +1,94 @@ + + +#!/usr/bin/env bash +set -euo pipefail +# Orchestrate a full demo run end-to-end in one go. +# +# Usage: +# scripts/70_run_one.sh -d RUN_DIR -n N -f FRACTION [-p POOLS] [-t TOTAL_STAKE] +# Examples (from demo/): +# scripts/70_run_one.sh -d run16 -n 16 -f 1.0 +# scripts/70_run_one.sh -d run32 -n 32 -f 0.85 -p 400 -t 200000 +# +# Notes: +# - This is a convenience wrapper that calls: +# 10_init_inputs.sh +# 20_make_registry.sh +# 30_cast_votes.sh +# 40_make_certificate.sh +# 50_verify_certificate.sh +# 60_pretty_print_cert.sh +# - Each sub-script prints its own status; this wrapper adds a compact summary. + +DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEMO_ROOT="$(cd "$DIR_SCRIPT/.." && pwd)" + +RUN_DIR="" +N="" +FRACTION="" +POOLS="" +TOTAL_STAKE="" + +usage() { + cat <&2; usage; exit 2;; + esac +done + +if [[ -z "$RUN_DIR" || -z "$N" || -z "$FRACTION" ]]; then + echo "Error: need -d RUN_DIR, -n N, and -f FRACTION" >&2 + usage; exit 2 +fi + +RUN_DIR_ABS="$(cd "$DEMO_ROOT"; mkdir -p "$RUN_DIR"; cd "$RUN_DIR" && pwd)" +echo "== [70_run_one] DIR=${RUN_DIR_ABS} N=${N} FRACTION=${FRACTION} POOLS=${POOLS:-default} TOTAL_STAKE=${TOTAL_STAKE:-default} ==" + +# ---- 10: init inputs ---- +INIT_CMD=("$DIR_SCRIPT/10_init_inputs.sh" -d "$RUN_DIR") +[[ -n "$POOLS" ]] && INIT_CMD+=( -p "$POOLS" ) +[[ -n "$TOTAL_STAKE" ]] && INIT_CMD+=( -t "$TOTAL_STAKE" ) +"${INIT_CMD[@]}" + +# ---- 20: make registry ---- +"$DIR_SCRIPT/20_make_registry.sh" -d "$RUN_DIR" -n "$N" + +# ---- 30: cast votes ---- +"$DIR_SCRIPT/30_cast_votes.sh" -d "$RUN_DIR" -f "$FRACTION" + +# ---- 40: make certificate ---- +"$DIR_SCRIPT/40_make_certificate.sh" -d "$RUN_DIR" + +# ---- 50: cryptographic verification ---- +"$DIR_SCRIPT/50_verify_certificate.sh" -d "$RUN_DIR" + +# ---- 60: show sizes + summary JSON ---- +"$DIR_SCRIPT/60_pretty_print_cert.sh" -d "$RUN_DIR" + +# ---- compact tail summary ---- +PRETTY_JSON="${RUN_DIR_ABS}/certificate.pretty.json" +if [[ -f "$PRETTY_JSON" ]] && command -v jq >/dev/null 2>&1; then + echo "-- Summary --" + jq '{eid, eb, persistent_voters_count, nonpersistent_voters_count, votes_bytes, certificate_bytes, compression_ratio}' "$PRETTY_JSON" +else + echo "(Tip) Install jq for a compact summary: brew install jq" +fi + +echo "Done." \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/80_sweep.sh b/crypto-benchmarks.rs/demo/scripts/80_sweep.sh new file mode 100755 index 000000000..6ab6eb078 --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/80_sweep.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +set -euo pipefail +# Sweep over multiple voter counts and collect size/gain metrics. +# +# Usage: +# scripts/80_sweep.sh -d DIR -f FRACTION --ns "16 32 64 128" [-p POOLS] [-t TOTAL_STAKE] +# Examples: +# scripts/80_sweep.sh -d sweep1 -f 1.0 --ns "16 32 64 128" +# scripts/80_sweep.sh -d sweepA -f 0.85 --ns "16 32" -p 400 -t 200000 +# +# Notes: +# - Creates sub-runs under demo//run and calls scripts/70_run_one.sh for each N +# - Appends results to demo//sweep_results.csv with columns: +# N,votes_bytes,certificate_bytes,compression_ratio,nonpersistent_voters_count,persistent_voters_count + +DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEMO_ROOT="$(cd "$DIR_SCRIPT/.." && pwd)" + +BASE_DIR="" +FRACTION="" +NS_LIST="" +POOLS="" +TOTAL_STAKE="" + +usage() { + cat <&2; usage; exit 2;; + esac +done + +if [[ -z "$BASE_DIR" || -z "$FRACTION" || -z "$NS_LIST" ]]; then + echo "Error: need -d DIR, -f FRACTION, and --ns \"N1 N2 ...\"" >&2 + usage; exit 2 +fi + +BASE_ABS="$(cd "$DEMO_ROOT"; mkdir -p "$BASE_DIR"; cd "$BASE_DIR" && pwd)" +RESULTS_CSV="${BASE_ABS}/sweep_results.csv" + +# header +if [[ ! -f "$RESULTS_CSV" ]]; then + echo "N,votes_bytes,certificate_bytes,compression_ratio,nonpersistent_voters_count,persistent_voters_count" > "$RESULTS_CSV" +fi + +echo "== [80_sweep] BASE=${BASE_ABS} FRACTION=${FRACTION} NS=(${NS_LIST}) POOLS=${POOLS:-default} TOTAL_STAKE=${TOTAL_STAKE:-default} ==" + +for N in ${NS_LIST}; do + RUN_DIR_REL="${BASE_DIR}/run${N}" + echo "-- N=${N} -> ${RUN_DIR_REL}" + + # Build command for a single run + ONE_CMD=("$DIR_SCRIPT/70_run_one.sh" -d "$RUN_DIR_REL" -n "$N" -f "$FRACTION") + [[ -n "$POOLS" ]] && ONE_CMD+=( -p "$POOLS" ) + [[ -n "$TOTAL_STAKE" ]] && ONE_CMD+=( -t "$TOTAL_STAKE" ) + + # Execute + "${ONE_CMD[@]}" + + PRETTY_JSON="${DEMO_ROOT}/${RUN_DIR_REL}/certificate.pretty.json" + if [[ -f "$PRETTY_JSON" ]]; then + # Extract fields with jq; fall back to Python if jq is not available + if command -v jq >/dev/null 2>&1; then + VOTES=$(jq -r '.votes_bytes' "$PRETTY_JSON") + CERT=$(jq -r '.certificate_bytes' "$PRETTY_JSON") + RATIO=$(jq -r '.compression_ratio' "$PRETTY_JSON") + NP=$(jq -r '.nonpersistent_voters_count' "$PRETTY_JSON") + PV=$(jq -r '.persistent_voters_count' "$PRETTY_JSON") + else + # Python fallback (no jq). Use a simple one-liner and parse. + PY_OUT=$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("votes_bytes"), d.get("certificate_bytes"), d.get("compression_ratio"), d.get("nonpersistent_voters_count"), d.get("persistent_voters_count"))' "$PRETTY_JSON") + read -r VOTES CERT RATIO NP PV <<< "$PY_OUT" + fi + echo "${N},${VOTES},${CERT},${RATIO},${NP},${PV}" >> "$RESULTS_CSV" + else + echo "Warning: ${PRETTY_JSON} not found; skipping CSV append" >&2 + fi + +done + +echo "Sweep complete. Results: ${RESULTS_CSV}" diff --git a/crypto-benchmarks.rs/demo/scripts/85_plot_sweep.sh b/crypto-benchmarks.rs/demo/scripts/85_plot_sweep.sh new file mode 100755 index 000000000..27285d169 --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/85_plot_sweep.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +set -euo pipefail +# Plot compression ratios from a sweep run. +# Usage: +# scripts/85_plot_sweep.sh -d DIR [--open] +# Where: +# DIR is the sweep directory under demo/ that contains sweep_results.csv +# +# This script prefers demo/scripts/90_plot_sweep.py if present. +# If not, it falls back to a built-in Python snippet. + +DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEMO_ROOT="$(cd "$DIR_SCRIPT/.." && pwd)" + +BASE_DIR="" +DO_OPEN=0 + +usage() { + cat <&2; usage; exit 2;; + esac +done + +[[ -z "$BASE_DIR" ]] && { echo "Error: need -d DIR"; usage; exit 2; } + +SWEEP_DIR="${DEMO_ROOT}/${BASE_DIR}" +CSV_PATH="${SWEEP_DIR}/sweep_results.csv" +PNG_PATH="${SWEEP_DIR}/summary_vs_N.png" + +if [[ ! -f "$CSV_PATH" ]]; then + echo "sweep_results.csv not found at: $CSV_PATH" + echo "Run scripts/80_sweep.sh first." + exit 1 +fi + +# Choose Python +PY_BIN="${DEMO_ROOT}/.venv/bin/python" +[[ -x "$PY_BIN" ]] || PY_BIN="python3" + +# Ensure matplotlib (install into venv if possible) +if ! "$PY_BIN" -c "import matplotlib" >/dev/null 2>&1; then + echo "matplotlib not found; attempting install..." + if [[ -x "${DEMO_ROOT}/.venv/bin/pip" ]]; then + "${DEMO_ROOT}/.venv/bin/pip" install --upgrade pip matplotlib >/dev/null + else + "$PY_BIN" -m pip install --user --upgrade pip matplotlib >/dev/null || true + fi +fi + +# Always use the inline fallback Python to ensure consistent styling/labels. +DEMO_ROOT_OVERRIDE="${DEMO_ROOT}" \ +"$PY_BIN" - "${BASE_DIR}" <<'PYFALLBACK' +import sys, csv, os +import matplotlib.pyplot as plt + +# argv[1] = base dir under demo/ (e.g., "sweep1") +if len(sys.argv) < 2: + sys.exit("Usage: fallback: python - ") +base_dir = sys.argv[1] + +# We rely on DEMO_ROOT_OVERRIDE passed from the shell +demo_root = os.environ.get('DEMO_ROOT_OVERRIDE') +if not demo_root: + raise SystemExit("DEMO_ROOT_OVERRIDE not set; cannot resolve demo path.") + +base = os.path.join(demo_root, base_dir) +csv_path = os.path.join(base, 'sweep_results.csv') +png_path = os.path.join(base, 'summary_vs_N.png') + +if not os.path.exists(csv_path): + raise SystemExit(f"sweep_results.csv not found at: {csv_path}") + +N = [] +ratio = [] +pv = [] # persistent_voters_count +npv = [] # nonpersistent_voters_count + +with open(csv_path, newline='') as f: + rdr = csv.DictReader(f) + for row in rdr: + try: + N.append(int(row['N'])) + ratio.append(float(row.get('compression_ratio', 'nan'))) + pv.append(int(row.get('persistent_voters_count', 0))) + npv.append(int(row.get('nonpersistent_voters_count', 0))) + except Exception: + pass + +pairs = sorted(zip(N, ratio, pv, npv), key=lambda x: x[0]) +if pairs: + N, ratio, pv, npv = zip(*pairs) +else: + N, ratio, pv, npv = [], [], [], [] + +# ---- Build clean x-ticks to avoid clutter and hide 64 explicitly ---- +# Strategy: +# * Always show the first and last N +# * Prefer showing {32, 128, 256, 512, 1024, 2056, 3000} if present (skip 64) +# * Fill remaining slots up to ~8 labels with evenly spaced indices, but +# drop label == 64 if it slips in. + +def build_xticks_labels(values): + if not values: + return [], [] + idxs = set([0, len(values) - 1]) + preferred = [32, 128, 256, 512, 1024, 2056, 3000] + pos = {v: i for i, v in enumerate(values)} + for v in preferred: + if v in pos: + idxs.add(pos[v]) + target = min(8, len(values)) + if len(idxs) < target: + k = target + for j in range(k): + idx = int(round(j * (len(values) - 1) / max(1, (k - 1)))) + if values[idx] == 64: + continue + idxs.add(idx) + if len(idxs) >= target: + break + idxs = sorted(idxs) + ticks = [values[i] for i in idxs if values[i] != 64] + labels = [str(v) for v in ticks] + return ticks, labels + +xticks, xlabels = build_xticks_labels(list(N)) + +fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(7.5, 7.5), sharex=True) + +# Top subplot: Compression ratio vs Committee size (use a vivid green) +ax1.plot(N, ratio, marker='^', linestyle='--', color='#00aa00', label='Compression ratio (×)') +ax1.set_title('Compression Gain vs Committee Size') +ax1.set_ylabel('Compression ratio (×)') +ax1.grid(True, which='both', linestyle=':') +# Ensure the TOP subplot also shows x tick labels +ax1.set_xticks(xticks) +ax1.set_xticklabels(xlabels, rotation=45, ha='right', fontsize=9) +ax1.tick_params(axis='x', which='both', labelbottom=True) + +# Bottom subplot: Persistent voters vs Committee size +ax2.plot(N, N, color='gray', linestyle='--', label='Committee size (y=x)') +ax2.plot(N, pv, marker='o', label='Persistent voters') +ax2.set_title('Persistent Voters vs Committee Size') +ax2.set_xlabel('Committee size (N)') +ax2.set_ylabel('Count') +ax2.grid(True, which='both', linestyle=':') +ax2.set_xticks(xticks) +ax2.set_xticklabels(xlabels, rotation=45, ha='right', fontsize=9) +ax2.legend(loc='best') + +plt.tight_layout() +plt.subplots_adjust(bottom=0.14) +plt.savefig(png_path, dpi=160) +print(f"Wrote {png_path}") +PYFALLBACK + +echo "Plot: ${PNG_PATH}" +if [[ $DO_OPEN -eq 1 ]] && command -v open >/dev/null 2>&1; then + open "${PNG_PATH}" || true +fi diff --git a/crypto-benchmarks.rs/demo/scripts/pretty_cert.py b/crypto-benchmarks.rs/demo/scripts/pretty_cert.py new file mode 100644 index 000000000..62446ff07 --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/pretty_cert.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +import sys, os, json +try: + import cbor2 +except Exception as e: + print(json.dumps({"error": f"cbor2 import failed: {e}"}, indent=2)) + sys.exit(1) + +if len(sys.argv) < 2: + print("Usage: pretty_cert.py CERT_PATH [VOTES_PATH]", file=sys.stderr) + sys.exit(2) + +cert_path = sys.argv[1] +votes_path = sys.argv[2] if len(sys.argv) > 2 else None + +if not os.path.isfile(cert_path): + print(json.dumps({"error": f"certificate not found: {cert_path}"}, indent=2)) + sys.exit(1) + +def hex8(b): + if isinstance(b, (bytes, bytearray)): + return b[:8].hex() + return None + +def hex_full(b): + if isinstance(b, (bytes, bytearray)): + return "0x" + b.hex() + return None + +with open(cert_path, 'rb') as f: + c = cbor2.load(f) + +pv = c.get("persistent_voters") or c.get("persistent") or c.get("pv") +npv = c.get("nonpersistent_voters") or c.get("nonpersistent") or c.get("npv") + +pv_count = len(pv) if isinstance(pv, (list, tuple)) else 0 +if isinstance(npv, dict): + npv_keys = list(npv.keys()) + npv_count = len(npv) +elif isinstance(npv, list): + npv_keys = npv + npv_count = len(npv) +else: + npv_keys = [] + npv_count = 0 + +votes_bytes = None +if votes_path and os.path.isfile(votes_path): + try: + votes_bytes = os.path.getsize(votes_path) + except OSError: + votes_bytes = None + +cert_bytes = None +try: + cert_bytes = os.path.getsize(cert_path) +except OSError: + cert_bytes = None + +compression_ratio = None +if votes_bytes and cert_bytes and cert_bytes > 0: + compression_ratio = round(votes_bytes / cert_bytes, 3) + +out = { + "sigma_tilde_eid_prefix": hex8(c.get("sigma_tilde_eid") or c.get("agg_sig_eid")), + "sigma_tilde_m_prefix": hex8(c.get("sigma_tilde_m") or c.get("agg_sig_m")), + "eid": hex_full(c.get("eid")), + "eb": hex_full(c.get("eb")), + "persistent_voters_count": pv_count, + "nonpersistent_voters_count": npv_count, + "nonpersistent_voters_sample": npv_keys[:3], +} + +if votes_bytes is not None: + out["votes_bytes"] = votes_bytes +if cert_bytes is not None: + out["certificate_bytes"] = cert_bytes +if compression_ratio is not None: + out["compression_ratio"] = compression_ratio + +print(json.dumps(out, indent=2)) \ No newline at end of file From 9b080feed4683b3373a7ad1d92bda5fdc7577d41 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Fri, 3 Oct 2025 14:03:42 +0900 Subject: [PATCH 02/16] update pretty_print_cert to print voter IDs in summary --- .../demo/scripts/60_pretty_print_cert.sh | 64 +++---- .../demo/scripts/pretty_cert.py | 156 ++++++++++-------- 2 files changed, 127 insertions(+), 93 deletions(-) diff --git a/crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh b/crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh index 5ae008f86..70cdd6a29 100755 --- a/crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh +++ b/crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh @@ -1,43 +1,49 @@ #!/usr/bin/env bash # 60_pretty_print_cert.sh -# Wrapper that pretty-prints certificate & votes using an embedded Python helper. -# Usage: ./60_pretty_print_cert.sh -d RUN_DIR +# Pretty-print certificate & votes using a Python helper. +# Usage: +# ./60_pretty_print_cert.sh -d RUN_DIR [--all-voters] [--max-ids N] set -euo pipefail -usage() { echo "Usage: $0 -d RUN_DIR | --dir RUN_DIR"; exit 2; } +usage() { + cat <<'EOF' +Usage: 60_pretty_print_cert.sh -d RUN_DIR [--all-voters] [--max-ids N] +Options: + -d, --dir RUN_DIR Run directory containing certificate.cbor (and votes.cbor) + --all-voters Include full lists of persistent and non-persistent voters + --max-ids N When not using --all-voters, include at most N voter IDs (default 5) +EOF + exit 2 +} # Defaults DIR="" +ALL_VOTERS=0 +MAX_IDS="5" -# Parse short options -while getopts "d:" opt; do - case "$opt" in - d) DIR="$OPTARG" ;; - *) usage ;; - esac -done -shift $((OPTIND-1)) - -# Parse long options (e.g., --dir RUN_DIR) -while (( "$#" )); do +# Single portable arg parser (handles both short and long flags) +while [[ $# -gt 0 ]]; do case "$1" in - --dir) - DIR="${2:-}" - shift 2 ;; + -d|--dir) + [[ $# -ge 2 ]] || usage + DIR="$2"; shift 2 ;; + --all-voters) + ALL_VOTERS=1; shift ;; + --max-ids) + [[ $# -ge 2 ]] || usage + MAX_IDS="$2"; shift 2 ;; --) shift; break ;; -*) usage ;; *) - # ignore positional args + # ignore stray positional args shift ;; esac done -if [[ -z "${DIR}" ]]; then - usage -fi +[[ -z "${DIR}" ]] && usage # Resolve absolute paths DIR_ABS="$(cd "${DIR}" 2>/dev/null && pwd || true)" @@ -52,11 +58,11 @@ CERT="${DIR_ABS}/certificate.cbor" VOTES="${DIR_ABS}/votes.cbor" if [[ ! -f "${CERT}" ]]; then - echo "{ \"error\": \"certificate not found: ${CERT}\" }" + printf '{ "error": "certificate not found: %s" }\n' "${CERT}" exit 1 fi -# Prefer virtualenv python in demo/.venv if present, then $PYTHON, then python3, then python +# Prefer venv python in demo/.venv if present, then $PYTHON, then python3, then python SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" DEMO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" if [[ -x "${DEMO_ROOT}/.venv/bin/python" ]]; then @@ -72,9 +78,11 @@ else exit 2 fi -# Run the Python helper, print to console, and also save to RUN_DIR/certificate.pretty.json -OUTPUT="$("${PY_BIN}" "${SCRIPT_DIR}/pretty_cert.py" "${CERT}" "${VOTES}")" +# Build args for the Python helper +PY_ARGS=( "${CERT}" ) +[[ -f "${VOTES}" ]] && PY_ARGS+=( "${VOTES}" ) +PY_ARGS+=( --max-ids "${MAX_IDS}" ) +[[ "${ALL_VOTERS}" -eq 1 ]] && PY_ARGS+=( --all-voters ) -# Print to console and save to file -echo "${OUTPUT}" | tee "${DIR_ABS}/certificate.pretty.json" >/dev/null -echo "${OUTPUT}" \ No newline at end of file +# Run the Python helper and emit JSON to both console and file +"${PY_BIN}" "${SCRIPT_DIR}/pretty_cert.py" "${PY_ARGS[@]}" | tee "${DIR_ABS}/certificate.pretty.json" \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/pretty_cert.py b/crypto-benchmarks.rs/demo/scripts/pretty_cert.py index 62446ff07..04cce43d1 100644 --- a/crypto-benchmarks.rs/demo/scripts/pretty_cert.py +++ b/crypto-benchmarks.rs/demo/scripts/pretty_cert.py @@ -1,20 +1,11 @@ #!/usr/bin/env python3 -import sys, os, json +import sys, os, json, argparse + try: import cbor2 except Exception as e: - print(json.dumps({"error": f"cbor2 import failed: {e}"}, indent=2)) - sys.exit(1) - -if len(sys.argv) < 2: - print("Usage: pretty_cert.py CERT_PATH [VOTES_PATH]", file=sys.stderr) - sys.exit(2) - -cert_path = sys.argv[1] -votes_path = sys.argv[2] if len(sys.argv) > 2 else None - -if not os.path.isfile(cert_path): - print(json.dumps({"error": f"certificate not found: {cert_path}"}, indent=2)) + print(json.dumps({"error": f"cbor2 import failed: {e}"}, + indent=2)) sys.exit(1) def hex8(b): @@ -27,55 +18,90 @@ def hex_full(b): return "0x" + b.hex() return None -with open(cert_path, 'rb') as f: - c = cbor2.load(f) - -pv = c.get("persistent_voters") or c.get("persistent") or c.get("pv") -npv = c.get("nonpersistent_voters") or c.get("nonpersistent") or c.get("npv") - -pv_count = len(pv) if isinstance(pv, (list, tuple)) else 0 -if isinstance(npv, dict): - npv_keys = list(npv.keys()) - npv_count = len(npv) -elif isinstance(npv, list): - npv_keys = npv - npv_count = len(npv) -else: - npv_keys = [] - npv_count = 0 - -votes_bytes = None -if votes_path and os.path.isfile(votes_path): - try: - votes_bytes = os.path.getsize(votes_path) - except OSError: - votes_bytes = None - -cert_bytes = None -try: - cert_bytes = os.path.getsize(cert_path) -except OSError: - cert_bytes = None - -compression_ratio = None -if votes_bytes and cert_bytes and cert_bytes > 0: - compression_ratio = round(votes_bytes / cert_bytes, 3) - -out = { - "sigma_tilde_eid_prefix": hex8(c.get("sigma_tilde_eid") or c.get("agg_sig_eid")), - "sigma_tilde_m_prefix": hex8(c.get("sigma_tilde_m") or c.get("agg_sig_m")), - "eid": hex_full(c.get("eid")), - "eb": hex_full(c.get("eb")), - "persistent_voters_count": pv_count, - "nonpersistent_voters_count": npv_count, - "nonpersistent_voters_sample": npv_keys[:3], -} - -if votes_bytes is not None: - out["votes_bytes"] = votes_bytes -if cert_bytes is not None: - out["certificate_bytes"] = cert_bytes -if compression_ratio is not None: - out["compression_ratio"] = compression_ratio - -print(json.dumps(out, indent=2)) \ No newline at end of file +def load_cbor(path): + with open(path, 'rb') as f: + return cbor2.load(f) + +def main(): + ap = argparse.ArgumentParser( + description="Pretty print certificate (and optional votes) as JSON") + ap.add_argument("certificate", help="Path to certificate.cbor") + ap.add_argument("votes", nargs="?", default=None, + help="Optional path to votes.cbor") + ap.add_argument("--all-voters", action="store_true", + help="Include full lists of persistent and non-persistent voters") + ap.add_argument("--max-ids", type=int, default=5, + help="When not using --all-voters, include at most N voter IDs (default 5)") + args = ap.parse_args() + + cert_path = args.certificate + votes_path = args.votes + + if not os.path.isfile(cert_path): + print(json.dumps({"error": f"certificate not found: {cert_path}"}, indent=2)) + sys.exit(1) + + c = load_cbor(cert_path) + + # Handle schema variants + pv = c.get("persistent_voters") or c.get("persistent") or c.get("pv") + npv = c.get("nonpersistent_voters") or c.get("nonpersistent") or c.get("npv") + + # Normalize PV list + if isinstance(pv, (list, tuple)): + pv_list = list(pv) + elif isinstance(pv, dict): + # (unlikely) but normalize to keys + pv_list = list(pv.keys()) + else: + pv_list = [] + + # Normalize NPV list (keys, since values are sigs) + if isinstance(npv, dict): + npv_list = list(npv.keys()) + elif isinstance(npv, (list, tuple)): + npv_list = list(npv) + else: + npv_list = [] + + pv_count = len(pv_list) + npv_count = len(npv_list) + + # Sizes for compression ratio + votes_bytes = os.path.getsize(votes_path) if (votes_path and os.path.isfile(votes_path)) else None + cert_bytes = os.path.getsize(cert_path) + compression_ratio = None + if votes_bytes and cert_bytes > 0: + compression_ratio = round(votes_bytes / cert_bytes, 3) + + out = { + "sigma_tilde_eid_prefix": hex8(c.get("sigma_tilde_eid") or c.get("agg_sig_eid")), + "sigma_tilde_m_prefix": hex8(c.get("sigma_tilde_m") or c.get("agg_sig_m")), + "eid": hex_full(c.get("eid")), + "eb": hex_full(c.get("eb")), + "persistent_voters_count": pv_count, + "nonpersistent_voters_count": npv_count, + } + + # Samples vs full lists + if args.all_voters: + out["persistent_voters"] = pv_list + out["nonpersistent_voters"] = npv_list + else: + k = max(0, args.max_ids) + if k > 0: + # Include a sample of both lists (consistent ordering shown by tool) + out["persistent_voters_sample"] = pv_list[:k] + out["nonpersistent_voters_sample"] = npv_list[:k] + + if votes_bytes is not None: + out["votes_bytes"] = votes_bytes + if cert_bytes is not None: + out["certificate_bytes"] = cert_bytes + if compression_ratio is not None: + out["compression_ratio"] = compression_ratio + + print(json.dumps(out, indent=2)) + +if __name__ == "__main__": + main() \ No newline at end of file From e3cccc9e647230a19c3add8e627cb45405dde32e Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Fri, 3 Oct 2025 16:16:30 +0900 Subject: [PATCH 03/16] add minimal ui that summarizes certificate --- crypto-benchmarks.rs/demo/ui/server.py | 38 +++++ crypto-benchmarks.rs/demo/ui/static/app.js | 81 ++++++++++ .../demo/ui/static/styles.css | 141 ++++++++++++++++++ .../demo/ui/templates/index.html | 82 ++++++++++ 4 files changed, 342 insertions(+) create mode 100644 crypto-benchmarks.rs/demo/ui/server.py create mode 100644 crypto-benchmarks.rs/demo/ui/static/app.js create mode 100644 crypto-benchmarks.rs/demo/ui/static/styles.css create mode 100644 crypto-benchmarks.rs/demo/ui/templates/index.html diff --git a/crypto-benchmarks.rs/demo/ui/server.py b/crypto-benchmarks.rs/demo/ui/server.py new file mode 100644 index 000000000..136cd2016 --- /dev/null +++ b/crypto-benchmarks.rs/demo/ui/server.py @@ -0,0 +1,38 @@ +from flask import Flask, jsonify, render_template, redirect, url_for +import os, json + +app = Flask(__name__, static_folder="static", template_folder="templates") + +# Resolve demo root (one level up from this `ui` folder) +HERE = os.path.abspath(os.path.dirname(__file__)) +DEMO_ROOT = os.path.abspath(os.path.join(HERE, "..")) + +# === Example endpoint for cert JSON === +@app.route("/cert/") +def cert(run): + # Load certificate.pretty.json from ..//certificate.pretty.json + # so it works regardless of the current working directory + rel_path = os.path.join(run, "certificate.pretty.json") + path = os.path.join(DEMO_ROOT, rel_path) + if not os.path.isfile(path): + return jsonify({ + "error": "certificate.pretty.json not found", + "run": run, + "looked_for": path + }), 404 + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + return jsonify(data) + +# === UI endpoint === +@app.route("/ui") +def ui(): + return render_template("index.html") + +# Small helper route to redirect `/` to `/ui` +@app.route("/") +def root(): + return redirect(url_for("ui")) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5050, debug=True) \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/static/app.js b/crypto-benchmarks.rs/demo/ui/static/app.js new file mode 100644 index 000000000..ff59c6bef --- /dev/null +++ b/crypto-benchmarks.rs/demo/ui/static/app.js @@ -0,0 +1,81 @@ +async function fetchCert(run) { + const res = await fetch(`/cert/${encodeURIComponent(run)}`); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.json(); +} + +function setText(id, text) { + const el = document.getElementById(id); + if (el) el.textContent = text ?? "—"; +} + +function fillTable(tableId, data) { + const tbody = document.querySelector(`#${tableId} tbody`); + if (!tbody) return; + tbody.innerHTML = ""; + data.forEach((id, i) => { + const tr = document.createElement("tr"); + const idx = document.createElement("td"); + idx.textContent = i + 1; + const val = document.createElement("td"); + val.textContent = id; + tr.append(idx, val); + tbody.appendChild(tr); + }); +} + +function firstAvailableList(full, sample) { + if (Array.isArray(full) && full.length) return { list: full, sampled: false }; + if (Array.isArray(sample) && sample.length) return { list: sample, sampled: true }; + return { list: [], sampled: false }; +} + +function shortenHex(hex, head = 20, tail = 20) { + if (!hex || typeof hex !== "string") return hex; + const clean = hex.trim(); + if (clean.length <= head + tail) return clean; + return clean.slice(0, head) + "…" + clean.slice(-tail); +} + +function formatBytes(n) { + if (typeof n !== "number") return n ?? "—"; + return n.toLocaleString() + " B"; +} + +async function load(run) { + try { + const data = await fetchCert(run); + + setText("eid", shortenHex(data.eid)); + const ebElem = document.getElementById("eb"); + ebElem.textContent = shortenHex(data.eb); + ebElem.title = data.eb || ""; + + setText("votes_bytes", formatBytes(data.votes_bytes)); + setText("certificate_bytes", formatBytes(data.certificate_bytes)); + setText("compression_ratio", data.compression_ratio); + + setText("pv_count", data.persistent_voters_count); + setText("npv_count", data.nonpersistent_voters_count); + + const pv = firstAvailableList(data.persistent_voters, data.persistent_voters_sample); + const npv = firstAvailableList(data.nonpersistent_voters, data.nonpersistent_voters_sample); + + fillTable("pv_table", pv.list); + fillTable("npv_table", npv.list); + + document.getElementById("pv_note").hidden = !pv.sampled; + document.getElementById("npv_note").hidden = !npv.sampled; + } catch (e) { + alert(`Failed to load certificate for ${run}: ${e.message}`); + } +} + +document.getElementById("run-form").addEventListener("submit", (e) => { + e.preventDefault(); + const run = document.getElementById("run-input").value.trim(); + if (run) load(run); +}); + +// auto-load default +load(document.getElementById("run-input").value.trim()); \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/static/styles.css b/crypto-benchmarks.rs/demo/ui/static/styles.css new file mode 100644 index 000000000..639659525 --- /dev/null +++ b/crypto-benchmarks.rs/demo/ui/static/styles.css @@ -0,0 +1,141 @@ +:root { + --bg: #0b0f14; + --card: #121a22; + --text: #e7eef7; + --muted: #9fb2c7; + --green: #10b981; + /* crisp green */ + --border: #1f2a35; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + background: var(--bg); + color: var(--text); + font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; +} + +header { + max-width: 1100px; + margin: 20px auto; + padding: 0 16px; +} + +header h1 { + margin: 0 0 12px; + font-size: 24px; +} + +header form { + display: flex; + gap: 8px; + align-items: center; +} + +header input { + padding: 8px 10px; + border: 1px solid var(--border); + background: #0e141b; + color: var(--text); + border-radius: 6px; +} + +header button { + padding: 8px 12px; + background: #2a4365; + color: #fff; + border: 0; + border-radius: 6px; + cursor: pointer; +} + +header button:hover { + background: #2f4a73; +} + +main { + max-width: 1100px; + margin: 0 auto; + padding: 0 16px 24px; + display: grid; + gap: 16px; +} + +.card { + background: var(--card); + border: 1px solid var(--border); + border-radius: 10px; + padding: 16px; +} + +.card h2 { + margin: 0 0 12px; + font-size: 18px; +} + +.card h3 { + margin: 0 0 8px; + font-size: 16px; + color: var(--muted); +} + +.grid.two { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px 20px; +} + +@media (max-width: 800px) { + .grid.two { + grid-template-columns: 1fr; + } +} + +/* green highlight for gain */ +#compression_ratio { + color: var(--green); + font-weight: 700; +} + +/* tables */ +table { + width: 100%; + border-collapse: collapse; + border: 1px solid var(--border); + background: #0f1720; +} + +th, +td { + padding: 8px 10px; + border-bottom: 1px solid var(--border); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 12.5px; +} + +th { + text-align: left; + color: var(--muted); +} + +tbody tr:nth-child(odd) { + background: rgba(255, 255, 255, 0.02); +} + +tbody tr:hover { + background: rgba(255, 255, 255, 0.05); +} + +.note { + margin-top: 6px; + color: var(--muted); + font-size: 12px; +} + +#summary .grid.two>div>div { + margin-bottom: 12px; +} \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/templates/index.html b/crypto-benchmarks.rs/demo/ui/templates/index.html new file mode 100644 index 000000000..4e0c1c902 --- /dev/null +++ b/crypto-benchmarks.rs/demo/ui/templates/index.html @@ -0,0 +1,82 @@ + + + + + + + Leios Certificate Viewer + + + + + +
+

Leios Certificate Viewer

+
+ + + +
+
+ +
+
+

Summary

+
+
+
EID +
+
+
Votes size +
+
+
Compression gain +
+
+
+
+
EB hash +
+
+
Certificate size +
+
+
+
+
+ +
+

Voters

+
+
+

Persistent (0)

+ + + + + + + + +
#Voter ID
+ +
+
+

Non-persistent (0)

+ + + + + + + + +
#Voter ID
+ +
+
+
+
+ + + \ No newline at end of file From 20a1498230910481fe00b18e31bffa947b4e0426 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Mon, 6 Oct 2025 15:43:40 +0900 Subject: [PATCH 04/16] make UI visualize committee, voters and certificate info --- crypto-benchmarks.rs/demo/README.md | 16 +- .../demo/scripts/10_init_inputs.sh | 2 +- .../demo/scripts/extract_committee.py | 203 ++++++++++++++ crypto-benchmarks.rs/demo/ui/server.py | 131 +++++++-- crypto-benchmarks.rs/demo/ui/static/app.js | 250 ++++++++++++++---- .../demo/ui/static/styles.css | 74 ++++++ .../demo/ui/templates/index.html | 90 +++++-- 7 files changed, 669 insertions(+), 97 deletions(-) create mode 100755 crypto-benchmarks.rs/demo/scripts/extract_committee.py diff --git a/crypto-benchmarks.rs/demo/README.md b/crypto-benchmarks.rs/demo/README.md index 35db94145..570ecc7d0 100644 --- a/crypto-benchmarks.rs/demo/README.md +++ b/crypto-benchmarks.rs/demo/README.md @@ -45,7 +45,7 @@ You can run each script individually to understand and control each step of the Initialize inputs for N voters: ```bash -scripts/10_init_inputs.sh -d run32 +scripts/10_init_inputs.sh -d run64 --pools 200 --stake 100000 --alpha 8 --beta 1 ``` #### 20_make_registry.sh @@ -53,7 +53,7 @@ scripts/10_init_inputs.sh -d run32 Build the registry from initialized inputs: ```bash -scripts/20_make_registry.sh -d run32 -n 32 +scripts/20_make_registry.sh -d run64 -n 64 ``` #### 30_cast_votes.sh @@ -61,7 +61,7 @@ scripts/20_make_registry.sh -d run32 -n 32 Cast votes with a specified fraction of voters voting (e.g., 1.0 means all vote): ```bash -scripts/30_cast_votes.sh -d run32 -f 1.0 +scripts/30_cast_votes.sh -d run64 -f 0.75 ``` #### 40_make_certificate.sh @@ -69,7 +69,7 @@ scripts/30_cast_votes.sh -d run32 -f 1.0 Generate the aggregated certificate: ```bash -scripts/40_make_certificate.sh -d run32 +scripts/40_make_certificate.sh -d run64 ``` #### 50_verify_certificate.sh @@ -77,7 +77,7 @@ scripts/40_make_certificate.sh -d run32 Verify the generated certificate: ```bash -scripts/50_verify_certificate.sh -d run32 +scripts/50_verify_certificate.sh -d run64 ``` #### 60_pretty_print_cert.sh @@ -85,13 +85,13 @@ scripts/50_verify_certificate.sh -d run32 Pretty-print key metrics and statistics of the certificate: ```bash -scripts/60_pretty_print_cert.sh -d run32 +scripts/60_pretty_print_cert.sh -d run64 ``` ### Run a Single End-to-End Demo ```bash -scripts/70_run_one.sh -d run32 -n 32 -f 1.0 +scripts/70_run_one.sh -d run64 -n 64 -f 0.75 ``` This will: @@ -103,7 +103,7 @@ This will: 5. Verify the certificate (`50_verify_certificate.sh`) 6. Pretty-print key metrics (`60_pretty_print_cert.sh`) -All files are placed in `demo/run32/`. +All files are placed in `demo/run64/`. ### Sweep Across Multiple N diff --git a/crypto-benchmarks.rs/demo/scripts/10_init_inputs.sh b/crypto-benchmarks.rs/demo/scripts/10_init_inputs.sh index a6389bda2..e8f1c99ee 100755 --- a/crypto-benchmarks.rs/demo/scripts/10_init_inputs.sh +++ b/crypto-benchmarks.rs/demo/scripts/10_init_inputs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -# Usage: demo/scripts/10_init_inputs.sh [-d DEMO_DIR] [--pools 3000] [--stake 100000] [--alpha 5] [--beta 1] +# Usage: demo/scripts/10_init_inputs.sh [-d DEMO_DIR] [--pools 5000] [--stake 100000] [--alpha 5] [--beta 1] DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$DIR_SCRIPT/.env_cli" diff --git a/crypto-benchmarks.rs/demo/scripts/extract_committee.py b/crypto-benchmarks.rs/demo/scripts/extract_committee.py new file mode 100755 index 000000000..5ee4e9e6e --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/extract_committee.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +# Strict, registry-first committee extraction. +# - Source of truth: registry.cbor -> .info (pool -> {stake, ...}) +# - No fallbacks to stake.cbor / pools.cbor. +# - Fail fast with a clear error when registry is missing/malformed. +# - Optionally map the persistent committee from registry.{persistent_pool|persistent_id}. +# - Optionally read certificate.pretty.json to list elected voters (persistent/non-persistent). + +import os, sys, json, re +from typing import Dict, Any, List, Tuple + +try: + import cbor2 +except Exception as e: + print(json.dumps({"error": f"cbor2 import failed: {e}"})) + sys.exit(1) + +# ----------------------------- args & paths ----------------------------- + +if len(sys.argv) < 2: + print("Usage: extract_committee.py RUN_DIR", file=sys.stderr) + sys.exit(2) + +RUN_DIR = sys.argv[1] +REG_PATH = os.path.join(RUN_DIR, "registry.cbor") +PRETTY_PATH = os.path.join(RUN_DIR, "certificate.pretty.json") + +# Accept 20..64 bytes (40..128 hex chars), with optional 0x/x prefix +HEX_RE = re.compile(r'^(?:0x|x)?[0-9a-f]{40,128}$') + +# ----------------------------- helpers ----------------------------- + +def norm_hex_prefix(s: str) -> str: + s = s.lower() + if s.startswith("0x"): return s + if s.startswith("x"): return "0x" + s[1:] + return "0x" + s + +def normalize_pool_id(val) -> str | None: + """Return pool id as lowercase 0x-prefixed hex string or None.""" + if isinstance(val, (bytes, bytearray)): + return "0x" + val.hex() + if isinstance(val, str): + s = val.strip().lower() + if HEX_RE.match(s): + return norm_hex_prefix(s) + return None + +def die(out_obj: Dict[str, Any], msg: str) -> None: + out_obj.setdefault("notes", []).append("FATAL") + out_obj["error"] = msg + print(json.dumps(out_obj, indent=2)) + sys.exit(1) + +def load_registry(path: str, out_obj: Dict[str, Any]) -> Dict[str, Any]: + if not os.path.isfile(path): + die(out_obj, "registry.cbor not found; please run scripts/20_make_registry.sh first.") + try: + with open(path, "rb") as f: + reg = cbor2.load(f) + except Exception as e: + die(out_obj, f"Failed to parse registry.cbor: {e}") + if not isinstance(reg, dict): + die(out_obj, "registry.cbor has unexpected shape (expected a CBOR map/dict).") + return reg + +def build_stake_map(reg: Dict[str, Any], out_obj: Dict[str, Any]) -> Dict[str, int]: + info = reg.get("info") + if not isinstance(info, dict) or not info: + die(out_obj, "registry.cbor missing non-empty 'info' map; cannot derive stakes.") + stake_map: Dict[str, int] = {} + for pool_key, rec in info.items(): + pid = normalize_pool_id(pool_key) + if pid is None: + die(out_obj, f"registry.info key not a valid pool id hex: {pool_key!r}") + if not isinstance(rec, dict): + die(out_obj, f"registry.info entry for {pool_key} is not a dict.") + st = rec.get("stake") + if not isinstance(st, int): + die(out_obj, f"registry.info entry for {pool_key} missing integer 'stake'.") + stake_map[pid] = int(st) + if not stake_map: + die(out_obj, "No stakes found in registry.info; cannot build committee.") + return stake_map + +def compute_target_seats(reg: Dict[str, Any], max_available: int) -> int: + for k in ("voters", "voter_count", "n", "N", "seats", "committee_size", "committeeSeats"): + v = reg.get(k) + if isinstance(v, int) and 1 <= v <= 5000: + return min(v, max_available) + # fallback to env or 32, clamped to available + try: + env_n = int(os.environ.get("DEMO_SEATS", "32")) + if 1 <= env_n <= 5000: + return min(env_n, max_available) + except Exception: + pass + return min(32, max_available) + +def persistent_committee_from_registry(reg: Dict[str, Any], + stake_map: Dict[str, int], + out_obj: Dict[str, Any]) -> List[Dict[str, Any]]: + result: List[Dict[str, Any]] = [] + pp = reg.get("persistent_pool") + if isinstance(pp, dict) and pp: + # Keys are indices (usually 0..n-1). Sort by numeric index if possible. + try: + items = sorted(pp.items(), key=lambda kv: int(kv[0])) + except Exception: + items = sorted(pp.items(), key=lambda kv: str(kv[0])) + for idx, pool_val in items: + pid_hex = normalize_pool_id(pool_val) + if pid_hex: + result.append({"id": int(idx), "pool_id": pid_hex, "stake": stake_map.get(pid_hex)}) + if result: + out_obj.setdefault("notes", []).append("Persistent committee mapped from registry.cbor persistent_pool") + else: + out_obj.setdefault("notes", []).append("registry.persistent_pool present but contained no valid pool ids") + return result + + # Fallback: invert persistent_id (pool -> index) + pid_map = reg.get("persistent_id") + if isinstance(pid_map, dict) and pid_map: + inv: Dict[int, str] = {} + for pool_val, id_val in pid_map.items(): + pid_hex = normalize_pool_id(pool_val) + try: + idx = int(id_val) + except Exception: + continue + if pid_hex is not None: + inv[idx] = pid_hex + if inv: + for idx, pid_hex in sorted(inv.items(), key=lambda kv: kv[0]): + result.append({"id": int(idx), "pool_id": pid_hex, "stake": stake_map.get(pid_hex)}) + out_obj.setdefault("notes", []).append("Persistent committee inferred by inverting registry.cbor persistent_id") + return result + + out_obj.setdefault("notes", []).append( + "registry.cbor present but has no persistent_pool/persistent_id mappings; persistent committee unavailable" + ) + return result + +def elected_from_pretty(path: str, out_obj: Dict[str, Any]) -> List[Dict[str, Any]]: + if not os.path.isfile(path): + return [] + try: + with open(path) as f: + c = json.load(f) + except Exception as e: + out_obj.setdefault("notes", []).append(f"Failed to read certificate.pretty.json: {e}") + return [] + pv = c.get("persistent_voters") or c.get("persistent_voters_sample") or [] + npv = c.get("nonpersistent_voters") or c.get("nonpersistent_voters_sample") or [] + elected: List[Dict[str, Any]] = ( + [{"voter_id": v, "type": "persistent"} for v in pv] + + [{"voter_id": v, "type": "non-persistent"} for v in npv] + ) + if not (c.get("persistent_voters") or c.get("persistent_voters_sample")) and (c.get("nonpersistent_voters") or c.get("nonpersistent_voters_sample")): + out_obj.setdefault("notes", []).append("All voters are non-persistent in this run; non-persistent voter IDs are ephemeral and do not equal pool IDs.") + return elected + +# ----------------------------- main ----------------------------- + +def main() -> None: + out: Dict[str, Any] = { + "selected_pools": [], # [{index, pool_id, stake}] + "elected": [], # [{voter_id, type}] + "selected_count": 0, + "elected_count": 0, + "persistent_committee": [], + "persistent_count": 0, + "notes": [] + } + + # Load registry once + registry = load_registry(REG_PATH, out) + + # Build stakes from registry.info + stake_map = build_stake_map(registry, out) + + # Selection (top-N by stake, stable tiebreaker by pool id) + target = compute_target_seats(registry, max_available=len(stake_map)) + ordered: List[Tuple[str, int]] = sorted(stake_map.items(), key=lambda kv: (-kv[1], kv[0])) + selected_ids = [pid for pid, _ in ordered[:target]] + + for i, pid_hex in enumerate(selected_ids, 1): + out["selected_pools"].append({"index": i, "pool_id": pid_hex, "stake": stake_map.get(pid_hex)}) + out["selected_count"] = len(out["selected_pools"]) + out["notes"].append("selected_pools derived strictly from registry.cbor info (top-N by stake)") + + # Persistent committee mapping (from the same registry) + out["persistent_committee"] = persistent_committee_from_registry(registry, stake_map, out) + out["persistent_count"] = len(out["persistent_committee"]) + + # Elected voters (optional, from pretty JSON) + out["elected"] = elected_from_pretty(PRETTY_PATH, out) + out["elected_count"] = len(out["elected"]) + + print(json.dumps(out, indent=2)) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/server.py b/crypto-benchmarks.rs/demo/ui/server.py index 136cd2016..fb87bd348 100644 --- a/crypto-benchmarks.rs/demo/ui/server.py +++ b/crypto-benchmarks.rs/demo/ui/server.py @@ -1,28 +1,121 @@ -from flask import Flask, jsonify, render_template, redirect, url_for -import os, json +from flask import Flask, send_from_directory, jsonify, render_template, request, abort +import os, json, subprocess +import cbor2 app = Flask(__name__, static_folder="static", template_folder="templates") -# Resolve demo root (one level up from this `ui` folder) -HERE = os.path.abspath(os.path.dirname(__file__)) -DEMO_ROOT = os.path.abspath(os.path.join(HERE, "..")) +app = Flask(__name__, static_folder="static", template_folder="templates") +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + +def run_dir_path(run): + return os.path.join(ROOT, run) + +def pretty_json_path(run): + return os.path.join(run_dir_path(run), "certificate.pretty.json") + + +def regenerate_pretty(run: str, all_voters: bool) -> None: + """ + Re-generate certificate.pretty.json by invoking our pretty script. + We call the Python module directly to avoid shell pitfalls. + """ + rdir = run_dir_path(run) + cert = os.path.join(rdir, "certificate.cbor") + votes = os.path.join(rdir, "votes.cbor") + script = os.path.join(ROOT, "scripts", "pretty_cert.py") + if not os.path.isfile(script): + abort(500, f"pretty_cert.py not found at {script}") + if not os.path.isfile(cert): + abort(404, f"certificate.cbor not found in {rdir}") + + args = [ "python3", script, cert, votes ] + if all_voters: + args.append("--all-voters") + + # Write to certificate.pretty.json + out_path = pretty_json_path(run) + try: + out = subprocess.check_output(args, cwd=ROOT, text=True) + with open(out_path, "w") as f: + f.write(out) + except subprocess.CalledProcessError as e: + abort(500, f"pretty_cert.py failed: {e.output or e}") + +@app.route("/committee/") +def committee(run): + rdir = run_dir_path(run) + script = os.path.join(ROOT, "scripts", "extract_committee.py") + if not os.path.isfile(script): + abort(500, f"extract_committee.py not found at {script}") + + try: + out = subprocess.check_output(["python3", script, rdir], cwd=ROOT, text=True) + data = json.loads(out) + return jsonify(data) + except subprocess.CalledProcessError as e: + abort(500, f"extract_committee failed: {e.output or e}") + + +@app.route("/registry/") +def registry(run): + """Return pool list + stakes (best-effort).""" + d = run_dir_path(run) + stake_path = os.path.join(d, "stake.cbor") + pools = [] + total_stake = 0 + + if os.path.isfile(stake_path): + with open(stake_path, "rb") as f: + stake = cbor2.load(f) + # stake is likely a map {poolId(bytes|hex): int stake} + if isinstance(stake, dict): + for k, v in stake.items(): + pid = k.hex() if isinstance(k, (bytes, bytearray)) else str(k) + s = int(v) if isinstance(v, int) else 0 + pools.append({"id": pid, "stake": s}) + total_stake += s + + return jsonify({ + "pools": pools, + "total_pools": len(pools), + "total_stake": total_stake + }) + +@app.route("/votes/") +def votes(run): + """Expose elected voter IDs (first pass: read from certificate.pretty.json).""" + d = run_dir_path(run) + cert_path = os.path.join(d, "certificate.pretty.json") + voters = [] + pv = [] + npv = [] + if os.path.isfile(cert_path): + with open(cert_path) as f: + c = json.load(f) + # prefer full lists if present, else samples + pv = c.get("persistent_voters") or [] + npv = c.get("nonpersistent_voters") or c.get("nonpersistent_voters_sample") or [] + voters = (pv or []) + (npv or []) + + return jsonify({ + "elected": voters, + "persistent": pv, + "nonpersistent": npv + }) -# === Example endpoint for cert JSON === @app.route("/cert/") def cert(run): - # Load certificate.pretty.json from ..//certificate.pretty.json - # so it works regardless of the current working directory - rel_path = os.path.join(run, "certificate.pretty.json") - path = os.path.join(DEMO_ROOT, rel_path) - if not os.path.isfile(path): - return jsonify({ - "error": "certificate.pretty.json not found", - "run": run, - "looked_for": path - }), 404 - with open(path, "r", encoding="utf-8") as f: - data = json.load(f) - return jsonify(data) + all_flag = request.args.get("all") == "1" + # If 'all=1' is requested or pretty.json is missing, regenerate + if all_flag or not os.path.isfile(pretty_json_path(run)): + regenerate_pretty(run, all_voters=all_flag) + + try: + with open(pretty_json_path(run)) as f: + data = json.load(f) + return jsonify(data) + except FileNotFoundError: + abort(404, f"certificate.pretty.json not found for {run}") # === UI endpoint === @app.route("/ui") diff --git a/crypto-benchmarks.rs/demo/ui/static/app.js b/crypto-benchmarks.rs/demo/ui/static/app.js index ff59c6bef..97f723b06 100644 --- a/crypto-benchmarks.rs/demo/ui/static/app.js +++ b/crypto-benchmarks.rs/demo/ui/static/app.js @@ -1,40 +1,50 @@ -async function fetchCert(run) { - const res = await fetch(`/cert/${encodeURIComponent(run)}`); - if (!res.ok) throw new Error(`HTTP ${res.status}`); - return res.json(); -} - -function setText(id, text) { - const el = document.getElementById(id); - if (el) el.textContent = text ?? "—"; -} - -function fillTable(tableId, data) { - const tbody = document.querySelector(`#${tableId} tbody`); - if (!tbody) return; - tbody.innerHTML = ""; - data.forEach((id, i) => { - const tr = document.createElement("tr"); - const idx = document.createElement("td"); - idx.textContent = i + 1; - const val = document.createElement("td"); - val.textContent = id; - tr.append(idx, val); - tbody.appendChild(tr); - }); +// ---- utilities ------------------------------------------------------------- + +async function fetchJSON(url) { + const r = await fetch(url); + if (!r.ok) throw new Error(`${url} -> HTTP ${r.status}`); + return r.json(); +} +const $ = (id) => document.getElementById(id); + +function setText(id, txt) { + const el = $(id); + if (el) el.textContent = txt ?? "—"; +} + +function setHidden(id, hidden) { + const el = $(id); + if (el) el.hidden = !!hidden; } -function firstAvailableList(full, sample) { - if (Array.isArray(full) && full.length) return { list: full, sampled: false }; - if (Array.isArray(sample) && sample.length) return { list: sample, sampled: true }; - return { list: [], sampled: false }; +function showError(msg) { + const banner = $("error_banner"); + const text = $("error_text"); + if (banner) banner.hidden = !msg; + if (text) text.textContent = msg || ""; } -function shortenHex(hex, head = 20, tail = 20) { +function clearError() { + showError(""); +} + +// Build a
  • text
  • and replace list content +function fillList(ulId, items) { + const ul = $(ulId); + if (!ul) return; + ul.innerHTML = ""; + items.forEach((t) => { + const li = document.createElement("li"); + li.textContent = t; + ul.appendChild(li); + }); +} + +function shortenHex(hex, head = 14, tail = 14) { if (!hex || typeof hex !== "string") return hex; - const clean = hex.trim(); - if (clean.length <= head + tail) return clean; - return clean.slice(0, head) + "…" + clean.slice(-tail); + const s = hex.trim(); + if (s.length <= head + tail) return s; + return s.slice(0, head) + "…" + s.slice(-tail); } function formatBytes(n) { @@ -42,40 +52,172 @@ function formatBytes(n) { return n.toLocaleString() + " B"; } -async function load(run) { - try { - const data = await fetchCert(run); +// Build a …… +function tr(cells) { + const tr = document.createElement("tr"); + cells.forEach((v) => { + const td = document.createElement("td"); + td.textContent = v; + tr.appendChild(td); + }); + return tr; +} - setText("eid", shortenHex(data.eid)); - const ebElem = document.getElementById("eb"); - ebElem.textContent = shortenHex(data.eb); - ebElem.title = data.eb || ""; +// Replace tbody contents +function fillTbody(tbodyId, rows) { + const tb = $(tbodyId); + if (!tb) return; + tb.innerHTML = ""; + rows.forEach((cells) => tb.appendChild(tr(cells))); +} - setText("votes_bytes", formatBytes(data.votes_bytes)); - setText("certificate_bytes", formatBytes(data.certificate_bytes)); - setText("compression_ratio", data.compression_ratio); +// Render persistent committee (if present) +function renderPersistent(committee) { + const pcs = committee?.persistent_committee ?? []; + const rows = pcs.map((e, i) => { + // each entry: { persistent_id, pool_id, stake } + const pid = (typeof e === "object" ? e.persistent_id : undefined); + const pool = (typeof e === "object" ? e.pool_id : e); + const stake = (typeof e === "object" ? e.stake : undefined); + return [String(i + 1), (pid ?? "—"), shortenHex(pool ?? ""), stake ?? ""]; + }); - setText("pv_count", data.persistent_voters_count); - setText("npv_count", data.nonpersistent_voters_count); + // header count + const count = committee?.persistent_count ?? pcs.length ?? 0; + setText("persistent_title_count", String(count || "0")); + fillTbody("persistent_tbody", rows); - const pv = firstAvailableList(data.persistent_voters, data.persistent_voters_sample); - const npv = firstAvailableList(data.nonpersistent_voters, data.nonpersistent_voters_sample); + // note text (shown only if no persistent seats) + const hasPersistent = (count > 0); + setHidden("persistent_note", hasPersistent); +} - fillTable("pv_table", pv.list); - fillTable("npv_table", npv.list); +// ---- renderers ------------------------------------------------------------- - document.getElementById("pv_note").hidden = !pv.sampled; - document.getElementById("npv_note").hidden = !npv.sampled; - } catch (e) { - alert(`Failed to load certificate for ${run}: ${e.message}`); +function renderSummary(cert) { + setText("eid", cert?.eid ? shortenHex(cert.eid) : "—"); + const ebEl = $("eb"); + if (ebEl) { + ebEl.textContent = cert?.eb ? shortenHex(cert.eb, 18, 18) : "—"; + ebEl.title = cert?.eb ?? ""; } + setText("votes_bytes", formatBytes(cert?.votes_bytes)); + setText("certificate_bytes", formatBytes(cert?.certificate_bytes)); + setText("compression_ratio", cert?.compression_ratio ?? "—"); +} + +function renderSelectedPools(committee) { + const rows = []; + const pools = committee?.selected_pools ?? []; + + pools.forEach((p, i) => { + // Accept both object and string + if (typeof p === "string") { + rows.push([String(i + 1), shortenHex(p), ""]); + } else { + const pid = p.pool_id || p.id || p.voter_id || ""; + const stake = (p.stake ?? "") === "" ? "" : String(p.stake); + rows.push([String(i + 1), shortenHex(pid), stake]); + } + }); + + // header count + const count = committee?.selected_count ?? pools.length ?? 0; + setText("selected_title_count", String(count || "—")); + + fillTbody("selected_tbody", rows); } +function renderElected(committee, votesFallback) { + // Prefer enriched committee.elected (objects). Fall back to /votes. + let elected = committee?.elected; + if (!Array.isArray(elected) || elected.length === 0) { + elected = votesFallback?.elected ?? []; + } + + const rows = elected.map((e, i) => { + if (typeof e === "string") { + return [String(i + 1), shortenHex(e), ""]; + } else { + const id = e.voter_id || e.id || ""; + const t = e.type || ""; + return [String(i + 1), shortenHex(id), t]; + } + }); + + const count = + committee?.elected_count ?? + (Array.isArray(elected) ? elected.length : 0); + setText("elected_title_count", String(count || "—")); + + fillTbody("elected_tbody", rows); +} + +function renderNotes(committee) { + // Note list + const notes = Array.isArray(committee?.notes) ? committee.notes : []; + fillList("notes_ul", notes); + + // If any note looks like an error, surface it. + const err = notes.find((n) => typeof n === "string" && /^error/i.test(n)); + if (err) { + showError(err); + } else { + clearError(); + } +} + +function renderVoters(cert) { + // persistent + const pv = cert?.persistent_voters ?? cert?.persistent_voters_sample ?? []; + const pvRows = pv.map((id, i) => [String(i + 1), shortenHex(id)]); + setText("pv_count", String(pv.length || 0)); + fillTbody("pv_tbody", pvRows); + + // non-persistent + const npv = + cert?.nonpersistent_voters ?? + cert?.nonpersistent_voters_sample ?? + []; + const npvRows = npv.map((id, i) => [String(i + 1), shortenHex(id)]); + setText("npv_count", String(npv.length || 0)); + fillTbody("npv_tbody", npvRows); + + // “(sample)” badges + $("pv_note") && ($("pv_note").hidden = Array.isArray(cert?.persistent_voters) && cert.persistent_voters.length > 0); + $("npv_note") && ($("npv_note").hidden = Array.isArray(cert?.nonpersistent_voters) && cert.nonpersistent_voters.length > 0); +} + +// ---- main load ------------------------------------------------------------ + +async function load(run) { + try { + // Force-refresh the pretty file (all=1 gives full voters if available) + const [cert, committee, votes] = await Promise.all([ + fetchJSON(`/cert/${encodeURIComponent(run)}?all=1`), + fetchJSON(`/committee/${encodeURIComponent(run)}`), + fetchJSON(`/votes/${encodeURIComponent(run)}`) + ]); + + renderSummary(cert); + renderSelectedPools(committee); + renderElected(committee, votes); + renderVoters(cert); + renderPersistent(committee); + renderNotes(committee); + } catch (err) { + showError(`Failed to load data for ${run}: ${err.message}`); + console.error(err); + } +} + +// ---- hooks --------------------------------------------------------------- + document.getElementById("run-form").addEventListener("submit", (e) => { e.preventDefault(); const run = document.getElementById("run-input").value.trim(); if (run) load(run); }); -// auto-load default -load(document.getElementById("run-input").value.trim()); \ No newline at end of file +// initial load +load(document.getElementById("run-input").value.trim() || "run32"); \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/static/styles.css b/crypto-benchmarks.rs/demo/ui/static/styles.css index 639659525..b359c6638 100644 --- a/crypto-benchmarks.rs/demo/ui/static/styles.css +++ b/crypto-benchmarks.rs/demo/ui/static/styles.css @@ -138,4 +138,78 @@ tbody tr:hover { #summary .grid.two>div>div { margin-bottom: 12px; +} + +.gain div { + color: var(--green); + font-weight: 700; +} + +/* Wider left column for Committee section */ +.grid.two.wide-left { + display: grid; + grid-template-columns: 2fr 1.2fr; + gap: 16px 20px; +} + +@media (max-width: 1100px) { + .grid.two.wide-left { + grid-template-columns: 1fr; + } +} + +/* Extra vertical breathing room between stacked info lines */ +#summary .stack { + margin-top: 10px; +} + +#summary .gain div { + color: var(--green); + font-weight: 700; +} + +/* Make tables never run outside the card */ +.card table { + display: block; + width: 100%; + overflow-x: auto; + white-space: nowrap; +} + +/* Tighten the note under the sample tables */ +.note { + margin-top: 6px; + color: var(--muted); + font-size: 12px; +} + +/* Wider left column for Committee so 32-pool table has room */ +.grid.two.wide-left { + display: grid; + grid-template-columns: 1.6fr 1.2fr; + gap: 16px 20px; +} + +@media (max-width: 1100px) { + .grid.two.wide-left { + grid-template-columns: 1fr; + } +} + +/* More vertical spacing between stacked lines */ +#certificate .stack { + margin-top: 10px; +} + +#certificate .gain div { + color: var(--green); + font-weight: 700; +} + +/* Prevent any table from spilling horizontally */ +.card table { + display: block; + width: 100%; + overflow-x: auto; + white-space: nowrap; } \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/templates/index.html b/crypto-benchmarks.rs/demo/ui/templates/index.html index 4e0c1c902..128163755 100644 --- a/crypto-benchmarks.rs/demo/ui/templates/index.html +++ b/crypto-benchmarks.rs/demo/ui/templates/index.html @@ -4,14 +4,14 @@ - Leios Certificate Viewer + Leios Voting Demo
    -

    Leios Certificate Viewer

    +

    Leios Voting Demo

    @@ -20,32 +20,71 @@

    Leios Certificate Viewer

    -
    -

    Summary

    + + +
    +

    Election identifiers

    EID
    -
    Votes size -
    -
    -
    Compression gain -
    -
    EB hash
    -
    Certificate size -
    +
    +
    +
    + + +
    +

    Committee

    +
    + +
    +

    Selected pools ()

    +
    + Derived strictly from registry.cbor.info, ordered by stake (desc), then pool id + (tiebreaker).
    + + + + + + + + + +
    #Pool IDStake
    +
    + +
    +

    Elected (0)

    +
    + Derived from the certificate voters. Types: persistent or non-persistent. +
    + + + + + + + + + +
    #Voter IDType
    -
    + +

    Voters

    @@ -57,7 +96,7 @@

    Persistent (0)

    Voter ID - +
    @@ -70,12 +109,33 @@

    Non-persistent (0)

    Voter ID - +
    + + +
    +

    Certificate

    +
    +
    +
    Votes size +
    +
    +
    Compression gain +
    +
    +
    +
    +
    Certificate size +
    +
    +
    +
    +
    +
    From 11fb5744b751f5d45067d5b3247f0b1542ed0184 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Tue, 7 Oct 2025 15:41:56 +0900 Subject: [PATCH 05/16] make UI visualize Pools in appealing way --- crypto-benchmarks.rs/demo/run64/demo.json | 1728 +++++++++++++++++ .../demo/scripts/25_export_demo_json.sh | 128 ++ crypto-benchmarks.rs/demo/ui/server.py | 13 +- crypto-benchmarks.rs/demo/ui/static/app.js | 425 ++-- .../demo/ui/static/styles.css | 142 +- .../demo/ui/templates/index.html | 151 +- 6 files changed, 2275 insertions(+), 312 deletions(-) create mode 100644 crypto-benchmarks.rs/demo/run64/demo.json create mode 100755 crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh diff --git a/crypto-benchmarks.rs/demo/run64/demo.json b/crypto-benchmarks.rs/demo/run64/demo.json new file mode 100644 index 000000000..c92732cea --- /dev/null +++ b/crypto-benchmarks.rs/demo/run64/demo.json @@ -0,0 +1,1728 @@ +{ + "params": { + "N": 64, + "pool_count": 199, + "total_stake": 100003 + }, + "universe": [ + { + "pool_id": "297d513e8a16fface8ffb11d535e89d0f4ca996ece5a8c2dd7395372", + "stake": 4558, + "is_persistent": true + }, + { + "pool_id": "e7ca79019900d2ff1139d3fae05eff7fc474019386f101ffc7a413d1", + "stake": 4436, + "is_persistent": true + }, + { + "pool_id": "510000bd0d89608c5f480100db9abac7ad2c011e8891d8603704d8f1", + "stake": 3861, + "is_persistent": true + }, + { + "pool_id": "f640a7267abc27ff5399b59b783195003b59bd699c7de7b1a94b90e7", + "stake": 3666, + "is_persistent": true + }, + { + "pool_id": "2bffe13687b1d07dd6a5a8c0e4f64f7c565b6b07634bde1cf5cde490", + "stake": 3404, + "is_persistent": true + }, + { + "pool_id": "4014004f403600cc7835b95fa6597a1daf75ee91d2f8a489c4cc4621", + "stake": 3261, + "is_persistent": true + }, + { + "pool_id": "0caaf065c5e8e01e9c9b01ffee6d5d28166daf9e9f013e7c59f5eea0", + "stake": 3209, + "is_persistent": true + }, + { + "pool_id": "2e3fff190094ec4b1e555381f10132cd8503bcfab77ac69f31061f80", + "stake": 3208, + "is_persistent": true + }, + { + "pool_id": "be7f491f0142d68fbe5db88f81f3611a003e1860df76ff4c993bff55", + "stake": 3002, + "is_persistent": true + }, + { + "pool_id": "80576f0db3dd223800c04d529cec22a7a4e95685fb44b3248579d129", + "stake": 2818, + "is_persistent": true + }, + { + "pool_id": "6b33e699e62bb5e9a253e94001b2e0008928af07480180efacf19eff", + "stake": 2696, + "is_persistent": true + }, + { + "pool_id": "1fac9270dce82a31acdb0044ea2bc33001bccfa733a8343ab508f8db", + "stake": 2581, + "is_persistent": true + }, + { + "pool_id": "010000ea011f3b5f560000c3b7ff09099b00cee6c904ff8db5be05af", + "stake": 2563, + "is_persistent": true + }, + { + "pool_id": "ced712e76857ae0301394b9f80d9883aa915438f3a634948e561cb1c", + "stake": 2394, + "is_persistent": true + }, + { + "pool_id": "68ff8120d80176efacffac2b21455d940a19c1c9d7015715015a2ac1", + "stake": 2347, + "is_persistent": true + }, + { + "pool_id": "8ed5d017d237129da3415d93c29bf235b802fa0d1b326d0e00943e7d", + "stake": 2340, + "is_persistent": true + }, + { + "pool_id": "f1ffb84f6cd5961467690750815dc6fe4528e47c10b5029c62aa017d", + "stake": 2300, + "is_persistent": true + }, + { + "pool_id": "f9ffdfc4cfd3aeb25b759621d4dad6c8cbc6d330d3e865f6e2ccdccd", + "stake": 2265, + "is_persistent": true + }, + { + "pool_id": "2859011646cb69e9af2b00f8a6e99a5e98c315c30d3e0950e2f936d6", + "stake": 2160, + "is_persistent": true + }, + { + "pool_id": "ba2e466a8e04eae7d9601b280bff7730395b62f753fc4dc9daac6551", + "stake": 2140, + "is_persistent": true + }, + { + "pool_id": "55077dbd0017ac00133d4d451fee91ffd732ae0f0028ee91d1f4fe25", + "stake": 1950, + "is_persistent": true + }, + { + "pool_id": "71e9fdd001726ccdce084db3f5d19175c88d7a6ffe72fb7bc7ff9fca", + "stake": 1570, + "is_persistent": true + }, + { + "pool_id": "ff017f87e40030d332e3a201790cb4d191f77cff402359afbfc3f133", + "stake": 1412, + "is_persistent": true + }, + { + "pool_id": "2c01ff0bfee051d6f9a0542f83d86f8ace9400a0bbae83125124ad6a", + "stake": 1365, + "is_persistent": true + }, + { + "pool_id": "a6f18a62cd970f4101011eff07f2ab0924c682d95701a591b2703900", + "stake": 1358, + "is_persistent": true + }, + { + "pool_id": "01b517a3397cf1f2fcffb3de23e53b356cff3000ff8175ff15ce46b5", + "stake": 1283, + "is_persistent": true + }, + { + "pool_id": "2a0197eb60069d9b9674b36d01c05633c7fd9fd9ff62ac440101e1b4", + "stake": 1275, + "is_persistent": true + }, + { + "pool_id": "ab65112ee36236dfb0cbe04901dd8664aa53e14e3a2201e8ba43010f", + "stake": 1238, + "is_persistent": true + }, + { + "pool_id": "42ff05ff217a522a4d0157b811f728ee479ddeeeb6bb791fff00adb4", + "stake": 1209, + "is_persistent": true + }, + { + "pool_id": "4a0814a0d39aff838261015cd7696a86d5bc4893ff334d9011f05dff", + "stake": 1186, + "is_persistent": true + }, + { + "pool_id": "19168e8ba075a44ce41eaacc0ba82193fbfdff79adfaef8075e96c25", + "stake": 1179, + "is_persistent": true + }, + { + "pool_id": "b0a969ed896461627b2d580817d6b723ffdd5b57eff345d2ff0dcf1f", + "stake": 1153, + "is_persistent": true + }, + { + "pool_id": "811e1c4eb5acb7088b384601455e118c0501da7e579807ed6f1042cb", + "stake": 1126, + "is_persistent": true + }, + { + "pool_id": "ebe0eb00bf42788a8cd370e5260166436500a39210f516b5d9ebdc8b", + "stake": 1107, + "is_persistent": true + }, + { + "pool_id": "e534f8d3582a76a57f5c28003af6cdf1c3bcd2a977e27c3999f1f9f3", + "stake": 1091, + "is_persistent": true + }, + { + "pool_id": "73efe21dcd67fb690069dfbd0430002c993fe43d775ed60166b93b5b", + "stake": 1065, + "is_persistent": true + }, + { + "pool_id": "4e70a101cb06c0005072c580645652ff9d5e47f717004ccaba9582ad", + "stake": 1040, + "is_persistent": true + }, + { + "pool_id": "500385315cff8eb2e06b532fadaaa001928b08049da30ce6ff93b769", + "stake": 1018, + "is_persistent": true + }, + { + "pool_id": "d7fffa2e2dd3efe4ae003687be09ff11a53cff8c9d36d4130cb4b5fc", + "stake": 934, + "is_persistent": true + }, + { + "pool_id": "3b96fb5124d0df7e90440fa9ff5501bfab014b31db694c01b03ddbbe", + "stake": 908, + "is_persistent": true + }, + { + "pool_id": "c140043b5a723fe346f700bf44f4b40a8c4f324dbe17b5ac01e3b45e", + "stake": 893, + "is_persistent": true + }, + { + "pool_id": "db5640fd0014ee5c43b3498573454314eeeb89ffbf001f5724e3ff1b", + "stake": 750, + "is_persistent": true + }, + { + "pool_id": "90d495ff0101155eb09ed7ebb1760016b444316a202eff090b82a0c9", + "stake": 746, + "is_persistent": true + }, + { + "pool_id": "58ffff4bd20fc2838e882341014f43f2b5a22934207a2c4f7a57e409", + "stake": 717, + "is_persistent": true + }, + { + "pool_id": "ff49416cbb7e6309ef340b8702deb8cf8d77019b1cc62cf76c3b426e", + "stake": 698, + "is_persistent": true + }, + { + "pool_id": "a82c016500d5ffb7845300684039d9628352c61d6f45d7c1d0d58941", + "stake": 642, + "is_persistent": true + }, + { + "pool_id": "a69cf8710100aba9476c695c78fcbdcb9b89a84e0191695d2a5dcc47", + "stake": 631, + "is_persistent": true + }, + { + "pool_id": "0a754dfc0100b004638dbd846abe3ec155c82b0f8dee5f5e6c00d9ff", + "stake": 568, + "is_persistent": true + }, + { + "pool_id": "f3aacec30955e959ce047c75ffdea67b77d8b9fffab3aa01ffde0dcb", + "stake": 560, + "is_persistent": true + }, + { + "pool_id": "443e7518ab3045e4ff88cb23c4a334d997747e24a64e9ea34f247505", + "stake": 539, + "is_persistent": true + }, + { + "pool_id": "6733785fcc6301e8e58df40e90ed37ee1ef53cb05179007300d31564", + "stake": 533, + "is_persistent": true + }, + { + "pool_id": "1ecb00477c12ef001979a45884f823fac7c2572527fa810c9e0e7aea", + "stake": 498, + "is_persistent": true + }, + { + "pool_id": "01aa219b77ffff26b05caa247625319fc2673c3bfadb0136dc14a604", + "stake": 495, + "is_persistent": true + }, + { + "pool_id": "9d8a72e60df13e75575c00947192a3d493014bf5ce4f4f00a000370b", + "stake": 430, + "is_persistent": true + }, + { + "pool_id": "75d82022f485bcedb6b38c3a01016bd7050fdc9edc2f7fbaa09a40bc", + "stake": 415, + "is_persistent": true + }, + { + "pool_id": "d252010f5c7a970bb92626da9c8e2280921e997c83ee499148bb5101", + "stake": 358, + "is_persistent": false + }, + { + "pool_id": "9adf3ed0bcc79e9b2f9781e209fdd604132868b5ba9e7bb26cff6c5b", + "stake": 353, + "is_persistent": false + }, + { + "pool_id": "2b9199fadb0a912d78b7010e12ff6298aec7000d000462b5b061e4f1", + "stake": 315, + "is_persistent": false + }, + { + "pool_id": "b839bc8cf9f25702013e019bd0d3318c380097e105f9915b653192cb", + "stake": 297, + "is_persistent": false + }, + { + "pool_id": "2b6edf630000468d9dc8f60fffcb9281957c01f6dc945a6a764689f1", + "stake": 294, + "is_persistent": false + }, + { + "pool_id": "bb2ada9401fb84d1a407a8d6a95dfad6a4f30f40bc74ff6fe85a1f77", + "stake": 292, + "is_persistent": false + }, + { + "pool_id": "edcd00b778815439000046ac0036806b2e49e5208b97f2373eefa399", + "stake": 290, + "is_persistent": false + }, + { + "pool_id": "1aaa13bf4f51da2001ac5d0059d846cbdce7ebb95718c9a3dc4b8313", + "stake": 285, + "is_persistent": false + }, + { + "pool_id": "aac1f0971050401758f1597d75d7ff429661fd80f2ae7efddcff2b44", + "stake": 282, + "is_persistent": false + }, + { + "pool_id": "2c807bb6431eba01bafdccb7ed9724ffe4a4ff9b60e0499b0e66c82e", + "stake": 280, + "is_persistent": false + }, + { + "pool_id": "00b76527d1f8f75a1c8bc8d47737a1456f39e900f92ddc360a44035d", + "stake": 251, + "is_persistent": false + }, + { + "pool_id": "caffe8edb5c9fa60ff0101934957014a489501b91b1785f9bfc07b91", + "stake": 218, + "is_persistent": false + }, + { + "pool_id": "c3d75a8b44f1bca7dfdd77a7bd837e5c3fca45ffcdffaff54f950023", + "stake": 215, + "is_persistent": false + }, + { + "pool_id": "3d38fc9218cc6ee9a2bbff011ed815fcb156d14211c11599be5ba788", + "stake": 202, + "is_persistent": false + }, + { + "pool_id": "01503a091fdf01a1fe93bb8cd54e858e11efcb3062ffc08fe5551c61", + "stake": 196, + "is_persistent": false + }, + { + "pool_id": "a9b7d4687b76cfaaf5336bd1b8056a9801d64d472e62ff46d03128a0", + "stake": 196, + "is_persistent": false + }, + { + "pool_id": "ffe264724801dbe3c900838152e884435c57e51a8ef9a701028a3969", + "stake": 181, + "is_persistent": false + }, + { + "pool_id": "cf8a0ae2ff175c5dddb4b3f82bde410a62e6284c3eb8b0c95a001e77", + "stake": 161, + "is_persistent": false + }, + { + "pool_id": "4e01238c2f8994651251c1f3009331e9e38602f0b3c6ff5c78ff86eb", + "stake": 160, + "is_persistent": false + }, + { + "pool_id": "30010fecff2471437901d979b0949fdf0029888ca985041359957bfe", + "stake": 157, + "is_persistent": false + }, + { + "pool_id": "0083ff8b27e6e9ff193bab599900507f97627b3696e5cd0101a65a12", + "stake": 146, + "is_persistent": false + }, + { + "pool_id": "6b5ecc2d7900f8732d944bff0031f84a59dc015bffd801a0bc3a01d1", + "stake": 144, + "is_persistent": false + }, + { + "pool_id": "207309e066ffaf681d01ec627f6b26eb017401568fe788b73101e470", + "stake": 137, + "is_persistent": false + }, + { + "pool_id": "001cb6d54c00316a5bdc38c25dec0002805d01275300c8556ebcc3f0", + "stake": 120, + "is_persistent": false + }, + { + "pool_id": "531fd03400ff89603a49cf1292c85bdd014aac24b4b06cac87bb35cc", + "stake": 112, + "is_persistent": false + }, + { + "pool_id": "ee0e8f64875d4573c839c801a2ff844d822f0c0a8583efabe800bee8", + "stake": 106, + "is_persistent": false + }, + { + "pool_id": "5756abb4f757ff01cb5b72028c01f5b9c38acefd6a6e13fd58019595", + "stake": 102, + "is_persistent": false + }, + { + "pool_id": "4d01b0bcc8c8006300a331d71fe30b1e4d25bf9977015fdaac58e496", + "stake": 99, + "is_persistent": false + }, + { + "pool_id": "1af27c3f21e7009c0aac1f653901718fc24f46c80b1001086ebbc000", + "stake": 88, + "is_persistent": false + }, + { + "pool_id": "dc9000e798a70e9582018179fb1dedf3ff439929b54f01c62300ba34", + "stake": 86, + "is_persistent": false + }, + { + "pool_id": "335701a8a706df54592e06d0b0a6303f86c8a0cf45160f4233063a23", + "stake": 77, + "is_persistent": false + }, + { + "pool_id": "ff435e46f95900e77300c809c9acbee7be000a177d27af8e5677c1d0", + "stake": 67, + "is_persistent": false + }, + { + "pool_id": "80e59d860f7af300c8fa0a0085381e98ffc016c55201f4e07903c5b5", + "stake": 63, + "is_persistent": false + }, + { + "pool_id": "0101caf77c2455864e765290680006008ea882ff7119336178d570d6", + "stake": 58, + "is_persistent": false + }, + { + "pool_id": "b0c6e4ac306cee2a93d0883901ff024056298dbfe3caff01651a5f40", + "stake": 55, + "is_persistent": false + }, + { + "pool_id": "01c7fff54615ff017aeefa017a436f01018062949601e3c1e17361d3", + "stake": 54, + "is_persistent": false + }, + { + "pool_id": "86341e8327010418ec386fc2472b68faeabd07e601ca3c4a9c60dcd9", + "stake": 45, + "is_persistent": false + }, + { + "pool_id": "ba50ebe1716c7bc601769781f4d001f60f2b01a500637393d5d71c1e", + "stake": 44, + "is_persistent": false + }, + { + "pool_id": "0563d9b90133ff2a61b0db2d654cbd8b34845d4550582f3799c047b4", + "stake": 42, + "is_persistent": false + }, + { + "pool_id": "000d0069f82085e3f6239016124290523a41a616a849bab01f098a39", + "stake": 41, + "is_persistent": false + }, + { + "pool_id": "265f28934f7ebd02014e9e301300ff940490a806f824a063f4dd0006", + "stake": 38, + "is_persistent": false + }, + { + "pool_id": "02a542e643ed8181ff0094d801520de5de6eab31e93af8b98ab4ab16", + "stake": 36, + "is_persistent": false + }, + { + "pool_id": "f4a9c6af00ff7f38fca05fbb30c612201d0747e85d46c9c739feec61", + "stake": 33, + "is_persistent": false + }, + { + "pool_id": "fa44e912ce3e0138976ccd75a43ed91b005a2b81382a55fe30457001", + "stake": 33, + "is_persistent": false + }, + { + "pool_id": "0f45ccc70009f917c37ce778ffd15e6feb94301501ae61470013ab95", + "stake": 32, + "is_persistent": false + }, + { + "pool_id": "ae763c00f5eb985709e87c7052eefcef8c6e2e00053ae43dbbadde23", + "stake": 29, + "is_persistent": false + }, + { + "pool_id": "0e49da3e75c8da00da48bf4f44ad6a6773d12ea6e2fbd06ac4708498", + "stake": 28, + "is_persistent": false + }, + { + "pool_id": "7da0a5ff9b13a7fb37f086c7ffff23eb6e9f22ecb737cf847ed0f54f", + "stake": 25, + "is_persistent": false + }, + { + "pool_id": "7adca68c6dae1a9ffd8e750e365724ff1973e8000bc5d414be96ddd8", + "stake": 23, + "is_persistent": false + }, + { + "pool_id": "b4795c951bdc6eb4b4fff0e7fb58a0ed66019b6feeb3d672f5d5fe4c", + "stake": 22, + "is_persistent": false + }, + { + "pool_id": "355d06bb68721a3f32012d7e01cdce721fff534b64daac0cf4c81154", + "stake": 18, + "is_persistent": false + }, + { + "pool_id": "c31868f06551db9fb49efa008c986ad74f016e5e7b16ebfa974f2638", + "stake": 18, + "is_persistent": false + }, + { + "pool_id": "f9b715e13a124f5020a11f2dca0100a701fbc2d464717ea7cbff563e", + "stake": 17, + "is_persistent": false + }, + { + "pool_id": "8017be2c179b0b006b994cc3e360ff4a01855bffccff265f15dec969", + "stake": 15, + "is_persistent": false + }, + { + "pool_id": "d97dec3dced0d17cff6e28ff0164475d66d4ff408bef00b06dd47514", + "stake": 15, + "is_persistent": false + }, + { + "pool_id": "18daffe3f32a85016a9d3b191ff243bfa00126d57bf7c96ce3740160", + "stake": 14, + "is_persistent": false + }, + { + "pool_id": "637ea1112b52012500377800fcbe2d630d49f7d112cfd811062c0001", + "stake": 14, + "is_persistent": false + }, + { + "pool_id": "d4559298046806732261ff121033bfcad7727f2e3fcd19fa7d92f221", + "stake": 13, + "is_persistent": false + }, + { + "pool_id": "3963d9c18518b0ed37d5e2f13a761f42ca42834400351d07ab006e88", + "stake": 11, + "is_persistent": false + }, + { + "pool_id": "f8095955680192bd0b5c1fd1fb097e77d4009a3528aa1da5b03f01bb", + "stake": 10, + "is_persistent": false + }, + { + "pool_id": "0de3e268b7a37d6be7858651fcb0e4471aaa004d107101be225b52b2", + "stake": 9, + "is_persistent": false + }, + { + "pool_id": "20ecc3af4979fdd8182e70d95fff01a000010b269a07e6eb4b2110a5", + "stake": 8, + "is_persistent": false + }, + { + "pool_id": "93a2f528ea475c20765525ae00370aeea70db47dff0d80516f25b5a5", + "stake": 8, + "is_persistent": false + }, + { + "pool_id": "99ed0aeb8fecf0ff4f005bb2e314bd82009a306da0b5d2005af20e2d", + "stake": 7, + "is_persistent": false + }, + { + "pool_id": "a7000014ff9c2d4d01ff650cb658a2ab68de94017955252b452cbb67", + "stake": 7, + "is_persistent": false + }, + { + "pool_id": "478d8eed13452a013ed5cacffadcca01b0515c1de41e956a4c008200", + "stake": 5, + "is_persistent": false + }, + { + "pool_id": "7888ffdcd24591bbcd6435a6e45b2927070a691221c710d04c4a1462", + "stake": 5, + "is_persistent": false + }, + { + "pool_id": "ff25ff7affffe9ffb4acff8830154f6c5ae61cca9900ccf9e9a0b9e1", + "stake": 5, + "is_persistent": false + }, + { + "pool_id": "1fcd8559ffd00f01118267bdcf2249918fdeeb0045fb01d8d48e3c61", + "stake": 4, + "is_persistent": false + }, + { + "pool_id": "54e2bbe6cb0118b4f300e499cdb1d56b7c01e0ff23ae3bb1c98180b9", + "stake": 4, + "is_persistent": false + }, + { + "pool_id": "f835c4cf5356f604e3d50f321d3c2b41248f700103762400479d59fc", + "stake": 4, + "is_persistent": false + }, + { + "pool_id": "82e98351e11d9cad3affca45e6348fdb1f81055d880013b709be427e", + "stake": 3, + "is_persistent": false + }, + { + "pool_id": "bc2e1db28d542ac322050c271aef6c419effaceb9118c59c343ec4e5", + "stake": 3, + "is_persistent": false + }, + { + "pool_id": "ccd3b6592176ff005baf7e6fd5eaa0853f219b3f0dbeefe8c801ebc2", + "stake": 3, + "is_persistent": false + }, + { + "pool_id": "d8fff0ffe3ff7fd67e59ae471725c4e2af3e6c56f00d012f52620104", + "stake": 3, + "is_persistent": false + }, + { + "pool_id": "ec3a4f922b9fecda5bd4746503ff085b6e9f322838644adbb950dffc", + "stake": 3, + "is_persistent": false + }, + { + "pool_id": "22277d0101e05d42a527f33785e6b08631c02dcf3de70ba550f5c9ff", + "stake": 2, + "is_persistent": false + }, + { + "pool_id": "59006160937cac095bbc4ce7a96a8f3fe5527659008f95b4eefba797", + "stake": 2, + "is_persistent": false + }, + { + "pool_id": "71b1a01a93ff410e7bf4391404ce537f5d0d19ba967dc2eec8a636b7", + "stake": 2, + "is_persistent": false + }, + { + "pool_id": "98b59653e3bf4859d49bafc5e540abeb8d2339c56232b837ffb833b4", + "stake": 2, + "is_persistent": false + }, + { + "pool_id": "ff25ff50f09bee01f10058ffdc596d91dee5f60005b9b3ffa951f879", + "stake": 2, + "is_persistent": false + }, + { + "pool_id": "37c401bad06f05d2ec9a77da395b3156372600ffee4f511b6864d02a", + "stake": 1, + "is_persistent": false + }, + { + "pool_id": "39010395d8f5e16969d721797bf846618a525e32015b23c7c60176ea", + "stake": 1, + "is_persistent": false + }, + { + "pool_id": "49f07c93777ce5720c35c21a48cadb9cfde005015033f3f5b4cb2b88", + "stake": 1, + "is_persistent": false + }, + { + "pool_id": "8300897d3c7d6e9a2907335fce58eef00009f22a56e500f46201392f", + "stake": 1, + "is_persistent": false + }, + { + "pool_id": "95a79da700235961ff73dc78ff5b6d2401177e79b5b2c6dbef711484", + "stake": 1, + "is_persistent": false + }, + { + "pool_id": "c3838dcbac6ee5c64999c3160517199db36edf0a00ffaac9adf22798", + "stake": 1, + "is_persistent": false + }, + { + "pool_id": "ff9314d1834e2506f701fcdaeca726b512ff518bc31b8c1136e4c4eb", + "stake": 1, + "is_persistent": false + }, + { + "pool_id": "ffcae231011c0d6bdd7546303abb2f1ca253e6f2a9f980008afcafff", + "stake": 1, + "is_persistent": false + }, + { + "pool_id": "00b9364927d3ff64bdb81559031af0a79793de4dd18a2004524316cd", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "00b9bd6144ff16889ff63ce92f500e0144e1f3578f34220090c9f8ab", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "01511e82363771cc1101c4409bde01c2ff2d692f58a88eae37cd187a", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "01752201757f364caac686e9e34d20896999bb6064003b1494726af5", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "017e5ea1a154748a90be98ecd0436cac89776ca200029eff3a95de2c", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "10a8c8ec01ed04434f7a463e14ff0a994296e97718a822c46baab88c", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "113e737d14c51476cb5db565ef179759e99a1d44999084108f2866d8", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "13717c14c3207e44d578edff0bf76a2c4e6930990e35a0009970010f", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "14ffc31bfb0110002db1187f26b16bd3482d9fafff519fb0d3b2a38d", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "20dba91387599e09d5606b6aefec981d0f5b0fed8501b579cb8e848f", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "34e8478201ffbeb0ab04fe41eabb095d36ed6e0087340fe617d09955", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "3a06013f8b1a6691aa4d9a2c952b89ce112616774ccd9e24422f09a4", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "3a6e005c411e8ef0c3a2852aec01890dd7ce00bc7e8400b983477701", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "3d007bd3a16b52cb06e5701d5b4466a5007619ccbd19871c77eaf57b", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "50446c4f26ffaf6598c783410bcdde5905354efe00fce6f7375301ff", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "522a8d515c38e3b9fffc8dcb25016b018ebaf1010164012024e0a338", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "52aa09ff004fdd8fc3f149163e7d79257aaf5d4fc3cff7576d029c7b", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "5300231cfdec3f47e44411fc8ebd00f924f45f5420a44d42ffff4681", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "596ebe0738ca1b90cbcbd10a0b804ac20001f94e6474ff384229c14c", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "5abdb5007572b254df05579124c6ff921b0051405af73ea7aba38220", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "5db101e507115c40e4468201231453ad67e1ef7d33ef52e8f601e246", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "5e8b326e867efc6e6af10f22029ed62e10d86bd2ab252f280d1a4f48", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "5ef19efeff7c483e5643b481e40d8af0f6f07bcfffbddd378a6e5407", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "639a0e1530c500d14d4d849c3c95907957896ba98b125e3f00ffd631", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "653debf312a13afba13f2cf426c101292000de0a090ea91e0d3d39dd", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "6c00017cc07aada307933f15167a869f11a101c21deee345d8bf3f25", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "6d1a598dcca7d4c324d50c0940016067507622b6a485252aa60c6969", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "71aace9f096f1101d3fd3b005694ff6242461ccb710ada3800429d4a", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "7500290ce900ff55e0ead88900fcae3b6130aabdce1c82251b864abc", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "7a5b49558d006748d492ec8d7b5a4c3150f101032e05f601b292b4cf", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "7da96f49fff07a7c1e3a000150fe00209b1dff4b7bafecb9d2ce0459", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "7f00a078e500d7d25c47be2cde9531d28b16fcf2e33919fcd9e44901", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "8a8aa178e900bea547c0d05b2aebc95545476a1a1ddf508defec68fa", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "925cff8cc10114e6b4bb6b644ab1515ad1d5f7a14acace906001d9b9", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "9629f17dafd13dd2ff50a10e5875c22b657cd97cff53ff2a2d18a14a", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "9a15ba62b8db62910e9c3912d1343a91437ab701ff24b9656deea4ae", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "9a453c8df82bffc7d689000bdc7ef50d6de0ae20c601be6ed34f7150", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "9a780054ff9f0d133a21e1830af5944b2168f7577790eedb3d01752d", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "9c67c57acd01a8e832000d00184438b80f9342b162c401c1a1be12e3", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "a05bff2f497264b839ab77016b7cff8b371a36ccb643ff0500b629d9", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "a1d4f80d5a0c3b0df1c53100d60000e6d3852a4d4d2d7d843c22a437", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "a1e427086d013db41e1c5097ef6c60c76531a5313e29c037f49757eb", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "a4e29acfb9642ee43438b0513c2a96ef4def8262d069c3acd2462501", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "afe159d207188718330fff49e3ffa0d27b834cb49b6bf656ff8f0154", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "bc5cbcfb2fd1ecbebf89daa69c579108a91b13ff95ae8946ff290340", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "c2b4ccea0d400dee7e42defe8a983648db60ff54103f6871fc07f5e3", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "cb80746a9eb1765bb7490080a3012d0e010936c16ea6923c551e3b72", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "d6dcab7100bf65a073e5003e0cf162cfc22bdbd6c971cb70ee0b3ca7", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "e0e9010efff096cb8e8dd7cc6d01dbf70c362755da9618f63f26e235", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "ee7b6eff6f754401ffc74f6d14a6087eebe1a80100ff8dbf60dd9ca5", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "f42f010142a697076ea5f42296b10f45cdca1087df799ca8ef8a195c", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "fecb57048d31890103990628480100b05211e497adc8dd2ddd5af14f", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "ff03ff27010810b9c9fd4006d59eb200ae937ce98ef499967cb80100", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "ff14b167c2f0d174457ac4bda2b2c91a18b77ea16232cd0001de80f2", + "stake": 0, + "is_persistent": false + }, + { + "pool_id": "ff330da5a8f6da32801d8f923d07ff6ec2640157fc483f34f1a483e7", + "stake": 0, + "is_persistent": false + } + ], + "persistent_map": { + "0": "297d513e8a16fface8ffb11d535e89d0f4ca996ece5a8c2dd7395372", + "1": "e7ca79019900d2ff1139d3fae05eff7fc474019386f101ffc7a413d1", + "2": "510000bd0d89608c5f480100db9abac7ad2c011e8891d8603704d8f1", + "3": "f640a7267abc27ff5399b59b783195003b59bd699c7de7b1a94b90e7", + "4": "2bffe13687b1d07dd6a5a8c0e4f64f7c565b6b07634bde1cf5cde490", + "5": "4014004f403600cc7835b95fa6597a1daf75ee91d2f8a489c4cc4621", + "6": "0caaf065c5e8e01e9c9b01ffee6d5d28166daf9e9f013e7c59f5eea0", + "7": "2e3fff190094ec4b1e555381f10132cd8503bcfab77ac69f31061f80", + "8": "be7f491f0142d68fbe5db88f81f3611a003e1860df76ff4c993bff55", + "9": "80576f0db3dd223800c04d529cec22a7a4e95685fb44b3248579d129", + "10": "6b33e699e62bb5e9a253e94001b2e0008928af07480180efacf19eff", + "11": "1fac9270dce82a31acdb0044ea2bc33001bccfa733a8343ab508f8db", + "12": "010000ea011f3b5f560000c3b7ff09099b00cee6c904ff8db5be05af", + "13": "ced712e76857ae0301394b9f80d9883aa915438f3a634948e561cb1c", + "14": "68ff8120d80176efacffac2b21455d940a19c1c9d7015715015a2ac1", + "15": "8ed5d017d237129da3415d93c29bf235b802fa0d1b326d0e00943e7d", + "16": "f1ffb84f6cd5961467690750815dc6fe4528e47c10b5029c62aa017d", + "17": "f9ffdfc4cfd3aeb25b759621d4dad6c8cbc6d330d3e865f6e2ccdccd", + "18": "2859011646cb69e9af2b00f8a6e99a5e98c315c30d3e0950e2f936d6", + "19": "ba2e466a8e04eae7d9601b280bff7730395b62f753fc4dc9daac6551", + "20": "55077dbd0017ac00133d4d451fee91ffd732ae0f0028ee91d1f4fe25", + "21": "71e9fdd001726ccdce084db3f5d19175c88d7a6ffe72fb7bc7ff9fca", + "22": "ff017f87e40030d332e3a201790cb4d191f77cff402359afbfc3f133", + "23": "2c01ff0bfee051d6f9a0542f83d86f8ace9400a0bbae83125124ad6a", + "24": "a6f18a62cd970f4101011eff07f2ab0924c682d95701a591b2703900", + "25": "01b517a3397cf1f2fcffb3de23e53b356cff3000ff8175ff15ce46b5", + "26": "2a0197eb60069d9b9674b36d01c05633c7fd9fd9ff62ac440101e1b4", + "27": "ab65112ee36236dfb0cbe04901dd8664aa53e14e3a2201e8ba43010f", + "28": "42ff05ff217a522a4d0157b811f728ee479ddeeeb6bb791fff00adb4", + "29": "4a0814a0d39aff838261015cd7696a86d5bc4893ff334d9011f05dff", + "30": "19168e8ba075a44ce41eaacc0ba82193fbfdff79adfaef8075e96c25", + "31": "b0a969ed896461627b2d580817d6b723ffdd5b57eff345d2ff0dcf1f", + "32": "811e1c4eb5acb7088b384601455e118c0501da7e579807ed6f1042cb", + "33": "ebe0eb00bf42788a8cd370e5260166436500a39210f516b5d9ebdc8b", + "34": "e534f8d3582a76a57f5c28003af6cdf1c3bcd2a977e27c3999f1f9f3", + "35": "73efe21dcd67fb690069dfbd0430002c993fe43d775ed60166b93b5b", + "36": "4e70a101cb06c0005072c580645652ff9d5e47f717004ccaba9582ad", + "37": "500385315cff8eb2e06b532fadaaa001928b08049da30ce6ff93b769", + "38": "d7fffa2e2dd3efe4ae003687be09ff11a53cff8c9d36d4130cb4b5fc", + "39": "3b96fb5124d0df7e90440fa9ff5501bfab014b31db694c01b03ddbbe", + "40": "c140043b5a723fe346f700bf44f4b40a8c4f324dbe17b5ac01e3b45e", + "41": "db5640fd0014ee5c43b3498573454314eeeb89ffbf001f5724e3ff1b", + "42": "90d495ff0101155eb09ed7ebb1760016b444316a202eff090b82a0c9", + "43": "58ffff4bd20fc2838e882341014f43f2b5a22934207a2c4f7a57e409", + "44": "ff49416cbb7e6309ef340b8702deb8cf8d77019b1cc62cf76c3b426e", + "45": "a82c016500d5ffb7845300684039d9628352c61d6f45d7c1d0d58941", + "46": "a69cf8710100aba9476c695c78fcbdcb9b89a84e0191695d2a5dcc47", + "47": "0a754dfc0100b004638dbd846abe3ec155c82b0f8dee5f5e6c00d9ff", + "48": "f3aacec30955e959ce047c75ffdea67b77d8b9fffab3aa01ffde0dcb", + "49": "443e7518ab3045e4ff88cb23c4a334d997747e24a64e9ea34f247505", + "50": "6733785fcc6301e8e58df40e90ed37ee1ef53cb05179007300d31564", + "51": "1ecb00477c12ef001979a45884f823fac7c2572527fa810c9e0e7aea", + "52": "01aa219b77ffff26b05caa247625319fc2673c3bfadb0136dc14a604", + "53": "9d8a72e60df13e75575c00947192a3d493014bf5ce4f4f00a000370b", + "54": "75d82022f485bcedb6b38c3a01016bd7050fdc9edc2f7fbaa09a40bc" + }, + "committee": [ + { + "index": 1, + "pool_id": "297d513e8a16fface8ffb11d535e89d0f4ca996ece5a8c2dd7395372", + "stake": 4558 + }, + { + "index": 2, + "pool_id": "e7ca79019900d2ff1139d3fae05eff7fc474019386f101ffc7a413d1", + "stake": 4436 + }, + { + "index": 3, + "pool_id": "510000bd0d89608c5f480100db9abac7ad2c011e8891d8603704d8f1", + "stake": 3861 + }, + { + "index": 4, + "pool_id": "f640a7267abc27ff5399b59b783195003b59bd699c7de7b1a94b90e7", + "stake": 3666 + }, + { + "index": 5, + "pool_id": "2bffe13687b1d07dd6a5a8c0e4f64f7c565b6b07634bde1cf5cde490", + "stake": 3404 + }, + { + "index": 6, + "pool_id": "4014004f403600cc7835b95fa6597a1daf75ee91d2f8a489c4cc4621", + "stake": 3261 + }, + { + "index": 7, + "pool_id": "0caaf065c5e8e01e9c9b01ffee6d5d28166daf9e9f013e7c59f5eea0", + "stake": 3209 + }, + { + "index": 8, + "pool_id": "2e3fff190094ec4b1e555381f10132cd8503bcfab77ac69f31061f80", + "stake": 3208 + }, + { + "index": 9, + "pool_id": "be7f491f0142d68fbe5db88f81f3611a003e1860df76ff4c993bff55", + "stake": 3002 + }, + { + "index": 10, + "pool_id": "80576f0db3dd223800c04d529cec22a7a4e95685fb44b3248579d129", + "stake": 2818 + }, + { + "index": 11, + "pool_id": "6b33e699e62bb5e9a253e94001b2e0008928af07480180efacf19eff", + "stake": 2696 + }, + { + "index": 12, + "pool_id": "1fac9270dce82a31acdb0044ea2bc33001bccfa733a8343ab508f8db", + "stake": 2581 + }, + { + "index": 13, + "pool_id": "010000ea011f3b5f560000c3b7ff09099b00cee6c904ff8db5be05af", + "stake": 2563 + }, + { + "index": 14, + "pool_id": "ced712e76857ae0301394b9f80d9883aa915438f3a634948e561cb1c", + "stake": 2394 + }, + { + "index": 15, + "pool_id": "68ff8120d80176efacffac2b21455d940a19c1c9d7015715015a2ac1", + "stake": 2347 + }, + { + "index": 16, + "pool_id": "8ed5d017d237129da3415d93c29bf235b802fa0d1b326d0e00943e7d", + "stake": 2340 + }, + { + "index": 17, + "pool_id": "f1ffb84f6cd5961467690750815dc6fe4528e47c10b5029c62aa017d", + "stake": 2300 + }, + { + "index": 18, + "pool_id": "f9ffdfc4cfd3aeb25b759621d4dad6c8cbc6d330d3e865f6e2ccdccd", + "stake": 2265 + }, + { + "index": 19, + "pool_id": "2859011646cb69e9af2b00f8a6e99a5e98c315c30d3e0950e2f936d6", + "stake": 2160 + }, + { + "index": 20, + "pool_id": "ba2e466a8e04eae7d9601b280bff7730395b62f753fc4dc9daac6551", + "stake": 2140 + }, + { + "index": 21, + "pool_id": "55077dbd0017ac00133d4d451fee91ffd732ae0f0028ee91d1f4fe25", + "stake": 1950 + }, + { + "index": 22, + "pool_id": "71e9fdd001726ccdce084db3f5d19175c88d7a6ffe72fb7bc7ff9fca", + "stake": 1570 + }, + { + "index": 23, + "pool_id": "ff017f87e40030d332e3a201790cb4d191f77cff402359afbfc3f133", + "stake": 1412 + }, + { + "index": 24, + "pool_id": "2c01ff0bfee051d6f9a0542f83d86f8ace9400a0bbae83125124ad6a", + "stake": 1365 + }, + { + "index": 25, + "pool_id": "a6f18a62cd970f4101011eff07f2ab0924c682d95701a591b2703900", + "stake": 1358 + }, + { + "index": 26, + "pool_id": "01b517a3397cf1f2fcffb3de23e53b356cff3000ff8175ff15ce46b5", + "stake": 1283 + }, + { + "index": 27, + "pool_id": "2a0197eb60069d9b9674b36d01c05633c7fd9fd9ff62ac440101e1b4", + "stake": 1275 + }, + { + "index": 28, + "pool_id": "ab65112ee36236dfb0cbe04901dd8664aa53e14e3a2201e8ba43010f", + "stake": 1238 + }, + { + "index": 29, + "pool_id": "42ff05ff217a522a4d0157b811f728ee479ddeeeb6bb791fff00adb4", + "stake": 1209 + }, + { + "index": 30, + "pool_id": "4a0814a0d39aff838261015cd7696a86d5bc4893ff334d9011f05dff", + "stake": 1186 + }, + { + "index": 31, + "pool_id": "19168e8ba075a44ce41eaacc0ba82193fbfdff79adfaef8075e96c25", + "stake": 1179 + }, + { + "index": 32, + "pool_id": "b0a969ed896461627b2d580817d6b723ffdd5b57eff345d2ff0dcf1f", + "stake": 1153 + }, + { + "index": 33, + "pool_id": "811e1c4eb5acb7088b384601455e118c0501da7e579807ed6f1042cb", + "stake": 1126 + }, + { + "index": 34, + "pool_id": "ebe0eb00bf42788a8cd370e5260166436500a39210f516b5d9ebdc8b", + "stake": 1107 + }, + { + "index": 35, + "pool_id": "e534f8d3582a76a57f5c28003af6cdf1c3bcd2a977e27c3999f1f9f3", + "stake": 1091 + }, + { + "index": 36, + "pool_id": "73efe21dcd67fb690069dfbd0430002c993fe43d775ed60166b93b5b", + "stake": 1065 + }, + { + "index": 37, + "pool_id": "4e70a101cb06c0005072c580645652ff9d5e47f717004ccaba9582ad", + "stake": 1040 + }, + { + "index": 38, + "pool_id": "500385315cff8eb2e06b532fadaaa001928b08049da30ce6ff93b769", + "stake": 1018 + }, + { + "index": 39, + "pool_id": "d7fffa2e2dd3efe4ae003687be09ff11a53cff8c9d36d4130cb4b5fc", + "stake": 934 + }, + { + "index": 40, + "pool_id": "3b96fb5124d0df7e90440fa9ff5501bfab014b31db694c01b03ddbbe", + "stake": 908 + }, + { + "index": 41, + "pool_id": "c140043b5a723fe346f700bf44f4b40a8c4f324dbe17b5ac01e3b45e", + "stake": 893 + }, + { + "index": 42, + "pool_id": "db5640fd0014ee5c43b3498573454314eeeb89ffbf001f5724e3ff1b", + "stake": 750 + }, + { + "index": 43, + "pool_id": "90d495ff0101155eb09ed7ebb1760016b444316a202eff090b82a0c9", + "stake": 746 + }, + { + "index": 44, + "pool_id": "58ffff4bd20fc2838e882341014f43f2b5a22934207a2c4f7a57e409", + "stake": 717 + }, + { + "index": 45, + "pool_id": "ff49416cbb7e6309ef340b8702deb8cf8d77019b1cc62cf76c3b426e", + "stake": 698 + }, + { + "index": 46, + "pool_id": "a82c016500d5ffb7845300684039d9628352c61d6f45d7c1d0d58941", + "stake": 642 + }, + { + "index": 47, + "pool_id": "a69cf8710100aba9476c695c78fcbdcb9b89a84e0191695d2a5dcc47", + "stake": 631 + }, + { + "index": 48, + "pool_id": "0a754dfc0100b004638dbd846abe3ec155c82b0f8dee5f5e6c00d9ff", + "stake": 568 + }, + { + "index": 49, + "pool_id": "f3aacec30955e959ce047c75ffdea67b77d8b9fffab3aa01ffde0dcb", + "stake": 560 + }, + { + "index": 50, + "pool_id": "443e7518ab3045e4ff88cb23c4a334d997747e24a64e9ea34f247505", + "stake": 539 + }, + { + "index": 51, + "pool_id": "6733785fcc6301e8e58df40e90ed37ee1ef53cb05179007300d31564", + "stake": 533 + }, + { + "index": 52, + "pool_id": "1ecb00477c12ef001979a45884f823fac7c2572527fa810c9e0e7aea", + "stake": 498 + }, + { + "index": 53, + "pool_id": "01aa219b77ffff26b05caa247625319fc2673c3bfadb0136dc14a604", + "stake": 495 + }, + { + "index": 54, + "pool_id": "9d8a72e60df13e75575c00947192a3d493014bf5ce4f4f00a000370b", + "stake": 430 + }, + { + "index": 55, + "pool_id": "75d82022f485bcedb6b38c3a01016bd7050fdc9edc2f7fbaa09a40bc", + "stake": 415 + }, + { + "index": 56, + "pool_id": "d252010f5c7a970bb92626da9c8e2280921e997c83ee499148bb5101", + "stake": 358 + }, + { + "index": 57, + "pool_id": "9adf3ed0bcc79e9b2f9781e209fdd604132868b5ba9e7bb26cff6c5b", + "stake": 353 + }, + { + "index": 58, + "pool_id": "2b9199fadb0a912d78b7010e12ff6298aec7000d000462b5b061e4f1", + "stake": 315 + }, + { + "index": 59, + "pool_id": "b839bc8cf9f25702013e019bd0d3318c380097e105f9915b653192cb", + "stake": 297 + }, + { + "index": 60, + "pool_id": "2b6edf630000468d9dc8f60fffcb9281957c01f6dc945a6a764689f1", + "stake": 294 + }, + { + "index": 61, + "pool_id": "bb2ada9401fb84d1a407a8d6a95dfad6a4f30f40bc74ff6fe85a1f77", + "stake": 292 + }, + { + "index": 62, + "pool_id": "edcd00b778815439000046ac0036806b2e49e5208b97f2373eefa399", + "stake": 290 + }, + { + "index": 63, + "pool_id": "1aaa13bf4f51da2001ac5d0059d846cbdce7ebb95718c9a3dc4b8313", + "stake": 285 + }, + { + "index": 64, + "pool_id": "aac1f0971050401758f1597d75d7ff429661fd80f2ae7efddcff2b44", + "stake": 282 + } + ], + "lookup": { + "universe_index_by_pool_id": { + "297d513e8a16fface8ffb11d535e89d0f4ca996ece5a8c2dd7395372": 0, + "e7ca79019900d2ff1139d3fae05eff7fc474019386f101ffc7a413d1": 1, + "510000bd0d89608c5f480100db9abac7ad2c011e8891d8603704d8f1": 2, + "f640a7267abc27ff5399b59b783195003b59bd699c7de7b1a94b90e7": 3, + "2bffe13687b1d07dd6a5a8c0e4f64f7c565b6b07634bde1cf5cde490": 4, + "4014004f403600cc7835b95fa6597a1daf75ee91d2f8a489c4cc4621": 5, + "0caaf065c5e8e01e9c9b01ffee6d5d28166daf9e9f013e7c59f5eea0": 6, + "2e3fff190094ec4b1e555381f10132cd8503bcfab77ac69f31061f80": 7, + "be7f491f0142d68fbe5db88f81f3611a003e1860df76ff4c993bff55": 8, + "80576f0db3dd223800c04d529cec22a7a4e95685fb44b3248579d129": 9, + "6b33e699e62bb5e9a253e94001b2e0008928af07480180efacf19eff": 10, + "1fac9270dce82a31acdb0044ea2bc33001bccfa733a8343ab508f8db": 11, + "010000ea011f3b5f560000c3b7ff09099b00cee6c904ff8db5be05af": 12, + "ced712e76857ae0301394b9f80d9883aa915438f3a634948e561cb1c": 13, + "68ff8120d80176efacffac2b21455d940a19c1c9d7015715015a2ac1": 14, + "8ed5d017d237129da3415d93c29bf235b802fa0d1b326d0e00943e7d": 15, + "f1ffb84f6cd5961467690750815dc6fe4528e47c10b5029c62aa017d": 16, + "f9ffdfc4cfd3aeb25b759621d4dad6c8cbc6d330d3e865f6e2ccdccd": 17, + "2859011646cb69e9af2b00f8a6e99a5e98c315c30d3e0950e2f936d6": 18, + "ba2e466a8e04eae7d9601b280bff7730395b62f753fc4dc9daac6551": 19, + "55077dbd0017ac00133d4d451fee91ffd732ae0f0028ee91d1f4fe25": 20, + "71e9fdd001726ccdce084db3f5d19175c88d7a6ffe72fb7bc7ff9fca": 21, + "ff017f87e40030d332e3a201790cb4d191f77cff402359afbfc3f133": 22, + "2c01ff0bfee051d6f9a0542f83d86f8ace9400a0bbae83125124ad6a": 23, + "a6f18a62cd970f4101011eff07f2ab0924c682d95701a591b2703900": 24, + "01b517a3397cf1f2fcffb3de23e53b356cff3000ff8175ff15ce46b5": 25, + "2a0197eb60069d9b9674b36d01c05633c7fd9fd9ff62ac440101e1b4": 26, + "ab65112ee36236dfb0cbe04901dd8664aa53e14e3a2201e8ba43010f": 27, + "42ff05ff217a522a4d0157b811f728ee479ddeeeb6bb791fff00adb4": 28, + "4a0814a0d39aff838261015cd7696a86d5bc4893ff334d9011f05dff": 29, + "19168e8ba075a44ce41eaacc0ba82193fbfdff79adfaef8075e96c25": 30, + "b0a969ed896461627b2d580817d6b723ffdd5b57eff345d2ff0dcf1f": 31, + "811e1c4eb5acb7088b384601455e118c0501da7e579807ed6f1042cb": 32, + "ebe0eb00bf42788a8cd370e5260166436500a39210f516b5d9ebdc8b": 33, + "e534f8d3582a76a57f5c28003af6cdf1c3bcd2a977e27c3999f1f9f3": 34, + "73efe21dcd67fb690069dfbd0430002c993fe43d775ed60166b93b5b": 35, + "4e70a101cb06c0005072c580645652ff9d5e47f717004ccaba9582ad": 36, + "500385315cff8eb2e06b532fadaaa001928b08049da30ce6ff93b769": 37, + "d7fffa2e2dd3efe4ae003687be09ff11a53cff8c9d36d4130cb4b5fc": 38, + "3b96fb5124d0df7e90440fa9ff5501bfab014b31db694c01b03ddbbe": 39, + "c140043b5a723fe346f700bf44f4b40a8c4f324dbe17b5ac01e3b45e": 40, + "db5640fd0014ee5c43b3498573454314eeeb89ffbf001f5724e3ff1b": 41, + "90d495ff0101155eb09ed7ebb1760016b444316a202eff090b82a0c9": 42, + "58ffff4bd20fc2838e882341014f43f2b5a22934207a2c4f7a57e409": 43, + "ff49416cbb7e6309ef340b8702deb8cf8d77019b1cc62cf76c3b426e": 44, + "a82c016500d5ffb7845300684039d9628352c61d6f45d7c1d0d58941": 45, + "a69cf8710100aba9476c695c78fcbdcb9b89a84e0191695d2a5dcc47": 46, + "0a754dfc0100b004638dbd846abe3ec155c82b0f8dee5f5e6c00d9ff": 47, + "f3aacec30955e959ce047c75ffdea67b77d8b9fffab3aa01ffde0dcb": 48, + "443e7518ab3045e4ff88cb23c4a334d997747e24a64e9ea34f247505": 49, + "6733785fcc6301e8e58df40e90ed37ee1ef53cb05179007300d31564": 50, + "1ecb00477c12ef001979a45884f823fac7c2572527fa810c9e0e7aea": 51, + "01aa219b77ffff26b05caa247625319fc2673c3bfadb0136dc14a604": 52, + "9d8a72e60df13e75575c00947192a3d493014bf5ce4f4f00a000370b": 53, + "75d82022f485bcedb6b38c3a01016bd7050fdc9edc2f7fbaa09a40bc": 54, + "d252010f5c7a970bb92626da9c8e2280921e997c83ee499148bb5101": 55, + "9adf3ed0bcc79e9b2f9781e209fdd604132868b5ba9e7bb26cff6c5b": 56, + "2b9199fadb0a912d78b7010e12ff6298aec7000d000462b5b061e4f1": 57, + "b839bc8cf9f25702013e019bd0d3318c380097e105f9915b653192cb": 58, + "2b6edf630000468d9dc8f60fffcb9281957c01f6dc945a6a764689f1": 59, + "bb2ada9401fb84d1a407a8d6a95dfad6a4f30f40bc74ff6fe85a1f77": 60, + "edcd00b778815439000046ac0036806b2e49e5208b97f2373eefa399": 61, + "1aaa13bf4f51da2001ac5d0059d846cbdce7ebb95718c9a3dc4b8313": 62, + "aac1f0971050401758f1597d75d7ff429661fd80f2ae7efddcff2b44": 63, + "2c807bb6431eba01bafdccb7ed9724ffe4a4ff9b60e0499b0e66c82e": 64, + "00b76527d1f8f75a1c8bc8d47737a1456f39e900f92ddc360a44035d": 65, + "caffe8edb5c9fa60ff0101934957014a489501b91b1785f9bfc07b91": 66, + "c3d75a8b44f1bca7dfdd77a7bd837e5c3fca45ffcdffaff54f950023": 67, + "3d38fc9218cc6ee9a2bbff011ed815fcb156d14211c11599be5ba788": 68, + "01503a091fdf01a1fe93bb8cd54e858e11efcb3062ffc08fe5551c61": 69, + "a9b7d4687b76cfaaf5336bd1b8056a9801d64d472e62ff46d03128a0": 70, + "ffe264724801dbe3c900838152e884435c57e51a8ef9a701028a3969": 71, + "cf8a0ae2ff175c5dddb4b3f82bde410a62e6284c3eb8b0c95a001e77": 72, + "4e01238c2f8994651251c1f3009331e9e38602f0b3c6ff5c78ff86eb": 73, + "30010fecff2471437901d979b0949fdf0029888ca985041359957bfe": 74, + "0083ff8b27e6e9ff193bab599900507f97627b3696e5cd0101a65a12": 75, + "6b5ecc2d7900f8732d944bff0031f84a59dc015bffd801a0bc3a01d1": 76, + "207309e066ffaf681d01ec627f6b26eb017401568fe788b73101e470": 77, + "001cb6d54c00316a5bdc38c25dec0002805d01275300c8556ebcc3f0": 78, + "531fd03400ff89603a49cf1292c85bdd014aac24b4b06cac87bb35cc": 79, + "ee0e8f64875d4573c839c801a2ff844d822f0c0a8583efabe800bee8": 80, + "5756abb4f757ff01cb5b72028c01f5b9c38acefd6a6e13fd58019595": 81, + "4d01b0bcc8c8006300a331d71fe30b1e4d25bf9977015fdaac58e496": 82, + "1af27c3f21e7009c0aac1f653901718fc24f46c80b1001086ebbc000": 83, + "dc9000e798a70e9582018179fb1dedf3ff439929b54f01c62300ba34": 84, + "335701a8a706df54592e06d0b0a6303f86c8a0cf45160f4233063a23": 85, + "ff435e46f95900e77300c809c9acbee7be000a177d27af8e5677c1d0": 86, + "80e59d860f7af300c8fa0a0085381e98ffc016c55201f4e07903c5b5": 87, + "0101caf77c2455864e765290680006008ea882ff7119336178d570d6": 88, + "b0c6e4ac306cee2a93d0883901ff024056298dbfe3caff01651a5f40": 89, + "01c7fff54615ff017aeefa017a436f01018062949601e3c1e17361d3": 90, + "86341e8327010418ec386fc2472b68faeabd07e601ca3c4a9c60dcd9": 91, + "ba50ebe1716c7bc601769781f4d001f60f2b01a500637393d5d71c1e": 92, + "0563d9b90133ff2a61b0db2d654cbd8b34845d4550582f3799c047b4": 93, + "000d0069f82085e3f6239016124290523a41a616a849bab01f098a39": 94, + "265f28934f7ebd02014e9e301300ff940490a806f824a063f4dd0006": 95, + "02a542e643ed8181ff0094d801520de5de6eab31e93af8b98ab4ab16": 96, + "f4a9c6af00ff7f38fca05fbb30c612201d0747e85d46c9c739feec61": 97, + "fa44e912ce3e0138976ccd75a43ed91b005a2b81382a55fe30457001": 98, + "0f45ccc70009f917c37ce778ffd15e6feb94301501ae61470013ab95": 99, + "ae763c00f5eb985709e87c7052eefcef8c6e2e00053ae43dbbadde23": 100, + "0e49da3e75c8da00da48bf4f44ad6a6773d12ea6e2fbd06ac4708498": 101, + "7da0a5ff9b13a7fb37f086c7ffff23eb6e9f22ecb737cf847ed0f54f": 102, + "7adca68c6dae1a9ffd8e750e365724ff1973e8000bc5d414be96ddd8": 103, + "b4795c951bdc6eb4b4fff0e7fb58a0ed66019b6feeb3d672f5d5fe4c": 104, + "355d06bb68721a3f32012d7e01cdce721fff534b64daac0cf4c81154": 105, + "c31868f06551db9fb49efa008c986ad74f016e5e7b16ebfa974f2638": 106, + "f9b715e13a124f5020a11f2dca0100a701fbc2d464717ea7cbff563e": 107, + "8017be2c179b0b006b994cc3e360ff4a01855bffccff265f15dec969": 108, + "d97dec3dced0d17cff6e28ff0164475d66d4ff408bef00b06dd47514": 109, + "18daffe3f32a85016a9d3b191ff243bfa00126d57bf7c96ce3740160": 110, + "637ea1112b52012500377800fcbe2d630d49f7d112cfd811062c0001": 111, + "d4559298046806732261ff121033bfcad7727f2e3fcd19fa7d92f221": 112, + "3963d9c18518b0ed37d5e2f13a761f42ca42834400351d07ab006e88": 113, + "f8095955680192bd0b5c1fd1fb097e77d4009a3528aa1da5b03f01bb": 114, + "0de3e268b7a37d6be7858651fcb0e4471aaa004d107101be225b52b2": 115, + "20ecc3af4979fdd8182e70d95fff01a000010b269a07e6eb4b2110a5": 116, + "93a2f528ea475c20765525ae00370aeea70db47dff0d80516f25b5a5": 117, + "99ed0aeb8fecf0ff4f005bb2e314bd82009a306da0b5d2005af20e2d": 118, + "a7000014ff9c2d4d01ff650cb658a2ab68de94017955252b452cbb67": 119, + "478d8eed13452a013ed5cacffadcca01b0515c1de41e956a4c008200": 120, + "7888ffdcd24591bbcd6435a6e45b2927070a691221c710d04c4a1462": 121, + "ff25ff7affffe9ffb4acff8830154f6c5ae61cca9900ccf9e9a0b9e1": 122, + "1fcd8559ffd00f01118267bdcf2249918fdeeb0045fb01d8d48e3c61": 123, + "54e2bbe6cb0118b4f300e499cdb1d56b7c01e0ff23ae3bb1c98180b9": 124, + "f835c4cf5356f604e3d50f321d3c2b41248f700103762400479d59fc": 125, + "82e98351e11d9cad3affca45e6348fdb1f81055d880013b709be427e": 126, + "bc2e1db28d542ac322050c271aef6c419effaceb9118c59c343ec4e5": 127, + "ccd3b6592176ff005baf7e6fd5eaa0853f219b3f0dbeefe8c801ebc2": 128, + "d8fff0ffe3ff7fd67e59ae471725c4e2af3e6c56f00d012f52620104": 129, + "ec3a4f922b9fecda5bd4746503ff085b6e9f322838644adbb950dffc": 130, + "22277d0101e05d42a527f33785e6b08631c02dcf3de70ba550f5c9ff": 131, + "59006160937cac095bbc4ce7a96a8f3fe5527659008f95b4eefba797": 132, + "71b1a01a93ff410e7bf4391404ce537f5d0d19ba967dc2eec8a636b7": 133, + "98b59653e3bf4859d49bafc5e540abeb8d2339c56232b837ffb833b4": 134, + "ff25ff50f09bee01f10058ffdc596d91dee5f60005b9b3ffa951f879": 135, + "37c401bad06f05d2ec9a77da395b3156372600ffee4f511b6864d02a": 136, + "39010395d8f5e16969d721797bf846618a525e32015b23c7c60176ea": 137, + "49f07c93777ce5720c35c21a48cadb9cfde005015033f3f5b4cb2b88": 138, + "8300897d3c7d6e9a2907335fce58eef00009f22a56e500f46201392f": 139, + "95a79da700235961ff73dc78ff5b6d2401177e79b5b2c6dbef711484": 140, + "c3838dcbac6ee5c64999c3160517199db36edf0a00ffaac9adf22798": 141, + "ff9314d1834e2506f701fcdaeca726b512ff518bc31b8c1136e4c4eb": 142, + "ffcae231011c0d6bdd7546303abb2f1ca253e6f2a9f980008afcafff": 143, + "00b9364927d3ff64bdb81559031af0a79793de4dd18a2004524316cd": 144, + "00b9bd6144ff16889ff63ce92f500e0144e1f3578f34220090c9f8ab": 145, + "01511e82363771cc1101c4409bde01c2ff2d692f58a88eae37cd187a": 146, + "01752201757f364caac686e9e34d20896999bb6064003b1494726af5": 147, + "017e5ea1a154748a90be98ecd0436cac89776ca200029eff3a95de2c": 148, + "10a8c8ec01ed04434f7a463e14ff0a994296e97718a822c46baab88c": 149, + "113e737d14c51476cb5db565ef179759e99a1d44999084108f2866d8": 150, + "13717c14c3207e44d578edff0bf76a2c4e6930990e35a0009970010f": 151, + "14ffc31bfb0110002db1187f26b16bd3482d9fafff519fb0d3b2a38d": 152, + "20dba91387599e09d5606b6aefec981d0f5b0fed8501b579cb8e848f": 153, + "34e8478201ffbeb0ab04fe41eabb095d36ed6e0087340fe617d09955": 154, + "3a06013f8b1a6691aa4d9a2c952b89ce112616774ccd9e24422f09a4": 155, + "3a6e005c411e8ef0c3a2852aec01890dd7ce00bc7e8400b983477701": 156, + "3d007bd3a16b52cb06e5701d5b4466a5007619ccbd19871c77eaf57b": 157, + "50446c4f26ffaf6598c783410bcdde5905354efe00fce6f7375301ff": 158, + "522a8d515c38e3b9fffc8dcb25016b018ebaf1010164012024e0a338": 159, + "52aa09ff004fdd8fc3f149163e7d79257aaf5d4fc3cff7576d029c7b": 160, + "5300231cfdec3f47e44411fc8ebd00f924f45f5420a44d42ffff4681": 161, + "596ebe0738ca1b90cbcbd10a0b804ac20001f94e6474ff384229c14c": 162, + "5abdb5007572b254df05579124c6ff921b0051405af73ea7aba38220": 163, + "5db101e507115c40e4468201231453ad67e1ef7d33ef52e8f601e246": 164, + "5e8b326e867efc6e6af10f22029ed62e10d86bd2ab252f280d1a4f48": 165, + "5ef19efeff7c483e5643b481e40d8af0f6f07bcfffbddd378a6e5407": 166, + "639a0e1530c500d14d4d849c3c95907957896ba98b125e3f00ffd631": 167, + "653debf312a13afba13f2cf426c101292000de0a090ea91e0d3d39dd": 168, + "6c00017cc07aada307933f15167a869f11a101c21deee345d8bf3f25": 169, + "6d1a598dcca7d4c324d50c0940016067507622b6a485252aa60c6969": 170, + "71aace9f096f1101d3fd3b005694ff6242461ccb710ada3800429d4a": 171, + "7500290ce900ff55e0ead88900fcae3b6130aabdce1c82251b864abc": 172, + "7a5b49558d006748d492ec8d7b5a4c3150f101032e05f601b292b4cf": 173, + "7da96f49fff07a7c1e3a000150fe00209b1dff4b7bafecb9d2ce0459": 174, + "7f00a078e500d7d25c47be2cde9531d28b16fcf2e33919fcd9e44901": 175, + "8a8aa178e900bea547c0d05b2aebc95545476a1a1ddf508defec68fa": 176, + "925cff8cc10114e6b4bb6b644ab1515ad1d5f7a14acace906001d9b9": 177, + "9629f17dafd13dd2ff50a10e5875c22b657cd97cff53ff2a2d18a14a": 178, + "9a15ba62b8db62910e9c3912d1343a91437ab701ff24b9656deea4ae": 179, + "9a453c8df82bffc7d689000bdc7ef50d6de0ae20c601be6ed34f7150": 180, + "9a780054ff9f0d133a21e1830af5944b2168f7577790eedb3d01752d": 181, + "9c67c57acd01a8e832000d00184438b80f9342b162c401c1a1be12e3": 182, + "a05bff2f497264b839ab77016b7cff8b371a36ccb643ff0500b629d9": 183, + "a1d4f80d5a0c3b0df1c53100d60000e6d3852a4d4d2d7d843c22a437": 184, + "a1e427086d013db41e1c5097ef6c60c76531a5313e29c037f49757eb": 185, + "a4e29acfb9642ee43438b0513c2a96ef4def8262d069c3acd2462501": 186, + "afe159d207188718330fff49e3ffa0d27b834cb49b6bf656ff8f0154": 187, + "bc5cbcfb2fd1ecbebf89daa69c579108a91b13ff95ae8946ff290340": 188, + "c2b4ccea0d400dee7e42defe8a983648db60ff54103f6871fc07f5e3": 189, + "cb80746a9eb1765bb7490080a3012d0e010936c16ea6923c551e3b72": 190, + "d6dcab7100bf65a073e5003e0cf162cfc22bdbd6c971cb70ee0b3ca7": 191, + "e0e9010efff096cb8e8dd7cc6d01dbf70c362755da9618f63f26e235": 192, + "ee7b6eff6f754401ffc74f6d14a6087eebe1a80100ff8dbf60dd9ca5": 193, + "f42f010142a697076ea5f42296b10f45cdca1087df799ca8ef8a195c": 194, + "fecb57048d31890103990628480100b05211e497adc8dd2ddd5af14f": 195, + "ff03ff27010810b9c9fd4006d59eb200ae937ce98ef499967cb80100": 196, + "ff14b167c2f0d174457ac4bda2b2c91a18b77ea16232cd0001de80f2": 197, + "ff330da5a8f6da32801d8f923d07ff6ec2640157fc483f34f1a483e7": 198 + }, + "committee_index_by_pool_id": { + "297d513e8a16fface8ffb11d535e89d0f4ca996ece5a8c2dd7395372": 1, + "e7ca79019900d2ff1139d3fae05eff7fc474019386f101ffc7a413d1": 2, + "510000bd0d89608c5f480100db9abac7ad2c011e8891d8603704d8f1": 3, + "f640a7267abc27ff5399b59b783195003b59bd699c7de7b1a94b90e7": 4, + "2bffe13687b1d07dd6a5a8c0e4f64f7c565b6b07634bde1cf5cde490": 5, + "4014004f403600cc7835b95fa6597a1daf75ee91d2f8a489c4cc4621": 6, + "0caaf065c5e8e01e9c9b01ffee6d5d28166daf9e9f013e7c59f5eea0": 7, + "2e3fff190094ec4b1e555381f10132cd8503bcfab77ac69f31061f80": 8, + "be7f491f0142d68fbe5db88f81f3611a003e1860df76ff4c993bff55": 9, + "80576f0db3dd223800c04d529cec22a7a4e95685fb44b3248579d129": 10, + "6b33e699e62bb5e9a253e94001b2e0008928af07480180efacf19eff": 11, + "1fac9270dce82a31acdb0044ea2bc33001bccfa733a8343ab508f8db": 12, + "010000ea011f3b5f560000c3b7ff09099b00cee6c904ff8db5be05af": 13, + "ced712e76857ae0301394b9f80d9883aa915438f3a634948e561cb1c": 14, + "68ff8120d80176efacffac2b21455d940a19c1c9d7015715015a2ac1": 15, + "8ed5d017d237129da3415d93c29bf235b802fa0d1b326d0e00943e7d": 16, + "f1ffb84f6cd5961467690750815dc6fe4528e47c10b5029c62aa017d": 17, + "f9ffdfc4cfd3aeb25b759621d4dad6c8cbc6d330d3e865f6e2ccdccd": 18, + "2859011646cb69e9af2b00f8a6e99a5e98c315c30d3e0950e2f936d6": 19, + "ba2e466a8e04eae7d9601b280bff7730395b62f753fc4dc9daac6551": 20, + "55077dbd0017ac00133d4d451fee91ffd732ae0f0028ee91d1f4fe25": 21, + "71e9fdd001726ccdce084db3f5d19175c88d7a6ffe72fb7bc7ff9fca": 22, + "ff017f87e40030d332e3a201790cb4d191f77cff402359afbfc3f133": 23, + "2c01ff0bfee051d6f9a0542f83d86f8ace9400a0bbae83125124ad6a": 24, + "a6f18a62cd970f4101011eff07f2ab0924c682d95701a591b2703900": 25, + "01b517a3397cf1f2fcffb3de23e53b356cff3000ff8175ff15ce46b5": 26, + "2a0197eb60069d9b9674b36d01c05633c7fd9fd9ff62ac440101e1b4": 27, + "ab65112ee36236dfb0cbe04901dd8664aa53e14e3a2201e8ba43010f": 28, + "42ff05ff217a522a4d0157b811f728ee479ddeeeb6bb791fff00adb4": 29, + "4a0814a0d39aff838261015cd7696a86d5bc4893ff334d9011f05dff": 30, + "19168e8ba075a44ce41eaacc0ba82193fbfdff79adfaef8075e96c25": 31, + "b0a969ed896461627b2d580817d6b723ffdd5b57eff345d2ff0dcf1f": 32, + "811e1c4eb5acb7088b384601455e118c0501da7e579807ed6f1042cb": 33, + "ebe0eb00bf42788a8cd370e5260166436500a39210f516b5d9ebdc8b": 34, + "e534f8d3582a76a57f5c28003af6cdf1c3bcd2a977e27c3999f1f9f3": 35, + "73efe21dcd67fb690069dfbd0430002c993fe43d775ed60166b93b5b": 36, + "4e70a101cb06c0005072c580645652ff9d5e47f717004ccaba9582ad": 37, + "500385315cff8eb2e06b532fadaaa001928b08049da30ce6ff93b769": 38, + "d7fffa2e2dd3efe4ae003687be09ff11a53cff8c9d36d4130cb4b5fc": 39, + "3b96fb5124d0df7e90440fa9ff5501bfab014b31db694c01b03ddbbe": 40, + "c140043b5a723fe346f700bf44f4b40a8c4f324dbe17b5ac01e3b45e": 41, + "db5640fd0014ee5c43b3498573454314eeeb89ffbf001f5724e3ff1b": 42, + "90d495ff0101155eb09ed7ebb1760016b444316a202eff090b82a0c9": 43, + "58ffff4bd20fc2838e882341014f43f2b5a22934207a2c4f7a57e409": 44, + "ff49416cbb7e6309ef340b8702deb8cf8d77019b1cc62cf76c3b426e": 45, + "a82c016500d5ffb7845300684039d9628352c61d6f45d7c1d0d58941": 46, + "a69cf8710100aba9476c695c78fcbdcb9b89a84e0191695d2a5dcc47": 47, + "0a754dfc0100b004638dbd846abe3ec155c82b0f8dee5f5e6c00d9ff": 48, + "f3aacec30955e959ce047c75ffdea67b77d8b9fffab3aa01ffde0dcb": 49, + "443e7518ab3045e4ff88cb23c4a334d997747e24a64e9ea34f247505": 50, + "6733785fcc6301e8e58df40e90ed37ee1ef53cb05179007300d31564": 51, + "1ecb00477c12ef001979a45884f823fac7c2572527fa810c9e0e7aea": 52, + "01aa219b77ffff26b05caa247625319fc2673c3bfadb0136dc14a604": 53, + "9d8a72e60df13e75575c00947192a3d493014bf5ce4f4f00a000370b": 54, + "75d82022f485bcedb6b38c3a01016bd7050fdc9edc2f7fbaa09a40bc": 55, + "d252010f5c7a970bb92626da9c8e2280921e997c83ee499148bb5101": 56, + "9adf3ed0bcc79e9b2f9781e209fdd604132868b5ba9e7bb26cff6c5b": 57, + "2b9199fadb0a912d78b7010e12ff6298aec7000d000462b5b061e4f1": 58, + "b839bc8cf9f25702013e019bd0d3318c380097e105f9915b653192cb": 59, + "2b6edf630000468d9dc8f60fffcb9281957c01f6dc945a6a764689f1": 60, + "bb2ada9401fb84d1a407a8d6a95dfad6a4f30f40bc74ff6fe85a1f77": 61, + "edcd00b778815439000046ac0036806b2e49e5208b97f2373eefa399": 62, + "1aaa13bf4f51da2001ac5d0059d846cbdce7ebb95718c9a3dc4b8313": 63, + "aac1f0971050401758f1597d75d7ff429661fd80f2ae7efddcff2b44": 64 + } + }, + "voters": { + "persistent_ids": [ + 52, + 6, + 30, + 51, + 11, + 26, + 4, + 5, + 28, + 49, + 29, + 36, + 37, + 43, + 50, + 14, + 10, + 21, + 35, + 54, + 15, + 53, + 24, + 45, + 27, + 31, + 8, + 40, + 13, + 41, + 34, + 1, + 33, + 16, + 48, + 17, + 22 + ], + "nonpersistent_pool_ids": [ + "001cb6d54c00316a5bdc38c25dec0002805d01275300c8556ebcc3f0", + "00b76527d1f8f75a1c8bc8d47737a1456f39e900f92ddc360a44035d", + "0101caf77c2455864e765290680006008ea882ff7119336178d570d6", + "01503a091fdf01a1fe93bb8cd54e858e11efcb3062ffc08fe5551c61", + "1aaa13bf4f51da2001ac5d0059d846cbdce7ebb95718c9a3dc4b8313", + "2b6edf630000468d9dc8f60fffcb9281957c01f6dc945a6a764689f1", + "2b9199fadb0a912d78b7010e12ff6298aec7000d000462b5b061e4f1", + "2c807bb6431eba01bafdccb7ed9724ffe4a4ff9b60e0499b0e66c82e", + "30010fecff2471437901d979b0949fdf0029888ca985041359957bfe", + "4d01b0bcc8c8006300a331d71fe30b1e4d25bf9977015fdaac58e496", + "4e01238c2f8994651251c1f3009331e9e38602f0b3c6ff5c78ff86eb", + "531fd03400ff89603a49cf1292c85bdd014aac24b4b06cac87bb35cc", + "9adf3ed0bcc79e9b2f9781e209fdd604132868b5ba9e7bb26cff6c5b", + "aac1f0971050401758f1597d75d7ff429661fd80f2ae7efddcff2b44", + "b0c6e4ac306cee2a93d0883901ff024056298dbfe3caff01651a5f40", + "b839bc8cf9f25702013e019bd0d3318c380097e105f9915b653192cb", + "c3d75a8b44f1bca7dfdd77a7bd837e5c3fca45ffcdffaff54f950023", + "caffe8edb5c9fa60ff0101934957014a489501b91b1785f9bfc07b91", + "d252010f5c7a970bb92626da9c8e2280921e997c83ee499148bb5101", + "edcd00b778815439000046ac0036806b2e49e5208b97f2373eefa399", + "f4a9c6af00ff7f38fca05fbb30c612201d0747e85d46c9c739feec61", + "ffe264724801dbe3c900838152e884435c57e51a8ef9a701028a3969" + ] + }, + "certificate": { + "sigma_tilde_eid_prefix": "906b66a8602c760e", + "sigma_tilde_m_prefix": "85b692b4e7a131ae", + "eid": "0x64dbf8afc7c8a40c", + "eb": "0xcf06b28637f7696c82e5015d8f687b2b7700dccbfa06fe370027dd4bff21a974", + "persistent_voters_count": 37, + "nonpersistent_voters_count": 22, + "votes_bytes": 10380, + "certificate_bytes": 2659, + "compression_ratio": 3.904 + } +} \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh b/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh new file mode 100755 index 000000000..510957c16 --- /dev/null +++ b/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +set -euo pipefail +DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUN_DIR="" + +while [[ $# -gt 0 ]]; do + case "$1" in + -d) RUN_DIR="$2"; shift 2;; + *) echo "Usage: $0 -d RUN_DIR"; exit 2;; + esac +done + +if [[ -z "$RUN_DIR" ]]; then + echo "need -d RUN_DIR" + exit 2 +fi + +# ✅ Quote paths with spaces +RUN_DIR="$(cd "$DIR_SCRIPT/.." && cd "$RUN_DIR" && pwd)" +PY="${VIRTUAL_ENV:+$VIRTUAL_ENV/bin/python}" +PY="${PY:-python3}" + +pushd "$RUN_DIR" >/dev/null + "$PY" - <<'PY' +import os, json +try: + import cbor2 +except ImportError: + raise SystemExit("cbor2 not installed (pip install cbor2)") + +def must(path): + if not os.path.exists(path): + raise SystemExit(f"missing {path}") + return path + +# --- Load registry (required) --- +reg = cbor2.load(open(must("registry.cbor"), "rb")) +info = reg.get("info", {}) or {} +persistent_pool = reg.get("persistent_pool", {}) or {} +persistent_set = set(persistent_pool.values()) +total_stake = reg.get("total_stake") +N = reg.get("voters", 0) + +# --- Universe (all pools) --- +# Each entry now has: pool_id, stake, is_persistent (for easy coloring on UI) +universe = [ + {"pool_id": pid, "stake": rec.get("stake", 0), "is_persistent": pid in persistent_set} + for pid, rec in info.items() +] +universe.sort(key=lambda x: x["stake"], reverse=True) + +# --- Committee (top-N by stake, same as backend endpoint) --- +committee = [ + {"index": i + 1, "pool_id": universe[i]["pool_id"], "stake": universe[i]["stake"]} + for i in range(min(N, len(universe))) +] + +# --- Lookup maps for fast UI highlighting / tooltips --- +universe_index_by_pool_id = {entry["pool_id"]: idx for idx, entry in enumerate(universe)} +committee_index_by_pool_id = {entry["pool_id"]: entry["index"] for entry in committee} + +# --- Voters (optional; present when votes.cbor exists) --- +voters_persistent = [] +voters_nonpersistent = [] +if os.path.exists("votes.cbor"): + votes = cbor2.load(open("votes.cbor", "rb")) + for v in votes: + if "Persistent" in v: + pid = v["Persistent"]["persistent"] + if isinstance(pid, int): + voters_persistent.append(pid) + elif "Nonpersistent" in v: + pool = v["Nonpersistent"]["pool"] + if isinstance(pool, str): + voters_nonpersistent.append(pool) + +# --- Certificate summary (optional) --- +cert_summary = {} +votes_bytes = os.path.getsize("votes.cbor") if os.path.exists("votes.cbor") else None +certificate_bytes = os.path.getsize("certificate.cbor") if os.path.exists("certificate.cbor") else None + +if os.path.exists("certificate.cbor"): + cert = cbor2.load(open("certificate.cbor", "rb")) + pv = cert.get("persistent_voters", []) or [] + npv = cert.get("nonpersistent_voters") or {} + cert_summary = { + "sigma_tilde_eid_prefix": cert["sigma_tilde_eid"][:8].hex(), + "sigma_tilde_m_prefix": cert["sigma_tilde_m"][:8].hex(), + "eid": "0x" + cert["eid"].hex(), + "eb": "0x" + cert["eb"].hex(), + "persistent_voters_count": len(pv), + "nonpersistent_voters_count": len(npv.keys()), + } + +if votes_bytes is not None: + cert_summary["votes_bytes"] = votes_bytes +if certificate_bytes is not None: + cert_summary["certificate_bytes"] = certificate_bytes + if votes_bytes: + cert_summary["compression_ratio"] = round(votes_bytes / certificate_bytes, 3) + +# --- Params (handy for the UI header) --- +params = { + "N": N, + "pool_count": len(universe), + "total_stake": total_stake, +} + +out = { + "params": params, + "universe": universe, + "persistent_map": {int(k): v for k, v in persistent_pool.items()}, + "committee": committee, + "lookup": { + "universe_index_by_pool_id": universe_index_by_pool_id, + "committee_index_by_pool_id": committee_index_by_pool_id, + }, + "voters": { + "persistent_ids": voters_persistent, + "nonpersistent_pool_ids": voters_nonpersistent, + }, + "certificate": cert_summary, +} + +json.dump(out, open("demo.json", "w"), indent=2) +print("Wrote demo.json") +PY +popd >/dev/null \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/server.py b/crypto-benchmarks.rs/demo/ui/server.py index fb87bd348..753cbf5d6 100644 --- a/crypto-benchmarks.rs/demo/ui/server.py +++ b/crypto-benchmarks.rs/demo/ui/server.py @@ -1,9 +1,7 @@ -from flask import Flask, send_from_directory, jsonify, render_template, request, abort +from flask import Flask, send_from_directory, jsonify, render_template, request, abort, redirect, url_for import os, json, subprocess import cbor2 -app = Flask(__name__, static_folder="static", template_folder="templates") - app = Flask(__name__, static_folder="static", template_folder="templates") ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) @@ -81,6 +79,15 @@ def registry(run): "total_stake": total_stake }) +@app.route("/demo/") +def demo_for_run(run): + """Serve demo.json from the given run directory.""" + run_dir = run_dir_path(run) + demo_path = os.path.join(run_dir, "demo.json") + if os.path.isfile(demo_path): + return send_from_directory(run_dir, "demo.json") + abort(404, f"demo.json not found in {run_dir}") + @app.route("/votes/") def votes(run): """Expose elected voter IDs (first pass: read from certificate.pretty.json).""" diff --git a/crypto-benchmarks.rs/demo/ui/static/app.js b/crypto-benchmarks.rs/demo/ui/static/app.js index 97f723b06..399ec9eae 100644 --- a/crypto-benchmarks.rs/demo/ui/static/app.js +++ b/crypto-benchmarks.rs/demo/ui/static/app.js @@ -1,223 +1,280 @@ -// ---- utilities ------------------------------------------------------------- +// Minimal, robust renderer for the SPO "universe" panel. +// - Tries /demo/ (default run64), then /static/demo.json, then /demo.json +// - If found, renders all pools as circles and fills counts + identifiers +// - If not found, renders a small placeholder message -async function fetchJSON(url) { - const r = await fetch(url); - if (!r.ok) throw new Error(`${url} -> HTTP ${r.status}`); - return r.json(); -} -const $ = (id) => document.getElementById(id); +// ---------- tiny helpers ---------- +const $ = (sel) => document.querySelector(sel); -function setText(id, txt) { - const el = $(id); - if (el) el.textContent = txt ?? "—"; +function setText(id, value) { + const el = document.getElementById(id); + if (el) el.textContent = value ?? "—"; } -function setHidden(id, hidden) { - const el = $(id); - if (el) el.hidden = !!hidden; +function shortenHex(hex) { + if (typeof hex !== "string") return ""; + const h = hex.startsWith("0x") ? hex.slice(2) : hex; + if (h.length <= 12) return "0x" + h; + return "0x" + h.slice(0, 6) + "…" + h.slice(-4); } -function showError(msg) { - const banner = $("error_banner"); - const text = $("error_text"); - if (banner) banner.hidden = !msg; - if (text) text.textContent = msg || ""; -} +// ---------- CSS bootstrap (minimal positioning only) ---------- +function ensureUniverseStyles() { + if (document.getElementById("universe-inline-css")) return; + const style = document.createElement("style"); + style.id = "universe-inline-css"; + style.textContent = ` + :root { + --dot-size: 30px; /* diameter of each circle */ + --dot-gap: 5px; /* space between circles */ + } -function clearError() { - showError(""); -} + #universe_canvas { + min-height: calc(var(--dot-size) * 6); + position: relative; + } -// Build a
  • text
  • and replace list content -function fillList(ulId, items) { - const ul = $(ulId); - if (!ul) return; - ul.innerHTML = ""; - items.forEach((t) => { - const li = document.createElement("li"); - li.textContent = t; - ul.appendChild(li); - }); -} + /* Grid that lays out the circles */ + .universe-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(var(--dot-size), var(--dot-size))); + grid-auto-rows: var(--dot-size); + gap: var(--dot-gap) var(--dot-gap); + align-content: start; + justify-content: start; + padding-top: 8px; + } -function shortenHex(hex, head = 14, tail = 14) { - if (!hex || typeof hex !== "string") return hex; - const s = hex.trim(); - if (s.length <= head + tail) return s; - return s.slice(0, head) + "…" + s.slice(-tail); -} + /* Ensure each dot consumes the whole grid cell and is a perfect circle */ + .pool-dot { + width: var(--dot-size); + height: var(--dot-size); + border-radius: 50%; + position: relative; /* hosts the centered numeric label */ + display: inline-block; + } -function formatBytes(n) { - if (typeof n !== "number") return n ?? "—"; - return n.toLocaleString() + " B"; + /* Numeric label inside each dot */ + .pool-dot .node-label { + position: absolute; + top: 50%; + left: 0; + width: 100%; + transform: translateY(-50%); + text-align: center; + font-weight: 700; + color: #ffffff; /* white text for contrast */ + text-shadow: 0 1px 2px rgba(0,0,0,0.4); /* subtle halo for readability */ + pointer-events: none; + user-select: none; + } + .tooltip-box { + position: absolute; + background: rgba(30, 30, 30, 0.9); + color: #fff; + padding: 6px 8px; + border-radius: 6px; + font-size: 12px; + line-height: 1.4; + pointer-events: none; + z-index: 1000; + box-shadow: 0 2px 6px rgba(0,0,0,0.3); + } + `; + document.head.appendChild(style); } -// Build a …… -function tr(cells) { - const tr = document.createElement("tr"); - cells.forEach((v) => { - const td = document.createElement("td"); - td.textContent = v; - tr.appendChild(td); - }); - return tr; -} +// ---------- main render ---------- +function renderUniverse(universe) { + ensureUniverseStyles(); -// Replace tbody contents -function fillTbody(tbodyId, rows) { - const tb = $(tbodyId); - if (!tb) return; - tb.innerHTML = ""; - rows.forEach((cells) => tb.appendChild(tr(cells))); -} + let pools = []; + if (Array.isArray(universe)) { + pools = universe; + } else if (universe && typeof universe === "object" && Array.isArray(universe.pools)) { + pools = universe.pools; + } -// Render persistent committee (if present) -function renderPersistent(committee) { - const pcs = committee?.persistent_committee ?? []; - const rows = pcs.map((e, i) => { - // each entry: { persistent_id, pool_id, stake } - const pid = (typeof e === "object" ? e.persistent_id : undefined); - const pool = (typeof e === "object" ? e.pool_id : e); - const stake = (typeof e === "object" ? e.stake : undefined); - return [String(i + 1), (pid ?? "—"), shortenHex(pool ?? ""), stake ?? ""]; + const total = pools.length; + let persistentCount = 0; + let nonPersistentCount = 0; + + pools.forEach((pool) => { + const isPersistent = + pool.is_persistent === true || + pool.persistent === true || + typeof pool.persistent_id !== "undefined"; + if (isPersistent) persistentCount++; + else nonPersistentCount++; }); - // header count - const count = committee?.persistent_count ?? pcs.length ?? 0; - setText("persistent_title_count", String(count || "0")); - fillTbody("persistent_tbody", rows); - - // note text (shown only if no persistent seats) - const hasPersistent = (count > 0); - setHidden("persistent_note", hasPersistent); -} + setText("universe_total", total || "—"); + setText("universe_persistent", persistentCount || "—"); + setText("universe_nonpersistent", nonPersistentCount || "—"); -// ---- renderers ------------------------------------------------------------- + const container = $("#universe_canvas"); + if (!container) return; -function renderSummary(cert) { - setText("eid", cert?.eid ? shortenHex(cert.eid) : "—"); - const ebEl = $("eb"); - if (ebEl) { - ebEl.textContent = cert?.eb ? shortenHex(cert.eb, 18, 18) : "—"; - ebEl.title = cert?.eb ?? ""; + if (total === 0) { + container.innerHTML = "

    No data available

    "; + return; } - setText("votes_bytes", formatBytes(cert?.votes_bytes)); - setText("certificate_bytes", formatBytes(cert?.certificate_bytes)); - setText("compression_ratio", cert?.compression_ratio ?? "—"); -} -function renderSelectedPools(committee) { - const rows = []; - const pools = committee?.selected_pools ?? []; - - pools.forEach((p, i) => { - // Accept both object and string - if (typeof p === "string") { - rows.push([String(i + 1), shortenHex(p), ""]); - } else { - const pid = p.pool_id || p.id || p.voter_id || ""; - const stake = (p.stake ?? "") === "" ? "" : String(p.stake); - rows.push([String(i + 1), shortenHex(pid), stake]); - } + container.classList.add("universe-grid"); + container.innerHTML = ""; + + pools.forEach((pool) => { + const isPersistent = + pool.is_persistent === true || + pool.persistent === true || + typeof pool.persistent_id !== "undefined"; + const isSelected = pool.is_selected === true || pool.selected === true; + const isElected = pool.is_elected === true || pool.elected === true; + + const div = document.createElement("div"); + div.classList.add("pool-dot"); + if (isPersistent) div.classList.add("is-persistent"); + else div.classList.add("is-nonpersistent"); + if (isSelected) div.classList.add("is-selected"); + if (isElected) div.classList.add("is-elected"); + + div.style.position = "relative"; + + const label = document.createElement("span"); + label.classList.add("node-label"); + + // 1‑based index for the pool number + const idx = pools.indexOf(pool) + 1; + label.textContent = idx; + + // Dynamic font size: slightly smaller for 3‑digit numbers so they fit + label.style.fontSize = (idx >= 100 ? "11px" : "13px"); + div.appendChild(label); + + const poolId = pool.pool_id || pool.id || ""; + const stake = typeof pool.stake !== "undefined" ? pool.stake : ""; + // Use HTML tooltip (bold + spacing) + div.setAttribute("data-tooltip-html", `Pool ID: ${poolId}
    Stake: ${stake}`); + div.title = ""; // fallback plain title cleared + + div.addEventListener("mouseenter", (e) => { + const tooltip = document.createElement("div"); + tooltip.className = "tooltip-box"; + tooltip.innerHTML = div.getAttribute("data-tooltip-html"); + document.body.appendChild(tooltip); + const rect = e.target.getBoundingClientRect(); + tooltip.style.left = rect.left + window.scrollX + "px"; + tooltip.style.top = rect.top + window.scrollY - 40 + "px"; + div._tooltip = tooltip; + }); + div.addEventListener("mouseleave", () => { + if (div._tooltip) { + div._tooltip.remove(); + div._tooltip = null; + } + }); + + container.appendChild(div); }); +} - // header count - const count = committee?.selected_count ?? pools.length ?? 0; - setText("selected_title_count", String(count || "—")); - - fillTbody("selected_tbody", rows); +// ---------- data loading ---------- +function getRunFromURL() { + const p = new URLSearchParams(window.location.search); + const run = p.get("run"); + // default to run64 if not provided + return run && /^run\d+$/i.test(run) ? run : "run64"; } -function renderElected(committee, votesFallback) { - // Prefer enriched committee.elected (objects). Fall back to /votes. - let elected = committee?.elected; - if (!Array.isArray(elected) || elected.length === 0) { - elected = votesFallback?.elected ?? []; +async function tryFetchJson(url) { + try { + const r = await fetch(url, { cache: "no-store" }); + if (!r.ok) return null; + return await r.json(); + } catch { + return null; } +} - const rows = elected.map((e, i) => { - if (typeof e === "string") { - return [String(i + 1), shortenHex(e), ""]; - } else { - const id = e.voter_id || e.id || ""; - const t = e.type || ""; - return [String(i + 1), shortenHex(id), t]; - } - }); +async function loadDemoJson() { + const run = getRunFromURL(); + let data = await tryFetchJson(`/demo/${run}`); + if (!data) data = await tryFetchJson("/static/demo.json"); + if (!data) data = await tryFetchJson("/demo.json"); + return data; +} - const count = - committee?.elected_count ?? - (Array.isArray(elected) ? elected.length : 0); - setText("elected_title_count", String(count || "—")); +function fillIdentifiers(obj) { + if (!obj) return; + const eid = obj.eid || obj.EID; + const eb = obj.eb_hash || obj.eb || obj.EB || obj.ebHash; - fillTbody("elected_tbody", rows); + // Update both legacy ids and the display ids + if (eid) { + setText("eid", eid); + setText("eid_value", eid); + } + if (eb) { + setText("eb", eb); + setText("ebhash_value", eb); + } } -function renderNotes(committee) { - // Note list - const notes = Array.isArray(committee?.notes) ? committee.notes : []; - fillList("notes_ul", notes); - - // If any note looks like an error, surface it. - const err = notes.find((n) => typeof n === "string" && /^error/i.test(n)); - if (err) { - showError(err); +// ---------- boot ---------- +document.addEventListener("DOMContentLoaded", async () => { + setText("universe_total", "—"); + setText("universe_persistent", "—"); + setText("universe_nonpersistent", "—"); + setText("eid", "—"); + setText("eb", "—"); + setText("eid_value", "—"); + setText("ebhash_value", "—"); + + const demo = await loadDemoJson(); + + if (demo) { + const universe = demo.universe || demo; + renderUniverse(universe); + + // Try identifiers from either `identifiers` or `certificate` blocks + fillIdentifiers(demo.identifiers); + fillIdentifiers(demo.certificate); } else { - clearError(); + renderUniverse([]); } -} +}); -function renderVoters(cert) { - // persistent - const pv = cert?.persistent_voters ?? cert?.persistent_voters_sample ?? []; - const pvRows = pv.map((id, i) => [String(i + 1), shortenHex(id)]); - setText("pv_count", String(pv.length || 0)); - fillTbody("pv_tbody", pvRows); - - // non-persistent - const npv = - cert?.nonpersistent_voters ?? - cert?.nonpersistent_voters_sample ?? - []; - const npvRows = npv.map((id, i) => [String(i + 1), shortenHex(id)]); - setText("npv_count", String(npv.length || 0)); - fillTbody("npv_tbody", npvRows); - - // “(sample)” badges - $("pv_note") && ($("pv_note").hidden = Array.isArray(cert?.persistent_voters) && cert.persistent_voters.length > 0); - $("npv_note") && ($("npv_note").hidden = Array.isArray(cert?.nonpersistent_voters) && cert.nonpersistent_voters.length > 0); +// === Middle-ellipsis for long identifiers (keep start and end) === +function shortenMiddle(str, keepStart = 26, keepEnd = 22) { + if (!str || typeof str !== 'string') return str; + if (str.length <= keepStart + keepEnd + 1) return str; + return str.slice(0, keepStart) + '…' + str.slice(-keepEnd); } -// ---- main load ------------------------------------------------------------ +function watchAndEllipsize(id, opts = {}) { + const el = document.getElementById(id); + if (!el) return; -async function load(run) { - try { - // Force-refresh the pretty file (all=1 gives full voters if available) - const [cert, committee, votes] = await Promise.all([ - fetchJSON(`/cert/${encodeURIComponent(run)}?all=1`), - fetchJSON(`/committee/${encodeURIComponent(run)}`), - fetchJSON(`/votes/${encodeURIComponent(run)}`) - ]); - - renderSummary(cert); - renderSelectedPools(committee); - renderElected(committee, votes); - renderVoters(cert); - renderPersistent(committee); - renderNotes(committee); - } catch (err) { - showError(`Failed to load data for ${run}: ${err.message}`); - console.error(err); - } -} + const { keepStart = 26, keepEnd = 22, truncate = true } = opts; -// ---- hooks --------------------------------------------------------------- + const apply = () => { + // Remember the full value once, then only display a truncated version + const full = el.getAttribute('data-full') || el.textContent.trim(); + el.setAttribute('data-full', full); + el.title = full; // hover shows the complete hash + el.classList.add('mono'); // monospace for readability + el.textContent = truncate ? shortenMiddle(full, keepStart, keepEnd) : full; + }; -document.getElementById("run-form").addEventListener("submit", (e) => { - e.preventDefault(); - const run = document.getElementById("run-input").value.trim(); - if (run) load(run); -}); + // Re-apply if someone else overwrites the text + const mo = new MutationObserver(apply); + mo.observe(el, { childList: true, characterData: true, subtree: true }); + + apply(); +} -// initial load -load(document.getElementById("run-input").value.trim() || "run32"); \ No newline at end of file +document.addEventListener('DOMContentLoaded', () => { + // EID: show full; EB hash: show middle-ellipsized so both fit on one line + watchAndEllipsize('eid_value', { truncate: false }); + watchAndEllipsize('ebhash_value', { keepStart: 26, keepEnd: 22, truncate: true }); +}); \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/static/styles.css b/crypto-benchmarks.rs/demo/ui/static/styles.css index b359c6638..2f7894112 100644 --- a/crypto-benchmarks.rs/demo/ui/static/styles.css +++ b/crypto-benchmarks.rs/demo/ui/static/styles.css @@ -5,6 +5,7 @@ --muted: #9fb2c7; --green: #10b981; /* crisp green */ + --orange: #f59e0b; --border: #1f2a35; } @@ -19,6 +20,18 @@ body { font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; } +.count { + font-weight: 700; +} + +.persistent-color { + color: var(--green); +} + +.nonpersistent-color { + color: var(--orange); +} + header { max-width: 1100px; margin: 20px auto; @@ -176,40 +189,121 @@ tbody tr:hover { white-space: nowrap; } -/* Tighten the note under the sample tables */ -.note { - margin-top: 6px; - color: var(--muted); - font-size: 12px; +/* === Universe (Stake Pools) visualization === */ +.universe-canvas { + position: relative; + /* anchor for the tooltip */ + display: grid; + grid-template-columns: repeat(auto-fill, minmax(16px, 1fr)); + grid-auto-rows: 16px; + /* keep rows consistent with dot size */ + gap: 6px; + padding: 8px 0; + align-items: center; + max-width: 700px; + margin-top: 12px; } -/* Wider left column for Committee so 32-pool table has room */ -.grid.two.wide-left { - display: grid; - grid-template-columns: 1.6fr 1.2fr; - gap: 16px 20px; +.pool-dot { + width: 16px; + height: 16px; + border-radius: 50%; + border: 1px solid #999; + background: #f0f0f0; + cursor: help; + transition: transform 0.15s ease, box-shadow 0.15s ease; } -@media (max-width: 1100px) { - .grid.two.wide-left { - grid-template-columns: 1fr; +.pool-dot.is-nonpersistent { + background: var(--orange); + border-color: #b45309; + /* darker orange edge for contrast */ +} + +.pool-dot:hover { + transform: scale(1.2); + box-shadow: 0 0 5px rgba(255, 255, 255, 0.5); +} + +.pool-dot.is-persistent { + background: var(--green); + border-color: #059669; + /* darker green edge for contrast */ +} + +.pool-dot.is-selected { + outline: 2px solid #007bff; +} + +.pool-dot.is-elected { + box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.8) inset; +} + +/* Tooltip for pool hover (positioned inside .universe-canvas) */ +#universe_tooltip { + position: absolute; + padding: 6px 8px; + background: rgba(15, 23, 32, 0.98); + border: 1px solid var(--border); + color: var(--text); + font-size: 12px; + border-radius: 6px; + pointer-events: none; + white-space: nowrap; + transform: translate(-50%, -130%); + z-index: 1000; +} + +@media (min-width: 900px) { + .pool-dot { + width: 18px; + height: 18px; + } + + .universe-canvas { + grid-auto-rows: 18px; } } -/* More vertical spacing between stacked lines */ -#certificate .stack { - margin-top: 10px; +/* === Election identifiers row (EID + EB hash) === */ +.identifiers-row { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 24px; + flex-wrap: nowrap; } -#certificate .gain div { - color: var(--green); - font-weight: 700; +.eid-block { + flex: 0 0 auto; + /* content-sized (EID is short) */ + min-width: 0; } -/* Prevent any table from spilling horizontally */ -.card table { - display: block; - width: 100%; - overflow-x: auto; +.eb-block { + flex: 1 1 auto; + /* EB hash takes the remaining space */ + min-width: 0; + /* allow shrinking inside the flex row */ +} + +.eid-block .value, +.eb-block .value { white-space: nowrap; + overflow: visible; + /* do not trigger CSS ellipsis */ + text-overflow: clip; + /* no automatic end-ellipsis */ + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 13.5px; +} + +.text-wrap { + word-break: break-all; + overflow-wrap: anywhere; +} + +.mono { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 13.5px; } \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/templates/index.html b/crypto-benchmarks.rs/demo/ui/templates/index.html index 128163755..b18e9442b 100644 --- a/crypto-benchmarks.rs/demo/ui/templates/index.html +++ b/crypto-benchmarks.rs/demo/ui/templates/index.html @@ -14,127 +14,76 @@

    Leios Voting Demo

    - +
    - - -
    -

    Election identifiers

    -
    -
    -
    EID -
    -
    -
    -
    -
    EB hash -
    -
    -
    -
    -
    + +
    +

    Set of SPOs

    - -
    -

    Committee

    -
    -

    Selected pools ()

    -
    - Derived strictly from registry.cbor.info, ordered by stake (desc), then pool id - (tiebreaker). +
    + Total pools: +
    +
    + Persistent: + +
    +
    + Non-persistent: +
    - - - - - - - - - -
    #Pool IDStake
    -
    -

    Elected (0)

    -
    - Derived from the certificate voters. Types: persistent or non-persistent. + + - - - - - - - - - -
    #Voter IDType
    +
    - - -
    -

    Voters

    -
    -
    -

    Persistent (0)

    - - - - - - - - -
    #Voter ID
    - + + +
    +

    Election

    +
    +
    + EID: +
    -
    -

    Non-persistent (0)

    - - - - - - - - -
    #Voter ID
    - +
    + EB hash: +
    +
    - -
    -

    Certificate

    -
    -
    -
    Votes size -
    -
    -
    Compression gain -
    -
    -
    -
    -
    Certificate size -
    -
    -
    -
    -
    + + + + +
    From 0edd25d8f92791bb576277b8066ef36b10a353ee Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Wed, 8 Oct 2025 15:12:10 +0900 Subject: [PATCH 06/16] make UI visualize Pools, Committee and Voters --- crypto-benchmarks.rs/.gitignore | 1 + crypto-benchmarks.rs/demo/README.md | 10 +- crypto-benchmarks.rs/demo/run64/demo.json | 1778 +++++++++-------- .../demo/scripts/25_export_demo_json.sh | 195 +- crypto-benchmarks.rs/demo/ui/static/app.js | 323 ++- .../demo/ui/static/styles.css | 31 + .../demo/ui/templates/index.html | 62 +- 7 files changed, 1503 insertions(+), 897 deletions(-) diff --git a/crypto-benchmarks.rs/.gitignore b/crypto-benchmarks.rs/.gitignore index 928cb0fc5..4feae9c67 100644 --- a/crypto-benchmarks.rs/.gitignore +++ b/crypto-benchmarks.rs/.gitignore @@ -4,6 +4,7 @@ target/ # Demo artifacts demo/**/*.pretty.json +demo/**/*.json demo/**/*.png demo/**/*.csv demo/scripts/.env_cli diff --git a/crypto-benchmarks.rs/demo/README.md b/crypto-benchmarks.rs/demo/README.md index 570ecc7d0..23cff8629 100644 --- a/crypto-benchmarks.rs/demo/README.md +++ b/crypto-benchmarks.rs/demo/README.md @@ -61,7 +61,7 @@ scripts/20_make_registry.sh -d run64 -n 64 Cast votes with a specified fraction of voters voting (e.g., 1.0 means all vote): ```bash -scripts/30_cast_votes.sh -d run64 -f 0.75 +scripts/30_cast_votes.sh -d run64 -f 0.55 ``` #### 40_make_certificate.sh @@ -88,6 +88,14 @@ Pretty-print key metrics and statistics of the certificate: scripts/60_pretty_print_cert.sh -d run64 ``` +#### 25_export_demo_json.sh + +Export all relevant data (pools, committee, voters, and certificate summary) into a single `demo.json` file used by the visualization UI. + +```bash +scripts/25_export_demo_json.sh -d run64 +``` + ### Run a Single End-to-End Demo ```bash diff --git a/crypto-benchmarks.rs/demo/run64/demo.json b/crypto-benchmarks.rs/demo/run64/demo.json index c92732cea..07ac04135 100644 --- a/crypto-benchmarks.rs/demo/run64/demo.json +++ b/crypto-benchmarks.rs/demo/run64/demo.json @@ -2,1727 +2,1845 @@ "params": { "N": 64, "pool_count": 199, - "total_stake": 100003 + "total_stake": 99996 }, "universe": [ { - "pool_id": "297d513e8a16fface8ffb11d535e89d0f4ca996ece5a8c2dd7395372", - "stake": 4558, + "pool_id": "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c", + "stake": 4530, "is_persistent": true }, { - "pool_id": "e7ca79019900d2ff1139d3fae05eff7fc474019386f101ffc7a413d1", - "stake": 4436, + "pool_id": "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647", + "stake": 3917, "is_persistent": true }, { - "pool_id": "510000bd0d89608c5f480100db9abac7ad2c011e8891d8603704d8f1", - "stake": 3861, + "pool_id": "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c", + "stake": 3892, "is_persistent": true }, { - "pool_id": "f640a7267abc27ff5399b59b783195003b59bd699c7de7b1a94b90e7", - "stake": 3666, + "pool_id": "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529", + "stake": 3681, "is_persistent": true }, { - "pool_id": "2bffe13687b1d07dd6a5a8c0e4f64f7c565b6b07634bde1cf5cde490", - "stake": 3404, + "pool_id": "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d", + "stake": 3081, "is_persistent": true }, { - "pool_id": "4014004f403600cc7835b95fa6597a1daf75ee91d2f8a489c4cc4621", - "stake": 3261, + "pool_id": "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479", + "stake": 2982, "is_persistent": true }, { - "pool_id": "0caaf065c5e8e01e9c9b01ffee6d5d28166daf9e9f013e7c59f5eea0", - "stake": 3209, + "pool_id": "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb", + "stake": 2904, "is_persistent": true }, { - "pool_id": "2e3fff190094ec4b1e555381f10132cd8503bcfab77ac69f31061f80", - "stake": 3208, + "pool_id": "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1", + "stake": 2901, "is_persistent": true }, { - "pool_id": "be7f491f0142d68fbe5db88f81f3611a003e1860df76ff4c993bff55", - "stake": 3002, + "pool_id": "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0", + "stake": 2827, "is_persistent": true }, { - "pool_id": "80576f0db3dd223800c04d529cec22a7a4e95685fb44b3248579d129", - "stake": 2818, + "pool_id": "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874", + "stake": 2714, "is_persistent": true }, { - "pool_id": "6b33e699e62bb5e9a253e94001b2e0008928af07480180efacf19eff", - "stake": 2696, + "pool_id": "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef", + "stake": 2654, "is_persistent": true }, { - "pool_id": "1fac9270dce82a31acdb0044ea2bc33001bccfa733a8343ab508f8db", - "stake": 2581, + "pool_id": "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff", + "stake": 2635, "is_persistent": true }, { - "pool_id": "010000ea011f3b5f560000c3b7ff09099b00cee6c904ff8db5be05af", - "stake": 2563, + "pool_id": "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c", + "stake": 2602, "is_persistent": true }, { - "pool_id": "ced712e76857ae0301394b9f80d9883aa915438f3a634948e561cb1c", - "stake": 2394, + "pool_id": "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137", + "stake": 2537, "is_persistent": true }, { - "pool_id": "68ff8120d80176efacffac2b21455d940a19c1c9d7015715015a2ac1", - "stake": 2347, + "pool_id": "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f", + "stake": 2432, "is_persistent": true }, { - "pool_id": "8ed5d017d237129da3415d93c29bf235b802fa0d1b326d0e00943e7d", - "stake": 2340, + "pool_id": "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e", + "stake": 2182, "is_persistent": true }, { - "pool_id": "f1ffb84f6cd5961467690750815dc6fe4528e47c10b5029c62aa017d", - "stake": 2300, + "pool_id": "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8", + "stake": 2080, "is_persistent": true }, { - "pool_id": "f9ffdfc4cfd3aeb25b759621d4dad6c8cbc6d330d3e865f6e2ccdccd", - "stake": 2265, + "pool_id": "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07", + "stake": 1994, "is_persistent": true }, { - "pool_id": "2859011646cb69e9af2b00f8a6e99a5e98c315c30d3e0950e2f936d6", - "stake": 2160, + "pool_id": "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff", + "stake": 1800, "is_persistent": true }, { - "pool_id": "ba2e466a8e04eae7d9601b280bff7730395b62f753fc4dc9daac6551", - "stake": 2140, + "pool_id": "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be", + "stake": 1796, "is_persistent": true }, { - "pool_id": "55077dbd0017ac00133d4d451fee91ffd732ae0f0028ee91d1f4fe25", - "stake": 1950, + "pool_id": "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de", + "stake": 1752, "is_persistent": true }, { - "pool_id": "71e9fdd001726ccdce084db3f5d19175c88d7a6ffe72fb7bc7ff9fca", - "stake": 1570, + "pool_id": "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000", + "stake": 1721, "is_persistent": true }, { - "pool_id": "ff017f87e40030d332e3a201790cb4d191f77cff402359afbfc3f133", - "stake": 1412, + "pool_id": "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155", + "stake": 1689, "is_persistent": true }, { - "pool_id": "2c01ff0bfee051d6f9a0542f83d86f8ace9400a0bbae83125124ad6a", - "stake": 1365, + "pool_id": "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2", + "stake": 1686, "is_persistent": true }, { - "pool_id": "a6f18a62cd970f4101011eff07f2ab0924c682d95701a591b2703900", - "stake": 1358, + "pool_id": "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c", + "stake": 1621, "is_persistent": true }, { - "pool_id": "01b517a3397cf1f2fcffb3de23e53b356cff3000ff8175ff15ce46b5", - "stake": 1283, + "pool_id": "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58", + "stake": 1610, "is_persistent": true }, { - "pool_id": "2a0197eb60069d9b9674b36d01c05633c7fd9fd9ff62ac440101e1b4", - "stake": 1275, + "pool_id": "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff", + "stake": 1506, "is_persistent": true }, { - "pool_id": "ab65112ee36236dfb0cbe04901dd8664aa53e14e3a2201e8ba43010f", - "stake": 1238, + "pool_id": "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376", + "stake": 1470, "is_persistent": true }, { - "pool_id": "42ff05ff217a522a4d0157b811f728ee479ddeeeb6bb791fff00adb4", - "stake": 1209, + "pool_id": "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551", + "stake": 1463, "is_persistent": true }, { - "pool_id": "4a0814a0d39aff838261015cd7696a86d5bc4893ff334d9011f05dff", - "stake": 1186, + "pool_id": "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297", + "stake": 1362, "is_persistent": true }, { - "pool_id": "19168e8ba075a44ce41eaacc0ba82193fbfdff79adfaef8075e96c25", - "stake": 1179, + "pool_id": "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01", + "stake": 1319, "is_persistent": true }, { - "pool_id": "b0a969ed896461627b2d580817d6b723ffdd5b57eff345d2ff0dcf1f", - "stake": 1153, + "pool_id": "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc", + "stake": 1294, "is_persistent": true }, { - "pool_id": "811e1c4eb5acb7088b384601455e118c0501da7e579807ed6f1042cb", - "stake": 1126, + "pool_id": "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966", + "stake": 1279, "is_persistent": true }, { - "pool_id": "ebe0eb00bf42788a8cd370e5260166436500a39210f516b5d9ebdc8b", - "stake": 1107, + "pool_id": "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938", + "stake": 1146, "is_persistent": true }, { - "pool_id": "e534f8d3582a76a57f5c28003af6cdf1c3bcd2a977e27c3999f1f9f3", - "stake": 1091, + "pool_id": "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf", + "stake": 1066, "is_persistent": true }, { - "pool_id": "73efe21dcd67fb690069dfbd0430002c993fe43d775ed60166b93b5b", - "stake": 1065, + "pool_id": "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf", + "stake": 1059, "is_persistent": true }, { - "pool_id": "4e70a101cb06c0005072c580645652ff9d5e47f717004ccaba9582ad", - "stake": 1040, + "pool_id": "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8", + "stake": 1045, "is_persistent": true }, { - "pool_id": "500385315cff8eb2e06b532fadaaa001928b08049da30ce6ff93b769", - "stake": 1018, + "pool_id": "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11", + "stake": 1016, "is_persistent": true }, { - "pool_id": "d7fffa2e2dd3efe4ae003687be09ff11a53cff8c9d36d4130cb4b5fc", - "stake": 934, + "pool_id": "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416", + "stake": 970, "is_persistent": true }, { - "pool_id": "3b96fb5124d0df7e90440fa9ff5501bfab014b31db694c01b03ddbbe", - "stake": 908, + "pool_id": "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283", + "stake": 967, "is_persistent": true }, { - "pool_id": "c140043b5a723fe346f700bf44f4b40a8c4f324dbe17b5ac01e3b45e", - "stake": 893, + "pool_id": "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64", + "stake": 935, "is_persistent": true }, { - "pool_id": "db5640fd0014ee5c43b3498573454314eeeb89ffbf001f5724e3ff1b", - "stake": 750, + "pool_id": "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8", + "stake": 891, "is_persistent": true }, { - "pool_id": "90d495ff0101155eb09ed7ebb1760016b444316a202eff090b82a0c9", - "stake": 746, + "pool_id": "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99", + "stake": 793, "is_persistent": true }, { - "pool_id": "58ffff4bd20fc2838e882341014f43f2b5a22934207a2c4f7a57e409", - "stake": 717, + "pool_id": "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d", + "stake": 665, "is_persistent": true }, { - "pool_id": "ff49416cbb7e6309ef340b8702deb8cf8d77019b1cc62cf76c3b426e", - "stake": 698, + "pool_id": "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c", + "stake": 640, "is_persistent": true }, { - "pool_id": "a82c016500d5ffb7845300684039d9628352c61d6f45d7c1d0d58941", - "stake": 642, + "pool_id": "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3", + "stake": 638, "is_persistent": true }, { - "pool_id": "a69cf8710100aba9476c695c78fcbdcb9b89a84e0191695d2a5dcc47", - "stake": 631, + "pool_id": "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690", + "stake": 611, "is_persistent": true }, { - "pool_id": "0a754dfc0100b004638dbd846abe3ec155c82b0f8dee5f5e6c00d9ff", - "stake": 568, + "pool_id": "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff", + "stake": 598, "is_persistent": true }, { - "pool_id": "f3aacec30955e959ce047c75ffdea67b77d8b9fffab3aa01ffde0dcb", - "stake": 560, + "pool_id": "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601", + "stake": 589, "is_persistent": true }, { - "pool_id": "443e7518ab3045e4ff88cb23c4a334d997747e24a64e9ea34f247505", - "stake": 539, + "pool_id": "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2", + "stake": 561, "is_persistent": true }, { - "pool_id": "6733785fcc6301e8e58df40e90ed37ee1ef53cb05179007300d31564", - "stake": 533, + "pool_id": "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b", + "stake": 548, "is_persistent": true }, { - "pool_id": "1ecb00477c12ef001979a45884f823fac7c2572527fa810c9e0e7aea", - "stake": 498, + "pool_id": "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2", + "stake": 474, "is_persistent": true }, { - "pool_id": "01aa219b77ffff26b05caa247625319fc2673c3bfadb0136dc14a604", - "stake": 495, + "pool_id": "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201", + "stake": 451, "is_persistent": true }, { - "pool_id": "9d8a72e60df13e75575c00947192a3d493014bf5ce4f4f00a000370b", - "stake": 430, + "pool_id": "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7", + "stake": 441, "is_persistent": true }, { - "pool_id": "75d82022f485bcedb6b38c3a01016bd7050fdc9edc2f7fbaa09a40bc", - "stake": 415, + "pool_id": "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b", + "stake": 424, "is_persistent": true }, { - "pool_id": "d252010f5c7a970bb92626da9c8e2280921e997c83ee499148bb5101", - "stake": 358, + "pool_id": "e408900057c66f11f77a090112a8a9d885c435735ce121ddfad32771", + "stake": 411, "is_persistent": false }, { - "pool_id": "9adf3ed0bcc79e9b2f9781e209fdd604132868b5ba9e7bb26cff6c5b", - "stake": 353, + "pool_id": "b12a95008fb0cbcbf023dd6f8efeb2cf71f71356c54200209b1c78ff", + "stake": 365, "is_persistent": false }, { - "pool_id": "2b9199fadb0a912d78b7010e12ff6298aec7000d000462b5b061e4f1", - "stake": 315, + "pool_id": "d687c5c9643f59210012ee0b39cad3a7a75cd1614e69720101080ffa", + "stake": 350, "is_persistent": false }, { - "pool_id": "b839bc8cf9f25702013e019bd0d3318c380097e105f9915b653192cb", - "stake": 297, + "pool_id": "d77e83a3519663dd32e8bb0e5ae8bf8f8624ab8a0208e59e82befd77", + "stake": 310, "is_persistent": false }, { - "pool_id": "2b6edf630000468d9dc8f60fffcb9281957c01f6dc945a6a764689f1", - "stake": 294, + "pool_id": "f25001dc1b54cefa2a1e0bf75b9701ecc0001ce16f12cb7fb98ed60f", + "stake": 308, "is_persistent": false }, { - "pool_id": "bb2ada9401fb84d1a407a8d6a95dfad6a4f30f40bc74ff6fe85a1f77", - "stake": 292, + "pool_id": "6201cbc21cd34e200053404dd9afa9009c77196e329afaff960cf534", + "stake": 306, "is_persistent": false }, { - "pool_id": "edcd00b778815439000046ac0036806b2e49e5208b97f2373eefa399", - "stake": 290, + "pool_id": "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631", + "stake": 304, "is_persistent": false }, { - "pool_id": "1aaa13bf4f51da2001ac5d0059d846cbdce7ebb95718c9a3dc4b8313", - "stake": 285, + "pool_id": "ffd0ffff97a811e712c14add1047bec50016d7e50101b6fb3872ff00", + "stake": 302, "is_persistent": false }, { - "pool_id": "aac1f0971050401758f1597d75d7ff429661fd80f2ae7efddcff2b44", - "stake": 282, + "pool_id": "ef39dc5761054877a0c3a51e00a7424b72fb4effdc722fede2c3ac26", + "stake": 295, "is_persistent": false }, { - "pool_id": "2c807bb6431eba01bafdccb7ed9724ffe4a4ff9b60e0499b0e66c82e", - "stake": 280, + "pool_id": "3e746153eff970bd4887887c7003007035014a11cf0001bf01466cfe", + "stake": 273, "is_persistent": false }, { - "pool_id": "00b76527d1f8f75a1c8bc8d47737a1456f39e900f92ddc360a44035d", - "stake": 251, + "pool_id": "e9d532daff2d3e922baa003d3e5447e92facd96a2dcb722c30ff8d02", + "stake": 265, "is_persistent": false }, { - "pool_id": "caffe8edb5c9fa60ff0101934957014a489501b91b1785f9bfc07b91", - "stake": 218, + "pool_id": "d2ec42d9f307fdc985e0010001d9e5603b00198e3751a0487eb2b03a", + "stake": 259, "is_persistent": false }, { - "pool_id": "c3d75a8b44f1bca7dfdd77a7bd837e5c3fca45ffcdffaff54f950023", - "stake": 215, + "pool_id": "240cb24fdab700ef2e8a6029a4bb9a82a626737d413da2787c01b3b9", + "stake": 238, "is_persistent": false }, { - "pool_id": "3d38fc9218cc6ee9a2bbff011ed815fcb156d14211c11599be5ba788", - "stake": 202, + "pool_id": "aa821cd818d52c9aa71a83a64443558439a950be61ff281d070478ff", + "stake": 217, "is_persistent": false }, { - "pool_id": "01503a091fdf01a1fe93bb8cd54e858e11efcb3062ffc08fe5551c61", - "stake": 196, + "pool_id": "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3", + "stake": 215, "is_persistent": false }, { - "pool_id": "a9b7d4687b76cfaaf5336bd1b8056a9801d64d472e62ff46d03128a0", - "stake": 196, + "pool_id": "43ff5b810501f390d17faf096b9812efa79866fbb20023be2f012201", + "stake": 213, "is_persistent": false }, { - "pool_id": "ffe264724801dbe3c900838152e884435c57e51a8ef9a701028a3969", - "stake": 181, + "pool_id": "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff", + "stake": 179, "is_persistent": false }, { - "pool_id": "cf8a0ae2ff175c5dddb4b3f82bde410a62e6284c3eb8b0c95a001e77", - "stake": 161, + "pool_id": "007b7ae1a544a2fe7cff0feeffffa0aba683d0ff5326ff4674ec5da4", + "stake": 172, "is_persistent": false }, { - "pool_id": "4e01238c2f8994651251c1f3009331e9e38602f0b3c6ff5c78ff86eb", - "stake": 160, + "pool_id": "c99031b2f1858bd91d522bf565784c28a98897011ad3cb845fdd58f6", + "stake": 163, "is_persistent": false }, { - "pool_id": "30010fecff2471437901d979b0949fdf0029888ca985041359957bfe", - "stake": 157, + "pool_id": "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7", + "stake": 154, "is_persistent": false }, { - "pool_id": "0083ff8b27e6e9ff193bab599900507f97627b3696e5cd0101a65a12", + "pool_id": "c76154138a2dee9754f07a426ce7bb5c629099e571a410afb710beba", "stake": 146, "is_persistent": false }, { - "pool_id": "6b5ecc2d7900f8732d944bff0031f84a59dc015bffd801a0bc3a01d1", - "stake": 144, + "pool_id": "ff0258361001a58a5a959c501608034201700c01fe14e49028fb248c", + "stake": 125, "is_persistent": false }, { - "pool_id": "207309e066ffaf681d01ec627f6b26eb017401568fe788b73101e470", - "stake": 137, + "pool_id": "e00327090b0133c301979bdf4b08de0198d70775b451733d6381650a", + "stake": 115, "is_persistent": false }, { - "pool_id": "001cb6d54c00316a5bdc38c25dec0002805d01275300c8556ebcc3f0", - "stake": 120, + "pool_id": "0c036b0369bd5cbd5f00588bab585433771de2004bce47d000a11500", + "stake": 110, "is_persistent": false }, { - "pool_id": "531fd03400ff89603a49cf1292c85bdd014aac24b4b06cac87bb35cc", - "stake": 112, + "pool_id": "fa12ffe085438b9fff811a2003caaf8fde9b00b63f24f0fef270aa7a", + "stake": 106, "is_persistent": false }, { - "pool_id": "ee0e8f64875d4573c839c801a2ff844d822f0c0a8583efabe800bee8", - "stake": 106, + "pool_id": "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a", + "stake": 104, "is_persistent": false }, { - "pool_id": "5756abb4f757ff01cb5b72028c01f5b9c38acefd6a6e13fd58019595", - "stake": 102, + "pool_id": "b8c4dc65f9ffad410fee7997f047e8f521cd01d31c0392deb8e65a42", + "stake": 103, "is_persistent": false }, { - "pool_id": "4d01b0bcc8c8006300a331d71fe30b1e4d25bf9977015fdaac58e496", - "stake": 99, + "pool_id": "266464354274595cd5b4008fa0e87c95e4c9e223fe95bee1dfcc0d9c", + "stake": 95, "is_persistent": false }, { - "pool_id": "1af27c3f21e7009c0aac1f653901718fc24f46c80b1001086ebbc000", - "stake": 88, + "pool_id": "07a0e9d497f6836060fd84af10afb2f2994e04d0f8a2b770ff3c88b3", + "stake": 93, "is_persistent": false }, { - "pool_id": "dc9000e798a70e9582018179fb1dedf3ff439929b54f01c62300ba34", + "pool_id": "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6", "stake": 86, "is_persistent": false }, { - "pool_id": "335701a8a706df54592e06d0b0a6303f86c8a0cf45160f4233063a23", - "stake": 77, + "pool_id": "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2", + "stake": 86, "is_persistent": false }, { - "pool_id": "ff435e46f95900e77300c809c9acbee7be000a177d27af8e5677c1d0", - "stake": 67, + "pool_id": "be99c5965bb5017581c9d8e9c3b68100dcf3e4236653ed51f8b90b44", + "stake": 77, "is_persistent": false }, { - "pool_id": "80e59d860f7af300c8fa0a0085381e98ffc016c55201f4e07903c5b5", - "stake": 63, + "pool_id": "f40bf01daf809d61e68649f04a1d8dda9151ed6838c89078b68dc3fe", + "stake": 77, "is_persistent": false }, { - "pool_id": "0101caf77c2455864e765290680006008ea882ff7119336178d570d6", - "stake": 58, + "pool_id": "afd43c3b45c0214e05e5ff7610411c39faef681c92d09c2c693720c2", + "stake": 72, "is_persistent": false }, { - "pool_id": "b0c6e4ac306cee2a93d0883901ff024056298dbfe3caff01651a5f40", - "stake": 55, + "pool_id": "a9bf7f2ccd409bae63b3e6c101497909c3e0ffaf1a47e900ff00f8c2", + "stake": 67, "is_persistent": false }, { - "pool_id": "01c7fff54615ff017aeefa017a436f01018062949601e3c1e17361d3", - "stake": 54, + "pool_id": "6f2e179432e5c4c6df001d558e0000e0e210c5262d00a4182ad76426", + "stake": 59, "is_persistent": false }, { - "pool_id": "86341e8327010418ec386fc2472b68faeabd07e601ca3c4a9c60dcd9", - "stake": 45, + "pool_id": "b0d3ed01ff74ffb000a9954dae59e570dca5ff5bbbd3470101c7cf39", + "stake": 59, "is_persistent": false }, { - "pool_id": "ba50ebe1716c7bc601769781f4d001f60f2b01a500637393d5d71c1e", - "stake": 44, + "pool_id": "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9", + "stake": 57, "is_persistent": false }, { - "pool_id": "0563d9b90133ff2a61b0db2d654cbd8b34845d4550582f3799c047b4", - "stake": 42, + "pool_id": "007a17b9f86300003200366e28b71dff215de1e8c47d558d10b6ffbe", + "stake": 43, "is_persistent": false }, { - "pool_id": "000d0069f82085e3f6239016124290523a41a616a849bab01f098a39", + "pool_id": "1c3ef8ff647f35904412ed32ebc625b33edac269017e0197005d1ccf", "stake": 41, "is_persistent": false }, { - "pool_id": "265f28934f7ebd02014e9e301300ff940490a806f824a063f4dd0006", - "stake": 38, + "pool_id": "ec2ca9da0aaa43535b95ff537c7bb700ffbafc5c0efbe90063aebf00", + "stake": 40, "is_persistent": false }, { - "pool_id": "02a542e643ed8181ff0094d801520de5de6eab31e93af8b98ab4ab16", - "stake": 36, + "pool_id": "e075b8012d3a1001794f751396dce75b672e6a0004ffff01c71ac100", + "stake": 39, "is_persistent": false }, { - "pool_id": "f4a9c6af00ff7f38fca05fbb30c612201d0747e85d46c9c739feec61", - "stake": 33, + "pool_id": "796240fffbf7c801e8402469010115a270c3f6d7ce21fa9e6fff052d", + "stake": 34, "is_persistent": false }, { - "pool_id": "fa44e912ce3e0138976ccd75a43ed91b005a2b81382a55fe30457001", + "pool_id": "e57ec7c7a233479e45d4dfb23c85b21b85ffe93d2c038986f60d0132", "stake": 33, "is_persistent": false }, { - "pool_id": "0f45ccc70009f917c37ce778ffd15e6feb94301501ae61470013ab95", + "pool_id": "cbf94fc524a51d030876a158f9ec8dffffce8f2148f1bb5b61445b81", "stake": 32, "is_persistent": false }, { - "pool_id": "ae763c00f5eb985709e87c7052eefcef8c6e2e00053ae43dbbadde23", - "stake": 29, + "pool_id": "72394760a3ce4d7e01ffffc45cd7e8de83806bffff382dde3cac0f33", + "stake": 27, "is_persistent": false }, { - "pool_id": "0e49da3e75c8da00da48bf4f44ad6a6773d12ea6e2fbd06ac4708498", - "stake": 28, + "pool_id": "004cb789ff416601c489fe8a2a69bca620ff7cd4d32cfd9449543e06", + "stake": 22, "is_persistent": false }, { - "pool_id": "7da0a5ff9b13a7fb37f086c7ffff23eb6e9f22ecb737cf847ed0f54f", - "stake": 25, + "pool_id": "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571", + "stake": 21, "is_persistent": false }, { - "pool_id": "7adca68c6dae1a9ffd8e750e365724ff1973e8000bc5d414be96ddd8", - "stake": 23, + "pool_id": "a17fb2e3ff00d898bd89a6b9ef49bf08e21c237a297f71379aeed225", + "stake": 21, "is_persistent": false }, { - "pool_id": "b4795c951bdc6eb4b4fff0e7fb58a0ed66019b6feeb3d672f5d5fe4c", - "stake": 22, + "pool_id": "ff3737d352fef3e653a296978a09b419e2d0016fbfff7c77aaeaa001", + "stake": 21, "is_persistent": false }, { - "pool_id": "355d06bb68721a3f32012d7e01cdce721fff534b64daac0cf4c81154", - "stake": 18, + "pool_id": "5d272ea05f63673c6c001f9bdb6d3e225fa602e6d2ff2a663fa66745", + "stake": 20, "is_persistent": false }, { - "pool_id": "c31868f06551db9fb49efa008c986ad74f016e5e7b16ebfa974f2638", - "stake": 18, + "pool_id": "01a9b2a9c6778ff1072bf76e0064943701608a1fffd618d301654fa7", + "stake": 19, "is_persistent": false }, { - "pool_id": "f9b715e13a124f5020a11f2dca0100a701fbc2d464717ea7cbff563e", - "stake": 17, + "pool_id": "bc70858172851b9dff34ffe231d8892c57833a9e666e8f0313df691b", + "stake": 19, "is_persistent": false }, { - "pool_id": "8017be2c179b0b006b994cc3e360ff4a01855bffccff265f15dec969", - "stake": 15, + "pool_id": "d57ed603a89d8342859e075947c91b1a255ae94a9d01b1a530457ff2", + "stake": 16, "is_persistent": false }, { - "pool_id": "d97dec3dced0d17cff6e28ff0164475d66d4ff408bef00b06dd47514", + "pool_id": "091b2203cb2f3b885a10c326cfe92b7d0bfe2b00024779b4a7892d71", "stake": 15, "is_persistent": false }, { - "pool_id": "18daffe3f32a85016a9d3b191ff243bfa00126d57bf7c96ce3740160", - "stake": 14, + "pool_id": "ffebd8db93a0ef539f836c8312550100003fa9641ce984597ef53398", + "stake": 13, "is_persistent": false }, { - "pool_id": "637ea1112b52012500377800fcbe2d630d49f7d112cfd811062c0001", - "stake": 14, + "pool_id": "4fff4cf77029f600d9ffc0c8fd981a0ef37cffd4bd42db9050eff524", + "stake": 12, "is_persistent": false }, { - "pool_id": "d4559298046806732261ff121033bfcad7727f2e3fcd19fa7d92f221", - "stake": 13, + "pool_id": "9cf24b7b01a1d90a01a9009d3a4466b39500e4191588639ee7f7b800", + "stake": 12, "is_persistent": false }, { - "pool_id": "3963d9c18518b0ed37d5e2f13a761f42ca42834400351d07ab006e88", + "pool_id": "194d9c992e9cfe00ac2f46cd76008dd20723d254c0dd07fab24da548", "stake": 11, "is_persistent": false }, { - "pool_id": "f8095955680192bd0b5c1fd1fb097e77d4009a3528aa1da5b03f01bb", - "stake": 10, + "pool_id": "2fbc5ad791c60d17bb0ff900717c9fa1197a612a72cd4e200182c273", + "stake": 11, "is_persistent": false }, { - "pool_id": "0de3e268b7a37d6be7858651fcb0e4471aaa004d107101be225b52b2", - "stake": 9, + "pool_id": "c1410131a6f50142d468da34025d7a06f4ff007a78fb7394c346e513", + "stake": 11, "is_persistent": false }, { - "pool_id": "20ecc3af4979fdd8182e70d95fff01a000010b269a07e6eb4b2110a5", - "stake": 8, + "pool_id": "02f28ceeeb8f8eff0be401993255ffba60bffdc2a2c19a01b661978b", + "stake": 7, "is_persistent": false }, { - "pool_id": "93a2f528ea475c20765525ae00370aeea70db47dff0d80516f25b5a5", - "stake": 8, + "pool_id": "0fffaa50003b42815a126701fa6b50d1216fcc86aef307e798f737c0", + "stake": 7, "is_persistent": false }, { - "pool_id": "99ed0aeb8fecf0ff4f005bb2e314bd82009a306da0b5d2005af20e2d", + "pool_id": "16ea953d050b5f84e64e137a26a4cec82bf85000f6b25fede6ff3503", "stake": 7, "is_persistent": false }, { - "pool_id": "a7000014ff9c2d4d01ff650cb658a2ab68de94017955252b452cbb67", + "pool_id": "2c66f80bec0367db4d43e9459e82fe7e446c5789d5ac017f2dd79a01", "stake": 7, "is_persistent": false }, { - "pool_id": "478d8eed13452a013ed5cacffadcca01b0515c1de41e956a4c008200", - "stake": 5, + "pool_id": "819acf08e187cd01ec1fd839391b61e4c8aacc303a8437bf2fd5887c", + "stake": 7, "is_persistent": false }, { - "pool_id": "7888ffdcd24591bbcd6435a6e45b2927070a691221c710d04c4a1462", + "pool_id": "ec2c06173fe7c758fff3d8011e01547f09e95dd49ebf9ef20143f770", "stake": 5, "is_persistent": false }, { - "pool_id": "ff25ff7affffe9ffb4acff8830154f6c5ae61cca9900ccf9e9a0b9e1", - "stake": 5, + "pool_id": "25f0ff0009242e694cefb24c45008eb58e4daa1ab6ff507fdd0e59a1", + "stake": 4, "is_persistent": false }, { - "pool_id": "1fcd8559ffd00f01118267bdcf2249918fdeeb0045fb01d8d48e3c61", + "pool_id": "3900e35dff96420ceff3f6c2e129172af3d001e66a8c01501d77a247", "stake": 4, "is_persistent": false }, { - "pool_id": "54e2bbe6cb0118b4f300e499cdb1d56b7c01e0ff23ae3bb1c98180b9", + "pool_id": "7be8985d82660003fd79f9d2ffffe524418faf6611f3362671845f1f", "stake": 4, "is_persistent": false }, { - "pool_id": "f835c4cf5356f604e3d50f321d3c2b41248f700103762400479d59fc", + "pool_id": "acf227b3222573270a400f97aad8ca6d976ef6a67632f6491c00d8f8", "stake": 4, "is_persistent": false }, { - "pool_id": "82e98351e11d9cad3affca45e6348fdb1f81055d880013b709be427e", + "pool_id": "0d4ae5a909eed7912c494cc705a97ea26025cd98483101314aa2fb01", "stake": 3, "is_persistent": false }, { - "pool_id": "bc2e1db28d542ac322050c271aef6c419effaceb9118c59c343ec4e5", + "pool_id": "5ded7dca81e99cb3869563b6ff4a759351e2b98b071b29307a743a2e", "stake": 3, "is_persistent": false }, { - "pool_id": "ccd3b6592176ff005baf7e6fd5eaa0853f219b3f0dbeefe8c801ebc2", + "pool_id": "734cf0ff12bb7b70a2ac9ab91dbbf205205368b30bc5ee3fffc05601", "stake": 3, "is_persistent": false }, { - "pool_id": "d8fff0ffe3ff7fd67e59ae471725c4e2af3e6c56f00d012f52620104", + "pool_id": "77ffba0e5b2f65cdf4f22e201d924a8377ff153aff6e148dfb78eee7", "stake": 3, "is_persistent": false }, { - "pool_id": "ec3a4f922b9fecda5bd4746503ff085b6e9f322838644adbb950dffc", - "stake": 3, + "pool_id": "01d190af9500d0aeaa702ab126011e0d247adc1cfd25ffdeef910a15", + "stake": 2, "is_persistent": false }, { - "pool_id": "22277d0101e05d42a527f33785e6b08631c02dcf3de70ba550f5c9ff", + "pool_id": "1a6e415a3687e9007845ceeee56fea563e085ba89cf36f64c10eaa5c", "stake": 2, "is_persistent": false }, { - "pool_id": "59006160937cac095bbc4ce7a96a8f3fe5527659008f95b4eefba797", + "pool_id": "abc5877fb3e9dc6c2ecebcb6bdd3a2ff0172f50722c4e0ffe60058a3", "stake": 2, "is_persistent": false }, { - "pool_id": "71b1a01a93ff410e7bf4391404ce537f5d0d19ba967dc2eec8a636b7", + "pool_id": "b2ed18f25c6f642cd232a5d119b94116e2fff900afee000e82026c5c", "stake": 2, "is_persistent": false }, { - "pool_id": "98b59653e3bf4859d49bafc5e540abeb8d2339c56232b837ffb833b4", - "stake": 2, + "pool_id": "0fc78d01677d5b1c6b3ba67546c012a6a61b13a48733214231a5528f", + "stake": 1, "is_persistent": false }, { - "pool_id": "ff25ff50f09bee01f10058ffdc596d91dee5f60005b9b3ffa951f879", - "stake": 2, + "pool_id": "1c4e2a0065d7942da94b7a91b434226ff65fb7256b355fc5bea52e18", + "stake": 1, "is_persistent": false }, { - "pool_id": "37c401bad06f05d2ec9a77da395b3156372600ffee4f511b6864d02a", + "pool_id": "494fc1cbd17a5333681ceb01290953018fe9c53c4f9adbb9a1af0046", "stake": 1, "is_persistent": false }, { - "pool_id": "39010395d8f5e16969d721797bf846618a525e32015b23c7c60176ea", + "pool_id": "4c75a8782091320088cd01e900a8aac412f4838654f729a787564b68", "stake": 1, "is_persistent": false }, { - "pool_id": "49f07c93777ce5720c35c21a48cadb9cfde005015033f3f5b4cb2b88", + "pool_id": "5be0ea83207a384c55f0c77336e02e7b93ff43ac5a36e23092a9ff0f", "stake": 1, "is_persistent": false }, { - "pool_id": "8300897d3c7d6e9a2907335fce58eef00009f22a56e500f46201392f", + "pool_id": "95264801d09a36c0ff36dd56479215bfea3d006f5500548ffff69059", "stake": 1, "is_persistent": false }, { - "pool_id": "95a79da700235961ff73dc78ff5b6d2401177e79b5b2c6dbef711484", + "pool_id": "a34a3a08407225822a8a25660081101461ffb022eb8a4d9f0b6200d5", "stake": 1, "is_persistent": false }, { - "pool_id": "c3838dcbac6ee5c64999c3160517199db36edf0a00ffaac9adf22798", + "pool_id": "d3ff4b015b31bb991295a301fc5eea195744f31fc276a817d284350a", "stake": 1, "is_persistent": false }, { - "pool_id": "ff9314d1834e2506f701fcdaeca726b512ff518bc31b8c1136e4c4eb", + "pool_id": "db8f1457b1c223a2d03a8b42ff169100ffff3995cefff5985c5100e8", "stake": 1, "is_persistent": false }, { - "pool_id": "ffcae231011c0d6bdd7546303abb2f1ca253e6f2a9f980008afcafff", + "pool_id": "ff860082386cf7001aead49dfbd50e75beff4ca7f138d4711ee7016b", "stake": 1, "is_persistent": false }, { - "pool_id": "00b9364927d3ff64bdb81559031af0a79793de4dd18a2004524316cd", + "pool_id": "00600cc9d279ccaabd76ffff00eba32b210027c4038c305b99962a77", "stake": 0, "is_persistent": false }, { - "pool_id": "00b9bd6144ff16889ff63ce92f500e0144e1f3578f34220090c9f8ab", + "pool_id": "016c8800ff690bda9802a96f134a4f03ac65eb4541f9656d0e523d00", "stake": 0, "is_persistent": false }, { - "pool_id": "01511e82363771cc1101c4409bde01c2ff2d692f58a88eae37cd187a", + "pool_id": "01f702c970b3ff2bb3490d9a6b677cd46e1e043621ff1c174024087d", "stake": 0, "is_persistent": false }, { - "pool_id": "01752201757f364caac686e9e34d20896999bb6064003b1494726af5", + "pool_id": "0ed40164004ac401ffde79a9e60422e200a7fd9c43a4ef283c00145d", "stake": 0, "is_persistent": false }, { - "pool_id": "017e5ea1a154748a90be98ecd0436cac89776ca200029eff3a95de2c", + "pool_id": "191d68de0259e70d2a55ae59e463ae00c7dcc69136bb0096af86c346", "stake": 0, "is_persistent": false }, { - "pool_id": "10a8c8ec01ed04434f7a463e14ff0a994296e97718a822c46baab88c", + "pool_id": "1a0113e50000119207ae22fce1bd3ebfe885a29cb3b9f5bd7013e38e", "stake": 0, "is_persistent": false }, { - "pool_id": "113e737d14c51476cb5db565ef179759e99a1d44999084108f2866d8", + "pool_id": "1a51ffff00668dff0497605e3dca074f81337cf6bf21e0fff1d80042", "stake": 0, "is_persistent": false }, { - "pool_id": "13717c14c3207e44d578edff0bf76a2c4e6930990e35a0009970010f", + "pool_id": "1bd26b6fbf600023666d90ad14bfea1bff12cfa018d74e471e4384c1", "stake": 0, "is_persistent": false }, { - "pool_id": "14ffc31bfb0110002db1187f26b16bd3482d9fafff519fb0d3b2a38d", + "pool_id": "1c7acb7a0073e9c3b78aff9b00f016f6533f3800ff01429e3078cbec", "stake": 0, "is_persistent": false }, { - "pool_id": "20dba91387599e09d5606b6aefec981d0f5b0fed8501b579cb8e848f", + "pool_id": "1cc4d59507714de1b61e362142ef26fbe7ee4aac91df5e4bee01f1f5", "stake": 0, "is_persistent": false }, { - "pool_id": "34e8478201ffbeb0ab04fe41eabb095d36ed6e0087340fe617d09955", + "pool_id": "25c36181b2f2d234816d0c7600800d7a2c62e3e5dd258d4ec5217008", "stake": 0, "is_persistent": false }, { - "pool_id": "3a06013f8b1a6691aa4d9a2c952b89ce112616774ccd9e24422f09a4", + "pool_id": "2ff34789ff111c01ad6ea6ac03f7a0014cffe9626ad536aa62007479", "stake": 0, "is_persistent": false }, { - "pool_id": "3a6e005c411e8ef0c3a2852aec01890dd7ce00bc7e8400b983477701", + "pool_id": "33248dee56388df4016a36d000440ec36f5b392e7ee170e9964fc97c", "stake": 0, "is_persistent": false }, { - "pool_id": "3d007bd3a16b52cb06e5701d5b4466a5007619ccbd19871c77eaf57b", + "pool_id": "36353475ad3c1ae87b7000ff67d6b800d200b31900ba17011343f831", "stake": 0, "is_persistent": false }, { - "pool_id": "50446c4f26ffaf6598c783410bcdde5905354efe00fce6f7375301ff", + "pool_id": "374145d49f4f4f074e5c2e0153d888933399599ef2c4be0121017947", "stake": 0, "is_persistent": false }, { - "pool_id": "522a8d515c38e3b9fffc8dcb25016b018ebaf1010164012024e0a338", + "pool_id": "3940f037f98d800000019e415e59d0009517a17ae67f6d1cf983583b", "stake": 0, "is_persistent": false }, { - "pool_id": "52aa09ff004fdd8fc3f149163e7d79257aaf5d4fc3cff7576d029c7b", + "pool_id": "45d2abaa00a900ff78ada641f03879ff335e1fd9e86630fa0403d6ff", "stake": 0, "is_persistent": false }, { - "pool_id": "5300231cfdec3f47e44411fc8ebd00f924f45f5420a44d42ffff4681", + "pool_id": "4a892cc61350083301181737176d0c7f69b6c12768980ee19afd49a2", "stake": 0, "is_persistent": false }, { - "pool_id": "596ebe0738ca1b90cbcbd10a0b804ac20001f94e6474ff384229c14c", + "pool_id": "50ccc49ec9c00b8a04231f25a256275613d2f4b991195fc201003932", "stake": 0, "is_persistent": false }, { - "pool_id": "5abdb5007572b254df05579124c6ff921b0051405af73ea7aba38220", + "pool_id": "566900ee95b45900f4d57b8f65c9f58087492e850ac2294d92144981", "stake": 0, "is_persistent": false }, { - "pool_id": "5db101e507115c40e4468201231453ad67e1ef7d33ef52e8f601e246", + "pool_id": "68858ee00098a6c201492fdb09f8be0b546e3d537dfe811e98ab615c", "stake": 0, "is_persistent": false }, { - "pool_id": "5e8b326e867efc6e6af10f22029ed62e10d86bd2ab252f280d1a4f48", + "pool_id": "6ec653bc3501fba1eceff8004464dd47aa788966e2988813a2b19292", "stake": 0, "is_persistent": false }, { - "pool_id": "5ef19efeff7c483e5643b481e40d8af0f6f07bcfffbddd378a6e5407", + "pool_id": "704d2d1c018eb90166c653602d00e0b52a01faeb0e29ff92ff791422", "stake": 0, "is_persistent": false }, { - "pool_id": "639a0e1530c500d14d4d849c3c95907957896ba98b125e3f00ffd631", + "pool_id": "764801d5b6f51b85d1c425ffdb01926813fb0029d19e79f73741e2d5", "stake": 0, "is_persistent": false }, { - "pool_id": "653debf312a13afba13f2cf426c101292000de0a090ea91e0d3d39dd", + "pool_id": "7f70ef01c542bb9953ff025e477aa461511eaf2213f4ef94bfcd0011", "stake": 0, "is_persistent": false }, { - "pool_id": "6c00017cc07aada307933f15167a869f11a101c21deee345d8bf3f25", + "pool_id": "7f9969004c1550edd7b04ee88b5274013b12a50f990189fcca22c8cb", "stake": 0, "is_persistent": false }, { - "pool_id": "6d1a598dcca7d4c324d50c0940016067507622b6a485252aa60c6969", + "pool_id": "8275f96f65ce710176d500375005c37a8dff6da2ff1745ac1159ff78", "stake": 0, "is_persistent": false }, { - "pool_id": "71aace9f096f1101d3fd3b005694ff6242461ccb710ada3800429d4a", + "pool_id": "8d8fd800075c6ae000bbff1fafd7ead5c4abe729d101f7cc7e6a06e7", "stake": 0, "is_persistent": false }, { - "pool_id": "7500290ce900ff55e0ead88900fcae3b6130aabdce1c82251b864abc", + "pool_id": "9d9f9b28e4dcc35c3a053fdc0b066df880fd4411f02c2a171deee088", "stake": 0, "is_persistent": false }, { - "pool_id": "7a5b49558d006748d492ec8d7b5a4c3150f101032e05f601b292b4cf", + "pool_id": "a08ae12343f534ee49bdf7ba256db00103e4203d00c0ffffb1074759", "stake": 0, "is_persistent": false }, { - "pool_id": "7da96f49fff07a7c1e3a000150fe00209b1dff4b7bafecb9d2ce0459", + "pool_id": "a37776b4002dd68c14012a6d52820b2d89e9c001f608014b43e692a3", "stake": 0, "is_persistent": false }, { - "pool_id": "7f00a078e500d7d25c47be2cde9531d28b16fcf2e33919fcd9e44901", + "pool_id": "a3989c00d6dc79286474ffed49c1dfeaf879592ff9a0ba38060500ad", "stake": 0, "is_persistent": false }, { - "pool_id": "8a8aa178e900bea547c0d05b2aebc95545476a1a1ddf508defec68fa", + "pool_id": "a6931c2de82054753ad1735087a72721573f88cbe9003b7242d6f9fb", "stake": 0, "is_persistent": false }, { - "pool_id": "925cff8cc10114e6b4bb6b644ab1515ad1d5f7a14acace906001d9b9", + "pool_id": "b3c2b40baa974b5bc50a5a2abebc0107389e4f738301650aea247773", "stake": 0, "is_persistent": false }, { - "pool_id": "9629f17dafd13dd2ff50a10e5875c22b657cd97cff53ff2a2d18a14a", + "pool_id": "b64d005afff675d5fa56e0cd58c86101dff90118d0559901f301139c", "stake": 0, "is_persistent": false }, { - "pool_id": "9a15ba62b8db62910e9c3912d1343a91437ab701ff24b9656deea4ae", + "pool_id": "b8f5ff8ce57fe80cb4ffaeb6e290380094d1fa01016b0154e204a5a0", "stake": 0, "is_persistent": false }, { - "pool_id": "9a453c8df82bffc7d689000bdc7ef50d6de0ae20c601be6ed34f7150", + "pool_id": "be37348ef3c8ab12541d008a848d0100449f8992013a0197b204b3b7", "stake": 0, "is_persistent": false }, { - "pool_id": "9a780054ff9f0d133a21e1830af5944b2168f7577790eedb3d01752d", + "pool_id": "c200115f1d2248f470012dfff6df0b921602e93fd8eff38eea30ffe2", "stake": 0, "is_persistent": false }, { - "pool_id": "9c67c57acd01a8e832000d00184438b80f9342b162c401c1a1be12e3", + "pool_id": "c3b822a5961ef78b091206a8dcf750d48470b71adb46d21d652221cb", "stake": 0, "is_persistent": false }, { - "pool_id": "a05bff2f497264b839ab77016b7cff8b371a36ccb643ff0500b629d9", + "pool_id": "c555e91c3c7ad44a24b39a609ce6320e4f791c73006aa1ff8beaa27a", "stake": 0, "is_persistent": false }, { - "pool_id": "a1d4f80d5a0c3b0df1c53100d60000e6d3852a4d4d2d7d843c22a437", + "pool_id": "c5e0157840b431e37609a9d50f4cb894e9565dbda4e47bd2ffe1c579", "stake": 0, "is_persistent": false }, { - "pool_id": "a1e427086d013db41e1c5097ef6c60c76531a5313e29c037f49757eb", + "pool_id": "ca509072b97a359e9c4d0b9965f43f91cf01badd2adfef5b92d182e2", "stake": 0, "is_persistent": false }, { - "pool_id": "a4e29acfb9642ee43438b0513c2a96ef4def8262d069c3acd2462501", + "pool_id": "cc01ffa7295ed9bc1a0088f11036bda6207c8ffa953d96b9632876fc", "stake": 0, "is_persistent": false }, { - "pool_id": "afe159d207188718330fff49e3ffa0d27b834cb49b6bf656ff8f0154", + "pool_id": "cd59c4100e56a495ff6b601bf54f73b184ce16a2ffae965c36ebc9b6", "stake": 0, "is_persistent": false }, { - "pool_id": "bc5cbcfb2fd1ecbebf89daa69c579108a91b13ff95ae8946ff290340", + "pool_id": "df01fa63958c47c3a980a5e359b491b7b30ec6a16be10188a7850cba", "stake": 0, "is_persistent": false }, { - "pool_id": "c2b4ccea0d400dee7e42defe8a983648db60ff54103f6871fc07f5e3", + "pool_id": "e0d4796c1e839ee0f6cbc30ebf612206e258addfcec7091041ff393a", "stake": 0, "is_persistent": false }, { - "pool_id": "cb80746a9eb1765bb7490080a3012d0e010936c16ea6923c551e3b72", + "pool_id": "e2bcb1dc01c43d5ffd88321d01054fff2e164a797f056bb089390038", "stake": 0, "is_persistent": false }, { - "pool_id": "d6dcab7100bf65a073e5003e0cf162cfc22bdbd6c971cb70ee0b3ca7", + "pool_id": "e4e12d25b5158aa86a9fad026702835c0060f3ffd6ffdbc537e02fd3", "stake": 0, "is_persistent": false }, { - "pool_id": "e0e9010efff096cb8e8dd7cc6d01dbf70c362755da9618f63f26e235", + "pool_id": "e5809e783436a02e5529b900ffff1600d1c25eb935f1f9f4907a3300", "stake": 0, "is_persistent": false }, { - "pool_id": "ee7b6eff6f754401ffc74f6d14a6087eebe1a80100ff8dbf60dd9ca5", + "pool_id": "e84acc16a9470098f3007844976401019217f99ca565a301ec6f6401", "stake": 0, "is_persistent": false }, { - "pool_id": "f42f010142a697076ea5f42296b10f45cdca1087df799ca8ef8a195c", + "pool_id": "f57ded9f084058a247f274ff8cfeed013774b4b5a993badef13d26d0", "stake": 0, "is_persistent": false }, { - "pool_id": "fecb57048d31890103990628480100b05211e497adc8dd2ddd5af14f", + "pool_id": "f6d22048980001e100166713014a9cba3e90ff8cecb6ccb0405c6da0", "stake": 0, "is_persistent": false }, { - "pool_id": "ff03ff27010810b9c9fd4006d59eb200ae937ce98ef499967cb80100", + "pool_id": "ff0d46fb0e55505106f2c507e252f1a1b4d401e4df369ad0e5ed5db1", "stake": 0, "is_persistent": false }, { - "pool_id": "ff14b167c2f0d174457ac4bda2b2c91a18b77ea16232cd0001de80f2", + "pool_id": "ffd271d2cc05518088726b58fbc40c3b53efb000ff9bf0bca7a9ef8f", "stake": 0, "is_persistent": false }, { - "pool_id": "ff330da5a8f6da32801d8f923d07ff6ec2640157fc483f34f1a483e7", + "pool_id": "fff6ab31d94ddcb37334ec6a5c01016130018dffa3b86f010dc8fb3a", "stake": 0, "is_persistent": false } ], "persistent_map": { - "0": "297d513e8a16fface8ffb11d535e89d0f4ca996ece5a8c2dd7395372", - "1": "e7ca79019900d2ff1139d3fae05eff7fc474019386f101ffc7a413d1", - "2": "510000bd0d89608c5f480100db9abac7ad2c011e8891d8603704d8f1", - "3": "f640a7267abc27ff5399b59b783195003b59bd699c7de7b1a94b90e7", - "4": "2bffe13687b1d07dd6a5a8c0e4f64f7c565b6b07634bde1cf5cde490", - "5": "4014004f403600cc7835b95fa6597a1daf75ee91d2f8a489c4cc4621", - "6": "0caaf065c5e8e01e9c9b01ffee6d5d28166daf9e9f013e7c59f5eea0", - "7": "2e3fff190094ec4b1e555381f10132cd8503bcfab77ac69f31061f80", - "8": "be7f491f0142d68fbe5db88f81f3611a003e1860df76ff4c993bff55", - "9": "80576f0db3dd223800c04d529cec22a7a4e95685fb44b3248579d129", - "10": "6b33e699e62bb5e9a253e94001b2e0008928af07480180efacf19eff", - "11": "1fac9270dce82a31acdb0044ea2bc33001bccfa733a8343ab508f8db", - "12": "010000ea011f3b5f560000c3b7ff09099b00cee6c904ff8db5be05af", - "13": "ced712e76857ae0301394b9f80d9883aa915438f3a634948e561cb1c", - "14": "68ff8120d80176efacffac2b21455d940a19c1c9d7015715015a2ac1", - "15": "8ed5d017d237129da3415d93c29bf235b802fa0d1b326d0e00943e7d", - "16": "f1ffb84f6cd5961467690750815dc6fe4528e47c10b5029c62aa017d", - "17": "f9ffdfc4cfd3aeb25b759621d4dad6c8cbc6d330d3e865f6e2ccdccd", - "18": "2859011646cb69e9af2b00f8a6e99a5e98c315c30d3e0950e2f936d6", - "19": "ba2e466a8e04eae7d9601b280bff7730395b62f753fc4dc9daac6551", - "20": "55077dbd0017ac00133d4d451fee91ffd732ae0f0028ee91d1f4fe25", - "21": "71e9fdd001726ccdce084db3f5d19175c88d7a6ffe72fb7bc7ff9fca", - "22": "ff017f87e40030d332e3a201790cb4d191f77cff402359afbfc3f133", - "23": "2c01ff0bfee051d6f9a0542f83d86f8ace9400a0bbae83125124ad6a", - "24": "a6f18a62cd970f4101011eff07f2ab0924c682d95701a591b2703900", - "25": "01b517a3397cf1f2fcffb3de23e53b356cff3000ff8175ff15ce46b5", - "26": "2a0197eb60069d9b9674b36d01c05633c7fd9fd9ff62ac440101e1b4", - "27": "ab65112ee36236dfb0cbe04901dd8664aa53e14e3a2201e8ba43010f", - "28": "42ff05ff217a522a4d0157b811f728ee479ddeeeb6bb791fff00adb4", - "29": "4a0814a0d39aff838261015cd7696a86d5bc4893ff334d9011f05dff", - "30": "19168e8ba075a44ce41eaacc0ba82193fbfdff79adfaef8075e96c25", - "31": "b0a969ed896461627b2d580817d6b723ffdd5b57eff345d2ff0dcf1f", - "32": "811e1c4eb5acb7088b384601455e118c0501da7e579807ed6f1042cb", - "33": "ebe0eb00bf42788a8cd370e5260166436500a39210f516b5d9ebdc8b", - "34": "e534f8d3582a76a57f5c28003af6cdf1c3bcd2a977e27c3999f1f9f3", - "35": "73efe21dcd67fb690069dfbd0430002c993fe43d775ed60166b93b5b", - "36": "4e70a101cb06c0005072c580645652ff9d5e47f717004ccaba9582ad", - "37": "500385315cff8eb2e06b532fadaaa001928b08049da30ce6ff93b769", - "38": "d7fffa2e2dd3efe4ae003687be09ff11a53cff8c9d36d4130cb4b5fc", - "39": "3b96fb5124d0df7e90440fa9ff5501bfab014b31db694c01b03ddbbe", - "40": "c140043b5a723fe346f700bf44f4b40a8c4f324dbe17b5ac01e3b45e", - "41": "db5640fd0014ee5c43b3498573454314eeeb89ffbf001f5724e3ff1b", - "42": "90d495ff0101155eb09ed7ebb1760016b444316a202eff090b82a0c9", - "43": "58ffff4bd20fc2838e882341014f43f2b5a22934207a2c4f7a57e409", - "44": "ff49416cbb7e6309ef340b8702deb8cf8d77019b1cc62cf76c3b426e", - "45": "a82c016500d5ffb7845300684039d9628352c61d6f45d7c1d0d58941", - "46": "a69cf8710100aba9476c695c78fcbdcb9b89a84e0191695d2a5dcc47", - "47": "0a754dfc0100b004638dbd846abe3ec155c82b0f8dee5f5e6c00d9ff", - "48": "f3aacec30955e959ce047c75ffdea67b77d8b9fffab3aa01ffde0dcb", - "49": "443e7518ab3045e4ff88cb23c4a334d997747e24a64e9ea34f247505", - "50": "6733785fcc6301e8e58df40e90ed37ee1ef53cb05179007300d31564", - "51": "1ecb00477c12ef001979a45884f823fac7c2572527fa810c9e0e7aea", - "52": "01aa219b77ffff26b05caa247625319fc2673c3bfadb0136dc14a604", - "53": "9d8a72e60df13e75575c00947192a3d493014bf5ce4f4f00a000370b", - "54": "75d82022f485bcedb6b38c3a01016bd7050fdc9edc2f7fbaa09a40bc" + "0": "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c", + "1": "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647", + "2": "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c", + "3": "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529", + "4": "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d", + "5": "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479", + "6": "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb", + "7": "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1", + "8": "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0", + "9": "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874", + "10": "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef", + "11": "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff", + "12": "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c", + "13": "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137", + "14": "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f", + "15": "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e", + "16": "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8", + "17": "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07", + "18": "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff", + "19": "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be", + "20": "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de", + "21": "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000", + "22": "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155", + "23": "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2", + "24": "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c", + "25": "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58", + "26": "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff", + "27": "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376", + "28": "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551", + "29": "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297", + "30": "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01", + "31": "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc", + "32": "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966", + "33": "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938", + "34": "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf", + "35": "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf", + "36": "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8", + "37": "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11", + "38": "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416", + "39": "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283", + "40": "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64", + "41": "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8", + "42": "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99", + "43": "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d", + "44": "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c", + "45": "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3", + "46": "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690", + "47": "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff", + "48": "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601", + "49": "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2", + "50": "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b", + "51": "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2", + "52": "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201", + "53": "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7", + "54": "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b" }, "committee": [ { + "position": 1, "index": 1, - "pool_id": "297d513e8a16fface8ffb11d535e89d0f4ca996ece5a8c2dd7395372", - "stake": 4558 + "pool_id": "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c", + "stake": 4530 }, { + "position": 2, "index": 2, - "pool_id": "e7ca79019900d2ff1139d3fae05eff7fc474019386f101ffc7a413d1", - "stake": 4436 + "pool_id": "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647", + "stake": 3917 }, { + "position": 3, "index": 3, - "pool_id": "510000bd0d89608c5f480100db9abac7ad2c011e8891d8603704d8f1", - "stake": 3861 + "pool_id": "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c", + "stake": 3892 }, { + "position": 4, "index": 4, - "pool_id": "f640a7267abc27ff5399b59b783195003b59bd699c7de7b1a94b90e7", - "stake": 3666 + "pool_id": "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529", + "stake": 3681 }, { + "position": 5, "index": 5, - "pool_id": "2bffe13687b1d07dd6a5a8c0e4f64f7c565b6b07634bde1cf5cde490", - "stake": 3404 + "pool_id": "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d", + "stake": 3081 }, { + "position": 6, "index": 6, - "pool_id": "4014004f403600cc7835b95fa6597a1daf75ee91d2f8a489c4cc4621", - "stake": 3261 + "pool_id": "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479", + "stake": 2982 }, { + "position": 7, "index": 7, - "pool_id": "0caaf065c5e8e01e9c9b01ffee6d5d28166daf9e9f013e7c59f5eea0", - "stake": 3209 + "pool_id": "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb", + "stake": 2904 }, { + "position": 8, "index": 8, - "pool_id": "2e3fff190094ec4b1e555381f10132cd8503bcfab77ac69f31061f80", - "stake": 3208 + "pool_id": "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1", + "stake": 2901 }, { + "position": 9, "index": 9, - "pool_id": "be7f491f0142d68fbe5db88f81f3611a003e1860df76ff4c993bff55", - "stake": 3002 + "pool_id": "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0", + "stake": 2827 }, { + "position": 10, "index": 10, - "pool_id": "80576f0db3dd223800c04d529cec22a7a4e95685fb44b3248579d129", - "stake": 2818 + "pool_id": "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874", + "stake": 2714 }, { + "position": 11, "index": 11, - "pool_id": "6b33e699e62bb5e9a253e94001b2e0008928af07480180efacf19eff", - "stake": 2696 + "pool_id": "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef", + "stake": 2654 }, { + "position": 12, "index": 12, - "pool_id": "1fac9270dce82a31acdb0044ea2bc33001bccfa733a8343ab508f8db", - "stake": 2581 + "pool_id": "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff", + "stake": 2635 }, { + "position": 13, "index": 13, - "pool_id": "010000ea011f3b5f560000c3b7ff09099b00cee6c904ff8db5be05af", - "stake": 2563 + "pool_id": "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c", + "stake": 2602 }, { + "position": 14, "index": 14, - "pool_id": "ced712e76857ae0301394b9f80d9883aa915438f3a634948e561cb1c", - "stake": 2394 + "pool_id": "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137", + "stake": 2537 }, { + "position": 15, "index": 15, - "pool_id": "68ff8120d80176efacffac2b21455d940a19c1c9d7015715015a2ac1", - "stake": 2347 + "pool_id": "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f", + "stake": 2432 }, { + "position": 16, "index": 16, - "pool_id": "8ed5d017d237129da3415d93c29bf235b802fa0d1b326d0e00943e7d", - "stake": 2340 + "pool_id": "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e", + "stake": 2182 }, { + "position": 17, "index": 17, - "pool_id": "f1ffb84f6cd5961467690750815dc6fe4528e47c10b5029c62aa017d", - "stake": 2300 + "pool_id": "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8", + "stake": 2080 }, { + "position": 18, "index": 18, - "pool_id": "f9ffdfc4cfd3aeb25b759621d4dad6c8cbc6d330d3e865f6e2ccdccd", - "stake": 2265 + "pool_id": "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07", + "stake": 1994 }, { + "position": 19, "index": 19, - "pool_id": "2859011646cb69e9af2b00f8a6e99a5e98c315c30d3e0950e2f936d6", - "stake": 2160 + "pool_id": "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff", + "stake": 1800 }, { + "position": 20, "index": 20, - "pool_id": "ba2e466a8e04eae7d9601b280bff7730395b62f753fc4dc9daac6551", - "stake": 2140 + "pool_id": "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be", + "stake": 1796 }, { + "position": 21, "index": 21, - "pool_id": "55077dbd0017ac00133d4d451fee91ffd732ae0f0028ee91d1f4fe25", - "stake": 1950 + "pool_id": "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de", + "stake": 1752 }, { + "position": 22, "index": 22, - "pool_id": "71e9fdd001726ccdce084db3f5d19175c88d7a6ffe72fb7bc7ff9fca", - "stake": 1570 + "pool_id": "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000", + "stake": 1721 }, { + "position": 23, "index": 23, - "pool_id": "ff017f87e40030d332e3a201790cb4d191f77cff402359afbfc3f133", - "stake": 1412 + "pool_id": "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155", + "stake": 1689 }, { + "position": 24, "index": 24, - "pool_id": "2c01ff0bfee051d6f9a0542f83d86f8ace9400a0bbae83125124ad6a", - "stake": 1365 + "pool_id": "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2", + "stake": 1686 }, { + "position": 25, "index": 25, - "pool_id": "a6f18a62cd970f4101011eff07f2ab0924c682d95701a591b2703900", - "stake": 1358 + "pool_id": "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c", + "stake": 1621 }, { + "position": 26, "index": 26, - "pool_id": "01b517a3397cf1f2fcffb3de23e53b356cff3000ff8175ff15ce46b5", - "stake": 1283 + "pool_id": "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58", + "stake": 1610 }, { + "position": 27, "index": 27, - "pool_id": "2a0197eb60069d9b9674b36d01c05633c7fd9fd9ff62ac440101e1b4", - "stake": 1275 + "pool_id": "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff", + "stake": 1506 }, { + "position": 28, "index": 28, - "pool_id": "ab65112ee36236dfb0cbe04901dd8664aa53e14e3a2201e8ba43010f", - "stake": 1238 + "pool_id": "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376", + "stake": 1470 }, { + "position": 29, "index": 29, - "pool_id": "42ff05ff217a522a4d0157b811f728ee479ddeeeb6bb791fff00adb4", - "stake": 1209 + "pool_id": "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551", + "stake": 1463 }, { + "position": 30, "index": 30, - "pool_id": "4a0814a0d39aff838261015cd7696a86d5bc4893ff334d9011f05dff", - "stake": 1186 + "pool_id": "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297", + "stake": 1362 }, { + "position": 31, "index": 31, - "pool_id": "19168e8ba075a44ce41eaacc0ba82193fbfdff79adfaef8075e96c25", - "stake": 1179 + "pool_id": "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01", + "stake": 1319 }, { + "position": 32, "index": 32, - "pool_id": "b0a969ed896461627b2d580817d6b723ffdd5b57eff345d2ff0dcf1f", - "stake": 1153 + "pool_id": "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc", + "stake": 1294 }, { + "position": 33, "index": 33, - "pool_id": "811e1c4eb5acb7088b384601455e118c0501da7e579807ed6f1042cb", - "stake": 1126 + "pool_id": "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966", + "stake": 1279 }, { + "position": 34, "index": 34, - "pool_id": "ebe0eb00bf42788a8cd370e5260166436500a39210f516b5d9ebdc8b", - "stake": 1107 + "pool_id": "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938", + "stake": 1146 }, { + "position": 35, "index": 35, - "pool_id": "e534f8d3582a76a57f5c28003af6cdf1c3bcd2a977e27c3999f1f9f3", - "stake": 1091 + "pool_id": "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf", + "stake": 1066 }, { + "position": 36, "index": 36, - "pool_id": "73efe21dcd67fb690069dfbd0430002c993fe43d775ed60166b93b5b", - "stake": 1065 + "pool_id": "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf", + "stake": 1059 }, { + "position": 37, "index": 37, - "pool_id": "4e70a101cb06c0005072c580645652ff9d5e47f717004ccaba9582ad", - "stake": 1040 + "pool_id": "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8", + "stake": 1045 }, { + "position": 38, "index": 38, - "pool_id": "500385315cff8eb2e06b532fadaaa001928b08049da30ce6ff93b769", - "stake": 1018 + "pool_id": "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11", + "stake": 1016 }, { + "position": 39, "index": 39, - "pool_id": "d7fffa2e2dd3efe4ae003687be09ff11a53cff8c9d36d4130cb4b5fc", - "stake": 934 + "pool_id": "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416", + "stake": 970 }, { + "position": 40, "index": 40, - "pool_id": "3b96fb5124d0df7e90440fa9ff5501bfab014b31db694c01b03ddbbe", - "stake": 908 + "pool_id": "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283", + "stake": 967 }, { + "position": 41, "index": 41, - "pool_id": "c140043b5a723fe346f700bf44f4b40a8c4f324dbe17b5ac01e3b45e", - "stake": 893 + "pool_id": "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64", + "stake": 935 }, { + "position": 42, "index": 42, - "pool_id": "db5640fd0014ee5c43b3498573454314eeeb89ffbf001f5724e3ff1b", - "stake": 750 + "pool_id": "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8", + "stake": 891 }, { + "position": 43, "index": 43, - "pool_id": "90d495ff0101155eb09ed7ebb1760016b444316a202eff090b82a0c9", - "stake": 746 + "pool_id": "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99", + "stake": 793 }, { + "position": 44, "index": 44, - "pool_id": "58ffff4bd20fc2838e882341014f43f2b5a22934207a2c4f7a57e409", - "stake": 717 + "pool_id": "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d", + "stake": 665 }, { + "position": 45, "index": 45, - "pool_id": "ff49416cbb7e6309ef340b8702deb8cf8d77019b1cc62cf76c3b426e", - "stake": 698 + "pool_id": "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c", + "stake": 640 }, { + "position": 46, "index": 46, - "pool_id": "a82c016500d5ffb7845300684039d9628352c61d6f45d7c1d0d58941", - "stake": 642 + "pool_id": "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3", + "stake": 638 }, { + "position": 47, "index": 47, - "pool_id": "a69cf8710100aba9476c695c78fcbdcb9b89a84e0191695d2a5dcc47", - "stake": 631 + "pool_id": "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690", + "stake": 611 }, { + "position": 48, "index": 48, - "pool_id": "0a754dfc0100b004638dbd846abe3ec155c82b0f8dee5f5e6c00d9ff", - "stake": 568 + "pool_id": "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff", + "stake": 598 }, { + "position": 49, "index": 49, - "pool_id": "f3aacec30955e959ce047c75ffdea67b77d8b9fffab3aa01ffde0dcb", - "stake": 560 + "pool_id": "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601", + "stake": 589 }, { + "position": 50, "index": 50, - "pool_id": "443e7518ab3045e4ff88cb23c4a334d997747e24a64e9ea34f247505", - "stake": 539 + "pool_id": "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2", + "stake": 561 }, { + "position": 51, "index": 51, - "pool_id": "6733785fcc6301e8e58df40e90ed37ee1ef53cb05179007300d31564", - "stake": 533 + "pool_id": "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b", + "stake": 548 }, { + "position": 52, "index": 52, - "pool_id": "1ecb00477c12ef001979a45884f823fac7c2572527fa810c9e0e7aea", - "stake": 498 + "pool_id": "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2", + "stake": 474 }, { + "position": 53, "index": 53, - "pool_id": "01aa219b77ffff26b05caa247625319fc2673c3bfadb0136dc14a604", - "stake": 495 + "pool_id": "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201", + "stake": 451 }, { + "position": 54, "index": 54, - "pool_id": "9d8a72e60df13e75575c00947192a3d493014bf5ce4f4f00a000370b", - "stake": 430 + "pool_id": "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7", + "stake": 441 }, { + "position": 55, "index": 55, - "pool_id": "75d82022f485bcedb6b38c3a01016bd7050fdc9edc2f7fbaa09a40bc", - "stake": 415 + "pool_id": "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b", + "stake": 424 }, { - "index": 56, - "pool_id": "d252010f5c7a970bb92626da9c8e2280921e997c83ee499148bb5101", - "stake": 358 + "position": 56, + "index": 70, + "pool_id": "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3", + "stake": 215 }, { - "index": 57, - "pool_id": "9adf3ed0bcc79e9b2f9781e209fdd604132868b5ba9e7bb26cff6c5b", - "stake": 353 + "position": 57, + "index": 81, + "pool_id": "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a", + "stake": 104 }, { - "index": 58, - "pool_id": "2b9199fadb0a912d78b7010e12ff6298aec7000d000462b5b061e4f1", - "stake": 315 + "position": 58, + "index": 62, + "pool_id": "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631", + "stake": 304 }, { - "index": 59, - "pool_id": "b839bc8cf9f25702013e019bd0d3318c380097e105f9915b653192cb", - "stake": 297 + "position": 59, + "index": 85, + "pool_id": "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6", + "stake": 86 }, { - "index": 60, - "pool_id": "2b6edf630000468d9dc8f60fffcb9281957c01f6dc945a6a764689f1", - "stake": 294 + "position": 60, + "index": 93, + "pool_id": "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9", + "stake": 57 }, { - "index": 61, - "pool_id": "bb2ada9401fb84d1a407a8d6a95dfad6a4f30f40bc74ff6fe85a1f77", - "stake": 292 + "position": 61, + "index": 103, + "pool_id": "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571", + "stake": 21 }, { - "index": 62, - "pool_id": "edcd00b778815439000046ac0036806b2e49e5208b97f2373eefa399", - "stake": 290 + "position": 62, + "index": 72, + "pool_id": "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff", + "stake": 179 }, { - "index": 63, - "pool_id": "1aaa13bf4f51da2001ac5d0059d846cbdce7ebb95718c9a3dc4b8313", - "stake": 285 + "position": 63, + "index": 75, + "pool_id": "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7", + "stake": 154 }, { - "index": 64, - "pool_id": "aac1f0971050401758f1597d75d7ff429661fd80f2ae7efddcff2b44", - "stake": 282 + "position": 64, + "index": 86, + "pool_id": "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2", + "stake": 86 } ], + "committee_source": "fa+certificate", "lookup": { "universe_index_by_pool_id": { - "297d513e8a16fface8ffb11d535e89d0f4ca996ece5a8c2dd7395372": 0, - "e7ca79019900d2ff1139d3fae05eff7fc474019386f101ffc7a413d1": 1, - "510000bd0d89608c5f480100db9abac7ad2c011e8891d8603704d8f1": 2, - "f640a7267abc27ff5399b59b783195003b59bd699c7de7b1a94b90e7": 3, - "2bffe13687b1d07dd6a5a8c0e4f64f7c565b6b07634bde1cf5cde490": 4, - "4014004f403600cc7835b95fa6597a1daf75ee91d2f8a489c4cc4621": 5, - "0caaf065c5e8e01e9c9b01ffee6d5d28166daf9e9f013e7c59f5eea0": 6, - "2e3fff190094ec4b1e555381f10132cd8503bcfab77ac69f31061f80": 7, - "be7f491f0142d68fbe5db88f81f3611a003e1860df76ff4c993bff55": 8, - "80576f0db3dd223800c04d529cec22a7a4e95685fb44b3248579d129": 9, - "6b33e699e62bb5e9a253e94001b2e0008928af07480180efacf19eff": 10, - "1fac9270dce82a31acdb0044ea2bc33001bccfa733a8343ab508f8db": 11, - "010000ea011f3b5f560000c3b7ff09099b00cee6c904ff8db5be05af": 12, - "ced712e76857ae0301394b9f80d9883aa915438f3a634948e561cb1c": 13, - "68ff8120d80176efacffac2b21455d940a19c1c9d7015715015a2ac1": 14, - "8ed5d017d237129da3415d93c29bf235b802fa0d1b326d0e00943e7d": 15, - "f1ffb84f6cd5961467690750815dc6fe4528e47c10b5029c62aa017d": 16, - "f9ffdfc4cfd3aeb25b759621d4dad6c8cbc6d330d3e865f6e2ccdccd": 17, - "2859011646cb69e9af2b00f8a6e99a5e98c315c30d3e0950e2f936d6": 18, - "ba2e466a8e04eae7d9601b280bff7730395b62f753fc4dc9daac6551": 19, - "55077dbd0017ac00133d4d451fee91ffd732ae0f0028ee91d1f4fe25": 20, - "71e9fdd001726ccdce084db3f5d19175c88d7a6ffe72fb7bc7ff9fca": 21, - "ff017f87e40030d332e3a201790cb4d191f77cff402359afbfc3f133": 22, - "2c01ff0bfee051d6f9a0542f83d86f8ace9400a0bbae83125124ad6a": 23, - "a6f18a62cd970f4101011eff07f2ab0924c682d95701a591b2703900": 24, - "01b517a3397cf1f2fcffb3de23e53b356cff3000ff8175ff15ce46b5": 25, - "2a0197eb60069d9b9674b36d01c05633c7fd9fd9ff62ac440101e1b4": 26, - "ab65112ee36236dfb0cbe04901dd8664aa53e14e3a2201e8ba43010f": 27, - "42ff05ff217a522a4d0157b811f728ee479ddeeeb6bb791fff00adb4": 28, - "4a0814a0d39aff838261015cd7696a86d5bc4893ff334d9011f05dff": 29, - "19168e8ba075a44ce41eaacc0ba82193fbfdff79adfaef8075e96c25": 30, - "b0a969ed896461627b2d580817d6b723ffdd5b57eff345d2ff0dcf1f": 31, - "811e1c4eb5acb7088b384601455e118c0501da7e579807ed6f1042cb": 32, - "ebe0eb00bf42788a8cd370e5260166436500a39210f516b5d9ebdc8b": 33, - "e534f8d3582a76a57f5c28003af6cdf1c3bcd2a977e27c3999f1f9f3": 34, - "73efe21dcd67fb690069dfbd0430002c993fe43d775ed60166b93b5b": 35, - "4e70a101cb06c0005072c580645652ff9d5e47f717004ccaba9582ad": 36, - "500385315cff8eb2e06b532fadaaa001928b08049da30ce6ff93b769": 37, - "d7fffa2e2dd3efe4ae003687be09ff11a53cff8c9d36d4130cb4b5fc": 38, - "3b96fb5124d0df7e90440fa9ff5501bfab014b31db694c01b03ddbbe": 39, - "c140043b5a723fe346f700bf44f4b40a8c4f324dbe17b5ac01e3b45e": 40, - "db5640fd0014ee5c43b3498573454314eeeb89ffbf001f5724e3ff1b": 41, - "90d495ff0101155eb09ed7ebb1760016b444316a202eff090b82a0c9": 42, - "58ffff4bd20fc2838e882341014f43f2b5a22934207a2c4f7a57e409": 43, - "ff49416cbb7e6309ef340b8702deb8cf8d77019b1cc62cf76c3b426e": 44, - "a82c016500d5ffb7845300684039d9628352c61d6f45d7c1d0d58941": 45, - "a69cf8710100aba9476c695c78fcbdcb9b89a84e0191695d2a5dcc47": 46, - "0a754dfc0100b004638dbd846abe3ec155c82b0f8dee5f5e6c00d9ff": 47, - "f3aacec30955e959ce047c75ffdea67b77d8b9fffab3aa01ffde0dcb": 48, - "443e7518ab3045e4ff88cb23c4a334d997747e24a64e9ea34f247505": 49, - "6733785fcc6301e8e58df40e90ed37ee1ef53cb05179007300d31564": 50, - "1ecb00477c12ef001979a45884f823fac7c2572527fa810c9e0e7aea": 51, - "01aa219b77ffff26b05caa247625319fc2673c3bfadb0136dc14a604": 52, - "9d8a72e60df13e75575c00947192a3d493014bf5ce4f4f00a000370b": 53, - "75d82022f485bcedb6b38c3a01016bd7050fdc9edc2f7fbaa09a40bc": 54, - "d252010f5c7a970bb92626da9c8e2280921e997c83ee499148bb5101": 55, - "9adf3ed0bcc79e9b2f9781e209fdd604132868b5ba9e7bb26cff6c5b": 56, - "2b9199fadb0a912d78b7010e12ff6298aec7000d000462b5b061e4f1": 57, - "b839bc8cf9f25702013e019bd0d3318c380097e105f9915b653192cb": 58, - "2b6edf630000468d9dc8f60fffcb9281957c01f6dc945a6a764689f1": 59, - "bb2ada9401fb84d1a407a8d6a95dfad6a4f30f40bc74ff6fe85a1f77": 60, - "edcd00b778815439000046ac0036806b2e49e5208b97f2373eefa399": 61, - "1aaa13bf4f51da2001ac5d0059d846cbdce7ebb95718c9a3dc4b8313": 62, - "aac1f0971050401758f1597d75d7ff429661fd80f2ae7efddcff2b44": 63, - "2c807bb6431eba01bafdccb7ed9724ffe4a4ff9b60e0499b0e66c82e": 64, - "00b76527d1f8f75a1c8bc8d47737a1456f39e900f92ddc360a44035d": 65, - "caffe8edb5c9fa60ff0101934957014a489501b91b1785f9bfc07b91": 66, - "c3d75a8b44f1bca7dfdd77a7bd837e5c3fca45ffcdffaff54f950023": 67, - "3d38fc9218cc6ee9a2bbff011ed815fcb156d14211c11599be5ba788": 68, - "01503a091fdf01a1fe93bb8cd54e858e11efcb3062ffc08fe5551c61": 69, - "a9b7d4687b76cfaaf5336bd1b8056a9801d64d472e62ff46d03128a0": 70, - "ffe264724801dbe3c900838152e884435c57e51a8ef9a701028a3969": 71, - "cf8a0ae2ff175c5dddb4b3f82bde410a62e6284c3eb8b0c95a001e77": 72, - "4e01238c2f8994651251c1f3009331e9e38602f0b3c6ff5c78ff86eb": 73, - "30010fecff2471437901d979b0949fdf0029888ca985041359957bfe": 74, - "0083ff8b27e6e9ff193bab599900507f97627b3696e5cd0101a65a12": 75, - "6b5ecc2d7900f8732d944bff0031f84a59dc015bffd801a0bc3a01d1": 76, - "207309e066ffaf681d01ec627f6b26eb017401568fe788b73101e470": 77, - "001cb6d54c00316a5bdc38c25dec0002805d01275300c8556ebcc3f0": 78, - "531fd03400ff89603a49cf1292c85bdd014aac24b4b06cac87bb35cc": 79, - "ee0e8f64875d4573c839c801a2ff844d822f0c0a8583efabe800bee8": 80, - "5756abb4f757ff01cb5b72028c01f5b9c38acefd6a6e13fd58019595": 81, - "4d01b0bcc8c8006300a331d71fe30b1e4d25bf9977015fdaac58e496": 82, - "1af27c3f21e7009c0aac1f653901718fc24f46c80b1001086ebbc000": 83, - "dc9000e798a70e9582018179fb1dedf3ff439929b54f01c62300ba34": 84, - "335701a8a706df54592e06d0b0a6303f86c8a0cf45160f4233063a23": 85, - "ff435e46f95900e77300c809c9acbee7be000a177d27af8e5677c1d0": 86, - "80e59d860f7af300c8fa0a0085381e98ffc016c55201f4e07903c5b5": 87, - "0101caf77c2455864e765290680006008ea882ff7119336178d570d6": 88, - "b0c6e4ac306cee2a93d0883901ff024056298dbfe3caff01651a5f40": 89, - "01c7fff54615ff017aeefa017a436f01018062949601e3c1e17361d3": 90, - "86341e8327010418ec386fc2472b68faeabd07e601ca3c4a9c60dcd9": 91, - "ba50ebe1716c7bc601769781f4d001f60f2b01a500637393d5d71c1e": 92, - "0563d9b90133ff2a61b0db2d654cbd8b34845d4550582f3799c047b4": 93, - "000d0069f82085e3f6239016124290523a41a616a849bab01f098a39": 94, - "265f28934f7ebd02014e9e301300ff940490a806f824a063f4dd0006": 95, - "02a542e643ed8181ff0094d801520de5de6eab31e93af8b98ab4ab16": 96, - "f4a9c6af00ff7f38fca05fbb30c612201d0747e85d46c9c739feec61": 97, - "fa44e912ce3e0138976ccd75a43ed91b005a2b81382a55fe30457001": 98, - "0f45ccc70009f917c37ce778ffd15e6feb94301501ae61470013ab95": 99, - "ae763c00f5eb985709e87c7052eefcef8c6e2e00053ae43dbbadde23": 100, - "0e49da3e75c8da00da48bf4f44ad6a6773d12ea6e2fbd06ac4708498": 101, - "7da0a5ff9b13a7fb37f086c7ffff23eb6e9f22ecb737cf847ed0f54f": 102, - "7adca68c6dae1a9ffd8e750e365724ff1973e8000bc5d414be96ddd8": 103, - "b4795c951bdc6eb4b4fff0e7fb58a0ed66019b6feeb3d672f5d5fe4c": 104, - "355d06bb68721a3f32012d7e01cdce721fff534b64daac0cf4c81154": 105, - "c31868f06551db9fb49efa008c986ad74f016e5e7b16ebfa974f2638": 106, - "f9b715e13a124f5020a11f2dca0100a701fbc2d464717ea7cbff563e": 107, - "8017be2c179b0b006b994cc3e360ff4a01855bffccff265f15dec969": 108, - "d97dec3dced0d17cff6e28ff0164475d66d4ff408bef00b06dd47514": 109, - "18daffe3f32a85016a9d3b191ff243bfa00126d57bf7c96ce3740160": 110, - "637ea1112b52012500377800fcbe2d630d49f7d112cfd811062c0001": 111, - "d4559298046806732261ff121033bfcad7727f2e3fcd19fa7d92f221": 112, - "3963d9c18518b0ed37d5e2f13a761f42ca42834400351d07ab006e88": 113, - "f8095955680192bd0b5c1fd1fb097e77d4009a3528aa1da5b03f01bb": 114, - "0de3e268b7a37d6be7858651fcb0e4471aaa004d107101be225b52b2": 115, - "20ecc3af4979fdd8182e70d95fff01a000010b269a07e6eb4b2110a5": 116, - "93a2f528ea475c20765525ae00370aeea70db47dff0d80516f25b5a5": 117, - "99ed0aeb8fecf0ff4f005bb2e314bd82009a306da0b5d2005af20e2d": 118, - "a7000014ff9c2d4d01ff650cb658a2ab68de94017955252b452cbb67": 119, - "478d8eed13452a013ed5cacffadcca01b0515c1de41e956a4c008200": 120, - "7888ffdcd24591bbcd6435a6e45b2927070a691221c710d04c4a1462": 121, - "ff25ff7affffe9ffb4acff8830154f6c5ae61cca9900ccf9e9a0b9e1": 122, - "1fcd8559ffd00f01118267bdcf2249918fdeeb0045fb01d8d48e3c61": 123, - "54e2bbe6cb0118b4f300e499cdb1d56b7c01e0ff23ae3bb1c98180b9": 124, - "f835c4cf5356f604e3d50f321d3c2b41248f700103762400479d59fc": 125, - "82e98351e11d9cad3affca45e6348fdb1f81055d880013b709be427e": 126, - "bc2e1db28d542ac322050c271aef6c419effaceb9118c59c343ec4e5": 127, - "ccd3b6592176ff005baf7e6fd5eaa0853f219b3f0dbeefe8c801ebc2": 128, - "d8fff0ffe3ff7fd67e59ae471725c4e2af3e6c56f00d012f52620104": 129, - "ec3a4f922b9fecda5bd4746503ff085b6e9f322838644adbb950dffc": 130, - "22277d0101e05d42a527f33785e6b08631c02dcf3de70ba550f5c9ff": 131, - "59006160937cac095bbc4ce7a96a8f3fe5527659008f95b4eefba797": 132, - "71b1a01a93ff410e7bf4391404ce537f5d0d19ba967dc2eec8a636b7": 133, - "98b59653e3bf4859d49bafc5e540abeb8d2339c56232b837ffb833b4": 134, - "ff25ff50f09bee01f10058ffdc596d91dee5f60005b9b3ffa951f879": 135, - "37c401bad06f05d2ec9a77da395b3156372600ffee4f511b6864d02a": 136, - "39010395d8f5e16969d721797bf846618a525e32015b23c7c60176ea": 137, - "49f07c93777ce5720c35c21a48cadb9cfde005015033f3f5b4cb2b88": 138, - "8300897d3c7d6e9a2907335fce58eef00009f22a56e500f46201392f": 139, - "95a79da700235961ff73dc78ff5b6d2401177e79b5b2c6dbef711484": 140, - "c3838dcbac6ee5c64999c3160517199db36edf0a00ffaac9adf22798": 141, - "ff9314d1834e2506f701fcdaeca726b512ff518bc31b8c1136e4c4eb": 142, - "ffcae231011c0d6bdd7546303abb2f1ca253e6f2a9f980008afcafff": 143, - "00b9364927d3ff64bdb81559031af0a79793de4dd18a2004524316cd": 144, - "00b9bd6144ff16889ff63ce92f500e0144e1f3578f34220090c9f8ab": 145, - "01511e82363771cc1101c4409bde01c2ff2d692f58a88eae37cd187a": 146, - "01752201757f364caac686e9e34d20896999bb6064003b1494726af5": 147, - "017e5ea1a154748a90be98ecd0436cac89776ca200029eff3a95de2c": 148, - "10a8c8ec01ed04434f7a463e14ff0a994296e97718a822c46baab88c": 149, - "113e737d14c51476cb5db565ef179759e99a1d44999084108f2866d8": 150, - "13717c14c3207e44d578edff0bf76a2c4e6930990e35a0009970010f": 151, - "14ffc31bfb0110002db1187f26b16bd3482d9fafff519fb0d3b2a38d": 152, - "20dba91387599e09d5606b6aefec981d0f5b0fed8501b579cb8e848f": 153, - "34e8478201ffbeb0ab04fe41eabb095d36ed6e0087340fe617d09955": 154, - "3a06013f8b1a6691aa4d9a2c952b89ce112616774ccd9e24422f09a4": 155, - "3a6e005c411e8ef0c3a2852aec01890dd7ce00bc7e8400b983477701": 156, - "3d007bd3a16b52cb06e5701d5b4466a5007619ccbd19871c77eaf57b": 157, - "50446c4f26ffaf6598c783410bcdde5905354efe00fce6f7375301ff": 158, - "522a8d515c38e3b9fffc8dcb25016b018ebaf1010164012024e0a338": 159, - "52aa09ff004fdd8fc3f149163e7d79257aaf5d4fc3cff7576d029c7b": 160, - "5300231cfdec3f47e44411fc8ebd00f924f45f5420a44d42ffff4681": 161, - "596ebe0738ca1b90cbcbd10a0b804ac20001f94e6474ff384229c14c": 162, - "5abdb5007572b254df05579124c6ff921b0051405af73ea7aba38220": 163, - "5db101e507115c40e4468201231453ad67e1ef7d33ef52e8f601e246": 164, - "5e8b326e867efc6e6af10f22029ed62e10d86bd2ab252f280d1a4f48": 165, - "5ef19efeff7c483e5643b481e40d8af0f6f07bcfffbddd378a6e5407": 166, - "639a0e1530c500d14d4d849c3c95907957896ba98b125e3f00ffd631": 167, - "653debf312a13afba13f2cf426c101292000de0a090ea91e0d3d39dd": 168, - "6c00017cc07aada307933f15167a869f11a101c21deee345d8bf3f25": 169, - "6d1a598dcca7d4c324d50c0940016067507622b6a485252aa60c6969": 170, - "71aace9f096f1101d3fd3b005694ff6242461ccb710ada3800429d4a": 171, - "7500290ce900ff55e0ead88900fcae3b6130aabdce1c82251b864abc": 172, - "7a5b49558d006748d492ec8d7b5a4c3150f101032e05f601b292b4cf": 173, - "7da96f49fff07a7c1e3a000150fe00209b1dff4b7bafecb9d2ce0459": 174, - "7f00a078e500d7d25c47be2cde9531d28b16fcf2e33919fcd9e44901": 175, - "8a8aa178e900bea547c0d05b2aebc95545476a1a1ddf508defec68fa": 176, - "925cff8cc10114e6b4bb6b644ab1515ad1d5f7a14acace906001d9b9": 177, - "9629f17dafd13dd2ff50a10e5875c22b657cd97cff53ff2a2d18a14a": 178, - "9a15ba62b8db62910e9c3912d1343a91437ab701ff24b9656deea4ae": 179, - "9a453c8df82bffc7d689000bdc7ef50d6de0ae20c601be6ed34f7150": 180, - "9a780054ff9f0d133a21e1830af5944b2168f7577790eedb3d01752d": 181, - "9c67c57acd01a8e832000d00184438b80f9342b162c401c1a1be12e3": 182, - "a05bff2f497264b839ab77016b7cff8b371a36ccb643ff0500b629d9": 183, - "a1d4f80d5a0c3b0df1c53100d60000e6d3852a4d4d2d7d843c22a437": 184, - "a1e427086d013db41e1c5097ef6c60c76531a5313e29c037f49757eb": 185, - "a4e29acfb9642ee43438b0513c2a96ef4def8262d069c3acd2462501": 186, - "afe159d207188718330fff49e3ffa0d27b834cb49b6bf656ff8f0154": 187, - "bc5cbcfb2fd1ecbebf89daa69c579108a91b13ff95ae8946ff290340": 188, - "c2b4ccea0d400dee7e42defe8a983648db60ff54103f6871fc07f5e3": 189, - "cb80746a9eb1765bb7490080a3012d0e010936c16ea6923c551e3b72": 190, - "d6dcab7100bf65a073e5003e0cf162cfc22bdbd6c971cb70ee0b3ca7": 191, - "e0e9010efff096cb8e8dd7cc6d01dbf70c362755da9618f63f26e235": 192, - "ee7b6eff6f754401ffc74f6d14a6087eebe1a80100ff8dbf60dd9ca5": 193, - "f42f010142a697076ea5f42296b10f45cdca1087df799ca8ef8a195c": 194, - "fecb57048d31890103990628480100b05211e497adc8dd2ddd5af14f": 195, - "ff03ff27010810b9c9fd4006d59eb200ae937ce98ef499967cb80100": 196, - "ff14b167c2f0d174457ac4bda2b2c91a18b77ea16232cd0001de80f2": 197, - "ff330da5a8f6da32801d8f923d07ff6ec2640157fc483f34f1a483e7": 198 + "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c": 1, + "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647": 2, + "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c": 3, + "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529": 4, + "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d": 5, + "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479": 6, + "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb": 7, + "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1": 8, + "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0": 9, + "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874": 10, + "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef": 11, + "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff": 12, + "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c": 13, + "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137": 14, + "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f": 15, + "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e": 16, + "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8": 17, + "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07": 18, + "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff": 19, + "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be": 20, + "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de": 21, + "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000": 22, + "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155": 23, + "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2": 24, + "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c": 25, + "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58": 26, + "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff": 27, + "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376": 28, + "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551": 29, + "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297": 30, + "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01": 31, + "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc": 32, + "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966": 33, + "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938": 34, + "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf": 35, + "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf": 36, + "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8": 37, + "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11": 38, + "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416": 39, + "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283": 40, + "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64": 41, + "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8": 42, + "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99": 43, + "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d": 44, + "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c": 45, + "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3": 46, + "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690": 47, + "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff": 48, + "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601": 49, + "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2": 50, + "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b": 51, + "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2": 52, + "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201": 53, + "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7": 54, + "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b": 55, + "e408900057c66f11f77a090112a8a9d885c435735ce121ddfad32771": 56, + "b12a95008fb0cbcbf023dd6f8efeb2cf71f71356c54200209b1c78ff": 57, + "d687c5c9643f59210012ee0b39cad3a7a75cd1614e69720101080ffa": 58, + "d77e83a3519663dd32e8bb0e5ae8bf8f8624ab8a0208e59e82befd77": 59, + "f25001dc1b54cefa2a1e0bf75b9701ecc0001ce16f12cb7fb98ed60f": 60, + "6201cbc21cd34e200053404dd9afa9009c77196e329afaff960cf534": 61, + "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631": 62, + "ffd0ffff97a811e712c14add1047bec50016d7e50101b6fb3872ff00": 63, + "ef39dc5761054877a0c3a51e00a7424b72fb4effdc722fede2c3ac26": 64, + "3e746153eff970bd4887887c7003007035014a11cf0001bf01466cfe": 65, + "e9d532daff2d3e922baa003d3e5447e92facd96a2dcb722c30ff8d02": 66, + "d2ec42d9f307fdc985e0010001d9e5603b00198e3751a0487eb2b03a": 67, + "240cb24fdab700ef2e8a6029a4bb9a82a626737d413da2787c01b3b9": 68, + "aa821cd818d52c9aa71a83a64443558439a950be61ff281d070478ff": 69, + "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3": 70, + "43ff5b810501f390d17faf096b9812efa79866fbb20023be2f012201": 71, + "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff": 72, + "007b7ae1a544a2fe7cff0feeffffa0aba683d0ff5326ff4674ec5da4": 73, + "c99031b2f1858bd91d522bf565784c28a98897011ad3cb845fdd58f6": 74, + "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7": 75, + "c76154138a2dee9754f07a426ce7bb5c629099e571a410afb710beba": 76, + "ff0258361001a58a5a959c501608034201700c01fe14e49028fb248c": 77, + "e00327090b0133c301979bdf4b08de0198d70775b451733d6381650a": 78, + "0c036b0369bd5cbd5f00588bab585433771de2004bce47d000a11500": 79, + "fa12ffe085438b9fff811a2003caaf8fde9b00b63f24f0fef270aa7a": 80, + "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a": 81, + "b8c4dc65f9ffad410fee7997f047e8f521cd01d31c0392deb8e65a42": 82, + "266464354274595cd5b4008fa0e87c95e4c9e223fe95bee1dfcc0d9c": 83, + "07a0e9d497f6836060fd84af10afb2f2994e04d0f8a2b770ff3c88b3": 84, + "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6": 85, + "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2": 86, + "be99c5965bb5017581c9d8e9c3b68100dcf3e4236653ed51f8b90b44": 87, + "f40bf01daf809d61e68649f04a1d8dda9151ed6838c89078b68dc3fe": 88, + "afd43c3b45c0214e05e5ff7610411c39faef681c92d09c2c693720c2": 89, + "a9bf7f2ccd409bae63b3e6c101497909c3e0ffaf1a47e900ff00f8c2": 90, + "6f2e179432e5c4c6df001d558e0000e0e210c5262d00a4182ad76426": 91, + "b0d3ed01ff74ffb000a9954dae59e570dca5ff5bbbd3470101c7cf39": 92, + "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9": 93, + "007a17b9f86300003200366e28b71dff215de1e8c47d558d10b6ffbe": 94, + "1c3ef8ff647f35904412ed32ebc625b33edac269017e0197005d1ccf": 95, + "ec2ca9da0aaa43535b95ff537c7bb700ffbafc5c0efbe90063aebf00": 96, + "e075b8012d3a1001794f751396dce75b672e6a0004ffff01c71ac100": 97, + "796240fffbf7c801e8402469010115a270c3f6d7ce21fa9e6fff052d": 98, + "e57ec7c7a233479e45d4dfb23c85b21b85ffe93d2c038986f60d0132": 99, + "cbf94fc524a51d030876a158f9ec8dffffce8f2148f1bb5b61445b81": 100, + "72394760a3ce4d7e01ffffc45cd7e8de83806bffff382dde3cac0f33": 101, + "004cb789ff416601c489fe8a2a69bca620ff7cd4d32cfd9449543e06": 102, + "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571": 103, + "a17fb2e3ff00d898bd89a6b9ef49bf08e21c237a297f71379aeed225": 104, + "ff3737d352fef3e653a296978a09b419e2d0016fbfff7c77aaeaa001": 105, + "5d272ea05f63673c6c001f9bdb6d3e225fa602e6d2ff2a663fa66745": 106, + "01a9b2a9c6778ff1072bf76e0064943701608a1fffd618d301654fa7": 107, + "bc70858172851b9dff34ffe231d8892c57833a9e666e8f0313df691b": 108, + "d57ed603a89d8342859e075947c91b1a255ae94a9d01b1a530457ff2": 109, + "091b2203cb2f3b885a10c326cfe92b7d0bfe2b00024779b4a7892d71": 110, + "ffebd8db93a0ef539f836c8312550100003fa9641ce984597ef53398": 111, + "4fff4cf77029f600d9ffc0c8fd981a0ef37cffd4bd42db9050eff524": 112, + "9cf24b7b01a1d90a01a9009d3a4466b39500e4191588639ee7f7b800": 113, + "194d9c992e9cfe00ac2f46cd76008dd20723d254c0dd07fab24da548": 114, + "2fbc5ad791c60d17bb0ff900717c9fa1197a612a72cd4e200182c273": 115, + "c1410131a6f50142d468da34025d7a06f4ff007a78fb7394c346e513": 116, + "02f28ceeeb8f8eff0be401993255ffba60bffdc2a2c19a01b661978b": 117, + "0fffaa50003b42815a126701fa6b50d1216fcc86aef307e798f737c0": 118, + "16ea953d050b5f84e64e137a26a4cec82bf85000f6b25fede6ff3503": 119, + "2c66f80bec0367db4d43e9459e82fe7e446c5789d5ac017f2dd79a01": 120, + "819acf08e187cd01ec1fd839391b61e4c8aacc303a8437bf2fd5887c": 121, + "ec2c06173fe7c758fff3d8011e01547f09e95dd49ebf9ef20143f770": 122, + "25f0ff0009242e694cefb24c45008eb58e4daa1ab6ff507fdd0e59a1": 123, + "3900e35dff96420ceff3f6c2e129172af3d001e66a8c01501d77a247": 124, + "7be8985d82660003fd79f9d2ffffe524418faf6611f3362671845f1f": 125, + "acf227b3222573270a400f97aad8ca6d976ef6a67632f6491c00d8f8": 126, + "0d4ae5a909eed7912c494cc705a97ea26025cd98483101314aa2fb01": 127, + "5ded7dca81e99cb3869563b6ff4a759351e2b98b071b29307a743a2e": 128, + "734cf0ff12bb7b70a2ac9ab91dbbf205205368b30bc5ee3fffc05601": 129, + "77ffba0e5b2f65cdf4f22e201d924a8377ff153aff6e148dfb78eee7": 130, + "01d190af9500d0aeaa702ab126011e0d247adc1cfd25ffdeef910a15": 131, + "1a6e415a3687e9007845ceeee56fea563e085ba89cf36f64c10eaa5c": 132, + "abc5877fb3e9dc6c2ecebcb6bdd3a2ff0172f50722c4e0ffe60058a3": 133, + "b2ed18f25c6f642cd232a5d119b94116e2fff900afee000e82026c5c": 134, + "0fc78d01677d5b1c6b3ba67546c012a6a61b13a48733214231a5528f": 135, + "1c4e2a0065d7942da94b7a91b434226ff65fb7256b355fc5bea52e18": 136, + "494fc1cbd17a5333681ceb01290953018fe9c53c4f9adbb9a1af0046": 137, + "4c75a8782091320088cd01e900a8aac412f4838654f729a787564b68": 138, + "5be0ea83207a384c55f0c77336e02e7b93ff43ac5a36e23092a9ff0f": 139, + "95264801d09a36c0ff36dd56479215bfea3d006f5500548ffff69059": 140, + "a34a3a08407225822a8a25660081101461ffb022eb8a4d9f0b6200d5": 141, + "d3ff4b015b31bb991295a301fc5eea195744f31fc276a817d284350a": 142, + "db8f1457b1c223a2d03a8b42ff169100ffff3995cefff5985c5100e8": 143, + "ff860082386cf7001aead49dfbd50e75beff4ca7f138d4711ee7016b": 144, + "00600cc9d279ccaabd76ffff00eba32b210027c4038c305b99962a77": 145, + "016c8800ff690bda9802a96f134a4f03ac65eb4541f9656d0e523d00": 146, + "01f702c970b3ff2bb3490d9a6b677cd46e1e043621ff1c174024087d": 147, + "0ed40164004ac401ffde79a9e60422e200a7fd9c43a4ef283c00145d": 148, + "191d68de0259e70d2a55ae59e463ae00c7dcc69136bb0096af86c346": 149, + "1a0113e50000119207ae22fce1bd3ebfe885a29cb3b9f5bd7013e38e": 150, + "1a51ffff00668dff0497605e3dca074f81337cf6bf21e0fff1d80042": 151, + "1bd26b6fbf600023666d90ad14bfea1bff12cfa018d74e471e4384c1": 152, + "1c7acb7a0073e9c3b78aff9b00f016f6533f3800ff01429e3078cbec": 153, + "1cc4d59507714de1b61e362142ef26fbe7ee4aac91df5e4bee01f1f5": 154, + "25c36181b2f2d234816d0c7600800d7a2c62e3e5dd258d4ec5217008": 155, + "2ff34789ff111c01ad6ea6ac03f7a0014cffe9626ad536aa62007479": 156, + "33248dee56388df4016a36d000440ec36f5b392e7ee170e9964fc97c": 157, + "36353475ad3c1ae87b7000ff67d6b800d200b31900ba17011343f831": 158, + "374145d49f4f4f074e5c2e0153d888933399599ef2c4be0121017947": 159, + "3940f037f98d800000019e415e59d0009517a17ae67f6d1cf983583b": 160, + "45d2abaa00a900ff78ada641f03879ff335e1fd9e86630fa0403d6ff": 161, + "4a892cc61350083301181737176d0c7f69b6c12768980ee19afd49a2": 162, + "50ccc49ec9c00b8a04231f25a256275613d2f4b991195fc201003932": 163, + "566900ee95b45900f4d57b8f65c9f58087492e850ac2294d92144981": 164, + "68858ee00098a6c201492fdb09f8be0b546e3d537dfe811e98ab615c": 165, + "6ec653bc3501fba1eceff8004464dd47aa788966e2988813a2b19292": 166, + "704d2d1c018eb90166c653602d00e0b52a01faeb0e29ff92ff791422": 167, + "764801d5b6f51b85d1c425ffdb01926813fb0029d19e79f73741e2d5": 168, + "7f70ef01c542bb9953ff025e477aa461511eaf2213f4ef94bfcd0011": 169, + "7f9969004c1550edd7b04ee88b5274013b12a50f990189fcca22c8cb": 170, + "8275f96f65ce710176d500375005c37a8dff6da2ff1745ac1159ff78": 171, + "8d8fd800075c6ae000bbff1fafd7ead5c4abe729d101f7cc7e6a06e7": 172, + "9d9f9b28e4dcc35c3a053fdc0b066df880fd4411f02c2a171deee088": 173, + "a08ae12343f534ee49bdf7ba256db00103e4203d00c0ffffb1074759": 174, + "a37776b4002dd68c14012a6d52820b2d89e9c001f608014b43e692a3": 175, + "a3989c00d6dc79286474ffed49c1dfeaf879592ff9a0ba38060500ad": 176, + "a6931c2de82054753ad1735087a72721573f88cbe9003b7242d6f9fb": 177, + "b3c2b40baa974b5bc50a5a2abebc0107389e4f738301650aea247773": 178, + "b64d005afff675d5fa56e0cd58c86101dff90118d0559901f301139c": 179, + "b8f5ff8ce57fe80cb4ffaeb6e290380094d1fa01016b0154e204a5a0": 180, + "be37348ef3c8ab12541d008a848d0100449f8992013a0197b204b3b7": 181, + "c200115f1d2248f470012dfff6df0b921602e93fd8eff38eea30ffe2": 182, + "c3b822a5961ef78b091206a8dcf750d48470b71adb46d21d652221cb": 183, + "c555e91c3c7ad44a24b39a609ce6320e4f791c73006aa1ff8beaa27a": 184, + "c5e0157840b431e37609a9d50f4cb894e9565dbda4e47bd2ffe1c579": 185, + "ca509072b97a359e9c4d0b9965f43f91cf01badd2adfef5b92d182e2": 186, + "cc01ffa7295ed9bc1a0088f11036bda6207c8ffa953d96b9632876fc": 187, + "cd59c4100e56a495ff6b601bf54f73b184ce16a2ffae965c36ebc9b6": 188, + "df01fa63958c47c3a980a5e359b491b7b30ec6a16be10188a7850cba": 189, + "e0d4796c1e839ee0f6cbc30ebf612206e258addfcec7091041ff393a": 190, + "e2bcb1dc01c43d5ffd88321d01054fff2e164a797f056bb089390038": 191, + "e4e12d25b5158aa86a9fad026702835c0060f3ffd6ffdbc537e02fd3": 192, + "e5809e783436a02e5529b900ffff1600d1c25eb935f1f9f4907a3300": 193, + "e84acc16a9470098f3007844976401019217f99ca565a301ec6f6401": 194, + "f57ded9f084058a247f274ff8cfeed013774b4b5a993badef13d26d0": 195, + "f6d22048980001e100166713014a9cba3e90ff8cecb6ccb0405c6da0": 196, + "ff0d46fb0e55505106f2c507e252f1a1b4d401e4df369ad0e5ed5db1": 197, + "ffd271d2cc05518088726b58fbc40c3b53efb000ff9bf0bca7a9ef8f": 198, + "fff6ab31d94ddcb37334ec6a5c01016130018dffa3b86f010dc8fb3a": 199 }, "committee_index_by_pool_id": { - "297d513e8a16fface8ffb11d535e89d0f4ca996ece5a8c2dd7395372": 1, - "e7ca79019900d2ff1139d3fae05eff7fc474019386f101ffc7a413d1": 2, - "510000bd0d89608c5f480100db9abac7ad2c011e8891d8603704d8f1": 3, - "f640a7267abc27ff5399b59b783195003b59bd699c7de7b1a94b90e7": 4, - "2bffe13687b1d07dd6a5a8c0e4f64f7c565b6b07634bde1cf5cde490": 5, - "4014004f403600cc7835b95fa6597a1daf75ee91d2f8a489c4cc4621": 6, - "0caaf065c5e8e01e9c9b01ffee6d5d28166daf9e9f013e7c59f5eea0": 7, - "2e3fff190094ec4b1e555381f10132cd8503bcfab77ac69f31061f80": 8, - "be7f491f0142d68fbe5db88f81f3611a003e1860df76ff4c993bff55": 9, - "80576f0db3dd223800c04d529cec22a7a4e95685fb44b3248579d129": 10, - "6b33e699e62bb5e9a253e94001b2e0008928af07480180efacf19eff": 11, - "1fac9270dce82a31acdb0044ea2bc33001bccfa733a8343ab508f8db": 12, - "010000ea011f3b5f560000c3b7ff09099b00cee6c904ff8db5be05af": 13, - "ced712e76857ae0301394b9f80d9883aa915438f3a634948e561cb1c": 14, - "68ff8120d80176efacffac2b21455d940a19c1c9d7015715015a2ac1": 15, - "8ed5d017d237129da3415d93c29bf235b802fa0d1b326d0e00943e7d": 16, - "f1ffb84f6cd5961467690750815dc6fe4528e47c10b5029c62aa017d": 17, - "f9ffdfc4cfd3aeb25b759621d4dad6c8cbc6d330d3e865f6e2ccdccd": 18, - "2859011646cb69e9af2b00f8a6e99a5e98c315c30d3e0950e2f936d6": 19, - "ba2e466a8e04eae7d9601b280bff7730395b62f753fc4dc9daac6551": 20, - "55077dbd0017ac00133d4d451fee91ffd732ae0f0028ee91d1f4fe25": 21, - "71e9fdd001726ccdce084db3f5d19175c88d7a6ffe72fb7bc7ff9fca": 22, - "ff017f87e40030d332e3a201790cb4d191f77cff402359afbfc3f133": 23, - "2c01ff0bfee051d6f9a0542f83d86f8ace9400a0bbae83125124ad6a": 24, - "a6f18a62cd970f4101011eff07f2ab0924c682d95701a591b2703900": 25, - "01b517a3397cf1f2fcffb3de23e53b356cff3000ff8175ff15ce46b5": 26, - "2a0197eb60069d9b9674b36d01c05633c7fd9fd9ff62ac440101e1b4": 27, - "ab65112ee36236dfb0cbe04901dd8664aa53e14e3a2201e8ba43010f": 28, - "42ff05ff217a522a4d0157b811f728ee479ddeeeb6bb791fff00adb4": 29, - "4a0814a0d39aff838261015cd7696a86d5bc4893ff334d9011f05dff": 30, - "19168e8ba075a44ce41eaacc0ba82193fbfdff79adfaef8075e96c25": 31, - "b0a969ed896461627b2d580817d6b723ffdd5b57eff345d2ff0dcf1f": 32, - "811e1c4eb5acb7088b384601455e118c0501da7e579807ed6f1042cb": 33, - "ebe0eb00bf42788a8cd370e5260166436500a39210f516b5d9ebdc8b": 34, - "e534f8d3582a76a57f5c28003af6cdf1c3bcd2a977e27c3999f1f9f3": 35, - "73efe21dcd67fb690069dfbd0430002c993fe43d775ed60166b93b5b": 36, - "4e70a101cb06c0005072c580645652ff9d5e47f717004ccaba9582ad": 37, - "500385315cff8eb2e06b532fadaaa001928b08049da30ce6ff93b769": 38, - "d7fffa2e2dd3efe4ae003687be09ff11a53cff8c9d36d4130cb4b5fc": 39, - "3b96fb5124d0df7e90440fa9ff5501bfab014b31db694c01b03ddbbe": 40, - "c140043b5a723fe346f700bf44f4b40a8c4f324dbe17b5ac01e3b45e": 41, - "db5640fd0014ee5c43b3498573454314eeeb89ffbf001f5724e3ff1b": 42, - "90d495ff0101155eb09ed7ebb1760016b444316a202eff090b82a0c9": 43, - "58ffff4bd20fc2838e882341014f43f2b5a22934207a2c4f7a57e409": 44, - "ff49416cbb7e6309ef340b8702deb8cf8d77019b1cc62cf76c3b426e": 45, - "a82c016500d5ffb7845300684039d9628352c61d6f45d7c1d0d58941": 46, - "a69cf8710100aba9476c695c78fcbdcb9b89a84e0191695d2a5dcc47": 47, - "0a754dfc0100b004638dbd846abe3ec155c82b0f8dee5f5e6c00d9ff": 48, - "f3aacec30955e959ce047c75ffdea67b77d8b9fffab3aa01ffde0dcb": 49, - "443e7518ab3045e4ff88cb23c4a334d997747e24a64e9ea34f247505": 50, - "6733785fcc6301e8e58df40e90ed37ee1ef53cb05179007300d31564": 51, - "1ecb00477c12ef001979a45884f823fac7c2572527fa810c9e0e7aea": 52, - "01aa219b77ffff26b05caa247625319fc2673c3bfadb0136dc14a604": 53, - "9d8a72e60df13e75575c00947192a3d493014bf5ce4f4f00a000370b": 54, - "75d82022f485bcedb6b38c3a01016bd7050fdc9edc2f7fbaa09a40bc": 55, - "d252010f5c7a970bb92626da9c8e2280921e997c83ee499148bb5101": 56, - "9adf3ed0bcc79e9b2f9781e209fdd604132868b5ba9e7bb26cff6c5b": 57, - "2b9199fadb0a912d78b7010e12ff6298aec7000d000462b5b061e4f1": 58, - "b839bc8cf9f25702013e019bd0d3318c380097e105f9915b653192cb": 59, - "2b6edf630000468d9dc8f60fffcb9281957c01f6dc945a6a764689f1": 60, - "bb2ada9401fb84d1a407a8d6a95dfad6a4f30f40bc74ff6fe85a1f77": 61, - "edcd00b778815439000046ac0036806b2e49e5208b97f2373eefa399": 62, - "1aaa13bf4f51da2001ac5d0059d846cbdce7ebb95718c9a3dc4b8313": 63, - "aac1f0971050401758f1597d75d7ff429661fd80f2ae7efddcff2b44": 64 + "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c": 1, + "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647": 2, + "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c": 3, + "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529": 4, + "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d": 5, + "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479": 6, + "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb": 7, + "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1": 8, + "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0": 9, + "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874": 10, + "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef": 11, + "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff": 12, + "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c": 13, + "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137": 14, + "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f": 15, + "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e": 16, + "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8": 17, + "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07": 18, + "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff": 19, + "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be": 20, + "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de": 21, + "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000": 22, + "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155": 23, + "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2": 24, + "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c": 25, + "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58": 26, + "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff": 27, + "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376": 28, + "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551": 29, + "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297": 30, + "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01": 31, + "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc": 32, + "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966": 33, + "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938": 34, + "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf": 35, + "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf": 36, + "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8": 37, + "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11": 38, + "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416": 39, + "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283": 40, + "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64": 41, + "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8": 42, + "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99": 43, + "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d": 44, + "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c": 45, + "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3": 46, + "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690": 47, + "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff": 48, + "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601": 49, + "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2": 50, + "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b": 51, + "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2": 52, + "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201": 53, + "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7": 54, + "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b": 55, + "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3": 56, + "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a": 57, + "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631": 58, + "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6": 59, + "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9": 60, + "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571": 61, + "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff": 62, + "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7": 63, + "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2": 64 + }, + "committee_position_by_pool_id": { + "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c": 1, + "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647": 2, + "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c": 3, + "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529": 4, + "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d": 5, + "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479": 6, + "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb": 7, + "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1": 8, + "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0": 9, + "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874": 10, + "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef": 11, + "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff": 12, + "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c": 13, + "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137": 14, + "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f": 15, + "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e": 16, + "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8": 17, + "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07": 18, + "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff": 19, + "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be": 20, + "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de": 21, + "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000": 22, + "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155": 23, + "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2": 24, + "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c": 25, + "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58": 26, + "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff": 27, + "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376": 28, + "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551": 29, + "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297": 30, + "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01": 31, + "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc": 32, + "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966": 33, + "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938": 34, + "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf": 35, + "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf": 36, + "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8": 37, + "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11": 38, + "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416": 39, + "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283": 40, + "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64": 41, + "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8": 42, + "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99": 43, + "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d": 44, + "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c": 45, + "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3": 46, + "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690": 47, + "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff": 48, + "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601": 49, + "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2": 50, + "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b": 51, + "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2": 52, + "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201": 53, + "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7": 54, + "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b": 55, + "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3": 56, + "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a": 57, + "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631": 58, + "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6": 59, + "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9": 60, + "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571": 61, + "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff": 62, + "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7": 63, + "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2": 64 } }, "voters": { "persistent_ids": [ - 52, - 6, - 30, - 51, - 11, + 3, 26, - 4, + 25, + 23, 5, - 28, - 49, - 29, - 36, - 37, - 43, - 50, + 6, + 4, + 20, 14, - 10, + 30, 21, - 35, + 46, 54, - 15, - 53, - 24, - 45, - 27, - 31, - 8, + 49, + 47, 40, + 17, + 10, + 32, + 50, + 18, + 43, + 2, 13, - 41, - 34, - 1, 33, - 16, + 24, + 28, 48, - 17, - 22 + 35 ], "nonpersistent_pool_ids": [ - "001cb6d54c00316a5bdc38c25dec0002805d01275300c8556ebcc3f0", - "00b76527d1f8f75a1c8bc8d47737a1456f39e900f92ddc360a44035d", - "0101caf77c2455864e765290680006008ea882ff7119336178d570d6", - "01503a091fdf01a1fe93bb8cd54e858e11efcb3062ffc08fe5551c61", - "1aaa13bf4f51da2001ac5d0059d846cbdce7ebb95718c9a3dc4b8313", - "2b6edf630000468d9dc8f60fffcb9281957c01f6dc945a6a764689f1", - "2b9199fadb0a912d78b7010e12ff6298aec7000d000462b5b061e4f1", - "2c807bb6431eba01bafdccb7ed9724ffe4a4ff9b60e0499b0e66c82e", - "30010fecff2471437901d979b0949fdf0029888ca985041359957bfe", - "4d01b0bcc8c8006300a331d71fe30b1e4d25bf9977015fdaac58e496", - "4e01238c2f8994651251c1f3009331e9e38602f0b3c6ff5c78ff86eb", - "531fd03400ff89603a49cf1292c85bdd014aac24b4b06cac87bb35cc", - "9adf3ed0bcc79e9b2f9781e209fdd604132868b5ba9e7bb26cff6c5b", - "aac1f0971050401758f1597d75d7ff429661fd80f2ae7efddcff2b44", - "b0c6e4ac306cee2a93d0883901ff024056298dbfe3caff01651a5f40", - "b839bc8cf9f25702013e019bd0d3318c380097e105f9915b653192cb", - "c3d75a8b44f1bca7dfdd77a7bd837e5c3fca45ffcdffaff54f950023", - "caffe8edb5c9fa60ff0101934957014a489501b91b1785f9bfc07b91", - "d252010f5c7a970bb92626da9c8e2280921e997c83ee499148bb5101", - "edcd00b778815439000046ac0036806b2e49e5208b97f2373eefa399", - "f4a9c6af00ff7f38fca05fbb30c612201d0747e85d46c9c739feec61", - "ffe264724801dbe3c900838152e884435c57e51a8ef9a701028a3969" + "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3", + "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a", + "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631", + "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6", + "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9", + "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571", + "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff", + "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7", + "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2", + "afd43c3b45c0214e05e5ff7610411c39faef681c92d09c2c693720c2", + "c76154138a2dee9754f07a426ce7bb5c629099e571a410afb710beba", + "c99031b2f1858bd91d522bf565784c28a98897011ad3cb845fdd58f6", + "e408900057c66f11f77a090112a8a9d885c435735ce121ddfad32771", + "e57ec7c7a233479e45d4dfb23c85b21b85ffe93d2c038986f60d0132", + "e9d532daff2d3e922baa003d3e5447e92facd96a2dcb722c30ff8d02", + "f25001dc1b54cefa2a1e0bf75b9701ecc0001ce16f12cb7fb98ed60f", + "ff0258361001a58a5a959c501608034201700c01fe14e49028fb248c" ] }, "certificate": { - "sigma_tilde_eid_prefix": "906b66a8602c760e", - "sigma_tilde_m_prefix": "85b692b4e7a131ae", - "eid": "0x64dbf8afc7c8a40c", - "eb": "0xcf06b28637f7696c82e5015d8f687b2b7700dccbfa06fe370027dd4bff21a974", - "persistent_voters_count": 37, - "nonpersistent_voters_count": 22, - "votes_bytes": 10380, - "certificate_bytes": 2659, - "compression_ratio": 3.904 + "sigma_tilde_eid_prefix": "b7a4c667e0a7a741", + "sigma_tilde_m_prefix": "8b0733a367d3dcdb", + "eid": "0x26f8d6de52069c34", + "eb": "0x00d960ffbea019a0edc9e20656cf84ceb0c9bd5f8bf8ee9bf975940617489ba4", + "persistent_voters_count": 29, + "nonpersistent_voters_count": 17, + "votes_bytes": 8074, + "certificate_bytes": 2104, + "compression_ratio": 3.837 } } \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh b/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh index 510957c16..b3d77b6a8 100755 --- a/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh +++ b/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh @@ -11,18 +11,18 @@ while [[ $# -gt 0 ]]; do done if [[ -z "$RUN_DIR" ]]; then - echo "need -d RUN_DIR" + echo "need -d RUN_DIR" >&2 exit 2 fi -# ✅ Quote paths with spaces +# Resolve the run directory (handle paths with spaces) RUN_DIR="$(cd "$DIR_SCRIPT/.." && cd "$RUN_DIR" && pwd)" PY="${VIRTUAL_ENV:+$VIRTUAL_ENV/bin/python}" PY="${PY:-python3}" pushd "$RUN_DIR" >/dev/null - "$PY" - <<'PY' -import os, json +"$PY" - <<'PY' +import os, json, collections try: import cbor2 except ImportError: @@ -37,42 +37,147 @@ def must(path): reg = cbor2.load(open(must("registry.cbor"), "rb")) info = reg.get("info", {}) or {} persistent_pool = reg.get("persistent_pool", {}) or {} -persistent_set = set(persistent_pool.values()) total_stake = reg.get("total_stake") -N = reg.get("voters", 0) +N = int(reg.get("voters", 0) or 0) -# --- Universe (all pools) --- -# Each entry now has: pool_id, stake, is_persistent (for easy coloring on UI) +# --- Universe (all pools from registry.info) --- +# Each entry: pool_id, stake, is_persistent +persistent_set = set(persistent_pool.values()) universe = [ {"pool_id": pid, "stake": rec.get("stake", 0), "is_persistent": pid in persistent_set} for pid, rec in info.items() ] -universe.sort(key=lambda x: x["stake"], reverse=True) - -# --- Committee (top-N by stake, same as backend endpoint) --- -committee = [ - {"index": i + 1, "pool_id": universe[i]["pool_id"], "stake": universe[i]["stake"]} - for i in range(min(N, len(universe))) -] - -# --- Lookup maps for fast UI highlighting / tooltips --- -universe_index_by_pool_id = {entry["pool_id"]: idx for idx, entry in enumerate(universe)} -committee_index_by_pool_id = {entry["pool_id"]: entry["index"] for entry in committee} - -# --- Voters (optional; present when votes.cbor exists) --- +# Sort universe by stake desc, then pool_id for stability +universe.sort(key=lambda x: (-int(x.get("stake", 0) or 0), x["pool_id"])) + +# Quick lookup for stake / presence +stake_by_pid = {e["pool_id"]: int(e.get("stake", 0) or 0) for e in universe} + +# --- Persistent seats (ordered by persistent id) --- +persist_entries = [] +for pid_idx in sorted(persistent_pool): + pid = persistent_pool[pid_idx] + persist_entries.append({ + "pool_id": pid, + "stake": stake_by_pid.get(pid, 0), + }) + +# How many nonpersistent seats do we need? +np_needed = max(0, N - len(persist_entries)) + +# --- Non-persistent winners: +# Prefer certificate.cbor (authoritative). If absent, derive from votes.cbor. +np_winners = [] +if np_needed > 0 and os.path.exists("certificate.cbor"): + cert = cbor2.load(open("certificate.cbor", "rb")) + # Many builds export winners under 'nonpersistent_voters' as a map {pool_id: proof/...} + np_map = cert.get("nonpersistent_voters") or {} + # Keep the CBOR/map iteration order (winners order). Fall back to sorted order for stability. + if isinstance(np_map, dict): + np_winners = list(np_map.keys()) + else: + # Some variants serialize as list of objects + try: + np_winners = [x.get("pool") for x in np_map if isinstance(x, dict) and x.get("pool")] + except Exception: + np_winners = [] +elif np_needed > 0 and os.path.exists("votes.cbor"): + # Derive per-EB top by vote count for Nonpersistent votes + votes = cbor2.load(open("votes.cbor", "rb")) + ctr = collections.Counter() + order = [] # first-appearance order (for tie-breaking) + seen = set() + for v in votes: + if "Nonpersistent" in v: + pid = v["Nonpersistent"].get("pool") + if not isinstance(pid, str): + continue + ctr[pid] += 1 + if pid not in seen: + seen.add(pid) + order.append(pid) + # Rank by count desc, break ties by first appearance + np_winners = sorted(ctr.keys(), key=lambda k: (-ctr[k], order.index(k) if k in order else 1e9)) + +# Deduplicate and trim to needed seats, skipping any that duplicate persistent +np_final = [] +seen = set(p["pool_id"] for p in persist_entries) +for pid in np_winners: + if not isinstance(pid, str): + continue + if pid in seen: + continue + np_final.append(pid) + seen.add(pid) + if len(np_final) >= np_needed: + break + +# --- Only keep NP winners that already exist in the universe (no stubs) +universe_pids = set(e["pool_id"] for e in universe) +np_final = [pid for pid in np_final if pid in universe_pids] + +# Rebuild lookup maps after potential extensions +universe.sort(key=lambda x: (-int(x.get("stake", 0) or 0), x["pool_id"])) +universe_index_by_pool_id = {entry["pool_id"]: idx for idx, entry in enumerate(universe, start=1)} + +# --- Build final committee list: keep committee order (position) but set +# "index" to the pool's 1-based index in the *universe* array. +committee = [] +# persistent first (in persistent id order) +for pos, entry in enumerate(persist_entries, start=1): + pid = entry["pool_id"] + committee.append({ + "position": pos, + "index": universe_index_by_pool_id.get(pid), # universe index + "pool_id": pid, + "stake": entry["stake"], + }) +# then non-persistent winners (in certificate order or derived order) +pos = len(committee) + 1 +for pid in np_final: + committee.append({ + "position": pos, + "index": universe_index_by_pool_id.get(pid), # universe index + "pool_id": pid, + "stake": stake_by_pid.get(pid, 0), + }) + pos += 1 + +# If we still don't have N seats (missing certificate/votes), fill from top-by-stake excluding already chosen +if len(committee) < N: + chosen = set(c["pool_id"] for c in committee) + for e in universe: + if e["pool_id"] in chosen: + continue + committee.append({ + "position": len(committee) + 1, + "index": universe_index_by_pool_id.get(e["pool_id"]), + "pool_id": e["pool_id"], + "stake": e.get("stake", 0), + }) + if len(committee) >= N: + break + committee_source = "fa+votes+fallback" +else: + committee_source = "fa+certificate" if os.path.exists("certificate.cbor") else ("fa+votes" if os.path.exists("votes.cbor") else "fallback_topN") + +# --- Voters quick view (optional) voters_persistent = [] voters_nonpersistent = [] if os.path.exists("votes.cbor"): - votes = cbor2.load(open("votes.cbor", "rb")) - for v in votes: - if "Persistent" in v: - pid = v["Persistent"]["persistent"] - if isinstance(pid, int): - voters_persistent.append(pid) - elif "Nonpersistent" in v: - pool = v["Nonpersistent"]["pool"] - if isinstance(pool, str): - voters_nonpersistent.append(pool) + try: + votes = cbor2.load(open("votes.cbor", "rb")) + for v in votes: + if "Persistent" in v: + pid = v["Persistent"].get("persistent") + if isinstance(pid, int): + voters_persistent.append(pid) + elif "Nonpersistent" in v: + pool = v["Nonpersistent"].get("pool") + if isinstance(pool, str): + voters_nonpersistent.append(pool) + except Exception: + pass # --- Certificate summary (optional) --- cert_summary = {} @@ -83,13 +188,18 @@ if os.path.exists("certificate.cbor"): cert = cbor2.load(open("certificate.cbor", "rb")) pv = cert.get("persistent_voters", []) or [] npv = cert.get("nonpersistent_voters") or {} + def hex_pref(x): + try: + return (x or b"")[:8].hex() + except Exception: + return None cert_summary = { - "sigma_tilde_eid_prefix": cert["sigma_tilde_eid"][:8].hex(), - "sigma_tilde_m_prefix": cert["sigma_tilde_m"][:8].hex(), - "eid": "0x" + cert["eid"].hex(), - "eb": "0x" + cert["eb"].hex(), - "persistent_voters_count": len(pv), - "nonpersistent_voters_count": len(npv.keys()), + "sigma_tilde_eid_prefix": hex_pref(cert.get("sigma_tilde_eid")), + "sigma_tilde_m_prefix": hex_pref(cert.get("sigma_tilde_m")), + "eid": "0x" + (cert.get("eid") or b"").hex() if cert.get("eid") is not None else None, + "eb": "0x" + (cert.get("eb") or b"").hex() if cert.get("eb") is not None else None, + "persistent_voters_count": len(pv) if hasattr(pv, "__len__") else None, + "nonpersistent_voters_count": (len(npv.keys()) if isinstance(npv, dict) else (len(npv) if hasattr(npv, "__len__") else None)), } if votes_bytes is not None: @@ -97,7 +207,10 @@ if votes_bytes is not None: if certificate_bytes is not None: cert_summary["certificate_bytes"] = certificate_bytes if votes_bytes: - cert_summary["compression_ratio"] = round(votes_bytes / certificate_bytes, 3) + try: + cert_summary["compression_ratio"] = round(votes_bytes / certificate_bytes, 3) + except Exception: + pass # --- Params (handy for the UI header) --- params = { @@ -111,9 +224,13 @@ out = { "universe": universe, "persistent_map": {int(k): v for k, v in persistent_pool.items()}, "committee": committee, + "committee_source": committee_source, "lookup": { "universe_index_by_pool_id": universe_index_by_pool_id, - "committee_index_by_pool_id": committee_index_by_pool_id, + # Back-compat: this has always meant the committee *order* (position) + "committee_index_by_pool_id": {entry["pool_id"]: entry["position"] for entry in committee}, + # New explicit alias for clarity + "committee_position_by_pool_id": {entry["pool_id"]: entry["position"] for entry in committee}, }, "voters": { "persistent_ids": voters_persistent, diff --git a/crypto-benchmarks.rs/demo/ui/static/app.js b/crypto-benchmarks.rs/demo/ui/static/app.js index 399ec9eae..63163ad98 100644 --- a/crypto-benchmarks.rs/demo/ui/static/app.js +++ b/crypto-benchmarks.rs/demo/ui/static/app.js @@ -1,9 +1,10 @@ // Minimal, robust renderer for the SPO "universe" panel. -// - Tries /demo/ (default run64), then /static/demo.json, then /demo.json -// - If found, renders all pools as circles and fills counts + identifiers -// - If not found, renders a small placeholder message +// Implements: +// 1. Sorting circles by universe index in both Committee and Voters sections. +// 2. Left-side stats for Committee (total / persistent / non-persistent). +// 3. Updated Voters stats (total / persistent / non-persistent + in-committee vs outside breakdown). +// 4. "(this election)" and "(observed votes)" strings are handled in HTML, not here. -// ---------- tiny helpers ---------- const $ = (sel) => document.querySelector(sel); function setText(id, value) { @@ -80,10 +81,71 @@ function ensureUniverseStyles() { z-index: 1000; box-shadow: 0 2px 6px rgba(0,0,0,0.3); } + .pool-dot.is-voter::after { + content: ""; + position: absolute; + inset: -3px; + border: 2px solid #19c37d; /* green ring for voters */ + border-radius: 50%; + } `; document.head.appendChild(style); } +// ---------- grid column sync so rows align across sections ---------- +function computeGridColumnsFrom(el) { + // Derive how many columns the Universe grid is using based on the cell size and gaps. + if (!el) return null; + const cs = getComputedStyle(document.documentElement); + const dot = parseFloat(cs.getPropertyValue("--dot-size")) || 30; + const gap = parseFloat(cs.getPropertyValue("--dot-gap")) || 5; + + // Available width inside the container + const width = el.clientWidth; + if (!width || width <= 0) return null; + + // Each column uses dot width plus the horizontal gap, except the last column which has no trailing gap. + // We approximate by including the gap to compute a safe floor. + const cols = Math.max(1, Math.floor((width + gap) / (dot + gap))); + return cols; +} + +function applyFixedColumns(el, cols) { + if (!el || !cols) return; + el.style.display = "grid"; + el.style.gridTemplateColumns = `repeat(${cols}, var(--dot-size))`; + el.style.gridAutoRows = "var(--dot-size)"; + el.style.gap = "var(--dot-gap)"; +} + +function syncGridColumns() { + const universeEl = document.getElementById("universe_canvas"); + const cols = computeGridColumnsFrom(universeEl); + if (!cols) return; + const committeeEl = document.getElementById("committee_canvas"); + const votersEl = document.getElementById("voters_canvas"); + applyFixedColumns(committeeEl, cols); + applyFixedColumns(votersEl, cols); +} + +// Helper: Find 1-based universe index for a given poolId +function findUniverseIndexByPoolId(demo, poolId) { + if (!demo || !Array.isArray(demo.universe)) return null; + const i = demo.universe.findIndex(p => (p.pool_id || p.id) === poolId); + return i >= 0 ? (i + 1) : null; +} + +// Helper: Get a mapping from poolId to universe index (1-based) +function buildPoolIdToUniverseIndex(demo) { + const map = new Map(); + if (!demo || !Array.isArray(demo.universe)) return map; + for (let i = 0; i < demo.universe.length; ++i) { + const poolId = demo.universe[i].pool_id || demo.universe[i].id; + if (poolId) map.set(poolId, i + 1); + } + return map; +} + // ---------- main render ---------- function renderUniverse(universe) { ensureUniverseStyles(); @@ -123,7 +185,7 @@ function renderUniverse(universe) { container.classList.add("universe-grid"); container.innerHTML = ""; - pools.forEach((pool) => { + pools.forEach((pool, i) => { const isPersistent = pool.is_persistent === true || pool.persistent === true || @@ -142,20 +204,15 @@ function renderUniverse(universe) { const label = document.createElement("span"); label.classList.add("node-label"); - - // 1‑based index for the pool number - const idx = pools.indexOf(pool) + 1; + const idx = i + 1; label.textContent = idx; - - // Dynamic font size: slightly smaller for 3‑digit numbers so they fit label.style.fontSize = (idx >= 100 ? "11px" : "13px"); div.appendChild(label); const poolId = pool.pool_id || pool.id || ""; const stake = typeof pool.stake !== "undefined" ? pool.stake : ""; - // Use HTML tooltip (bold + spacing) div.setAttribute("data-tooltip-html", `Pool ID: ${poolId}
    Stake: ${stake}`); - div.title = ""; // fallback plain title cleared + div.title = ""; div.addEventListener("mouseenter", (e) => { const tooltip = document.createElement("div"); @@ -173,11 +230,222 @@ function renderUniverse(universe) { div._tooltip = null; } }); + container.appendChild(div); + }); +} + +// ---------- committee render (selected pools) ---------- +function buildPersistentSet(demo) { + const set = new Set(); + if (demo && Array.isArray(demo.universe)) { + for (const p of demo.universe) { + if (p && (p.is_persistent === true || p.persistent === true || typeof p.persistent_id !== "undefined")) { + const id = p.pool_id || p.id; + if (id) set.add(id); + } + } + } + if (demo && demo.persistent_map && typeof demo.persistent_map === "object") { + for (const k of Object.keys(demo.persistent_map)) { + const pid = demo.persistent_map[k]; + if (pid) set.add(pid); + } + } + return set; +} + +function buildPersistentIdToPoolId(map) { + const m = new Map(); + if (!map || typeof map !== "object") return m; + for (const [k, v] of Object.entries(map)) m.set(Number(k), v); + return m; +} + +// For safety: get poolId from a committee member (support legacy/modern) +function getCommitteePoolId(x) { + return x && (x.pool_id || x.id); +} + +function renderCommitteeFromDemo(demo) { + if (!demo || !Array.isArray(demo.committee)) return; + ensureUniverseStyles(); + const container = document.getElementById("committee_canvas"); + if (!container) return; + + const committee = demo.committee; + const persistentSet = buildPersistentSet(demo); + const poolIdToUniverseIndex = buildPoolIdToUniverseIndex(demo); + + // For stats: count persistent/non-persistent + let persistentCount = 0, nonPersistentCount = 0; + for (const member of committee) { + const poolId = getCommitteePoolId(member); + if (persistentSet.has(poolId)) persistentCount++; + else nonPersistentCount++; + } + setText("committee_total", committee.length || "—"); + setText("committee_persistent", persistentCount || "—"); + setText("committee_nonpersistent", nonPersistentCount || "—"); + + // Sort committee members by universe index (ascending), fallback to original order + const sorted = [...committee].sort((a, b) => { + const idxA = poolIdToUniverseIndex.get(getCommitteePoolId(a)) ?? 99999; + const idxB = poolIdToUniverseIndex.get(getCommitteePoolId(b)) ?? 99999; + return idxA - idxB; + }); + + container.classList.add("universe-grid"); + container.innerHTML = ""; + sorted.forEach((member, i) => { + const poolId = getCommitteePoolId(member) || ""; + const isPersistent = persistentSet.has(poolId); + const universeIdx = poolIdToUniverseIndex.get(poolId) || ""; + + const div = document.createElement("div"); + div.classList.add("pool-dot"); + div.classList.add(isPersistent ? "is-persistent" : "is-nonpersistent"); + + // Numeric label: universe index (if known), else fallback to committee index + const label = document.createElement("span"); + label.classList.add("node-label"); + const idx = universeIdx || (i + 1); + label.textContent = idx; + label.style.fontSize = (idx >= 100 ? "11px" : "13px"); + div.appendChild(label); + const stake = typeof member.stake !== "undefined" ? member.stake : ""; + div.setAttribute("data-tooltip-html", `Pool ID: ${poolId}
    Stake: ${stake}`); + div.title = ""; + + div.addEventListener("mouseenter", (e) => { + const tooltip = document.createElement("div"); + tooltip.className = "tooltip-box"; + tooltip.innerHTML = div.getAttribute("data-tooltip-html"); + document.body.appendChild(tooltip); + const rect = e.target.getBoundingClientRect(); + tooltip.style.left = rect.left + window.scrollX + "px"; + tooltip.style.top = rect.top + window.scrollY - 40 + "px"; + div._tooltip = tooltip; + }); + div.addEventListener("mouseleave", () => { + if (div._tooltip) { + div._tooltip.remove(); + div._tooltip = null; + } + }); container.appendChild(div); }); } +function buildVoterPools(demo) { + const pidToPool = buildPersistentIdToPoolId(demo && demo.persistent_map); + const pids = (demo?.voters?.persistent_ids ?? []); + const nonp = (demo?.voters?.nonpersistent_pool_ids ?? []); + const persistentPools = pids.map(pid => pidToPool.get(pid)).filter(Boolean); + const nonpersistentPools = nonp.filter(Boolean); + return { + persistentPools, + nonpersistentPools, + all: [...new Set([...persistentPools, ...nonpersistentPools])] + }; +} + +function renderVotersFromDemo(demo) { + if (!demo) return; + const container = document.getElementById("voters_canvas"); + const { persistentPools, nonpersistentPools, all } = buildVoterPools(demo); + const poolIdToUniverseIndex = buildPoolIdToUniverseIndex(demo); + + // Build a lookup for stakes so tooltips can show them for voters + const poolIdToStake = new Map(); + if (Array.isArray(demo.universe)) { + for (const p of demo.universe) { + const id = p.pool_id || p.id; + if (id) poolIdToStake.set(id, (typeof p.stake !== "undefined") ? p.stake : ""); + } + } + if (Array.isArray(demo.committee)) { + for (const m of demo.committee) { + const id = m.pool_id || m.id; + if (id && !poolIdToStake.has(id)) { + poolIdToStake.set(id, (typeof m.stake !== "undefined") ? m.stake : ""); + } + } + } + + // Stats: persistent/nonpersistent, and breakdown for *nonpersistent only* + const persistentCount = persistentPools.length; + const nonPersistentCount = nonpersistentPools.length; + + // Build a set of committee poolIds for quick membership checks + const committeeSet = new Set(); + if (demo.committee) { + for (const member of demo.committee) { + const pid = getCommitteePoolId(member); + if (pid) committeeSet.add(pid); + } + } + + // Count only among non-persistent voters + let nonpInCommittee = 0, nonpOutsideCommittee = 0; + for (const poolId of nonpersistentPools) { + if (committeeSet.has(poolId)) nonpInCommittee++; + else nonpOutsideCommittee++; + } + + setText("voters_total", (persistentCount + nonPersistentCount) || "—"); + setText("voters_persistent", persistentCount || "—"); + setText("voters_nonpersistent", nonPersistentCount || "—"); + setText("voters_nonpersistent_in_committee", nonpInCommittee || "—"); + setText("voters_nonpersistent_outside_committee", nonpOutsideCommittee || "—"); + + if (!container) return; + ensureUniverseStyles(); + container.classList.add("universe-grid"); + container.innerHTML = ""; + + // Sort by universe index (ascending) + const sorted = [...all].sort((a, b) => { + const idxA = poolIdToUniverseIndex.get(a) ?? 99999; + const idxB = poolIdToUniverseIndex.get(b) ?? 99999; + return idxA - idxB; + }); + for (const poolId of sorted) { + const div = document.createElement("div"); + div.className = "pool-dot"; + if (persistentPools.includes(poolId)) div.classList.add("is-persistent"); + else div.classList.add("is-nonpersistent"); + + // label = 1-based index in universe + const label = document.createElement("span"); + label.classList.add("node-label"); + const idx = poolIdToUniverseIndex.get(poolId) || ""; + label.textContent = idx ? String(idx) : ""; + label.style.fontSize = (idx >= 100 ? "11px" : "13px"); + div.appendChild(label); + + const stake = poolIdToStake.get(poolId); + const stakeLine = (stake !== undefined && stake !== "") ? `
    Stake: ${stake}` : ""; + div.setAttribute("data-tooltip-html", `Pool ID: ${poolId}` + stakeLine); + div.title = ""; + + div.addEventListener("mouseenter", (e) => { + const tip = document.createElement("div"); + tip.className = "tooltip-box"; + tip.innerHTML = div.getAttribute("data-tooltip-html"); + document.body.appendChild(tip); + const rect = e.target.getBoundingClientRect(); + tip.style.left = rect.left + window.scrollX + "px"; + tip.style.top = rect.top + window.scrollY - 40 + "px"; + div._tooltip = tip; + }); + div.addEventListener("mouseleave", () => { + if (div._tooltip) { div._tooltip.remove(); div._tooltip = null; } + }); + container.appendChild(div); + } +} + // ---------- data loading ---------- function getRunFromURL() { const p = new URLSearchParams(window.location.search); @@ -208,8 +476,6 @@ function fillIdentifiers(obj) { if (!obj) return; const eid = obj.eid || obj.EID; const eb = obj.eb_hash || obj.eb || obj.EB || obj.ebHash; - - // Update both legacy ids and the display ids if (eid) { setText("eid", eid); setText("eid_value", eid); @@ -225,20 +491,34 @@ document.addEventListener("DOMContentLoaded", async () => { setText("universe_total", "—"); setText("universe_persistent", "—"); setText("universe_nonpersistent", "—"); + setText("committee_total", "—"); + setText("committee_persistent", "—"); + setText("committee_nonpersistent", "—"); setText("eid", "—"); setText("eb", "—"); setText("eid_value", "—"); setText("ebhash_value", "—"); + setText("voters_total", "—"); + setText("voters_persistent", "—"); + setText("voters_nonpersistent", "—"); + setText("voters_in_committee", "—"); + setText("voters_outside_committee", "—"); const demo = await loadDemoJson(); if (demo) { const universe = demo.universe || demo; renderUniverse(universe); - - // Try identifiers from either `identifiers` or `certificate` blocks fillIdentifiers(demo.identifiers); fillIdentifiers(demo.certificate); + renderCommitteeFromDemo(demo); + renderVotersFromDemo(demo); + + // Align grid columns across sections and keep them in sync on resize + syncGridColumns(); + window.addEventListener("resize", () => { + syncGridColumns(); + }); } else { renderUniverse([]); } @@ -254,27 +534,20 @@ function shortenMiddle(str, keepStart = 26, keepEnd = 22) { function watchAndEllipsize(id, opts = {}) { const el = document.getElementById(id); if (!el) return; - const { keepStart = 26, keepEnd = 22, truncate = true } = opts; - const apply = () => { - // Remember the full value once, then only display a truncated version const full = el.getAttribute('data-full') || el.textContent.trim(); el.setAttribute('data-full', full); - el.title = full; // hover shows the complete hash - el.classList.add('mono'); // monospace for readability + el.title = full; + el.classList.add('mono'); el.textContent = truncate ? shortenMiddle(full, keepStart, keepEnd) : full; }; - - // Re-apply if someone else overwrites the text const mo = new MutationObserver(apply); mo.observe(el, { childList: true, characterData: true, subtree: true }); - apply(); } document.addEventListener('DOMContentLoaded', () => { - // EID: show full; EB hash: show middle-ellipsized so both fit on one line watchAndEllipsize('eid_value', { truncate: false }); watchAndEllipsize('ebhash_value', { keepStart: 26, keepEnd: 22, truncate: true }); }); \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/static/styles.css b/crypto-benchmarks.rs/demo/ui/static/styles.css index 2f7894112..eac9a6482 100644 --- a/crypto-benchmarks.rs/demo/ui/static/styles.css +++ b/crypto-benchmarks.rs/demo/ui/static/styles.css @@ -32,6 +32,17 @@ body { color: var(--orange); } +/* utility helpers */ +.center { + text-align: center; +} + +.card-subtitle { + margin: 0 0 10px; + color: var(--muted); + font-size: 13px; +} + header { max-width: 1100px; margin: 20px auto; @@ -254,6 +265,21 @@ tbody tr:hover { z-index: 1000; } +/* Tooltip for voters hover (positioned inside .universe-canvas) */ +#voters_tooltip { + position: absolute; + padding: 6px 8px; + background: rgba(15, 23, 32, 0.98); + border: 1px solid var(--border); + color: var(--text); + font-size: 12px; + border-radius: 6px; + pointer-events: none; + white-space: nowrap; + transform: translate(-50%, -130%); + z-index: 1000; +} + @media (min-width: 900px) { .pool-dot { width: 18px; @@ -306,4 +332,9 @@ tbody tr:hover { .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 13.5px; +} + +.nonpersistent-indent { + margin-left: 18px; + color: var(--orange); } \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/templates/index.html b/crypto-benchmarks.rs/demo/ui/templates/index.html index b18e9442b..d8eb2f114 100644 --- a/crypto-benchmarks.rs/demo/ui/templates/index.html +++ b/crypto-benchmarks.rs/demo/ui/templates/index.html @@ -78,10 +78,68 @@

    Election

    } --> + +
    +

    Committee

    +

    + Persistent (Fait Accompli) and Non-persistent (Local Sortition) +

    - +
    +
    +
    + Total committee seats: +
    +
    + Persistent: + +
    +
    + Non-persistent: + +
    +
    + + +
    +
    + +
    +

    Voters

    - +
    +
    +
    + Total voters: +
    +
    + Persistent: + +
    +
    + Non-persistent: + +
    +
    + in committee: + +
    +
    + outside committee: + +
    +
    +
    + + + +
    +
    +
    From c0b0adb81512e66f4f7d5ed24f73a1b921a81850 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Mon, 20 Oct 2025 19:53:36 +0900 Subject: [PATCH 07/16] enhance UI visualization of Pools, Committee Selection and Voting --- crypto-benchmarks.rs/demo/README.md | 22 +- crypto-benchmarks.rs/demo/run64/demo.json | 1846 ----------------- .../demo/scripts/10_init_inputs.sh | 6 +- .../demo/scripts/25_export_demo_json.sh | 302 +-- .../demo/scripts/30_cast_votes.sh | 2 + .../demo/scripts/70_run_one.sh | 3 + .../demo/scripts/extract_committee.py | 203 -- crypto-benchmarks.rs/demo/ui/server.py | 11 +- crypto-benchmarks.rs/demo/ui/static/app.js | 1067 +++++++--- .../demo/ui/static/styles.css | 670 ++++-- .../demo/ui/templates/index.html | 149 +- 11 files changed, 1565 insertions(+), 2716 deletions(-) delete mode 100644 crypto-benchmarks.rs/demo/run64/demo.json delete mode 100755 crypto-benchmarks.rs/demo/scripts/extract_committee.py diff --git a/crypto-benchmarks.rs/demo/README.md b/crypto-benchmarks.rs/demo/README.md index 23cff8629..751c164fd 100644 --- a/crypto-benchmarks.rs/demo/README.md +++ b/crypto-benchmarks.rs/demo/README.md @@ -40,12 +40,14 @@ The scripts are designed to be run from the `demo/` directory. You can run each script individually to understand and control each step of the process for a given number of voters (e.g., 32). Use the `-d` option to specify the output directory (e.g., `run32`). +RUN=run100 + #### 10_init_inputs.sh Initialize inputs for N voters: ```bash -scripts/10_init_inputs.sh -d run64 --pools 200 --stake 100000 --alpha 8 --beta 1 +scripts/10_init_inputs.sh -d "$RUN" --pools 500 --stake 100000 --alpha 9 --beta 1 ``` #### 20_make_registry.sh @@ -53,7 +55,7 @@ scripts/10_init_inputs.sh -d run64 --pools 200 --stake 100000 --alpha 8 --beta 1 Build the registry from initialized inputs: ```bash -scripts/20_make_registry.sh -d run64 -n 64 +./scripts/20_make_registry.sh -d "$RUN" -n 100 ``` #### 30_cast_votes.sh @@ -61,7 +63,7 @@ scripts/20_make_registry.sh -d run64 -n 64 Cast votes with a specified fraction of voters voting (e.g., 1.0 means all vote): ```bash -scripts/30_cast_votes.sh -d run64 -f 0.55 +scripts/30_cast_votes.sh -d "$RUN" -f 0.75 ``` #### 40_make_certificate.sh @@ -69,7 +71,7 @@ scripts/30_cast_votes.sh -d run64 -f 0.55 Generate the aggregated certificate: ```bash -scripts/40_make_certificate.sh -d run64 +scripts/40_make_certificate.sh -d "$RUN" ``` #### 50_verify_certificate.sh @@ -77,7 +79,7 @@ scripts/40_make_certificate.sh -d run64 Verify the generated certificate: ```bash -scripts/50_verify_certificate.sh -d run64 +scripts/50_verify_certificate.sh -d "$RUN" ``` #### 60_pretty_print_cert.sh @@ -85,7 +87,7 @@ scripts/50_verify_certificate.sh -d run64 Pretty-print key metrics and statistics of the certificate: ```bash -scripts/60_pretty_print_cert.sh -d run64 +scripts/60_pretty_print_cert.sh -d "$RUN" ``` #### 25_export_demo_json.sh @@ -93,13 +95,13 @@ scripts/60_pretty_print_cert.sh -d run64 Export all relevant data (pools, committee, voters, and certificate summary) into a single `demo.json` file used by the visualization UI. ```bash -scripts/25_export_demo_json.sh -d run64 +scripts/25_export_demo_json.sh -d "$RUN" ``` ### Run a Single End-to-End Demo ```bash -scripts/70_run_one.sh -d run64 -n 64 -f 0.75 +scripts/70_run_one.sh -d "$RUN" -n 100 -f 0.75 ``` This will: @@ -111,12 +113,12 @@ This will: 5. Verify the certificate (`50_verify_certificate.sh`) 6. Pretty-print key metrics (`60_pretty_print_cert.sh`) -All files are placed in `demo/run64/`. +All files are placed in `demo/run100/`. ### Sweep Across Multiple N ```bash -scripts/80_sweep.sh -d sweep1 -f 1.0 --ns "32 64 128 256 512 1024 2056 3000" +scripts/80_sweep.sh -d sweep1 -f 1.0 --ns "50 100 200 500 1000 2000 3000" ``` This will run the full pipeline for multiple voter sizes (`N`) and write a CSV of results: diff --git a/crypto-benchmarks.rs/demo/run64/demo.json b/crypto-benchmarks.rs/demo/run64/demo.json deleted file mode 100644 index 07ac04135..000000000 --- a/crypto-benchmarks.rs/demo/run64/demo.json +++ /dev/null @@ -1,1846 +0,0 @@ -{ - "params": { - "N": 64, - "pool_count": 199, - "total_stake": 99996 - }, - "universe": [ - { - "pool_id": "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c", - "stake": 4530, - "is_persistent": true - }, - { - "pool_id": "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647", - "stake": 3917, - "is_persistent": true - }, - { - "pool_id": "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c", - "stake": 3892, - "is_persistent": true - }, - { - "pool_id": "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529", - "stake": 3681, - "is_persistent": true - }, - { - "pool_id": "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d", - "stake": 3081, - "is_persistent": true - }, - { - "pool_id": "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479", - "stake": 2982, - "is_persistent": true - }, - { - "pool_id": "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb", - "stake": 2904, - "is_persistent": true - }, - { - "pool_id": "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1", - "stake": 2901, - "is_persistent": true - }, - { - "pool_id": "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0", - "stake": 2827, - "is_persistent": true - }, - { - "pool_id": "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874", - "stake": 2714, - "is_persistent": true - }, - { - "pool_id": "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef", - "stake": 2654, - "is_persistent": true - }, - { - "pool_id": "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff", - "stake": 2635, - "is_persistent": true - }, - { - "pool_id": "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c", - "stake": 2602, - "is_persistent": true - }, - { - "pool_id": "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137", - "stake": 2537, - "is_persistent": true - }, - { - "pool_id": "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f", - "stake": 2432, - "is_persistent": true - }, - { - "pool_id": "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e", - "stake": 2182, - "is_persistent": true - }, - { - "pool_id": "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8", - "stake": 2080, - "is_persistent": true - }, - { - "pool_id": "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07", - "stake": 1994, - "is_persistent": true - }, - { - "pool_id": "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff", - "stake": 1800, - "is_persistent": true - }, - { - "pool_id": "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be", - "stake": 1796, - "is_persistent": true - }, - { - "pool_id": "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de", - "stake": 1752, - "is_persistent": true - }, - { - "pool_id": "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000", - "stake": 1721, - "is_persistent": true - }, - { - "pool_id": "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155", - "stake": 1689, - "is_persistent": true - }, - { - "pool_id": "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2", - "stake": 1686, - "is_persistent": true - }, - { - "pool_id": "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c", - "stake": 1621, - "is_persistent": true - }, - { - "pool_id": "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58", - "stake": 1610, - "is_persistent": true - }, - { - "pool_id": "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff", - "stake": 1506, - "is_persistent": true - }, - { - "pool_id": "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376", - "stake": 1470, - "is_persistent": true - }, - { - "pool_id": "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551", - "stake": 1463, - "is_persistent": true - }, - { - "pool_id": "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297", - "stake": 1362, - "is_persistent": true - }, - { - "pool_id": "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01", - "stake": 1319, - "is_persistent": true - }, - { - "pool_id": "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc", - "stake": 1294, - "is_persistent": true - }, - { - "pool_id": "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966", - "stake": 1279, - "is_persistent": true - }, - { - "pool_id": "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938", - "stake": 1146, - "is_persistent": true - }, - { - "pool_id": "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf", - "stake": 1066, - "is_persistent": true - }, - { - "pool_id": "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf", - "stake": 1059, - "is_persistent": true - }, - { - "pool_id": "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8", - "stake": 1045, - "is_persistent": true - }, - { - "pool_id": "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11", - "stake": 1016, - "is_persistent": true - }, - { - "pool_id": "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416", - "stake": 970, - "is_persistent": true - }, - { - "pool_id": "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283", - "stake": 967, - "is_persistent": true - }, - { - "pool_id": "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64", - "stake": 935, - "is_persistent": true - }, - { - "pool_id": "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8", - "stake": 891, - "is_persistent": true - }, - { - "pool_id": "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99", - "stake": 793, - "is_persistent": true - }, - { - "pool_id": "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d", - "stake": 665, - "is_persistent": true - }, - { - "pool_id": "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c", - "stake": 640, - "is_persistent": true - }, - { - "pool_id": "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3", - "stake": 638, - "is_persistent": true - }, - { - "pool_id": "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690", - "stake": 611, - "is_persistent": true - }, - { - "pool_id": "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff", - "stake": 598, - "is_persistent": true - }, - { - "pool_id": "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601", - "stake": 589, - "is_persistent": true - }, - { - "pool_id": "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2", - "stake": 561, - "is_persistent": true - }, - { - "pool_id": "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b", - "stake": 548, - "is_persistent": true - }, - { - "pool_id": "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2", - "stake": 474, - "is_persistent": true - }, - { - "pool_id": "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201", - "stake": 451, - "is_persistent": true - }, - { - "pool_id": "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7", - "stake": 441, - "is_persistent": true - }, - { - "pool_id": "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b", - "stake": 424, - "is_persistent": true - }, - { - "pool_id": "e408900057c66f11f77a090112a8a9d885c435735ce121ddfad32771", - "stake": 411, - "is_persistent": false - }, - { - "pool_id": "b12a95008fb0cbcbf023dd6f8efeb2cf71f71356c54200209b1c78ff", - "stake": 365, - "is_persistent": false - }, - { - "pool_id": "d687c5c9643f59210012ee0b39cad3a7a75cd1614e69720101080ffa", - "stake": 350, - "is_persistent": false - }, - { - "pool_id": "d77e83a3519663dd32e8bb0e5ae8bf8f8624ab8a0208e59e82befd77", - "stake": 310, - "is_persistent": false - }, - { - "pool_id": "f25001dc1b54cefa2a1e0bf75b9701ecc0001ce16f12cb7fb98ed60f", - "stake": 308, - "is_persistent": false - }, - { - "pool_id": "6201cbc21cd34e200053404dd9afa9009c77196e329afaff960cf534", - "stake": 306, - "is_persistent": false - }, - { - "pool_id": "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631", - "stake": 304, - "is_persistent": false - }, - { - "pool_id": "ffd0ffff97a811e712c14add1047bec50016d7e50101b6fb3872ff00", - "stake": 302, - "is_persistent": false - }, - { - "pool_id": "ef39dc5761054877a0c3a51e00a7424b72fb4effdc722fede2c3ac26", - "stake": 295, - "is_persistent": false - }, - { - "pool_id": "3e746153eff970bd4887887c7003007035014a11cf0001bf01466cfe", - "stake": 273, - "is_persistent": false - }, - { - "pool_id": "e9d532daff2d3e922baa003d3e5447e92facd96a2dcb722c30ff8d02", - "stake": 265, - "is_persistent": false - }, - { - "pool_id": "d2ec42d9f307fdc985e0010001d9e5603b00198e3751a0487eb2b03a", - "stake": 259, - "is_persistent": false - }, - { - "pool_id": "240cb24fdab700ef2e8a6029a4bb9a82a626737d413da2787c01b3b9", - "stake": 238, - "is_persistent": false - }, - { - "pool_id": "aa821cd818d52c9aa71a83a64443558439a950be61ff281d070478ff", - "stake": 217, - "is_persistent": false - }, - { - "pool_id": "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3", - "stake": 215, - "is_persistent": false - }, - { - "pool_id": "43ff5b810501f390d17faf096b9812efa79866fbb20023be2f012201", - "stake": 213, - "is_persistent": false - }, - { - "pool_id": "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff", - "stake": 179, - "is_persistent": false - }, - { - "pool_id": "007b7ae1a544a2fe7cff0feeffffa0aba683d0ff5326ff4674ec5da4", - "stake": 172, - "is_persistent": false - }, - { - "pool_id": "c99031b2f1858bd91d522bf565784c28a98897011ad3cb845fdd58f6", - "stake": 163, - "is_persistent": false - }, - { - "pool_id": "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7", - "stake": 154, - "is_persistent": false - }, - { - "pool_id": "c76154138a2dee9754f07a426ce7bb5c629099e571a410afb710beba", - "stake": 146, - "is_persistent": false - }, - { - "pool_id": "ff0258361001a58a5a959c501608034201700c01fe14e49028fb248c", - "stake": 125, - "is_persistent": false - }, - { - "pool_id": "e00327090b0133c301979bdf4b08de0198d70775b451733d6381650a", - "stake": 115, - "is_persistent": false - }, - { - "pool_id": "0c036b0369bd5cbd5f00588bab585433771de2004bce47d000a11500", - "stake": 110, - "is_persistent": false - }, - { - "pool_id": "fa12ffe085438b9fff811a2003caaf8fde9b00b63f24f0fef270aa7a", - "stake": 106, - "is_persistent": false - }, - { - "pool_id": "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a", - "stake": 104, - "is_persistent": false - }, - { - "pool_id": "b8c4dc65f9ffad410fee7997f047e8f521cd01d31c0392deb8e65a42", - "stake": 103, - "is_persistent": false - }, - { - "pool_id": "266464354274595cd5b4008fa0e87c95e4c9e223fe95bee1dfcc0d9c", - "stake": 95, - "is_persistent": false - }, - { - "pool_id": "07a0e9d497f6836060fd84af10afb2f2994e04d0f8a2b770ff3c88b3", - "stake": 93, - "is_persistent": false - }, - { - "pool_id": "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6", - "stake": 86, - "is_persistent": false - }, - { - "pool_id": "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2", - "stake": 86, - "is_persistent": false - }, - { - "pool_id": "be99c5965bb5017581c9d8e9c3b68100dcf3e4236653ed51f8b90b44", - "stake": 77, - "is_persistent": false - }, - { - "pool_id": "f40bf01daf809d61e68649f04a1d8dda9151ed6838c89078b68dc3fe", - "stake": 77, - "is_persistent": false - }, - { - "pool_id": "afd43c3b45c0214e05e5ff7610411c39faef681c92d09c2c693720c2", - "stake": 72, - "is_persistent": false - }, - { - "pool_id": "a9bf7f2ccd409bae63b3e6c101497909c3e0ffaf1a47e900ff00f8c2", - "stake": 67, - "is_persistent": false - }, - { - "pool_id": "6f2e179432e5c4c6df001d558e0000e0e210c5262d00a4182ad76426", - "stake": 59, - "is_persistent": false - }, - { - "pool_id": "b0d3ed01ff74ffb000a9954dae59e570dca5ff5bbbd3470101c7cf39", - "stake": 59, - "is_persistent": false - }, - { - "pool_id": "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9", - "stake": 57, - "is_persistent": false - }, - { - "pool_id": "007a17b9f86300003200366e28b71dff215de1e8c47d558d10b6ffbe", - "stake": 43, - "is_persistent": false - }, - { - "pool_id": "1c3ef8ff647f35904412ed32ebc625b33edac269017e0197005d1ccf", - "stake": 41, - "is_persistent": false - }, - { - "pool_id": "ec2ca9da0aaa43535b95ff537c7bb700ffbafc5c0efbe90063aebf00", - "stake": 40, - "is_persistent": false - }, - { - "pool_id": "e075b8012d3a1001794f751396dce75b672e6a0004ffff01c71ac100", - "stake": 39, - "is_persistent": false - }, - { - "pool_id": "796240fffbf7c801e8402469010115a270c3f6d7ce21fa9e6fff052d", - "stake": 34, - "is_persistent": false - }, - { - "pool_id": "e57ec7c7a233479e45d4dfb23c85b21b85ffe93d2c038986f60d0132", - "stake": 33, - "is_persistent": false - }, - { - "pool_id": "cbf94fc524a51d030876a158f9ec8dffffce8f2148f1bb5b61445b81", - "stake": 32, - "is_persistent": false - }, - { - "pool_id": "72394760a3ce4d7e01ffffc45cd7e8de83806bffff382dde3cac0f33", - "stake": 27, - "is_persistent": false - }, - { - "pool_id": "004cb789ff416601c489fe8a2a69bca620ff7cd4d32cfd9449543e06", - "stake": 22, - "is_persistent": false - }, - { - "pool_id": "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571", - "stake": 21, - "is_persistent": false - }, - { - "pool_id": "a17fb2e3ff00d898bd89a6b9ef49bf08e21c237a297f71379aeed225", - "stake": 21, - "is_persistent": false - }, - { - "pool_id": "ff3737d352fef3e653a296978a09b419e2d0016fbfff7c77aaeaa001", - "stake": 21, - "is_persistent": false - }, - { - "pool_id": "5d272ea05f63673c6c001f9bdb6d3e225fa602e6d2ff2a663fa66745", - "stake": 20, - "is_persistent": false - }, - { - "pool_id": "01a9b2a9c6778ff1072bf76e0064943701608a1fffd618d301654fa7", - "stake": 19, - "is_persistent": false - }, - { - "pool_id": "bc70858172851b9dff34ffe231d8892c57833a9e666e8f0313df691b", - "stake": 19, - "is_persistent": false - }, - { - "pool_id": "d57ed603a89d8342859e075947c91b1a255ae94a9d01b1a530457ff2", - "stake": 16, - "is_persistent": false - }, - { - "pool_id": "091b2203cb2f3b885a10c326cfe92b7d0bfe2b00024779b4a7892d71", - "stake": 15, - "is_persistent": false - }, - { - "pool_id": "ffebd8db93a0ef539f836c8312550100003fa9641ce984597ef53398", - "stake": 13, - "is_persistent": false - }, - { - "pool_id": "4fff4cf77029f600d9ffc0c8fd981a0ef37cffd4bd42db9050eff524", - "stake": 12, - "is_persistent": false - }, - { - "pool_id": "9cf24b7b01a1d90a01a9009d3a4466b39500e4191588639ee7f7b800", - "stake": 12, - "is_persistent": false - }, - { - "pool_id": "194d9c992e9cfe00ac2f46cd76008dd20723d254c0dd07fab24da548", - "stake": 11, - "is_persistent": false - }, - { - "pool_id": "2fbc5ad791c60d17bb0ff900717c9fa1197a612a72cd4e200182c273", - "stake": 11, - "is_persistent": false - }, - { - "pool_id": "c1410131a6f50142d468da34025d7a06f4ff007a78fb7394c346e513", - "stake": 11, - "is_persistent": false - }, - { - "pool_id": "02f28ceeeb8f8eff0be401993255ffba60bffdc2a2c19a01b661978b", - "stake": 7, - "is_persistent": false - }, - { - "pool_id": "0fffaa50003b42815a126701fa6b50d1216fcc86aef307e798f737c0", - "stake": 7, - "is_persistent": false - }, - { - "pool_id": "16ea953d050b5f84e64e137a26a4cec82bf85000f6b25fede6ff3503", - "stake": 7, - "is_persistent": false - }, - { - "pool_id": "2c66f80bec0367db4d43e9459e82fe7e446c5789d5ac017f2dd79a01", - "stake": 7, - "is_persistent": false - }, - { - "pool_id": "819acf08e187cd01ec1fd839391b61e4c8aacc303a8437bf2fd5887c", - "stake": 7, - "is_persistent": false - }, - { - "pool_id": "ec2c06173fe7c758fff3d8011e01547f09e95dd49ebf9ef20143f770", - "stake": 5, - "is_persistent": false - }, - { - "pool_id": "25f0ff0009242e694cefb24c45008eb58e4daa1ab6ff507fdd0e59a1", - "stake": 4, - "is_persistent": false - }, - { - "pool_id": "3900e35dff96420ceff3f6c2e129172af3d001e66a8c01501d77a247", - "stake": 4, - "is_persistent": false - }, - { - "pool_id": "7be8985d82660003fd79f9d2ffffe524418faf6611f3362671845f1f", - "stake": 4, - "is_persistent": false - }, - { - "pool_id": "acf227b3222573270a400f97aad8ca6d976ef6a67632f6491c00d8f8", - "stake": 4, - "is_persistent": false - }, - { - "pool_id": "0d4ae5a909eed7912c494cc705a97ea26025cd98483101314aa2fb01", - "stake": 3, - "is_persistent": false - }, - { - "pool_id": "5ded7dca81e99cb3869563b6ff4a759351e2b98b071b29307a743a2e", - "stake": 3, - "is_persistent": false - }, - { - "pool_id": "734cf0ff12bb7b70a2ac9ab91dbbf205205368b30bc5ee3fffc05601", - "stake": 3, - "is_persistent": false - }, - { - "pool_id": "77ffba0e5b2f65cdf4f22e201d924a8377ff153aff6e148dfb78eee7", - "stake": 3, - "is_persistent": false - }, - { - "pool_id": "01d190af9500d0aeaa702ab126011e0d247adc1cfd25ffdeef910a15", - "stake": 2, - "is_persistent": false - }, - { - "pool_id": "1a6e415a3687e9007845ceeee56fea563e085ba89cf36f64c10eaa5c", - "stake": 2, - "is_persistent": false - }, - { - "pool_id": "abc5877fb3e9dc6c2ecebcb6bdd3a2ff0172f50722c4e0ffe60058a3", - "stake": 2, - "is_persistent": false - }, - { - "pool_id": "b2ed18f25c6f642cd232a5d119b94116e2fff900afee000e82026c5c", - "stake": 2, - "is_persistent": false - }, - { - "pool_id": "0fc78d01677d5b1c6b3ba67546c012a6a61b13a48733214231a5528f", - "stake": 1, - "is_persistent": false - }, - { - "pool_id": "1c4e2a0065d7942da94b7a91b434226ff65fb7256b355fc5bea52e18", - "stake": 1, - "is_persistent": false - }, - { - "pool_id": "494fc1cbd17a5333681ceb01290953018fe9c53c4f9adbb9a1af0046", - "stake": 1, - "is_persistent": false - }, - { - "pool_id": "4c75a8782091320088cd01e900a8aac412f4838654f729a787564b68", - "stake": 1, - "is_persistent": false - }, - { - "pool_id": "5be0ea83207a384c55f0c77336e02e7b93ff43ac5a36e23092a9ff0f", - "stake": 1, - "is_persistent": false - }, - { - "pool_id": "95264801d09a36c0ff36dd56479215bfea3d006f5500548ffff69059", - "stake": 1, - "is_persistent": false - }, - { - "pool_id": "a34a3a08407225822a8a25660081101461ffb022eb8a4d9f0b6200d5", - "stake": 1, - "is_persistent": false - }, - { - "pool_id": "d3ff4b015b31bb991295a301fc5eea195744f31fc276a817d284350a", - "stake": 1, - "is_persistent": false - }, - { - "pool_id": "db8f1457b1c223a2d03a8b42ff169100ffff3995cefff5985c5100e8", - "stake": 1, - "is_persistent": false - }, - { - "pool_id": "ff860082386cf7001aead49dfbd50e75beff4ca7f138d4711ee7016b", - "stake": 1, - "is_persistent": false - }, - { - "pool_id": "00600cc9d279ccaabd76ffff00eba32b210027c4038c305b99962a77", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "016c8800ff690bda9802a96f134a4f03ac65eb4541f9656d0e523d00", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "01f702c970b3ff2bb3490d9a6b677cd46e1e043621ff1c174024087d", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "0ed40164004ac401ffde79a9e60422e200a7fd9c43a4ef283c00145d", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "191d68de0259e70d2a55ae59e463ae00c7dcc69136bb0096af86c346", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "1a0113e50000119207ae22fce1bd3ebfe885a29cb3b9f5bd7013e38e", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "1a51ffff00668dff0497605e3dca074f81337cf6bf21e0fff1d80042", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "1bd26b6fbf600023666d90ad14bfea1bff12cfa018d74e471e4384c1", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "1c7acb7a0073e9c3b78aff9b00f016f6533f3800ff01429e3078cbec", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "1cc4d59507714de1b61e362142ef26fbe7ee4aac91df5e4bee01f1f5", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "25c36181b2f2d234816d0c7600800d7a2c62e3e5dd258d4ec5217008", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "2ff34789ff111c01ad6ea6ac03f7a0014cffe9626ad536aa62007479", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "33248dee56388df4016a36d000440ec36f5b392e7ee170e9964fc97c", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "36353475ad3c1ae87b7000ff67d6b800d200b31900ba17011343f831", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "374145d49f4f4f074e5c2e0153d888933399599ef2c4be0121017947", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "3940f037f98d800000019e415e59d0009517a17ae67f6d1cf983583b", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "45d2abaa00a900ff78ada641f03879ff335e1fd9e86630fa0403d6ff", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "4a892cc61350083301181737176d0c7f69b6c12768980ee19afd49a2", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "50ccc49ec9c00b8a04231f25a256275613d2f4b991195fc201003932", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "566900ee95b45900f4d57b8f65c9f58087492e850ac2294d92144981", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "68858ee00098a6c201492fdb09f8be0b546e3d537dfe811e98ab615c", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "6ec653bc3501fba1eceff8004464dd47aa788966e2988813a2b19292", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "704d2d1c018eb90166c653602d00e0b52a01faeb0e29ff92ff791422", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "764801d5b6f51b85d1c425ffdb01926813fb0029d19e79f73741e2d5", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "7f70ef01c542bb9953ff025e477aa461511eaf2213f4ef94bfcd0011", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "7f9969004c1550edd7b04ee88b5274013b12a50f990189fcca22c8cb", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "8275f96f65ce710176d500375005c37a8dff6da2ff1745ac1159ff78", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "8d8fd800075c6ae000bbff1fafd7ead5c4abe729d101f7cc7e6a06e7", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "9d9f9b28e4dcc35c3a053fdc0b066df880fd4411f02c2a171deee088", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "a08ae12343f534ee49bdf7ba256db00103e4203d00c0ffffb1074759", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "a37776b4002dd68c14012a6d52820b2d89e9c001f608014b43e692a3", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "a3989c00d6dc79286474ffed49c1dfeaf879592ff9a0ba38060500ad", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "a6931c2de82054753ad1735087a72721573f88cbe9003b7242d6f9fb", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "b3c2b40baa974b5bc50a5a2abebc0107389e4f738301650aea247773", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "b64d005afff675d5fa56e0cd58c86101dff90118d0559901f301139c", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "b8f5ff8ce57fe80cb4ffaeb6e290380094d1fa01016b0154e204a5a0", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "be37348ef3c8ab12541d008a848d0100449f8992013a0197b204b3b7", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "c200115f1d2248f470012dfff6df0b921602e93fd8eff38eea30ffe2", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "c3b822a5961ef78b091206a8dcf750d48470b71adb46d21d652221cb", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "c555e91c3c7ad44a24b39a609ce6320e4f791c73006aa1ff8beaa27a", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "c5e0157840b431e37609a9d50f4cb894e9565dbda4e47bd2ffe1c579", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "ca509072b97a359e9c4d0b9965f43f91cf01badd2adfef5b92d182e2", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "cc01ffa7295ed9bc1a0088f11036bda6207c8ffa953d96b9632876fc", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "cd59c4100e56a495ff6b601bf54f73b184ce16a2ffae965c36ebc9b6", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "df01fa63958c47c3a980a5e359b491b7b30ec6a16be10188a7850cba", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "e0d4796c1e839ee0f6cbc30ebf612206e258addfcec7091041ff393a", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "e2bcb1dc01c43d5ffd88321d01054fff2e164a797f056bb089390038", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "e4e12d25b5158aa86a9fad026702835c0060f3ffd6ffdbc537e02fd3", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "e5809e783436a02e5529b900ffff1600d1c25eb935f1f9f4907a3300", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "e84acc16a9470098f3007844976401019217f99ca565a301ec6f6401", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "f57ded9f084058a247f274ff8cfeed013774b4b5a993badef13d26d0", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "f6d22048980001e100166713014a9cba3e90ff8cecb6ccb0405c6da0", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "ff0d46fb0e55505106f2c507e252f1a1b4d401e4df369ad0e5ed5db1", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "ffd271d2cc05518088726b58fbc40c3b53efb000ff9bf0bca7a9ef8f", - "stake": 0, - "is_persistent": false - }, - { - "pool_id": "fff6ab31d94ddcb37334ec6a5c01016130018dffa3b86f010dc8fb3a", - "stake": 0, - "is_persistent": false - } - ], - "persistent_map": { - "0": "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c", - "1": "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647", - "2": "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c", - "3": "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529", - "4": "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d", - "5": "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479", - "6": "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb", - "7": "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1", - "8": "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0", - "9": "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874", - "10": "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef", - "11": "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff", - "12": "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c", - "13": "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137", - "14": "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f", - "15": "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e", - "16": "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8", - "17": "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07", - "18": "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff", - "19": "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be", - "20": "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de", - "21": "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000", - "22": "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155", - "23": "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2", - "24": "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c", - "25": "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58", - "26": "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff", - "27": "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376", - "28": "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551", - "29": "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297", - "30": "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01", - "31": "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc", - "32": "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966", - "33": "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938", - "34": "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf", - "35": "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf", - "36": "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8", - "37": "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11", - "38": "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416", - "39": "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283", - "40": "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64", - "41": "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8", - "42": "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99", - "43": "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d", - "44": "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c", - "45": "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3", - "46": "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690", - "47": "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff", - "48": "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601", - "49": "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2", - "50": "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b", - "51": "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2", - "52": "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201", - "53": "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7", - "54": "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b" - }, - "committee": [ - { - "position": 1, - "index": 1, - "pool_id": "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c", - "stake": 4530 - }, - { - "position": 2, - "index": 2, - "pool_id": "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647", - "stake": 3917 - }, - { - "position": 3, - "index": 3, - "pool_id": "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c", - "stake": 3892 - }, - { - "position": 4, - "index": 4, - "pool_id": "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529", - "stake": 3681 - }, - { - "position": 5, - "index": 5, - "pool_id": "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d", - "stake": 3081 - }, - { - "position": 6, - "index": 6, - "pool_id": "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479", - "stake": 2982 - }, - { - "position": 7, - "index": 7, - "pool_id": "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb", - "stake": 2904 - }, - { - "position": 8, - "index": 8, - "pool_id": "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1", - "stake": 2901 - }, - { - "position": 9, - "index": 9, - "pool_id": "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0", - "stake": 2827 - }, - { - "position": 10, - "index": 10, - "pool_id": "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874", - "stake": 2714 - }, - { - "position": 11, - "index": 11, - "pool_id": "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef", - "stake": 2654 - }, - { - "position": 12, - "index": 12, - "pool_id": "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff", - "stake": 2635 - }, - { - "position": 13, - "index": 13, - "pool_id": "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c", - "stake": 2602 - }, - { - "position": 14, - "index": 14, - "pool_id": "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137", - "stake": 2537 - }, - { - "position": 15, - "index": 15, - "pool_id": "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f", - "stake": 2432 - }, - { - "position": 16, - "index": 16, - "pool_id": "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e", - "stake": 2182 - }, - { - "position": 17, - "index": 17, - "pool_id": "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8", - "stake": 2080 - }, - { - "position": 18, - "index": 18, - "pool_id": "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07", - "stake": 1994 - }, - { - "position": 19, - "index": 19, - "pool_id": "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff", - "stake": 1800 - }, - { - "position": 20, - "index": 20, - "pool_id": "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be", - "stake": 1796 - }, - { - "position": 21, - "index": 21, - "pool_id": "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de", - "stake": 1752 - }, - { - "position": 22, - "index": 22, - "pool_id": "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000", - "stake": 1721 - }, - { - "position": 23, - "index": 23, - "pool_id": "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155", - "stake": 1689 - }, - { - "position": 24, - "index": 24, - "pool_id": "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2", - "stake": 1686 - }, - { - "position": 25, - "index": 25, - "pool_id": "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c", - "stake": 1621 - }, - { - "position": 26, - "index": 26, - "pool_id": "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58", - "stake": 1610 - }, - { - "position": 27, - "index": 27, - "pool_id": "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff", - "stake": 1506 - }, - { - "position": 28, - "index": 28, - "pool_id": "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376", - "stake": 1470 - }, - { - "position": 29, - "index": 29, - "pool_id": "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551", - "stake": 1463 - }, - { - "position": 30, - "index": 30, - "pool_id": "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297", - "stake": 1362 - }, - { - "position": 31, - "index": 31, - "pool_id": "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01", - "stake": 1319 - }, - { - "position": 32, - "index": 32, - "pool_id": "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc", - "stake": 1294 - }, - { - "position": 33, - "index": 33, - "pool_id": "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966", - "stake": 1279 - }, - { - "position": 34, - "index": 34, - "pool_id": "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938", - "stake": 1146 - }, - { - "position": 35, - "index": 35, - "pool_id": "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf", - "stake": 1066 - }, - { - "position": 36, - "index": 36, - "pool_id": "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf", - "stake": 1059 - }, - { - "position": 37, - "index": 37, - "pool_id": "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8", - "stake": 1045 - }, - { - "position": 38, - "index": 38, - "pool_id": "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11", - "stake": 1016 - }, - { - "position": 39, - "index": 39, - "pool_id": "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416", - "stake": 970 - }, - { - "position": 40, - "index": 40, - "pool_id": "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283", - "stake": 967 - }, - { - "position": 41, - "index": 41, - "pool_id": "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64", - "stake": 935 - }, - { - "position": 42, - "index": 42, - "pool_id": "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8", - "stake": 891 - }, - { - "position": 43, - "index": 43, - "pool_id": "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99", - "stake": 793 - }, - { - "position": 44, - "index": 44, - "pool_id": "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d", - "stake": 665 - }, - { - "position": 45, - "index": 45, - "pool_id": "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c", - "stake": 640 - }, - { - "position": 46, - "index": 46, - "pool_id": "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3", - "stake": 638 - }, - { - "position": 47, - "index": 47, - "pool_id": "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690", - "stake": 611 - }, - { - "position": 48, - "index": 48, - "pool_id": "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff", - "stake": 598 - }, - { - "position": 49, - "index": 49, - "pool_id": "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601", - "stake": 589 - }, - { - "position": 50, - "index": 50, - "pool_id": "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2", - "stake": 561 - }, - { - "position": 51, - "index": 51, - "pool_id": "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b", - "stake": 548 - }, - { - "position": 52, - "index": 52, - "pool_id": "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2", - "stake": 474 - }, - { - "position": 53, - "index": 53, - "pool_id": "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201", - "stake": 451 - }, - { - "position": 54, - "index": 54, - "pool_id": "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7", - "stake": 441 - }, - { - "position": 55, - "index": 55, - "pool_id": "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b", - "stake": 424 - }, - { - "position": 56, - "index": 70, - "pool_id": "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3", - "stake": 215 - }, - { - "position": 57, - "index": 81, - "pool_id": "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a", - "stake": 104 - }, - { - "position": 58, - "index": 62, - "pool_id": "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631", - "stake": 304 - }, - { - "position": 59, - "index": 85, - "pool_id": "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6", - "stake": 86 - }, - { - "position": 60, - "index": 93, - "pool_id": "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9", - "stake": 57 - }, - { - "position": 61, - "index": 103, - "pool_id": "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571", - "stake": 21 - }, - { - "position": 62, - "index": 72, - "pool_id": "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff", - "stake": 179 - }, - { - "position": 63, - "index": 75, - "pool_id": "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7", - "stake": 154 - }, - { - "position": 64, - "index": 86, - "pool_id": "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2", - "stake": 86 - } - ], - "committee_source": "fa+certificate", - "lookup": { - "universe_index_by_pool_id": { - "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c": 1, - "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647": 2, - "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c": 3, - "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529": 4, - "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d": 5, - "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479": 6, - "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb": 7, - "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1": 8, - "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0": 9, - "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874": 10, - "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef": 11, - "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff": 12, - "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c": 13, - "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137": 14, - "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f": 15, - "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e": 16, - "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8": 17, - "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07": 18, - "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff": 19, - "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be": 20, - "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de": 21, - "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000": 22, - "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155": 23, - "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2": 24, - "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c": 25, - "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58": 26, - "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff": 27, - "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376": 28, - "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551": 29, - "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297": 30, - "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01": 31, - "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc": 32, - "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966": 33, - "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938": 34, - "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf": 35, - "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf": 36, - "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8": 37, - "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11": 38, - "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416": 39, - "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283": 40, - "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64": 41, - "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8": 42, - "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99": 43, - "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d": 44, - "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c": 45, - "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3": 46, - "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690": 47, - "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff": 48, - "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601": 49, - "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2": 50, - "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b": 51, - "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2": 52, - "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201": 53, - "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7": 54, - "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b": 55, - "e408900057c66f11f77a090112a8a9d885c435735ce121ddfad32771": 56, - "b12a95008fb0cbcbf023dd6f8efeb2cf71f71356c54200209b1c78ff": 57, - "d687c5c9643f59210012ee0b39cad3a7a75cd1614e69720101080ffa": 58, - "d77e83a3519663dd32e8bb0e5ae8bf8f8624ab8a0208e59e82befd77": 59, - "f25001dc1b54cefa2a1e0bf75b9701ecc0001ce16f12cb7fb98ed60f": 60, - "6201cbc21cd34e200053404dd9afa9009c77196e329afaff960cf534": 61, - "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631": 62, - "ffd0ffff97a811e712c14add1047bec50016d7e50101b6fb3872ff00": 63, - "ef39dc5761054877a0c3a51e00a7424b72fb4effdc722fede2c3ac26": 64, - "3e746153eff970bd4887887c7003007035014a11cf0001bf01466cfe": 65, - "e9d532daff2d3e922baa003d3e5447e92facd96a2dcb722c30ff8d02": 66, - "d2ec42d9f307fdc985e0010001d9e5603b00198e3751a0487eb2b03a": 67, - "240cb24fdab700ef2e8a6029a4bb9a82a626737d413da2787c01b3b9": 68, - "aa821cd818d52c9aa71a83a64443558439a950be61ff281d070478ff": 69, - "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3": 70, - "43ff5b810501f390d17faf096b9812efa79866fbb20023be2f012201": 71, - "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff": 72, - "007b7ae1a544a2fe7cff0feeffffa0aba683d0ff5326ff4674ec5da4": 73, - "c99031b2f1858bd91d522bf565784c28a98897011ad3cb845fdd58f6": 74, - "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7": 75, - "c76154138a2dee9754f07a426ce7bb5c629099e571a410afb710beba": 76, - "ff0258361001a58a5a959c501608034201700c01fe14e49028fb248c": 77, - "e00327090b0133c301979bdf4b08de0198d70775b451733d6381650a": 78, - "0c036b0369bd5cbd5f00588bab585433771de2004bce47d000a11500": 79, - "fa12ffe085438b9fff811a2003caaf8fde9b00b63f24f0fef270aa7a": 80, - "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a": 81, - "b8c4dc65f9ffad410fee7997f047e8f521cd01d31c0392deb8e65a42": 82, - "266464354274595cd5b4008fa0e87c95e4c9e223fe95bee1dfcc0d9c": 83, - "07a0e9d497f6836060fd84af10afb2f2994e04d0f8a2b770ff3c88b3": 84, - "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6": 85, - "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2": 86, - "be99c5965bb5017581c9d8e9c3b68100dcf3e4236653ed51f8b90b44": 87, - "f40bf01daf809d61e68649f04a1d8dda9151ed6838c89078b68dc3fe": 88, - "afd43c3b45c0214e05e5ff7610411c39faef681c92d09c2c693720c2": 89, - "a9bf7f2ccd409bae63b3e6c101497909c3e0ffaf1a47e900ff00f8c2": 90, - "6f2e179432e5c4c6df001d558e0000e0e210c5262d00a4182ad76426": 91, - "b0d3ed01ff74ffb000a9954dae59e570dca5ff5bbbd3470101c7cf39": 92, - "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9": 93, - "007a17b9f86300003200366e28b71dff215de1e8c47d558d10b6ffbe": 94, - "1c3ef8ff647f35904412ed32ebc625b33edac269017e0197005d1ccf": 95, - "ec2ca9da0aaa43535b95ff537c7bb700ffbafc5c0efbe90063aebf00": 96, - "e075b8012d3a1001794f751396dce75b672e6a0004ffff01c71ac100": 97, - "796240fffbf7c801e8402469010115a270c3f6d7ce21fa9e6fff052d": 98, - "e57ec7c7a233479e45d4dfb23c85b21b85ffe93d2c038986f60d0132": 99, - "cbf94fc524a51d030876a158f9ec8dffffce8f2148f1bb5b61445b81": 100, - "72394760a3ce4d7e01ffffc45cd7e8de83806bffff382dde3cac0f33": 101, - "004cb789ff416601c489fe8a2a69bca620ff7cd4d32cfd9449543e06": 102, - "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571": 103, - "a17fb2e3ff00d898bd89a6b9ef49bf08e21c237a297f71379aeed225": 104, - "ff3737d352fef3e653a296978a09b419e2d0016fbfff7c77aaeaa001": 105, - "5d272ea05f63673c6c001f9bdb6d3e225fa602e6d2ff2a663fa66745": 106, - "01a9b2a9c6778ff1072bf76e0064943701608a1fffd618d301654fa7": 107, - "bc70858172851b9dff34ffe231d8892c57833a9e666e8f0313df691b": 108, - "d57ed603a89d8342859e075947c91b1a255ae94a9d01b1a530457ff2": 109, - "091b2203cb2f3b885a10c326cfe92b7d0bfe2b00024779b4a7892d71": 110, - "ffebd8db93a0ef539f836c8312550100003fa9641ce984597ef53398": 111, - "4fff4cf77029f600d9ffc0c8fd981a0ef37cffd4bd42db9050eff524": 112, - "9cf24b7b01a1d90a01a9009d3a4466b39500e4191588639ee7f7b800": 113, - "194d9c992e9cfe00ac2f46cd76008dd20723d254c0dd07fab24da548": 114, - "2fbc5ad791c60d17bb0ff900717c9fa1197a612a72cd4e200182c273": 115, - "c1410131a6f50142d468da34025d7a06f4ff007a78fb7394c346e513": 116, - "02f28ceeeb8f8eff0be401993255ffba60bffdc2a2c19a01b661978b": 117, - "0fffaa50003b42815a126701fa6b50d1216fcc86aef307e798f737c0": 118, - "16ea953d050b5f84e64e137a26a4cec82bf85000f6b25fede6ff3503": 119, - "2c66f80bec0367db4d43e9459e82fe7e446c5789d5ac017f2dd79a01": 120, - "819acf08e187cd01ec1fd839391b61e4c8aacc303a8437bf2fd5887c": 121, - "ec2c06173fe7c758fff3d8011e01547f09e95dd49ebf9ef20143f770": 122, - "25f0ff0009242e694cefb24c45008eb58e4daa1ab6ff507fdd0e59a1": 123, - "3900e35dff96420ceff3f6c2e129172af3d001e66a8c01501d77a247": 124, - "7be8985d82660003fd79f9d2ffffe524418faf6611f3362671845f1f": 125, - "acf227b3222573270a400f97aad8ca6d976ef6a67632f6491c00d8f8": 126, - "0d4ae5a909eed7912c494cc705a97ea26025cd98483101314aa2fb01": 127, - "5ded7dca81e99cb3869563b6ff4a759351e2b98b071b29307a743a2e": 128, - "734cf0ff12bb7b70a2ac9ab91dbbf205205368b30bc5ee3fffc05601": 129, - "77ffba0e5b2f65cdf4f22e201d924a8377ff153aff6e148dfb78eee7": 130, - "01d190af9500d0aeaa702ab126011e0d247adc1cfd25ffdeef910a15": 131, - "1a6e415a3687e9007845ceeee56fea563e085ba89cf36f64c10eaa5c": 132, - "abc5877fb3e9dc6c2ecebcb6bdd3a2ff0172f50722c4e0ffe60058a3": 133, - "b2ed18f25c6f642cd232a5d119b94116e2fff900afee000e82026c5c": 134, - "0fc78d01677d5b1c6b3ba67546c012a6a61b13a48733214231a5528f": 135, - "1c4e2a0065d7942da94b7a91b434226ff65fb7256b355fc5bea52e18": 136, - "494fc1cbd17a5333681ceb01290953018fe9c53c4f9adbb9a1af0046": 137, - "4c75a8782091320088cd01e900a8aac412f4838654f729a787564b68": 138, - "5be0ea83207a384c55f0c77336e02e7b93ff43ac5a36e23092a9ff0f": 139, - "95264801d09a36c0ff36dd56479215bfea3d006f5500548ffff69059": 140, - "a34a3a08407225822a8a25660081101461ffb022eb8a4d9f0b6200d5": 141, - "d3ff4b015b31bb991295a301fc5eea195744f31fc276a817d284350a": 142, - "db8f1457b1c223a2d03a8b42ff169100ffff3995cefff5985c5100e8": 143, - "ff860082386cf7001aead49dfbd50e75beff4ca7f138d4711ee7016b": 144, - "00600cc9d279ccaabd76ffff00eba32b210027c4038c305b99962a77": 145, - "016c8800ff690bda9802a96f134a4f03ac65eb4541f9656d0e523d00": 146, - "01f702c970b3ff2bb3490d9a6b677cd46e1e043621ff1c174024087d": 147, - "0ed40164004ac401ffde79a9e60422e200a7fd9c43a4ef283c00145d": 148, - "191d68de0259e70d2a55ae59e463ae00c7dcc69136bb0096af86c346": 149, - "1a0113e50000119207ae22fce1bd3ebfe885a29cb3b9f5bd7013e38e": 150, - "1a51ffff00668dff0497605e3dca074f81337cf6bf21e0fff1d80042": 151, - "1bd26b6fbf600023666d90ad14bfea1bff12cfa018d74e471e4384c1": 152, - "1c7acb7a0073e9c3b78aff9b00f016f6533f3800ff01429e3078cbec": 153, - "1cc4d59507714de1b61e362142ef26fbe7ee4aac91df5e4bee01f1f5": 154, - "25c36181b2f2d234816d0c7600800d7a2c62e3e5dd258d4ec5217008": 155, - "2ff34789ff111c01ad6ea6ac03f7a0014cffe9626ad536aa62007479": 156, - "33248dee56388df4016a36d000440ec36f5b392e7ee170e9964fc97c": 157, - "36353475ad3c1ae87b7000ff67d6b800d200b31900ba17011343f831": 158, - "374145d49f4f4f074e5c2e0153d888933399599ef2c4be0121017947": 159, - "3940f037f98d800000019e415e59d0009517a17ae67f6d1cf983583b": 160, - "45d2abaa00a900ff78ada641f03879ff335e1fd9e86630fa0403d6ff": 161, - "4a892cc61350083301181737176d0c7f69b6c12768980ee19afd49a2": 162, - "50ccc49ec9c00b8a04231f25a256275613d2f4b991195fc201003932": 163, - "566900ee95b45900f4d57b8f65c9f58087492e850ac2294d92144981": 164, - "68858ee00098a6c201492fdb09f8be0b546e3d537dfe811e98ab615c": 165, - "6ec653bc3501fba1eceff8004464dd47aa788966e2988813a2b19292": 166, - "704d2d1c018eb90166c653602d00e0b52a01faeb0e29ff92ff791422": 167, - "764801d5b6f51b85d1c425ffdb01926813fb0029d19e79f73741e2d5": 168, - "7f70ef01c542bb9953ff025e477aa461511eaf2213f4ef94bfcd0011": 169, - "7f9969004c1550edd7b04ee88b5274013b12a50f990189fcca22c8cb": 170, - "8275f96f65ce710176d500375005c37a8dff6da2ff1745ac1159ff78": 171, - "8d8fd800075c6ae000bbff1fafd7ead5c4abe729d101f7cc7e6a06e7": 172, - "9d9f9b28e4dcc35c3a053fdc0b066df880fd4411f02c2a171deee088": 173, - "a08ae12343f534ee49bdf7ba256db00103e4203d00c0ffffb1074759": 174, - "a37776b4002dd68c14012a6d52820b2d89e9c001f608014b43e692a3": 175, - "a3989c00d6dc79286474ffed49c1dfeaf879592ff9a0ba38060500ad": 176, - "a6931c2de82054753ad1735087a72721573f88cbe9003b7242d6f9fb": 177, - "b3c2b40baa974b5bc50a5a2abebc0107389e4f738301650aea247773": 178, - "b64d005afff675d5fa56e0cd58c86101dff90118d0559901f301139c": 179, - "b8f5ff8ce57fe80cb4ffaeb6e290380094d1fa01016b0154e204a5a0": 180, - "be37348ef3c8ab12541d008a848d0100449f8992013a0197b204b3b7": 181, - "c200115f1d2248f470012dfff6df0b921602e93fd8eff38eea30ffe2": 182, - "c3b822a5961ef78b091206a8dcf750d48470b71adb46d21d652221cb": 183, - "c555e91c3c7ad44a24b39a609ce6320e4f791c73006aa1ff8beaa27a": 184, - "c5e0157840b431e37609a9d50f4cb894e9565dbda4e47bd2ffe1c579": 185, - "ca509072b97a359e9c4d0b9965f43f91cf01badd2adfef5b92d182e2": 186, - "cc01ffa7295ed9bc1a0088f11036bda6207c8ffa953d96b9632876fc": 187, - "cd59c4100e56a495ff6b601bf54f73b184ce16a2ffae965c36ebc9b6": 188, - "df01fa63958c47c3a980a5e359b491b7b30ec6a16be10188a7850cba": 189, - "e0d4796c1e839ee0f6cbc30ebf612206e258addfcec7091041ff393a": 190, - "e2bcb1dc01c43d5ffd88321d01054fff2e164a797f056bb089390038": 191, - "e4e12d25b5158aa86a9fad026702835c0060f3ffd6ffdbc537e02fd3": 192, - "e5809e783436a02e5529b900ffff1600d1c25eb935f1f9f4907a3300": 193, - "e84acc16a9470098f3007844976401019217f99ca565a301ec6f6401": 194, - "f57ded9f084058a247f274ff8cfeed013774b4b5a993badef13d26d0": 195, - "f6d22048980001e100166713014a9cba3e90ff8cecb6ccb0405c6da0": 196, - "ff0d46fb0e55505106f2c507e252f1a1b4d401e4df369ad0e5ed5db1": 197, - "ffd271d2cc05518088726b58fbc40c3b53efb000ff9bf0bca7a9ef8f": 198, - "fff6ab31d94ddcb37334ec6a5c01016130018dffa3b86f010dc8fb3a": 199 - }, - "committee_index_by_pool_id": { - "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c": 1, - "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647": 2, - "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c": 3, - "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529": 4, - "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d": 5, - "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479": 6, - "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb": 7, - "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1": 8, - "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0": 9, - "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874": 10, - "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef": 11, - "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff": 12, - "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c": 13, - "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137": 14, - "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f": 15, - "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e": 16, - "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8": 17, - "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07": 18, - "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff": 19, - "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be": 20, - "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de": 21, - "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000": 22, - "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155": 23, - "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2": 24, - "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c": 25, - "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58": 26, - "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff": 27, - "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376": 28, - "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551": 29, - "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297": 30, - "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01": 31, - "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc": 32, - "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966": 33, - "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938": 34, - "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf": 35, - "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf": 36, - "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8": 37, - "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11": 38, - "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416": 39, - "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283": 40, - "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64": 41, - "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8": 42, - "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99": 43, - "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d": 44, - "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c": 45, - "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3": 46, - "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690": 47, - "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff": 48, - "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601": 49, - "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2": 50, - "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b": 51, - "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2": 52, - "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201": 53, - "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7": 54, - "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b": 55, - "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3": 56, - "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a": 57, - "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631": 58, - "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6": 59, - "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9": 60, - "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571": 61, - "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff": 62, - "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7": 63, - "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2": 64 - }, - "committee_position_by_pool_id": { - "a253670aa0109a0100c41cb8005c943e5a6f013c04d0010060ec709c": 1, - "8f499cee2c2a00e30095b265d08bff8806f6c1c2c11980c43e00a647": 2, - "ac1112624de3a3e2ad3263016179f6dd94cee339d5ff073300dc980c": 3, - "0058ba4bb580b413af2b1508d1002dd66fff71353069ee75866a4529": 4, - "1674831783291900e1d97bf8d132ec02f2eb978dbc05f327f2f7300d": 5, - "03324b1cedc201541affa601524cc2dd99f70100e2ffef3811ffe479": 6, - "0d04016b5afa0142e3d69e6c004af38d1e263771850cc559c931dceb": 7, - "a23031dbf4ed14ffe76835205c2ec26b64f3b8a900ff0197d40192b1": 8, - "4d195fcad3ab040701ab9150e4165fc6c3d7167d5d16017961a801d0": 9, - "c0a5fdb085bb3c57b6bc59ed5220c2c81f347a176a1e25582e522874": 10, - "818b02f200bc0179d50a1d7cd27917040017ff095f18b510257a58ef": 11, - "04a4c2f0f97f5f3de12801ae78ff873a741f26ff005de4f23cd073ff": 12, - "0c77d9a79636bf00eb668ca7b063f9b2006b012f806427af3552456c": 13, - "bedb6e991cff24c800a6276678feec20a54dbd8d3402013f53660137": 14, - "315061f60f0fe43d72f468e90801072a81115215d20eed759100679f": 15, - "633c40e237f82c50cb00018affac55ffd1a7161d39d8cc016900803e": 16, - "7fc3397cf6ff6be8ef5cc95fdcd143eddec09a0029ad16accd5301b8": 17, - "7ed1f10b855fba01c35704e52741c308d47001c5ff70018f8b0f0b07": 18, - "9bfa01329444f9b41865a66b08071805de715c2e48c900f6233700ff": 19, - "ff130130e4ffe5ff4f01d1b22457cbb1a921ea00c725acdfc8bb00be": 20, - "2f8d19cef9ab0af390ad52f6c94c967443dea53dcde96295dba1e8de": 21, - "36ff37ffa34f3915b65900b1953eddd67cffbb00566c35a95fc95000": 22, - "56829e00071d9a9a154c1e0105387c3ff61766df9ef624736edd7155": 23, - "01ecc6ff9c429b478230921e137601561595696e8b09ff4716d0ccc2": 24, - "c9240f8ecbd98567e1ff934b7167bcba0bd5c66decd5a52b10cabe0c": 25, - "010574c9da8613f47b90d70174546d2e01c0756126c429ae40d9ac58": 26, - "007e4e91ed00c67e49a10597f7e94bbd55496f08b1ed09cc7684f5ff": 27, - "add4217d868713e346520072aa2eff4d9d59c9cd39d05e263d635376": 28, - "d3e832be00187f4932b0a77901017236ce2f93ffff0112b765cea551": 29, - "b2096bb956018ed39cb0012cce12ac9b1b2d783482ebddf390233297": 30, - "31a20317074f33c33dab51512332d4bb809ebfb011fff676be01ac01": 31, - "f61d5aa701a4284e323ad1bc35c800c93e1c5d39b37308f7acfadfcc": 32, - "8f1386e9b91fbab435d92700009c040ad72df8b2c801548830cf1966": 33, - "c114b441b231fedae7ccf9bb00dfc964754dd270cf1a7fffc3ca0938": 34, - "ba2494ed135ce12799cf6effba2b7c2bffb0e5da1aef10a646c296cf": 35, - "ff002a55005fcafe9a46d31c44043ca023f4d4ea9e6c9d0cb8ed3dcf": 36, - "ffb730529983dfff2f7343a494caddffc229ab910b5a6b931236c9e8": 37, - "ffc81363f388785d82f8000100bf3500439cff9fff447bf400ff2f11": 38, - "07aa40984f01e9234cffaca4703c71217a052e01592fdcd18781e416": 39, - "a72b59ffb97f025fa26095214a84f04c8201bc01a91a6edc0ce33283": 40, - "7a49ff9c8d97139e71000c0106acff82011eae2b95b6d615d04ffc64": 41, - "9d3811b26b94c9b7011662f3a1e3010eb3690858113357001e9337e8": 42, - "19006a7b9959dc86fa2d0520434afd65e594230170770d305e47fc99": 43, - "abbe2d4a00b6cda01562d1771b464a7bf2d85ba271e76a0800008c5d": 44, - "5560775d42a6fcc1faffbe4f2d08e400d8d70101e7fc30b4ff41c59c": 45, - "c06aac4adf0079fd482dcc5bc2d8e119833036201fece3e6047f00a3": 46, - "4758a819006a9c9cd69003d58462a52f009643b2f600a3595a360690": 47, - "72c1abccab2af0035fb0017f23e61b28ab1956a7e4ff9931d3c466ff": 48, - "eac3a06ebb3b49002ff87ab6d5865d4948723cbab12803814cf83601": 49, - "6cff6e6487b88a023072a4ec94509dca00121600ee349b00756881d2": 50, - "989b615290c6461d90484eb0f18d7c4eddd901575b0099300967c78b": 51, - "fcea178a04c3c06c4379beadcba8e0079b8e034bff47cd70088dffe2": 52, - "5200dd00c575cd8dee964a184ed26f3a7ae59aff3f6f120d97dea201": 53, - "f0b2d2cf999beab2b70924b7002601e1673e4500018cefdf02ac91e7": 54, - "4d948a6c7492e501718b690627c7294600be5311cb0033c6009fb82b": 55, - "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3": 56, - "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a": 57, - "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631": 58, - "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6": 59, - "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9": 60, - "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571": 61, - "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff": 62, - "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7": 63, - "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2": 64 - } - }, - "voters": { - "persistent_ids": [ - 3, - 26, - 25, - 23, - 5, - 6, - 4, - 20, - 14, - 30, - 21, - 46, - 54, - 49, - 47, - 40, - 17, - 10, - 32, - 50, - 18, - 43, - 2, - 13, - 33, - 24, - 28, - 48, - 35 - ], - "nonpersistent_pool_ids": [ - "3b54c9da426100beb037ffb572aaef88468628cd3b7a003e00d4b7b3", - "3c4dffb3003dcdff7957ae181a30fccb220cf30afbd54801f620824a", - "4fd391ffff08478aeb7e5c90e20fc997fa47788f04c8b96f9ee8b631", - "550128ccc5fd001337f5fffb0bd0f9ffba9b1d4ce9676ab5333813b6", - "562704faf795ab410102d1ff4cd291ad1598f500b0c6b0b1d8cc1ad9", - "5e9b350b2d403a73bdf001a4fcdad99d8398478c6ae801d8453ec571", - "6f5c8c1f2cf7cca22046beff90b5b2dcd2e81029589e35423d956eff", - "910cf3ad60dcb545517d00506a304226b628128aa156ab2198ec9eb7", - "999601efea63ff489e6b75abfeb8e7047cc0cdd7bb705500889407d2", - "afd43c3b45c0214e05e5ff7610411c39faef681c92d09c2c693720c2", - "c76154138a2dee9754f07a426ce7bb5c629099e571a410afb710beba", - "c99031b2f1858bd91d522bf565784c28a98897011ad3cb845fdd58f6", - "e408900057c66f11f77a090112a8a9d885c435735ce121ddfad32771", - "e57ec7c7a233479e45d4dfb23c85b21b85ffe93d2c038986f60d0132", - "e9d532daff2d3e922baa003d3e5447e92facd96a2dcb722c30ff8d02", - "f25001dc1b54cefa2a1e0bf75b9701ecc0001ce16f12cb7fb98ed60f", - "ff0258361001a58a5a959c501608034201700c01fe14e49028fb248c" - ] - }, - "certificate": { - "sigma_tilde_eid_prefix": "b7a4c667e0a7a741", - "sigma_tilde_m_prefix": "8b0733a367d3dcdb", - "eid": "0x26f8d6de52069c34", - "eb": "0x00d960ffbea019a0edc9e20656cf84ceb0c9bd5f8bf8ee9bf975940617489ba4", - "persistent_voters_count": 29, - "nonpersistent_voters_count": 17, - "votes_bytes": 8074, - "certificate_bytes": 2104, - "compression_ratio": 3.837 - } -} \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/10_init_inputs.sh b/crypto-benchmarks.rs/demo/scripts/10_init_inputs.sh index e8f1c99ee..050cdee77 100755 --- a/crypto-benchmarks.rs/demo/scripts/10_init_inputs.sh +++ b/crypto-benchmarks.rs/demo/scripts/10_init_inputs.sh @@ -5,9 +5,9 @@ DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$DIR_SCRIPT/.env_cli" DEMO_DIR="$(cd "$DIR_SCRIPT/.." && pwd)" -POOLS=5000 -TOTAL_STAKE=100000 -ALPHA=5 +POOLS=500 +TOTAL_STAKE=1000000 +ALPHA=9 BETA=1 while [[ $# -gt 0 ]]; do diff --git a/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh b/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh index b3d77b6a8..8703c33fc 100755 --- a/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh +++ b/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh @@ -34,23 +34,51 @@ def must(path): return path # --- Load registry (required) --- + reg = cbor2.load(open(must("registry.cbor"), "rb")) info = reg.get("info", {}) or {} persistent_pool = reg.get("persistent_pool", {}) or {} total_stake = reg.get("total_stake") N = int(reg.get("voters", 0) or 0) +# Normalize persistent seat IDs to integers for robust comparisons +try: + persistent_seat_ids = {int(k) for k in persistent_pool.keys()} +except Exception: + persistent_seat_ids = set() + +# --- Quorum / voting fraction (optional, written by 30_cast_votes.sh as fraction.txt) --- +def read_quorum_fraction(): + for fname in ("fraction.txt", "quorum.txt", "quorum_fraction.txt"): + path = os.path.join(os.getcwd(), fname) + if os.path.exists(path): + try: + raw = open(path, "r", encoding="utf-8").read().strip() + except Exception: + continue + if not raw: + continue + # Prefer numeric if possible, otherwise keep as string + try: + return float(raw) + except ValueError: + return raw + return None + +quorum_fraction = read_quorum_fraction() + # --- Universe (all pools from registry.info) --- -# Each entry: pool_id, stake, is_persistent -persistent_set = set(persistent_pool.values()) +# Each entry: pool_id, stake (no persistent flag here; committee carries epoch membership) +# Preserve original generation order from registry.info (Python dict preserves insertion order). universe = [ - {"pool_id": pid, "stake": rec.get("stake", 0), "is_persistent": pid in persistent_set} + {"pool_id": pid, "stake": rec.get("stake", 0)} for pid, rec in info.items() ] -# Sort universe by stake desc, then pool_id for stability -universe.sort(key=lambda x: (-int(x.get("stake", 0) or 0), x["pool_id"])) # Quick lookup for stake / presence + +# Build quick index: pool_id -> zero-based position in the universe array +universe_index_by_pool_id = {e["pool_id"]: i for i, e in enumerate(universe)} stake_by_pid = {e["pool_id"]: int(e.get("stake", 0) or 0) for e in universe} # --- Persistent seats (ordered by persistent id) --- @@ -65,124 +93,146 @@ for pid_idx in sorted(persistent_pool): # How many nonpersistent seats do we need? np_needed = max(0, N - len(persist_entries)) -# --- Non-persistent winners: -# Prefer certificate.cbor (authoritative). If absent, derive from votes.cbor. +# --- Non-persistent winners: (disabled for persistent-only committee export) np_winners = [] -if np_needed > 0 and os.path.exists("certificate.cbor"): - cert = cbor2.load(open("certificate.cbor", "rb")) - # Many builds export winners under 'nonpersistent_voters' as a map {pool_id: proof/...} - np_map = cert.get("nonpersistent_voters") or {} - # Keep the CBOR/map iteration order (winners order). Fall back to sorted order for stability. - if isinstance(np_map, dict): - np_winners = list(np_map.keys()) - else: - # Some variants serialize as list of objects - try: - np_winners = [x.get("pool") for x in np_map if isinstance(x, dict) and x.get("pool")] - except Exception: - np_winners = [] -elif np_needed > 0 and os.path.exists("votes.cbor"): - # Derive per-EB top by vote count for Nonpersistent votes - votes = cbor2.load(open("votes.cbor", "rb")) - ctr = collections.Counter() - order = [] # first-appearance order (for tie-breaking) - seen = set() - for v in votes: - if "Nonpersistent" in v: - pid = v["Nonpersistent"].get("pool") - if not isinstance(pid, str): - continue - ctr[pid] += 1 - if pid not in seen: - seen.add(pid) - order.append(pid) - # Rank by count desc, break ties by first appearance - np_winners = sorted(ctr.keys(), key=lambda k: (-ctr[k], order.index(k) if k in order else 1e9)) - -# Deduplicate and trim to needed seats, skipping any that duplicate persistent np_final = [] -seen = set(p["pool_id"] for p in persist_entries) -for pid in np_winners: - if not isinstance(pid, str): - continue - if pid in seen: - continue - np_final.append(pid) - seen.add(pid) - if len(np_final) >= np_needed: - break - -# --- Only keep NP winners that already exist in the universe (no stubs) -universe_pids = set(e["pool_id"] for e in universe) -np_final = [pid for pid in np_final if pid in universe_pids] - -# Rebuild lookup maps after potential extensions -universe.sort(key=lambda x: (-int(x.get("stake", 0) or 0), x["pool_id"])) -universe_index_by_pool_id = {entry["pool_id"]: idx for idx, entry in enumerate(universe, start=1)} - -# --- Build final committee list: keep committee order (position) but set -# "index" to the pool's 1-based index in the *universe* array. -committee = [] -# persistent first (in persistent id order) +np_final_set = set() + +# --- Build final committee: persistent seats + placeholders for non-persistent slots +seats = [] for pos, entry in enumerate(persist_entries, start=1): pid = entry["pool_id"] - committee.append({ + seats.append({ "position": pos, "index": universe_index_by_pool_id.get(pid), # universe index "pool_id": pid, "stake": entry["stake"], + "kind": "persistent", }) -# then non-persistent winners (in certificate order or derived order) -pos = len(committee) + 1 -for pid in np_final: - committee.append({ - "position": pos, - "index": universe_index_by_pool_id.get(pid), # universe index - "pool_id": pid, - "stake": stake_by_pid.get(pid, 0), +# Append placeholders for non-persistent slots (to be filled per‑election) +for i in range(np_needed): + seats.append({ + "position": len(seats) + 1, + "slot": "nonpersistent", + "kind": "nonpersistent", }) - pos += 1 - -# If we still don't have N seats (missing certificate/votes), fill from top-by-stake excluding already chosen -if len(committee) < N: - chosen = set(c["pool_id"] for c in committee) - for e in universe: - if e["pool_id"] in chosen: - continue - committee.append({ - "position": len(committee) + 1, - "index": universe_index_by_pool_id.get(e["pool_id"]), - "pool_id": e["pool_id"], - "stake": e.get("stake", 0), - }) - if len(committee) >= N: - break - committee_source = "fa+votes+fallback" -else: - committee_source = "fa+certificate" if os.path.exists("certificate.cbor") else ("fa+votes" if os.path.exists("votes.cbor") else "fallback_topN") - -# --- Voters quick view (optional) -voters_persistent = [] -voters_nonpersistent = [] +committee = { + "size": N, + "persistent_count": len(persist_entries), + "nonpersistent_slots": np_needed, + "seats": seats, +} +committee_source = "persistent_plus_placeholders" + +# --- Voters quick view (optional) + committee-based filtering --- +voters_unfiltered = { + "persistent_ids": [], + "nonpersistent_pool_ids": [], +} +voters_filtered = { + "persistent_ids": [], + "nonpersistent_pool_ids": [], +} +filtered_votes_raw = [] +filtered_p_raw = [] +filtered_np_raw = [] +voters_filter_stats = { + "source_total": 0, + "kept_total": 0, + "removed_outside_committee": 0, + "removed_persistent": 0, + "removed_nonpersistent": 0, +} + +def _to_int(x): + try: + return int(x) + except Exception: + return None + +def _is_persistent_id_in_committee(pid) -> bool: + """Return True iff the persistent seat id is in the registry's persistent set.""" + pid_i = _to_int(pid) + return (pid_i is not None) and (pid_i in persistent_seat_ids) + +def _is_np_pool_in_committee(pool_id: str) -> bool: + return isinstance(pool_id, str) and (pool_id in np_final_set) + +votes_preview = [] + if os.path.exists("votes.cbor"): try: - votes = cbor2.load(open("votes.cbor", "rb")) - for v in votes: + votes_raw = cbor2.load(open("votes.cbor", "rb")) + for v in votes_raw: + # Unfiltered bookkeeping + if "Persistent" in v: + pid_raw = v["Persistent"].get("persistent") + pid = _to_int(pid_raw) + if pid is not None: + voters_unfiltered["persistent_ids"].append(pid) + votes_preview.append({"type": "persistent", "seat_id": pid}) + elif "Nonpersistent" in v: + pool = v["Nonpersistent"].get("pool") + if isinstance(pool, str): + voters_unfiltered["nonpersistent_pool_ids"].append(pool) + sigma_eid = v["Nonpersistent"].get("sigma_eid") + prefix = None + if isinstance(sigma_eid, (bytes, bytearray)): + prefix = "0x" + sigma_eid[:12].hex() + votes_preview.append({ + "type": "nonpersistent", + "pool_id": pool, + "eligibility_sigma_eid_prefix": prefix, + }) + + # Filtering (committee only) if "Persistent" in v: - pid = v["Persistent"].get("persistent") - if isinstance(pid, int): - voters_persistent.append(pid) + pid_raw = v["Persistent"].get("persistent") + pid = _to_int(pid_raw) + if pid is not None: + voters_filter_stats["source_total"] += 1 + if _is_persistent_id_in_committee(pid): + voters_filtered["persistent_ids"].append(pid) + voters_filter_stats["kept_total"] += 1 + filtered_votes_raw.append(v) + filtered_p_raw.append(v) + else: + voters_filter_stats["removed_outside_committee"] += 1 + voters_filter_stats["removed_persistent"] += 1 elif "Nonpersistent" in v: pool = v["Nonpersistent"].get("pool") if isinstance(pool, str): - voters_nonpersistent.append(pool) + voters_filter_stats["source_total"] += 1 + if _is_np_pool_in_committee(pool): + voters_filtered["nonpersistent_pool_ids"].append(pool) + voters_filter_stats["kept_total"] += 1 + filtered_votes_raw.append(v) + filtered_np_raw.append(v) + else: + voters_filter_stats["removed_outside_committee"] += 1 + voters_filter_stats["removed_nonpersistent"] += 1 except Exception: pass +# --- Compute filtered vote sizes (bytes) by CBOR re-encoding the kept votes only +votes_bytes_raw = os.path.getsize("votes.cbor") if os.path.exists("votes.cbor") else None +votes_bytes_filtered = None +votes_bytes_p_filtered = None +votes_bytes_np_filtered = None +try: + if filtered_votes_raw: + import cbor2 as _cbor2 # already imported, but keep local alias + votes_bytes_filtered = len(_cbor2.dumps(filtered_votes_raw)) + if filtered_p_raw: + votes_bytes_p_filtered = len(_cbor2.dumps(filtered_p_raw)) + if filtered_np_raw: + votes_bytes_np_filtered = len(_cbor2.dumps(filtered_np_raw)) +except Exception: + # Fall back to raw file size if anything goes wrong + votes_bytes_filtered = votes_bytes_raw + # --- Certificate summary (optional) --- cert_summary = {} -votes_bytes = os.path.getsize("votes.cbor") if os.path.exists("votes.cbor") else None -certificate_bytes = os.path.getsize("certificate.cbor") if os.path.exists("certificate.cbor") else None if os.path.exists("certificate.cbor"): cert = cbor2.load(open("certificate.cbor", "rb")) @@ -201,42 +251,58 @@ if os.path.exists("certificate.cbor"): "persistent_voters_count": len(pv) if hasattr(pv, "__len__") else None, "nonpersistent_voters_count": (len(npv.keys()) if isinstance(npv, dict) else (len(npv) if hasattr(npv, "__len__") else None)), } + # Override certificate counts with committee‑filtered counts + kept_p = len(set(voters_filtered["persistent_ids"])) + kept_np = len(set(voters_filtered["nonpersistent_pool_ids"])) + cert_summary["persistent_voters_count"] = kept_p + cert_summary["nonpersistent_voters_count"] = kept_np -if votes_bytes is not None: - cert_summary["votes_bytes"] = votes_bytes +# --- Bytes & compression (use filtered votes only) +certificate_bytes = os.path.getsize("certificate.cbor") if os.path.exists("certificate.cbor") else None +if votes_bytes_filtered is not None: + cert_summary["votes_bytes"] = votes_bytes_filtered + # Keep a raw reference for diagnostics + if votes_bytes_raw is not None: + cert_summary["votes_bytes_raw"] = votes_bytes_raw + if votes_bytes_p_filtered is not None: + cert_summary["votes_bytes_persistent"] = votes_bytes_p_filtered + if votes_bytes_np_filtered is not None: + cert_summary["votes_bytes_nonpersistent"] = votes_bytes_np_filtered if certificate_bytes is not None: cert_summary["certificate_bytes"] = certificate_bytes - if votes_bytes: - try: - cert_summary["compression_ratio"] = round(votes_bytes / certificate_bytes, 3) - except Exception: - pass +if ("votes_bytes" in cert_summary) and ("certificate_bytes" in cert_summary): + try: + cert_summary["compression_ratio"] = round( + cert_summary["votes_bytes"] / cert_summary["certificate_bytes"], 3 + ) + except Exception: + pass # --- Params (handy for the UI header) --- params = { "N": N, "pool_count": len(universe), "total_stake": total_stake, + "quorum_fraction": quorum_fraction, # may be None if not recorded } out = { "params": params, "universe": universe, - "persistent_map": {int(k): v for k, v in persistent_pool.items()}, "committee": committee, "committee_source": committee_source, "lookup": { "universe_index_by_pool_id": universe_index_by_pool_id, - # Back-compat: this has always meant the committee *order* (position) - "committee_index_by_pool_id": {entry["pool_id"]: entry["position"] for entry in committee}, - # New explicit alias for clarity - "committee_position_by_pool_id": {entry["pool_id"]: entry["position"] for entry in committee}, - }, - "voters": { - "persistent_ids": voters_persistent, - "nonpersistent_pool_ids": voters_nonpersistent, + "committee_position_by_pool_id": { + seat["pool_id"]: seat["position"] + for seat in committee["seats"] + if "pool_id" in seat + }, }, + "voters_unfiltered": voters_unfiltered, # raw ids (persistent seat ids and NP pool ids) + "voters_filter_stats": voters_filter_stats, "certificate": cert_summary, + "votes_preview": votes_preview, } json.dump(out, open("demo.json", "w"), indent=2) diff --git a/crypto-benchmarks.rs/demo/scripts/30_cast_votes.sh b/crypto-benchmarks.rs/demo/scripts/30_cast_votes.sh index 928800e66..5d57097f1 100755 --- a/crypto-benchmarks.rs/demo/scripts/30_cast_votes.sh +++ b/crypto-benchmarks.rs/demo/scripts/30_cast_votes.sh @@ -29,6 +29,8 @@ fi # Resolve run directory relative to demo/ RUN_DIR="$(cd "$DIR_SCRIPT/.."; cd "$RUN_DIR" && pwd)" echo "== [30_cast_votes] DIR=${RUN_DIR} FRACTION=${FRACTION} ==" +# Persist the voting fraction (quorum) for downstream scripts +printf '%s\n' "$FRACTION" > "${RUN_DIR}/fraction.txt" # ---- run cast-votes ---- pushd "$RUN_DIR" >/dev/null diff --git a/crypto-benchmarks.rs/demo/scripts/70_run_one.sh b/crypto-benchmarks.rs/demo/scripts/70_run_one.sh index 5be4a650e..a5242b171 100755 --- a/crypto-benchmarks.rs/demo/scripts/70_run_one.sh +++ b/crypto-benchmarks.rs/demo/scripts/70_run_one.sh @@ -82,6 +82,9 @@ INIT_CMD=("$DIR_SCRIPT/10_init_inputs.sh" -d "$RUN_DIR") # ---- 60: show sizes + summary JSON ---- "$DIR_SCRIPT/60_pretty_print_cert.sh" -d "$RUN_DIR" +# ---- 25: generate JSON for UI ---- +"$DIR_SCRIPT/25_export_demo_json.sh" -d "$RUN_DIR" + # ---- compact tail summary ---- PRETTY_JSON="${RUN_DIR_ABS}/certificate.pretty.json" if [[ -f "$PRETTY_JSON" ]] && command -v jq >/dev/null 2>&1; then diff --git a/crypto-benchmarks.rs/demo/scripts/extract_committee.py b/crypto-benchmarks.rs/demo/scripts/extract_committee.py deleted file mode 100755 index 5ee4e9e6e..000000000 --- a/crypto-benchmarks.rs/demo/scripts/extract_committee.py +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env python3 -# Strict, registry-first committee extraction. -# - Source of truth: registry.cbor -> .info (pool -> {stake, ...}) -# - No fallbacks to stake.cbor / pools.cbor. -# - Fail fast with a clear error when registry is missing/malformed. -# - Optionally map the persistent committee from registry.{persistent_pool|persistent_id}. -# - Optionally read certificate.pretty.json to list elected voters (persistent/non-persistent). - -import os, sys, json, re -from typing import Dict, Any, List, Tuple - -try: - import cbor2 -except Exception as e: - print(json.dumps({"error": f"cbor2 import failed: {e}"})) - sys.exit(1) - -# ----------------------------- args & paths ----------------------------- - -if len(sys.argv) < 2: - print("Usage: extract_committee.py RUN_DIR", file=sys.stderr) - sys.exit(2) - -RUN_DIR = sys.argv[1] -REG_PATH = os.path.join(RUN_DIR, "registry.cbor") -PRETTY_PATH = os.path.join(RUN_DIR, "certificate.pretty.json") - -# Accept 20..64 bytes (40..128 hex chars), with optional 0x/x prefix -HEX_RE = re.compile(r'^(?:0x|x)?[0-9a-f]{40,128}$') - -# ----------------------------- helpers ----------------------------- - -def norm_hex_prefix(s: str) -> str: - s = s.lower() - if s.startswith("0x"): return s - if s.startswith("x"): return "0x" + s[1:] - return "0x" + s - -def normalize_pool_id(val) -> str | None: - """Return pool id as lowercase 0x-prefixed hex string or None.""" - if isinstance(val, (bytes, bytearray)): - return "0x" + val.hex() - if isinstance(val, str): - s = val.strip().lower() - if HEX_RE.match(s): - return norm_hex_prefix(s) - return None - -def die(out_obj: Dict[str, Any], msg: str) -> None: - out_obj.setdefault("notes", []).append("FATAL") - out_obj["error"] = msg - print(json.dumps(out_obj, indent=2)) - sys.exit(1) - -def load_registry(path: str, out_obj: Dict[str, Any]) -> Dict[str, Any]: - if not os.path.isfile(path): - die(out_obj, "registry.cbor not found; please run scripts/20_make_registry.sh first.") - try: - with open(path, "rb") as f: - reg = cbor2.load(f) - except Exception as e: - die(out_obj, f"Failed to parse registry.cbor: {e}") - if not isinstance(reg, dict): - die(out_obj, "registry.cbor has unexpected shape (expected a CBOR map/dict).") - return reg - -def build_stake_map(reg: Dict[str, Any], out_obj: Dict[str, Any]) -> Dict[str, int]: - info = reg.get("info") - if not isinstance(info, dict) or not info: - die(out_obj, "registry.cbor missing non-empty 'info' map; cannot derive stakes.") - stake_map: Dict[str, int] = {} - for pool_key, rec in info.items(): - pid = normalize_pool_id(pool_key) - if pid is None: - die(out_obj, f"registry.info key not a valid pool id hex: {pool_key!r}") - if not isinstance(rec, dict): - die(out_obj, f"registry.info entry for {pool_key} is not a dict.") - st = rec.get("stake") - if not isinstance(st, int): - die(out_obj, f"registry.info entry for {pool_key} missing integer 'stake'.") - stake_map[pid] = int(st) - if not stake_map: - die(out_obj, "No stakes found in registry.info; cannot build committee.") - return stake_map - -def compute_target_seats(reg: Dict[str, Any], max_available: int) -> int: - for k in ("voters", "voter_count", "n", "N", "seats", "committee_size", "committeeSeats"): - v = reg.get(k) - if isinstance(v, int) and 1 <= v <= 5000: - return min(v, max_available) - # fallback to env or 32, clamped to available - try: - env_n = int(os.environ.get("DEMO_SEATS", "32")) - if 1 <= env_n <= 5000: - return min(env_n, max_available) - except Exception: - pass - return min(32, max_available) - -def persistent_committee_from_registry(reg: Dict[str, Any], - stake_map: Dict[str, int], - out_obj: Dict[str, Any]) -> List[Dict[str, Any]]: - result: List[Dict[str, Any]] = [] - pp = reg.get("persistent_pool") - if isinstance(pp, dict) and pp: - # Keys are indices (usually 0..n-1). Sort by numeric index if possible. - try: - items = sorted(pp.items(), key=lambda kv: int(kv[0])) - except Exception: - items = sorted(pp.items(), key=lambda kv: str(kv[0])) - for idx, pool_val in items: - pid_hex = normalize_pool_id(pool_val) - if pid_hex: - result.append({"id": int(idx), "pool_id": pid_hex, "stake": stake_map.get(pid_hex)}) - if result: - out_obj.setdefault("notes", []).append("Persistent committee mapped from registry.cbor persistent_pool") - else: - out_obj.setdefault("notes", []).append("registry.persistent_pool present but contained no valid pool ids") - return result - - # Fallback: invert persistent_id (pool -> index) - pid_map = reg.get("persistent_id") - if isinstance(pid_map, dict) and pid_map: - inv: Dict[int, str] = {} - for pool_val, id_val in pid_map.items(): - pid_hex = normalize_pool_id(pool_val) - try: - idx = int(id_val) - except Exception: - continue - if pid_hex is not None: - inv[idx] = pid_hex - if inv: - for idx, pid_hex in sorted(inv.items(), key=lambda kv: kv[0]): - result.append({"id": int(idx), "pool_id": pid_hex, "stake": stake_map.get(pid_hex)}) - out_obj.setdefault("notes", []).append("Persistent committee inferred by inverting registry.cbor persistent_id") - return result - - out_obj.setdefault("notes", []).append( - "registry.cbor present but has no persistent_pool/persistent_id mappings; persistent committee unavailable" - ) - return result - -def elected_from_pretty(path: str, out_obj: Dict[str, Any]) -> List[Dict[str, Any]]: - if not os.path.isfile(path): - return [] - try: - with open(path) as f: - c = json.load(f) - except Exception as e: - out_obj.setdefault("notes", []).append(f"Failed to read certificate.pretty.json: {e}") - return [] - pv = c.get("persistent_voters") or c.get("persistent_voters_sample") or [] - npv = c.get("nonpersistent_voters") or c.get("nonpersistent_voters_sample") or [] - elected: List[Dict[str, Any]] = ( - [{"voter_id": v, "type": "persistent"} for v in pv] + - [{"voter_id": v, "type": "non-persistent"} for v in npv] - ) - if not (c.get("persistent_voters") or c.get("persistent_voters_sample")) and (c.get("nonpersistent_voters") or c.get("nonpersistent_voters_sample")): - out_obj.setdefault("notes", []).append("All voters are non-persistent in this run; non-persistent voter IDs are ephemeral and do not equal pool IDs.") - return elected - -# ----------------------------- main ----------------------------- - -def main() -> None: - out: Dict[str, Any] = { - "selected_pools": [], # [{index, pool_id, stake}] - "elected": [], # [{voter_id, type}] - "selected_count": 0, - "elected_count": 0, - "persistent_committee": [], - "persistent_count": 0, - "notes": [] - } - - # Load registry once - registry = load_registry(REG_PATH, out) - - # Build stakes from registry.info - stake_map = build_stake_map(registry, out) - - # Selection (top-N by stake, stable tiebreaker by pool id) - target = compute_target_seats(registry, max_available=len(stake_map)) - ordered: List[Tuple[str, int]] = sorted(stake_map.items(), key=lambda kv: (-kv[1], kv[0])) - selected_ids = [pid for pid, _ in ordered[:target]] - - for i, pid_hex in enumerate(selected_ids, 1): - out["selected_pools"].append({"index": i, "pool_id": pid_hex, "stake": stake_map.get(pid_hex)}) - out["selected_count"] = len(out["selected_pools"]) - out["notes"].append("selected_pools derived strictly from registry.cbor info (top-N by stake)") - - # Persistent committee mapping (from the same registry) - out["persistent_committee"] = persistent_committee_from_registry(registry, stake_map, out) - out["persistent_count"] = len(out["persistent_committee"]) - - # Elected voters (optional, from pretty JSON) - out["elected"] = elected_from_pretty(PRETTY_PATH, out) - out["elected_count"] = len(out["elected"]) - - print(json.dumps(out, indent=2)) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/ui/server.py b/crypto-benchmarks.rs/demo/ui/server.py index 753cbf5d6..8cd0e445e 100644 --- a/crypto-benchmarks.rs/demo/ui/server.py +++ b/crypto-benchmarks.rs/demo/ui/server.py @@ -88,6 +88,15 @@ def demo_for_run(run): return send_from_directory(run_dir, "demo.json") abort(404, f"demo.json not found in {run_dir}") +@app.route("/demo//") +def demo_asset(run, filename): + """Serve auxiliary files (eid.txt, ebhash.txt, etc.) from the run directory.""" + run_dir = run_dir_path(run) + target_path = os.path.join(run_dir, filename) + if os.path.isfile(target_path): + return send_from_directory(run_dir, filename) + abort(404, f"{filename} not found in {run_dir}") + @app.route("/votes/") def votes(run): """Expose elected voter IDs (first pass: read from certificate.pretty.json).""" @@ -135,4 +144,4 @@ def root(): return redirect(url_for("ui")) if __name__ == "__main__": - app.run(host="0.0.0.0", port=5050, debug=True) \ No newline at end of file + app.run(host="0.0.0.0", port=5050, debug=True) diff --git a/crypto-benchmarks.rs/demo/ui/static/app.js b/crypto-benchmarks.rs/demo/ui/static/app.js index 63163ad98..f470eb8fb 100644 --- a/crypto-benchmarks.rs/demo/ui/static/app.js +++ b/crypto-benchmarks.rs/demo/ui/static/app.js @@ -19,77 +19,86 @@ function shortenHex(hex) { return "0x" + h.slice(0, 6) + "…" + h.slice(-4); } -// ---------- CSS bootstrap (minimal positioning only) ---------- -function ensureUniverseStyles() { - if (document.getElementById("universe-inline-css")) return; - const style = document.createElement("style"); - style.id = "universe-inline-css"; - style.textContent = ` - :root { - --dot-size: 30px; /* diameter of each circle */ - --dot-gap: 5px; /* space between circles */ - } - - #universe_canvas { - min-height: calc(var(--dot-size) * 6); - position: relative; - } - - /* Grid that lays out the circles */ - .universe-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(var(--dot-size), var(--dot-size))); - grid-auto-rows: var(--dot-size); - gap: var(--dot-gap) var(--dot-gap); - align-content: start; - justify-content: start; - padding-top: 8px; - } - - /* Ensure each dot consumes the whole grid cell and is a perfect circle */ - .pool-dot { - width: var(--dot-size); - height: var(--dot-size); - border-radius: 50%; - position: relative; /* hosts the centered numeric label */ - display: inline-block; - } - - /* Numeric label inside each dot */ - .pool-dot .node-label { - position: absolute; - top: 50%; - left: 0; - width: 100%; - transform: translateY(-50%); - text-align: center; - font-weight: 700; - color: #ffffff; /* white text for contrast */ - text-shadow: 0 1px 2px rgba(0,0,0,0.4); /* subtle halo for readability */ - pointer-events: none; - user-select: none; - } - .tooltip-box { - position: absolute; - background: rgba(30, 30, 30, 0.9); - color: #fff; - padding: 6px 8px; - border-radius: 6px; - font-size: 12px; - line-height: 1.4; - pointer-events: none; - z-index: 1000; - box-shadow: 0 2px 6px rgba(0,0,0,0.3); - } - .pool-dot.is-voter::after { - content: ""; - position: absolute; - inset: -3px; - border: 2px solid #19c37d; /* green ring for voters */ - border-radius: 50%; - } - `; - document.head.appendChild(style); +function formatNumber(value) { + if (value === null || value === undefined || value === "") return "—"; + const num = Number(value); + if (Number.isFinite(num)) return num.toLocaleString(); + if (typeof value === "string") return value; + return String(value); +} + +function firstFinite(...values) { + for (const value of values) { + if (value === null || value === undefined || value === "") continue; + const num = Number(value); + if (Number.isFinite(num)) return num; + } + return null; +} + +function createEmptyState(message) { + const p = document.createElement("p"); + p.className = "empty-state"; + p.textContent = message; + return p; +} + +// --- Vote byte sizes (visualization constants) --- +const PERSISTENT_VOTE_BYTES = 134; // bytes per persistent vote (viz) +const NONPERSISTENT_VOTE_BYTES = 247; // bytes per non-persistent vote (viz) + +const FIXED_GRID_COLUMNS = 20; // circles per row for Voters alignment +const UNIVERSE_COLUMNS = 30; // circles per row for Universe panel +const COMMITTEE_COLUMNS = 20; // seat boxes per row for Committee +const COMMITTEE_ROW_HEIGHT = "calc(var(--seat-size) + 10px)"; + +// ---------- tooltip helpers ---------- +function positionTooltip(target, tooltip) { + if (!target || !tooltip) return; + const rect = target.getBoundingClientRect(); + const top = rect.top + window.scrollY - 40; + tooltip.style.left = `${rect.left + window.scrollX}px`; + tooltip.style.top = `${Math.max(window.scrollY + 4, top)}px`; +} + +function attachTooltip(element, html) { + if (!element) return; + if (!html) { + element.removeAttribute("data-tooltip-html"); + return; + } + + element.dataset.tooltipHtml = html; + element.removeAttribute("title"); + + if (element._tooltipHandlers) return; + + const show = (event) => { + const tooltip = document.createElement("div"); + tooltip.className = "tooltip-box"; + tooltip.innerHTML = element.dataset.tooltipHtml; + document.body.appendChild(tooltip); + positionTooltip(event.currentTarget, tooltip); + element._tooltip = tooltip; + }; + + const move = (event) => { + if (element._tooltip) { + positionTooltip(event.currentTarget, element._tooltip); + } + }; + + const hide = () => { + if (element._tooltip) { + element._tooltip.remove(); + element._tooltip = null; + } + }; + + element.addEventListener("mouseenter", show); + element.addEventListener("mousemove", move); + element.addEventListener("mouseleave", hide); + element._tooltipHandlers = { show, move, hide }; } // ---------- grid column sync so rows align across sections ---------- @@ -110,22 +119,50 @@ function computeGridColumnsFrom(el) { return cols; } -function applyFixedColumns(el, cols) { +function applyFixedColumns( + el, + cols, + columnSize = "var(--dot-size)", + rowSize = "var(--dot-size)", + gapSize = "var(--dot-gap)" +) { if (!el || !cols) return; el.style.display = "grid"; - el.style.gridTemplateColumns = `repeat(${cols}, var(--dot-size))`; - el.style.gridAutoRows = "var(--dot-size)"; - el.style.gap = "var(--dot-gap)"; + el.style.gridTemplateColumns = `repeat(${cols}, ${columnSize})`; + el.style.gridAutoRows = rowSize; + el.style.gap = gapSize; } function syncGridColumns() { - const universeEl = document.getElementById("universe_canvas"); - const cols = computeGridColumnsFrom(universeEl); - if (!cols) return; const committeeEl = document.getElementById("committee_canvas"); - const votersEl = document.getElementById("voters_canvas"); - applyFixedColumns(committeeEl, cols); - applyFixedColumns(votersEl, cols); + if (committeeEl) { + const seatCount = committeeEl.querySelectorAll(".seat-box").length; + const committeeCols = seatCount ? Math.min(COMMITTEE_COLUMNS, seatCount) : COMMITTEE_COLUMNS; + applyFixedColumns( + committeeEl, + committeeCols, + "var(--seat-size)", + COMMITTEE_ROW_HEIGHT, + "var(--seat-gap)" + ); + } +} +// Shared helper to compute vote rectangle width based on bytes +function voteWidthPx(bytes) { + const maxBytes = Math.max(PERSISTENT_VOTE_BYTES, NONPERSISTENT_VOTE_BYTES) || 1; + const maxWidthPx = 48; // width used for the largest vote (non-persistent) + return Math.max(14, Math.round((bytes / maxBytes) * maxWidthPx)); +} + + +// Human-readable byte formatter +function formatBytes(bytes) { + if (bytes === null || bytes === undefined || isNaN(bytes)) return "—"; + const units = ['bytes', 'KB', 'MB', 'GB']; + let b = Math.max(0, Number(bytes)); + let i = 0; + while (b >= 1024 && i < units.length - 1) { b /= 1024; i++; } + return `${b.toFixed(b < 10 && i > 0 ? 1 : 0)} ${units[i]}`; } // Helper: Find 1-based universe index for a given poolId @@ -148,8 +185,6 @@ function buildPoolIdToUniverseIndex(demo) { // ---------- main render ---------- function renderUniverse(universe) { - ensureUniverseStyles(); - let pools = []; if (Array.isArray(universe)) { pools = universe; @@ -170,19 +205,25 @@ function renderUniverse(universe) { else nonPersistentCount++; }); - setText("universe_total", total || "—"); - setText("universe_persistent", persistentCount || "—"); - setText("universe_nonpersistent", nonPersistentCount || "—"); + setText("universe_total", (total ?? "—")); + setText("universe_persistent", (persistentCount ?? "—")); + setText("universe_nonpersistent", (nonPersistentCount ?? "—")); + // Add total stake calculation + const totalStake = pools.reduce((sum, p) => sum + (Number(p.stake) || 0), 0); + const hasStake = pools.some(p => p.stake !== undefined && p.stake !== null); + setText("universe_stake", hasStake ? formatNumber(totalStake) : "—"); const container = $("#universe_canvas"); if (!container) return; if (total === 0) { - container.innerHTML = "

    No data available

    "; + container.classList.add("universe-grid"); + container.replaceChildren(createEmptyState("No data available")); return; } container.classList.add("universe-grid"); + container.style.gridTemplateColumns = `repeat(${UNIVERSE_COLUMNS}, var(--dot-size))`; container.innerHTML = ""; pools.forEach((pool, i) => { @@ -200,8 +241,6 @@ function renderUniverse(universe) { if (isSelected) div.classList.add("is-selected"); if (isElected) div.classList.add("is-elected"); - div.style.position = "relative"; - const label = document.createElement("span"); label.classList.add("node-label"); const idx = i + 1; @@ -210,26 +249,13 @@ function renderUniverse(universe) { div.appendChild(label); const poolId = pool.pool_id || pool.id || ""; - const stake = typeof pool.stake !== "undefined" ? pool.stake : ""; - div.setAttribute("data-tooltip-html", `Pool ID: ${poolId}
    Stake: ${stake}`); - div.title = ""; - - div.addEventListener("mouseenter", (e) => { - const tooltip = document.createElement("div"); - tooltip.className = "tooltip-box"; - tooltip.innerHTML = div.getAttribute("data-tooltip-html"); - document.body.appendChild(tooltip); - const rect = e.target.getBoundingClientRect(); - tooltip.style.left = rect.left + window.scrollX + "px"; - tooltip.style.top = rect.top + window.scrollY - 40 + "px"; - div._tooltip = tooltip; - }); - div.addEventListener("mouseleave", () => { - if (div._tooltip) { - div._tooltip.remove(); - div._tooltip = null; - } - }); + const stake = typeof pool.stake !== "undefined" ? pool.stake : pool.total_stake; + const tooltipHtml = [ + `Pool ID: ${poolId || "—"}`, + `Stake: ${formatNumber(stake)}` + ].join("
    "); + attachTooltip(div, tooltipHtml); + container.appendChild(div); }); } @@ -254,10 +280,26 @@ function buildPersistentSet(demo) { return set; } -function buildPersistentIdToPoolId(map) { +function buildPersistentIdToPoolId(demo) { const m = new Map(); - if (!map || typeof map !== "object") return m; - for (const [k, v] of Object.entries(map)) m.set(Number(k), v); + const raw = demo?.persistent_map; + if (raw && typeof raw === "object") { + for (const [k, v] of Object.entries(raw)) { + if (v) { + const numericKey = Number(k); + if (!Number.isNaN(numericKey)) m.set(numericKey, v); + m.set(String(k), v); + } + } + } + + if (m.size === 0 && Array.isArray(demo?.committee?.seats)) { + for (const seat of demo.committee.seats) { + if (!seat || !seat.pool_id) continue; + if (seat.position !== undefined) m.set(Number(seat.position), seat.pool_id); + if (seat.index !== undefined) m.set(Number(seat.index), seat.pool_id); + } + } return m; } @@ -267,96 +309,222 @@ function getCommitteePoolId(x) { } function renderCommitteeFromDemo(demo) { - if (!demo || !Array.isArray(demo.committee)) return; - ensureUniverseStyles(); + if (!demo) return; const container = document.getElementById("committee_canvas"); if (!container) return; - const committee = demo.committee; - const persistentSet = buildPersistentSet(demo); - const poolIdToUniverseIndex = buildPoolIdToUniverseIndex(demo); - - // For stats: count persistent/non-persistent - let persistentCount = 0, nonPersistentCount = 0; - for (const member of committee) { - const poolId = getCommitteePoolId(member); - if (persistentSet.has(poolId)) persistentCount++; - else nonPersistentCount++; + // Normalize seats source: + // Prefer new model: demo.committee.seats = [{position, pool_id?, stake?, kind: "persistent"|"nonpersistent"}] + // Fallback to legacy: demo.committee = [ {pool_id, stake?}, ... ] where all entries are persistent seats. + let seats = []; + if (demo.committee && Array.isArray(demo.committee.seats)) { + seats = demo.committee.seats.slice().sort((a, b) => (a.position || 0) - (b.position || 0)); + } else if (Array.isArray(demo.committee)) { + seats = demo.committee.map((m, i) => ({ + position: (m.position ?? (i + 1)), + pool_id: getCommitteePoolId(m), + stake: (typeof m.stake !== "undefined" ? m.stake : undefined), + kind: "persistent" + })); + } else { + // Nothing to render + container.classList.remove("committee-grid"); + container.replaceChildren(createEmptyState("No committee seats")); + return; } - setText("committee_total", committee.length || "—"); - setText("committee_persistent", persistentCount || "—"); - setText("committee_nonpersistent", nonPersistentCount || "—"); - // Sort committee members by universe index (ascending), fallback to original order - const sorted = [...committee].sort((a, b) => { - const idxA = poolIdToUniverseIndex.get(getCommitteePoolId(a)) ?? 99999; - const idxB = poolIdToUniverseIndex.get(getCommitteePoolId(b)) ?? 99999; - return idxA - idxB; - }); + const poolIdToUniverseIndex = buildPoolIdToUniverseIndex(demo); - container.classList.add("universe-grid"); + // Stats: show total, persistent, and non-persistent seat counts. + setText("committee_total", (seats.length ?? "—")); + // Compute persistent/nonpersistent seat counts + const persistentCount = seats.filter(s => s.kind === "persistent").length; + const nonPersistentCount = seats.length - persistentCount; + setText("committee_persistent", persistentCount); + setText("committee_nonpersistent", nonPersistentCount); + + // Layout as seats (boxes), not dots + container.classList.remove("universe-grid"); + container.classList.add("committee-grid"); container.innerHTML = ""; - sorted.forEach((member, i) => { - const poolId = getCommitteePoolId(member) || ""; - const isPersistent = persistentSet.has(poolId); - const universeIdx = poolIdToUniverseIndex.get(poolId) || ""; - + const initialCols = Math.max(1, Math.min(COMMITTEE_COLUMNS, seats.length)); + applyFixedColumns( + container, + initialCols, + "var(--seat-size)", + COMMITTEE_ROW_HEIGHT, + "var(--seat-gap)" + ); + + for (const seat of seats) { const div = document.createElement("div"); - div.classList.add("pool-dot"); - div.classList.add(isPersistent ? "is-persistent" : "is-nonpersistent"); + div.className = "seat-box"; + const hasPool = !!seat.pool_id; + const isPersistentSeat = seat.kind === "persistent"; + div.classList.add(isPersistentSeat ? "is-persistent-seat" : "is-nonpersistent-seat"); + + // Seat number at top-left + const num = document.createElement("span"); + num.className = "seat-num"; + num.textContent = String(seat.position ?? ""); + div.appendChild(num); + + // If seat has a pool, add a pool-dot (reuse universal styling) + if (hasPool) { + const poolCircle = document.createElement("div"); + poolCircle.className = "pool-dot is-nonpersistent"; + const lbl = document.createElement("span"); + lbl.className = "node-label"; + const uidx = poolIdToUniverseIndex.get(seat.pool_id); + lbl.textContent = uidx ? String(uidx) : ""; + lbl.style.fontSize = (uidx >= 100 ? "11px" : "13px"); + poolCircle.appendChild(lbl); + div.appendChild(poolCircle); + } - // Numeric label: universe index (if known), else fallback to committee index - const label = document.createElement("span"); - label.classList.add("node-label"); - const idx = universeIdx || (i + 1); - label.textContent = idx; - label.style.fontSize = (idx >= 100 ? "11px" : "13px"); - div.appendChild(label); + // Set tooltip for assigned/non-assigned seats + if (hasPool) { + const poolId = seat.pool_id; + const stake = (typeof seat.stake !== "undefined") ? seat.stake : ""; + const tooltipHtml = [ + `Seat #${seat.position}`, + `Pool ID: ${poolId}`, + stake !== "" ? `Stake: ${formatNumber(stake)}` : "" + ].filter(Boolean).join("
    "); + attachTooltip(div, tooltipHtml); + } else { + attachTooltip(div, `Seat #${seat.position}
    Empty non-persistent slot`); + } - const stake = typeof member.stake !== "undefined" ? member.stake : ""; - div.setAttribute("data-tooltip-html", `Pool ID: ${poolId}
    Stake: ${stake}`); - div.title = ""; - - div.addEventListener("mouseenter", (e) => { - const tooltip = document.createElement("div"); - tooltip.className = "tooltip-box"; - tooltip.innerHTML = div.getAttribute("data-tooltip-html"); - document.body.appendChild(tooltip); - const rect = e.target.getBoundingClientRect(); - tooltip.style.left = rect.left + window.scrollX + "px"; - tooltip.style.top = rect.top + window.scrollY - 40 + "px"; - div._tooltip = tooltip; - }); - div.addEventListener("mouseleave", () => { - if (div._tooltip) { - div._tooltip.remove(); - div._tooltip = null; - } - }); container.appendChild(div); - }); + } } function buildVoterPools(demo) { - const pidToPool = buildPersistentIdToPoolId(demo && demo.persistent_map); - const pids = (demo?.voters?.persistent_ids ?? []); - const nonp = (demo?.voters?.nonpersistent_pool_ids ?? []); - const persistentPools = pids.map(pid => pidToPool.get(pid)).filter(Boolean); - const nonpersistentPools = nonp.filter(Boolean); + const pidToPool = buildPersistentIdToPoolId(demo); + + const persistentMap = new Map(); + const nonPersistentMap = new Map(); + + const ensurePersistentEntry = (seatId) => { + if (seatId === undefined || seatId === null) return null; + const numericSeat = Number(seatId); + const normalizedSeat = Number.isFinite(numericSeat) ? numericSeat : seatId; + const mapKey = String(normalizedSeat); + let entry = persistentMap.get(mapKey); + if (!entry) { + const poolId = pidToPool.get(normalizedSeat) ?? pidToPool.get(mapKey) ?? null; + entry = { + seatId: normalizedSeat, + poolId, + hasVote: false, + signature: null, + seat: null + }; + persistentMap.set(mapKey, entry); + } + return entry; + }; + + const ensureNonPersistentEntry = (poolId) => { + if (!poolId) return null; + const key = String(poolId); + let entry = nonPersistentMap.get(key); + if (!entry) { + entry = { + poolId: key, + eligibility: null, + signature: null, + hasVote: false + }; + nonPersistentMap.set(key, entry); + } + return entry; + }; + + const votersObj = demo?.voters ?? demo?.voters_filtered ?? demo?.voters_unfiltered ?? null; + if (votersObj && typeof votersObj === "object") { + const persistentIds = votersObj.persistent_ids ?? []; + for (const seatId of persistentIds) ensurePersistentEntry(seatId); + + const nonIds = votersObj.nonpersistent_pool_ids ?? []; + for (const poolId of nonIds) ensureNonPersistentEntry(poolId); + } + + if (Array.isArray(demo?.votes_preview)) { + for (const entry of demo.votes_preview) { + if (!entry) continue; + if (entry.type === "persistent") { + const record = ensurePersistentEntry(entry.seat_id ?? entry.seatId ?? entry.id); + if (record) { + record.hasVote = true; + record.signature = entry.signature ?? entry.vote_signature ?? null; + if (!record.poolId && entry.pool_id) record.poolId = entry.pool_id; + } + } else if (entry.type === "nonpersistent") { + const record = ensureNonPersistentEntry(entry.pool_id ?? entry.id); + if (record) { + record.signature = record.signature ?? entry.signature ?? entry.vote_signature ?? null; + record.eligibility = record.eligibility ?? entry.eligibility_sigma_eid_prefix ?? entry.eligibility ?? null; + record.hasVote = true; + } + } + } + } + + const committeePersistentSeats = Array.isArray(demo?.committee?.seats) + ? demo.committee.seats.filter(seat => seat && seat.kind === "persistent") + : []; + + for (const seat of committeePersistentSeats) { + const record = ensurePersistentEntry(seat.position); + if (record) { + if (!record.poolId && seat.pool_id) record.poolId = seat.pool_id; + record.seat = seat; + } + } + + const persistentEntries = Array.from(persistentMap.values()); + const nonPersistentEntries = Array.from(nonPersistentMap.values()).filter(entry => entry.hasVote); + const persistentVotePoolIds = persistentEntries + .filter(entry => entry.hasVote && entry.poolId) + .map(entry => entry.poolId); + const nonPersistentPoolIds = nonPersistentEntries + .filter(entry => entry.poolId) + .map(entry => entry.poolId); + return { - persistentPools, - nonpersistentPools, - all: [...new Set([...persistentPools, ...nonpersistentPools])] + persistentEntries, + nonPersistentEntries, + persistentPoolIds: persistentVotePoolIds, + nonPersistentPoolIds, + persistentSeatCount: committeePersistentSeats.length, + nonPersistentSeatCount: Array.isArray(demo?.committee?.seats) + ? demo.committee.seats.filter(seat => seat && seat.kind === "nonpersistent").length + : 0 }; } function renderVotersFromDemo(demo) { if (!demo) return; const container = document.getElementById("voters_canvas"); - const { persistentPools, nonpersistentPools, all } = buildVoterPools(demo); + const { + persistentEntries, + nonPersistentEntries, + persistentPoolIds, + nonPersistentPoolIds, + persistentSeatCount, + nonPersistentSeatCount + } = buildVoterPools(demo); const poolIdToUniverseIndex = buildPoolIdToUniverseIndex(demo); - - // Build a lookup for stakes so tooltips can show them for voters + const committeePositionLookup = new Map( + Object.entries(demo?.lookup?.committee_position_by_pool_id || {}).map(([k, v]) => [k, Number(v)]) + ); + const committeeMembers = Array.isArray(demo?.committee?.seats) + ? demo.committee.seats + : (Array.isArray(demo?.committee) ? demo.committee : []); + + // Build a lookup for stakes so tooltips can show them for voters (on circle), and vote tooltips can show voter id const poolIdToStake = new Map(); if (Array.isArray(demo.universe)) { for (const p of demo.universe) { @@ -364,94 +532,366 @@ function renderVotersFromDemo(demo) { if (id) poolIdToStake.set(id, (typeof p.stake !== "undefined") ? p.stake : ""); } } - if (Array.isArray(demo.committee)) { - for (const m of demo.committee) { - const id = m.pool_id || m.id; - if (id && !poolIdToStake.has(id)) { - poolIdToStake.set(id, (typeof m.stake !== "undefined") ? m.stake : ""); - } + for (const m of committeeMembers) { + const id = m.pool_id || m.id; + if (id && !poolIdToStake.has(id)) { + poolIdToStake.set(id, (typeof m.stake !== "undefined") ? m.stake : ""); } } - // Stats: persistent/nonpersistent, and breakdown for *nonpersistent only* - const persistentCount = persistentPools.length; - const nonPersistentCount = nonpersistentPools.length; - - // Build a set of committee poolIds for quick membership checks - const committeeSet = new Set(); - if (demo.committee) { - for (const member of demo.committee) { - const pid = getCommitteePoolId(member); - if (pid) committeeSet.add(pid); - } + // Quorum / fraction (if present). Accept many possible shapes/keys and strings. + // Do NOT set any default if not found; simply show '—'. + let q = ( + demo?.parameters?.vote_fraction ?? + demo?.parameters?.fraction ?? + demo?.voters?.fraction ?? + demo?.voters?.quorum ?? + demo?.vote_fraction ?? + demo?.fraction ?? + demo?.quorum ?? + demo?.metadata?.vote_fraction ?? + demo?.params?.vote_fraction ?? + demo?.params?.fraction ?? + demo?.params?.quorum ?? + demo?.params?.quorum_fraction + ); + if (typeof q === 'string') q = parseFloat(q); + if (q !== undefined && q !== null && !Number.isNaN(q)) { + const pct = q <= 1 ? Math.round(q * 100) : Math.round(q); + setText('voters_quorum', pct + '%'); + } else { + setText('voters_quorum', '—'); } - // Count only among non-persistent voters - let nonpInCommittee = 0, nonpOutsideCommittee = 0; - for (const poolId of nonpersistentPools) { - if (committeeSet.has(poolId)) nonpInCommittee++; - else nonpOutsideCommittee++; - } + // Stats: persistent/nonpersistent, and breakdown for nonpersistent only + const persistentTotalSeats = persistentSeatCount; + const persistentVotesCount = persistentEntries.filter(entry => entry.hasVote).length; + const nonPersistentTotalSlots = nonPersistentSeatCount; + + const displayedPersistentVotes = persistentVotesCount; + const targetNonPersistent = nonPersistentTotalSlots + ? Math.min(Math.round(nonPersistentTotalSlots * 0.75), nonPersistentEntries.length) + : Math.min(Math.round(nonPersistentEntries.length * 0.75), nonPersistentEntries.length); + const targetNonPersistentCount = Math.max(targetNonPersistent, 0); + + setText("voters_persistent", `${displayedPersistentVotes}/${persistentTotalSeats || "—"}`); - setText("voters_total", (persistentCount + nonPersistentCount) || "—"); - setText("voters_persistent", persistentCount || "—"); - setText("voters_nonpersistent", nonPersistentCount || "—"); - setText("voters_nonpersistent_in_committee", nonpInCommittee || "—"); - setText("voters_nonpersistent_outside_committee", nonpOutsideCommittee || "—"); if (!container) return; - ensureUniverseStyles(); - container.classList.add("universe-grid"); + + container.classList.remove("universe-grid", "voting-list"); + container.classList.add("votes-board"); container.innerHTML = ""; - // Sort by universe index (ascending) - const sorted = [...all].sort((a, b) => { - const idxA = poolIdToUniverseIndex.get(a) ?? 99999; - const idxB = poolIdToUniverseIndex.get(b) ?? 99999; + if (!persistentEntries.length && !nonPersistentEntries.length) { + container.appendChild(createEmptyState("No voters recorded")); + return; + } + + const persistentSorted = [...persistentEntries].sort((a, b) => { + const seatA = Number(a.seatId); + const seatB = Number(b.seatId); + const idxA = Number.isFinite(seatA) ? seatA : (poolIdToUniverseIndex.get(a.poolId) ?? 99999); + const idxB = Number.isFinite(seatB) ? seatB : (poolIdToUniverseIndex.get(b.poolId) ?? 99999); return idxA - idxB; }); - for (const poolId of sorted) { - const div = document.createElement("div"); - div.className = "pool-dot"; - if (persistentPools.includes(poolId)) div.classList.add("is-persistent"); - else div.classList.add("is-nonpersistent"); + const nonPersistentSorted = [...nonPersistentEntries].sort((a, b) => { + const idxA = poolIdToUniverseIndex.get(a.poolId) ?? 99999; + const idxB = poolIdToUniverseIndex.get(b.poolId) ?? 99999; + return idxA - idxB; + }); + const displayedNonPersistent = nonPersistentSorted.slice(0, targetNonPersistentCount); + const displayedNonPersistentCount = displayedNonPersistent.length; + + const totalVotersDisplayed = displayedPersistentVotes + displayedNonPersistentCount; + setText("voters_total", `${totalVotersDisplayed}`); + setText("voters_nonpersistent", `${displayedNonPersistentCount}/${nonPersistentTotalSlots || "—"}`); + + const poolIdToSeat = new Map(); + const positionToSeat = new Map(); + const nonPersistentSeatsOrdered = []; + for (const seat of committeeMembers) { + if (seat && seat.pool_id) { + poolIdToSeat.set(seat.pool_id, seat); + } + if (seat && seat.position !== undefined) { + positionToSeat.set(Number(seat.position), seat); + } + if (seat && seat.kind === "nonpersistent") { + nonPersistentSeatsOrdered.push(seat); + } + } + nonPersistentSeatsOrdered.sort((a, b) => (a.position || 0) - (b.position || 0)); + + const createArrow = () => { + const arrow = document.createElement("span"); + arrow.className = "vote-arrow vote-flow__arrow"; + return arrow; + }; - // label = 1-based index in universe + const createSeatTile = (seat, poolId, variant = "persistent") => { + const seatTile = document.createElement("div"); + seatTile.className = `seat-box vote-seat-inline ${variant === "persistent" ? "is-persistent-seat" : "is-nonpersistent-seat"}`; + + const seatNum = seat?.position ?? seat?.index ?? null; + const numSpan = document.createElement("span"); + numSpan.className = "seat-num"; + numSpan.textContent = seatNum ? String(seatNum) : (variant === "persistent" ? "—" : ""); + seatTile.appendChild(numSpan); + + const dot = document.createElement("div"); + dot.className = "pool-dot is-nonpersistent"; const label = document.createElement("span"); - label.classList.add("node-label"); - const idx = poolIdToUniverseIndex.get(poolId) || ""; - label.textContent = idx ? String(idx) : ""; - label.style.fontSize = (idx >= 100 ? "11px" : "13px"); - div.appendChild(label); + label.className = "node-label"; + const resolvedPoolId = poolId ?? seat?.pool_id ?? null; + let universeIdx = null; + if (resolvedPoolId) { + if (seat && seat.index !== undefined) { + const seatIndexNumeric = Number(seat.index); + universeIdx = Number.isFinite(seatIndexNumeric) ? (seatIndexNumeric + 1) : null; + } + if (universeIdx === null) { + const lookupIdx = poolIdToUniverseIndex.get(resolvedPoolId) ?? committeePositionLookup.get(resolvedPoolId); + if (lookupIdx !== undefined && lookupIdx !== null) { + universeIdx = Number(lookupIdx); + if (Number.isFinite(universeIdx)) universeIdx += 1; + } + } + } + label.textContent = universeIdx ? String(universeIdx) : ""; + label.style.fontSize = (universeIdx >= 100 ? "11px" : "13px"); + dot.appendChild(label); + seatTile.appendChild(dot); + + const tooltip = []; + if (seatNum) tooltip.push(`Seat #${seatNum}`); + if (resolvedPoolId) tooltip.push(`Pool ID: ${resolvedPoolId}`); + if (seat && seat.stake !== undefined) { + tooltip.push(`Stake: ${formatNumber(seat.stake)}`); + } + attachTooltip(seatTile, tooltip.join("
    ")); + return seatTile; + }; + + const createNodeTile = (poolId, isPersistent) => { + const node = document.createElement("div"); + node.className = "vote-node"; + const dot = document.createElement("div"); + dot.className = "pool-dot"; + dot.classList.add(isPersistent ? "is-persistent" : "is-nonpersistent", "is-voter"); + const label = document.createElement("span"); + label.className = "node-label"; + const universeIdx = poolIdToUniverseIndex.get(poolId); + label.textContent = universeIdx ? String(universeIdx) : ""; + label.style.fontSize = (universeIdx >= 100 ? "11px" : "13px"); + dot.appendChild(label); const stake = poolIdToStake.get(poolId); - const stakeLine = (stake !== undefined && stake !== "") ? `
    Stake: ${stake}` : ""; - div.setAttribute("data-tooltip-html", `Pool ID: ${poolId}` + stakeLine); - div.title = ""; - - div.addEventListener("mouseenter", (e) => { - const tip = document.createElement("div"); - tip.className = "tooltip-box"; - tip.innerHTML = div.getAttribute("data-tooltip-html"); - document.body.appendChild(tip); - const rect = e.target.getBoundingClientRect(); - tip.style.left = rect.left + window.scrollX + "px"; - tip.style.top = rect.top + window.scrollY - 40 + "px"; - div._tooltip = tip; - }); - div.addEventListener("mouseleave", () => { - if (div._tooltip) { div._tooltip.remove(); div._tooltip = null; } + const lines = []; + if (poolId) { + lines.push(`Pool ID: ${poolId}`); + } + if (stake !== undefined && stake !== "") { + lines.push(`Stake: ${formatNumber(stake)}`); + } + attachTooltip(dot, lines.join("
    ")); + node.appendChild(dot); + + return node; + }; + + const createVoteRect = (info, isPersistent) => { + const voteRect = document.createElement("div"); + voteRect.className = "vote-rect"; + voteRect.classList.add(isPersistent ? "is-persistent" : "is-nonpersistent"); + const vBytes = isPersistent ? PERSISTENT_VOTE_BYTES : NONPERSISTENT_VOTE_BYTES; + voteRect.style.width = `${voteWidthPx(vBytes)}px`; + + const tooltip = []; + if (isPersistent) { + const seatNum = info.seatId ?? info.seat?.position ?? info.seat?.index ?? "—"; + tooltip.push(`Seat #${seatNum}`); + tooltip.push(`Size: ${formatBytes(vBytes)}`); + if (info.signature) tooltip.push(`Signature: ${info.signature}`); + } else { + tooltip.push(`Pool ID: ${info.poolId}`); + if (info.eligibility) tooltip.push(`Eligibility prefix: ${info.eligibility}`); + if (info.signature) tooltip.push(`Signature: ${info.signature}`); + tooltip.push(`Size: ${formatBytes(vBytes)}`); + } + attachTooltip(voteRect, tooltip.join("
    ")); + return voteRect; + }; + + const createPersistentRow = (entry, idx) => { + const row = document.createElement("div"); + row.className = "vote-flow__row vote-flow__row--persistent"; + const seat = entry.seat ?? poolIdToSeat.get(entry.poolId) ?? positionToSeat.get(entry.seatId); + row.appendChild(createSeatTile(seat, entry.poolId ?? seat?.pool_id ?? null, "persistent")); + + const arrow = createArrow(); + if (!entry.hasVote) { + arrow.classList.add("placeholder"); + } + row.appendChild(arrow); + + if (entry.hasVote) { + row.appendChild(createVoteRect({ ...entry, seat }, true)); + } else { + const placeholder = document.createElement("div"); + placeholder.className = "vote-rect placeholder persistent"; + placeholder.style.width = `${voteWidthPx(PERSISTENT_VOTE_BYTES)}px`; + row.appendChild(placeholder); + } + return row; + }; + + const createNonPersistentRow = (entry, idx) => { + const row = document.createElement("div"); + row.className = "vote-flow__row vote-flow__row--nonpersistent"; + const seat = entry.seat ?? nonPersistentSeatsOrdered[idx] ?? null; + row.appendChild(createSeatTile(seat, entry.poolId, "nonpersistent")); + row.appendChild(createArrow()); + row.appendChild(createVoteRect(entry, false)); + return row; + }; + + const appendFlow = (entries, buildRow, modifier) => { + if (!entries.length) return; + const flow = document.createElement("div"); + flow.className = "vote-flow"; + if (modifier) flow.classList.add(modifier); + entries.forEach((entry, index) => { + if (!entry.seat && buildRow === createNonPersistentRow) { + entry.seat = nonPersistentSeatsOrdered[index] ?? null; + } + flow.appendChild(buildRow(entry, index)); }); - container.appendChild(div); + container.appendChild(flow); + }; + + appendFlow(persistentSorted, createPersistentRow, "vote-flow--persistent"); + appendFlow(displayedNonPersistent, createNonPersistentRow, "vote-flow--nonpersistent"); +} + +function renderAggregationFromDemo(demo) { + const container = document.getElementById('aggregation_canvas'); + if (!container) return; + + const { persistentPoolIds, nonPersistentPoolIds } = buildVoterPools(demo); + + const persistentBytes = firstFinite( + demo?.aggregation?.persistent_bytes, + demo?.aggregation?.persistent_vote_bytes, + demo?.metrics?.persistent_bytes, + demo?.metrics?.persistent_vote_bytes, + persistentPoolIds.length * PERSISTENT_VOTE_BYTES + ); + const nonPersistentBytes = firstFinite( + demo?.aggregation?.nonpersistent_bytes, + demo?.aggregation?.nonpersistent_vote_bytes, + demo?.aggregation?.non_persistent_bytes, + demo?.metrics?.nonpersistent_bytes, + demo?.metrics?.nonpersistent_vote_bytes, + nonPersistentPoolIds.length * NONPERSISTENT_VOTE_BYTES + ); + const totalVotesBytes = persistentBytes + nonPersistentBytes; + const certBytes = firstFinite( + demo?.certificate?.cert_bytes, + demo?.certificate?.certificate_bytes, + demo?.certificate?.bytes, + demo?.aggregation?.certificate_bytes + ); + + const votesEl = document.getElementById('agg_votes_size'); + if (votesEl) { + const pieces = [ + `${formatNumber(totalVotesBytes)} B`, + `Persistent: ${formatNumber(persistentBytes)} B`, + `Non-persistent: ${formatNumber(nonPersistentBytes)} B` + ]; + votesEl.innerHTML = pieces.join('
    '); + } + + setText('agg_cert_size', certBytes !== null ? formatBytes(certBytes) : '—'); + + const gainEl = document.getElementById('agg_gain'); + if (gainEl) { + let ratioVal = firstFinite( + demo?.certificate?.compression_ratio, + demo?.compression_ratio, + demo?.aggregation?.compression_ratio, + demo?.metrics?.compression_ratio, + null + ); + if (!ratioVal && totalVotesBytes > 0 && Number(certBytes) > 0) { + ratioVal = totalVotesBytes / Number(certBytes); + } + gainEl.textContent = ratioVal && isFinite(ratioVal) ? `${ratioVal.toFixed(2)}×` : '—'; + } + + container.classList.add('aggregation-row'); + container.innerHTML = ''; + + if (persistentPoolIds.length + nonPersistentPoolIds.length === 0) { + container.appendChild(createEmptyState("No votes recorded")); + return; + } + + const votesGrid = document.createElement('div'); + votesGrid.className = 'agg-votes'; + + const addVoteRect = (poolId, bytes, isPersistent) => { + const vote = document.createElement('div'); + vote.className = 'vote-rect'; + vote.classList.add(isPersistent ? 'is-persistent' : 'is-nonpersistent'); + vote.style.width = `${voteWidthPx(bytes)}px`; + attachTooltip(vote, `Vote
    From: ${poolId}
    Size: ${bytes} bytes`); + votesGrid.appendChild(vote); + }; + + persistentPoolIds.forEach(pid => addVoteRect(pid, PERSISTENT_VOTE_BYTES, true)); + nonPersistentPoolIds.forEach(pid => addVoteRect(pid, NONPERSISTENT_VOTE_BYTES, false)); + + const arrow = document.createElement('span'); + arrow.className = 'big-arrow'; + arrow.textContent = '⇒'; + + const cert = document.createElement('div'); + cert.className = 'certificate-rect'; + cert.textContent = 'Certificate'; + + if (totalVotesBytes > 0 && Number(certBytes) > 0) { + const minWidth = 140; + const maxWidth = 420; + const scaled = Math.min(maxWidth, Math.max(minWidth, (Number(certBytes) / totalVotesBytes) * 600)); + cert.style.width = `${Math.round(scaled)}px`; + cert.style.minWidth = `${Math.round(scaled)}px`; + } + + const certificateTooltip = []; + const eid = demo?.certificate?.eid ?? demo?.identifiers?.eid; + const eb = demo?.certificate?.eb_hash ?? demo?.identifiers?.eb_hash; + const signer = demo?.certificate?.signer; + if (signer) certificateTooltip.push(`Signer: ${signer}`); + if (eid) certificateTooltip.push(`EID: ${eid}`); + if (eb) certificateTooltip.push(`EB hash: ${eb}`); + if (certBytes !== null) certificateTooltip.push(`Size: ${formatBytes(certBytes)}`); + if (certificateTooltip.length) { + attachTooltip(cert, certificateTooltip.join('
    ')); } + + container.appendChild(votesGrid); + container.appendChild(arrow); + container.appendChild(cert); } // ---------- data loading ---------- function getRunFromURL() { const p = new URLSearchParams(window.location.search); const run = p.get("run"); - // default to run64 if not provided - return run && /^run\d+$/i.test(run) ? run : "run64"; + // No default fallback here. Only accept valid runX format or null. + return run && /^run\d+$/i.test(run) ? run : null; } async function tryFetchJson(url) { @@ -464,11 +904,33 @@ async function tryFetchJson(url) { } } -async function loadDemoJson() { - const run = getRunFromURL(); - let data = await tryFetchJson(`/demo/${run}`); - if (!data) data = await tryFetchJson("/static/demo.json"); - if (!data) data = await tryFetchJson("/demo.json"); +async function tryFetchText(url) { + try { + const r = await fetch(url, { cache: "no-store" }); + if (!r.ok) return null; + return (await r.text()).trim(); + } catch { + return null; + } +} + +// Loads demo JSON for a specific run directory (runDir: string) +async function loadDemoJson(runDir) { + if (!runDir || typeof runDir !== "string") return null; + + const data = await tryFetchJson(`/demo/${runDir}`); + if (!data) return null; + + if (!data.identifiers) { + const [eid, eb] = await Promise.all([ + tryFetchText(`/demo/${runDir}/eid.txt`), + tryFetchText(`/demo/${runDir}/ebhash.txt`) + ]); + data.identifiers = { + eid: eid || null, + eb_hash: eb || null + }; + } return data; } @@ -478,16 +940,14 @@ function fillIdentifiers(obj) { const eb = obj.eb_hash || obj.eb || obj.EB || obj.ebHash; if (eid) { setText("eid", eid); - setText("eid_value", eid); } if (eb) { setText("eb", eb); - setText("ebhash_value", eb); } } -// ---------- boot ---------- -document.addEventListener("DOMContentLoaded", async () => { +// --- UI clearing helpers --- +function clearUIPlaceholders() { setText("universe_total", "—"); setText("universe_persistent", "—"); setText("universe_nonpersistent", "—"); @@ -496,16 +956,29 @@ document.addEventListener("DOMContentLoaded", async () => { setText("committee_nonpersistent", "—"); setText("eid", "—"); setText("eb", "—"); - setText("eid_value", "—"); - setText("ebhash_value", "—"); setText("voters_total", "—"); setText("voters_persistent", "—"); setText("voters_nonpersistent", "—"); - setText("voters_in_committee", "—"); - setText("voters_outside_committee", "—"); - - const demo = await loadDemoJson(); + setText('voters_quorum', '—'); + setText('agg_votes_size', '—'); + setText('agg_cert_size', '—'); + setText('agg_gain', '—'); + // Clear main panels + const clearIds = ["universe_canvas", "committee_canvas", "voters_canvas", "aggregation_canvas"]; + for (const id of clearIds) { + const el = document.getElementById(id); + if (el) el.innerHTML = ""; + } +} +// --- Data loading and rendering sequence for a given runDir --- +async function loadAndRenderRun(runDir) { + clearUIPlaceholders(); + if (!runDir || typeof runDir !== "string") { + renderUniverse([]); + return; + } + const demo = await loadDemoJson(runDir); if (demo) { const universe = demo.universe || demo; renderUniverse(universe); @@ -513,15 +986,57 @@ document.addEventListener("DOMContentLoaded", async () => { fillIdentifiers(demo.certificate); renderCommitteeFromDemo(demo); renderVotersFromDemo(demo); - - // Align grid columns across sections and keep them in sync on resize + renderAggregationFromDemo(demo); syncGridColumns(); - window.addEventListener("resize", () => { - syncGridColumns(); - }); + window.addEventListener("resize", syncGridColumns); } else { renderUniverse([]); } +} + +// --- Boot logic: only auto-load if localStorage has lastRun --- +document.addEventListener("DOMContentLoaded", () => { + clearUIPlaceholders(); + + // Setup form submission for run directory selection + const form = document.getElementById("run-form"); + if (form) { + form.addEventListener("submit", async (e) => { + e.preventDefault(); + const input = form.querySelector("input[name='run']") || document.getElementById("run-input"); + if (!input) return; + let runDir = input.value.trim(); + // Accept only runX format (e.g., run64, run5, etc.) + if (!/^run\d+$/i.test(runDir)) { + alert("Please enter a valid run directory (e.g., run64)."); + input.focus(); + return; + } + // Save last used run to localStorage + localStorage.setItem("lastRun", runDir); + await loadAndRenderRun(runDir); + }); + } + + // Prefer run from URL param if present (e.g., /ui?run=run100) + const runFromURL = getRunFromURL(); + if (runFromURL) { + const inputUrl = document.querySelector("#run-form input[name='run']") || document.getElementById("run-input"); + if (inputUrl) inputUrl.value = runFromURL; + localStorage.setItem("lastRun", runFromURL); + loadAndRenderRun(runFromURL); + return; // skip other auto-loading + } + + // If localStorage has lastRun, auto-load it; else wait for user input + const lastRun = localStorage.getItem("lastRun"); + if (lastRun && /^run\d+$/i.test(lastRun)) { + // Optionally set the input field, if present + const input = document.querySelector("#run-form input[name='run']") || document.getElementById("run-input"); + if (input) input.value = lastRun; + loadAndRenderRun(lastRun); + } + // else: UI remains in cleared state, waiting for user to submit run directory }); // === Middle-ellipsis for long identifiers (keep start and end) === @@ -536,11 +1051,21 @@ function watchAndEllipsize(id, opts = {}) { if (!el) return; const { keepStart = 26, keepEnd = 22, truncate = true } = opts; const apply = () => { - const full = el.getAttribute('data-full') || el.textContent.trim(); - el.setAttribute('data-full', full); + const current = (el.textContent || "").trim(); + const stored = el.getAttribute('data-full'); + const full = current || stored || ""; + const next = truncate ? shortenMiddle(full, keepStart, keepEnd) : full; + + if (stored !== full) { + el.setAttribute('data-full', full); + } + if (!el.classList.contains('mono')) { + el.classList.add('mono'); + } el.title = full; - el.classList.add('mono'); - el.textContent = truncate ? shortenMiddle(full, keepStart, keepEnd) : full; + if (el.textContent !== next) { + el.textContent = next; + } }; const mo = new MutationObserver(apply); mo.observe(el, { childList: true, characterData: true, subtree: true }); @@ -548,6 +1073,6 @@ function watchAndEllipsize(id, opts = {}) { } document.addEventListener('DOMContentLoaded', () => { - watchAndEllipsize('eid_value', { truncate: false }); - watchAndEllipsize('ebhash_value', { keepStart: 26, keepEnd: 22, truncate: true }); -}); \ No newline at end of file + watchAndEllipsize('eid', { truncate: false }); + watchAndEllipsize('eb', { keepStart: 26, keepEnd: 22, truncate: true }); +}); diff --git a/crypto-benchmarks.rs/demo/ui/static/styles.css b/crypto-benchmarks.rs/demo/ui/static/styles.css index eac9a6482..359eae038 100644 --- a/crypto-benchmarks.rs/demo/ui/static/styles.css +++ b/crypto-benchmarks.rs/demo/ui/static/styles.css @@ -3,88 +3,103 @@ --card: #121a22; --text: #e7eef7; --muted: #9fb2c7; - --green: #10b981; - /* crisp green */ + --green: #19c37d; --orange: #f59e0b; --border: #1f2a35; + --dot-size: 30px; + --dot-gap: 6px; + --seat-size: 48px; + --seat-gap: 6px; + --vote-height: 36px; + --card-radius: 10px; } * { box-sizing: border-box; } +body, +button, +input { + font-family: ui-sans-serif, system-ui, -apple-system, blinkmacsystemfont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; +} + body { margin: 0; background: var(--bg); color: var(--text); - font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; + line-height: 1.5; } -.count { - font-weight: 700; +a { + color: inherit; } -.persistent-color { - color: var(--green); -} - -.nonpersistent-color { - color: var(--orange); -} - -/* utility helpers */ -.center { - text-align: center; -} - -.card-subtitle { - margin: 0 0 10px; - color: var(--muted); - font-size: 13px; -} - -header { - max-width: 1100px; - margin: 20px auto; +.app-header { + max-width: 960px; + margin: 24px auto 16px; padding: 0 16px; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.75rem; + text-align: center; } -header h1 { - margin: 0 0 12px; - font-size: 24px; +.app-title { + margin: 0; + font-size: 28px; + font-weight: 700; } -header form { +.app-header__form { display: flex; - gap: 8px; + flex-wrap: wrap; + gap: 0.75rem; align-items: center; + justify-content: center; +} + +.app-header__form label { + font-weight: 600; } -header input { - padding: 8px 10px; +.app-header__form input { + padding: 8px 12px; + border-radius: 6px; border: 1px solid var(--border); background: #0e141b; color: var(--text); - border-radius: 6px; + min-width: 180px; } -header button { - padding: 8px 12px; - background: #2a4365; - color: #fff; - border: 0; +.app-header__form button { + padding: 8px 14px; border-radius: 6px; + border: 1px solid transparent; + background: #2a4365; + color: #ffffff; + font-weight: 600; cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease; } -header button:hover { - background: #2f4a73; +.app-header__form button:hover, +.app-header__form button:focus-visible { + background: #33527c; + border-color: #4b6b97; +} + +.app-header__form button:focus-visible, +.app-header__form input:focus-visible { + outline: 2px solid #4b6b97; + outline-offset: 2px; } main { - max-width: 1100px; - margin: 0 auto; - padding: 0 16px 24px; + max-width: 1400px; + margin: 0 auto 32px; + padding: 0 16px 32px; display: grid; gap: 16px; } @@ -92,249 +107,520 @@ main { .card { background: var(--card); border: 1px solid var(--border); - border-radius: 10px; - padding: 16px; + border-radius: var(--card-radius); + padding: 16px 20px; } -.card h2 { +.card-title { margin: 0 0 12px; - font-size: 18px; + font-size: 20px; + font-weight: 700; } -.card h3 { - margin: 0 0 8px; - font-size: 16px; +.card-subtitle { + margin: -6px 0 14px; color: var(--muted); + font-size: 14px; } -.grid.two { +.center { + text-align: center; +} + +.grid { display: grid; + gap: 16px; +} + +.grid.two { grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 12px 20px; } -@media (max-width: 800px) { +.grid.two.wide-left { + grid-template-columns: 1.25fr 2fr; +} + +.identifiers-row { + display: flex; + gap: 1rem; + align-items: center; + flex-wrap: wrap; +} + +.eid-block, +.eb-block { + display: flex; + align-items: center; + gap: 0.5rem; + min-width: 0; +} + +.eb-block { + margin-left: auto; + text-align: right; + justify-content: flex-end; +} + +@media (max-width: 1024px) { .grid.two { grid-template-columns: 1fr; } + + .grid.two.wide-left { + grid-template-columns: 1fr; + } } -/* green highlight for gain */ -#compression_ratio { - color: var(--green); - font-weight: 700; +.stat-group { + display: flex; + flex-direction: column; + gap: 0.5rem; } -/* tables */ -table { - width: 100%; - border-collapse: collapse; - border: 1px solid var(--border); - background: #0f1720; +.stat { + display: flex; + align-items: baseline; + gap: 0.5rem; } -th, -td { - padding: 8px 10px; - border-bottom: 1px solid var(--border); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 12.5px; +.stat--indent { + padding-left: 1.25rem; } -th { - text-align: left; - color: var(--muted); +.stat--indent-deep { + padding-left: 2.25rem; } -tbody tr:nth-child(odd) { - background: rgba(255, 255, 255, 0.02); +.stat-label { + font-weight: 600; + color: inherit; } -tbody tr:hover { - background: rgba(255, 255, 255, 0.05); +.stat-value { + font-weight: 700; + white-space: nowrap; + color: inherit; } -.note { - margin-top: 6px; +.text-muted { color: var(--muted); - font-size: 12px; } -#summary .grid.two>div>div { - margin-bottom: 12px; +.text-green { + color: var(--green); } -.gain div { - color: var(--green); +.text-orange { + color: var(--orange); +} + +.text-blue { + color: #2563eb; +} + +.count { font-weight: 700; } -/* Wider left column for Committee section */ -.grid.two.wide-left { - display: grid; - grid-template-columns: 2fr 1.2fr; - gap: 16px 20px; +.mono { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: inherit; } -@media (max-width: 1100px) { - .grid.two.wide-left { - grid-template-columns: 1fr; - } +.text-wrap { + overflow-wrap: anywhere; +} + +.banner { + display: flex; + align-items: center; + gap: 0.5rem; + margin: 0 auto 16px; + padding: 12px 16px; + border-radius: 8px; + border: 1px solid #f87171; + background: rgba(248, 113, 113, 0.15); + color: #fecaca; + max-width: min(780px, 100%); } -/* Extra vertical breathing room between stacked info lines */ -#summary .stack { - margin-top: 10px; +.banner[hidden] { + display: none; } -#summary .gain div { - color: var(--green); - font-weight: 700; +.empty-state { + margin: 0; + color: var(--muted); + font-size: 13px; } -/* Make tables never run outside the card */ -.card table { - display: block; - width: 100%; - overflow-x: auto; +.tooltip { + position: absolute; + padding: 6px 8px; + background: rgba(15, 23, 32, 0.95); + border: 1px solid var(--border); + color: var(--text); + font-size: 12px; + border-radius: 6px; + pointer-events: none; white-space: nowrap; + z-index: 20; +} + +.tooltip[hidden] { + display: none; } -/* === Universe (Stake Pools) visualization === */ -.universe-canvas { +.tooltip-box { + position: absolute; + background: rgba(30, 30, 30, 0.92); + color: #ffffff; + padding: 6px 8px; + border-radius: 6px; + font-size: 12px; + line-height: 1.4; + pointer-events: none; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35); + z-index: 1000; +} + +.universe-canvas, +.committee-canvas { position: relative; - /* anchor for the tooltip */ display: grid; - grid-template-columns: repeat(auto-fill, minmax(16px, 1fr)); - grid-auto-rows: 16px; - /* keep rows consistent with dot size */ - gap: 6px; - padding: 8px 0; - align-items: center; - max-width: 700px; - margin-top: 12px; + grid-template-columns: repeat(auto-fill, minmax(var(--dot-size), var(--dot-size))); + grid-auto-rows: var(--dot-size); + gap: var(--dot-gap); + align-content: start; + justify-content: flex-start; + padding-top: 8px; + min-height: calc(var(--dot-size) * 4); +} + +.universe-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(var(--dot-size), var(--dot-size))); + grid-auto-rows: var(--dot-size); + gap: var(--dot-gap); } .pool-dot { - width: 16px; - height: 16px; + width: var(--dot-size); + height: var(--dot-size); border-radius: 50%; - border: 1px solid #999; - background: #f0f0f0; - cursor: help; + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + background: #475569; + color: #ffffff; + font-weight: 700; + cursor: default; transition: transform 0.15s ease, box-shadow 0.15s ease; } -.pool-dot.is-nonpersistent { - background: var(--orange); - border-color: #b45309; - /* darker orange edge for contrast */ -} - .pool-dot:hover { - transform: scale(1.2); - box-shadow: 0 0 5px rgba(255, 255, 255, 0.5); + transform: scale(1.08); + box-shadow: 0 0 6px rgba(255, 255, 255, 0.3); } .pool-dot.is-persistent { background: var(--green); - border-color: #059669; - /* darker green edge for contrast */ +} + +.pool-dot.is-nonpersistent { + background: var(--orange); } .pool-dot.is-selected { - outline: 2px solid #007bff; + outline: 2px solid rgba(56, 189, 248, 0.9); } .pool-dot.is-elected { box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.8) inset; } -/* Tooltip for pool hover (positioned inside .universe-canvas) */ -#universe_tooltip { +.pool-dot.is-voter::after { + content: ""; position: absolute; - padding: 6px 8px; - background: rgba(15, 23, 32, 0.98); - border: 1px solid var(--border); - color: var(--text); - font-size: 12px; - border-radius: 6px; + inset: -3px; + border-radius: 50%; + border: 2px solid rgba(255, 255, 255, 0.6); pointer-events: none; - white-space: nowrap; - transform: translate(-50%, -130%); - z-index: 1000; } -/* Tooltip for voters hover (positioned inside .universe-canvas) */ -#voters_tooltip { +.pool-dot .node-label { position: absolute; - padding: 6px 8px; - background: rgba(15, 23, 32, 0.98); - border: 1px solid var(--border); - color: var(--text); + top: 50%; + left: 0; + width: 100%; + transform: translateY(-50%); + text-align: center; + pointer-events: none; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); +} + +.committee-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(var(--seat-size), var(--seat-size))); + grid-auto-rows: calc(var(--seat-size) + 10px); + gap: var(--seat-gap); + align-content: start; + justify-content: flex-start; + padding-top: 8px; +} + +.seat-box { + position: relative; + width: var(--seat-size); + height: calc(var(--seat-size) + 10px); + border-radius: 10px; + border: 2px solid rgba(148, 163, 184, 0.35); + background: rgba(148, 163, 184, 0.06); + display: flex; + align-items: center; + justify-content: center; +} + +.seat-box.is-persistent-seat { + border-color: rgba(25, 195, 125, 0.75); + background: rgba(25, 195, 125, 0.1); +} + +.seat-box.is-nonpersistent-seat { + border-color: rgba(59, 130, 246, 0.7); + background: rgba(59, 130, 246, 0.18); +} + +.seat-box.is-nonpersistent-seat .seat-num { + color: #dbeafe; +} + +.seat-num { + position: absolute; + top: 5px; + left: 8px; font-size: 12px; - border-radius: 6px; + font-weight: 700; + color: #ffffff; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45); pointer-events: none; - white-space: nowrap; - transform: translate(-50%, -130%); - z-index: 1000; } -@media (min-width: 900px) { - .pool-dot { - width: 18px; - height: 18px; +.seat-box .pool-dot { + position: absolute; + top: 62%; + left: 50%; + transform: translate(-50%, -50%); +} + +.voting-list { + display: flex; + flex-wrap: wrap; + gap: 10px 18px; + align-items: flex-start; + padding-top: 8px; +} + +.voter-row { + display: flex; + align-items: center; + gap: 0.5rem; + min-height: var(--dot-size); +} + + +.votes-board { + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; + padding-top: 4px; +} + +.vote-flow { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 0.7rem 1.6rem; + justify-items: center; +} + +.votes-board .vote-flow + .vote-flow { + margin-top: 1rem; +} + +.vote-flow--persistent { + gap: 0.7rem 1.6rem; +} + +.vote-flow--nonpersistent { + gap: 0.85rem 1.9rem; +} + +.vote-flow__row { + display: grid; + grid-template-columns: var(--seat-size) 24px minmax(120px, 1fr); + align-items: center; + justify-items: center; + gap: 0.35rem; + min-height: calc(var(--seat-size) + 8px); +} + +@media (max-width: 900px) { + .vote-flow { + grid-template-columns: repeat(3, minmax(0, 1fr)); } +} - .universe-canvas { - grid-auto-rows: 18px; +@media (max-width: 600px) { + .vote-flow { + grid-template-columns: repeat(1, minmax(0, 1fr)); } } -/* === Election identifiers row (EID + EB hash) === */ -.identifiers-row { +.vote-flow__row--nonpersistent .pool-dot { + box-shadow: none; +} + +.vote-seat-inline { + position: relative; + flex: 0 0 var(--seat-size); + max-width: var(--seat-size); +} + +.vote-node { display: flex; - justify-content: space-between; - align-items: baseline; - gap: 24px; - flex-wrap: nowrap; + flex-direction: column; + align-items: center; + gap: 4px; + min-width: var(--seat-size); + flex-shrink: 0; +} + +.vote-flow__arrow { + width: 32px; + display: inline-flex; + justify-content: center; } -.eid-block { +.vote-flow__row .vote-rect { + justify-self: stretch; +} + +.vote-rect.placeholder { + height: var(--vote-height); + border-radius: 4px; + border: 0; + background: transparent; + opacity: 0; +} + +.vote-arrow.placeholder { + visibility: hidden; +} + +.vote-flow__row .vote-rect.is-persistent { + background: #16a34a; + border-color: rgba(34, 197, 94, 0.5); +} + +.vote-flow__row .vote-rect.is-nonpersistent { + background: #2563eb; + border-color: rgba(59, 130, 246, 0.55); +} + +.vote-arrow { + position: relative; + width: 28px; + height: 2px; + background: var(--muted); flex: 0 0 auto; - /* content-sized (EID is short) */ - min-width: 0; } -.eb-block { - flex: 1 1 auto; - /* EB hash takes the remaining space */ - min-width: 0; - /* allow shrinking inside the flex row */ +.vote-arrow::after { + content: ""; + position: absolute; + right: -2px; + top: -4px; + border-left: 8px solid var(--muted); + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; } -.eid-block .value, -.eb-block .value { - white-space: nowrap; - overflow: visible; - /* do not trigger CSS ellipsis */ - text-overflow: clip; - /* no automatic end-ellipsis */ - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 13.5px; +.vote-rect { + min-width: 14px; + height: var(--vote-height); + border-radius: 4px; + background: #4a90e2; + border: 1px solid rgba(255, 255, 255, 0.1); + cursor: default; } -.text-wrap { - word-break: break-all; - overflow-wrap: anywhere; +.vote-rect.is-persistent { + background: #16a34a; + border-color: rgba(34, 197, 94, 0.5); } -.mono { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 13.5px; +.vote-rect.is-nonpersistent { + background: #2563eb; + border-color: rgba(59, 130, 246, 0.55); } -.nonpersistent-indent { - margin-left: 18px; - color: var(--orange); -} \ No newline at end of file +.aggregation-canvas { + min-height: 220px; +} + +.aggregation-row { + display: flex; + align-items: center; + gap: 16px; + justify-content: flex-start; + min-height: 220px; +} + +.agg-votes { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: flex-start; + justify-content: flex-start; + flex: 1 1 auto; +} + +.big-arrow { + font-size: 28px; + line-height: 1; + color: var(--muted); + user-select: none; +} + +.certificate-rect { + min-width: 140px; + height: 80px; + border-radius: 10px; + background: #7c3aed; + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + cursor: default; +} + +@media (max-width: 720px) { + .app-header { + margin-top: 16px; + } + + .certificate-rect { + min-width: 120px; + height: 70px; + } + + .aggregation-row { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/crypto-benchmarks.rs/demo/ui/templates/index.html b/crypto-benchmarks.rs/demo/ui/templates/index.html index d8eb2f114..eb4e1b3f7 100644 --- a/crypto-benchmarks.rs/demo/ui/templates/index.html +++ b/crypto-benchmarks.rs/demo/ui/templates/index.html @@ -10,11 +10,11 @@ -
    -

    Leios Voting Demo

    -
    +
    +

    Leios Voting Demo

    + - +
    @@ -25,17 +25,14 @@

    Leios Voting Demo

    Set of SPOs

    -
    -
    - Total pools: -
    -
    - Persistent: - +
    +
    + Total pools: +
    -
    - Non-persistent: - +
    + Total stake: +
    @@ -51,52 +48,24 @@

    Set of SPOs

    Backend error:
    - -
    -

    Election

    -
    -
    - EID: - -
    -
    - EB hash: - -
    -
    - -
    -

    Committee

    -

    - Persistent (Fait Accompli) and Non-persistent (Local Sortition) -

    +

    Committee Selection

    +

    Persistent seats selected for this epoch via Fait Accompli

    -
    -
    - Total committee seats: +
    +
    + Total seats: +
    -
    - Persistent: - +
    + Persistent seats: +
    -
    - Non-persistent: - +
    + Non-persistent seats: +
    + +
    +

    Election

    +
    +
    + EID: + +
    +
    + EB hash: + +
    +
    +
    -

    Voters

    +

    Voting

    -
    -
    - Total voters: +
    +
    + Quorum: +
    -
    - Persistent: - +
    + Total voters: +
    -
    - Non-persistent: - +
    + Persistent: +
    -
    - in committee: - -
    -
    - outside committee: - +
    + Non-persistent: +
    @@ -141,9 +121,34 @@

    Voters

    + +
    +

    Aggregation

    +
    +
    +
    + Total votes size: + +
    +
    + Certificate size: + +
    +
    + Compression gain: + +
    +
    +
    + +
    +
    +
    + - \ No newline at end of file + From 51ff6d30de9233e4dc5a3d04264978d011e1992f Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Tue, 21 Oct 2025 11:35:00 +0900 Subject: [PATCH 08/16] make UI shows all steps of Voting --- crypto-benchmarks.rs/demo/ui/static/app.js | 158 +++++++++++++----- .../demo/ui/static/styles.css | 27 ++- .../demo/ui/templates/index.html | 8 +- 3 files changed, 140 insertions(+), 53 deletions(-) diff --git a/crypto-benchmarks.rs/demo/ui/static/app.js b/crypto-benchmarks.rs/demo/ui/static/app.js index f470eb8fb..0169f5689 100644 --- a/crypto-benchmarks.rs/demo/ui/static/app.js +++ b/crypto-benchmarks.rs/demo/ui/static/app.js @@ -51,6 +51,7 @@ const FIXED_GRID_COLUMNS = 20; // circles per row for Voters alignment const UNIVERSE_COLUMNS = 30; // circles per row for Universe panel const COMMITTEE_COLUMNS = 20; // seat boxes per row for Committee const COMMITTEE_ROW_HEIGHT = "calc(var(--seat-size) + 10px)"; +const VOTE_RECT_HEIGHT = 36; // ---------- tooltip helpers ---------- function positionTooltip(target, tooltip) { @@ -778,62 +779,80 @@ function renderAggregationFromDemo(demo) { const container = document.getElementById('aggregation_canvas'); if (!container) return; - const { persistentPoolIds, nonPersistentPoolIds } = buildVoterPools(demo); + const { + persistentEntries, + nonPersistentEntries, + nonPersistentSeatCount + } = buildVoterPools(demo); - const persistentBytes = firstFinite( - demo?.aggregation?.persistent_bytes, - demo?.aggregation?.persistent_vote_bytes, - demo?.metrics?.persistent_bytes, - demo?.metrics?.persistent_vote_bytes, - persistentPoolIds.length * PERSISTENT_VOTE_BYTES - ); - const nonPersistentBytes = firstFinite( - demo?.aggregation?.nonpersistent_bytes, - demo?.aggregation?.nonpersistent_vote_bytes, - demo?.aggregation?.non_persistent_bytes, - demo?.metrics?.nonpersistent_bytes, - demo?.metrics?.nonpersistent_vote_bytes, - nonPersistentPoolIds.length * NONPERSISTENT_VOTE_BYTES - ); + const poolIdToUniverseIndex = buildPoolIdToUniverseIndex(demo); + const committeeMembers = Array.isArray(demo?.committee?.seats) + ? demo.committee.seats + : (Array.isArray(demo?.committee) ? demo.committee : []); + + const poolIdToSeat = new Map(); + const positionToSeat = new Map(); + for (const seat of committeeMembers) { + if (!seat) continue; + if (seat.pool_id) poolIdToSeat.set(seat.pool_id, seat); + if (seat.position !== undefined) positionToSeat.set(Number(seat.position), seat); + } + + const persistentSorted = [...persistentEntries].sort((a, b) => { + const seatA = Number(a.seatId); + const seatB = Number(b.seatId); + const idxA = Number.isFinite(seatA) ? seatA : (poolIdToUniverseIndex.get(a.poolId) ?? 99999); + const idxB = Number.isFinite(seatB) ? seatB : (poolIdToUniverseIndex.get(b.poolId) ?? 99999); + return idxA - idxB; + }); + const persistentVotes = persistentSorted.filter(entry => entry.hasVote); + + const nonPersistentSorted = [...nonPersistentEntries].sort((a, b) => { + const idxA = poolIdToUniverseIndex.get(a.poolId) ?? 99999; + const idxB = poolIdToUniverseIndex.get(b.poolId) ?? 99999; + return idxA - idxB; + }); + const targetNonPersistentBase = nonPersistentSeatCount + ? Math.min(Math.round(nonPersistentSeatCount * 0.75), nonPersistentSorted.length) + : Math.min(Math.round(nonPersistentSorted.length * 0.75), nonPersistentSorted.length); + const targetNonPersistentCount = Math.max(targetNonPersistentBase, 0); + const displayedNonPersistent = nonPersistentSorted.slice(0, targetNonPersistentCount); + + const persistentBytes = persistentVotes.length * PERSISTENT_VOTE_BYTES; + const nonPersistentBytes = displayedNonPersistent.length * NONPERSISTENT_VOTE_BYTES; const totalVotesBytes = persistentBytes + nonPersistentBytes; - const certBytes = firstFinite( + const certBytesRaw = firstFinite( demo?.certificate?.cert_bytes, demo?.certificate?.certificate_bytes, demo?.certificate?.bytes, demo?.aggregation?.certificate_bytes ); + const certBytes = Number(certBytesRaw); const votesEl = document.getElementById('agg_votes_size'); if (votesEl) { const pieces = [ `${formatNumber(totalVotesBytes)} B`, - `Persistent: ${formatNumber(persistentBytes)} B`, - `Non-persistent: ${formatNumber(nonPersistentBytes)} B` + ` Persistent: ${formatNumber(persistentBytes)} B`, + ` Non-persistent: ${formatNumber(nonPersistentBytes)} B` ]; votesEl.innerHTML = pieces.join('
    '); } - setText('agg_cert_size', certBytes !== null ? formatBytes(certBytes) : '—'); + setText('agg_cert_size', certBytes && isFinite(certBytes) ? formatBytes(certBytes) : '—'); const gainEl = document.getElementById('agg_gain'); if (gainEl) { - let ratioVal = firstFinite( - demo?.certificate?.compression_ratio, - demo?.compression_ratio, - demo?.aggregation?.compression_ratio, - demo?.metrics?.compression_ratio, - null - ); - if (!ratioVal && totalVotesBytes > 0 && Number(certBytes) > 0) { - ratioVal = totalVotesBytes / Number(certBytes); - } + const ratioVal = totalVotesBytes > 0 && Number(certBytes) > 0 + ? totalVotesBytes / Number(certBytes) + : null; gainEl.textContent = ratioVal && isFinite(ratioVal) ? `${ratioVal.toFixed(2)}×` : '—'; } container.classList.add('aggregation-row'); container.innerHTML = ''; - if (persistentPoolIds.length + nonPersistentPoolIds.length === 0) { + if (persistentVotes.length + displayedNonPersistent.length === 0) { container.appendChild(createEmptyState("No votes recorded")); return; } @@ -841,17 +860,31 @@ function renderAggregationFromDemo(demo) { const votesGrid = document.createElement('div'); votesGrid.className = 'agg-votes'; - const addVoteRect = (poolId, bytes, isPersistent) => { + const addVoteRect = (entry, isPersistent) => { const vote = document.createElement('div'); vote.className = 'vote-rect'; vote.classList.add(isPersistent ? 'is-persistent' : 'is-nonpersistent'); + const bytes = isPersistent ? PERSISTENT_VOTE_BYTES : NONPERSISTENT_VOTE_BYTES; vote.style.width = `${voteWidthPx(bytes)}px`; - attachTooltip(vote, `Vote
    From: ${poolId}
    Size: ${bytes} bytes`); + const tooltip = []; + if (isPersistent) { + const seat = entry.seat ?? poolIdToSeat.get(entry.poolId) ?? positionToSeat.get(entry.seatId); + const seatNum = entry.seatId ?? seat?.position ?? seat?.index ?? "—"; + tooltip.push(`Seat #${seatNum}`); + tooltip.push(`Size: ${formatBytes(bytes)}`); + if (entry.signature) tooltip.push(`Signature: ${entry.signature}`); + } else { + if (entry.poolId) tooltip.push(`Pool ID: ${entry.poolId}`); + if (entry.eligibility) tooltip.push(`Eligibility prefix: ${entry.eligibility}`); + if (entry.signature) tooltip.push(`Signature: ${entry.signature}`); + tooltip.push(`Size: ${formatBytes(bytes)}`); + } + attachTooltip(vote, tooltip.join('
    ')); votesGrid.appendChild(vote); }; - persistentPoolIds.forEach(pid => addVoteRect(pid, PERSISTENT_VOTE_BYTES, true)); - nonPersistentPoolIds.forEach(pid => addVoteRect(pid, NONPERSISTENT_VOTE_BYTES, false)); + persistentVotes.forEach(entry => addVoteRect(entry, true)); + displayedNonPersistent.forEach(entry => addVoteRect(entry, false)); const arrow = document.createElement('span'); arrow.className = 'big-arrow'; @@ -861,12 +894,55 @@ function renderAggregationFromDemo(demo) { cert.className = 'certificate-rect'; cert.textContent = 'Certificate'; - if (totalVotesBytes > 0 && Number(certBytes) > 0) { - const minWidth = 140; - const maxWidth = 420; - const scaled = Math.min(maxWidth, Math.max(minWidth, (Number(certBytes) / totalVotesBytes) * 600)); - cert.style.width = `${Math.round(scaled)}px`; - cert.style.minWidth = `${Math.round(scaled)}px`; + if (totalVotesBytes > 0 && isFinite(certBytes) && certBytes > 0) { + const persistentWidth = persistentVotes.length * voteWidthPx(PERSISTENT_VOTE_BYTES); + const nonPersistentWidth = displayedNonPersistent.length * voteWidthPx(NONPERSISTENT_VOTE_BYTES); + const totalVoteWidth = persistentWidth + nonPersistentWidth; + const voteArea = totalVoteWidth * VOTE_RECT_HEIGHT; + const certificateArea = voteArea * (certBytes / totalVotesBytes); + if (certificateArea > 0 && Number.isFinite(certificateArea)) { + const minWidth = 12; + const minHeight = VOTE_RECT_HEIGHT; + const maxWidth = totalVoteWidth > 0 ? totalVoteWidth : null; + + let width = Math.sqrt(certificateArea); + if (maxWidth && width > maxWidth) { + width = maxWidth; + } + width = Math.max(minWidth, width); + + let height = certificateArea / width; + + if (height < minHeight) { + height = minHeight; + width = certificateArea / height; + if (maxWidth && width > maxWidth) { + width = maxWidth; + height = certificateArea / width; + } + } + + if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) { + width = Math.max(minWidth, Math.sqrt(Math.max(certificateArea, 1))); + if (maxWidth && width > maxWidth) { + width = maxWidth; + } + height = certificateArea / width; + } + + const widthPx = Math.max(1, width); + const heightPx = Math.max(1, height); + const widthStr = `${widthPx.toFixed(1)}px`; + cert.style.width = widthStr; + cert.style.minWidth = widthStr; + cert.style.height = `${heightPx.toFixed(1)}px`; + } else { + cert.style.height = "110px"; + cert.style.width = "150px"; + } + } else { + cert.style.height = "110px"; + cert.style.width = "150px"; } const certificateTooltip = []; diff --git a/crypto-benchmarks.rs/demo/ui/static/styles.css b/crypto-benchmarks.rs/demo/ui/static/styles.css index 359eae038..43f5309f7 100644 --- a/crypto-benchmarks.rs/demo/ui/static/styles.css +++ b/crypto-benchmarks.rs/demo/ui/static/styles.css @@ -5,6 +5,7 @@ --muted: #9fb2c7; --green: #19c37d; --orange: #f59e0b; + --yellow: #facc15; --border: #1f2a35; --dot-size: 30px; --dot-gap: 6px; @@ -117,6 +118,11 @@ main { font-weight: 700; } +#aggregation .card-title, +#voters .card-title { + margin-bottom: 20px; +} + .card-subtitle { margin: -6px 0 14px; color: var(--muted); @@ -218,6 +224,10 @@ main { color: #2563eb; } +.text-yellow { + color: var(--yellow); +} + .count { font-weight: 700; } @@ -445,7 +455,7 @@ main { justify-items: center; } -.votes-board .vote-flow + .vote-flow { +.votes-board .vote-flow+.vote-flow { margin-top: 1rem; } @@ -579,12 +589,13 @@ main { } .agg-votes { - display: flex; - flex-wrap: wrap; - gap: 8px; - align-items: flex-start; - justify-content: flex-start; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(50px, 1fr)); + gap: 10px 6px; + align-items: center; + justify-items: start; flex: 1 1 auto; + max-width: 65%; } .big-arrow { @@ -595,8 +606,8 @@ main { } .certificate-rect { - min-width: 140px; - height: 80px; + min-width: 0; + height: 100px; border-radius: 10px; background: #7c3aed; color: #ffffff; diff --git a/crypto-benchmarks.rs/demo/ui/templates/index.html b/crypto-benchmarks.rs/demo/ui/templates/index.html index eb4e1b3f7..8cd74ad95 100644 --- a/crypto-benchmarks.rs/demo/ui/templates/index.html +++ b/crypto-benchmarks.rs/demo/ui/templates/index.html @@ -64,8 +64,8 @@

    Committee Selection

    - Non-persistent seats: - + Non-persistent seats: +
    - + -
    -

    Aggregation

    + + + + + From 4647c8675787477513d5a0648787ba9be6529f1e Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Thu, 23 Oct 2025 15:42:09 +0900 Subject: [PATCH 10/16] clean UI code --- crypto-benchmarks.rs/demo/ui/static/app.js | 7 ------- crypto-benchmarks.rs/demo/ui/static/styles.css | 8 -------- 2 files changed, 15 deletions(-) diff --git a/crypto-benchmarks.rs/demo/ui/static/app.js b/crypto-benchmarks.rs/demo/ui/static/app.js index 37857a150..decf61fe8 100644 --- a/crypto-benchmarks.rs/demo/ui/static/app.js +++ b/crypto-benchmarks.rs/demo/ui/static/app.js @@ -12,13 +12,6 @@ function setText(id, value) { if (el) el.textContent = value ?? "—"; } -function shortenHex(hex) { - if (typeof hex !== "string") return ""; - const h = hex.startsWith("0x") ? hex.slice(2) : hex; - if (h.length <= 12) return "0x" + h; - return "0x" + h.slice(0, 6) + "…" + h.slice(-4); -} - function formatNumber(value) { if (value === null || value === undefined || value === "") return "—"; const num = Number(value); diff --git a/crypto-benchmarks.rs/demo/ui/static/styles.css b/crypto-benchmarks.rs/demo/ui/static/styles.css index acdc46276..bf3bb4222 100644 --- a/crypto-benchmarks.rs/demo/ui/static/styles.css +++ b/crypto-benchmarks.rs/demo/ui/static/styles.css @@ -313,14 +313,6 @@ main { cursor: pointer; } -.verify-label { - font-weight: 600; - color: var(--muted); - margin-right: 0.35rem; - display: inline-flex; - align-items: center; -} - .verify-result { display: inline-flex; align-items: center; From dbcbbddd584de6eacc12c0887ee309303a60028b Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Thu, 23 Oct 2025 16:05:22 +0900 Subject: [PATCH 11/16] rename/clean scripts --- crypto-benchmarks.rs/demo/README.md | 32 +--- .../demo/scripts/40_make_certificate.sh | 2 - ...rt_demo_json.sh => 60_export_demo_json.sh} | 3 + .../demo/scripts/60_pretty_print_cert.sh | 88 --------- .../demo/scripts/70_run_one.sh | 19 +- crypto-benchmarks.rs/demo/scripts/80_sweep.sh | 97 ---------- .../demo/scripts/85_plot_sweep.sh | 172 ------------------ .../demo/scripts/pretty_cert.py | 107 ----------- 8 files changed, 8 insertions(+), 512 deletions(-) rename crypto-benchmarks.rs/demo/scripts/{25_export_demo_json.sh => 60_export_demo_json.sh} (99%) delete mode 100755 crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh delete mode 100755 crypto-benchmarks.rs/demo/scripts/80_sweep.sh delete mode 100755 crypto-benchmarks.rs/demo/scripts/85_plot_sweep.sh delete mode 100644 crypto-benchmarks.rs/demo/scripts/pretty_cert.py diff --git a/crypto-benchmarks.rs/demo/README.md b/crypto-benchmarks.rs/demo/README.md index 47d6aaaae..85b48290c 100644 --- a/crypto-benchmarks.rs/demo/README.md +++ b/crypto-benchmarks.rs/demo/README.md @@ -82,20 +82,12 @@ Verify the generated certificate: scripts/50_verify_certificate.sh -d "$RUN" ``` -#### 60_pretty_print_cert.sh - -Pretty-print key metrics and statistics of the certificate: - -```bash -scripts/60_pretty_print_cert.sh -d "$RUN" -``` - -#### 25_export_demo_json.sh +#### 60_export_demo_json.sh Export all relevant data (pools, committee, voters, and certificate summary) into a single `demo.json` file used by the visualization UI. ```bash -scripts/25_export_demo_json.sh -d "$RUN" +scripts/60_export_demo_json.sh -d "$RUN" ``` ### Run a Single End-to-End Demo @@ -115,26 +107,6 @@ This will: All files are placed in `demo/run100/`. -### Sweep Across Multiple N - -```bash -scripts/80_sweep.sh -d sweep1 -f 1.0 --ns "50 100 200 500 1000 2000 3000" -``` - -This will run the full pipeline for multiple voter sizes (`N`) and write a CSV of results: - -``` -demo/sweep1/sweep_results.csv -``` - -### Plot Sweep Results - -```bash -scripts/85_plot_sweep.sh -d sweep1 --open -``` - -This will generate a plot `gain_vs_N.png` showing compression ratio vs. number of voters. -Use `--open` to automatically open the PNG. ## Notes diff --git a/crypto-benchmarks.rs/demo/scripts/40_make_certificate.sh b/crypto-benchmarks.rs/demo/scripts/40_make_certificate.sh index 835119875..c7a0f4d14 100755 --- a/crypto-benchmarks.rs/demo/scripts/40_make_certificate.sh +++ b/crypto-benchmarks.rs/demo/scripts/40_make_certificate.sh @@ -46,7 +46,5 @@ popd >/dev/null # ---- summary output ---- -echo -echo "== Certificate creation summary ==" BYTES_CERT="$(wc -c < "${RUN_DIR}/certificate.cbor" 2>/dev/null || echo "?")" echo "Certificate size (bytes): ${BYTES_CERT}" diff --git a/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh b/crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh similarity index 99% rename from crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh rename to crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh index 8703c33fc..6c38b1585 100755 --- a/crypto-benchmarks.rs/demo/scripts/25_export_demo_json.sh +++ b/crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh @@ -17,9 +17,12 @@ fi # Resolve the run directory (handle paths with spaces) RUN_DIR="$(cd "$DIR_SCRIPT/.." && cd "$RUN_DIR" && pwd)" +echo "== [60_export_demo_json] DIR=${RUN_DIR} ==" + PY="${VIRTUAL_ENV:+$VIRTUAL_ENV/bin/python}" PY="${PY:-python3}" + pushd "$RUN_DIR" >/dev/null "$PY" - <<'PY' import os, json, collections diff --git a/crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh b/crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh deleted file mode 100755 index 70cdd6a29..000000000 --- a/crypto-benchmarks.rs/demo/scripts/60_pretty_print_cert.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash -# 60_pretty_print_cert.sh -# Pretty-print certificate & votes using a Python helper. -# Usage: -# ./60_pretty_print_cert.sh -d RUN_DIR [--all-voters] [--max-ids N] -set -euo pipefail - -usage() { - cat <<'EOF' -Usage: 60_pretty_print_cert.sh -d RUN_DIR [--all-voters] [--max-ids N] - -Options: - -d, --dir RUN_DIR Run directory containing certificate.cbor (and votes.cbor) - --all-voters Include full lists of persistent and non-persistent voters - --max-ids N When not using --all-voters, include at most N voter IDs (default 5) -EOF - exit 2 -} - -# Defaults -DIR="" -ALL_VOTERS=0 -MAX_IDS="5" - -# Single portable arg parser (handles both short and long flags) -while [[ $# -gt 0 ]]; do - case "$1" in - -d|--dir) - [[ $# -ge 2 ]] || usage - DIR="$2"; shift 2 ;; - --all-voters) - ALL_VOTERS=1; shift ;; - --max-ids) - [[ $# -ge 2 ]] || usage - MAX_IDS="$2"; shift 2 ;; - --) - shift; break ;; - -*) - usage ;; - *) - # ignore stray positional args - shift ;; - esac -done - -[[ -z "${DIR}" ]] && usage - -# Resolve absolute paths -DIR_ABS="$(cd "${DIR}" 2>/dev/null && pwd || true)" -if [[ -z "${DIR_ABS}" || ! -d "${DIR_ABS}" ]]; then - echo "Error: RUN_DIR not found: ${DIR}" >&2 - exit 1 -fi - -echo "== [60_pretty_print_cert] DIR=${DIR_ABS} ==" - -CERT="${DIR_ABS}/certificate.cbor" -VOTES="${DIR_ABS}/votes.cbor" - -if [[ ! -f "${CERT}" ]]; then - printf '{ "error": "certificate not found: %s" }\n' "${CERT}" - exit 1 -fi - -# Prefer venv python in demo/.venv if present, then $PYTHON, then python3, then python -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -DEMO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" -if [[ -x "${DEMO_ROOT}/.venv/bin/python" ]]; then - PY_BIN="${DEMO_ROOT}/.venv/bin/python" -elif [[ -n "${PYTHON:-}" && -x "${PYTHON}" ]]; then - PY_BIN="${PYTHON}" -elif command -v python3 >/dev/null 2>&1; then - PY_BIN="$(command -v python3)" -elif command -v python >/dev/null 2>&1; then - PY_BIN="$(command -v python)" -else - echo '{ "error": "No Python interpreter found. Install python3 or create .venv." }' - exit 2 -fi - -# Build args for the Python helper -PY_ARGS=( "${CERT}" ) -[[ -f "${VOTES}" ]] && PY_ARGS+=( "${VOTES}" ) -PY_ARGS+=( --max-ids "${MAX_IDS}" ) -[[ "${ALL_VOTERS}" -eq 1 ]] && PY_ARGS+=( --all-voters ) - -# Run the Python helper and emit JSON to both console and file -"${PY_BIN}" "${SCRIPT_DIR}/pretty_cert.py" "${PY_ARGS[@]}" | tee "${DIR_ABS}/certificate.pretty.json" \ No newline at end of file diff --git a/crypto-benchmarks.rs/demo/scripts/70_run_one.sh b/crypto-benchmarks.rs/demo/scripts/70_run_one.sh index c055f194b..36587a864 100755 --- a/crypto-benchmarks.rs/demo/scripts/70_run_one.sh +++ b/crypto-benchmarks.rs/demo/scripts/70_run_one.sh @@ -17,7 +17,6 @@ set -euo pipefail # 30_cast_votes.sh # 40_make_certificate.sh # 50_verify_certificate.sh -# 60_pretty_print_cert.sh # - Each sub-script prints its own status; this wrapper adds a compact summary. DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -105,20 +104,8 @@ fi end_verify="$(now_ms)" verify_ms=$(( end_verify - start_verify )) -# ---- 60: show sizes + summary JSON ---- -"$DIR_SCRIPT/60_pretty_print_cert.sh" -d "$RUN_DIR" - -# ---- 25: generate JSON for UI ---- -"$DIR_SCRIPT/25_export_demo_json.sh" -d "$RUN_DIR" - -# ---- compact tail summary ---- -PRETTY_JSON="${RUN_DIR_ABS}/certificate.pretty.json" -if [[ -f "$PRETTY_JSON" ]] && command -v jq >/dev/null 2>&1; then - echo "-- Summary --" - jq '{eid, eb, persistent_voters_count, nonpersistent_voters_count, votes_bytes, certificate_bytes, compression_ratio}' "$PRETTY_JSON" -else - echo "(Tip) Install jq for a compact summary: brew install jq" -fi +# ---- 60: generate JSON for UI ---- +"$DIR_SCRIPT/60_export_demo_json.sh" -d "$RUN_DIR" timings_path="${RUN_DIR_ABS}/timings.json" cat > "$timings_path" </run and calls scripts/70_run_one.sh for each N -# - Appends results to demo//sweep_results.csv with columns: -# N,votes_bytes,certificate_bytes,compression_ratio,nonpersistent_voters_count,persistent_voters_count - -DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DEMO_ROOT="$(cd "$DIR_SCRIPT/.." && pwd)" - -BASE_DIR="" -FRACTION="" -NS_LIST="" -POOLS="" -TOTAL_STAKE="" - -usage() { - cat <&2; usage; exit 2;; - esac -done - -if [[ -z "$BASE_DIR" || -z "$FRACTION" || -z "$NS_LIST" ]]; then - echo "Error: need -d DIR, -f FRACTION, and --ns \"N1 N2 ...\"" >&2 - usage; exit 2 -fi - -BASE_ABS="$(cd "$DEMO_ROOT"; mkdir -p "$BASE_DIR"; cd "$BASE_DIR" && pwd)" -RESULTS_CSV="${BASE_ABS}/sweep_results.csv" - -# header -if [[ ! -f "$RESULTS_CSV" ]]; then - echo "N,votes_bytes,certificate_bytes,compression_ratio,nonpersistent_voters_count,persistent_voters_count" > "$RESULTS_CSV" -fi - -echo "== [80_sweep] BASE=${BASE_ABS} FRACTION=${FRACTION} NS=(${NS_LIST}) POOLS=${POOLS:-default} TOTAL_STAKE=${TOTAL_STAKE:-default} ==" - -for N in ${NS_LIST}; do - RUN_DIR_REL="${BASE_DIR}/run${N}" - echo "-- N=${N} -> ${RUN_DIR_REL}" - - # Build command for a single run - ONE_CMD=("$DIR_SCRIPT/70_run_one.sh" -d "$RUN_DIR_REL" -n "$N" -f "$FRACTION") - [[ -n "$POOLS" ]] && ONE_CMD+=( -p "$POOLS" ) - [[ -n "$TOTAL_STAKE" ]] && ONE_CMD+=( -t "$TOTAL_STAKE" ) - - # Execute - "${ONE_CMD[@]}" - - PRETTY_JSON="${DEMO_ROOT}/${RUN_DIR_REL}/certificate.pretty.json" - if [[ -f "$PRETTY_JSON" ]]; then - # Extract fields with jq; fall back to Python if jq is not available - if command -v jq >/dev/null 2>&1; then - VOTES=$(jq -r '.votes_bytes' "$PRETTY_JSON") - CERT=$(jq -r '.certificate_bytes' "$PRETTY_JSON") - RATIO=$(jq -r '.compression_ratio' "$PRETTY_JSON") - NP=$(jq -r '.nonpersistent_voters_count' "$PRETTY_JSON") - PV=$(jq -r '.persistent_voters_count' "$PRETTY_JSON") - else - # Python fallback (no jq). Use a simple one-liner and parse. - PY_OUT=$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("votes_bytes"), d.get("certificate_bytes"), d.get("compression_ratio"), d.get("nonpersistent_voters_count"), d.get("persistent_voters_count"))' "$PRETTY_JSON") - read -r VOTES CERT RATIO NP PV <<< "$PY_OUT" - fi - echo "${N},${VOTES},${CERT},${RATIO},${NP},${PV}" >> "$RESULTS_CSV" - else - echo "Warning: ${PRETTY_JSON} not found; skipping CSV append" >&2 - fi - -done - -echo "Sweep complete. Results: ${RESULTS_CSV}" diff --git a/crypto-benchmarks.rs/demo/scripts/85_plot_sweep.sh b/crypto-benchmarks.rs/demo/scripts/85_plot_sweep.sh deleted file mode 100755 index 27285d169..000000000 --- a/crypto-benchmarks.rs/demo/scripts/85_plot_sweep.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -# Plot compression ratios from a sweep run. -# Usage: -# scripts/85_plot_sweep.sh -d DIR [--open] -# Where: -# DIR is the sweep directory under demo/ that contains sweep_results.csv -# -# This script prefers demo/scripts/90_plot_sweep.py if present. -# If not, it falls back to a built-in Python snippet. - -DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DEMO_ROOT="$(cd "$DIR_SCRIPT/.." && pwd)" - -BASE_DIR="" -DO_OPEN=0 - -usage() { - cat <&2; usage; exit 2;; - esac -done - -[[ -z "$BASE_DIR" ]] && { echo "Error: need -d DIR"; usage; exit 2; } - -SWEEP_DIR="${DEMO_ROOT}/${BASE_DIR}" -CSV_PATH="${SWEEP_DIR}/sweep_results.csv" -PNG_PATH="${SWEEP_DIR}/summary_vs_N.png" - -if [[ ! -f "$CSV_PATH" ]]; then - echo "sweep_results.csv not found at: $CSV_PATH" - echo "Run scripts/80_sweep.sh first." - exit 1 -fi - -# Choose Python -PY_BIN="${DEMO_ROOT}/.venv/bin/python" -[[ -x "$PY_BIN" ]] || PY_BIN="python3" - -# Ensure matplotlib (install into venv if possible) -if ! "$PY_BIN" -c "import matplotlib" >/dev/null 2>&1; then - echo "matplotlib not found; attempting install..." - if [[ -x "${DEMO_ROOT}/.venv/bin/pip" ]]; then - "${DEMO_ROOT}/.venv/bin/pip" install --upgrade pip matplotlib >/dev/null - else - "$PY_BIN" -m pip install --user --upgrade pip matplotlib >/dev/null || true - fi -fi - -# Always use the inline fallback Python to ensure consistent styling/labels. -DEMO_ROOT_OVERRIDE="${DEMO_ROOT}" \ -"$PY_BIN" - "${BASE_DIR}" <<'PYFALLBACK' -import sys, csv, os -import matplotlib.pyplot as plt - -# argv[1] = base dir under demo/ (e.g., "sweep1") -if len(sys.argv) < 2: - sys.exit("Usage: fallback: python - ") -base_dir = sys.argv[1] - -# We rely on DEMO_ROOT_OVERRIDE passed from the shell -demo_root = os.environ.get('DEMO_ROOT_OVERRIDE') -if not demo_root: - raise SystemExit("DEMO_ROOT_OVERRIDE not set; cannot resolve demo path.") - -base = os.path.join(demo_root, base_dir) -csv_path = os.path.join(base, 'sweep_results.csv') -png_path = os.path.join(base, 'summary_vs_N.png') - -if not os.path.exists(csv_path): - raise SystemExit(f"sweep_results.csv not found at: {csv_path}") - -N = [] -ratio = [] -pv = [] # persistent_voters_count -npv = [] # nonpersistent_voters_count - -with open(csv_path, newline='') as f: - rdr = csv.DictReader(f) - for row in rdr: - try: - N.append(int(row['N'])) - ratio.append(float(row.get('compression_ratio', 'nan'))) - pv.append(int(row.get('persistent_voters_count', 0))) - npv.append(int(row.get('nonpersistent_voters_count', 0))) - except Exception: - pass - -pairs = sorted(zip(N, ratio, pv, npv), key=lambda x: x[0]) -if pairs: - N, ratio, pv, npv = zip(*pairs) -else: - N, ratio, pv, npv = [], [], [], [] - -# ---- Build clean x-ticks to avoid clutter and hide 64 explicitly ---- -# Strategy: -# * Always show the first and last N -# * Prefer showing {32, 128, 256, 512, 1024, 2056, 3000} if present (skip 64) -# * Fill remaining slots up to ~8 labels with evenly spaced indices, but -# drop label == 64 if it slips in. - -def build_xticks_labels(values): - if not values: - return [], [] - idxs = set([0, len(values) - 1]) - preferred = [32, 128, 256, 512, 1024, 2056, 3000] - pos = {v: i for i, v in enumerate(values)} - for v in preferred: - if v in pos: - idxs.add(pos[v]) - target = min(8, len(values)) - if len(idxs) < target: - k = target - for j in range(k): - idx = int(round(j * (len(values) - 1) / max(1, (k - 1)))) - if values[idx] == 64: - continue - idxs.add(idx) - if len(idxs) >= target: - break - idxs = sorted(idxs) - ticks = [values[i] for i in idxs if values[i] != 64] - labels = [str(v) for v in ticks] - return ticks, labels - -xticks, xlabels = build_xticks_labels(list(N)) - -fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(7.5, 7.5), sharex=True) - -# Top subplot: Compression ratio vs Committee size (use a vivid green) -ax1.plot(N, ratio, marker='^', linestyle='--', color='#00aa00', label='Compression ratio (×)') -ax1.set_title('Compression Gain vs Committee Size') -ax1.set_ylabel('Compression ratio (×)') -ax1.grid(True, which='both', linestyle=':') -# Ensure the TOP subplot also shows x tick labels -ax1.set_xticks(xticks) -ax1.set_xticklabels(xlabels, rotation=45, ha='right', fontsize=9) -ax1.tick_params(axis='x', which='both', labelbottom=True) - -# Bottom subplot: Persistent voters vs Committee size -ax2.plot(N, N, color='gray', linestyle='--', label='Committee size (y=x)') -ax2.plot(N, pv, marker='o', label='Persistent voters') -ax2.set_title('Persistent Voters vs Committee Size') -ax2.set_xlabel('Committee size (N)') -ax2.set_ylabel('Count') -ax2.grid(True, which='both', linestyle=':') -ax2.set_xticks(xticks) -ax2.set_xticklabels(xlabels, rotation=45, ha='right', fontsize=9) -ax2.legend(loc='best') - -plt.tight_layout() -plt.subplots_adjust(bottom=0.14) -plt.savefig(png_path, dpi=160) -print(f"Wrote {png_path}") -PYFALLBACK - -echo "Plot: ${PNG_PATH}" -if [[ $DO_OPEN -eq 1 ]] && command -v open >/dev/null 2>&1; then - open "${PNG_PATH}" || true -fi diff --git a/crypto-benchmarks.rs/demo/scripts/pretty_cert.py b/crypto-benchmarks.rs/demo/scripts/pretty_cert.py deleted file mode 100644 index 04cce43d1..000000000 --- a/crypto-benchmarks.rs/demo/scripts/pretty_cert.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python3 -import sys, os, json, argparse - -try: - import cbor2 -except Exception as e: - print(json.dumps({"error": f"cbor2 import failed: {e}"}, - indent=2)) - sys.exit(1) - -def hex8(b): - if isinstance(b, (bytes, bytearray)): - return b[:8].hex() - return None - -def hex_full(b): - if isinstance(b, (bytes, bytearray)): - return "0x" + b.hex() - return None - -def load_cbor(path): - with open(path, 'rb') as f: - return cbor2.load(f) - -def main(): - ap = argparse.ArgumentParser( - description="Pretty print certificate (and optional votes) as JSON") - ap.add_argument("certificate", help="Path to certificate.cbor") - ap.add_argument("votes", nargs="?", default=None, - help="Optional path to votes.cbor") - ap.add_argument("--all-voters", action="store_true", - help="Include full lists of persistent and non-persistent voters") - ap.add_argument("--max-ids", type=int, default=5, - help="When not using --all-voters, include at most N voter IDs (default 5)") - args = ap.parse_args() - - cert_path = args.certificate - votes_path = args.votes - - if not os.path.isfile(cert_path): - print(json.dumps({"error": f"certificate not found: {cert_path}"}, indent=2)) - sys.exit(1) - - c = load_cbor(cert_path) - - # Handle schema variants - pv = c.get("persistent_voters") or c.get("persistent") or c.get("pv") - npv = c.get("nonpersistent_voters") or c.get("nonpersistent") or c.get("npv") - - # Normalize PV list - if isinstance(pv, (list, tuple)): - pv_list = list(pv) - elif isinstance(pv, dict): - # (unlikely) but normalize to keys - pv_list = list(pv.keys()) - else: - pv_list = [] - - # Normalize NPV list (keys, since values are sigs) - if isinstance(npv, dict): - npv_list = list(npv.keys()) - elif isinstance(npv, (list, tuple)): - npv_list = list(npv) - else: - npv_list = [] - - pv_count = len(pv_list) - npv_count = len(npv_list) - - # Sizes for compression ratio - votes_bytes = os.path.getsize(votes_path) if (votes_path and os.path.isfile(votes_path)) else None - cert_bytes = os.path.getsize(cert_path) - compression_ratio = None - if votes_bytes and cert_bytes > 0: - compression_ratio = round(votes_bytes / cert_bytes, 3) - - out = { - "sigma_tilde_eid_prefix": hex8(c.get("sigma_tilde_eid") or c.get("agg_sig_eid")), - "sigma_tilde_m_prefix": hex8(c.get("sigma_tilde_m") or c.get("agg_sig_m")), - "eid": hex_full(c.get("eid")), - "eb": hex_full(c.get("eb")), - "persistent_voters_count": pv_count, - "nonpersistent_voters_count": npv_count, - } - - # Samples vs full lists - if args.all_voters: - out["persistent_voters"] = pv_list - out["nonpersistent_voters"] = npv_list - else: - k = max(0, args.max_ids) - if k > 0: - # Include a sample of both lists (consistent ordering shown by tool) - out["persistent_voters_sample"] = pv_list[:k] - out["nonpersistent_voters_sample"] = npv_list[:k] - - if votes_bytes is not None: - out["votes_bytes"] = votes_bytes - if cert_bytes is not None: - out["certificate_bytes"] = cert_bytes - if compression_ratio is not None: - out["compression_ratio"] = compression_ratio - - print(json.dumps(out, indent=2)) - -if __name__ == "__main__": - main() \ No newline at end of file From 7b7a08d182d278c7634b71be0db8bc4a0a42f3f4 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Thu, 23 Oct 2025 16:05:22 +0900 Subject: [PATCH 12/16] clean server.py --- crypto-benchmarks.rs/demo/ui/server.py | 103 +++++-------------------- 1 file changed, 21 insertions(+), 82 deletions(-) diff --git a/crypto-benchmarks.rs/demo/ui/server.py b/crypto-benchmarks.rs/demo/ui/server.py index 8cd0e445e..8102fa3db 100644 --- a/crypto-benchmarks.rs/demo/ui/server.py +++ b/crypto-benchmarks.rs/demo/ui/server.py @@ -1,53 +1,28 @@ -from flask import Flask, send_from_directory, jsonify, render_template, request, abort, redirect, url_for -import os, json, subprocess +from flask import Flask, send_from_directory, jsonify, render_template, abort, redirect, url_for +import json +import subprocess +from pathlib import Path + import cbor2 -app = Flask(__name__, static_folder="static", template_folder="templates") -ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -def run_dir_path(run): - return os.path.join(ROOT, run) +app = Flask(__name__, static_folder="static", template_folder="templates") +ROOT = Path(__file__).resolve().parent.parent -def pretty_json_path(run): - return os.path.join(run_dir_path(run), "certificate.pretty.json") +def run_dir_path(run: str) -> Path: + return ROOT / run -def regenerate_pretty(run: str, all_voters: bool) -> None: - """ - Re-generate certificate.pretty.json by invoking our pretty script. - We call the Python module directly to avoid shell pitfalls. - """ - rdir = run_dir_path(run) - cert = os.path.join(rdir, "certificate.cbor") - votes = os.path.join(rdir, "votes.cbor") - script = os.path.join(ROOT, "scripts", "pretty_cert.py") - if not os.path.isfile(script): - abort(500, f"pretty_cert.py not found at {script}") - if not os.path.isfile(cert): - abort(404, f"certificate.cbor not found in {rdir}") - - args = [ "python3", script, cert, votes ] - if all_voters: - args.append("--all-voters") - - # Write to certificate.pretty.json - out_path = pretty_json_path(run) - try: - out = subprocess.check_output(args, cwd=ROOT, text=True) - with open(out_path, "w") as f: - f.write(out) - except subprocess.CalledProcessError as e: - abort(500, f"pretty_cert.py failed: {e.output or e}") @app.route("/committee/") def committee(run): rdir = run_dir_path(run) - script = os.path.join(ROOT, "scripts", "extract_committee.py") - if not os.path.isfile(script): + script = ROOT / "scripts" / "extract_committee.py" + if not script.is_file(): abort(500, f"extract_committee.py not found at {script}") try: - out = subprocess.check_output(["python3", script, rdir], cwd=ROOT, text=True) + out = subprocess.check_output(["python3", str(script), str(rdir)], cwd=ROOT, text=True) data = json.loads(out) return jsonify(data) except subprocess.CalledProcessError as e: @@ -58,12 +33,12 @@ def committee(run): def registry(run): """Return pool list + stakes (best-effort).""" d = run_dir_path(run) - stake_path = os.path.join(d, "stake.cbor") + stake_path = d / "stake.cbor" pools = [] total_stake = 0 - if os.path.isfile(stake_path): - with open(stake_path, "rb") as f: + if stake_path.is_file(): + with stake_path.open("rb") as f: stake = cbor2.load(f) # stake is likely a map {poolId(bytes|hex): int stake} if isinstance(stake, dict): @@ -83,56 +58,20 @@ def registry(run): def demo_for_run(run): """Serve demo.json from the given run directory.""" run_dir = run_dir_path(run) - demo_path = os.path.join(run_dir, "demo.json") - if os.path.isfile(demo_path): - return send_from_directory(run_dir, "demo.json") + demo_path = run_dir / "demo.json" + if demo_path.is_file(): + return send_from_directory(str(run_dir), "demo.json") abort(404, f"demo.json not found in {run_dir}") @app.route("/demo//") def demo_asset(run, filename): """Serve auxiliary files (eid.txt, ebhash.txt, etc.) from the run directory.""" run_dir = run_dir_path(run) - target_path = os.path.join(run_dir, filename) - if os.path.isfile(target_path): - return send_from_directory(run_dir, filename) + target_path = run_dir / filename + if target_path.is_file(): + return send_from_directory(str(run_dir), filename) abort(404, f"{filename} not found in {run_dir}") -@app.route("/votes/") -def votes(run): - """Expose elected voter IDs (first pass: read from certificate.pretty.json).""" - d = run_dir_path(run) - cert_path = os.path.join(d, "certificate.pretty.json") - voters = [] - pv = [] - npv = [] - if os.path.isfile(cert_path): - with open(cert_path) as f: - c = json.load(f) - # prefer full lists if present, else samples - pv = c.get("persistent_voters") or [] - npv = c.get("nonpersistent_voters") or c.get("nonpersistent_voters_sample") or [] - voters = (pv or []) + (npv or []) - - return jsonify({ - "elected": voters, - "persistent": pv, - "nonpersistent": npv - }) - -@app.route("/cert/") -def cert(run): - all_flag = request.args.get("all") == "1" - # If 'all=1' is requested or pretty.json is missing, regenerate - if all_flag or not os.path.isfile(pretty_json_path(run)): - regenerate_pretty(run, all_voters=all_flag) - - try: - with open(pretty_json_path(run)) as f: - data = json.load(f) - return jsonify(data) - except FileNotFoundError: - abort(404, f"certificate.pretty.json not found for {run}") - # === UI endpoint === @app.route("/ui") def ui(): From aac588d9b12e8c4f8ab96735a70bb615aa33daea Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Thu, 23 Oct 2025 17:37:40 +0900 Subject: [PATCH 13/16] clean README --- crypto-benchmarks.rs/demo/README.md | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/crypto-benchmarks.rs/demo/README.md b/crypto-benchmarks.rs/demo/README.md index 85b48290c..812ebd3cf 100644 --- a/crypto-benchmarks.rs/demo/README.md +++ b/crypto-benchmarks.rs/demo/README.md @@ -26,12 +26,6 @@ This folder contains scripts that orchestrate end-to-end demonstrations of BLS-b pip install cbor2 matplotlib ``` -- Make sure all scripts are executable: - - ```bash - chmod +x scripts/*.sh - ``` - ## Workflow The scripts are designed to be run from the `demo/` directory. @@ -82,14 +76,6 @@ Verify the generated certificate: scripts/50_verify_certificate.sh -d "$RUN" ``` -#### 60_export_demo_json.sh - -Export all relevant data (pools, committee, voters, and certificate summary) into a single `demo.json` file used by the visualization UI. - -```bash -scripts/60_export_demo_json.sh -d "$RUN" -``` - ### Run a Single End-to-End Demo ```bash @@ -103,11 +89,10 @@ This will: 3. Cast votes (`30_cast_votes.sh`) 4. Make a certificate (`40_make_certificate.sh`) 5. Verify the certificate (`50_verify_certificate.sh`) -6. Pretty-print key metrics (`60_pretty_print_cert.sh`) +6. Export data for the UI (`60_export_demo_json.sh`) All files are placed in `demo/run100/`. - ## Notes - All scripts must be run from within the `demo/` directory. @@ -118,4 +103,4 @@ All files are placed in `demo/run100/`. votes_bytes / certificate_bytes ``` -which illustrates the storage/bandwidth savings achieved by BLS aggregation. \ No newline at end of file +which illustrates the storage/bandwidth savings achieved by BLS aggregation. From e04ee6a0f30189effe51d5b7299c85467ca05036 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Wed, 29 Oct 2025 11:45:44 +0900 Subject: [PATCH 14/16] display environment information in the UI --- .../demo/scripts/60_export_demo_json.sh | 59 ++++++++++++++++++- crypto-benchmarks.rs/demo/ui/static/app.js | 56 +++++++++++++++++- .../demo/ui/static/styles.css | 21 +++++++ .../demo/ui/templates/index.html | 6 ++ 4 files changed, 138 insertions(+), 4 deletions(-) diff --git a/crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh b/crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh index 6c38b1585..00b81c43e 100755 --- a/crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh +++ b/crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh @@ -25,7 +25,11 @@ PY="${PY:-python3}" pushd "$RUN_DIR" >/dev/null "$PY" - <<'PY' -import os, json, collections +import json +import os +import platform +import subprocess +import sys try: import cbor2 except ImportError: @@ -289,6 +293,54 @@ params = { "quorum_fraction": quorum_fraction, # may be None if not recorded } + +def detect_total_memory_bytes(): + """Attempt to detect total physical memory without external dependencies.""" + try: + if sys.platform.startswith("linux"): + with open("/proc/meminfo", "r", encoding="utf-8") as f: + for line in f: + if line.startswith("MemTotal:"): + parts = line.split() + if len(parts) >= 2: + # Value reported in kB + return int(float(parts[1]) * 1024) + elif sys.platform == "darwin": + out = subprocess.check_output( + ["sysctl", "-n", "hw.memsize"], text=True, stderr=subprocess.DEVNULL + ).strip() + if out: + return int(out) + elif sys.platform.startswith("win"): + out = subprocess.check_output( + ["wmic", "ComputerSystem", "get", "TotalPhysicalMemory"], + text=True, + stderr=subprocess.DEVNULL + ) + for line in out.splitlines(): + line = line.strip() + if line.isdigit(): + return int(line) + except Exception: + return None + return None + + +def gather_environment(): + uname = platform.uname() + info = { + "os": f"{uname.system} {uname.release}".strip(), + "architecture": uname.machine or None, + "cpu_count": os.cpu_count(), + } + mem_bytes = detect_total_memory_bytes() + if mem_bytes: + info["memory_bytes"] = mem_bytes + return {k: v for k, v in info.items() if v is not None} + + +environment = gather_environment() + out = { "params": params, "universe": universe, @@ -308,7 +360,10 @@ out = { "votes_preview": votes_preview, } +if environment: + out["environment"] = environment + json.dump(out, open("demo.json", "w"), indent=2) print("Wrote demo.json") PY -popd >/dev/null \ No newline at end of file +popd >/dev/null diff --git a/crypto-benchmarks.rs/demo/ui/static/app.js b/crypto-benchmarks.rs/demo/ui/static/app.js index decf61fe8..abe45f611 100644 --- a/crypto-benchmarks.rs/demo/ui/static/app.js +++ b/crypto-benchmarks.rs/demo/ui/static/app.js @@ -1153,7 +1153,7 @@ async function loadTimingsForRun(runDir) { setText("verify_time", "—"); setText("verify_status", "—"); applyVerificationStatus(null); - return; + return null; } const timings = await tryFetchJson(`/demo/${runDir}/timings.json`); if (timings && typeof timings === "object") { @@ -1190,6 +1190,7 @@ async function loadTimingsForRun(runDir) { setText("verify_time", "—"); applyVerificationStatus(null); } + return timings ?? null; } function fillIdentifiers(obj) { @@ -1204,6 +1205,53 @@ function fillIdentifiers(obj) { } } +function applyEnvironmentInfo(environment) { + const container = document.getElementById("demo_meta"); + const summaryEl = document.getElementById("machine_summary"); + + if (!container || !summaryEl) return; + + const hasEnvironment = environment && typeof environment === "object"; + + if (!hasEnvironment) { + summaryEl.textContent = "—"; + container.hidden = true; + return; + } + + const parts = []; + if (environment.os && environment.architecture) { + parts.push(`${environment.os} (${environment.architecture})`); + } else if (environment.os) { + parts.push(environment.os); + } else if (environment.architecture) { + parts.push(environment.architecture); + } + + if (environment.cpu_count !== undefined && environment.cpu_count !== null) { + const cores = Number(environment.cpu_count); + if (Number.isFinite(cores) && cores > 0) { + parts.push(`${cores} ${cores === 1 ? "core" : "cores"}`); + } + } + + if (environment.memory_bytes !== undefined && environment.memory_bytes !== null) { + const memoryBytes = Number(environment.memory_bytes); + if (Number.isFinite(memoryBytes) && memoryBytes > 0) { + parts.push(`${formatBytes(memoryBytes)} RAM`); + } + } + + if (!parts.length) { + summaryEl.textContent = "—"; + container.hidden = true; + return; + } + + summaryEl.textContent = parts.join(" • "); + container.hidden = false; +} + const FLOW_STEPS = [ { wrapperId: "step_committee", @@ -1313,6 +1361,9 @@ function clearUIPlaceholders() { setText('agg_votes_size', '—'); setText('agg_cert_size', '—'); setText('agg_gain', '—'); + setText("machine_summary", "—"); + const metaEl = document.getElementById("demo_meta"); + if (metaEl) metaEl.hidden = true; // Clear main panels const clearIds = ["universe_canvas", "committee_canvas", "voters_canvas", "aggregation_canvas"]; for (const id of clearIds) { @@ -1342,8 +1393,9 @@ async function loadAndRenderRun(runDir) { renderCommitteeFromDemo(demo); renderVotersFromDemo(demo); renderAggregationFromDemo(demo); + applyEnvironmentInfo(demo?.environment ?? null); applyVerificationStatus(demo?.verification?.status ?? demo?.verification_status ?? null); - await loadTimingsForRun(runDir); + const timings = await loadTimingsForRun(runDir); syncGridColumns(); window.addEventListener("resize", syncGridColumns); } else { diff --git a/crypto-benchmarks.rs/demo/ui/static/styles.css b/crypto-benchmarks.rs/demo/ui/static/styles.css index bf3bb4222..006f6c855 100644 --- a/crypto-benchmarks.rs/demo/ui/static/styles.css +++ b/crypto-benchmarks.rs/demo/ui/static/styles.css @@ -49,6 +49,27 @@ a { text-align: center; } +.app-meta { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + justify-content: center; + font-size: 16px; + color: var(--muted); +} + +.app-meta__line { + display: flex; + gap: 0.35rem; + align-items: center; +} + +.app-meta__line strong { + color: var(--text); + font-weight: 600; +} + .app-title { margin: 0; font-size: 28px; diff --git a/crypto-benchmarks.rs/demo/ui/templates/index.html b/crypto-benchmarks.rs/demo/ui/templates/index.html index f364345bf..b7ec659ef 100644 --- a/crypto-benchmarks.rs/demo/ui/templates/index.html +++ b/crypto-benchmarks.rs/demo/ui/templates/index.html @@ -17,6 +17,12 @@

    Leios Voting Demo

    +
    From b18db7ea7dc81a038d4a9ce194dca17b721b55b5 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Wed, 29 Oct 2025 16:15:55 +0900 Subject: [PATCH 15/16] remove old dependencies from README --- crypto-benchmarks.rs/demo/README.md | 42 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/crypto-benchmarks.rs/demo/README.md b/crypto-benchmarks.rs/demo/README.md index 812ebd3cf..37373ecb0 100644 --- a/crypto-benchmarks.rs/demo/README.md +++ b/crypto-benchmarks.rs/demo/README.md @@ -6,24 +6,14 @@ This folder contains scripts that orchestrate end-to-end demonstrations of BLS-b ## Prerequisites -- Build the CLI once from the repository root: - - ```bash - cargo build --release -p crypto-benchmarks - ``` - - The resulting binary will be at: - ``` - target/release/leios_crypto_benchmarks - ``` - -- Ensure Python 3 is available with `cbor2` and `matplotlib` installed. +- Ensure the CLI built from the repository root is available; see `crypto-benchmarks.rs/ReadMe.md` for build instructions and usage details. +- Ensure Python 3 is available with `cbor2` installed. For example: ```bash python3 -m venv .venv source .venv/bin/activate - pip install cbor2 matplotlib + pip install cbor2 ``` ## Workflow @@ -32,16 +22,14 @@ The scripts are designed to be run from the `demo/` directory. ### Run Step by Step (Manual Mode) -You can run each script individually to understand and control each step of the process for a given number of voters (e.g., 32). Use the `-d` option to specify the output directory (e.g., `run32`). - -RUN=run100 +You can run each script individually to understand and control each step of the process for a given number of voters (e.g., 100). Use the `-d` option to specify the output directory (e.g., `run100`). #### 10_init_inputs.sh Initialize inputs for N voters: ```bash -scripts/10_init_inputs.sh -d "$RUN" --pools 500 --stake 100000 --alpha 9 --beta 1 +scripts/10_init_inputs.sh -d run100 --pools 500 --stake 100000 --alpha 9 --beta 1 ``` #### 20_make_registry.sh @@ -49,7 +37,7 @@ scripts/10_init_inputs.sh -d "$RUN" --pools 500 --stake 100000 --alpha 9 --beta Build the registry from initialized inputs: ```bash -./scripts/20_make_registry.sh -d "$RUN" -n 100 +./scripts/20_make_registry.sh -d run100 -n 100 ``` #### 30_cast_votes.sh @@ -57,7 +45,7 @@ Build the registry from initialized inputs: Cast votes with a specified fraction of voters voting (e.g., 1.0 means all vote): ```bash -scripts/30_cast_votes.sh -d "$RUN" -f 0.75 +scripts/30_cast_votes.sh -d run100 -f 0.75 ``` #### 40_make_certificate.sh @@ -65,7 +53,7 @@ scripts/30_cast_votes.sh -d "$RUN" -f 0.75 Generate the aggregated certificate: ```bash -scripts/40_make_certificate.sh -d "$RUN" +scripts/40_make_certificate.sh -d run100 ``` #### 50_verify_certificate.sh @@ -73,13 +61,13 @@ scripts/40_make_certificate.sh -d "$RUN" Verify the generated certificate: ```bash -scripts/50_verify_certificate.sh -d "$RUN" +scripts/50_verify_certificate.sh -d run100 ``` ### Run a Single End-to-End Demo ```bash -scripts/70_run_one.sh -d "$RUN" -p 500 -n 100 -f 0.75 +scripts/70_run_one.sh -d run100 -p 500 -n 100 -f 0.75 ``` This will: @@ -93,6 +81,16 @@ This will: All files are placed in `demo/run100/`. +### Launch the Demo UI + +After generating a demo run (for example via `scripts/70_run_one.sh`), start the UI server from this directory: + +```bash +python3 ui/server.py +``` + +Then open your browser at [http://127.0.0.1:5050/ui](http://127.0.0.1:5050/ui) to explore the results. + ## Notes - All scripts must be run from within the `demo/` directory. From 27dd526a1f95fb7a5023ec034884a77404120dbb Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Wed, 29 Oct 2025 18:53:46 +0900 Subject: [PATCH 16/16] display CPU in the demo UI --- .../demo/scripts/60_export_demo_json.sh | 47 ++++++++++++++++++- crypto-benchmarks.rs/demo/ui/static/app.js | 9 ++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh b/crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh index 00b81c43e..80904c861 100755 --- a/crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh +++ b/crypto-benchmarks.rs/demo/scripts/60_export_demo_json.sh @@ -326,6 +326,48 @@ def detect_total_memory_bytes(): return None +def detect_cpu_name(): + """Return a human-readable CPU name if available.""" + try: + if sys.platform.startswith("linux"): + with open("/proc/cpuinfo", "r", encoding="utf-8") as f: + for line in f: + if line.lower().startswith("model name"): + _, value = line.split(":", 1) + return value.strip() + elif sys.platform == "darwin": + try: + out = subprocess.check_output( + ["sysctl", "-n", "machdep.cpu.brand_string"], + text=True, + stderr=subprocess.DEVNULL + ).strip() + if out: + return out + except Exception: + pass + out = subprocess.check_output( + ["sysctl", "-n", "hw.model"], + text=True, + stderr=subprocess.DEVNULL + ).strip() + if out: + return out + elif sys.platform.startswith("win"): + out = subprocess.check_output( + ["wmic", "cpu", "get", "Name"], + text=True, + stderr=subprocess.DEVNULL + ) + for line in out.splitlines(): + line = line.strip() + if line and line.lower() != "name": + return line + except Exception: + return None + return platform.processor() or None + + def gather_environment(): uname = platform.uname() info = { @@ -334,8 +376,11 @@ def gather_environment(): "cpu_count": os.cpu_count(), } mem_bytes = detect_total_memory_bytes() - if mem_bytes: + if mem_bytes is not None: info["memory_bytes"] = mem_bytes + cpu_name = detect_cpu_name() + if cpu_name: + info["cpu"] = cpu_name return {k: v for k, v in info.items() if v is not None} diff --git a/crypto-benchmarks.rs/demo/ui/static/app.js b/crypto-benchmarks.rs/demo/ui/static/app.js index abe45f611..7febcba59 100644 --- a/crypto-benchmarks.rs/demo/ui/static/app.js +++ b/crypto-benchmarks.rs/demo/ui/static/app.js @@ -1228,6 +1228,15 @@ function applyEnvironmentInfo(environment) { parts.push(environment.architecture); } + if (environment.cpu) { + const cpuLabel = typeof environment.cpu === "string" + ? environment.cpu.trim() + : environment.cpu; + if (cpuLabel) { + parts.push(cpuLabel); + } + } + if (environment.cpu_count !== undefined && environment.cpu_count !== null) { const cores = Number(environment.cpu_count); if (Number.isFinite(cores) && cores > 0) {