T1-T5: Python voter fetcher (§V1-9)
- get_voters.py: scan Vexanium voters table (vexcore), retry/paginate, filter databisnisid single-vote + stake ≥1000 VEX, store weight verbatim, daily-replace SQLite snapshot (owner PK, scanned_at) - V8/V9 + test_get_voters.py (oracle, §V1-9) - SPEC.md, AGENTS.md, requirements.txt
This commit is contained in:
1 parent
539b83b330
commit
c5e30a4785
7 files changed
+496
-173
No files matched your search
@@ -0,0 +1,98 @@
|
||||
// Ganti dengan nama akun Block Producer Anda di Vexanium
|
||||
const TARGET_BP = 'databisnisid';
|
||||
|
||||
// Menggunakan API dari v2.vexascan.com
|
||||
const API_NODE = 'https://v2.vexascan.com:2096';
|
||||
|
||||
// Fungsi pembantu untuk fetch dengan mekanisme retry (karena node ini kadang timeout)
|
||||
async function fetchWithRetry(url, options, maxRetries = 3) {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (i === maxRetries - 1) throw error;
|
||||
console.log(`[Warning] Gagal menghubungi node, mencoba lagi... (${i + 1}/${maxRetries})`);
|
||||
await new Promise(res => setTimeout(res, 2000)); // tunggu 2 detik sebelum retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getVotersForMyBP() {
|
||||
let myVoters = [];
|
||||
let lowerBound = '';
|
||||
let hasMore = true;
|
||||
let totalScanned = 0;
|
||||
|
||||
console.log(`Mulai memindai voters untuk BP: "${TARGET_BP}"...`);
|
||||
|
||||
while (hasMore) {
|
||||
try {
|
||||
// Menggunakan fetchWithRetry yang sudah kita buat
|
||||
const response = await fetchWithRetry(`${API_NODE}/v1/chain/get_table_rows`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
json: true,
|
||||
// PENTING: Di Vexanium, kontrak sistem (untuk tabel voters) adalah vexcore, bukan vexio
|
||||
code: 'vexcore',
|
||||
scope: 'vexcore',
|
||||
table: 'voters',
|
||||
lower_bound: lowerBound,
|
||||
limit: 500
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const rows = data.rows || [];
|
||||
if (rows.length === 0) break;
|
||||
|
||||
// Hindari duplikasi elemen pertama saat pagination
|
||||
if (lowerBound !== '') {
|
||||
rows.shift();
|
||||
}
|
||||
|
||||
totalScanned += rows.length;
|
||||
|
||||
// Saring hanya pemilih yang HANYA mem-vote BP ini (eksklusif 1 vote)
|
||||
const filtered = rows.filter(voter =>
|
||||
Array.isArray(voter.producers) &&
|
||||
voter.producers.length === 1 &&
|
||||
voter.producers[0] === TARGET_BP
|
||||
);
|
||||
|
||||
myVoters = myVoters.concat(filtered);
|
||||
hasMore = data.more;
|
||||
|
||||
if (hasMore && rows.length > 0) {
|
||||
lowerBound = rows[rows.length - 1].owner;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('\nError saat mengambil data:', error.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n=== HASIL AKHIR ===`);
|
||||
console.log(`Total akun dipindai di jaringan: ${totalScanned}`);
|
||||
console.log(`Total voters untuk BP [${TARGET_BP}]: ${myVoters.length}`);
|
||||
|
||||
// Menampilkan daftar pemilih beserta VEX staked-nya
|
||||
console.log('\nDaftar Pemilih:');
|
||||
if (myVoters.length === 0) {
|
||||
console.log('(Tidak ada pemilih ditemukan untuk BP ini atau scan terhenti di tengah jalan)');
|
||||
} else {
|
||||
myVoters.forEach((v, index) => {
|
||||
// VEX stake disimpan dalam pecahan 1/10000 (4 desimal)
|
||||
const vexStaked = (v.staked / 10000).toLocaleString('id-ID');
|
||||
console.log(`${index + 1}. ${v.owner} - Stake: ${vexStaked} VEX`);
|
||||
});
|
||||
}
|
||||
|
||||
return myVoters;
|
||||
}
|
||||
|
||||
getVotersForMyBP();
|
||||
Reference in new issue
Block a user