157 lines
4.6 KiB
Python
157 lines
4.6 KiB
Python
"""Scan tabel voters Vexanium, simpan pemilih BP databisnisid + bobot suara ke SQLite.
|
|
|
|
Berjalan imparsial (repeatable) dan siap dijadwalkan (cron). Memakai requests.
|
|
"""
|
|
|
|
import sqlite3
|
|
import sys
|
|
import time
|
|
from datetime import datetime
|
|
|
|
import requests
|
|
|
|
from config import API_NODE, DB_PATH, MIN_STAKED_VEX, TARGET_BP
|
|
|
|
# Kontrak & scope sistem Vexanium = vexcore (BUKAN vexio sesuai konvensi EOS)
|
|
CODE = 'vexcore'
|
|
SCOPE = 'vexcore'
|
|
TABLE = 'voters'
|
|
|
|
# Ukuran halaman saat paginasi
|
|
LIMIT = 500
|
|
|
|
# Ambang-batas nama akun "0" (kodifikasi uint64 0) pada tabel voters
|
|
ZERO_OWNER = '...........q'
|
|
|
|
|
|
def fetch_with_retry(payload, max_retries=3, timeout=30):
|
|
"""POST ke RPC dengan retry (node publik sering timeout).
|
|
|
|
V1: ulang ≥3 kali pada timeout/error HTTP, jeda 2 detik antar percobaan.
|
|
"""
|
|
url = f'{API_NODE}/v1/chain/get_table_rows'
|
|
for attempt in range(max_retries):
|
|
try:
|
|
resp = requests.post(url, json=payload, timeout=timeout)
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
except requests.RequestException:
|
|
if attempt == max_retries - 1:
|
|
raise
|
|
print(f'[Peringatan] Gagal menghubungi node, mencoba lagi... '
|
|
f'({attempt + 1}/{max_retries})')
|
|
time.sleep(2)
|
|
|
|
|
|
def scan_voters():
|
|
"""Iterasi seluruh tabel voters (paginasi), kembalikan daftar mentah.
|
|
|
|
V2: majukan lower_bound ke owner terakhir; berhenti saat more=false | baris kosong.
|
|
V3: pada halaman kelanjutan, buang baris pertama (induk pamungkas duplikat).
|
|
"""
|
|
rows_all = []
|
|
lower_bound = ''
|
|
has_more = True
|
|
|
|
while has_more:
|
|
payload = {
|
|
'json': True,
|
|
'code': CODE,
|
|
'scope': SCOPE,
|
|
'table': TABLE,
|
|
'lower_bound': lower_bound,
|
|
'limit': LIMIT,
|
|
}
|
|
data = fetch_with_retry(payload)
|
|
rows = data.get('rows') or []
|
|
if not rows:
|
|
break
|
|
|
|
if lower_bound != '':
|
|
rows = rows[1:]
|
|
|
|
rows_all.extend(rows)
|
|
has_more = data.get('more', False)
|
|
|
|
if has_more:
|
|
lower_bound = rows_all[-1]['owner']
|
|
|
|
return rows_all
|
|
|
|
|
|
def normalize(row):
|
|
"""V4/V5/V6: saring ke baris yang relevan, bidang dinormalisasi."""
|
|
owner = row['owner']
|
|
if owner == ZERO_OWNER: # V4: lewati sentinel nama akun 0
|
|
return None
|
|
if not (row.get('producers') == [TARGET_BP]): # V5
|
|
return None
|
|
staked_vex = float(row.get('staked', 0)) / 10000 # V8/V9: bisa string, ÷10000
|
|
if staked_vex < MIN_STAKED_VEX:
|
|
return None
|
|
return {
|
|
'owner': owner,
|
|
'weight': row.get('last_vote_weight', ''), # V6: bobot verbatim (string)
|
|
'staked': staked_vex,
|
|
}
|
|
|
|
|
|
def persist(rows):
|
|
"""V7: ganti snapshot — DROP+INSERT tiap skan, ⊥ history/append.
|
|
|
|
WAL: journal_mode=WAL → pembaca (dashboard multi-worker) tak terblokir
|
|
saat DROP+INSERT harian. Mode ini persisten setelah diset.
|
|
"""
|
|
scanned_at = datetime.now().isoformat(timespec='seconds')
|
|
conn = sqlite3.connect(DB_PATH)
|
|
try:
|
|
conn.execute('PRAGMA journal_mode=WAL')
|
|
conn.execute('DROP TABLE IF EXISTS voters') # snapshot bersih
|
|
conn.execute(
|
|
'CREATE TABLE voters (' # I.db
|
|
'owner TEXT PRIMARY KEY, '
|
|
'weight TEXT NOT NULL, '
|
|
'staked REAL NOT NULL, '
|
|
'scanned_at TEXT NOT NULL)'
|
|
)
|
|
conn.executemany(
|
|
'INSERT INTO voters (owner, weight, staked, scanned_at) '
|
|
'VALUES (?, ?, ?, ?)',
|
|
[(r['owner'], r['weight'], r['staked'], scanned_at) for r in rows],
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
return scanned_at
|
|
|
|
|
|
def summarize(rows, scanned_at):
|
|
"""I.cmd: ringkasan stdout dalam bahasa Indonesia, format id-ID."""
|
|
print('\n=== HASIL AKHIR ===')
|
|
print(f'Total pemilih untuk BP [{TARGET_BP}]: {len(rows)}')
|
|
print(f'Waktu pemindaian: {scanned_at}')
|
|
print('\nDaftar Pemilih:')
|
|
if not rows:
|
|
print('(Tidak ada pemilih ditemukan untuk BP ini)')
|
|
return
|
|
for index, row in enumerate(rows, start=1):
|
|
print(f"{index}. {row['owner']} - Stake: "
|
|
f"{row['staked']:,.4f} VEX - Bobot: {row['weight']}")
|
|
|
|
|
|
def main():
|
|
"""Titik masuk CLI."""
|
|
print(f'Mulai memindai voters untuk BP: "{TARGET_BP}"...')
|
|
raw = scan_voters()
|
|
filtered = []
|
|
for row in raw:
|
|
item = normalize(row)
|
|
if item is not None:
|
|
filtered.append(item)
|
|
scanned_at = persist(filtered)
|
|
summarize(filtered, scanned_at)
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main()) |