Files
databisnisid/AGENTS.md
T
proitlab eb4f941b37 T13: klamp last_vote heuristik ke waktu scan (⊥ di masa depan)
Unstake tanpa revote membuat rasio bobot/stake membesar → estimasi
last_vote melewati now. normalize() kini mengklamp ke now; voters.db
dikembalikan (27 baris, MAX(last_vote) ≤ MAX(scanned_at)). Oracle lolos.
2026-08-05 10:21:45 +07:00

5.0 KiB
Raw Blame History

AGENTS.md

Two tools scan the Vexanium blockchain voters table for accounts whose only vote goes to this BP (databisnisid): a Node.js reference script and the Python production tool that persists results to SQLite.

Run

  • Node reference script: node get_voters.js (no deps, Node 18+).
  • Python tool (production): ./venv/bin/python get_voters.py. venv is Python 3.12, deps requests + flask + python-dotenv (requirements.txt). Install with ./venv/bin/pip install -r requirements.txt.
  • Web dashboard (read-only, reads voters.db): production ./venv/bin/gunicorn -c gunicorn.conf.py dashboard:app → http://127.0.0.1:5000/ (run from repo dir). gunicorn.conf.py imports config → .env honored; DASH_WORKERS (default 2) controls workers, DASH_HOST/DASH_PORT the bind. Dev server (single-process) still works via ./venv/bin/python dashboard.py. Paged 50/page, sorted staked DESC, live owner search (/api/search). Data freshness comes from the daily scan run — the dashboard never scans.
  • Config: all tunables load from env / .env via config.py (python-dotenv): VEX_TARGET_BP, VEX_API_NODE, VEX_DB_PATH, VEX_MIN_STAKED_VEX, DASH_PAGE_SIZE, DASH_HOST, DASH_PORT, DASH_WORKERS, VEX_STALE_DAYS. Copy .env.example → .env to override; .env is gitignored. Chain constants (vexcore/scope/table) stay hardcoded.
  • Syntax check: node --check get_voters.js, ./venv/bin/python -m py_compile config.py get_voters.py dashboard.py gunicorn.conf.py test_get_voters.py test_dashboard.py.
  • Tests (the verification oracles): ./venv/bin/python test_get_voters.py and ./venv/bin/python test_dashboard.py must both exit 0. They mock the network / use a temp DB — the live node is too flaky/slow for a full-scan test. Run after touching the relevant file.

Key facts

  • TARGET_BP (databisnisid) and API_NODE (https://v2.vexascan.com:2096) are the defaults in config.py (env-overridable); the JS reference still hardcodes them at the top of the file.
  • The public Vexanium RPC node is flaky/timeout-prone; both scripts have retry logic built in — don't remove or bypass it. A full scan of the voters table takes minutes.
  • The system contract on Vexanium is vexcore (not vexio), scope is also vexcore. Do not "fix" this to match EOS convention.
  • VEX stake is stored scaled by 10000; the Python tool divides by 10000 before storing.
  • The node sometimes returns staked as a string instead of a number — normalize coerces to float (spec §V8).
  • The voters table has a sentinel first row (owner="...........q", uint64 0) that must be skipped.
  • Authoritative vote weight is last_vote_weight (high-precision decimal string) — stored verbatim as TEXT, never float-converted (spec §V6).
  • Freshness filter (spec §V13): last_vote (estimated last re-vote date) is derived at scan time from the Vexanium weight formula last_vote_weight = staked_raw × 2^(years since 2000) → log2(weight / (staked×10000)). Voters whose last_vote is older than VEX_STALE_DAYS (default 28) — or can't be derived (zero/empty weight) — are dropped in normalize() and never stored; the dashboard therefore shows only fresh voters (no stale UI). The heuristic over-reads when a voter unstaked without re-voting (weight ÷ smaller stake ⇒ future date), so normalize() clamps any last_vote past scan time down to now.
  • The web list shows RANK / AKUN / STAKE (VEX) / VOTE TERAKHIR columns (3 stats cells). Vote weight stays in the DB and /api/search JSON but is not rendered as a column.

Layout

  • SPEC.md — spec (goal/constraints/interfaces/invariants/tasks/bug log), in caveman encoding. Build/backprop flow through it.
  • get_voters.py — production fetcher: scan → filter (stake + freshness, basi dibuang) → SQLite voters.db (each run replaces the table = daily snapshot, with scanned_at + derived last_vote; never appends history).
  • get_voters.js — reference implementation only.
  • dashboard.py + templates/index.html + static/style.css + static/app.js — Flask web dashboard; reads voters.db, styled per DESIGN.md; app.js = debounced live owner search (fetch /api/search), degrades to the server-side ?q= GET form if JS is off.
  • config.py — loads env/.env (python-dotenv) → TARGET_BP, API_NODE, DB_PATH, MIN_STAKED_VEX, PAGE_SIZE, DASH_HOST, DASH_PORT, DASH_WORKERS, VEX_STALE_DAYS; shared by get_voters & dashboard.
  • DESIGN.md — Bugatti austere style guide; the dashboard's CSS maps its tokens (canvas #000000, hairline #262626, weight 400 everywhere, fonts Saira Condensed / EB Garamond / JetBrains Mono).
  • voters.db — SQLite output (daily snapshot, gitignored in spirit).

Conventions

  • Comments and console output are in Indonesian — keep new output/comments in Indonesian.
  • Dashboard UI labels are Indonesian uppercase captions (e.g. "DAFTAR PEMILIH", "TOTAL PEMILIH").
  • JS style: 2-space indent, semicolons, single quotes, trailing commas, async/await.
  • Python style: 4-space indent, stdlib sqlite3, requests, flask, PEP8.