feat: USDT reward system — billing ledger, devnet treasury, settlement loop, forfeiture PoW, tracker dashboard (US-030…US-035, ADR-0015)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
223
tests/test_devnet_treasury.py
Normal file
223
tests/test_devnet_treasury.py
Normal file
@@ -0,0 +1,223 @@
|
||||
"""US-032: devnet custodial treasury — wallet binding + deposit watcher.
|
||||
|
||||
The deposit watcher polls the treasury token account and credits the sending
|
||||
wallet's bound API key exactly once per transaction signature. The watcher is
|
||||
exercised against a fake treasury (hermetic CI, ADR-0007); the real
|
||||
solana-test-validator flow runs only when the binary is installed.
|
||||
"""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
import pytest
|
||||
|
||||
from meshnet_tracker.billing import BillingLedger
|
||||
from meshnet_tracker.server import TrackerServer
|
||||
|
||||
|
||||
class _FakeDeposit:
|
||||
def __init__(self, signature, sender_wallet, amount_usdt):
|
||||
self.signature = signature
|
||||
self.sender_wallet = sender_wallet
|
||||
self.amount_usdt = amount_usdt
|
||||
|
||||
|
||||
class _FakeTreasury:
|
||||
"""Stands in for SolanaCustodialTreasury: a queue of confirmed deposits."""
|
||||
|
||||
def __init__(self):
|
||||
self.deposits: list[_FakeDeposit] = []
|
||||
|
||||
def list_new_deposits(self, is_seen, *, limit=200):
|
||||
return [d for d in self.deposits if not is_seen(d.signature)]
|
||||
|
||||
|
||||
def _post_json(url: str, payload: dict, headers: dict | None = None) -> dict:
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=json.dumps(payload).encode(),
|
||||
headers={"Content-Type": "application/json", **(headers or {})},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req) as r:
|
||||
return json.loads(r.read())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def watched_tracker():
|
||||
ledger = BillingLedger(starting_credit=0.0, default_price_per_1k=0.02)
|
||||
treasury = _FakeTreasury()
|
||||
tracker = TrackerServer(
|
||||
billing=ledger,
|
||||
treasury=treasury,
|
||||
deposit_poll_interval=0.1,
|
||||
)
|
||||
port = tracker.start()
|
||||
yield f"http://127.0.0.1:{port}", ledger, treasury
|
||||
tracker.stop()
|
||||
|
||||
|
||||
def _wait_for(predicate, timeout=3.0):
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if predicate():
|
||||
return True
|
||||
time.sleep(0.05)
|
||||
return False
|
||||
|
||||
|
||||
def test_wallet_register_requires_auth(watched_tracker):
|
||||
tracker_url, _, _ = watched_tracker
|
||||
with pytest.raises(urllib.error.HTTPError) as exc_info:
|
||||
_post_json(f"{tracker_url}/v1/wallet/register", {"wallet": "So1anaWa11et111"})
|
||||
assert exc_info.value.code == 401
|
||||
|
||||
|
||||
def test_deposit_credits_bound_api_key_exactly_once(watched_tracker):
|
||||
tracker_url, ledger, treasury = watched_tracker
|
||||
reply = _post_json(
|
||||
f"{tracker_url}/v1/wallet/register",
|
||||
{"wallet": "So1anaWa11et111"},
|
||||
headers={"Authorization": "Bearer client-key-1"},
|
||||
)
|
||||
assert reply["bound"] is True
|
||||
|
||||
treasury.deposits.append(_FakeDeposit("sig-1", "So1anaWa11et111", 25.0))
|
||||
assert _wait_for(lambda: ledger.get_client_balance("client-key-1") > 0)
|
||||
assert ledger.get_client_balance("client-key-1") == pytest.approx(25.0)
|
||||
|
||||
# the same signature keeps being reported by the RPC — credited once only
|
||||
time.sleep(0.4)
|
||||
assert ledger.get_client_balance("client-key-1") == pytest.approx(25.0)
|
||||
|
||||
# replay through the ledger API directly is also a no-op
|
||||
assert ledger.credit_deposit("client-key-1", 25.0, "sig-1") is None
|
||||
|
||||
|
||||
def test_unbound_wallet_deposit_is_not_credited(watched_tracker):
|
||||
_, ledger, treasury = watched_tracker
|
||||
treasury.deposits.append(_FakeDeposit("sig-2", "UnknownWallet999", 10.0))
|
||||
time.sleep(0.4)
|
||||
assert ledger.snapshot()["clients"] == {}
|
||||
|
||||
|
||||
def test_binding_replicates_via_events():
|
||||
a = BillingLedger(starting_credit=0.0)
|
||||
b = BillingLedger(starting_credit=0.0)
|
||||
a.bind_wallet("key-9", "WalletNine")
|
||||
a.credit_deposit("key-9", 3.0, "sig-9")
|
||||
events, _ = a.events_since(0)
|
||||
b.apply_events(events)
|
||||
assert b.api_key_for_wallet("WalletNine") == "key-9"
|
||||
assert b.get_client_balance("key-9") == pytest.approx(3.0)
|
||||
# deposit event id embeds the signature → replicated replay is a no-op
|
||||
assert b.apply_events(events) == 0
|
||||
|
||||
|
||||
def test_solana_adapter_derives_treasury_accounts():
|
||||
"""Adapter smoke test without any RPC round-trip."""
|
||||
pytest.importorskip("solders")
|
||||
token_instructions = pytest.importorskip("spl.token.instructions")
|
||||
if not hasattr(token_instructions, "InitializeMintParams"):
|
||||
pytest.skip("installed spl.token lacks InitializeMintParams")
|
||||
from solders.keypair import Keypair
|
||||
|
||||
from meshnet_contracts.solana_adapter import SolanaCustodialTreasury
|
||||
|
||||
keypair = Keypair()
|
||||
treasury = SolanaCustodialTreasury(
|
||||
"http://127.0.0.1:8899",
|
||||
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", # real USDT mint address shape
|
||||
keypair,
|
||||
)
|
||||
assert treasury.treasury_wallet == str(keypair.pubkey())
|
||||
assert treasury.treasury_token_account # deterministic ATA derivation
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
shutil.which("solana-test-validator") is None,
|
||||
reason="solana-test-validator not installed",
|
||||
)
|
||||
def test_mint_deposit_credit_flow_against_local_validator(tmp_path):
|
||||
"""Full mint → deposit → credit flow on a local validator (US-032)."""
|
||||
import subprocess
|
||||
|
||||
from solders.keypair import Keypair
|
||||
|
||||
from meshnet_contracts.solana_adapter import RpcClient, SolanaCustodialTreasury
|
||||
|
||||
rpc_url = "http://127.0.0.1:8899"
|
||||
ledger_dir = tmp_path / "ledger"
|
||||
validator = subprocess.Popen(
|
||||
["solana-test-validator", "--ledger", str(ledger_dir), "--quiet",
|
||||
"--rpc-port", "8899", "--reset"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
try:
|
||||
assert _wait_for(lambda: _rpc_alive(RpcClient(rpc_url)), timeout=60)
|
||||
|
||||
treasury_kp, client_kp = Keypair(), Keypair()
|
||||
bootstrap = SolanaCustodialTreasury(
|
||||
rpc_url, "11111111111111111111111111111111", treasury_kp,
|
||||
)
|
||||
client_bootstrap = SolanaCustodialTreasury(
|
||||
rpc_url, "11111111111111111111111111111111", client_kp,
|
||||
)
|
||||
bootstrap.request_airdrop(2.0)
|
||||
client_bootstrap.request_airdrop(2.0)
|
||||
assert _wait_for(lambda: bootstrap.get_sol_balance() > 0, timeout=30)
|
||||
assert _wait_for(lambda: client_bootstrap.get_sol_balance() > 0, timeout=30)
|
||||
|
||||
treasury, mint = bootstrap.create_mock_usdt_mint()
|
||||
assert _wait_for(
|
||||
lambda: treasury._account_exists( # noqa: SLF001 — test-only poke
|
||||
__import__("solders.pubkey", fromlist=["Pubkey"]).Pubkey.from_string(mint)
|
||||
),
|
||||
timeout=30,
|
||||
)
|
||||
treasury.ensure_token_account(treasury.treasury_wallet)
|
||||
treasury.mint_mock_usdt(str(client_kp.pubkey()), 50.0)
|
||||
|
||||
# the client depositing into the treasury is just a transfer the
|
||||
# client signs — reuse send_payouts from an adapter bound to their key
|
||||
client_side = SolanaCustodialTreasury(rpc_url, mint, client_kp)
|
||||
assert _wait_for(
|
||||
lambda: _try_send(client_side, treasury.treasury_wallet, 25.0), timeout=30,
|
||||
)
|
||||
|
||||
ledger = BillingLedger(starting_credit=0.0)
|
||||
ledger.bind_wallet("api-key-e2e", str(client_kp.pubkey()))
|
||||
|
||||
def _credited():
|
||||
for dep in treasury.list_new_deposits(
|
||||
lambda sig: ledger.has_event(f"deposit-{sig}")
|
||||
):
|
||||
key = ledger.api_key_for_wallet(dep.sender_wallet)
|
||||
if key:
|
||||
ledger.credit_deposit(key, dep.amount_usdt, dep.signature)
|
||||
return ledger.get_client_balance("api-key-e2e") > 0
|
||||
|
||||
assert _wait_for(_credited, timeout=60)
|
||||
assert ledger.get_client_balance("api-key-e2e") == pytest.approx(25.0)
|
||||
finally:
|
||||
validator.terminate()
|
||||
validator.wait(timeout=10)
|
||||
|
||||
|
||||
def _try_send(adapter, wallet: str, amount: float) -> bool:
|
||||
try:
|
||||
adapter.send_payouts([(wallet, amount)])
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _rpc_alive(rpc) -> bool:
|
||||
try:
|
||||
return rpc.call("getHealth") == "ok"
|
||||
except Exception:
|
||||
return False
|
||||
Reference in New Issue
Block a user