git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
840 lines
21 KiB
Markdown
840 lines
21 KiB
Markdown
# Edge-Net QDAG Credit System Architecture
|
|
|
|
## Table of Contents
|
|
|
|
1. [Overview](#overview)
|
|
2. [Architecture Components](#architecture-components)
|
|
3. [Credit Flow](#credit-flow)
|
|
4. [Security Model](#security-model)
|
|
5. [Multi-Device Synchronization](#multi-device-synchronization)
|
|
6. [API Reference](#api-reference)
|
|
7. [Data Models](#data-models)
|
|
8. [Implementation Details](#implementation-details)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The Edge-Net QDAG (Quantum Directed Acyclic Graph) credit system is a **secure, distributed ledger** for tracking computational contributions across the Edge-Net network. Credits (denominated in rUv) are earned by processing tasks and stored in a **Firestore-backed persistent ledger** that serves as the **single source of truth**.
|
|
|
|
### Key Principles
|
|
|
|
1. **Identity-Based Ledger**: Credits are tied to **Ed25519 public keys**, not device IDs
|
|
2. **Relay Authority**: Only the relay server can credit accounts via verified task completions
|
|
3. **No Self-Reporting**: Clients cannot increase their own credit balances
|
|
4. **Multi-Device Sync**: Same public key = same balance across all devices
|
|
5. **Firestore Truth**: The QDAG ledger in Firestore is the authoritative state
|
|
|
|
---
|
|
|
|
## Architecture Components
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Edge-Net QDAG System │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
│ Dashboard │◄───────►│ Relay │◄───────►│ Firestore │
|
|
│ (Client) │ WSS │ Server │ QDAG │ Database │
|
|
└──────────────┘ └──────────────┘ └──────────────┘
|
|
│ │ │
|
|
│ WASM Edge-Net │ Task Assignment │ Ledger
|
|
│ Local Compute │ Credit Verification │ Storage
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
│ PiKey ID │ │ Assigned │ │ Credit Ledger│
|
|
│ (Ed25519) │ │ Tasks Map │ │ (by pubkey) │
|
|
└──────────────┘ └──────────────┘ └──────────────┘
|
|
```
|
|
|
|
### Components
|
|
|
|
#### 1. **Dashboard (Client)**
|
|
- **Location**: `/examples/edge-net/dashboard/`
|
|
- **Role**: Browser-based UI for user interaction
|
|
- **Technology**: React + TypeScript + WASM
|
|
- **Responsibilities**:
|
|
- Generate Ed25519 identity (PiKey) via WASM
|
|
- Connect to relay server via WebSocket
|
|
- Process assigned computational tasks
|
|
- Display credit balance from QDAG
|
|
- Store local cache in IndexedDB (backup only)
|
|
|
|
#### 2. **Relay Server**
|
|
- **Location**: `/examples/edge-net/relay/index.js`
|
|
- **Role**: Central coordination and credit authority
|
|
- **Technology**: Node.js + WebSocket + Firestore
|
|
- **Responsibilities**:
|
|
- Track task assignments (prevent spoofing)
|
|
- Verify task completions
|
|
- Credit accounts in Firestore QDAG
|
|
- Synchronize balances across devices
|
|
- Enforce rate limits and security
|
|
|
|
#### 3. **Firestore QDAG**
|
|
- **Collection**: `edge-net-qdag`
|
|
- **Document Key**: Ed25519 public key (hex string)
|
|
- **Role**: Persistent, authoritative credit ledger
|
|
- **Technology**: Google Cloud Firestore
|
|
- **Responsibilities**:
|
|
- Store credit balances (earned, spent)
|
|
- Track task completion count
|
|
- Enable multi-device sync
|
|
- Provide audit trail
|
|
|
|
#### 4. **CLI (Optional)**
|
|
- **Location**: `/examples/edge-net/cli/`
|
|
- **Role**: Command-line interface for headless nodes
|
|
- **Technology**: Node.js + WASM
|
|
- **Responsibilities**:
|
|
- Same as dashboard, but CLI-based
|
|
- Uses same PiKey identity system
|
|
- Syncs to same QDAG ledger
|
|
|
|
---
|
|
|
|
## Credit Flow
|
|
|
|
### How Credits Are Earned
|
|
|
|
```
|
|
1. Task Submission
|
|
User A submits task → Relay adds to queue → Assigns to User B
|
|
|
|
2. Task Assignment (SECURITY CHECKPOINT)
|
|
Relay tracks: {
|
|
taskId → assignedTo: User B's nodeId,
|
|
assignedToPublicKey: User B's Ed25519 key,
|
|
submitter: User A's nodeId,
|
|
maxCredits: 1000000 (1 rUv)
|
|
}
|
|
|
|
3. Task Processing
|
|
User B's WASM node processes task → Completes task
|
|
|
|
4. Task Completion (SECURITY VERIFICATION)
|
|
User B sends: { type: 'task_complete', taskId }
|
|
|
|
Relay verifies:
|
|
✓ Task exists in assignedTasks map
|
|
✓ Task was assigned to User B (prevent spoofing)
|
|
✓ Task not already completed (prevent replay)
|
|
✓ User B has valid public key for crediting
|
|
|
|
5. Credit Award (QDAG UPDATE)
|
|
Relay calls: creditAccount(publicKey, amount, taskId)
|
|
|
|
Firestore update:
|
|
- ledger.earned += 1.0 rUv
|
|
- ledger.tasksCompleted += 1
|
|
- ledger.lastTaskId = taskId
|
|
- ledger.updatedAt = Date.now()
|
|
|
|
6. Balance Notification
|
|
Relay → User B: {
|
|
type: 'credit_earned',
|
|
amount: '1000000000' (nanoRuv),
|
|
balance: { earned, spent, available }
|
|
}
|
|
|
|
7. Client Update
|
|
Dashboard updates UI with new balance from QDAG
|
|
```
|
|
|
|
### Credit Storage Format
|
|
|
|
**Firestore Document** (`edge-net-qdag/{publicKey}`):
|
|
```json
|
|
{
|
|
"earned": 42.5, // Total rUv earned (float)
|
|
"spent": 10.0, // Total rUv spent (float)
|
|
"tasksCompleted": 123, // Number of tasks
|
|
"lastTaskId": "task-...",
|
|
"createdAt": 1704067200000,
|
|
"updatedAt": 1704153600000
|
|
}
|
|
```
|
|
|
|
**Client Representation** (nanoRuv):
|
|
```typescript
|
|
{
|
|
earned: "42500000000", // 42.5 rUv in nanoRuv
|
|
spent: "10000000000", // 10.0 rUv in nanoRuv
|
|
available: "32500000000" // earned - spent
|
|
}
|
|
```
|
|
|
|
**Conversion**: `1 rUv = 1,000,000,000 nanoRuv (1e9)`
|
|
|
|
---
|
|
|
|
## Security Model
|
|
|
|
### What Prevents Cheating?
|
|
|
|
#### 1. **Task Assignment Tracking**
|
|
```javascript
|
|
// Relay tracks assignments BEFORE tasks are sent
|
|
const assignedTasks = new Map(); // taskId → assignment details
|
|
|
|
// On task assignment:
|
|
assignedTasks.set(task.id, {
|
|
assignedTo: targetNodeId,
|
|
assignedToPublicKey: targetWs.publicKey,
|
|
submitter: task.submitter,
|
|
maxCredits: task.maxCredits,
|
|
assignedAt: Date.now(),
|
|
});
|
|
|
|
// On task completion - verify assignment:
|
|
if (assignment.assignedTo !== nodeId) {
|
|
console.warn('[SECURITY] SPOOFING ATTEMPT');
|
|
return; // Reject
|
|
}
|
|
```
|
|
|
|
**Protection**: Prevents nodes from claiming credit for tasks they didn't receive.
|
|
|
|
#### 2. **Double Completion Prevention**
|
|
```javascript
|
|
const completedTasks = new Set(); // Track completed task IDs
|
|
|
|
if (completedTasks.has(taskId)) {
|
|
console.warn('[SECURITY] REPLAY ATTEMPT');
|
|
return; // Reject
|
|
}
|
|
|
|
completedTasks.add(taskId); // Mark as completed BEFORE crediting
|
|
```
|
|
|
|
**Protection**: Prevents replay attacks where the same completion is submitted multiple times.
|
|
|
|
#### 3. **Client Cannot Self-Report Credits**
|
|
```javascript
|
|
case 'ledger_update':
|
|
// DEPRECATED: Clients cannot increase their own balance
|
|
console.warn('[SECURITY] Rejected ledger_update from client');
|
|
ws.send({ type: 'error', message: 'Credit self-reporting disabled' });
|
|
break;
|
|
```
|
|
|
|
**Protection**: Only the relay can call `creditAccount()` in Firestore.
|
|
|
|
#### 4. **Public Key Verification**
|
|
```javascript
|
|
// Credits require valid public key
|
|
if (!processorPublicKey) {
|
|
ws.send({ type: 'error', message: 'Public key required for credit' });
|
|
return;
|
|
}
|
|
|
|
// Credit is tied to public key, not node ID
|
|
await creditAccount(processorPublicKey, rewardRuv, taskId);
|
|
```
|
|
|
|
**Protection**: Credits tied to cryptographic identity, not ephemeral node IDs.
|
|
|
|
#### 5. **Task Expiration**
|
|
```javascript
|
|
setInterval(() => {
|
|
const TASK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
for (const [taskId, task] of assignedTasks) {
|
|
if (Date.now() - task.assignedAt > TASK_TIMEOUT) {
|
|
assignedTasks.delete(taskId);
|
|
}
|
|
}
|
|
}, 60000);
|
|
```
|
|
|
|
**Protection**: Prevents indefinite task hoarding or delayed completion attacks.
|
|
|
|
#### 6. **Rate Limiting**
|
|
```javascript
|
|
const RATE_LIMIT_WINDOW = 60000; // 1 minute
|
|
const RATE_LIMIT_MAX = 100; // max messages per window
|
|
|
|
function checkRateLimit(nodeId) {
|
|
// Track message count per node
|
|
// Reject if exceeded
|
|
}
|
|
```
|
|
|
|
**Protection**: Prevents spam and rapid task completion abuse.
|
|
|
|
#### 7. **Origin Validation**
|
|
```javascript
|
|
const ALLOWED_ORIGINS = new Set([
|
|
'http://localhost:3000',
|
|
'https://edge-net.ruv.io',
|
|
// ...
|
|
]);
|
|
|
|
if (!isOriginAllowed(origin)) {
|
|
ws.close(4001, 'Unauthorized origin');
|
|
}
|
|
```
|
|
|
|
**Protection**: Prevents unauthorized clients from connecting.
|
|
|
|
#### 8. **Firestore as Single Source of Truth**
|
|
|
|
```javascript
|
|
// Load from Firestore
|
|
const ledger = await loadLedger(publicKey);
|
|
// Cache locally but Firestore is authoritative
|
|
|
|
// Save to Firestore
|
|
await ledgerCollection.doc(publicKey).set(ledger, { merge: true });
|
|
```
|
|
|
|
**Protection**: Clients cannot manipulate balances; Firestore is immutable to clients.
|
|
|
|
---
|
|
|
|
## Multi-Device Synchronization
|
|
|
|
### Same Identity = Same Balance Everywhere
|
|
|
|
#### Identity Generation (PiKey)
|
|
|
|
**Dashboard** (`identityStore.ts`):
|
|
```typescript
|
|
// Generate Ed25519 key pair via WASM
|
|
const piKey = await edgeNetService.generateIdentity();
|
|
|
|
const identity = {
|
|
publicKey: bytesToHex(piKey.getPublicKey()), // hex string
|
|
shortId: piKey.getShortId(), // abbreviated ID
|
|
identityHex: piKey.getIdentityHex(), // full hex
|
|
hasPiMagic: piKey.verifyPiMagic(), // WASM validation
|
|
};
|
|
```
|
|
|
|
**CLI** (same WASM module):
|
|
```javascript
|
|
const piKey = edgeNet.PiKey.generate();
|
|
const publicKey = Buffer.from(piKey.getPublicKey()).toString('hex');
|
|
```
|
|
|
|
**Key Point**: Both use the same WASM `PiKey` module → same Ed25519 keys.
|
|
|
|
#### Ledger Synchronization Flow
|
|
|
|
```
|
|
1. Device A connects to relay
|
|
→ Sends: { type: 'register', publicKey: '0x123abc...' }
|
|
→ Relay stores: ws.publicKey = '0x123abc...'
|
|
|
|
2. Device A requests balance
|
|
→ Sends: { type: 'ledger_sync', publicKey: '0x123abc...' }
|
|
|
|
3. Relay loads from QDAG
|
|
→ Firestore.get('edge-net-qdag/0x123abc...')
|
|
→ Returns: { earned: 42.5, spent: 10.0 }
|
|
|
|
4. Device A receives authoritative balance
|
|
→ { type: 'ledger_sync_response', ledger: { earned, spent } }
|
|
→ Updates local UI
|
|
|
|
5. Device A completes task
|
|
→ Relay credits: creditAccount('0x123abc...', 1.0)
|
|
→ Firestore updates: earned = 43.5
|
|
|
|
6. Device B connects with SAME publicKey
|
|
→ Sends: { type: 'ledger_sync', publicKey: '0x123abc...' }
|
|
→ Receives: { earned: 43.5, spent: 10.0 }
|
|
→ Same balance as Device A ✓
|
|
```
|
|
|
|
### Backup and Recovery
|
|
|
|
**Export Identity** (Dashboard):
|
|
```typescript
|
|
// Create encrypted backup with Argon2id
|
|
const backup = currentPiKey.createEncryptedBackup(password);
|
|
const backupHex = bytesToHex(backup); // Store securely
|
|
```
|
|
|
|
**Import on New Device**:
|
|
```typescript
|
|
// Restore from encrypted backup
|
|
const seed = hexToBytes(backupHex);
|
|
const piKey = await edgeNetService.generateIdentity(seed);
|
|
// → Same public key → Same QDAG balance
|
|
```
|
|
|
|
---
|
|
|
|
## API Reference
|
|
|
|
### WebSocket Message Types
|
|
|
|
#### Client → Relay
|
|
|
|
##### `register`
|
|
Register a new node with the relay.
|
|
|
|
```typescript
|
|
{
|
|
type: 'register',
|
|
nodeId: string, // Session node ID
|
|
publicKey?: string, // Ed25519 public key (hex) for QDAG
|
|
capabilities: string[], // ['compute', 'storage']
|
|
version: string // Client version
|
|
}
|
|
```
|
|
|
|
**Response**: `welcome` message
|
|
|
|
---
|
|
|
|
##### `ledger_sync`
|
|
Request current balance from QDAG.
|
|
|
|
```typescript
|
|
{
|
|
type: 'ledger_sync',
|
|
publicKey: string, // Ed25519 public key (hex)
|
|
nodeId: string
|
|
}
|
|
```
|
|
|
|
**Response**: `ledger_sync_response`
|
|
|
|
---
|
|
|
|
##### `task_submit`
|
|
Submit a new task to the network.
|
|
|
|
```typescript
|
|
{
|
|
type: 'task_submit',
|
|
task: {
|
|
taskType: string, // 'compute' | 'inference' | 'storage'
|
|
payload: number[], // Task data as byte array
|
|
maxCredits: string // Max reward in nanoRuv
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response**: `task_accepted` with `taskId`
|
|
|
|
---
|
|
|
|
##### `task_complete`
|
|
Report task completion (triggers credit award).
|
|
|
|
```typescript
|
|
{
|
|
type: 'task_complete',
|
|
taskId: string,
|
|
result: unknown, // Task output
|
|
reward?: string // Requested reward (capped by maxCredits)
|
|
}
|
|
```
|
|
|
|
**Response**: `credit_earned` (if verified)
|
|
|
|
---
|
|
|
|
##### `heartbeat`
|
|
Keep connection alive.
|
|
|
|
```typescript
|
|
{
|
|
type: 'heartbeat'
|
|
}
|
|
```
|
|
|
|
**Response**: `heartbeat_ack`
|
|
|
|
---
|
|
|
|
#### Relay → Client
|
|
|
|
##### `welcome`
|
|
Initial connection confirmation.
|
|
|
|
```typescript
|
|
{
|
|
type: 'welcome',
|
|
nodeId: string,
|
|
networkState: {
|
|
genesisTime: number,
|
|
totalNodes: number,
|
|
activeNodes: number,
|
|
totalTasks: number,
|
|
totalRuvDistributed: string, // bigint as string
|
|
timeCrystalPhase: number
|
|
},
|
|
peers: string[] // Connected peer node IDs
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `ledger_sync_response`
|
|
Authoritative balance from QDAG.
|
|
|
|
```typescript
|
|
{
|
|
type: 'ledger_sync_response',
|
|
ledger: {
|
|
publicKey: string,
|
|
nodeId: string,
|
|
earned: string, // nanoRuv
|
|
spent: string, // nanoRuv
|
|
available: string, // earned - spent
|
|
tasksCompleted: number,
|
|
lastUpdated: number, // timestamp
|
|
signature: string // 'qdag-verified'
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `task_assignment`
|
|
Assigned task to process.
|
|
|
|
```typescript
|
|
{
|
|
type: 'task_assignment',
|
|
task: {
|
|
id: string,
|
|
submitter: string,
|
|
taskType: string,
|
|
payload: number[], // Task data
|
|
maxCredits: string, // Max reward in nanoRuv
|
|
submittedAt: number
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `credit_earned`
|
|
Credit awarded after task completion.
|
|
|
|
```typescript
|
|
{
|
|
type: 'credit_earned',
|
|
amount: string, // nanoRuv earned
|
|
taskId: string,
|
|
balance: {
|
|
earned: string, // Total earned (nanoRuv)
|
|
spent: string, // Total spent (nanoRuv)
|
|
available: string // Available (nanoRuv)
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `time_crystal_sync`
|
|
Network-wide time synchronization.
|
|
|
|
```typescript
|
|
{
|
|
type: 'time_crystal_sync',
|
|
phase: number, // 0-1 phase value
|
|
timestamp: number, // Unix timestamp
|
|
activeNodes: number
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `node_joined` / `node_left`
|
|
Peer connectivity events.
|
|
|
|
```typescript
|
|
{
|
|
type: 'node_joined' | 'node_left',
|
|
nodeId: string,
|
|
totalNodes: number
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `error`
|
|
Error response.
|
|
|
|
```typescript
|
|
{
|
|
type: 'error',
|
|
message: string
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### HTTP Endpoints
|
|
|
|
#### `GET /health`
|
|
Health check endpoint.
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"status": "healthy",
|
|
"nodes": 42,
|
|
"uptime": 3600000
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /stats`
|
|
Network statistics.
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"genesisTime": 1704067200000,
|
|
"totalNodes": 150,
|
|
"activeNodes": 142,
|
|
"totalTasks": 9876,
|
|
"totalRuvDistributed": "1234567890",
|
|
"timeCrystalPhase": 0.618,
|
|
"connectedNodes": ["node-1", "node-2", ...]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Data Models
|
|
|
|
### Firestore Schema
|
|
|
|
#### Collection: `edge-net-qdag`
|
|
|
|
**Document ID**: Ed25519 public key (hex string)
|
|
|
|
```typescript
|
|
{
|
|
earned: number, // Total rUv earned (float)
|
|
spent: number, // Total rUv spent (float)
|
|
tasksCompleted: number, // Count of completed tasks
|
|
lastTaskId?: string, // Most recent task ID
|
|
createdAt: number, // First entry timestamp
|
|
updatedAt: number // Last update timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```json
|
|
{
|
|
"earned": 127.3,
|
|
"spent": 25.0,
|
|
"tasksCompleted": 456,
|
|
"lastTaskId": "task-1704153600000-abc123",
|
|
"createdAt": 1704067200000,
|
|
"updatedAt": 1704153600000
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Client State
|
|
|
|
#### `networkStore.ts` - Credit Balance
|
|
|
|
```typescript
|
|
interface CreditBalance {
|
|
available: number, // earned - spent (rUv)
|
|
pending: number, // Credits not yet confirmed
|
|
earned: number, // Total earned (rUv)
|
|
spent: number // Total spent (rUv)
|
|
}
|
|
```
|
|
|
|
**Updated by**:
|
|
- `onCreditEarned`: Increment earned when task completes
|
|
- `onLedgerSync`: Replace with QDAG authoritative values
|
|
|
|
---
|
|
|
|
#### `identityStore.ts` - PiKey Identity
|
|
|
|
```typescript
|
|
interface PeerIdentity {
|
|
id: string, // Libp2p-style peer ID
|
|
publicKey: string, // Ed25519 public key (hex)
|
|
publicKeyBytes?: Uint8Array,
|
|
displayName: string,
|
|
createdAt: Date,
|
|
shortId: string, // Abbreviated ID
|
|
identityHex: string, // Full identity hex
|
|
hasPiMagic: boolean // WASM PiKey validation
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### IndexedDB Schema
|
|
|
|
#### Store: `edge-net-store`
|
|
|
|
**Purpose**: Local cache (NOT source of truth)
|
|
|
|
```typescript
|
|
{
|
|
id: 'primary',
|
|
nodeId: string,
|
|
creditsEarned: number, // Cache from QDAG
|
|
creditsSpent: number, // Cache from QDAG
|
|
tasksCompleted: number,
|
|
tasksSubmitted: number,
|
|
totalUptime: number,
|
|
lastActiveTimestamp: number,
|
|
consentGiven: boolean,
|
|
consentTimestamp: number | null,
|
|
cpuLimit: number,
|
|
gpuEnabled: boolean,
|
|
gpuLimit: number,
|
|
respectBattery: boolean,
|
|
onlyWhenIdle: boolean
|
|
}
|
|
```
|
|
|
|
**Note**: IndexedDB is a **backup only**. QDAG is the source of truth.
|
|
|
|
---
|
|
|
|
## Implementation Details
|
|
|
|
### Credit Award Flow (Relay)
|
|
|
|
```javascript
|
|
// /examples/edge-net/relay/index.js
|
|
|
|
case 'task_complete': {
|
|
const taskId = message.taskId;
|
|
|
|
// 1. Verify task assignment
|
|
const assignment = assignedTasks.get(taskId);
|
|
if (!assignment || assignment.assignedTo !== nodeId) {
|
|
return; // Reject spoofing attempt
|
|
}
|
|
|
|
// 2. Check double completion
|
|
if (completedTasks.has(taskId)) {
|
|
return; // Reject replay attack
|
|
}
|
|
|
|
// 3. Get processor's public key
|
|
const publicKey = assignment.assignedToPublicKey || ws.publicKey;
|
|
if (!publicKey) {
|
|
return; // Reject - no identity
|
|
}
|
|
|
|
// 4. Mark as completed (prevent race conditions)
|
|
completedTasks.add(taskId);
|
|
assignedTasks.delete(taskId);
|
|
|
|
// 5. Credit the account in QDAG
|
|
const rewardRuv = Number(message.reward || assignment.maxCredits) / 1e9;
|
|
const updatedLedger = await creditAccount(publicKey, rewardRuv, taskId);
|
|
|
|
// 6. Notify client
|
|
ws.send({
|
|
type: 'credit_earned',
|
|
amount: (rewardRuv * 1e9).toString(),
|
|
balance: {
|
|
earned: (updatedLedger.earned * 1e9).toString(),
|
|
spent: (updatedLedger.spent * 1e9).toString(),
|
|
available: ((updatedLedger.earned - updatedLedger.spent) * 1e9).toString(),
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Ledger Sync Flow (Dashboard)
|
|
|
|
```typescript
|
|
// /examples/edge-net/dashboard/src/stores/networkStore.ts
|
|
|
|
connectToRelay: async () => {
|
|
// 1. Get identity public key
|
|
const identityState = useIdentityStore.getState();
|
|
const publicKey = identityState.identity?.publicKey;
|
|
|
|
// 2. Connect to relay with public key
|
|
const connected = await relayClient.connect(nodeId, publicKey);
|
|
|
|
// 3. Request QDAG balance after connection
|
|
if (connected && publicKey) {
|
|
setTimeout(() => {
|
|
relayClient.requestLedgerSync(publicKey);
|
|
}, 500);
|
|
}
|
|
},
|
|
|
|
// 4. Handle QDAG response (authoritative)
|
|
onLedgerSync: (ledger) => {
|
|
const earnedRuv = Number(ledger.earned) / 1e9;
|
|
const spentRuv = Number(ledger.spent) / 1e9;
|
|
|
|
// Replace local state with QDAG values
|
|
set({
|
|
credits: {
|
|
earned: earnedRuv,
|
|
spent: spentRuv,
|
|
available: earnedRuv - spentRuv,
|
|
pending: 0,
|
|
},
|
|
});
|
|
|
|
// Save to IndexedDB as backup
|
|
get().saveToIndexedDB();
|
|
},
|
|
```
|
|
|
|
---
|
|
|
|
### Task Processing Flow (Dashboard)
|
|
|
|
```typescript
|
|
// /examples/edge-net/dashboard/src/stores/networkStore.ts
|
|
|
|
processAssignedTask: async (task) => {
|
|
// 1. Process task using WASM
|
|
const result = await edgeNetService.submitTask(
|
|
task.taskType,
|
|
task.payload,
|
|
task.maxCredits
|
|
);
|
|
|
|
await edgeNetService.processNextTask();
|
|
|
|
// 2. Report completion to relay
|
|
const reward = task.maxCredits / BigInt(2); // Earn half the max
|
|
relayClient.completeTask(task.id, task.submitter, result, reward);
|
|
|
|
// 3. Relay verifies and credits QDAG
|
|
// 4. Client receives credit_earned message
|
|
// 5. Balance updates automatically
|
|
},
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
The Edge-Net QDAG credit system provides a **secure, distributed ledger** for tracking computational contributions:
|
|
|
|
✅ **Identity-Based**: Credits tied to Ed25519 public keys, not devices
|
|
✅ **Relay Authority**: Only relay can credit accounts via verified tasks
|
|
✅ **Multi-Device Sync**: Same key = same balance everywhere
|
|
✅ **Firestore Truth**: QDAG in Firestore is the authoritative state
|
|
✅ **Security**: Prevents spoofing, replay, self-reporting, and double-completion
|
|
✅ **IndexedDB Cache**: Local backup, but QDAG is source of truth
|
|
|
|
**Key Insight**: The relay server acts as a **trusted coordinator** that verifies task completions before updating the QDAG ledger in Firestore. Clients cannot manipulate their balances; they can only earn credits by processing assigned tasks.
|