/** * @ruvector/edge-net QDAG (Quantum DAG) Implementation * * Directed Acyclic Graph for distributed consensus and task tracking * Inspired by IOTA Tangle and DAG-based blockchains * * Features: * - Tip selection algorithm * - Proof of contribution verification * - Transaction validation * - Network synchronization * * @module @ruvector/edge-net/qdag */ import { EventEmitter } from 'events'; import { randomBytes, createHash, createHmac } from 'crypto'; // ============================================ // TRANSACTION // ============================================ /** * QDAG Transaction */ export class Transaction { constructor(data = {}) { this.id = data.id || `tx-${randomBytes(16).toString('hex')}`; this.timestamp = data.timestamp || Date.now(); this.type = data.type || 'generic'; // 'genesis', 'task', 'reward', 'transfer' // Links to parent transactions (must reference 2 tips) this.parents = data.parents || []; // Transaction payload this.payload = data.payload || {}; // Proof of contribution this.proof = data.proof || null; // Issuer this.issuer = data.issuer || null; this.signature = data.signature || null; // Computed fields this.hash = data.hash || this.computeHash(); this.weight = data.weight || 1; this.cumulativeWeight = data.cumulativeWeight || 1; this.confirmed = data.confirmed || false; this.confirmedAt = data.confirmedAt || null; } /** * Compute transaction hash */ computeHash() { const content = JSON.stringify({ id: this.id, timestamp: this.timestamp, type: this.type, parents: this.parents, payload: this.payload, proof: this.proof, issuer: this.issuer, }); return createHash('sha256').update(content).digest('hex'); } /** * Sign transaction */ sign(privateKey) { const hmac = createHmac('sha256', privateKey); hmac.update(this.hash); this.signature = hmac.digest('hex'); return this.signature; } /** * Verify signature */ verify(publicKey) { if (!this.signature) return false; const hmac = createHmac('sha256', publicKey); hmac.update(this.hash); const expected = hmac.digest('hex'); return this.signature === expected; } /** * Serialize transaction */ toJSON() { return { id: this.id, timestamp: this.timestamp, type: this.type, parents: this.parents, payload: this.payload, proof: this.proof, issuer: this.issuer, signature: this.signature, hash: this.hash, weight: this.weight, cumulativeWeight: this.cumulativeWeight, confirmed: this.confirmed, confirmedAt: this.confirmedAt, }; } /** * Deserialize transaction */ static fromJSON(json) { return new Transaction(json); } } // ============================================ // QDAG (Quantum DAG) // ============================================ /** * QDAG - Directed Acyclic Graph for distributed consensus */ export class QDAG extends EventEmitter { constructor(options = {}) { super(); this.id = options.id || `qdag-${randomBytes(8).toString('hex')}`; this.nodeId = options.nodeId; // Transaction storage this.transactions = new Map(); this.tips = new Set(); // Unconfirmed transactions this.confirmed = new Set(); // Confirmed transactions // Indices this.byIssuer = new Map(); // issuer -> Set this.byType = new Map(); // type -> Set this.children = new Map(); // txId -> Set // Configuration this.confirmationThreshold = options.confirmationThreshold || 5; this.maxTips = options.maxTips || 100; this.pruneAge = options.pruneAge || 24 * 60 * 60 * 1000; // 24 hours // Stats this.stats = { transactions: 0, confirmed: 0, tips: 0, avgConfirmationTime: 0, }; // Create genesis if needed if (options.createGenesis !== false) { this.createGenesis(); } } /** * Create genesis transaction */ createGenesis() { const genesis = new Transaction({ id: 'genesis', type: 'genesis', parents: [], payload: { message: 'QDAG Genesis', timestamp: Date.now(), }, issuer: 'system', }); genesis.confirmed = true; genesis.confirmedAt = Date.now(); genesis.cumulativeWeight = this.confirmationThreshold + 1; this.transactions.set(genesis.id, genesis); this.tips.add(genesis.id); this.confirmed.add(genesis.id); this.emit('genesis', genesis); return genesis; } /** * Select tips for new transaction (weighted random walk) */ selectTips(count = 2) { // Ensure genesis exists if (!this.transactions.has('genesis')) { this.createGenesis(); } const tips = Array.from(this.tips); // Fallback to genesis if no tips available if (tips.length === 0) { return ['genesis']; } // Return all tips if we have fewer than requested if (tips.length <= count) { return [...tips]; // Return copy to prevent mutation issues } // Weighted random selection based on cumulative weight const selected = new Set(); const weights = tips.map(tipId => { const tx = this.transactions.get(tipId); return tx ? Math.max(tx.cumulativeWeight, 1) : 1; }); const totalWeight = weights.reduce((a, b) => a + b, 0); // Safety: prevent infinite loop let attempts = 0; const maxAttempts = count * 10; while (selected.size < count && selected.size < tips.length && attempts < maxAttempts) { let random = Math.random() * totalWeight; for (let i = 0; i < tips.length; i++) { random -= weights[i]; if (random <= 0) { selected.add(tips[i]); break; } } attempts++; } // Ensure we have at least one valid parent const result = Array.from(selected); if (result.length === 0) { result.push(tips[0] || 'genesis'); } return result; } /** * Add transaction to QDAG */ addTransaction(tx) { // Validate transaction with detailed error const validation = this.validateTransaction(tx, { returnError: true }); if (!validation.valid) { throw new Error(`Invalid transaction: ${validation.error}`); } // Check for duplicates if (this.transactions.has(tx.id)) { return this.transactions.get(tx.id); } // Store transaction this.transactions.set(tx.id, tx); this.tips.add(tx.id); this.stats.transactions++; // Update indices if (tx.issuer) { if (!this.byIssuer.has(tx.issuer)) { this.byIssuer.set(tx.issuer, new Set()); } this.byIssuer.get(tx.issuer).add(tx.id); } if (!this.byType.has(tx.type)) { this.byType.set(tx.type, new Set()); } this.byType.get(tx.type).add(tx.id); // Update parent references for (const parentId of tx.parents) { if (!this.children.has(parentId)) { this.children.set(parentId, new Set()); } this.children.get(parentId).add(tx.id); // Remove parent from tips this.tips.delete(parentId); } // Update weights this.updateWeights(tx.id); // Check for confirmations this.checkConfirmations(); this.emit('transaction', tx); return tx; } /** * Create and add a new transaction */ createTransaction(type, payload, options = {}) { const parents = options.parents || this.selectTips(2); const tx = new Transaction({ type, payload, parents, issuer: options.issuer || this.nodeId, proof: options.proof, }); if (options.privateKey) { tx.sign(options.privateKey); } return this.addTransaction(tx); } /** * Validate transaction * @returns {boolean|{valid: boolean, error: string}} */ validateTransaction(tx, options = {}) { const returnError = options.returnError || false; const fail = (msg) => returnError ? { valid: false, error: msg } : false; const pass = () => returnError ? { valid: true, error: null } : true; // Check required fields if (!tx.id) { return fail('Missing transaction id'); } if (!tx.timestamp) { return fail('Missing transaction timestamp'); } if (!tx.type) { return fail('Missing transaction type'); } // Genesis transactions don't need parents if (tx.type === 'genesis') { return pass(); } // Ensure genesis exists before validating non-genesis transactions if (!this.transactions.has('genesis')) { this.createGenesis(); } // Check parents exist if (!tx.parents || tx.parents.length === 0) { return fail('Non-genesis transaction must have at least one parent'); } for (const parentId of tx.parents) { if (!this.transactions.has(parentId)) { return fail(`Parent transaction not found: ${parentId}`); } } // Check no cycles (parents must be older or equal for simultaneous txs) for (const parentId of tx.parents) { const parent = this.transactions.get(parentId); // Allow equal timestamps (transactions created at same time) if (parent && parent.timestamp > tx.timestamp) { return fail(`Parent ${parentId} has future timestamp`); } } return pass(); } /** * Update cumulative weights */ updateWeights(txId) { const tx = this.transactions.get(txId); if (!tx) return; // Update weight of this transaction tx.cumulativeWeight = tx.weight; // Add weight of all children const children = this.children.get(txId); if (children) { for (const childId of children) { const child = this.transactions.get(childId); if (child) { tx.cumulativeWeight += child.cumulativeWeight; } } } // Propagate to parents for (const parentId of tx.parents) { this.updateWeights(parentId); } } /** * Check for newly confirmed transactions */ checkConfirmations() { for (const [txId, tx] of this.transactions) { if (!tx.confirmed && tx.cumulativeWeight >= this.confirmationThreshold) { tx.confirmed = true; tx.confirmedAt = Date.now(); this.confirmed.add(txId); this.stats.confirmed++; // Update average confirmation time const confirmTime = tx.confirmedAt - tx.timestamp; this.stats.avgConfirmationTime = (this.stats.avgConfirmationTime * (this.stats.confirmed - 1) + confirmTime) / this.stats.confirmed; this.emit('confirmed', tx); } } this.stats.tips = this.tips.size; } /** * Get transaction by ID */ getTransaction(txId) { return this.transactions.get(txId); } /** * Get transactions by issuer */ getByIssuer(issuer) { const txIds = this.byIssuer.get(issuer) || new Set(); return Array.from(txIds).map(id => this.transactions.get(id)); } /** * Get transactions by type */ getByType(type) { const txIds = this.byType.get(type) || new Set(); return Array.from(txIds).map(id => this.transactions.get(id)); } /** * Get current tips */ getTips() { return Array.from(this.tips).map(id => this.transactions.get(id)); } /** * Get confirmed transactions */ getConfirmed() { return Array.from(this.confirmed).map(id => this.transactions.get(id)); } /** * Prune old transactions */ prune() { const cutoff = Date.now() - this.pruneAge; let pruned = 0; for (const [txId, tx] of this.transactions) { if (tx.type === 'genesis') continue; if (tx.confirmed && tx.confirmedAt < cutoff) { // Remove from storage this.transactions.delete(txId); this.confirmed.delete(txId); this.tips.delete(txId); // Clean up indices if (tx.issuer && this.byIssuer.has(tx.issuer)) { this.byIssuer.get(tx.issuer).delete(txId); } if (this.byType.has(tx.type)) { this.byType.get(tx.type).delete(txId); } this.children.delete(txId); pruned++; } } if (pruned > 0) { this.emit('pruned', { count: pruned }); } return pruned; } /** * Get QDAG statistics */ getStats() { return { id: this.id, ...this.stats, size: this.transactions.size, memoryUsage: process.memoryUsage?.().heapUsed, }; } /** * Export QDAG for synchronization */ export(since = 0) { const transactions = []; for (const [txId, tx] of this.transactions) { if (tx.timestamp >= since) { transactions.push(tx.toJSON()); } } return { id: this.id, timestamp: Date.now(), transactions, }; } /** * Import transactions from another node */ import(data) { let imported = 0; // Sort by timestamp to maintain order const sorted = data.transactions.sort((a, b) => a.timestamp - b.timestamp); for (const txData of sorted) { try { const tx = Transaction.fromJSON(txData); if (!this.transactions.has(tx.id)) { this.addTransaction(tx); imported++; } } catch (error) { console.error('[QDAG] Import error:', error.message); } } this.emit('imported', { count: imported, from: data.id }); return imported; } /** * Merge with another QDAG */ merge(other) { return this.import(other.export()); } } // ============================================ // TASK TRANSACTION HELPERS // ============================================ /** * Create a task submission transaction */ export function createTaskTransaction(qdag, task, options = {}) { return qdag.createTransaction('task', { taskId: task.id, type: task.type, data: task.data, priority: task.priority || 'medium', reward: task.reward || 0, deadline: task.deadline, }, options); } /** * Create a task completion/reward transaction */ export function createRewardTransaction(qdag, taskTxId, result, options = {}) { const taskTx = qdag.getTransaction(taskTxId); if (!taskTx) throw new Error('Task transaction not found'); return qdag.createTransaction('reward', { taskTxId, result, worker: options.worker, reward: taskTx.payload.reward || 0, completedAt: Date.now(), }, { ...options, parents: [taskTxId, ...qdag.selectTips(1)], }); } /** * Create a credit transfer transaction */ export function createTransferTransaction(qdag, from, to, amount, options = {}) { return qdag.createTransaction('transfer', { from, to, amount, memo: options.memo, }, options); } // ============================================ // EXPORTS // ============================================ export default QDAG;