refine blockchain integration and protocols

This commit is contained in:
Dobromir Popov
2026-07-02 21:14:15 +02:00
parent c3d22c895e
commit 416ceba0f6
8 changed files with 306 additions and 6 deletions

View File

@@ -0,0 +1,33 @@
# USDT-direct custodial settlement with pending-balance collateral
Nodes are paid directly in USDT — 90% of inference fees, with 10% retained as the protocol cut. TAI (ADR-0002) is **deferred, not cancelled**: the reward token, revenue-backed floor, and halving curve remain the roadmap once volume exists, and the accumulated protocol cut is the future TAI liquidity source. Nothing in this design blocks minting TAI later (an SPL mint is a one-command operation); this ADR amends ADR-0002's settlement mechanics only.
## The design
**Custodial treasury.** A single project-owned Solana wallet holds all funds. Clients deposit USDT into it; a deposit watcher credits their API-key ledger balance off-chain. Node payouts are batched SPL transfers from the treasury. No Anchor/Rust programs are required — the entire on-chain surface is plain SPL token transfers, extending ADR-0007's contract-boundary approach with a real Solana adapter behind the same interface.
**Off-chain ledger, periodic on-chain settlement.** Trackers meter every request against the client's ledger balance and accrue node earnings as a pending balance. Settlement follows the mining-pool standard — pay a node when `pending ≥ payout_threshold` OR `time_since_last_payout ≥ max_period`, whichever fires first, with a dust floor. Dev defaults: period 60s, threshold ≈ 0 (so every run is verifiable on-chain). Prod defaults: period 24h, threshold a few USDT. Both are dynamic config so the period can grow with volume to avoid chain overhead.
**Raft leader settles.** In the tracker hive, only the current Raft leader runs the settlement loop; followers replicate the ledger. The treasury keypair is loaded only on settlement-capable trackers (operator-designated). Third-party trackers can join the mesh for routing but never hold the key.
**Pricing.** Clients are charged a per-1K-tokens USDT price set per model in tracker config. Revenue per request is split among the nodes that served it proportional to work units (layers × tokens), reusing existing attribution.
**Penalty = pending-balance forfeiture (amends ADR-0003).** The validator still re-runs ~5% of jobs. A caught cheater forfeits their entire pending balance to the treasury and receives a strike; three strikes bans the wallet. Because settlement is periodic, the pending balance itself is the collateral — no upfront stake deposit, preserving zero-friction node onboarding. With daily settlement, pending ≈ a day's earnings ≫ 20× the per-job gain at a 5% check rate, so cheating has negative expected value. The probationary period (ADR-0003 / issue 08) is retained as the anti-sybil re-entry cost.
**Cluster.** Development targets **devnet** (supersedes the "testnet, never devnet" note in issue 06 — both are free; devnet is the ecosystem standard for app development with reliable faucets). Real USDT exists only on mainnet, so devnet uses a self-created mock-USDT SPL mint (6 decimals); the mint address is config, making mainnet cutover a config change. CI uses `solana-test-validator` or the local deterministic adapter.
## Considered options
- **Anchor escrow program now**: trustless, but requires the Rust/Anchor toolchain and can't ship today; the custodial trust assumption is acceptable pre-mainnet — deferred.
- **Any tracker settles via lease**: most decentralized, but every mesh member would need the treasury key — rejected while custodial.
- **USDT stake deposits for nodes**: stronger collateral, but adds onboarding friction that contradicts the mining-style UX; may return later as optional stake-for-priority — deferred.
- **Keep TAI settlement (ADR-0002 as written)**: backing-price calc and buyback endpoint are too much machinery before there is volume — deferred.
- **USDT-direct custodial settlement with pending-balance collateral**: shippable immediately, verifiable on-chain end-to-end on devnet — **chosen**.
## Consequences
- The entire payment system works with zero smart contracts; the first Anchor program can replace the custodial treasury later behind the same `packages/contracts` boundary.
- Clients and nodes must trust the treasury key until then; this is explicit and acceptable pre-mainnet.
- Node collateral scales with the settlement period: shortening the period in prod weakens the fraud deterrent, so period changes must consider both chain overhead and collateral size.
- A node caught cheating immediately after a payout has little pending balance to forfeit — the strike/ban system covers this window.
- The 10% protocol cut accumulates in the treasury as the future TAI liquidity reserve.

View File

@@ -0,0 +1,25 @@
Status: ready-for-agent
# 31 — Billing ledger: per-token pricing, 90/10 split, pending balances
## What to build
Tracker-side off-chain billing per ADR-0015. Each model preset gets a USDT `price_per_1k_tokens` in tracker config. When a request completes, the tracker debits the client's API-key ledger balance (`price × total_tokens / 1000`) and credits 90% to the serving nodes' pending balances proportional to work units (layers × tokens, reusing the existing `ComputeAttribution`), with 10% accruing to the protocol cut. API keys with insufficient balance are rejected with HTTP 402 before any routing happens — no free work.
The ledger persists in the existing tracker SQLite store and replicates across the tracker hive so any follower can serve balance reads.
This story is pure off-chain accounting — no Solana calls.
## Acceptance criteria
- [ ] Per-model `price_per_1k_tokens` in tracker config with sane defaults
- [ ] Completed request debits client ledger: `price × total_tokens / 1000`
- [ ] 90% split across serving nodes by work_units; 10% accrues to `protocol_cut`
- [ ] Insufficient balance → HTTP 402 before routing
- [ ] Balances survive tracker restart (SQLite) and replicate to hive followers
- [ ] Unit tests: single-node route, 3-node split, exhausted balance, restart persistence
## Blocked by
- `23-heartbeat-stats.md`
- `25-rolling-rpm-stats.md`

View File

@@ -0,0 +1,28 @@
Status: ready-for-agent
# 32 — Devnet custodial treasury: mock-USDT mint + deposit watcher
## What to build
The real Solana adapter behind the `packages/contracts` boundary, custodial model per ADR-0015. All on-chain surface is plain SPL transfers — no Anchor programs.
**Setup script** (`scripts/devnet_setup.py`): creates the mock-USDT SPL mint (6 decimals, matching real USDT) and the treasury token account on devnet, airdropping SOL for fees, and prints the `.env.devnet` values (mint address, RPC URL, treasury keypair path).
**Wallet binding**: `POST /v1/wallet/register` binds a client wallet pubkey to an API key.
**Deposit watcher**: polls the treasury token account for confirmed incoming USDT transfers and credits the sending wallet's bound API-key ledger balance. Transaction signatures are deduplicated so replayed/re-observed transfers credit exactly once.
Note: this supersedes the "testnet, never devnet" note in issue 06 (see ADR-0015) — devnet is the ecosystem standard for app development and real USDT exists on neither cluster.
## Acceptance criteria
- [ ] `scripts/devnet_setup.py` creates mint + treasury ATA and prints `.env.devnet` values
- [ ] `POST /v1/wallet/register` binds client wallet pubkey to API key
- [ ] Deposit watcher credits ledger within one poll interval of a confirmed transfer
- [ ] Duplicate/replayed transactions credit exactly once (signature dedupe)
- [ ] Local `solana-test-validator` integration test covers mint → deposit → credit
- [ ] Deterministic local adapter still works for CI without any validator
## Blocked by
- `31-billing-ledger.md`

View File

@@ -0,0 +1,27 @@
Status: ready-for-agent
# 33 — Settlement loop: leader-only batched USDT payouts
## What to build
The on-chain settlement loop per ADR-0015, following the mining-pool standard: pay a node when `pending ≥ payout_threshold` OR `time_since_last_payout ≥ max_period`, whichever fires first, with a dust floor so no payout is smaller than it is worth.
Only the current Raft leader runs the loop; followers replicate the ledger but never sign. The treasury keypair is loaded only on settlement-capable trackers. Payouts are batched SPL transfers treasury → node wallets on devnet.
Config (all dynamic, so the period can grow with volume): dev defaults `period=60s, threshold≈0` so every run is verifiable on-chain; prod defaults `period=24h, threshold=a few USDT`.
Settlement history (settlement id, tx signature, node wallet, amount, timestamp) persists in SQLite, replicates across the hive, and is queryable over HTTP. Retries are idempotent by settlement id — a failed or timed-out transaction never double-pays.
## Acceptance criteria
- [ ] Only the Raft leader settles; followers never sign (asserted in a 3-tracker test)
- [ ] Trigger: `pending ≥ threshold` OR `elapsed ≥ max_period`; dust floor respected
- [ ] Batched SPL transfers land on devnet; pending balances zeroed atomically with recorded tx signature
- [ ] Failed/timeout transactions retry without double-pay (idempotent by settlement id)
- [ ] Settlement history queryable via tracker HTTP endpoint
- [ ] End-to-end devnet test: fund client → run inference → node wallet USDT balance increases
## Blocked by
- `19-binary-data-plane-and-peer-weight-transfer.md`
- `32-devnet-treasury-deposits.md`

View File

@@ -0,0 +1,28 @@
Status: ready-for-agent
# 34 — Hardened proof-of-work: pending-balance forfeiture penalty
## What to build
Wire the validator's ~5% sampling (issue 07) to the new penalty per ADR-0015. On confirmed output divergence:
1. The node's **entire pending balance is forfeited** to the protocol cut, in the same ledger transaction as the strike (no window where a payout can race the penalty).
2. A **strike** is recorded; the third strike **bans** the wallet — registration rejected, excluded from all routes, and any unpaid pending balance is never paid out.
The probationary period (first N jobs unpaid, default 50) is retained as the anti-sybil re-entry cost, and the node CLI shows remaining probation jobs.
Why this deters cheating: settlement is periodic, so the pending balance itself is the collateral — no upfront stake deposit, zero onboarding friction. At a 5% check rate a cheater is caught once per ~20 fraudulent jobs, so the penalty must exceed ~20× the per-job gain; with daily settlement, pending ≈ a full day's earnings, well above that bar. Document this math in the validator README.
## Acceptance criteria
- [ ] Divergence → pending balance forfeited to `protocol_cut` atomically with the strike
- [ ] 3rd strike bans wallet: registration rejected, excluded from all routes
- [ ] Banned wallet's unpaid pending balance is not paid at next settlement
- [ ] Probation: first N jobs (default 50) accrue no pending balance; node CLI shows remaining
- [ ] Integration test: deliberately-bad node loses pending, accrues strikes, banned within 60 requests
- [ ] Forfeiture events visible in tracker logs and settlement history
## Blocked by
- `07-fraud-detection-slash.md`
- `31-billing-ledger.md`

View File

@@ -0,0 +1,34 @@
Status: ready-for-agent
# 35 — Tracker web dashboard
## What to build
A read-only web dashboard served by the tracker itself at `GET /dashboard` — single page, plain HTML/JS polling the tracker's HTTP endpoints, static assets embedded in the tracker package. No new build toolchain.
Because the tracker hive replicates ledger and registry state, **any tracker in the mesh — leader or follower — can serve the dashboard** from its own state.
Panels:
- Hive membership and current Raft leader
- Node registry: health, scores, coverage map per model
- Client ledger balances
- Node pending balances and next-payout ETA
- Settlement history with devnet explorer links (tx signatures)
- Strikes / bans / forfeiture events
- Rolling RPM stats per model
Write operations (editing prices, manual settlement trigger) are deferred to a later story.
## Acceptance criteria
- [ ] `GET /dashboard` serves the UI from any tracker (leader or follower)
- [ ] All seven panels render with live data
- [ ] Auto-refresh ≤5s without page reload
- [ ] No new build toolchain — static assets embedded in the tracker package
- [ ] Works against a 3-tracker hive in the two-machine LAN test setup
## Blocked by
- `31-billing-ledger.md`
- `33-settlement-loop.md`

View File

@@ -710,6 +710,103 @@
"US-014",
"US-019"
]
},
{
"id": "US-031",
"title": "31 — Billing ledger: per-token pricing, 90/10 split, pending balances",
"description": "Tracker-side off-chain billing per ADR-0015. Each model preset has a USDT price per 1K tokens in tracker config. On request completion the tracker debits the client's API-key ledger balance and credits 90% to the serving nodes' pending balances proportional to work_units (layers × tokens), 10% to the protocol cut. Requests from API keys with insufficient balance are rejected with 402. Ledger persists in the existing tracker SQLite and replicates across the tracker hive.",
"acceptanceCriteria": [
"Per-model price_per_1k_tokens in tracker config with sane defaults",
"Completed request debits client ledger: price × total_tokens / 1000",
"90% split across serving nodes by work_units; 10% accrues to protocol_cut",
"Insufficient balance → HTTP 402 before routing (no free work)",
"Balances survive tracker restart (SQLite) and replicate to hive followers",
"Unit tests: single-node route, 3-node split, exhausted balance, restart persistence"
],
"priority": 31,
"status": "open",
"notes": "Pure off-chain — no Solana calls in this story. Reuses ComputeAttribution/work_units from packages/contracts.",
"dependsOn": [
"US-023",
"US-025"
]
},
{
"id": "US-032",
"title": "32 — Devnet custodial treasury: mock-USDT mint + deposit watcher",
"description": "Real Solana adapter behind the packages/contracts boundary, custodial model per ADR-0015. A setup script creates the mock-USDT SPL mint (6 decimals) and treasury token account on devnet. Clients register a wallet pubkey against their API key; the deposit watcher polls the treasury token account and credits the sender's API-key ledger balance on confirmed USDT transfers. Mint address, RPC URL, and treasury keypair path are config (.env.devnet).",
"acceptanceCriteria": [
"scripts/devnet_setup.py creates mint + treasury ATA and prints .env.devnet values",
"POST /v1/wallet/register binds client wallet pubkey to API key",
"Deposit watcher credits ledger within one poll interval of a confirmed transfer",
"Duplicate/replayed transactions are credited exactly once (signature dedupe)",
"Local solana-test-validator integration test covers mint→deposit→credit flow",
"Deterministic local adapter still works for CI without any validator"
],
"priority": 32,
"status": "open",
"notes": "Supersedes 'testnet never devnet' note in issue 06. solana-py + spl-token client libs.",
"dependsOn": [
"US-031"
]
},
{
"id": "US-033",
"title": "33 — Settlement loop: leader-only batched USDT payouts",
"description": "The Raft leader runs the settlement loop per ADR-0015: pay a node when pending ≥ payout_threshold OR time since its last payout ≥ max_period, whichever fires first, with a dust floor. Payouts are batched SPL transfers treasury→node wallet on devnet. Dev defaults: period 60s, threshold ~0; prod defaults: period 24h, threshold few USDT — all dynamic config. Settlement history (tx signature, node, amount, epoch) persists and replicates.",
"acceptanceCriteria": [
"Only the Raft leader settles; followers never sign (asserted in a 3-tracker test)",
"Trigger: pending ≥ threshold OR elapsed ≥ max_period; dust floor respected",
"Batched SPL transfers land on devnet; pending balances zeroed atomically with recorded tx signature",
"Failed/timeout transactions retry without double-pay (idempotent by settlement id)",
"Settlement history queryable via tracker HTTP endpoint",
"End-to-end devnet test: fund client → run inference → observe node wallet USDT balance increase"
],
"priority": 33,
"status": "open",
"notes": "Mining-pool standard: threshold + max-interval, dust floor. Treasury key only on settlement-capable trackers.",
"dependsOn": [
"US-019",
"US-032"
]
},
{
"id": "US-034",
"title": "34 — Hardened proof-of-work: pending-balance forfeiture penalty",
"description": "Wire the validator's ~5% sampling to the new penalty per ADR-0015: on confirmed output divergence the node's entire pending balance is forfeited to the protocol cut, a strike is recorded, and three strikes ban the wallet (tracker rejects registration and excludes from routes). Probationary period (first N jobs unpaid) is retained as the re-entry cost. Penalty math documented: at 5% sampling, forfeiting ~a day's pending earnings ≫ 20× per-job cheat gain.",
"acceptanceCriteria": [
"Validator divergence → pending balance forfeited to protocol_cut in the same ledger transaction as the strike",
"3rd strike bans wallet: registration rejected, excluded from all routes",
"Banned wallet's unpaid pending balance is not paid out at next settlement",
"Probation: first N jobs (default 50) accrue no pending balance; node CLI shows remaining",
"Integration test: deliberately-bad node loses pending, accrues strikes, gets banned within 60 requests",
"Slash/forfeiture events visible in tracker logs and settlement history"
],
"priority": 34,
"status": "open",
"notes": "No stake deposit — pending balance IS the collateral. Amends ADR-0003 penalty; sampling mechanics unchanged.",
"dependsOn": [
"US-031"
]
},
{
"id": "US-035",
"title": "35 — Tracker web dashboard",
"description": "Web dashboard served by the tracker (single-page, no build step — plain HTML/JS polling tracker HTTP endpoints). Shows: hive membership and Raft leader, node registry with health/scores/coverage map, client ledger balances, node pending balances, settlement history with devnet explorer links, strikes/bans, and rolling RPM stats. Read-only in this story; every tracker in the mesh can serve it from its replicated state.",
"acceptanceCriteria": [
"GET /dashboard serves the UI from any tracker (leader or follower)",
"Panels: hive/leader, nodes+coverage, client balances, pending payouts, settlement history (with tx links), strikes/bans, RPM stats",
"Auto-refresh ≤5s without page reload",
"No new build toolchain — static assets embedded in the tracker package",
"Works against a 3-tracker hive in the two-machine LAN test setup"
],
"priority": 35,
"status": "open",
"notes": "CLI dashboard already exists from US-016; this is the web counterpart. Write ops (price config, manual settle) deferred.",
"dependsOn": [
"US-031",
"US-033"
]
}
],
"metadata": {
@@ -724,4 +821,4 @@
"blocked": "Waiting on unresolved external dependency"
}
}
}
}