Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,588 @@
# Edge-Net Browser Deployment
Deploy edge-net directly in browsers without running your own infrastructure.
Earn **rUv (Resource Utility Vouchers)** by contributing idle compute.
```
┌─────────────────────────────────────────────────────────────────────────┐
│ BROWSER DEPLOYMENT OPTIONS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Option A: CDN + Public Genesis Option B: Self-Hosted │
│ ┌────────────────────────────┐ ┌────────────────────────┐ │
│ │ Your Website │ │ Your Website │ │
│ │ <script src="cdn/..."> │ │ <script src="local"> │ │
│ │ │ │ │ │ │ │
│ │ ▼ │ │ ▼ │ │
│ │ ┌────────────────┐ │ │ ┌────────────────┐ │ │
│ │ │ Public Genesis │◄──┐ │ │ │ Your Genesis │ │ │
│ │ │ Nodes │ │ │ │ │ Node │ │ │
│ │ └────────────────┘ │ │ │ └────────────────┘ │ │
│ │ │ │ │ │ │
│ │ ┌────────────────┐ │ │ │ P2P via WebRTC │ │
│ │ │ Public GUN │◄──┘ │ │ or WebSocket │ │
│ │ │ Relays │ │ │ │ │
│ │ └────────────────┘ │ └────────────────────────┘ │
│ └────────────────────────────┘ │
│ │
│ Best for: Quick start, testing Best for: Control, privacy │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
## Option A: CDN Quick Start (Recommended)
### 1. Add Script Tag
```html
<!DOCTYPE html>
<html>
<head>
<title>My Site</title>
</head>
<body>
<!-- Your website content -->
<!-- Edge-Net: Contribute compute, earn credits -->
<script type="module">
import { EdgeNet } from 'https://cdn.jsdelivr.net/npm/@ruvector/edge-net/dist/edge-net.min.js';
const node = await EdgeNet.init({
siteId: 'my-site-unique-id',
contribution: 0.3, // 30% CPU when idle
});
console.log(`Node ID: ${node.nodeId()}`);
console.log(`Balance: ${node.creditBalance()} rUv`);
</script>
</body>
</html>
```
### 2. NPM Installation (Alternative)
```bash
npm install @ruvector/edge-net
```
```javascript
import { EdgeNet } from '@ruvector/edge-net';
const node = await EdgeNet.init({
siteId: 'my-site',
contribution: 0.3,
});
```
## Configuration Options
### Basic Configuration
```javascript
const node = await EdgeNet.init({
// Required
siteId: 'your-unique-site-id',
// Contribution settings
contribution: {
cpuLimit: 0.3, // 0.0 - 1.0 (30% max CPU)
memoryLimit: 256_000_000, // 256MB max memory
bandwidthLimit: 1_000_000, // 1MB/s max bandwidth
tasks: ['vectors', 'embeddings', 'encryption'],
},
// Idle detection
idle: {
minIdleTime: 5000, // Wait 5s of idle before working
respectBattery: true, // Reduce when on battery
respectDataSaver: true, // Respect data saver mode
},
// UI integration
ui: {
showBadge: true, // Show contribution badge
badgePosition: 'bottom-right',
onEarn: (credits) => {
// Custom notification on earning
console.log(`Earned ${credits} QDAG!`);
},
},
});
```
### Advanced Configuration
```javascript
const node = await EdgeNet.init({
siteId: 'my-site',
// Network settings
network: {
// Use public genesis nodes (default)
genesis: [
'https://us-east1-edge-net.cloudfunctions.net/genesis',
'https://europe-west1-edge-net.cloudfunctions.net/genesis',
'https://asia-east1-edge-net.cloudfunctions.net/genesis',
],
// P2P relay servers
relays: [
'https://gun-manhattan.herokuapp.com/gun',
'https://gun-us.herokuapp.com/gun',
],
// WebRTC configuration
webrtc: {
enabled: true,
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
],
},
},
// Staking for higher priority
stake: {
amount: 100, // Stake 100 QDAG
autoStake: true, // Auto-stake earnings
},
// Callbacks
onCredit: (earned, total) => console.log(`+${earned} QDAG`),
onTask: (task) => console.log(`Processing: ${task.type}`),
onError: (error) => console.error('Edge-Net error:', error),
onConnect: (peers) => console.log(`Connected to ${peers} peers`),
onDisconnect: () => console.log('Disconnected'),
});
```
## Widget Integration
### Contribution Badge
Show users their rUv contribution status:
```html
<!-- Include the badge component -->
<div id="edge-net-badge"></div>
<script type="module">
import { EdgeNet, Badge } from '@ruvector/edge-net';
const node = await EdgeNet.init({ siteId: 'my-site' });
// Mount badge
Badge.mount('#edge-net-badge', node, {
theme: 'dark', // 'light' | 'dark' | 'auto'
showRuv: true, // Show rUv balance
showMultiplier: true,
showUptime: true,
minimizable: true,
});
</script>
```
### Dashboard Widget
Full contribution dashboard:
```html
<div id="edge-net-dashboard" style="width: 400px; height: 300px;"></div>
<script type="module">
import { EdgeNet, Dashboard } from '@ruvector/edge-net';
const node = await EdgeNet.init({ siteId: 'my-site' });
Dashboard.mount('#edge-net-dashboard', node, {
showStats: true,
showHistory: true,
showTasks: true,
showPeers: true,
});
</script>
```
## User Consent Patterns
### Opt-In Modal
```html
<script type="module">
import { EdgeNet, ConsentModal } from '@ruvector/edge-net';
// Show consent modal before initializing
const consent = await ConsentModal.show({
title: 'Help power our AI features',
message: 'Contribute idle compute cycles to earn QDAG credits.',
benefits: [
'Earn credits while browsing',
'Use credits for AI features',
'Early adopter bonus: 5.2x multiplier',
],
learnMoreUrl: '/edge-net-info',
});
if (consent.accepted) {
const node = await EdgeNet.init({
siteId: 'my-site',
contribution: consent.cpuLimit, // User-selected limit
});
}
</script>
```
### Banner Opt-In
```html
<div id="edge-net-banner"></div>
<script type="module">
import { EdgeNet, ConsentBanner } from '@ruvector/edge-net';
ConsentBanner.show('#edge-net-banner', {
onAccept: async (settings) => {
const node = await EdgeNet.init({
siteId: 'my-site',
contribution: settings.cpuLimit,
});
},
onDecline: () => {
// User declined - respect their choice
console.log('User declined edge-net participation');
},
});
</script>
```
## Task Submission
Use earned credits for compute tasks:
```javascript
// Check balance first
if (node.creditBalance() >= 5) {
// Submit vector search task
const result = await node.submitTask('vector_search', {
query: new Float32Array(128).fill(0.5),
k: 10,
}, {
maxRuv: 5, // Max rUv to spend
timeout: 30000, // 30s timeout
priority: 'normal', // 'low' | 'normal' | 'high'
});
console.log('Results:', result.results);
console.log('Cost:', result.cost, 'rUv');
}
```
### Available Task Types
| Type | Description | Cost |
|------|-------------|------|
| `vector_search` | k-NN search in HNSW index | ~1 rUv / 1K vectors |
| `vector_insert` | Add vectors to index | ~0.5 rUv / 100 vectors |
| `embedding` | Generate text embeddings | ~5 rUv / 100 texts |
| `semantic_match` | Task-to-agent routing | ~1 rUv / 10 queries |
| `encryption` | AES encrypt/decrypt | ~0.1 rUv / MB |
| `compression` | Adaptive quantization | ~0.2 rUv / MB |
## Framework Integration
### React
```jsx
import { useEdgeNet, Badge } from '@ruvector/edge-net/react';
function App() {
const { node, balance, multiplier, isConnected } = useEdgeNet({
siteId: 'my-react-app',
contribution: 0.3,
});
return (
<div>
<h1>My App</h1>
{isConnected && (
<Badge balance={balance} multiplier={multiplier} />
)}
</div>
);
}
```
### Vue 3
```vue
<template>
<div>
<h1>My App</h1>
<EdgeNetBadge v-if="isConnected" :node="node" />
</div>
</template>
<script setup>
import { useEdgeNet, EdgeNetBadge } from '@ruvector/edge-net/vue';
const { node, isConnected } = useEdgeNet({
siteId: 'my-vue-app',
contribution: 0.3,
});
</script>
```
### Next.js
```jsx
// components/EdgeNetProvider.jsx
'use client';
import { EdgeNetProvider } from '@ruvector/edge-net/react';
export default function Providers({ children }) {
return (
<EdgeNetProvider config={{ siteId: 'my-next-app', contribution: 0.3 }}>
{children}
</EdgeNetProvider>
);
}
// app/layout.jsx
import Providers from '@/components/EdgeNetProvider';
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
```
## Self-Hosting the WASM Bundle
If you prefer to host the WASM files yourself:
### 1. Download the Package
```bash
npm pack @ruvector/edge-net
tar -xzf ruvector-edge-net-*.tgz
cp -r package/dist/ ./public/edge-net/
```
### 2. Configure Your Web Server
```nginx
# nginx configuration
location /edge-net/ {
add_header Cross-Origin-Opener-Policy same-origin;
add_header Cross-Origin-Embedder-Policy require-corp;
# WASM MIME type
types {
application/wasm wasm;
}
}
```
### 3. Use Local Path
```html
<script type="module">
import { EdgeNet } from '/edge-net/edge-net.min.js';
const node = await EdgeNet.init({
siteId: 'my-site',
wasmPath: '/edge-net/', // Path to WASM files
});
</script>
```
## Option B: Self-Hosted Genesis Node
For full control, run your own genesis node:
### Using Docker
```bash
# Pull the edge-net genesis image
docker pull ruvector/edge-net-genesis:latest
# Run genesis node
docker run -d \
--name edge-net-genesis \
-p 8080:8080 \
-e NODE_ENV=production \
-e GENESIS_KEYS_PATH=/keys/genesis.json \
-v ./keys:/keys:ro \
ruvector/edge-net-genesis:latest
```
### Connect Browsers to Your Genesis
```javascript
const node = await EdgeNet.init({
siteId: 'my-site',
network: {
genesis: ['https://your-genesis.example.com'],
relays: ['wss://your-relay.example.com'],
},
});
```
See [../gcloud/README.md](../gcloud/README.md) for Google Cloud Functions deployment.
## Privacy & Compliance
### GDPR Compliance
```javascript
// Check for prior consent
const hasConsent = localStorage.getItem('edge-net-consent') === 'true';
if (hasConsent) {
const node = await EdgeNet.init({ siteId: 'my-site' });
} else {
// Show consent UI
showConsentDialog();
}
// Handle "forget me" requests
async function handleForgetMe() {
const node = await EdgeNet.getNode();
if (node) {
await node.deleteAllData();
await node.disconnect();
}
localStorage.removeItem('edge-net-consent');
}
```
### Data Collected
| Data | Purpose | Retention |
|------|---------|-----------|
| Node ID | Identity | Until user clears |
| Task results | Verification | 24 hours |
| rUv balance | Economics | Permanent (on-chain) |
| IP address | Rate limiting | Not stored |
| Browser fingerprint | Sybil prevention | Hashed, 7 days |
### No Personal Data
Edge-net does NOT collect:
- Names or emails
- Browsing history
- Cookie contents
- Form inputs
- Screen recordings
## Performance Impact
| Scenario | CPU Impact | Memory | Network |
|----------|------------|--------|---------|
| Idle (no tasks) | 0% | ~10MB | 0 |
| Light tasks | 5-10% | ~50MB | ~1KB/s |
| Active contribution | 10-30% | ~100MB | ~10KB/s |
| Heavy workload | 30% (capped) | ~256MB | ~50KB/s |
### Optimization Tips
```javascript
const node = await EdgeNet.init({
siteId: 'my-site',
contribution: {
cpuLimit: 0.2, // Lower CPU for sensitive sites
memoryLimit: 128_000_000, // Lower memory footprint
},
idle: {
minIdleTime: 10000, // Wait longer before starting
checkInterval: 5000, // Check less frequently
},
// Pause during critical interactions
pauseDuringInteraction: true,
});
// Manually pause during important operations
node.pause();
await performCriticalOperation();
node.resume();
```
## Monitoring & Analytics
### Built-in Stats
```javascript
const stats = node.getStats();
console.log({
uptime: stats.uptimeHours,
tasksCompleted: stats.tasksCompleted,
creditsEarned: stats.creditsEarned,
reputation: stats.reputation,
peers: stats.connectedPeers,
});
```
### Integration with Analytics
```javascript
// Send to your analytics
const node = await EdgeNet.init({
siteId: 'my-site',
onCredit: (earned, total) => {
gtag('event', 'edge_net_credit', {
earned,
total,
multiplier: node.getMultiplier(),
});
},
});
```
## Troubleshooting
### Common Issues
**WASM fails to load**
```
Error: Failed to load WASM module
```
Solution: Ensure CORS headers allow WASM loading from CDN.
**SharedArrayBuffer not available**
```
Error: SharedArrayBuffer is not defined
```
Solution: Add required COOP/COEP headers:
```
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
```
**WebWorkers blocked**
```
Error: Worker constructor blocked
```
Solution: Ensure your CSP allows worker-src.
### Debug Mode
```javascript
const node = await EdgeNet.init({
siteId: 'my-site',
debug: true, // Enable verbose logging
});
```
## Support
- Documentation: https://github.com/ruvnet/ruvector
- Issues: https://github.com/ruvnet/ruvector/issues
- Discord: https://discord.gg/ruvector

View File

@@ -0,0 +1,324 @@
/**
* Edge-Net Embed Snippet
*
* Minimal embed code for websites to include edge-net
*
* Usage:
* <script src="https://cdn.jsdelivr.net/npm/@ruvector/edge-net/embed.min.js"
* data-site-id="your-site-id"
* data-cpu-limit="30"
* data-show-badge="true">
* </script>
*/
(function() {
'use strict';
// Get configuration from script tag
const script = document.currentScript;
const config = {
siteId: script.getAttribute('data-site-id') || 'unknown',
cpuLimit: parseFloat(script.getAttribute('data-cpu-limit') || '30') / 100,
showBadge: script.getAttribute('data-show-badge') !== 'false',
badgePosition: script.getAttribute('data-badge-position') || 'bottom-right',
consentRequired: script.getAttribute('data-consent-required') !== 'false',
debug: script.getAttribute('data-debug') === 'true',
};
// CDN URLs
const CDN_BASE = 'https://cdn.jsdelivr.net/npm/@ruvector/edge-net@latest';
const WASM_URL = `${CDN_BASE}/dist/edge-net.wasm`;
const JS_URL = `${CDN_BASE}/dist/edge-net.min.js`;
// Logger
function log(...args) {
if (config.debug) {
console.log('[Edge-Net]', ...args);
}
}
// Storage keys
const CONSENT_KEY = 'edge-net-consent';
const NODE_KEY = 'edge-net-node';
// Check consent
function hasConsent() {
return localStorage.getItem(CONSENT_KEY) === 'true';
}
// Show consent banner
function showConsentBanner() {
const banner = document.createElement('div');
banner.id = 'edge-net-consent-banner';
banner.innerHTML = `
<style>
#edge-net-consent-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: white;
padding: 1rem 2rem;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 1rem;
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
}
#edge-net-consent-banner .content {
flex: 1;
min-width: 200px;
}
#edge-net-consent-banner h4 {
margin: 0 0 0.5rem 0;
font-size: 1rem;
color: #00d4ff;
}
#edge-net-consent-banner p {
margin: 0;
font-size: 0.9rem;
color: #888;
}
#edge-net-consent-banner .buttons {
display: flex;
gap: 0.75rem;
}
#edge-net-consent-banner button {
padding: 0.6rem 1.2rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
transition: transform 0.2s;
}
#edge-net-consent-banner button:hover {
transform: translateY(-2px);
}
#edge-net-consent-banner .accept {
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
color: white;
}
#edge-net-consent-banner .decline {
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
#edge-net-consent-banner .learn-more {
color: #00d4ff;
text-decoration: underline;
background: none;
padding: 0;
}
</style>
<div class="content">
<h4>Help power AI features</h4>
<p>Contribute idle compute to earn <strong>rUv</strong> (Resource Utility Vouchers). <button class="learn-more">Learn more</button></p>
</div>
<div class="buttons">
<button class="decline">Not now</button>
<button class="accept">Accept & Earn rUv</button>
</div>
`;
document.body.appendChild(banner);
// Event handlers
banner.querySelector('.accept').addEventListener('click', () => {
localStorage.setItem(CONSENT_KEY, 'true');
banner.remove();
init();
});
banner.querySelector('.decline').addEventListener('click', () => {
localStorage.setItem(CONSENT_KEY, 'false');
banner.remove();
});
banner.querySelector('.learn-more').addEventListener('click', () => {
window.open('https://github.com/ruvnet/ruvector/tree/main/examples/edge-net', '_blank');
});
}
// Create badge element
function createBadge() {
const badge = document.createElement('div');
badge.id = 'edge-net-badge';
const positions = {
'bottom-right': 'bottom: 20px; right: 20px;',
'bottom-left': 'bottom: 20px; left: 20px;',
'top-right': 'top: 20px; right: 20px;',
'top-left': 'top: 20px; left: 20px;',
};
badge.innerHTML = `
<style>
#edge-net-badge {
position: fixed;
${positions[config.badgePosition]}
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: white;
padding: 0.75rem 1rem;
border-radius: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 0.85rem;
z-index: 9999;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 0.75rem;
}
#edge-net-badge:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 212, 255, 0.2);
}
#edge-net-badge.minimized {
padding: 0.5rem;
border-radius: 50%;
}
#edge-net-badge.minimized .details {
display: none;
}
#edge-net-badge .status {
width: 10px;
height: 10px;
border-radius: 50%;
background: #00ff88;
animation: edge-net-pulse 2s infinite;
}
#edge-net-badge .status.paused {
background: #ffaa00;
animation: none;
}
#edge-net-badge .status.error {
background: #ff6b6b;
animation: none;
}
#edge-net-badge .balance {
color: #00ff88;
font-weight: bold;
}
#edge-net-badge .multiplier {
color: #ff6b6b;
font-size: 0.75rem;
}
@keyframes edge-net-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
<div class="status"></div>
<div class="details">
<span class="balance">0 rUv</span>
<span class="multiplier">• 10.0x</span>
</div>
`;
document.body.appendChild(badge);
// Toggle minimize on click
badge.addEventListener('click', () => {
badge.classList.toggle('minimized');
});
return badge;
}
// Update badge
function updateBadge(badge, stats) {
const balanceEl = badge.querySelector('.balance');
const multiplierEl = badge.querySelector('.multiplier');
const statusEl = badge.querySelector('.status');
if (balanceEl) balanceEl.textContent = `${stats.balance.toFixed(2)} rUv`;
if (multiplierEl) multiplierEl.textContent = `${stats.multiplier.toFixed(1)}x`;
if (statusEl) {
statusEl.classList.remove('paused', 'error');
if (stats.paused) statusEl.classList.add('paused');
if (stats.error) statusEl.classList.add('error');
}
}
// Load Edge-Net module
async function loadModule() {
log('Loading Edge-Net module...');
// Dynamic import from CDN
const module = await import(JS_URL);
return module.EdgeNet;
}
// Initialize Edge-Net
async function init() {
try {
log('Initializing with config:', config);
const EdgeNet = await loadModule();
const node = await EdgeNet.init({
siteId: config.siteId,
contribution: config.cpuLimit,
wasmUrl: WASM_URL,
onCredit: (earned, total) => {
log(`Earned ${earned} QDAG, total: ${total}`);
},
onError: (error) => {
console.error('[Edge-Net] Error:', error);
},
});
// Create badge if enabled
let badge = null;
if (config.showBadge) {
badge = createBadge();
}
// Update loop
setInterval(() => {
const stats = node.getStats();
if (badge) {
updateBadge(badge, stats);
}
}, 1000);
// Expose to window for debugging
window.EdgeNetNode = node;
log('Edge-Net initialized successfully');
// Dispatch ready event
window.dispatchEvent(new CustomEvent('edge-net-ready', { detail: { node } }));
} catch (error) {
console.error('[Edge-Net] Failed to initialize:', error);
}
}
// Entry point
function main() {
// Wait for DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
return;
}
log('Edge-Net embed script loaded');
// Check consent
if (config.consentRequired && !hasConsent()) {
showConsentBanner();
} else if (hasConsent() || !config.consentRequired) {
init();
}
}
main();
})();

View File

@@ -0,0 +1,643 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edge-Net Demo</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #fff;
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 3rem;
}
h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
color: #888;
font-size: 1.1rem;
}
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.card {
background: rgba(255, 255, 255, 0.05);
border-radius: 16px;
padding: 1.5rem;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
}
.card h3 {
font-size: 0.9rem;
color: #888;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 0.5rem;
}
.stat {
font-size: 2.5rem;
font-weight: bold;
color: #00d4ff;
}
.stat.credits {
color: #00ff88;
}
.stat.multiplier {
color: #ff6b6b;
}
.stat-label {
font-size: 0.85rem;
color: #666;
}
.status-indicator {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: rgba(0, 255, 136, 0.1);
border-radius: 20px;
font-size: 0.9rem;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #00ff88;
animation: pulse 2s infinite;
}
.status-dot.disconnected {
background: #ff6b6b;
animation: none;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.controls {
display: flex;
gap: 1rem;
flex-wrap: wrap;
margin-bottom: 2rem;
}
button {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s;
}
button.primary {
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
color: white;
}
button.secondary {
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.log {
background: rgba(0, 0, 0, 0.3);
border-radius: 12px;
padding: 1rem;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.85rem;
max-height: 300px;
overflow-y: auto;
}
.log-entry {
padding: 0.25rem 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.log-entry.info { color: #00d4ff; }
.log-entry.success { color: #00ff88; }
.log-entry.error { color: #ff6b6b; }
.log-entry.warning { color: #ffaa00; }
.timestamp {
color: #666;
margin-right: 0.5rem;
}
.consent-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.consent-modal.hidden {
display: none;
}
.consent-content {
background: #1a1a2e;
border-radius: 16px;
padding: 2rem;
max-width: 500px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.consent-content h2 {
margin-bottom: 1rem;
}
.consent-content p {
color: #888;
margin-bottom: 1.5rem;
line-height: 1.6;
}
.consent-content ul {
margin-bottom: 1.5rem;
padding-left: 1.5rem;
}
.consent-content li {
color: #00ff88;
margin-bottom: 0.5rem;
}
.consent-buttons {
display: flex;
gap: 1rem;
}
.slider-container {
margin-bottom: 1.5rem;
}
.slider-container label {
display: block;
margin-bottom: 0.5rem;
color: #888;
}
input[type="range"] {
width: 100%;
height: 8px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.1);
appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #00d4ff;
cursor: pointer;
}
.cpu-value {
text-align: center;
font-size: 1.5rem;
color: #00d4ff;
margin-top: 0.5rem;
}
.task-form {
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.task-form h3 {
margin-bottom: 1rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
color: #888;
}
select, input[type="text"] {
width: 100%;
padding: 0.75rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(0, 0, 0, 0.3);
color: white;
font-size: 1rem;
}
</style>
</head>
<body>
<!-- Consent Modal -->
<div id="consent-modal" class="consent-modal">
<div class="consent-content">
<h2>Join the Edge-Net Compute Network</h2>
<p>
Contribute idle CPU cycles while browsing to earn <strong>rUv</strong> (Resource Utility Vouchers).
Use your vouchers to access distributed AI compute power.
</p>
<ul>
<li>Earn rUv while you browse</li>
<li>Early adopter bonus: <span id="multiplier-preview">10x</span> multiplier</li>
<li>Zero cost - use what you earn</li>
<li>Full privacy - no personal data collected</li>
</ul>
<div class="slider-container">
<label>CPU Contribution Limit</label>
<input type="range" id="cpu-slider" min="10" max="50" value="30">
<div class="cpu-value"><span id="cpu-value">30</span>% max CPU</div>
</div>
<div class="consent-buttons">
<button class="primary" id="accept-btn">Start Contributing</button>
<button class="secondary" id="decline-btn">Not Now</button>
</div>
</div>
</div>
<div class="container">
<header>
<h1>Edge-Net Demo</h1>
<p class="subtitle">Distributed Compute Intelligence Network</p>
</header>
<div class="dashboard">
<div class="card">
<h3>Status</h3>
<div class="status-indicator">
<span class="status-dot disconnected" id="status-dot"></span>
<span id="status-text">Disconnected</span>
</div>
<div style="margin-top: 1rem;">
<span class="stat-label">Node ID:</span>
<div id="node-id" style="font-family: monospace; font-size: 0.8rem; color: #666;">-</div>
</div>
</div>
<div class="card">
<h3>Balance</h3>
<div class="stat credits" id="balance">0</div>
<div class="stat-label">rUv (Resource Utility Vouchers)</div>
</div>
<div class="card">
<h3>Multiplier</h3>
<div class="stat multiplier" id="multiplier">1.0x</div>
<div class="stat-label">Early Adopter Bonus</div>
</div>
<div class="card">
<h3>Tasks Completed</h3>
<div class="stat" id="tasks">0</div>
<div class="stat-label">Total Tasks</div>
</div>
<div class="card">
<h3>Uptime</h3>
<div class="stat" id="uptime">0:00</div>
<div class="stat-label">Hours Contributing</div>
</div>
<div class="card">
<h3>Connected Peers</h3>
<div class="stat" id="peers">0</div>
<div class="stat-label">Network Nodes</div>
</div>
</div>
<div class="controls">
<button class="primary" id="start-btn" disabled>Start Contributing</button>
<button class="secondary" id="pause-btn" disabled>Pause</button>
<button class="secondary" id="stake-btn" disabled>Stake 100 rUv</button>
<button class="secondary" id="disconnect-btn" disabled>Disconnect</button>
</div>
<!-- Task Submission -->
<div class="task-form">
<h3>Submit a Task (spend rUv)</h3>
<div class="form-group">
<label>Task Type</label>
<select id="task-type">
<option value="vector_search">Vector Search (1 rUv)</option>
<option value="embedding">Generate Embedding (5 rUv)</option>
<option value="encryption">Encryption (0.1 rUv)</option>
</select>
</div>
<button class="primary" id="submit-task-btn" disabled>Submit Task</button>
</div>
<div class="card">
<h3>Activity Log</h3>
<div class="log" id="log">
<div class="log-entry info">
<span class="timestamp">[--:--:--]</span>
Waiting for initialization...
</div>
</div>
</div>
</div>
<script type="module">
// Mock EdgeNet for demo (replace with actual import in production)
// import { EdgeNet } from '@ruvector/edge-net';
// DOM Elements
const consentModal = document.getElementById('consent-modal');
const cpuSlider = document.getElementById('cpu-slider');
const cpuValue = document.getElementById('cpu-value');
const acceptBtn = document.getElementById('accept-btn');
const declineBtn = document.getElementById('decline-btn');
const startBtn = document.getElementById('start-btn');
const pauseBtn = document.getElementById('pause-btn');
const stakeBtn = document.getElementById('stake-btn');
const disconnectBtn = document.getElementById('disconnect-btn');
const submitTaskBtn = document.getElementById('submit-task-btn');
const logContainer = document.getElementById('log');
// State
let node = null;
let isConnected = false;
let isPaused = false;
let startTime = null;
let updateInterval = null;
// Check for prior consent
const hasConsent = localStorage.getItem('edge-net-consent') === 'true';
if (hasConsent) {
consentModal.classList.add('hidden');
initEdgeNet(parseInt(localStorage.getItem('edge-net-cpu') || '30'));
}
// CPU slider
cpuSlider.addEventListener('input', () => {
cpuValue.textContent = cpuSlider.value;
});
// Accept consent
acceptBtn.addEventListener('click', async () => {
const cpuLimit = parseInt(cpuSlider.value);
localStorage.setItem('edge-net-consent', 'true');
localStorage.setItem('edge-net-cpu', cpuLimit.toString());
consentModal.classList.add('hidden');
await initEdgeNet(cpuLimit);
});
// Decline consent
declineBtn.addEventListener('click', () => {
consentModal.classList.add('hidden');
log('User declined edge-net participation', 'warning');
});
// Initialize EdgeNet
async function initEdgeNet(cpuLimit) {
log('Initializing Edge-Net...', 'info');
try {
// Mock initialization for demo
// In production: node = await EdgeNet.init({ siteId: 'demo', contribution: cpuLimit / 100 });
await simulateInit();
node = createMockNode();
isConnected = true;
startTime = Date.now();
updateUI();
startBtn.disabled = true;
pauseBtn.disabled = false;
stakeBtn.disabled = false;
disconnectBtn.disabled = false;
submitTaskBtn.disabled = false;
log(`Connected! Node ID: ${node.nodeId}`, 'success');
log(`Current multiplier: ${node.multiplier.toFixed(1)}x`, 'info');
// Start update loop
updateInterval = setInterval(updateStats, 1000);
// Simulate earning credits
simulateEarnings();
} catch (error) {
log(`Failed to initialize: ${error.message}`, 'error');
}
}
// Button handlers
pauseBtn.addEventListener('click', () => {
if (isPaused) {
isPaused = false;
pauseBtn.textContent = 'Pause';
log('Resumed contribution', 'info');
} else {
isPaused = true;
pauseBtn.textContent = 'Resume';
log('Paused contribution', 'warning');
}
});
stakeBtn.addEventListener('click', async () => {
if (node && node.balance >= 100) {
node.staked += 100;
node.balance -= 100;
updateUI();
log('Staked 100 rUv for priority task assignment', 'success');
} else {
log('Insufficient balance for staking', 'error');
}
});
disconnectBtn.addEventListener('click', () => {
if (updateInterval) {
clearInterval(updateInterval);
}
isConnected = false;
isPaused = false;
node = null;
updateUI();
log('Disconnected from Edge-Net', 'warning');
startBtn.disabled = false;
pauseBtn.disabled = true;
stakeBtn.disabled = true;
disconnectBtn.disabled = true;
submitTaskBtn.disabled = true;
});
startBtn.addEventListener('click', () => {
const cpuLimit = parseInt(localStorage.getItem('edge-net-cpu') || '30');
initEdgeNet(cpuLimit);
});
submitTaskBtn.addEventListener('click', async () => {
const taskType = document.getElementById('task-type').value;
const costs = { vector_search: 1, embedding: 5, encryption: 0.1 };
const cost = costs[taskType];
if (node && node.balance >= cost) {
node.balance -= cost;
log(`Submitted ${taskType} task (-${cost} rUv)`, 'info');
// Simulate task completion
await new Promise(r => setTimeout(r, 1500));
log(`Task completed: ${taskType}`, 'success');
updateUI();
} else {
log('Insufficient balance for task', 'error');
}
});
// Helper functions
function log(message, type = 'info') {
const time = new Date().toLocaleTimeString();
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
entry.innerHTML = `<span class="timestamp">[${time}]</span> ${message}`;
logContainer.appendChild(entry);
logContainer.scrollTop = logContainer.scrollHeight;
}
function updateUI() {
const statusDot = document.getElementById('status-dot');
const statusText = document.getElementById('status-text');
if (isConnected) {
statusDot.classList.remove('disconnected');
statusText.textContent = isPaused ? 'Paused' : 'Contributing';
document.getElementById('node-id').textContent = node?.nodeId || '-';
document.getElementById('balance').textContent = node?.balance.toFixed(2) + ' rUv' || '0 rUv';
document.getElementById('multiplier').textContent = `${node?.multiplier.toFixed(1)}x` || '1.0x';
document.getElementById('tasks').textContent = node?.tasksCompleted || '0';
document.getElementById('peers').textContent = node?.peers || '0';
} else {
statusDot.classList.add('disconnected');
statusText.textContent = 'Disconnected';
document.getElementById('node-id').textContent = '-';
document.getElementById('balance').textContent = '0 rUv';
document.getElementById('multiplier').textContent = '1.0x';
document.getElementById('tasks').textContent = '0';
document.getElementById('uptime').textContent = '0:00';
document.getElementById('peers').textContent = '0';
}
}
function updateStats() {
if (!isConnected || !startTime) return;
// Update uptime
const elapsed = Math.floor((Date.now() - startTime) / 1000);
const hours = Math.floor(elapsed / 3600);
const minutes = Math.floor((elapsed % 3600) / 60);
document.getElementById('uptime').textContent = `${hours}:${minutes.toString().padStart(2, '0')}`;
updateUI();
}
// Mock functions for demo
async function simulateInit() {
await new Promise(r => setTimeout(r, 1000));
}
function createMockNode() {
return {
nodeId: 'node-' + Math.random().toString(36).substr(2, 8),
balance: 0,
staked: 0,
multiplier: 9.1, // Early adopter bonus
tasksCompleted: 0,
peers: Math.floor(Math.random() * 50) + 10,
};
}
function simulateEarnings() {
if (!isConnected) return;
setInterval(() => {
if (!isConnected || isPaused || !node) return;
// Simulate task completion
if (Math.random() > 0.7) {
const baseReward = 0.1 + Math.random() * 0.4;
const reward = baseReward * node.multiplier;
node.balance += reward;
node.tasksCompleted++;
log(`Task completed! +${reward.toFixed(2)} rUv (${node.multiplier.toFixed(1)}x bonus)`, 'success');
// Randomly add peers
if (Math.random() > 0.8) {
node.peers += Math.floor(Math.random() * 3) + 1;
}
updateUI();
}
}, 3000);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,644 @@
# Edge-Net Genesis Nodes on Google Cloud
Deploy genesis relay nodes as Google Cloud Functions for global edge distribution.
Manage rUv (Resource Utility Vouchers) ledger and bootstrap the network until self-sustaining.
```
┌─────────────────────────────────────────────────────────────────────────┐
│ GENESIS NODE ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ GLOBAL EDGE NETWORK │ │
│ │ │ │
│ │ us-east1 europe-west1 asia-east1 │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │Genesis │ │Genesis │ │Genesis │ │ │
│ │ │Node 1 │◄──────►│Node 2 │◄─────────►│Node 3 │ │ │
│ │ └───┬────┘ └───┬────┘ └───┬────┘ │ │
│ │ │ │ │ │ │
│ │ └─────────────────┼────────────────────┘ │ │
│ │ │ │ │
│ │ ┌───────────▼───────────┐ │ │
│ │ │ Cloud Firestore │ │ │
│ │ │ (QDAG Ledger Sync) │ │ │
│ │ └───────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Browser Nodes Connect to Nearest Genesis Node via Edge CDN │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
## Why Google Cloud Functions?
| Feature | Benefit |
|---------|---------|
| **Global Edge** | 35+ regions, <50ms latency worldwide |
| **Auto-scaling** | 0 to millions of requests |
| **Pay-per-use** | $0 when idle, pennies under load |
| **Cold start** | <100ms with min instances |
| **WebSocket** | Via Cloud Run for persistent connections |
## Prerequisites
```bash
# Install Google Cloud SDK
curl https://sdk.cloud.google.com | bash
# Login and set project
gcloud auth login
gcloud config set project YOUR_PROJECT_ID
# Enable required APIs
gcloud services enable \
cloudfunctions.googleapis.com \
run.googleapis.com \
firestore.googleapis.com \
secretmanager.googleapis.com
```
## Deployment Steps
### 1. Create Firestore Database
```bash
# Create Firestore in Native mode (for QDAG ledger sync)
gcloud firestore databases create \
--region=nam5 \
--type=firestore-native
```
### 2. Store Genesis Keys
```bash
# Generate genesis keypair
node -e "
const crypto = require('crypto');
const keypair = crypto.generateKeyPairSync('ed25519');
console.log(JSON.stringify({
public: keypair.publicKey.export({type: 'spki', format: 'der'}).toString('hex'),
private: keypair.privateKey.export({type: 'pkcs8', format: 'der'}).toString('hex')
}));
" > genesis-keys.json
# Store in Secret Manager
gcloud secrets create edge-net-genesis-keys \
--data-file=genesis-keys.json
# Clean up local file
rm genesis-keys.json
```
### 3. Deploy Genesis Functions
```bash
# Deploy to multiple regions
for REGION in us-east1 europe-west1 asia-east1; do
gcloud functions deploy edge-net-genesis-$REGION \
--gen2 \
--runtime=nodejs20 \
--region=$REGION \
--source=. \
--entry-point=genesisHandler \
--trigger-http \
--allow-unauthenticated \
--memory=256MB \
--timeout=60s \
--min-instances=1 \
--max-instances=100 \
--set-env-vars=REGION=$REGION,NODE_ENV=production
done
```
### 4. Deploy WebSocket Relay (Cloud Run)
```bash
# Build and push container
gcloud builds submit \
--tag gcr.io/YOUR_PROJECT/edge-net-relay
# Deploy to Cloud Run
gcloud run deploy edge-net-relay \
--image gcr.io/YOUR_PROJECT/edge-net-relay \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--memory 512Mi \
--min-instances 1 \
--max-instances 10 \
--concurrency 1000 \
--timeout 3600
```
## Genesis Node Code
### index.js (Cloud Function)
```javascript
const functions = require('@google-cloud/functions-framework');
const { Firestore } = require('@google-cloud/firestore');
const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');
const firestore = new Firestore();
const secrets = new SecretManagerServiceClient();
// Genesis node state
let genesisKeys = null;
let ledgerState = null;
// Initialize genesis node
async function init() {
if (genesisKeys) return;
// Load genesis keys from Secret Manager
const [version] = await secrets.accessSecretVersion({
name: 'projects/YOUR_PROJECT/secrets/edge-net-genesis-keys/versions/latest',
});
genesisKeys = JSON.parse(version.payload.data.toString());
// Load or create genesis ledger
const genesisDoc = await firestore.collection('edge-net').doc('genesis').get();
if (!genesisDoc.exists) {
// Create genesis transaction
ledgerState = await createGenesisLedger();
await firestore.collection('edge-net').doc('genesis').set(ledgerState);
} else {
ledgerState = genesisDoc.data();
}
}
// Create genesis ledger with initial supply
async function createGenesisLedger() {
const crypto = require('crypto');
const genesis = {
id: crypto.randomBytes(32).toString('hex'),
type: 'genesis',
amount: 1_000_000_000_000_000, // 1 billion rUv (Resource Utility Vouchers)
recipient: genesisKeys.public,
timestamp: Date.now(),
transactions: [],
tips: [],
totalSupply: 1_000_000_000_000_000,
networkCompute: 0,
nodeCount: 0,
// Genesis sunset thresholds
sunsetPhase: 0, // 0=active, 1=transition, 2=read-only, 3=retired
sunsetThresholds: {
stopNewConnections: 10_000,
readOnlyMode: 50_000,
safeRetirement: 100_000,
},
};
return genesis;
}
// Main handler
functions.http('genesisHandler', async (req, res) => {
// CORS
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.set('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
return res.status(204).send('');
}
await init();
const { action, data } = req.body || {};
try {
switch (action) {
case 'status':
return res.json({
nodeId: `genesis-${process.env.REGION}`,
region: process.env.REGION,
ledger: {
totalSupply: ledgerState.totalSupply,
networkCompute: ledgerState.networkCompute,
nodeCount: ledgerState.nodeCount,
tipCount: ledgerState.tips.length,
},
multiplier: calculateMultiplier(ledgerState.networkCompute),
currency: 'rUv', // Resource Utility Vouchers
sunsetStatus: getSunsetStatus(ledgerState),
});
case 'register':
return await handleRegister(data, res);
case 'submitTransaction':
return await handleTransaction(data, res);
case 'getTips':
return res.json({ tips: ledgerState.tips.slice(-10) });
case 'sync':
return await handleSync(data, res);
default:
return res.status(400).json({ error: 'Unknown action' });
}
} catch (error) {
console.error('Error:', error);
return res.status(500).json({ error: error.message });
}
});
// Handle node registration
async function handleRegister(data, res) {
const { nodeId, pubkey, stake } = data;
// Validate registration
if (!nodeId || !pubkey) {
return res.status(400).json({ error: 'Missing nodeId or pubkey' });
}
// Store node in Firestore
await firestore.collection('edge-net').doc('nodes').collection(nodeId).set({
pubkey,
stake: stake || 0,
registeredAt: Date.now(),
region: process.env.REGION,
reputation: 0.5,
});
ledgerState.nodeCount++;
return res.json({
success: true,
nodeId,
multiplier: calculateMultiplier(ledgerState.networkCompute),
});
}
// Handle QDAG transaction
async function handleTransaction(data, res) {
const { transaction, signature } = data;
// Validate transaction
if (!validateTransaction(transaction, signature)) {
return res.status(400).json({ error: 'Invalid transaction' });
}
// Apply to ledger
await applyTransaction(transaction);
// Store in Firestore
await firestore.collection('edge-net').doc('transactions')
.collection(transaction.id).set(transaction);
// Update tips
ledgerState.tips = ledgerState.tips.filter(
tip => !transaction.validates.includes(tip)
);
ledgerState.tips.push(transaction.id);
// Sync to other genesis nodes
await syncToOtherNodes(transaction);
return res.json({
success: true,
txId: transaction.id,
newBalance: await getBalance(transaction.sender),
});
}
// Handle ledger sync from other genesis nodes
async function handleSync(data, res) {
const { transactions, fromNode } = data;
let imported = 0;
for (const tx of transactions) {
if (!ledgerState.transactions.find(t => t.id === tx.id)) {
if (validateTransaction(tx, tx.signature)) {
await applyTransaction(tx);
imported++;
}
}
}
return res.json({ imported, total: ledgerState.transactions.length });
}
// Validate transaction signature and structure
function validateTransaction(tx, signature) {
// TODO: Implement full Ed25519 verification
return tx && tx.id && tx.sender && tx.recipient && tx.amount >= 0;
}
// Apply transaction to ledger state
async function applyTransaction(tx) {
ledgerState.transactions.push(tx);
// Update network compute for reward calculation
if (tx.type === 'compute_reward') {
ledgerState.networkCompute += tx.computeHours || 0;
}
// Persist to Firestore
await firestore.collection('edge-net').doc('genesis').update({
transactions: ledgerState.transactions,
tips: ledgerState.tips,
networkCompute: ledgerState.networkCompute,
});
}
// Calculate contribution curve multiplier
function calculateMultiplier(networkCompute) {
const MAX_BONUS = 10.0;
const DECAY_CONSTANT = 1_000_000;
return 1 + (MAX_BONUS - 1) * Math.exp(-networkCompute / DECAY_CONSTANT);
}
// Get genesis sunset status
function getSunsetStatus(ledger) {
const thresholds = ledger.sunsetThresholds || {
stopNewConnections: 10_000,
readOnlyMode: 50_000,
safeRetirement: 100_000,
};
let phase = 0;
let phaseName = 'active';
if (ledger.nodeCount >= thresholds.safeRetirement) {
phase = 3;
phaseName = 'retired';
} else if (ledger.nodeCount >= thresholds.readOnlyMode) {
phase = 2;
phaseName = 'read_only';
} else if (ledger.nodeCount >= thresholds.stopNewConnections) {
phase = 1;
phaseName = 'transition';
}
return {
phase,
phaseName,
nodeCount: ledger.nodeCount,
nextThreshold: phase === 0 ? thresholds.stopNewConnections :
phase === 1 ? thresholds.readOnlyMode :
phase === 2 ? thresholds.safeRetirement : 0,
canRetire: phase >= 3,
message: phase >= 3 ?
'Network is self-sustaining. Genesis nodes can be safely retired.' :
`${((ledger.nodeCount / thresholds.safeRetirement) * 100).toFixed(1)}% to self-sustaining`
};
}
// Get balance for a node
async function getBalance(nodeId) {
let balance = 0;
for (const tx of ledgerState.transactions) {
if (tx.recipient === nodeId) balance += tx.amount;
if (tx.sender === nodeId) balance -= tx.amount;
}
return balance;
}
// Sync transaction to other genesis nodes
async function syncToOtherNodes(transaction) {
const regions = ['us-east1', 'europe-west1', 'asia-east1'];
const currentRegion = process.env.REGION;
for (const region of regions) {
if (region === currentRegion) continue;
try {
const url = `https://${region}-YOUR_PROJECT.cloudfunctions.net/edge-net-genesis-${region}`;
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'sync',
data: {
transactions: [transaction],
fromNode: `genesis-${currentRegion}`,
},
}),
});
} catch (error) {
console.error(`Failed to sync to ${region}:`, error.message);
}
}
}
```
### package.json
```json
{
"name": "edge-net-genesis",
"version": "1.0.0",
"main": "index.js",
"engines": {
"node": ">=20"
},
"dependencies": {
"@google-cloud/functions-framework": "^3.0.0",
"@google-cloud/firestore": "^7.0.0",
"@google-cloud/secret-manager": "^5.0.0"
}
}
```
## WebSocket Relay (Cloud Run)
### Dockerfile
```dockerfile
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 8080
CMD ["node", "relay.js"]
```
### relay.js
```javascript
const WebSocket = require('ws');
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Edge-Net Relay\n');
});
const wss = new WebSocket.Server({ server });
// Connected nodes
const nodes = new Map();
// Handle WebSocket connections
wss.on('connection', (ws, req) => {
const nodeId = req.headers['x-node-id'] || `anon-${Date.now()}`;
nodes.set(nodeId, ws);
console.log(`Node connected: ${nodeId}`);
ws.on('message', (data) => {
try {
const message = JSON.parse(data);
handleMessage(nodeId, message, ws);
} catch (error) {
console.error('Invalid message:', error);
}
});
ws.on('close', () => {
nodes.delete(nodeId);
console.log(`Node disconnected: ${nodeId}`);
});
// Send welcome message
ws.send(JSON.stringify({
type: 'welcome',
nodeId,
peers: nodes.size,
}));
});
// Handle incoming messages
function handleMessage(fromId, message, ws) {
switch (message.type) {
case 'broadcast':
// Broadcast to all other nodes
for (const [id, peer] of nodes) {
if (id !== fromId && peer.readyState === WebSocket.OPEN) {
peer.send(JSON.stringify({
type: 'message',
from: fromId,
data: message.data,
}));
}
}
break;
case 'direct':
// Send to specific node
const target = nodes.get(message.to);
if (target && target.readyState === WebSocket.OPEN) {
target.send(JSON.stringify({
type: 'message',
from: fromId,
data: message.data,
}));
}
break;
case 'peers':
// Return list of connected peers
ws.send(JSON.stringify({
type: 'peers',
peers: Array.from(nodes.keys()).filter(id => id !== fromId),
}));
break;
default:
console.warn('Unknown message type:', message.type);
}
}
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`Edge-Net Relay listening on port ${PORT}`);
});
```
## Monitoring
### Cloud Monitoring Dashboard
```bash
# Create dashboard
gcloud monitoring dashboards create \
--config-from-file=dashboard.json
```
### dashboard.json
```json
{
"displayName": "Edge-Net Genesis Nodes",
"mosaicLayout": {
"columns": 12,
"tiles": [
{
"width": 6,
"height": 4,
"widget": {
"title": "Request Count by Region",
"xyChart": {
"dataSets": [{
"timeSeriesQuery": {
"timeSeriesFilter": {
"filter": "resource.type=\"cloud_function\" AND metric.type=\"cloudfunctions.googleapis.com/function/execution_count\""
}
}
}]
}
}
},
{
"xPos": 6,
"width": 6,
"height": 4,
"widget": {
"title": "Execution Latency",
"xyChart": {
"dataSets": [{
"timeSeriesQuery": {
"timeSeriesFilter": {
"filter": "resource.type=\"cloud_function\" AND metric.type=\"cloudfunctions.googleapis.com/function/execution_times\""
}
}
}]
}
}
}
]
}
}
```
## Cost Estimate
| Component | Monthly Cost (Low Traffic) | Monthly Cost (High Traffic) |
|-----------|---------------------------|----------------------------|
| Cloud Functions (3 regions) | $5 | $50 |
| Cloud Run (WebSocket) | $10 | $100 |
| Firestore | $1 | $25 |
| Secret Manager | $0.06 | $0.06 |
| **Total** | **~$16** | **~$175** |
## Security Checklist
- [ ] Enable Cloud Armor for DDoS protection
- [ ] Configure VPC Service Controls
- [ ] Set up Cloud Audit Logs
- [ ] Enable Binary Authorization
- [ ] Configure IAM least privilege
- [ ] Enable Secret Manager rotation
- [ ] Set up alerting policies
## Next Steps
1. Deploy to all regions
2. Initialize genesis ledger
3. Configure DNS with global load balancer
4. Set up monitoring and alerting
5. Run load tests
6. Enable Cloud CDN for static assets