red-team send-path hardening: hold pada verify False/None, _send_rejected whitelist, fee guard, non-dict skip (V61/B20-B23)
This commit is contained in:
1 parent
69704fa23d
commit
d6b5927307
8 files changed
+182
-32
No files matched your search
+53
-21
@@ -123,7 +123,12 @@ def _get_json(url, params=None, max_retries=3, timeout=10):
|
||||
try:
|
||||
resp = requests.get(f'{node}{url}', params=params, timeout=timeout)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
data = resp.json()
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
# V61: 200 tapi bentuk tak dikenal (list/string/proxy salah)
|
||||
# ⊖ dianggap sukses — lewati host ini, coba cadangan.
|
||||
continue
|
||||
except requests.RequestException:
|
||||
continue
|
||||
if attempt < max_retries - 1:
|
||||
@@ -142,20 +147,46 @@ def verify_txid(txid):
|
||||
(bukan satu host hiccup) — hold V59 jadi jarang palsu.
|
||||
"""
|
||||
data = _get_json('/v2/history/get_transaction', params={'id': txid})
|
||||
if data is None:
|
||||
if not isinstance(data, dict): # V61: bentuk tak dikenal → ⊖ asumsi apapun
|
||||
return None
|
||||
return bool(data.get('executed'))
|
||||
|
||||
|
||||
def _verify_settled(txid, retries=3, delay=2):
|
||||
"""V61: verifikasi txid dgn settle-retry singkat — tx yang BARU mendarat
|
||||
bisa belum terindeks Hyperion (`executed: false` sesaat, pola V50/B14 di
|
||||
klaim). Baca `verify_txid` beberapa kali sebelum menyimpulkan tak-mendarat.
|
||||
|
||||
None (SEMUA host Hyperion mati) → langsung False (⊖ buang waktu sleep utk
|
||||
pool yang mati — pemanggil TAHAN). → True bila pernah `executed`; False
|
||||
bila tak bisa dikonfirmasi (pemanggil TAHAN, ⊖ resend). """
|
||||
for _ in range(retries):
|
||||
landed = verify_txid(txid)
|
||||
if landed is True:
|
||||
return True
|
||||
if landed is None:
|
||||
return False
|
||||
time.sleep(delay)
|
||||
return False
|
||||
|
||||
|
||||
def _send_rejected(resp):
|
||||
"""V59: deteksi penolakan chain dari respons `send()`.
|
||||
"""V61: deteksi penolakan chain dari respons `send()` — WHITELIST.
|
||||
|
||||
pyntelope ⊥ raise pada HTTP 500 — chain rejection datang sbg body JSON
|
||||
(pola V46/B11 di klaim): `{"code":500,"message":...,"error":{...}}` vs
|
||||
sukses `{"transaction_id":..., "processed":...}`. → True bila bentuk
|
||||
penolakan (ada `error`), False bila sukses/bentuk tak dikenal.
|
||||
(pola V46/B11). Sukses HANYA bila respons berbentuk dict YANG MEMILIKI
|
||||
`transaction_id` (nodeos selalu menyertakan pada push yang diterima);
|
||||
bentuk penolakan (`{"code":500,"error":...}`), bentuk tak dikenal, maupun
|
||||
`soft_fail`/`delayed` (200 tapi receipt ⊥ `executed`) → dianggap TOLAK.
|
||||
→ True bila harus diperlakukan sbg gagal (⊥ tandai `sent` utk tx yang
|
||||
belum tentu mendarat); False bila terbukti sukses sah.
|
||||
"""
|
||||
return isinstance(resp, dict) and resp.get('error') is not None
|
||||
if not isinstance(resp, dict) or not resp.get('transaction_id'):
|
||||
return True
|
||||
receipt = ((resp.get('processed') or {}).get('receipt') or {}).get('status')
|
||||
if receipt is not None and receipt != 'executed':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def build_signed_transfer(to, amount, memo):
|
||||
@@ -276,27 +307,28 @@ def run_distribution(dry_run=False):
|
||||
break
|
||||
except Exception as exc:
|
||||
# Timeout/penolakan setelah broadcast → tx mungkin mendarat.
|
||||
# Verifikasi sebelum resend agar ⊥ ganda (V22). Bila Hyperion
|
||||
# tak bisa dipastikan → TAHAN (tandai failed, jangan resend) —
|
||||
# V59: hold sungguhan (⊥ break hanya di attempt terakhir).
|
||||
# Verifikasi SEBELUM resend agar ⊥ ganda (V22). V61: HANYA
|
||||
# `executed` yang boleh dilanjutkan; False (Hyperion bilang tak
|
||||
# mendarat — bisa lag indeks V50/B14) MAUPUN None (Hyperion
|
||||
# down) → TAHAN (tandai failed, ⊖ resend): resend membawa
|
||||
# tapos/expiration baru → txid baru → ganda.
|
||||
if txid is not None:
|
||||
landed = verify_txid(txid)
|
||||
landed = _verify_settled(txid)
|
||||
if landed is True:
|
||||
db.update_payment_status(pid, 'sent', txid=txid)
|
||||
sent_amount += float(share)
|
||||
print(f'[OK] {owner}: {share} VEX '
|
||||
f'(terverifikasi, {txid[:16]}...)', flush=True)
|
||||
break
|
||||
if landed is None:
|
||||
print(f'[Peringatan] Verifikasi txid tak tersedia '
|
||||
f'untuk {owner} — ditahan (⊥ resend)',
|
||||
flush=True)
|
||||
db.update_payment_status(pid, 'failed', txid=txid,
|
||||
error=str(exc))
|
||||
failed.append((owner, exc))
|
||||
print(f'[DITAHAN] {owner}: {share} VEX — {exc}',
|
||||
flush=True)
|
||||
break
|
||||
print(f'[Peringatan] Verifikasi txid tak tersedia '
|
||||
f'untuk {owner} — ditahan (⊥ resend)',
|
||||
flush=True)
|
||||
db.update_payment_status(pid, 'failed', txid=txid,
|
||||
error=str(exc))
|
||||
failed.append((owner, exc))
|
||||
print(f'[DITAHAN] {owner}: {share} VEX — {exc}',
|
||||
flush=True)
|
||||
break
|
||||
if attempt == DISTRIBUTE_MAX_ATTEMPTS:
|
||||
db.update_payment_status(pid, 'failed', txid=txid,
|
||||
error=str(exc))
|
||||
|
||||
Reference in new issue
Block a user