Files

653 lines
31 KiB
Python

"""Uji verifikasi klaim reward BP (oracle §V32..V34).
`./venv/bin/python test_claim.py` harus exit 0. Memakai DB sementara + mock
jaringan/pyntelope — ⊥ klaim nyata. Verifikasi: jendela klaim (V32), parse
respons `get_table_rows` bentuk asli `{rows:[...]}` + recovery window lewat,
math fee (V33), siklus penuh klaim→fee, retry fee, resume, reward 0 →
skipped, klaim yang mendarat walau timeout (V34).
"""
import os
import sqlite3
import sys
import tempfile
from datetime import datetime, timedelta
from decimal import Decimal
import db
import claim
import telegram
import distribute as dist
def check(tag, cond):
if not cond:
raise AssertionError(f'GAGAL: {tag}')
print(f'ok: {tag}')
class FakeSigned:
"""Objek SignedTransaction tiruan: id deterministik + send terkontrol."""
def __init__(self, txid, exc=None):
self._txid = txid
self._exc = exc
def id(self):
return self._txid
def send(self):
if self._exc is not None:
raise self._exc
# V61: bentuk sukses nodeos asli (whitelist `_send_rejected` di
# `_send_fee` butuh `transaction_id` + receipt `executed`).
return {'transaction_id': self._txid,
'processed': {'receipt': {'status': 'executed'}}}
def fresh_db(tag):
"""DB sqlite sementara baru per bagian uji."""
tmp = tempfile.mkdtemp()
db.DB_PATH = os.path.join(tmp, f'claim-{tag}.db')
return db.DB_PATH
def last_claim_row():
row = db.queryone(
'SELECT claim_id, run_date, claim_txid, reward, fee_amount, '
' fee_status, fee_txid FROM claim_runs ORDER BY claim_id DESC '
'LIMIT 1')
return row
def fake_post(data):
return type('R', (), {'raise_for_status': lambda self: None,
'json': lambda self: data})()
def main():
# Matikan sleep nyata (settle/poll) + Telegram selama uji.
claim.time.sleep = lambda s: None
_tg_token = telegram.TELEGRAM_BOT_TOKEN
_tg_comm = telegram.TELEGRAM_COMMUNITY_CHAT_IDS
_tg_int = telegram.TELEGRAM_INTERNAL_CHAT_IDS
_tg_post = telegram.requests.post
_req_get = dist.requests.get
telegram.TELEGRAM_BOT_TOKEN = ''
telegram.TELEGRAM_COMMUNITY_CHAT_IDS = []
telegram.TELEGRAM_INTERNAL_CHAT_IDS = []
# Default: pengukuran reward via selisih saldo (tx-based diuji terpisah).
_orig_reward_from_tx = claim.reward_from_tx
claim.reward_from_tx = lambda txid: None
try:
# ————— V32: parse_claim_time — format chain —————
check('V32 parse format %f',
claim.parse_claim_time('2026-08-05T07:01:44.500')
== datetime(2026, 8, 5, 7, 1, 44, 500000))
check('V32 parse format detik',
claim.parse_claim_time('2026-08-05T07:01:44')
== datetime(2026, 8, 5, 7, 1, 44))
check('V32 parse kosong → None', claim.parse_claim_time(None) is None)
check('V32 parse sampah → None', claim.parse_claim_time('x') is None)
# ————— V32: fetch_producer_last_claim — bentuk asli get_table_rows —————
claim._post_json = lambda url, payload: {
'rows': [{'last_claim_time': '2026-08-05T07:01:44.500'}],
'more': False, 'next_key': ''}
check('V32 parse respons asli {rows:[...]} → waktu',
claim.fetch_producer_last_claim() == '2026-08-05T07:01:44.500')
claim._post_json = lambda url, payload: {'rows': [], 'more': False,
'next_key': ''}
check('V32 rows kosong → None',
claim.fetch_producer_last_claim() is None)
claim._post_json = lambda url, payload: {}
check('V32 respons tak dikenal → None (⊥ crash)',
claim.fetch_producer_last_claim() is None)
# ————— V32: next_window = last + 24 jam; lewat → klaim segera —————
claim.fetch_producer_last_claim = lambda: '2026-08-05T07:01:44.500'
check('V32 jendela = last + 24 jam',
claim.next_window(datetime(2026, 8, 6, 5, 0, 0))
== datetime(2026, 8, 6, 7, 1, 44, 500000))
check('V32 window lewat (≥24 jam) → now (recovery, ⊥ deadlock)',
claim.next_window(datetime(2026, 8, 6, 12, 0, 0))
== datetime(2026, 8, 6, 12, 0, 0))
claim.fetch_producer_last_claim = lambda: None
try:
claim.next_window(datetime(2026, 8, 6, 12, 0, 0))
raise AssertionError('GAGAL: baca producer gagal harus raise')
except RuntimeError:
pass
check('V46 baca producer gagal → raise (⊥ clamp prematur)',
True)
# BP baru (row ada, last_claim_time epoch/dulu) → jendela lewat → now
claim.fetch_producer_last_claim = lambda: '2000-01-01T00:00:00.000'
check('V32 last epoch (BP baru) → now',
claim.next_window(datetime(2026, 8, 6, 12, 0, 0))
== datetime(2026, 8, 6, 12, 0, 0))
# ————— V49: window math UTC-naive (konsisten dgn last_claim_time chain) —————
now_utc = claim._utcnow()
check('V49 _utcnow UTC-naive (⊥ tzinfo lokal)',
now_utc.tzinfo is None)
# step dengan now jauh sebelum last+24h → sleep (⊥ poll prematur)
fresh_db('prewindow')
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=12)
).strftime('%Y-%m-%dT%H:%M:%S')
check('V49 before window → sleep (⊥ poll prematur)',
claim.step() == 'sleep')
# ————— V32/V33: siklus penuh — klaim → catat → fee terkirim —————
fresh_db('cycle')
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=25)
).strftime('%Y-%m-%dT%H:%M:%S')
balances = iter([Decimal('100.0000'), Decimal('110.0004')])
dist.fetch_balance = lambda: next(balances)
claim.build_claim_action = lambda: FakeSigned('txclaim')
dist.verify_txid = lambda txid: True
r1 = claim.step()
check('V32 step klaim sukses → claimed', r1 == 'claimed')
row = last_claim_row()
check('V32 klaim tercatat (reward 10.0004, fee 1.0000 floor)',
row is not None and row[3] == 10.0004 and row[4] == 1.0
and row[2] == 'txclaim' and row[5] == 'pending')
memos = []
def fake_fee_sign(to, amount, memo):
memos.append((to, amount, memo))
return FakeSigned('txfee')
dist.build_signed_transfer = fake_fee_sign
r2 = claim.step()
check('V34 fee terkirim → fee_sent', r2 == 'fee_sent')
check('V33 fee ke wallet bpdbsjasprod', memos[-1][0] == 'bpdbsjasprod')
check('V33 memo BP FEE + tanggal',
memos[-1][2] == f'BP FEE {claim._utcnow().strftime("%Y-%m-%d")}')
row = last_claim_row()
check('V34 fee ditandai sent + txid',
row[5] == 'sent' and row[6] == 'txfee')
# ————— V33/V34: fee gagal → failed → diulang sampai terkirim —————
fresh_db('retry')
db.record_claim('2026-08-05', 'txold', 10.0, 1.0, 'pending',
'2026-08-05T08:00:00')
dist.build_signed_transfer = lambda to, amount, memo: FakeSigned(
'txfail', exc=RuntimeError('net down'))
dist.verify_txid = lambda txid: False
check('V34 fee gagal → fee_failed', claim.step() == 'fee_failed')
check('V34 status failed',
last_claim_row()[5] == 'failed')
dist.build_signed_transfer = lambda to, amount, memo: FakeSigned('txok')
check('V34 fee diulang → fee_sent', claim.step() == 'fee_sent')
row = last_claim_row()
check('V34 fee akhirnya sent', row[5] == 'sent' and row[6] == 'txok')
# ————— V33: reward 0 → fee skipped; step lanjut ke jadwal —————
# Reward 0 asli dibuktikan dari ISI TX (executed tanpa transfer bpay/vpay
# → Decimal('0')); selisih saldo 0 TIDAK dianggap reward 0 (B17) — delta
# 0 ambigu → jalur tak-terukur.
fresh_db('zero')
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=25)
).strftime('%Y-%m-%dT%H:%M:%S')
dist.fetch_balance = lambda: Decimal('100.0000') # ⊥ dipakai (tx prioritas)
claim.build_claim_action = lambda: FakeSigned('txzero')
dist.verify_txid = lambda txid: True
claim.reward_from_tx = lambda txid: Decimal('0') # genuine zero
check('V33 klaim reward 0 → claimed', claim.step() == 'claimed')
row = last_claim_row()
check('V33 fee 0 → skipped', row[4] == 0.0 and row[5] == 'skipped')
# pending_claim_fee ⊥ memuat fee 0 → step lanjut ke jadwal (sleep)
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=20)
).strftime('%Y-%m-%dT%H:%M:%S')
check('V34 ⊥ fee pending → jadwal (sleep)', claim.step() == 'sleep')
claim.reward_from_tx = lambda txid: None
# ————— V34: klaim timeout tapi mendarat (verify True) → diukur —————
fresh_db('landed')
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=25)
).strftime('%Y-%m-%dT%H:%M:%S')
balances = iter([Decimal('50.0000'), Decimal('60.0000')])
dist.fetch_balance = lambda: next(balances)
claim.build_claim_action = lambda: FakeSigned(
'txL', exc=RuntimeError('broadcast timeout'))
dist.verify_txid = lambda txid: True
check('V34 timeout tapi mendarat → claimed',
claim._try_claim_once() == 'claimed')
row = last_claim_row()
check('V34 reward terukur', row[3] == 10.0 and row[2] == 'txL')
# ————— V34/V45: verify None + last_claim_time benar2 MAJU → mendarat —————
fresh_db('reconcile')
last_state = {'n': 0}
def advancing_last():
last_state['n'] += 1
if last_state['n'] == 1: # baseline sebelum kirim
return (claim._utcnow() - timedelta(hours=2)
).strftime('%Y-%m-%dT%H:%M:%S')
return (claim._utcnow() - timedelta(hours=1)
).strftime('%Y-%m-%dT%H:%M:%S') # maju → mendarat
claim.fetch_producer_last_claim = advancing_last
balances = iter([Decimal('10.0000'), Decimal('12.0000')])
dist.fetch_balance = lambda: next(balances)
claim.build_claim_action = lambda: FakeSigned(
'txN', exc=RuntimeError('timeout'))
dist.verify_txid = lambda txid: None
check('V45 verify None + last MAJU → claimed',
claim._try_claim_once() == 'claimed')
check('V45 reward tetap diukur',
last_claim_row()[3] == 2.0)
# ————— V45: verify None + last TIDAK maju → retry (⊥ false success) —————
fresh_db('stale')
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=1)
).strftime('%Y-%m-%dT%H:%M:%S')
dist.fetch_balance = lambda: Decimal('100.0000')
claim.build_claim_action = lambda: FakeSigned(
'txS', exc=RuntimeError('timeout'))
dist.verify_txid = lambda txid: None
check('V45 last tak maju → retry (⊥ sukses-0)',
claim._try_claim_once() == 'retry')
db.ensure_claim_schema()
check('V45 ⊥ catat baris utk yg tak mendarat',
db.queryone('SELECT COUNT(*) FROM claim_runs')[0] == 0)
# ————— V46: send() "sukses" tapi chain MENOLAK (HTTP 500 ⊥ raise) —————
# pyntelope ⊥ raise saat chain reject — send kembali normal dgn body
# error; landing harus diverifikasi ulang → tak mendarat → retry.
fresh_db('rejectsilent')
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=25)
).strftime('%Y-%m-%dT%H:%M:%S')
dist.fetch_balance = lambda: Decimal('100.0000')
claim.build_claim_action = lambda: FakeSigned('txR2') # send ⊥ raise
dist.verify_txid = lambda txid: False # tak mendarat
notifs = []
_orig_nc2 = telegram.notify_claim
telegram.notify_claim = lambda r, f, w: notifs.append('claimed')
check('V46 send normal + verify False → retry (⊥ sukses-0)',
claim._try_claim_once() == 'retry')
db.ensure_claim_schema()
check('V46 ⊥ catat baris + ⊥ notif sukses',
db.queryone('SELECT COUNT(*) FROM claim_runs')[0] == 0
and notifs == [])
telegram.notify_claim = _orig_nc2
# ————— V46: send() "sukses" + last MAJU → benar-benar mendarat —————
fresh_db('sendsuccess')
last_s = {'n': 0}
def advancing_success():
last_s['n'] += 1
if last_s['n'] == 1:
return (claim._utcnow() - timedelta(hours=2)
).strftime('%Y-%m-%dT%H:%M:%S')
return (claim._utcnow() - timedelta(hours=1)
).strftime('%Y-%m-%dT%H:%M:%S')
claim.fetch_producer_last_claim = advancing_success
balances = iter([Decimal('10.0000'), Decimal('12.0000')])
dist.fetch_balance = lambda: next(balances)
claim.build_claim_action = lambda: FakeSigned('txR3') # send ⊥ raise
dist.verify_txid = lambda txid: None # Hyperion tak terjangkau
check('V46 send normal + last MAJU → claimed',
claim._try_claim_once() == 'claimed')
check('V46 reward terukur',
last_claim_row()[3] == 2.0)
# ————— V50: Hyperion `False` (lag indeks) tapi last_claim_time MAJU —————
# tx BARU SAJA mendarat belum terindeks Hyperion → verify False (⊥ None);
# landing tetap dideteksi via kemajuan `last_claim_time` (⊥ retry-loop).
fresh_db('lagindex')
last_l = {'n': 0}
def advancing_lag():
last_l['n'] += 1
if last_l['n'] == 1:
return (claim._utcnow() - timedelta(hours=2)
).strftime('%Y-%m-%dT%H:%M:%S')
return (claim._utcnow() - timedelta(hours=1)
).strftime('%Y-%m-%dT%H:%M:%S')
claim.fetch_producer_last_claim = advancing_lag
balances = iter([Decimal('20.0000'), Decimal('25.0000')])
dist.fetch_balance = lambda: next(balances)
claim.build_claim_action = lambda: FakeSigned('txR4') # send ⊥ raise
dist.verify_txid = lambda txid: False # Hyperion belum indeks (lag)
check('V50 verify False + last MAJU → claimed (⊥ retry-loop)',
claim._try_claim_once() == 'claimed')
check('V50 reward terukur + tercatat',
last_claim_row()[3] == 5.0 and last_claim_row()[2] == 'txR4')
# ————— V54/B16: baca last_after basi di attempt 1, maju di attempt 2 —————
# poll_claim menghitung baseline SEKALI; attempt 1 mendarat tapi node
# masih balikin last_claim_time lama → retry; attempt 2 (baseline sama)
# baca last_claim_time yg sudah maju → claimed (⊥ baseline ikut maju).
fresh_db('staleretry')
st = {'n': 0}
def stale_then_advance():
st['n'] += 1
if st['n'] == 1: # baseline (poll_claim awal window)
return (claim._utcnow() - timedelta(hours=2)
).strftime('%Y-%m-%dT%H:%M:%S')
if st['n'] == 2: # attempt 1: baca masih basi (belum ter-catch-up)
return (claim._utcnow() - timedelta(hours=2)
).strftime('%Y-%m-%dT%H:%M:%S')
return (claim._utcnow() - timedelta(hours=1)
).strftime('%Y-%m-%dT%H:%M:%S') # attempt 2: maju
claim.fetch_producer_last_claim = stale_then_advance
balances = iter([Decimal('30.0000'), Decimal('35.0000'),
Decimal('40.0000')])
dist.fetch_balance = lambda: next(balances)
sends = {'n': 0}
def send_once_then_reject():
sends['n'] += 1
if sends['n'] == 1:
return FakeSigned('txR5') # attempt 1: mendarat
return FakeSigned('txR5b', exc=RuntimeError('already claimed'))
claim.build_claim_action = send_once_then_reject
dist.verify_txid = lambda txid: False # Hyperion lag di kedua attempt
deadline = claim._utcnow() + timedelta(seconds=3600)
check('V54 stale lalu maju → poll_claim claimed',
claim.poll_claim(deadline) == 'claimed')
check('V54 reward terukur + tercatat',
last_claim_row()[3] == 5.0 and last_claim_row()[2] == 'txR5b')
# ————— V55/B17: reward_from_tx None + delta 0 → TAK TERUKUR (⊥ sukses-0) —————
# Delta saldo 0 (node belum up-date / saldo lama) ⊥ dianggap reward 0;
# keduanya gagal mengukur → fee pending + alert KLAIM MENDARAT, ⊖ notif
# sukses-0 palsu (kasus Telegram 2026-08-08: klaim mendarat, reward asli
# 1683.3641, tapi app notif `KLAIM REWARD SUKSES 0.0000`).
fresh_db('zerodelta')
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=25)
).strftime('%Y-%m-%dT%H:%M:%S')
claim.reward_from_tx = lambda txid: None # Hyperion belum indeks
bal_seq = iter([Decimal('100.0000'), Decimal('100.0000'),
Decimal('100.0000'), Decimal('100.0000')])
dist.fetch_balance = lambda: next(bal_seq) # delta selalu 0
claim.build_claim_action = lambda: FakeSigned('txB17')
dist.verify_txid = lambda txid: True
notifs = []
_orig_nc2 = telegram.notify_claim
_orig_ncu2 = telegram.notify_claim_unmeasured
telegram.notify_claim_unmeasured = lambda t: notifs.append('unmeasured')
telegram.notify_claim = lambda r, f, w: notifs.append('claimed')
check('V55 delta 0 → claimed (baris dicatat, ⊖ sukses-0)',
claim._try_claim_once() == 'claimed')
row = last_claim_row()
check('V55 fee pending utk cek manual (⊖ skipped)',
row[4] == 0.0 and row[5] == 'pending')
check('V55 alert cek manual, ⊖ notif sukses-0',
'unmeasured' in notifs and 'claimed' not in notifs)
telegram.notify_claim = _orig_nc2
telegram.notify_claim_unmeasured = _orig_ncu2
# ————— V55/B17: reward_from_tx catch-up (None lalu berhasil) —————
# Hyperion mengejar indeks tx → reward asli terbaca → fee tercatat
# (jalur normal yang menyelamatkan klaim seperti 2026-08-09).
fresh_db('catchup')
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=25)
).strftime('%Y-%m-%dT%H:%M:%S')
seq = iter([None, None, Decimal('1683.0126')])
claim.reward_from_tx = lambda txid: next(seq)
dist.fetch_balance = lambda: Decimal('100.0000') # ⊥ dipakai
claim.build_claim_action = lambda: FakeSigned('txC')
dist.verify_txid = lambda txid: True
check('V55 catch-up reward → claimed', claim._try_claim_once() == 'claimed')
row = last_claim_row()
check('V55 reward asli 1683.0126 + fee 168.3012 pending',
row[3] == 1683.0126 and row[4] == 168.3012
and row[5] == 'pending' and row[2] == 'txC')
# ————— V56/B18: klaim mendarat attempt 1, attempt 2 ditolak (already
# claimed) — before_balance STABIL per window → reward tetap terukur —————
# Kasus 2026-08-10: attempt 1 (bfa9ab02) mendarat tapi confirm baca
# basi → retry; attempt 2 (b0a8b2f9) ditolak chain tapi last_claim_time
# SUDAH maju → dianggap mendarat; before_balance dibaca SEKALI di awal
# window (belum termasuk reward) → delta fallback mengukur reward asli.
# Tanpa baseline stabil, `before` attempt 2 sudah post-credit → delta 0
# → alert KLAIM MENDARAT palsu.
fresh_db('b18land')
b18 = {'n': 0}
def b18_confirm():
b18['n'] += 1
if b18['n'] <= 4: # baseline + 3 baca attempt 1 → basi (stale)
return (claim._utcnow() - timedelta(hours=2)
).strftime('%Y-%m-%dT%H:%M:%S')
return (claim._utcnow() - timedelta(hours=1)
).strftime('%Y-%m-%dT%H:%M:%S') # attempt 2 → maju
claim.fetch_producer_last_claim = b18_confirm
claim.reward_from_tx = lambda txid: None # tx ditolak → ⊥ terbaca
bal_seq = iter([Decimal('1000.0000'), Decimal('2679.3365')])
dist.fetch_balance = lambda: next(bal_seq) # 1000 = baseline window
sends = {'n': 0}
def send_land_then_reject():
sends['n'] += 1
if sends['n'] == 1:
return FakeSigned('txA1') # attempt 1: mendarat
return FakeSigned('txA2', exc=RuntimeError('already claimed'))
claim.build_claim_action = send_land_then_reject
dist.verify_txid = lambda txid: False # Hyperion lag kedua kali
notifs = []
_orig_ncA = telegram.notify_claim
_orig_ncuA = telegram.notify_claim_unmeasured
telegram.notify_claim_unmeasured = lambda t: notifs.append('unmeasured')
telegram.notify_claim = lambda r, f, w: notifs.append('claimed')
deadline = claim._utcnow() + timedelta(seconds=3600)
check('V56 klaim attempt-1-mendarat → poll_claim claimed',
claim.poll_claim(deadline) == 'claimed')
row = last_claim_row()
check('V56 reward asli terukur via delta (before_balance stabil)',
row[3] == 1679.3365 and row[4] == 167.9336
and row[5] == 'pending' and row[2] == 'txA2')
check('V56 ⊖ alert tak-terukur, notif sukses dipakai',
'unmeasured' not in notifs and 'claimed' in notifs)
telegram.notify_claim = _orig_ncA
telegram.notify_claim_unmeasured = _orig_ncuA
# ————— V45: reward dari isi tx (bpay+vpay) → prioritas —————
fresh_db('txreward')
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=25)
).strftime('%Y-%m-%dT%H:%M:%S')
dist.fetch_balance = lambda: Decimal('100.0000') # ⊥ dipakai
claim.build_claim_action = lambda: FakeSigned('txR')
dist.verify_txid = lambda txid: True
def fake_hyperion(url, params=None, timeout=None):
return type('R', (), {
'ok': True,
'raise_for_status': lambda self: None,
'json': lambda self: {
'executed': True,
'actions': [
{'act': {'account': 'vexcore', 'name': 'claimrewards',
'data': {'owner': 'databisnisid'}}},
{'act': {'account': 'vex.token', 'name': 'issue',
'data': {'to': 'vexcore',
'quantity': '82744.9200 VEX'}}},
{'act': {'account': 'vex.token', 'name': 'transfer',
'data': {'from': 'vex.bpay', 'to': 'databisnisid',
'quantity': '638.9244 VEX'}}},
{'act': {'account': 'vex.token', 'name': 'transfer',
'data': {'from': 'vex.vpay', 'to': 'databisnisid',
'quantity': '1200.3217 VEX'}}},
{'act': {'account': 'vex.token', 'name': 'transfer',
'data': {'from': 'databisnisid', 'to': 'someone',
'quantity': '5.0000 VEX'}}},
],
},
})()
dist.requests.get = fake_hyperion
claim.reward_from_tx = _orig_reward_from_tx
check('V45 reward_from_tx jumlah bpay+vpay',
claim.reward_from_tx('txR') == Decimal('1839.2461'))
dist.requests.get = fake_hyperion
check('V45 siklus: reward dari tx → claimed',
claim._try_claim_once() == 'claimed')
row = last_claim_row()
check('V45 reward tx 1839.2461 + fee 183.9246 pending',
row[3] == 1839.2461 and row[4] == 183.9246
and row[5] == 'pending' and row[2] == 'txR')
dist.requests.get = _req_get
claim.reward_from_tx = lambda txid: None
# ————— V60: reward_from_tx failover pool Hyperion —————
_orig_hynodes = list(dist.HYPERION_NODES)
claim.reward_from_tx = _orig_reward_from_tx # fungsi asli (bukan stub)
def fake_hyperion_failover(url, params=None, timeout=None):
if url.startswith('http://hyperion-dead'):
raise dist.requests.RequestException('hyperion mati')
return type('R', (), {
'ok': True,
'raise_for_status': lambda self: None,
'json': lambda self: {
'executed': True,
'actions': [
{'act': {'account': 'vex.token', 'name': 'transfer',
'data': {'from': 'vex.bpay', 'to': 'databisnisid',
'quantity': '10.0000 VEX'}}},
],
},
})()
dist.HYPERION_NODES = ['http://hyperion-dead', 'http://hyperion-alive']
dist.requests.get = fake_hyperion_failover
check('V60 reward_from_tx failover → host cadangan',
claim.reward_from_tx('txF') == Decimal('10.0000'))
dist.requests.get = _req_get
dist.HYPERION_NODES = _orig_hynodes
# ————— V45: reward tak terukur (tx gagal + delta gagal) → fee pending —————
fresh_db('unmeasured')
claim.fetch_producer_last_claim = lambda: (
claim._utcnow() - timedelta(hours=25)
).strftime('%Y-%m-%dT%H:%M:%S')
claim.reward_from_tx = lambda txid: None
bal_seq = iter([Decimal('100.0000'), None, None, None]) # before ok, after ⊥
dist.fetch_balance = lambda: next(bal_seq)
claim.build_claim_action = lambda: FakeSigned('txU')
dist.verify_txid = lambda txid: True
notifs = []
_orig_nc = telegram.notify_claim
_orig_ncu = telegram.notify_claim_unmeasured
def capture_ni(label):
notifs.append('unmeasured')
telegram.notify_claim_unmeasured = capture_ni
telegram.notify_claim = lambda r, f, w: notifs.append('claimed')
check('V45 reward tak terukur → claimed (baris dicatat)',
claim._try_claim_once() == 'claimed')
row = last_claim_row()
check('V45 fee pending utk cek manual (⊥ skipped)',
row[4] == 0.0 and row[5] == 'pending')
check('V45 notif cek manual (⊥ sukses-0)',
'unmeasured' in notifs and 'claimed' not in notifs)
telegram.notify_claim = _orig_nc
telegram.notify_claim_unmeasured = _orig_ncu
# ————— V32: klaim ditolak → retry; poll deadline lewat → timeout —————
fresh_db('timeout')
claim.build_claim_action = lambda: FakeSigned(
'txT', exc=RuntimeError('rejected'))
dist.verify_txid = lambda txid: False
dist.fetch_balance = lambda: Decimal('100.0000')
check('V32 klaim ditolak → retry',
claim._try_claim_once() == 'retry')
check('V32 deadline lewat → timeout',
claim.poll_claim(claim._utcnow() - timedelta(seconds=1))
== 'timeout')
# ————— resume: fee failed dari DB → step mengirimnya —————
fresh_db('resume')
db.record_claim('2026-08-05', 'txr', 10.0, 1.0, 'failed',
'2026-08-05T08:00:00')
dist.build_signed_transfer = lambda to, amount, memo: FakeSigned('txr1')
dist.verify_txid = lambda txid: True
check('V34 resume fee dari DB → fee_sent', claim.step() == 'fee_sent')
check('V34 resume → sent',
last_claim_row()[5] == 'sent' and last_claim_row()[6] == 'txr1')
# ————— V61: fee ditolak chain (HTTP-500 body, V46/B11) → ⊖ 'sent' —————
# `_send_fee` ⊖ boleh tandai 'sent' utk fee yang chain TOLAK — pyntelope
# mengembalikan 500 sbg body, ⊖ raise (V61-F1). Fee harus tersisa
# 'failed' utk diulang, ⊖ pernah 'sent' dgn txid yg ⊥ mendarat.
fresh_db('feereject')
db.record_claim('2026-08-05', 'txz', 10.0, 1.0, 'failed',
'2026-08-05T08:00:00')
class FakeFeeReject:
def id(self):
return 'txfeej'
def send(self):
return {'code': 500, 'error': {'what': 'duplicate transaction'}}
dist.build_signed_transfer = (lambda to, amount, memo:
FakeFeeReject())
dist.verify_txid = lambda txid: False # txid penolakan ⊥ mendarat
check('V61 fee rejection-500 → fee_failed (⊥ sent)',
claim.step() == 'fee_failed')
row = last_claim_row()
check('V61 fee rejection → status failed (diulang), ⊖ sent',
row[5] == 'failed')
dist.build_signed_transfer = lambda to, amount, memo: FakeSigned('txr1')
# ————— V36: klaim reward → kanal INTERNAL saja, ⊥ komunitas —————
tg = {'posts': []}
def tg_post(url, json=None, timeout=None):
tg['posts'].append((url, json['chat_id'], json['text']))
return fake_post({})
telegram.TELEGRAM_BOT_TOKEN = '123:TEST'
telegram.TELEGRAM_COMMUNITY_CHAT_IDS = ['-1001']
telegram.TELEGRAM_INTERNAL_CHAT_IDS = ['-1002']
telegram.requests.post = tg_post
telegram.notify_claim('10.0000', '1.0000', 'bpdbsjasprod')
telegram.notify_claim_failure(RuntimeError('net down'))
check('V36 klaim → internal saja',
len(tg['posts']) == 2
and all(p[1] == '-1002' for p in tg['posts']))
check('V36 ⊥ komunitas utk klaim',
not any(p[1] == '-1001' for p in tg['posts']))
check('V36 isi notif klaim',
'KLAIM REWARD SUKSES' in tg['posts'][0][2]
and 'KLAIM GAGAL' in tg['posts'][1][2])
finally:
claim.time.sleep = time.sleep
telegram.TELEGRAM_BOT_TOKEN = _tg_token
telegram.TELEGRAM_COMMUNITY_CHAT_IDS = _tg_comm
telegram.TELEGRAM_INTERNAL_CHAT_IDS = _tg_int
telegram.requests.post = _tg_post
dist.requests.get = _req_get
print('\nSEMUA UJI PASS')
if __name__ == '__main__':
import time
sys.exit(main())