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.
5.0 KiB
5.0 KiB
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, depsrequests+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.pyimportsconfig→.envhonored;DASH_WORKERS(default 2) controls workers,DASH_HOST/DASH_PORTthe bind. Dev server (single-process) still works via./venv/bin/python dashboard.py. Paged 50/page, sortedstaked DESC, live owner search (/api/search). Data freshness comes from the daily scan run — the dashboard never scans. - Config: all tunables load from env /
.envviaconfig.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→.envto override;.envis 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.pyand./venv/bin/python test_dashboard.pymust 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) andAPI_NODE(https://v2.vexascan.com:2096) are the defaults inconfig.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(notvexio), scope is alsovexcore. 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
stakedas 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 formulalast_vote_weight = staked_raw × 2^(years since 2000)→log2(weight / (staked×10000)). Voters whoselast_voteis older thanVEX_STALE_DAYS(default 28) — or can't be derived (zero/empty weight) — are dropped innormalize()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), sonormalize()clamps anylast_votepast scan time down tonow. - The web list shows RANK / AKUN / STAKE (VEX) / VOTE TERAKHIR columns (3 stats cells). Vote weight stays in the DB and
/api/searchJSON 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) → SQLitevoters.db(each run replaces the table = daily snapshot, withscanned_at+ derivedlast_vote; never appends history).get_voters.js— reference implementation only.dashboard.py+templates/index.html+static/style.css+static/app.js— Flask web dashboard; readsvoters.db, styled perDESIGN.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.