Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
839
vendor/ruvector/examples/edge-net/docs/QDAG_ARCHITECTURE.md
vendored
Normal file
839
vendor/ruvector/examples/edge-net/docs/QDAG_ARCHITECTURE.md
vendored
Normal file
@@ -0,0 +1,839 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user