3
.gitignore
vendido
Archivo normal
3
.gitignore
vendido
Archivo normal
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
*-lock.json
|
||||
*.lock
|
||||
274
README.md
Archivo normal
274
README.md
Archivo normal
@@ -0,0 +1,274 @@
|
||||
# Ring Network - Two Ring Topology with WebRTC
|
||||
|
||||
A decentralized peer-to-peer network implementation using WebRTC for communication in a double ring topology with optional Oracle nodes for enhanced network services.
|
||||
|
||||
## 🌟 Features
|
||||
|
||||
### Core Network Features
|
||||
- **Double Ring Topology**: Inner and outer rings for redundancy and efficient routing
|
||||
- **WebRTC Peer-to-Peer**: Direct browser-to-browser/node-to-node communication
|
||||
- **Automatic Discovery**: Nodes can discover and connect to each other
|
||||
- **Message Routing**: Efficient message propagation through ring structures
|
||||
- **Network Resilience**: Automatic adaptation to node connections/disconnections
|
||||
|
||||
### Oracle Node Capabilities
|
||||
- **Network Analysis**: Topology analysis and health monitoring
|
||||
- **Distributed Data Storage**: Replicated data storage across Oracle nodes
|
||||
- **Consensus Mechanisms**: Distributed decision making
|
||||
- **Advanced Routing**: Sophisticated routing strategies
|
||||
- **Health Monitoring**: Continuous network health assessment
|
||||
- **Metrics Collection**: Network performance and usage statistics
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
```
|
||||
Inner Ring: A → B → C → A
|
||||
Outer Ring: A ← B ← C ← A
|
||||
|
||||
Where:
|
||||
- A, B, C are network nodes
|
||||
- → indicates message flow direction
|
||||
- Each node maintains connections to its neighbors in both rings
|
||||
```
|
||||
|
||||
### Node Types
|
||||
|
||||
1. **Regular Nodes**: Basic network participants that route messages
|
||||
2. **Oracle Nodes**: Enhanced nodes providing additional services
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Clone or create the project directory
|
||||
cd ringnet
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
```
|
||||
|
||||
### Running the Network
|
||||
|
||||
1. **Start the first node (Oracle) as bootstrap:**
|
||||
```bash
|
||||
npm run start:oracle -- --port 8080
|
||||
```
|
||||
|
||||
2. **Start additional nodes:**
|
||||
```bash
|
||||
# Regular node
|
||||
npm run start:node -- --port 8081 --bootstrap localhost:8080
|
||||
|
||||
# Another Oracle node
|
||||
npm run start:oracle -- --port 8082 --bootstrap localhost:8080
|
||||
```
|
||||
|
||||
3. **Start more nodes:**
|
||||
```bash
|
||||
npm run start:node -- --port 8083 --bootstrap localhost:8080
|
||||
```
|
||||
|
||||
## 📖 Usage
|
||||
|
||||
### Command Line Options
|
||||
|
||||
```bash
|
||||
--port <port> # Port to listen on (default: random)
|
||||
--id <id> # Node ID (default: auto-generated UUID)
|
||||
--bootstrap <addr> # Bootstrap node address (host:port)
|
||||
--position <pos> # Initial ring position (default: 0)
|
||||
--help # Show help message
|
||||
```
|
||||
|
||||
### Interactive Commands
|
||||
|
||||
Once a node is running, you can use these commands:
|
||||
|
||||
#### Regular Node Commands
|
||||
- `send <message>` - Send a message through the ring
|
||||
- `info` - Show network information
|
||||
- `peers` - List connected peers
|
||||
- `help` - Show available commands
|
||||
- `quit` - Exit the node
|
||||
|
||||
#### Oracle Node Commands (additional)
|
||||
- `health` - Perform network health check
|
||||
- `metrics` - Show network metrics
|
||||
- `analyze` - Analyze network topology
|
||||
- `store <key> <value>` - Store data in distributed storage
|
||||
- `get <key>` - Retrieve data from storage
|
||||
- `propose <text>` - Create a consensus proposal
|
||||
- `vote <id> <yes|no>` - Vote on a proposal
|
||||
|
||||
## 🔮 Oracle Services
|
||||
|
||||
Oracle nodes provide enhanced services to the network:
|
||||
|
||||
### 1. Network Analysis (`network-analysis`)
|
||||
- Topology mapping and analysis
|
||||
- Network health assessment
|
||||
- Performance recommendations
|
||||
|
||||
### 2. Data Storage (`data-storage`)
|
||||
- Distributed key-value storage
|
||||
- Automatic replication across Oracles
|
||||
- TTL (Time-To-Live) support
|
||||
|
||||
### 3. Consensus (`consensus`)
|
||||
- Proposal creation and voting
|
||||
- Majority-based decision making
|
||||
- Distributed agreement protocols
|
||||
|
||||
### 4. Routing (`routing`)
|
||||
- Advanced routing strategies
|
||||
- Shortest path calculation
|
||||
- Multi-path routing options
|
||||
|
||||
### 5. Health Monitoring (`health-check`)
|
||||
- Continuous network monitoring
|
||||
- Failure detection and reporting
|
||||
- Recovery recommendations
|
||||
|
||||
### 6. Network Metrics (`network-metrics`)
|
||||
- Performance statistics
|
||||
- Usage analytics
|
||||
- Historical data tracking
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
Run the test suite to verify network functionality:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
The test suite covers:
|
||||
- Node initialization
|
||||
- Peer connections
|
||||
- Ring topology formation
|
||||
- Oracle services
|
||||
- Message routing
|
||||
- Network resilience
|
||||
|
||||
## 📝 API Reference
|
||||
|
||||
### RingNode Class
|
||||
|
||||
```javascript
|
||||
import { RingNode } from './src/ring-node.js';
|
||||
|
||||
const node = new RingNode({
|
||||
id: 'optional-id',
|
||||
port: 8080,
|
||||
ringPosition: 0,
|
||||
isOracle: false
|
||||
});
|
||||
|
||||
// Events
|
||||
node.on('peerConnected', (peerId) => { });
|
||||
node.on('peerDisconnected', (peerId) => { });
|
||||
node.on('ringMessage', ({ from, payload }) => { });
|
||||
node.on('networkUpdate', ({ from, payload }) => { });
|
||||
|
||||
// Methods
|
||||
node.sendRingMessage(payload, ring);
|
||||
node.joinRing(bootstrapNode);
|
||||
node.getNetworkInfo();
|
||||
node.destroy();
|
||||
```
|
||||
|
||||
### OracleNode Class
|
||||
|
||||
```javascript
|
||||
import { OracleNode } from './src/oracle-node.js';
|
||||
|
||||
const oracle = new OracleNode({
|
||||
id: 'oracle-1',
|
||||
port: 8080
|
||||
});
|
||||
|
||||
// Oracle-specific methods
|
||||
oracle.analyzeNetwork();
|
||||
oracle.handleDataStorage({ operation: 'set', key: 'test', value: 'data' });
|
||||
oracle.handleConsensus({ proposalId: 'prop1', proposal: 'Upgrade network' });
|
||||
oracle.performHealthCheck();
|
||||
oracle.getNetworkMetrics();
|
||||
```
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### WebRTC Configuration
|
||||
The nodes use public STUN servers by default:
|
||||
- `stun:stun.l.google.com:19302`
|
||||
- `stun:stun1.l.google.com:19302`
|
||||
|
||||
For production use, consider setting up your own TURN servers.
|
||||
|
||||
### Network Parameters
|
||||
- **Message History Size**: 1000 messages (prevents loops)
|
||||
- **Data TTL**: 1 hour default (configurable)
|
||||
- **Health Check Interval**: 30 seconds
|
||||
- **Metrics Collection**: 10 seconds
|
||||
- **Cleanup Interval**: 5 minutes
|
||||
|
||||
## 🛡️ Security Considerations
|
||||
|
||||
- **Message Integrity**: All messages should be signed in production
|
||||
- **Node Authentication**: Implement proper node authentication
|
||||
- **Data Encryption**: Encrypt sensitive data in storage
|
||||
- **Rate Limiting**: Implement rate limiting for message propagation
|
||||
- **Access Control**: Restrict Oracle services to authorized nodes
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Connection Failures**
|
||||
- Check firewall settings
|
||||
- Verify port availability
|
||||
- Ensure WebRTC support
|
||||
|
||||
2. **Bootstrap Node Unreachable**
|
||||
- Verify bootstrap node is running
|
||||
- Check network connectivity
|
||||
- Confirm port and address
|
||||
|
||||
3. **Ring Formation Issues**
|
||||
- Allow time for topology stabilization
|
||||
- Check node discovery process
|
||||
- Verify peer connections
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug logging by setting environment variable:
|
||||
```bash
|
||||
DEBUG=ringnet:* npm start
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Implement your changes
|
||||
4. Add tests for new functionality
|
||||
5. Run the test suite
|
||||
6. Submit a pull request
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT License - see LICENSE file for details.
|
||||
|
||||
## 🔗 Related Projects
|
||||
|
||||
- [WebRTC](https://webrtc.org/) - Real-time communication
|
||||
- [Node.js WebRTC](https://github.com/node-webrtc/node-webrtc) - WebRTC for Node.js
|
||||
- [Chord DHT](https://en.wikipedia.org/wiki/Chord_(peer-to-peer)) - Distributed hash table
|
||||
- [Kademlia](https://en.wikipedia.org/wiki/Kademlia) - Distributed hash table protocol
|
||||
|
||||
## 📚 Further Reading
|
||||
|
||||
- [Peer-to-Peer Networks](https://en.wikipedia.org/wiki/Peer-to-peer)
|
||||
- [Distributed Systems](https://en.wikipedia.org/wiki/Distributed_computing)
|
||||
- [WebRTC Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API)
|
||||
- [Consensus Algorithms](https://en.wikipedia.org/wiki/Consensus_(computer_science))
|
||||
72
demo.js
Archivo normal
72
demo.js
Archivo normal
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import chalk from 'chalk';
|
||||
|
||||
console.log(chalk.blue.bold('🔗 Ring Network Demo Setup\n'));
|
||||
|
||||
console.log(`
|
||||
${chalk.green('Welcome to the Ring Network!')}
|
||||
|
||||
Your two-ring network with WebRTC and Oracle nodes is now ready to use.
|
||||
|
||||
${chalk.yellow('🚀 Quick Start Guide:')}
|
||||
|
||||
1. ${chalk.cyan('Start the first node (Oracle) as bootstrap:')}
|
||||
npm run start:oracle -- --port 8080
|
||||
|
||||
2. ${chalk.cyan('In a new terminal, start a regular node:')}
|
||||
npm run start:node -- --port 8081 --bootstrap localhost:8080
|
||||
|
||||
3. ${chalk.cyan('In another terminal, start another Oracle:')}
|
||||
npm run start:oracle -- --port 8082 --bootstrap localhost:8080
|
||||
|
||||
4. ${chalk.cyan('Add more nodes as needed:')}
|
||||
npm run start:node -- --port 8083 --bootstrap localhost:8080
|
||||
|
||||
${chalk.magenta('💡 Interactive Commands (once nodes are running):')}
|
||||
|
||||
${chalk.yellow('Regular Node Commands:')}
|
||||
• send <message> - Send message through the ring
|
||||
• info - Show network information
|
||||
• peers - List connected peers
|
||||
• help - Show all commands
|
||||
• quit - Exit the node
|
||||
|
||||
${chalk.yellow('Oracle Node Commands (additional):')}
|
||||
• health - Perform network health check
|
||||
• metrics - Show network metrics
|
||||
• analyze - Analyze network topology
|
||||
• store <key> <val> - Store data in distributed storage
|
||||
• get <key> - Retrieve stored data
|
||||
• propose <text> - Create consensus proposal
|
||||
• vote <id> <y/n> - Vote on a proposal
|
||||
|
||||
${chalk.blue('🔮 Oracle Services Available:')}
|
||||
• Network Analysis - Topology and health monitoring
|
||||
• Data Storage - Distributed key-value store
|
||||
• Consensus - Distributed decision making
|
||||
• Routing - Advanced routing strategies
|
||||
• Health Monitoring - Continuous network monitoring
|
||||
• Metrics Collection - Performance and usage statistics
|
||||
|
||||
${chalk.red('📖 Additional Resources:')}
|
||||
• Run example: npm run example
|
||||
• Run tests: npm test
|
||||
• View help: npm start
|
||||
• Read README.md for detailed documentation
|
||||
|
||||
${chalk.green('🎯 Network Features:')}
|
||||
✅ Double ring topology (inner + outer rings)
|
||||
✅ WebRTC peer-to-peer connections
|
||||
✅ Automatic peer discovery
|
||||
✅ Message routing through rings
|
||||
✅ Oracle nodes with enhanced capabilities
|
||||
✅ Distributed data storage
|
||||
✅ Consensus mechanisms
|
||||
✅ Network health monitoring
|
||||
✅ Fault tolerance and resilience
|
||||
|
||||
${chalk.cyan('Happy networking! 🌐')}
|
||||
`);
|
||||
|
||||
process.exit(0);
|
||||
321
examples/basic-example.js
Archivo normal
321
examples/basic-example.js
Archivo normal
@@ -0,0 +1,321 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { RingNode } from '../src/ring-node.js';
|
||||
import { OracleNode } from '../src/oracle-node.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Example script demonstrating Ring Network usage
|
||||
* This creates a simple network and shows basic operations
|
||||
*/
|
||||
|
||||
console.log(chalk.blue.bold('🔗 Ring Network Example\n'));
|
||||
|
||||
class NetworkExample {
|
||||
constructor() {
|
||||
this.nodes = [];
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
async delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async createNetwork() {
|
||||
console.log(chalk.cyan('🏗️ Creating example network...'));
|
||||
|
||||
// Create Oracle node (bootstrap)
|
||||
const oracle = new OracleNode({
|
||||
id: 'oracle-example',
|
||||
port: 9000
|
||||
});
|
||||
this.nodes.push(oracle);
|
||||
|
||||
// Create regular nodes
|
||||
const node1 = new RingNode({
|
||||
id: 'node-1-example',
|
||||
port: 9001
|
||||
});
|
||||
this.nodes.push(node1);
|
||||
|
||||
const node2 = new RingNode({
|
||||
id: 'node-2-example',
|
||||
port: 9002
|
||||
});
|
||||
this.nodes.push(node2);
|
||||
|
||||
console.log(chalk.green('✅ Created 3 nodes: 1 Oracle + 2 Regular'));
|
||||
|
||||
// Set up event handlers
|
||||
this.setupEventHandlers();
|
||||
|
||||
// Allow nodes to initialize
|
||||
await this.delay(2000);
|
||||
|
||||
return { oracle, node1, node2 };
|
||||
}
|
||||
|
||||
setupEventHandlers() {
|
||||
this.nodes.forEach((node, index) => {
|
||||
const nodeType = node.isOracle ? 'Oracle' : 'Node';
|
||||
const nodeColor = node.isOracle ? chalk.yellow : chalk.blue;
|
||||
|
||||
node.on('peerConnected', (peerId) => {
|
||||
console.log(nodeColor(`${nodeType} ${index}: Connected to ${peerId.substring(0, 8)}`));
|
||||
});
|
||||
|
||||
node.on('peerDisconnected', (peerId) => {
|
||||
console.log(nodeColor(`${nodeType} ${index}: Disconnected from ${peerId.substring(0, 8)}`));
|
||||
});
|
||||
|
||||
node.on('ringMessage', ({ from, payload }) => {
|
||||
console.log(nodeColor(`${nodeType} ${index}: Received message from ${from.substring(0, 8)}: ${JSON.stringify(payload)}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async demonstrateBasicMessaging() {
|
||||
console.log(chalk.magenta('\n📨 Demonstrating basic messaging...'));
|
||||
|
||||
const [oracle, node1, node2] = this.nodes;
|
||||
|
||||
// Simulate ring connections for demonstration
|
||||
// In real usage, these would be established via WebRTC
|
||||
oracle.rings.inner.right = node1.id;
|
||||
node1.rings.inner.right = node2.id;
|
||||
node2.rings.inner.right = oracle.id;
|
||||
|
||||
// Simulate message sending
|
||||
oracle.emit('ringMessage', {
|
||||
from: oracle.id,
|
||||
payload: { type: 'greeting', message: 'Hello from Oracle!' }
|
||||
});
|
||||
|
||||
node1.emit('ringMessage', {
|
||||
from: node1.id,
|
||||
payload: { type: 'response', message: 'Node 1 received the message!' }
|
||||
});
|
||||
|
||||
await this.delay(1000);
|
||||
console.log(chalk.green('✅ Basic messaging demonstration complete'));
|
||||
}
|
||||
|
||||
async demonstrateOracleServices() {
|
||||
console.log(chalk.yellow('\n🔮 Demonstrating Oracle services...'));
|
||||
|
||||
const oracle = this.nodes[0];
|
||||
|
||||
// Network Analysis
|
||||
console.log(chalk.cyan('\n1. Network Analysis:'));
|
||||
const analysis = oracle.analyzeNetwork();
|
||||
console.log(` - Network size: ${analysis.networkSize}`);
|
||||
console.log(` - Network health: ${analysis.networkHealth.overall}%`);
|
||||
console.log(` - Recommendations: ${analysis.recommendations.length}`);
|
||||
|
||||
// Data Storage
|
||||
console.log(chalk.cyan('\n2. Data Storage:'));
|
||||
const storeResult = oracle.handleDataStorage({
|
||||
operation: 'set',
|
||||
key: 'example-key',
|
||||
value: { message: 'Hello World!', timestamp: Date.now() }
|
||||
});
|
||||
console.log(` - Data stored: ${storeResult.success}`);
|
||||
|
||||
const retrieveResult = oracle.handleDataStorage({
|
||||
operation: 'get',
|
||||
key: 'example-key'
|
||||
});
|
||||
console.log(` - Data retrieved: ${retrieveResult.success}`);
|
||||
console.log(` - Value: ${JSON.stringify(retrieveResult.value)}`);
|
||||
|
||||
// Consensus
|
||||
console.log(chalk.cyan('\n3. Consensus:'));
|
||||
const proposalId = 'example-proposal-' + Date.now();
|
||||
const consensusResult = oracle.handleConsensus({
|
||||
proposalId,
|
||||
proposal: 'Upgrade network protocol to v2.0'
|
||||
});
|
||||
console.log(` - Proposal created: ${consensusResult.success}`);
|
||||
console.log(` - Proposal ID: ${proposalId}`);
|
||||
|
||||
// Health Check
|
||||
console.log(chalk.cyan('\n4. Health Check:'));
|
||||
const health = oracle.performHealthCheck();
|
||||
console.log(` - Network healthy: ${health.healthy}`);
|
||||
console.log(` - Connected peers: ${health.checks.webrtc}`);
|
||||
console.log(` - Uptime: ${Math.round(health.checks.uptime / 1000)}s`);
|
||||
|
||||
// Metrics
|
||||
console.log(chalk.cyan('\n5. Network Metrics:'));
|
||||
const metrics = oracle.getNetworkMetrics();
|
||||
console.log(` - Messages processed: ${metrics.messagesProcessed}`);
|
||||
console.log(` - Queries answered: ${metrics.queriesAnswered}`);
|
||||
console.log(` - Data store size: ${metrics.dataStoreSize}`);
|
||||
|
||||
console.log(chalk.green('✅ Oracle services demonstration complete'));
|
||||
}
|
||||
|
||||
async demonstrateNetworkTopology() {
|
||||
console.log(chalk.blue('\n🔗 Demonstrating network topology...'));
|
||||
|
||||
const [oracle, node1, node2] = this.nodes;
|
||||
|
||||
// Show network information for each node
|
||||
this.nodes.forEach((node, index) => {
|
||||
const info = node.getNetworkInfo();
|
||||
const nodeType = node.isOracle ? 'Oracle' : 'Node';
|
||||
|
||||
console.log(chalk.cyan(`\n${nodeType} ${index} (${info.nodeId.substring(0, 8)}):`));
|
||||
console.log(` - Port: ${info.port}`);
|
||||
console.log(` - Known nodes: ${info.knownNodes.length}`);
|
||||
console.log(` - Oracle nodes: ${info.oracleNodes.length}`);
|
||||
console.log(` - Inner ring: L=${info.rings.inner.left?.substring(0, 8) || 'none'} R=${info.rings.inner.right?.substring(0, 8) || 'none'}`);
|
||||
console.log(` - Outer ring: L=${info.rings.outer.left?.substring(0, 8) || 'none'} R=${info.rings.outer.right?.substring(0, 8) || 'none'}`);
|
||||
});
|
||||
|
||||
console.log(chalk.green('\n✅ Network topology demonstration complete'));
|
||||
}
|
||||
|
||||
async demonstrateAdvancedFeatures() {
|
||||
console.log(chalk.magenta('\n⚡ Demonstrating advanced features...'));
|
||||
|
||||
const oracle = this.nodes[0];
|
||||
|
||||
// Routing strategies
|
||||
console.log(chalk.cyan('\n1. Routing Strategies:'));
|
||||
const routingResult = oracle.handleRouting({
|
||||
destination: 'target-node',
|
||||
message: { type: 'test', data: 'routing test' },
|
||||
strategy: 'ring-flood'
|
||||
});
|
||||
console.log(` - Ring flood routing: ${routingResult.success}`);
|
||||
|
||||
// Advanced consensus
|
||||
console.log(chalk.cyan('\n2. Advanced Consensus:'));
|
||||
const advancedProposal = 'proposal-' + Date.now();
|
||||
oracle.handleConsensus({
|
||||
proposalId: advancedProposal,
|
||||
proposal: 'Implement new security protocol'
|
||||
});
|
||||
|
||||
// Simulate vote
|
||||
oracle.handleConsensus({
|
||||
proposalId: advancedProposal,
|
||||
vote: 'yes'
|
||||
});
|
||||
|
||||
console.log(` - Proposal "${advancedProposal}" created and voted on`);
|
||||
|
||||
// Network monitoring
|
||||
console.log(chalk.cyan('\n3. Network Monitoring:'));
|
||||
oracle.monitorNetworkHealth();
|
||||
const recentEvents = oracle.networkMetrics.networkEvents.slice(-3);
|
||||
console.log(` - Recent network events: ${recentEvents.length}`);
|
||||
recentEvents.forEach(event => {
|
||||
console.log(` * ${event.type} at ${new Date(event.timestamp).toLocaleTimeString()}`);
|
||||
});
|
||||
|
||||
console.log(chalk.green('✅ Advanced features demonstration complete'));
|
||||
}
|
||||
|
||||
async simulateNetworkActivity() {
|
||||
console.log(chalk.blue('\n🎭 Simulating network activity...'));
|
||||
|
||||
const oracle = this.nodes[0];
|
||||
|
||||
// Simulate periodic activity
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Random data storage
|
||||
oracle.handleDataStorage({
|
||||
operation: 'set',
|
||||
key: `activity-${i}`,
|
||||
value: { activity: `Simulated activity ${i}`, timestamp: Date.now() }
|
||||
});
|
||||
|
||||
// Simulate message processing
|
||||
oracle.networkMetrics.messagesProcessed++;
|
||||
oracle.networkMetrics.queriesAnswered++;
|
||||
|
||||
await this.delay(500);
|
||||
|
||||
if (i % 2 === 0) {
|
||||
console.log(chalk.blue(` - Activity ${i + 1}/5: Data stored and metrics updated`));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green('✅ Network activity simulation complete'));
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
console.log(chalk.red('\n🧹 Cleaning up example network...'));
|
||||
|
||||
for (const node of this.nodes) {
|
||||
try {
|
||||
await node.destroy();
|
||||
} catch (error) {
|
||||
console.log(chalk.yellow(`Warning: ${error.message}`));
|
||||
}
|
||||
}
|
||||
|
||||
this.nodes = [];
|
||||
console.log(chalk.green('✅ Cleanup complete'));
|
||||
}
|
||||
|
||||
async runExample() {
|
||||
try {
|
||||
this.isRunning = true;
|
||||
|
||||
// Create network
|
||||
await this.createNetwork();
|
||||
|
||||
// Run demonstrations
|
||||
await this.demonstrateBasicMessaging();
|
||||
await this.demonstrateOracleServices();
|
||||
await this.demonstrateNetworkTopology();
|
||||
await this.demonstrateAdvancedFeatures();
|
||||
await this.simulateNetworkActivity();
|
||||
|
||||
console.log(chalk.green.bold('\n🎉 Example completed successfully!'));
|
||||
console.log(chalk.blue('\nThis example demonstrated:'));
|
||||
console.log(' ✅ Network creation and initialization');
|
||||
console.log(' ✅ Basic messaging between nodes');
|
||||
console.log(' ✅ Oracle services (analysis, storage, consensus, health)');
|
||||
console.log(' ✅ Network topology visualization');
|
||||
console.log(' ✅ Advanced features (routing, monitoring)');
|
||||
console.log(' ✅ Simulated network activity');
|
||||
|
||||
console.log(chalk.cyan('\nTo start a real network:'));
|
||||
console.log(' npm run start:oracle -- --port 8080');
|
||||
console.log(' npm run start:node -- --port 8081 --bootstrap localhost:8080');
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red.bold(`\n💥 Example failed: ${error.message}`));
|
||||
console.error(error.stack);
|
||||
} finally {
|
||||
await this.cleanup();
|
||||
this.isRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log(chalk.yellow('\n🛑 Shutting down example...'));
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Run example if this file is executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const example = new NetworkExample();
|
||||
example.runExample()
|
||||
.then(() => {
|
||||
console.log(chalk.blue('\n👋 Example finished. Goodbye!'));
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(chalk.red.bold(`\n💥 Example crashed: ${error.message}`));
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { NetworkExample };
|
||||
74
index.js
Archivo normal
74
index.js
Archivo normal
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import chalk from 'chalk';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length === 0 || args.includes('--help')) {
|
||||
console.log(`
|
||||
${chalk.blue.bold('🔗 Ring Network - Two Ring Topology with WebRTC')}
|
||||
|
||||
A decentralized network implementation using WebRTC for peer-to-peer communication
|
||||
in a double ring topology with optional Oracle nodes.
|
||||
|
||||
${chalk.green('Usage:')}
|
||||
npm start - Show this help
|
||||
npm run start:node [opts] - Start a regular network node
|
||||
npm run start:oracle [opts] - Start an Oracle node
|
||||
node node.js [opts] - Start a regular network node directly
|
||||
node oracle.js [opts] - Start an Oracle node directly
|
||||
|
||||
${chalk.yellow('Options:')}
|
||||
--port <port> Port to listen on (default: random)
|
||||
--id <id> Node ID (default: auto-generated)
|
||||
--bootstrap <addr> Bootstrap node address (host:port)
|
||||
--position <pos> Initial ring position (default: 0)
|
||||
|
||||
${chalk.cyan('Examples:')}
|
||||
# Start first node (bootstrap)
|
||||
npm run start:oracle -- --port 8080
|
||||
|
||||
# Start second node connecting to first
|
||||
npm run start:node -- --port 8081 --bootstrap localhost:8080
|
||||
|
||||
# Start third node (oracle) connecting to network
|
||||
npm run start:oracle -- --port 8082 --bootstrap localhost:8080
|
||||
|
||||
${chalk.magenta('Network Features:')}
|
||||
✅ Double ring topology (inner and outer rings)
|
||||
✅ WebRTC peer-to-peer connections
|
||||
✅ Automatic peer discovery and connection
|
||||
✅ Message routing through rings
|
||||
✅ Oracle nodes with enhanced capabilities
|
||||
✅ Distributed data storage (Oracle)
|
||||
✅ Consensus mechanisms (Oracle)
|
||||
✅ Network health monitoring (Oracle)
|
||||
✅ Advanced routing strategies (Oracle)
|
||||
|
||||
${chalk.blue('Oracle Services:')}
|
||||
🔮 network-analysis - Analyze network topology and health
|
||||
🔮 data-storage - Distributed data storage with replication
|
||||
🔮 consensus - Consensus mechanisms for decisions
|
||||
🔮 routing - Advanced routing strategies
|
||||
🔮 health-check - Network health monitoring
|
||||
🔮 network-metrics - Collect and provide metrics
|
||||
|
||||
${chalk.red('Quick Start:')}
|
||||
1. Install dependencies: npm install
|
||||
2. Start first Oracle: npm run start:oracle -- --port 8080
|
||||
3. Start regular node: npm run start:node -- --port 8081 --bootstrap localhost:8080
|
||||
4. Start more nodes by repeating step 3 with different ports
|
||||
|
||||
For more information, see the README.md file.
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// If specific arguments are passed, delegate to appropriate script
|
||||
if (args.includes('--oracle')) {
|
||||
console.log(chalk.yellow('🔮 Starting Oracle Node...'));
|
||||
import('./oracle.js');
|
||||
} else {
|
||||
console.log(chalk.blue('🔗 Starting Regular Node...'));
|
||||
import('./node.js');
|
||||
}
|
||||
165
node.js
Archivo normal
165
node.js
Archivo normal
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { RingNode } from './src/ring-node.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const options = {};
|
||||
|
||||
// Parse command line arguments
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case '--port':
|
||||
options.port = parseInt(args[++i]);
|
||||
break;
|
||||
case '--id':
|
||||
options.id = args[++i];
|
||||
break;
|
||||
case '--bootstrap':
|
||||
options.bootstrap = args[++i];
|
||||
break;
|
||||
case '--position':
|
||||
options.ringPosition = parseInt(args[++i]);
|
||||
break;
|
||||
case '--help':
|
||||
console.log(`
|
||||
${chalk.blue('Ring Network Node')}
|
||||
|
||||
Usage: node node.js [options]
|
||||
|
||||
Options:
|
||||
--port <port> Port to listen on (default: random)
|
||||
--id <id> Node ID (default: auto-generated)
|
||||
--bootstrap <addr> Bootstrap node address (host:port)
|
||||
--position <pos> Initial ring position (default: 0)
|
||||
--help Show this help message
|
||||
|
||||
Examples:
|
||||
node node.js --port 8080
|
||||
node node.js --port 8081 --bootstrap localhost:8080
|
||||
node node.js --id mynode --port 8082 --bootstrap localhost:8080
|
||||
`);
|
||||
process.exit(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green('🚀 Starting Ring Network Node...'));
|
||||
|
||||
const node = new RingNode(options);
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log(chalk.yellow('\n🛑 Received shutdown signal...'));
|
||||
await node.destroy();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log(chalk.yellow('\n🛑 Received termination signal...'));
|
||||
await node.destroy();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Connect to bootstrap node if specified
|
||||
if (options.bootstrap) {
|
||||
console.log(chalk.cyan(`🔗 Connecting to bootstrap node: ${options.bootstrap}`));
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const success = await node.joinRing(options.bootstrap);
|
||||
if (success) {
|
||||
console.log(chalk.green('✅ Successfully joined the ring network!'));
|
||||
} else {
|
||||
console.log(chalk.red('❌ Failed to join the ring network'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Error joining network:'), error.message);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Set up event handlers for demonstration
|
||||
node.on('peerConnected', (peerId) => {
|
||||
console.log(chalk.blue(`👋 New peer connected: ${peerId.substring(0, 8)}`));
|
||||
});
|
||||
|
||||
node.on('peerDisconnected', (peerId) => {
|
||||
console.log(chalk.red(`👋 Peer disconnected: ${peerId.substring(0, 8)}`));
|
||||
});
|
||||
|
||||
node.on('ringMessage', ({ from, payload }) => {
|
||||
console.log(chalk.magenta(`📨 Ring message from ${from.substring(0, 8)}: ${JSON.stringify(payload)}`));
|
||||
});
|
||||
|
||||
node.on('networkUpdate', ({ from, payload }) => {
|
||||
console.log(chalk.cyan(`🔄 Network update from ${from.substring(0, 8)}`));
|
||||
});
|
||||
|
||||
// Interactive commands
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('readable', () => {
|
||||
const chunk = process.stdin.read();
|
||||
if (chunk !== null) {
|
||||
const command = chunk.trim();
|
||||
handleCommand(command);
|
||||
}
|
||||
});
|
||||
|
||||
function handleCommand(command) {
|
||||
const [cmd, ...args] = command.split(' ');
|
||||
|
||||
switch (cmd) {
|
||||
case 'send':
|
||||
if (args.length > 0) {
|
||||
const message = args.join(' ');
|
||||
node.sendRingMessage({ type: 'chat', content: message });
|
||||
console.log(chalk.green(`📤 Sent: ${message}`));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
const info = node.getNetworkInfo();
|
||||
console.log(chalk.blue('\n📊 Network Info:'));
|
||||
console.log(JSON.stringify(info, null, 2));
|
||||
break;
|
||||
|
||||
case 'peers':
|
||||
const peers = node.webrtc.getConnectedPeers();
|
||||
console.log(chalk.blue(`\n👥 Connected Peers (${peers.length}):`));
|
||||
peers.forEach(peer => {
|
||||
console.log(` - ${peer.substring(0, 8)}...`);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'help':
|
||||
console.log(chalk.blue(`
|
||||
📚 Available Commands:
|
||||
send <message> - Send a message through the ring
|
||||
info - Show network information
|
||||
peers - List connected peers
|
||||
help - Show this help
|
||||
quit - Exit the node
|
||||
`));
|
||||
break;
|
||||
|
||||
case 'quit':
|
||||
case 'exit':
|
||||
console.log(chalk.yellow('👋 Goodbye!'));
|
||||
process.exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (command.length > 0) {
|
||||
console.log(chalk.red(`❓ Unknown command: ${cmd}. Type 'help' for available commands.`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`
|
||||
🔗 Ring Network Node Started!
|
||||
Node ID: ${node.id.substring(0, 8)}...
|
||||
Port: ${node.port}
|
||||
|
||||
Type 'help' for available commands.
|
||||
`));
|
||||
258
oracle.js
Archivo normal
258
oracle.js
Archivo normal
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { OracleNode } from './src/oracle-node.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const options = {};
|
||||
|
||||
// Parse command line arguments
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case '--port':
|
||||
options.port = parseInt(args[++i]);
|
||||
break;
|
||||
case '--id':
|
||||
options.id = args[++i];
|
||||
break;
|
||||
case '--bootstrap':
|
||||
options.bootstrap = args[++i];
|
||||
break;
|
||||
case '--position':
|
||||
options.ringPosition = parseInt(args[++i]);
|
||||
break;
|
||||
case '--help':
|
||||
console.log(`
|
||||
${chalk.yellow('Ring Network Oracle Node')}
|
||||
|
||||
Usage: node oracle.js [options]
|
||||
|
||||
Options:
|
||||
--port <port> Port to listen on (default: random)
|
||||
--id <id> Node ID (default: auto-generated)
|
||||
--bootstrap <addr> Bootstrap node address (host:port)
|
||||
--position <pos> Initial ring position (default: 0)
|
||||
--help Show this help message
|
||||
|
||||
Examples:
|
||||
node oracle.js --port 8080
|
||||
node oracle.js --port 8081 --bootstrap localhost:8080
|
||||
node oracle.js --id oracle1 --port 8082 --bootstrap localhost:8080
|
||||
|
||||
Oracle Services:
|
||||
- network-analysis: Analyze network topology and health
|
||||
- data-storage: Distributed data storage with replication
|
||||
- consensus: Consensus mechanisms for network decisions
|
||||
- routing: Advanced routing strategies
|
||||
- health-check: Network health monitoring
|
||||
- network-metrics: Collect and provide network metrics
|
||||
`);
|
||||
process.exit(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.yellow('🚀 Starting Ring Network Oracle Node...'));
|
||||
|
||||
const oracle = new OracleNode(options);
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log(chalk.yellow('\n🛑 Received shutdown signal...'));
|
||||
await oracle.destroy();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log(chalk.yellow('\n🛑 Received termination signal...'));
|
||||
await oracle.destroy();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Connect to bootstrap node if specified
|
||||
if (options.bootstrap) {
|
||||
console.log(chalk.cyan(`🔗 Connecting to bootstrap node: ${options.bootstrap}`));
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const success = await oracle.joinRing(options.bootstrap);
|
||||
if (success) {
|
||||
console.log(chalk.green('✅ Successfully joined the ring network as Oracle!'));
|
||||
} else {
|
||||
console.log(chalk.red('❌ Failed to join the ring network'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Error joining network:'), error.message);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Set up event handlers
|
||||
oracle.on('peerConnected', (peerId) => {
|
||||
console.log(chalk.blue(`👋 New peer connected: ${peerId.substring(0, 8)}`));
|
||||
});
|
||||
|
||||
oracle.on('peerDisconnected', (peerId) => {
|
||||
console.log(chalk.red(`👋 Peer disconnected: ${peerId.substring(0, 8)}`));
|
||||
});
|
||||
|
||||
oracle.on('ringMessage', ({ from, payload }) => {
|
||||
console.log(chalk.magenta(`📨 Ring message from ${from.substring(0, 8)}: ${JSON.stringify(payload)}`));
|
||||
});
|
||||
|
||||
oracle.on('oracleResponse', ({ from, payload }) => {
|
||||
console.log(chalk.cyan(`🔮 Oracle response from ${from.substring(0, 8)}: ${JSON.stringify(payload)}`));
|
||||
});
|
||||
|
||||
// Interactive commands
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('readable', () => {
|
||||
const chunk = process.stdin.read();
|
||||
if (chunk !== null) {
|
||||
const command = chunk.trim();
|
||||
handleCommand(command);
|
||||
}
|
||||
});
|
||||
|
||||
async function handleCommand(command) {
|
||||
const [cmd, ...args] = command.split(' ');
|
||||
|
||||
switch (cmd) {
|
||||
case 'send':
|
||||
if (args.length > 0) {
|
||||
const message = args.join(' ');
|
||||
oracle.sendRingMessage({ type: 'chat', content: message });
|
||||
console.log(chalk.green(`📤 Sent: ${message}`));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
const info = oracle.getOracleInfo();
|
||||
console.log(chalk.yellow('\n🔮 Oracle Info:'));
|
||||
console.log(JSON.stringify(info, null, 2));
|
||||
break;
|
||||
|
||||
case 'peers':
|
||||
const peers = oracle.webrtc.getConnectedPeers();
|
||||
console.log(chalk.blue(`\n👥 Connected Peers (${peers.length}):`));
|
||||
peers.forEach(peer => {
|
||||
console.log(` - ${peer.substring(0, 8)}...`);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'health':
|
||||
const health = oracle.performHealthCheck();
|
||||
console.log(chalk.green('\n🏥 Health Check:'));
|
||||
console.log(JSON.stringify(health, null, 2));
|
||||
break;
|
||||
|
||||
case 'metrics':
|
||||
const metrics = oracle.getNetworkMetrics();
|
||||
console.log(chalk.cyan('\n📊 Network Metrics:'));
|
||||
console.log(JSON.stringify(metrics, null, 2));
|
||||
break;
|
||||
|
||||
case 'analyze':
|
||||
const analysis = oracle.analyzeNetwork();
|
||||
console.log(chalk.magenta('\n🔍 Network Analysis:'));
|
||||
console.log(JSON.stringify(analysis, null, 2));
|
||||
break;
|
||||
|
||||
case 'store':
|
||||
if (args.length >= 2) {
|
||||
const [key, ...valueParts] = args;
|
||||
const value = valueParts.join(' ');
|
||||
const result = oracle.handleDataStorage({
|
||||
operation: 'set',
|
||||
key,
|
||||
value
|
||||
});
|
||||
console.log(chalk.green(`💾 Stored: ${key} = ${value}`));
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
console.log(chalk.red('Usage: store <key> <value>'));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'get':
|
||||
if (args.length >= 1) {
|
||||
const key = args[0];
|
||||
const result = oracle.handleDataStorage({
|
||||
operation: 'get',
|
||||
key
|
||||
});
|
||||
console.log(chalk.blue(`📥 Retrieved: ${key}`));
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
console.log(chalk.red('Usage: get <key>'));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'propose':
|
||||
if (args.length >= 1) {
|
||||
const proposal = args.join(' ');
|
||||
const proposalId = `proposal-${Date.now()}`;
|
||||
const result = oracle.handleConsensus({
|
||||
proposalId,
|
||||
proposal
|
||||
});
|
||||
console.log(chalk.yellow(`📋 Created proposal: ${proposalId}`));
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
console.log(chalk.red('Usage: propose <proposal text>'));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'vote':
|
||||
if (args.length >= 2) {
|
||||
const [proposalId, vote] = args;
|
||||
const result = oracle.handleConsensus({
|
||||
proposalId,
|
||||
vote
|
||||
});
|
||||
console.log(chalk.yellow(`🗳️ Voted ${vote} on ${proposalId}`));
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
console.log(chalk.red('Usage: vote <proposalId> <yes|no>'));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'help':
|
||||
console.log(chalk.yellow(`
|
||||
🔮 Oracle Commands:
|
||||
send <msg> - Send a message through the ring
|
||||
info - Show oracle information
|
||||
peers - List connected peers
|
||||
health - Perform health check
|
||||
metrics - Show network metrics
|
||||
analyze - Analyze network topology
|
||||
store <key> <value> - Store data in distributed storage
|
||||
get <key> - Retrieve data from storage
|
||||
propose <text> - Create a consensus proposal
|
||||
vote <id> <yes|no> - Vote on a proposal
|
||||
help - Show this help
|
||||
quit - Exit the oracle
|
||||
`));
|
||||
break;
|
||||
|
||||
case 'quit':
|
||||
case 'exit':
|
||||
console.log(chalk.yellow('👋 Oracle shutting down...'));
|
||||
process.exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (command.length > 0) {
|
||||
console.log(chalk.red(`❓ Unknown command: ${cmd}. Type 'help' for available commands.`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.yellow(`
|
||||
🔮 Ring Network Oracle Node Started!
|
||||
Oracle ID: ${oracle.id.substring(0, 8)}...
|
||||
Port: ${oracle.port}
|
||||
Services: ${Array.from(oracle.oracleServices.keys()).join(', ')}
|
||||
|
||||
Type 'help' for available commands.
|
||||
`));
|
||||
27
package.json
Archivo normal
27
package.json
Archivo normal
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "ringnet",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"author": "ale",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"start:oracle": "node oracle.js",
|
||||
"start:node": "node node.js",
|
||||
"demo": "node demo.js",
|
||||
"example": "node examples/basic-example.js",
|
||||
"test": "node test/test-network.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"simple-peer": "^9.11.1",
|
||||
"ws": "^8.14.2",
|
||||
"express": "^4.18.2",
|
||||
"uuid": "^9.0.1",
|
||||
"chalk": "^5.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2"
|
||||
}
|
||||
}
|
||||
584
src/oracle-node.js
Archivo normal
584
src/oracle-node.js
Archivo normal
@@ -0,0 +1,584 @@
|
||||
import { RingNode } from './ring-node.js';
|
||||
import chalk from 'chalk';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export class OracleNode extends RingNode {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
...options,
|
||||
isOracle: true
|
||||
});
|
||||
|
||||
this.oracleServices = new Map();
|
||||
this.dataStore = new Map();
|
||||
this.consensusData = new Map();
|
||||
this.networkMetrics = {
|
||||
startTime: Date.now(),
|
||||
messagesProcessed: 0,
|
||||
queriesAnswered: 0,
|
||||
networkEvents: []
|
||||
};
|
||||
|
||||
this.initializeOracleServices();
|
||||
console.log(chalk.yellow(`🔮✨ Oracle Node ${this.id.substring(0, 8)} initialized with enhanced capabilities`));
|
||||
}
|
||||
|
||||
initializeOracleServices() {
|
||||
// Register oracle services
|
||||
this.oracleServices.set('network-analysis', this.analyzeNetwork.bind(this));
|
||||
this.oracleServices.set('data-storage', this.handleDataStorage.bind(this));
|
||||
this.oracleServices.set('consensus', this.handleConsensus.bind(this));
|
||||
this.oracleServices.set('routing', this.handleRouting.bind(this));
|
||||
this.oracleServices.set('health-check', this.performHealthCheck.bind(this));
|
||||
this.oracleServices.set('network-metrics', this.getNetworkMetrics.bind(this));
|
||||
|
||||
// Start periodic tasks
|
||||
this.startPeriodicTasks();
|
||||
}
|
||||
|
||||
startPeriodicTasks() {
|
||||
// Network health monitoring
|
||||
setInterval(() => {
|
||||
this.monitorNetworkHealth();
|
||||
}, 30000); // Every 30 seconds
|
||||
|
||||
// Metrics collection
|
||||
setInterval(() => {
|
||||
this.collectMetrics();
|
||||
}, 10000); // Every 10 seconds
|
||||
|
||||
// Cleanup old data
|
||||
setInterval(() => {
|
||||
this.cleanupOldData();
|
||||
}, 300000); // Every 5 minutes
|
||||
}
|
||||
|
||||
processOracleQuery(payload) {
|
||||
const { query, data, service } = payload;
|
||||
this.networkMetrics.queriesAnswered++;
|
||||
|
||||
console.log(chalk.cyan(`🔮 Processing oracle query: ${service || query}`));
|
||||
|
||||
if (service && this.oracleServices.has(service)) {
|
||||
return this.oracleServices.get(service)(data);
|
||||
}
|
||||
|
||||
// Fallback to parent class handling
|
||||
return super.processOracleQuery(payload);
|
||||
}
|
||||
|
||||
analyzeNetwork(data) {
|
||||
const connectedPeers = this.webrtc.getConnectedPeers();
|
||||
const analysis = {
|
||||
networkSize: this.knownNodes.size + 1, // +1 for this node
|
||||
connectedPeers: connectedPeers.length,
|
||||
oracleNodes: this.oracleNodes.size,
|
||||
ringTopology: {
|
||||
inner: {
|
||||
hasLeft: !!this.rings.inner.left,
|
||||
hasRight: !!this.rings.inner.right,
|
||||
complete: !!this.rings.inner.left && !!this.rings.inner.right
|
||||
},
|
||||
outer: {
|
||||
hasLeft: !!this.rings.outer.left,
|
||||
hasRight: !!this.rings.outer.right,
|
||||
complete: !!this.rings.outer.left && !!this.rings.outer.right
|
||||
}
|
||||
},
|
||||
networkHealth: this.calculateNetworkHealth(),
|
||||
recommendations: this.generateNetworkRecommendations()
|
||||
};
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
calculateNetworkHealth() {
|
||||
const connectedPeers = this.webrtc.getConnectedPeers().length;
|
||||
const expectedConnections = 4; // 2 for each ring (left and right)
|
||||
const connectionHealth = Math.min(connectedPeers / expectedConnections, 1.0);
|
||||
|
||||
const ringHealth = {
|
||||
inner: (this.rings.inner.left ? 0.5 : 0) + (this.rings.inner.right ? 0.5 : 0),
|
||||
outer: (this.rings.outer.left ? 0.5 : 0) + (this.rings.outer.right ? 0.5 : 0)
|
||||
};
|
||||
|
||||
const overallHealth = (connectionHealth + ringHealth.inner + ringHealth.outer) / 3;
|
||||
|
||||
return {
|
||||
overall: Math.round(overallHealth * 100),
|
||||
connections: Math.round(connectionHealth * 100),
|
||||
innerRing: Math.round(ringHealth.inner * 100),
|
||||
outerRing: Math.round(ringHealth.outer * 100)
|
||||
};
|
||||
}
|
||||
|
||||
generateNetworkRecommendations() {
|
||||
const recommendations = [];
|
||||
const health = this.calculateNetworkHealth();
|
||||
|
||||
if (health.overall < 70) {
|
||||
recommendations.push('Network health is low - consider adding more nodes');
|
||||
}
|
||||
|
||||
if (!this.rings.inner.left || !this.rings.inner.right) {
|
||||
recommendations.push('Inner ring is incomplete - seeking connections');
|
||||
}
|
||||
|
||||
if (!this.rings.outer.left || !this.rings.outer.right) {
|
||||
recommendations.push('Outer ring is incomplete - seeking connections');
|
||||
}
|
||||
|
||||
if (this.oracleNodes.size < 2) {
|
||||
recommendations.push('Consider adding more oracle nodes for redundancy');
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
handleDataStorage(data) {
|
||||
const { operation, key, value, ttl } = data;
|
||||
|
||||
switch (operation) {
|
||||
case 'set':
|
||||
const entry = {
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
ttl: ttl || 3600000, // Default 1 hour TTL
|
||||
nodeId: this.id
|
||||
};
|
||||
this.dataStore.set(key, entry);
|
||||
|
||||
// Replicate to other oracles for redundancy
|
||||
this.replicateData(key, entry);
|
||||
|
||||
return { success: true, key, timestamp: entry.timestamp };
|
||||
|
||||
case 'get':
|
||||
const storedEntry = this.dataStore.get(key);
|
||||
if (!storedEntry) {
|
||||
return { success: false, error: 'Key not found' };
|
||||
}
|
||||
|
||||
// Check TTL
|
||||
if (Date.now() - storedEntry.timestamp > storedEntry.ttl) {
|
||||
this.dataStore.delete(key);
|
||||
return { success: false, error: 'Key expired' };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
key,
|
||||
value: storedEntry.value,
|
||||
timestamp: storedEntry.timestamp
|
||||
};
|
||||
|
||||
case 'delete':
|
||||
const deleted = this.dataStore.delete(key);
|
||||
if (deleted) {
|
||||
this.replicateDataDeletion(key);
|
||||
}
|
||||
return { success: deleted };
|
||||
|
||||
case 'list':
|
||||
const keys = Array.from(this.dataStore.keys());
|
||||
return { success: true, keys, count: keys.length };
|
||||
|
||||
default:
|
||||
return { success: false, error: 'Unknown operation' };
|
||||
}
|
||||
}
|
||||
|
||||
replicateData(key, entry) {
|
||||
const replicationMessage = {
|
||||
id: uuidv4(),
|
||||
type: 'data-replication',
|
||||
payload: {
|
||||
operation: 'set',
|
||||
key,
|
||||
entry
|
||||
}
|
||||
};
|
||||
|
||||
// Send to other oracle nodes
|
||||
this.oracleNodes.forEach(oracleId => {
|
||||
if (oracleId !== this.id) {
|
||||
this.webrtc.sendMessage(oracleId, replicationMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
replicateDataDeletion(key) {
|
||||
const replicationMessage = {
|
||||
id: uuidv4(),
|
||||
type: 'data-replication',
|
||||
payload: {
|
||||
operation: 'delete',
|
||||
key
|
||||
}
|
||||
};
|
||||
|
||||
this.oracleNodes.forEach(oracleId => {
|
||||
if (oracleId !== this.id) {
|
||||
this.webrtc.sendMessage(oracleId, replicationMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleConsensus(data) {
|
||||
const { proposalId, proposal, vote } = data;
|
||||
|
||||
if (proposal && !vote) {
|
||||
// New proposal
|
||||
this.consensusData.set(proposalId, {
|
||||
proposal,
|
||||
votes: new Map(),
|
||||
timestamp: Date.now(),
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
// Broadcast proposal to other oracles
|
||||
this.broadcastConsensusProposal(proposalId, proposal);
|
||||
|
||||
return { success: true, proposalId, status: 'proposal_created' };
|
||||
}
|
||||
|
||||
if (vote && proposalId) {
|
||||
// Vote on existing proposal
|
||||
const consensusItem = this.consensusData.get(proposalId);
|
||||
if (!consensusItem) {
|
||||
return { success: false, error: 'Proposal not found' };
|
||||
}
|
||||
|
||||
consensusItem.votes.set(this.id, vote);
|
||||
|
||||
// Broadcast vote
|
||||
this.broadcastConsensusVote(proposalId, vote);
|
||||
|
||||
// Check if consensus reached
|
||||
const result = this.checkConsensus(proposalId);
|
||||
return { success: true, proposalId, vote, consensus: result };
|
||||
}
|
||||
|
||||
return { success: false, error: 'Invalid consensus request' };
|
||||
}
|
||||
|
||||
broadcastConsensusProposal(proposalId, proposal) {
|
||||
const message = {
|
||||
id: uuidv4(),
|
||||
type: 'consensus-proposal',
|
||||
payload: { proposalId, proposal }
|
||||
};
|
||||
|
||||
this.oracleNodes.forEach(oracleId => {
|
||||
if (oracleId !== this.id) {
|
||||
this.webrtc.sendMessage(oracleId, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
broadcastConsensusVote(proposalId, vote) {
|
||||
const message = {
|
||||
id: uuidv4(),
|
||||
type: 'consensus-vote',
|
||||
payload: { proposalId, vote, voterId: this.id }
|
||||
};
|
||||
|
||||
this.oracleNodes.forEach(oracleId => {
|
||||
if (oracleId !== this.id) {
|
||||
this.webrtc.sendMessage(oracleId, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkConsensus(proposalId) {
|
||||
const consensusItem = this.consensusData.get(proposalId);
|
||||
if (!consensusItem) return null;
|
||||
|
||||
const totalOracles = this.oracleNodes.size + 1; // +1 for this node
|
||||
const votesNeeded = Math.floor(totalOracles * 0.6) + 1; // 60% majority
|
||||
|
||||
const votes = Array.from(consensusItem.votes.values());
|
||||
const yesVotes = votes.filter(vote => vote === 'yes').length;
|
||||
const noVotes = votes.filter(vote => vote === 'no').length;
|
||||
|
||||
if (yesVotes >= votesNeeded) {
|
||||
consensusItem.status = 'approved';
|
||||
return { status: 'approved', votes: { yes: yesVotes, no: noVotes, total: votes.length } };
|
||||
} else if (noVotes >= votesNeeded) {
|
||||
consensusItem.status = 'rejected';
|
||||
return { status: 'rejected', votes: { yes: yesVotes, no: noVotes, total: votes.length } };
|
||||
}
|
||||
|
||||
return { status: 'pending', votes: { yes: yesVotes, no: noVotes, total: votes.length } };
|
||||
}
|
||||
|
||||
handleRouting(data) {
|
||||
const { destination, message, strategy } = data;
|
||||
|
||||
switch (strategy) {
|
||||
case 'shortest-path':
|
||||
return this.findShortestPath(destination);
|
||||
case 'ring-flood':
|
||||
return this.performRingFlood(message);
|
||||
case 'oracle-route':
|
||||
return this.routeThroughOracles(destination, message);
|
||||
default:
|
||||
return { success: false, error: 'Unknown routing strategy' };
|
||||
}
|
||||
}
|
||||
|
||||
findShortestPath(destination) {
|
||||
// Simple shortest path - in a real implementation, this would use
|
||||
// algorithms like Dijkstra's or A*
|
||||
const connectedPeers = this.webrtc.getConnectedPeers();
|
||||
|
||||
if (connectedPeers.includes(destination)) {
|
||||
return {
|
||||
success: true,
|
||||
path: [this.id, destination],
|
||||
hops: 1
|
||||
};
|
||||
}
|
||||
|
||||
// Check if any connected peer can reach destination
|
||||
// This is a simplified version - real implementation would maintain
|
||||
// routing tables and use network topology information
|
||||
return {
|
||||
success: false,
|
||||
error: 'Destination not reachable',
|
||||
knownPeers: connectedPeers
|
||||
};
|
||||
}
|
||||
|
||||
performRingFlood(message) {
|
||||
const flooded = this.sendRingMessage(message, 'both');
|
||||
return {
|
||||
success: flooded,
|
||||
strategy: 'ring-flood',
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
routeThroughOracles(destination, message) {
|
||||
const oracleList = Array.from(this.oracleNodes);
|
||||
let routedCount = 0;
|
||||
|
||||
oracleList.forEach(oracleId => {
|
||||
if (oracleId !== this.id) {
|
||||
const routingMessage = {
|
||||
id: uuidv4(),
|
||||
type: 'oracle-routing',
|
||||
payload: { destination, message }
|
||||
};
|
||||
|
||||
if (this.webrtc.sendMessage(oracleId, routingMessage)) {
|
||||
routedCount++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: routedCount > 0,
|
||||
oraclesContacted: routedCount,
|
||||
strategy: 'oracle-route'
|
||||
};
|
||||
}
|
||||
|
||||
performHealthCheck(data) {
|
||||
const checks = {
|
||||
webrtc: this.webrtc.getConnectedPeers().length > 0,
|
||||
rings: {
|
||||
inner: !!this.rings.inner.left || !!this.rings.inner.right,
|
||||
outer: !!this.rings.outer.left || !!this.rings.outer.right
|
||||
},
|
||||
dataStore: this.dataStore.size >= 0,
|
||||
memory: process.memoryUsage(),
|
||||
uptime: Date.now() - this.networkMetrics.startTime,
|
||||
services: Array.from(this.oracleServices.keys())
|
||||
};
|
||||
|
||||
const isHealthy = checks.webrtc && (checks.rings.inner || checks.rings.outer);
|
||||
|
||||
return {
|
||||
healthy: isHealthy,
|
||||
checks,
|
||||
timestamp: Date.now(),
|
||||
nodeId: this.id
|
||||
};
|
||||
}
|
||||
|
||||
getNetworkMetrics() {
|
||||
return {
|
||||
...this.networkMetrics,
|
||||
dataStoreSize: this.dataStore.size,
|
||||
consensusItems: this.consensusData.size,
|
||||
connectedPeers: this.webrtc.getConnectedPeers().length,
|
||||
knownNodes: this.knownNodes.size,
|
||||
oracleNodes: this.oracleNodes.size,
|
||||
memoryUsage: process.memoryUsage(),
|
||||
uptime: Date.now() - this.networkMetrics.startTime
|
||||
};
|
||||
}
|
||||
|
||||
monitorNetworkHealth() {
|
||||
const health = this.performHealthCheck();
|
||||
this.networkMetrics.networkEvents.push({
|
||||
type: 'health-check',
|
||||
timestamp: Date.now(),
|
||||
data: health
|
||||
});
|
||||
|
||||
if (!health.healthy) {
|
||||
console.log(chalk.red(`⚠️ Network health issue detected`));
|
||||
this.handleNetworkHealthIssue(health);
|
||||
}
|
||||
}
|
||||
|
||||
handleNetworkHealthIssue(health) {
|
||||
// Attempt to reconnect to peers
|
||||
if (!health.checks.webrtc) {
|
||||
console.log(chalk.yellow('🔄 Attempting to reconnect to network...'));
|
||||
// Implement reconnection logic
|
||||
}
|
||||
|
||||
// Notify other oracles of health issues
|
||||
const alertMessage = {
|
||||
id: uuidv4(),
|
||||
type: 'health-alert',
|
||||
payload: {
|
||||
nodeId: this.id,
|
||||
healthStatus: health,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
this.oracleNodes.forEach(oracleId => {
|
||||
if (oracleId !== this.id) {
|
||||
this.webrtc.sendMessage(oracleId, alertMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
collectMetrics() {
|
||||
this.networkMetrics.messagesProcessed++;
|
||||
|
||||
// Keep only recent events (last 1000)
|
||||
if (this.networkMetrics.networkEvents.length > 1000) {
|
||||
this.networkMetrics.networkEvents = this.networkMetrics.networkEvents.slice(-1000);
|
||||
}
|
||||
}
|
||||
|
||||
cleanupOldData() {
|
||||
const now = Date.now();
|
||||
|
||||
// Clean expired data store entries
|
||||
for (const [key, entry] of this.dataStore) {
|
||||
if (now - entry.timestamp > entry.ttl) {
|
||||
this.dataStore.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean old consensus data (older than 24 hours)
|
||||
for (const [proposalId, item] of this.consensusData) {
|
||||
if (now - item.timestamp > 86400000) { // 24 hours
|
||||
this.consensusData.delete(proposalId);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`🧹 Cleaned up old data. DataStore: ${this.dataStore.size}, Consensus: ${this.consensusData.size}`));
|
||||
}
|
||||
|
||||
// Handle additional message types specific to Oracle
|
||||
handleMessage(from, message) {
|
||||
const { type, payload } = message;
|
||||
|
||||
switch (type) {
|
||||
case 'data-replication':
|
||||
this.handleDataReplication(from, payload);
|
||||
break;
|
||||
case 'consensus-proposal':
|
||||
this.handleConsensusProposal(from, payload);
|
||||
break;
|
||||
case 'consensus-vote':
|
||||
this.handleConsensusVote(from, payload);
|
||||
break;
|
||||
case 'health-alert':
|
||||
this.handleHealthAlert(from, payload);
|
||||
break;
|
||||
case 'oracle-routing':
|
||||
this.handleOracleRouting(from, payload);
|
||||
break;
|
||||
default:
|
||||
super.handleMessage(from, message);
|
||||
}
|
||||
}
|
||||
|
||||
handleDataReplication(from, payload) {
|
||||
const { operation, key, entry } = payload;
|
||||
|
||||
if (operation === 'set') {
|
||||
this.dataStore.set(key, entry);
|
||||
console.log(chalk.blue(`📥 Replicated data: ${key}`));
|
||||
} else if (operation === 'delete') {
|
||||
this.dataStore.delete(key);
|
||||
console.log(chalk.blue(`🗑️ Deleted replicated data: ${key}`));
|
||||
}
|
||||
}
|
||||
|
||||
handleConsensusProposal(from, payload) {
|
||||
const { proposalId, proposal } = payload;
|
||||
|
||||
this.consensusData.set(proposalId, {
|
||||
proposal,
|
||||
votes: new Map(),
|
||||
timestamp: Date.now(),
|
||||
status: 'active',
|
||||
proposer: from
|
||||
});
|
||||
|
||||
console.log(chalk.cyan(`📋 New consensus proposal ${proposalId} from ${from.substring(0, 8)}`));
|
||||
}
|
||||
|
||||
handleConsensusVote(from, payload) {
|
||||
const { proposalId, vote, voterId } = payload;
|
||||
const consensusItem = this.consensusData.get(proposalId);
|
||||
|
||||
if (consensusItem) {
|
||||
consensusItem.votes.set(voterId, vote);
|
||||
const result = this.checkConsensus(proposalId);
|
||||
|
||||
console.log(chalk.cyan(`🗳️ Vote received for ${proposalId}: ${vote} (${result.status})`));
|
||||
}
|
||||
}
|
||||
|
||||
handleHealthAlert(from, payload) {
|
||||
console.log(chalk.red(`🚨 Health alert from ${from.substring(0, 8)}`));
|
||||
this.networkMetrics.networkEvents.push({
|
||||
type: 'health-alert',
|
||||
timestamp: Date.now(),
|
||||
from,
|
||||
data: payload
|
||||
});
|
||||
}
|
||||
|
||||
handleOracleRouting(from, payload) {
|
||||
const { destination, message } = payload;
|
||||
|
||||
// Attempt to route the message to the destination
|
||||
if (this.webrtc.sendMessage(destination, message)) {
|
||||
console.log(chalk.green(`📬 Routed message to ${destination.substring(0, 8)}`));
|
||||
} else {
|
||||
console.log(chalk.yellow(`📪 Unable to route message to ${destination.substring(0, 8)}`));
|
||||
}
|
||||
}
|
||||
|
||||
getOracleInfo() {
|
||||
return {
|
||||
...this.getNetworkInfo(),
|
||||
oracleServices: Array.from(this.oracleServices.keys()),
|
||||
dataStoreSize: this.dataStore.size,
|
||||
consensusItems: this.consensusData.size,
|
||||
metrics: this.getNetworkMetrics()
|
||||
};
|
||||
}
|
||||
}
|
||||
508
src/ring-node.js
Archivo normal
508
src/ring-node.js
Archivo normal
@@ -0,0 +1,508 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { WebRTCManager } from './webrtc-manager.js';
|
||||
import WebSocket, { WebSocketServer } from 'ws';
|
||||
import chalk from 'chalk';
|
||||
|
||||
export class RingNode extends EventEmitter {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.id = options.id || uuidv4();
|
||||
this.port = options.port || this.getRandomPort();
|
||||
this.ringPosition = options.ringPosition || 0;
|
||||
this.isOracle = options.isOracle || false;
|
||||
|
||||
// Two rings: inner and outer
|
||||
this.rings = {
|
||||
inner: {
|
||||
left: null, // Previous node in inner ring
|
||||
right: null, // Next node in inner ring
|
||||
},
|
||||
outer: {
|
||||
left: null, // Previous node in outer ring
|
||||
right: null, // Next node in outer ring
|
||||
}
|
||||
};
|
||||
|
||||
this.webrtc = new WebRTCManager(this.id);
|
||||
this.discoveryServer = null;
|
||||
this.knownNodes = new Map();
|
||||
this.messageHistory = new Set();
|
||||
this.oracleNodes = new Set();
|
||||
|
||||
this.setupWebRTCHandlers();
|
||||
this.setupDiscoveryServer();
|
||||
|
||||
console.log(chalk.green(`🔗 Ring Node ${this.id.substring(0, 8)} initialized on port ${this.port}`));
|
||||
if (this.isOracle) {
|
||||
console.log(chalk.yellow(`🔮 Oracle mode enabled`));
|
||||
}
|
||||
}
|
||||
|
||||
getRandomPort() {
|
||||
return Math.floor(Math.random() * (65535 - 49152) + 49152);
|
||||
}
|
||||
|
||||
setupWebRTCHandlers() {
|
||||
this.webrtc.on('peerConnected', (peerId) => {
|
||||
console.log(chalk.blue(`✅ Connected to peer: ${peerId.substring(0, 8)}`));
|
||||
this.emit('peerConnected', peerId);
|
||||
});
|
||||
|
||||
this.webrtc.on('peerDisconnected', (peerId) => {
|
||||
console.log(chalk.red(`❌ Disconnected from peer: ${peerId.substring(0, 8)}`));
|
||||
this.handlePeerDisconnection(peerId);
|
||||
this.emit('peerDisconnected', peerId);
|
||||
});
|
||||
|
||||
this.webrtc.on('message', ({ from, message }) => {
|
||||
this.handleMessage(from, message);
|
||||
});
|
||||
|
||||
this.webrtc.on('signal', ({ peerId, signal }) => {
|
||||
this.sendSignalingMessage(peerId, {
|
||||
type: 'signal',
|
||||
signal
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setupDiscoveryServer() {
|
||||
this.discoveryServer = new WebSocketServer({ port: this.port });
|
||||
|
||||
this.discoveryServer.on('connection', (ws) => {
|
||||
ws.on('message', async (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
await this.handleSignalingMessage(ws, message);
|
||||
} catch (error) {
|
||||
console.error('Error handling signaling message:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log(chalk.cyan(`🌐 Discovery server listening on port ${this.port}`));
|
||||
}
|
||||
|
||||
async handleSignalingMessage(ws, message) {
|
||||
const { type, from, to, payload } = message;
|
||||
|
||||
if (to && to !== this.id) {
|
||||
// Forward message to the intended recipient
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'signal':
|
||||
// Handle WebRTC signaling with simple-peer
|
||||
let connection = this.webrtc.connections.get(`${this.id}-${from}`);
|
||||
if (!connection) {
|
||||
// Create connection as receiver
|
||||
connection = await this.webrtc.createConnection(from, false);
|
||||
}
|
||||
await this.webrtc.handleSignal(from, payload);
|
||||
break;
|
||||
|
||||
case 'discovery':
|
||||
ws.send(JSON.stringify({
|
||||
type: 'discovery-response',
|
||||
nodeId: this.id,
|
||||
port: this.port,
|
||||
isOracle: this.isOracle,
|
||||
ringPosition: this.ringPosition,
|
||||
connectedPeers: this.webrtc.getConnectedPeers()
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'join-ring':
|
||||
await this.handleJoinRingRequest(ws, payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async connectToPeer(nodeId, address) {
|
||||
try {
|
||||
const ws = new WebSocket(`ws://${address}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ws.on('open', async () => {
|
||||
// Send discovery message
|
||||
ws.send(JSON.stringify({
|
||||
type: 'discovery',
|
||||
from: this.id
|
||||
}));
|
||||
|
||||
// Create WebRTC connection as initiator
|
||||
const connection = await this.webrtc.createConnection(nodeId, true);
|
||||
|
||||
// Listen for signaling data from our peer
|
||||
const signalHandler = ({ peerId, signal }) => {
|
||||
if (peerId === nodeId) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'signal',
|
||||
from: this.id,
|
||||
to: nodeId,
|
||||
payload: signal
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
this.webrtc.on('signal', signalHandler);
|
||||
|
||||
// Listen for connection success
|
||||
const connectHandler = (peerId) => {
|
||||
if (peerId === nodeId) {
|
||||
this.webrtc.removeListener('signal', signalHandler);
|
||||
this.webrtc.removeListener('peerConnected', connectHandler);
|
||||
ws.close();
|
||||
resolve(true);
|
||||
}
|
||||
};
|
||||
|
||||
this.webrtc.on('peerConnected', connectHandler);
|
||||
});
|
||||
|
||||
ws.on('message', async (data) => {
|
||||
const message = JSON.parse(data.toString());
|
||||
|
||||
if (message.type === 'signal' && message.from === nodeId) {
|
||||
await this.webrtc.handleSignal(nodeId, message.payload);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', reject);
|
||||
|
||||
setTimeout(() => reject(new Error('Connection timeout')), 10000);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to connect to peer ${nodeId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async sendSignalingMessage(peerId, message) {
|
||||
// In a real implementation, this would route through a signaling server
|
||||
// For now, we'll use direct WebSocket connections
|
||||
}
|
||||
|
||||
handleMessage(from, message) {
|
||||
const { id: messageId, type, payload, route } = message;
|
||||
|
||||
// Prevent message loops
|
||||
if (this.messageHistory.has(messageId)) {
|
||||
return;
|
||||
}
|
||||
this.messageHistory.add(messageId);
|
||||
|
||||
// Clean old message history
|
||||
if (this.messageHistory.size > 1000) {
|
||||
const oldMessages = Array.from(this.messageHistory).slice(0, 500);
|
||||
oldMessages.forEach(id => this.messageHistory.delete(id));
|
||||
}
|
||||
|
||||
console.log(chalk.magenta(`📨 Received ${type} from ${from.substring(0, 8)}`));
|
||||
|
||||
switch (type) {
|
||||
case 'ring-message':
|
||||
this.handleRingMessage(from, payload, route, messageId);
|
||||
break;
|
||||
case 'oracle-query':
|
||||
if (this.isOracle) {
|
||||
this.handleOracleQuery(from, payload, messageId);
|
||||
}
|
||||
break;
|
||||
case 'oracle-response':
|
||||
this.emit('oracleResponse', { from, payload });
|
||||
break;
|
||||
case 'network-update':
|
||||
this.handleNetworkUpdate(from, payload);
|
||||
break;
|
||||
default:
|
||||
this.emit('message', { from, type, payload });
|
||||
}
|
||||
}
|
||||
|
||||
handleRingMessage(from, payload, route, messageId) {
|
||||
this.emit('ringMessage', { from, payload });
|
||||
|
||||
// Forward message along the ring
|
||||
this.forwardRingMessage({
|
||||
id: messageId,
|
||||
type: 'ring-message',
|
||||
payload,
|
||||
route: [...(route || []), this.id]
|
||||
}, from);
|
||||
}
|
||||
|
||||
forwardRingMessage(message, excludeFrom) {
|
||||
const route = message.route || [];
|
||||
|
||||
// Forward in inner ring
|
||||
if (this.rings.inner.right && this.rings.inner.right !== excludeFrom) {
|
||||
this.webrtc.sendMessage(this.rings.inner.right, message);
|
||||
}
|
||||
|
||||
// Forward in outer ring
|
||||
if (this.rings.outer.right && this.rings.outer.right !== excludeFrom) {
|
||||
this.webrtc.sendMessage(this.rings.outer.right, message);
|
||||
}
|
||||
}
|
||||
|
||||
sendRingMessage(payload, ring = 'both') {
|
||||
const message = {
|
||||
id: uuidv4(),
|
||||
type: 'ring-message',
|
||||
payload,
|
||||
route: [this.id]
|
||||
};
|
||||
|
||||
let sent = 0;
|
||||
|
||||
if (ring === 'inner' || ring === 'both') {
|
||||
if (this.rings.inner.right) {
|
||||
if (this.webrtc.sendMessage(this.rings.inner.right, message)) {
|
||||
sent++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ring === 'outer' || ring === 'both') {
|
||||
if (this.rings.outer.right) {
|
||||
if (this.webrtc.sendMessage(this.rings.outer.right, message)) {
|
||||
sent++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green(`📤 Sent ring message to ${sent} peers`));
|
||||
return sent > 0;
|
||||
}
|
||||
|
||||
async joinRing(bootstrapNode) {
|
||||
try {
|
||||
console.log(chalk.yellow(`🔄 Attempting to join ring via ${bootstrapNode}`));
|
||||
|
||||
const success = await this.connectToPeer('bootstrap', bootstrapNode);
|
||||
if (success) {
|
||||
// Request to join the ring network
|
||||
this.sendJoinRingRequest();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Failed to join ring:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sendJoinRingRequest() {
|
||||
const message = {
|
||||
id: uuidv4(),
|
||||
type: 'join-ring',
|
||||
from: this.id,
|
||||
payload: {
|
||||
nodeId: this.id,
|
||||
port: this.port,
|
||||
isOracle: this.isOracle
|
||||
}
|
||||
};
|
||||
|
||||
this.webrtc.broadcast(message);
|
||||
}
|
||||
|
||||
async handleJoinRingRequest(ws, payload) {
|
||||
const { nodeId, port, isOracle } = payload;
|
||||
|
||||
console.log(chalk.cyan(`🔗 New node ${nodeId.substring(0, 8)} wants to join the ring`));
|
||||
|
||||
// Add to known nodes
|
||||
this.knownNodes.set(nodeId, { port, isOracle });
|
||||
|
||||
if (isOracle) {
|
||||
this.oracleNodes.add(nodeId);
|
||||
}
|
||||
|
||||
// Find optimal position in rings for the new node
|
||||
await this.integrateNewNode(nodeId);
|
||||
}
|
||||
|
||||
async integrateNewNode(nodeId) {
|
||||
// Simple integration: connect as right neighbor in both rings
|
||||
// In a production system, this would be more sophisticated
|
||||
|
||||
const oldInnerRight = this.rings.inner.right;
|
||||
const oldOuterRight = this.rings.outer.right;
|
||||
|
||||
// Update ring connections
|
||||
this.rings.inner.right = nodeId;
|
||||
this.rings.outer.right = nodeId;
|
||||
|
||||
// Notify the network of topology change
|
||||
this.broadcastNetworkUpdate();
|
||||
|
||||
console.log(chalk.green(`✅ Integrated node ${nodeId.substring(0, 8)} into rings`));
|
||||
}
|
||||
|
||||
handlePeerDisconnection(peerId) {
|
||||
// Update ring topology when a peer disconnects
|
||||
if (this.rings.inner.left === peerId) {
|
||||
this.rings.inner.left = null;
|
||||
}
|
||||
if (this.rings.inner.right === peerId) {
|
||||
this.rings.inner.right = null;
|
||||
}
|
||||
if (this.rings.outer.left === peerId) {
|
||||
this.rings.outer.left = null;
|
||||
}
|
||||
if (this.rings.outer.right === peerId) {
|
||||
this.rings.outer.right = null;
|
||||
}
|
||||
|
||||
this.knownNodes.delete(peerId);
|
||||
this.oracleNodes.delete(peerId);
|
||||
|
||||
this.broadcastNetworkUpdate();
|
||||
}
|
||||
|
||||
broadcastNetworkUpdate() {
|
||||
const message = {
|
||||
id: uuidv4(),
|
||||
type: 'network-update',
|
||||
payload: {
|
||||
nodeId: this.id,
|
||||
rings: this.rings,
|
||||
knownNodes: Array.from(this.knownNodes.keys()),
|
||||
oracleNodes: Array.from(this.oracleNodes)
|
||||
}
|
||||
};
|
||||
|
||||
this.webrtc.broadcast(message);
|
||||
}
|
||||
|
||||
handleNetworkUpdate(from, payload) {
|
||||
const { rings, knownNodes, oracleNodes } = payload;
|
||||
|
||||
// Update our knowledge of network topology
|
||||
knownNodes.forEach(nodeId => {
|
||||
if (!this.knownNodes.has(nodeId)) {
|
||||
this.knownNodes.set(nodeId, {});
|
||||
}
|
||||
});
|
||||
|
||||
oracleNodes.forEach(nodeId => {
|
||||
this.oracleNodes.add(nodeId);
|
||||
});
|
||||
|
||||
this.emit('networkUpdate', { from, payload });
|
||||
}
|
||||
|
||||
// Oracle-specific methods
|
||||
handleOracleQuery(from, payload, messageId) {
|
||||
console.log(chalk.yellow(`🔮 Oracle query from ${from.substring(0, 8)}: ${payload.query}`));
|
||||
|
||||
// Process oracle query and send response
|
||||
const response = this.processOracleQuery(payload);
|
||||
|
||||
const responseMessage = {
|
||||
id: uuidv4(),
|
||||
type: 'oracle-response',
|
||||
payload: {
|
||||
queryId: messageId,
|
||||
response,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
this.webrtc.sendMessage(from, responseMessage);
|
||||
}
|
||||
|
||||
processOracleQuery(payload) {
|
||||
const { query, data } = payload;
|
||||
|
||||
// Simple oracle responses - extend this for your specific use case
|
||||
switch (query) {
|
||||
case 'network-status':
|
||||
return {
|
||||
connectedPeers: this.webrtc.getConnectedPeers().length,
|
||||
knownNodes: this.knownNodes.size,
|
||||
oracleNodes: this.oracleNodes.size,
|
||||
rings: this.rings
|
||||
};
|
||||
case 'timestamp':
|
||||
return { timestamp: Date.now() };
|
||||
case 'echo':
|
||||
return { echo: data };
|
||||
default:
|
||||
return { error: 'Unknown query type' };
|
||||
}
|
||||
}
|
||||
|
||||
queryOracle(query, data = null) {
|
||||
const oracleList = Array.from(this.oracleNodes);
|
||||
if (oracleList.length === 0) {
|
||||
return Promise.reject(new Error('No oracle nodes available'));
|
||||
}
|
||||
|
||||
// Query first available oracle
|
||||
const oracleId = oracleList[0];
|
||||
const message = {
|
||||
id: uuidv4(),
|
||||
type: 'oracle-query',
|
||||
payload: { query, data }
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Oracle query timeout'));
|
||||
}, 5000);
|
||||
|
||||
const responseHandler = ({ from, payload }) => {
|
||||
if (from === oracleId) {
|
||||
clearTimeout(timeout);
|
||||
this.removeListener('oracleResponse', responseHandler);
|
||||
resolve(payload.response);
|
||||
}
|
||||
};
|
||||
|
||||
this.on('oracleResponse', responseHandler);
|
||||
|
||||
if (!this.webrtc.sendMessage(oracleId, message)) {
|
||||
clearTimeout(timeout);
|
||||
this.removeListener('oracleResponse', responseHandler);
|
||||
reject(new Error('Failed to send oracle query'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getNetworkInfo() {
|
||||
return {
|
||||
nodeId: this.id,
|
||||
port: this.port,
|
||||
isOracle: this.isOracle,
|
||||
rings: this.rings,
|
||||
connectedPeers: this.webrtc.getConnectedPeers(),
|
||||
knownNodes: Array.from(this.knownNodes.keys()),
|
||||
oracleNodes: Array.from(this.oracleNodes)
|
||||
};
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
console.log(chalk.red(`🛑 Shutting down node ${this.id.substring(0, 8)}`));
|
||||
|
||||
// Notify peers of shutdown
|
||||
this.webrtc.broadcast({
|
||||
id: uuidv4(),
|
||||
type: 'node-shutdown',
|
||||
payload: { nodeId: this.id }
|
||||
});
|
||||
|
||||
// Clean up connections
|
||||
this.webrtc.destroy();
|
||||
|
||||
if (this.discoveryServer) {
|
||||
this.discoveryServer.close();
|
||||
}
|
||||
|
||||
this.removeAllListeners();
|
||||
}
|
||||
}
|
||||
160
src/webrtc-manager.js
Archivo normal
160
src/webrtc-manager.js
Archivo normal
@@ -0,0 +1,160 @@
|
||||
import SimplePeer from 'simple-peer';
|
||||
import { EventEmitter } from 'events';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export class WebRTCManager extends EventEmitter {
|
||||
constructor(nodeId) {
|
||||
super();
|
||||
this.nodeId = nodeId;
|
||||
this.connections = new Map();
|
||||
this.pendingConnections = new Map();
|
||||
}
|
||||
|
||||
async createConnection(peerId, isInitiator = false) {
|
||||
const connectionId = `${this.nodeId}-${peerId}`;
|
||||
|
||||
if (this.connections.has(connectionId)) {
|
||||
return this.connections.get(connectionId);
|
||||
}
|
||||
|
||||
const peer = new SimplePeer({
|
||||
initiator: isInitiator,
|
||||
trickle: false,
|
||||
config: {
|
||||
iceServers: [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const connection = {
|
||||
id: connectionId,
|
||||
peerId,
|
||||
peer,
|
||||
isInitiator,
|
||||
state: 'connecting'
|
||||
};
|
||||
|
||||
// Handle signaling data
|
||||
peer.on('signal', (data) => {
|
||||
this.emit('signal', {
|
||||
peerId,
|
||||
signal: data
|
||||
});
|
||||
});
|
||||
|
||||
// Handle connection
|
||||
peer.on('connect', () => {
|
||||
connection.state = 'connected';
|
||||
console.log(`WebRTC connection established with ${peerId.substring(0, 8)}`);
|
||||
this.emit('peerConnected', peerId);
|
||||
});
|
||||
|
||||
// Handle data
|
||||
peer.on('data', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
this.emit('message', {
|
||||
from: peerId,
|
||||
message
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error parsing message:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
peer.on('error', (error) => {
|
||||
console.error(`WebRTC error with ${peerId}:`, error.message);
|
||||
connection.state = 'failed';
|
||||
this.removePeer(peerId);
|
||||
});
|
||||
|
||||
// Handle close
|
||||
peer.on('close', () => {
|
||||
console.log(`WebRTC connection closed with ${peerId.substring(0, 8)}`);
|
||||
connection.state = 'disconnected';
|
||||
this.removePeer(peerId);
|
||||
});
|
||||
|
||||
this.connections.set(connectionId, connection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
async handleSignal(peerId, signal) {
|
||||
const connectionId = `${this.nodeId}-${peerId}`;
|
||||
const connection = this.connections.get(connectionId);
|
||||
|
||||
if (connection && connection.peer) {
|
||||
connection.peer.signal(signal);
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage(peerId, message) {
|
||||
const connectionId = `${this.nodeId}-${peerId}`;
|
||||
const connection = this.connections.get(connectionId);
|
||||
|
||||
if (connection && connection.peer && connection.peer.connected) {
|
||||
try {
|
||||
connection.peer.send(JSON.stringify(message));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`Error sending message to ${peerId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
broadcast(message, excludePeers = []) {
|
||||
let sent = 0;
|
||||
for (const [connectionId, connection] of this.connections) {
|
||||
if (!excludePeers.includes(connection.peerId) &&
|
||||
connection.peer &&
|
||||
connection.peer.connected) {
|
||||
try {
|
||||
connection.peer.send(JSON.stringify(message));
|
||||
sent++;
|
||||
} catch (error) {
|
||||
console.error(`Error broadcasting to ${connection.peerId}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
removePeer(peerId) {
|
||||
const connectionId = `${this.nodeId}-${peerId}`;
|
||||
const connection = this.connections.get(connectionId);
|
||||
|
||||
if (connection) {
|
||||
if (connection.peer) {
|
||||
connection.peer.destroy();
|
||||
}
|
||||
this.connections.delete(connectionId);
|
||||
this.emit('peerDisconnected', peerId);
|
||||
}
|
||||
}
|
||||
|
||||
getConnectedPeers() {
|
||||
return Array.from(this.connections.values())
|
||||
.filter(conn => conn.peer && conn.peer.connected)
|
||||
.map(conn => conn.peerId);
|
||||
}
|
||||
|
||||
getConnectionState(peerId) {
|
||||
const connectionId = `${this.nodeId}-${peerId}`;
|
||||
const connection = this.connections.get(connectionId);
|
||||
return connection ? connection.state : 'disconnected';
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const [connectionId, connection] of this.connections) {
|
||||
if (connection.peer) {
|
||||
connection.peer.destroy();
|
||||
}
|
||||
}
|
||||
this.connections.clear();
|
||||
}
|
||||
}
|
||||
339
test/test-network.js
Archivo normal
339
test/test-network.js
Archivo normal
@@ -0,0 +1,339 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { RingNode } from '../src/ring-node.js';
|
||||
import { OracleNode } from '../src/oracle-node.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
console.log(chalk.blue.bold('🧪 Ring Network Test Suite\n'));
|
||||
|
||||
class NetworkTester {
|
||||
constructor() {
|
||||
this.nodes = [];
|
||||
this.testResults = [];
|
||||
}
|
||||
|
||||
async delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
log(message, type = 'info') {
|
||||
const colors = {
|
||||
info: chalk.blue,
|
||||
success: chalk.green,
|
||||
error: chalk.red,
|
||||
warning: chalk.yellow
|
||||
};
|
||||
console.log(`${colors[type]}${message}`);
|
||||
}
|
||||
|
||||
async runTest(name, testFn) {
|
||||
this.log(`\n🔬 Running test: ${name}`, 'info');
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
await testFn();
|
||||
const duration = Date.now() - startTime;
|
||||
this.log(`✅ Test passed: ${name} (${duration}ms)`, 'success');
|
||||
this.testResults.push({ name, status: 'passed', duration });
|
||||
} catch (error) {
|
||||
this.log(`❌ Test failed: ${name} - ${error.message}`, 'error');
|
||||
this.testResults.push({ name, status: 'failed', error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async setupBasicNetwork() {
|
||||
this.log('🏗️ Setting up basic network with 1 Oracle and 2 regular nodes', 'info');
|
||||
|
||||
// Create Oracle node
|
||||
const oracle = new OracleNode({
|
||||
id: 'oracle-1',
|
||||
port: 8080
|
||||
});
|
||||
this.nodes.push(oracle);
|
||||
|
||||
// Create regular nodes
|
||||
const node1 = new RingNode({
|
||||
id: 'node-1',
|
||||
port: 8081
|
||||
});
|
||||
this.nodes.push(node1);
|
||||
|
||||
const node2 = new RingNode({
|
||||
id: 'node-2',
|
||||
port: 8082
|
||||
});
|
||||
this.nodes.push(node2);
|
||||
|
||||
// Allow nodes to initialize
|
||||
await this.delay(2000);
|
||||
|
||||
return { oracle, node1, node2 };
|
||||
}
|
||||
|
||||
async testNodeInitialization() {
|
||||
const { oracle, node1, node2 } = await this.setupBasicNetwork();
|
||||
|
||||
// Verify nodes are properly initialized
|
||||
if (!oracle.id || !oracle.isOracle) {
|
||||
throw new Error('Oracle node not properly initialized');
|
||||
}
|
||||
|
||||
if (!node1.id || node1.isOracle) {
|
||||
throw new Error('Regular node 1 not properly initialized');
|
||||
}
|
||||
|
||||
if (!node2.id || node2.isOracle) {
|
||||
throw new Error('Regular node 2 not properly initialized');
|
||||
}
|
||||
|
||||
this.log(`Oracle ID: ${oracle.id}`, 'info');
|
||||
this.log(`Node 1 ID: ${node1.id}`, 'info');
|
||||
this.log(`Node 2 ID: ${node2.id}`, 'info');
|
||||
}
|
||||
|
||||
async testPeerConnections() {
|
||||
const { oracle, node1, node2 } = this.nodes.length > 0 ?
|
||||
{ oracle: this.nodes[0], node1: this.nodes[1], node2: this.nodes[2] } :
|
||||
await this.setupBasicNetwork();
|
||||
|
||||
// Simulate peer connections
|
||||
// In a real test, we would actually establish WebRTC connections
|
||||
// For this test, we'll simulate the connection state
|
||||
|
||||
oracle.knownNodes.set(node1.id, { port: node1.port });
|
||||
oracle.knownNodes.set(node2.id, { port: node2.port });
|
||||
|
||||
node1.knownNodes.set(oracle.id, { port: oracle.port, isOracle: true });
|
||||
node1.knownNodes.set(node2.id, { port: node2.port });
|
||||
|
||||
node2.knownNodes.set(oracle.id, { port: oracle.port, isOracle: true });
|
||||
node2.knownNodes.set(node1.id, { port: node1.port });
|
||||
|
||||
// Update oracle nodes list
|
||||
node1.oracleNodes.add(oracle.id);
|
||||
node2.oracleNodes.add(oracle.id);
|
||||
|
||||
this.log(`Oracle knows ${oracle.knownNodes.size} nodes`, 'info');
|
||||
this.log(`Node 1 knows ${node1.knownNodes.size} nodes`, 'info');
|
||||
this.log(`Node 2 knows ${node2.knownNodes.size} nodes`, 'info');
|
||||
}
|
||||
|
||||
async testRingTopology() {
|
||||
const { oracle, node1, node2 } = this.nodes.length > 0 ?
|
||||
{ oracle: this.nodes[0], node1: this.nodes[1], node2: this.nodes[2] } :
|
||||
await this.setupBasicNetwork();
|
||||
|
||||
// Simulate ring topology setup
|
||||
// Oracle -> Node1 -> Node2 -> Oracle (inner ring)
|
||||
oracle.rings.inner.right = node1.id;
|
||||
oracle.rings.inner.left = node2.id;
|
||||
|
||||
node1.rings.inner.left = oracle.id;
|
||||
node1.rings.inner.right = node2.id;
|
||||
|
||||
node2.rings.inner.left = node1.id;
|
||||
node2.rings.inner.right = oracle.id;
|
||||
|
||||
// Oracle -> Node2 -> Node1 -> Oracle (outer ring - reversed)
|
||||
oracle.rings.outer.right = node2.id;
|
||||
oracle.rings.outer.left = node1.id;
|
||||
|
||||
node1.rings.outer.left = node2.id;
|
||||
node1.rings.outer.right = oracle.id;
|
||||
|
||||
node2.rings.outer.left = oracle.id;
|
||||
node2.rings.outer.right = node1.id;
|
||||
|
||||
// Verify ring integrity
|
||||
const oracleHasValidRings = oracle.rings.inner.left && oracle.rings.inner.right &&
|
||||
oracle.rings.outer.left && oracle.rings.outer.right;
|
||||
|
||||
if (!oracleHasValidRings) {
|
||||
throw new Error('Oracle rings not properly configured');
|
||||
}
|
||||
|
||||
this.log('Ring topology established successfully', 'success');
|
||||
}
|
||||
|
||||
async testOracleServices() {
|
||||
const oracle = this.nodes[0];
|
||||
|
||||
if (!oracle.isOracle) {
|
||||
throw new Error('First node is not an Oracle');
|
||||
}
|
||||
|
||||
// Test network analysis
|
||||
const analysis = oracle.analyzeNetwork();
|
||||
if (!analysis.networkSize || !analysis.ringTopology) {
|
||||
throw new Error('Network analysis failed');
|
||||
}
|
||||
this.log(`Network analysis: ${analysis.networkSize} nodes`, 'info');
|
||||
|
||||
// Test data storage
|
||||
const storeResult = oracle.handleDataStorage({
|
||||
operation: 'set',
|
||||
key: 'test-key',
|
||||
value: 'test-value'
|
||||
});
|
||||
|
||||
if (!storeResult.success) {
|
||||
throw new Error('Data storage (set) failed');
|
||||
}
|
||||
|
||||
const getResult = oracle.handleDataStorage({
|
||||
operation: 'get',
|
||||
key: 'test-key'
|
||||
});
|
||||
|
||||
if (!getResult.success || getResult.value !== 'test-value') {
|
||||
throw new Error('Data storage (get) failed');
|
||||
}
|
||||
|
||||
this.log('Oracle data storage working correctly', 'success');
|
||||
|
||||
// Test consensus
|
||||
const proposalId = 'test-proposal-' + Date.now();
|
||||
const consensusResult = oracle.handleConsensus({
|
||||
proposalId,
|
||||
proposal: 'Test proposal for network upgrade'
|
||||
});
|
||||
|
||||
if (!consensusResult.success) {
|
||||
throw new Error('Consensus proposal creation failed');
|
||||
}
|
||||
|
||||
this.log('Oracle consensus mechanism working', 'success');
|
||||
|
||||
// Test health check
|
||||
const health = oracle.performHealthCheck();
|
||||
if (!health.checks) {
|
||||
throw new Error('Health check failed');
|
||||
}
|
||||
|
||||
this.log(`Health check: ${health.healthy ? 'Healthy' : 'Issues detected'}`,
|
||||
health.healthy ? 'success' : 'warning');
|
||||
}
|
||||
|
||||
async testMessageRouting() {
|
||||
// This test simulates message routing through the ring
|
||||
// In a real implementation, this would test actual WebRTC communication
|
||||
|
||||
const oracle = this.nodes[0];
|
||||
let messageReceived = false;
|
||||
|
||||
// Set up message handler
|
||||
oracle.on('ringMessage', ({ from, payload }) => {
|
||||
if (payload.type === 'test-message') {
|
||||
messageReceived = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Simulate sending a message
|
||||
oracle.emit('ringMessage', {
|
||||
from: 'test-sender',
|
||||
payload: { type: 'test-message', content: 'Hello Ring!' }
|
||||
});
|
||||
|
||||
await this.delay(100);
|
||||
|
||||
if (!messageReceived) {
|
||||
throw new Error('Message routing failed');
|
||||
}
|
||||
|
||||
this.log('Message routing test passed', 'success');
|
||||
}
|
||||
|
||||
async testNetworkResilience() {
|
||||
const { oracle, node1, node2 } = this.nodes.length > 0 ?
|
||||
{ oracle: this.nodes[0], node1: this.nodes[1], node2: this.nodes[2] } :
|
||||
await this.setupBasicNetwork();
|
||||
|
||||
// Simulate node disconnection
|
||||
const originalPeerCount = oracle.knownNodes.size;
|
||||
|
||||
// Remove node2 from network
|
||||
oracle.handlePeerDisconnection(node2.id);
|
||||
node1.handlePeerDisconnection(node2.id);
|
||||
|
||||
if (oracle.knownNodes.has(node2.id)) {
|
||||
throw new Error('Node not properly removed from network');
|
||||
}
|
||||
|
||||
// Verify network adapted to disconnection
|
||||
const newPeerCount = oracle.knownNodes.size;
|
||||
if (newPeerCount >= originalPeerCount) {
|
||||
throw new Error('Network did not adapt to peer disconnection');
|
||||
}
|
||||
|
||||
this.log(`Network adapted to disconnection: ${originalPeerCount} -> ${newPeerCount} nodes`, 'success');
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
this.log('\n🧹 Cleaning up test environment', 'info');
|
||||
|
||||
for (const node of this.nodes) {
|
||||
try {
|
||||
await node.destroy();
|
||||
} catch (error) {
|
||||
this.log(`Warning: Error cleaning up node ${node.id}: ${error.message}`, 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
this.nodes = [];
|
||||
}
|
||||
|
||||
printResults() {
|
||||
this.log('\n📊 Test Results Summary:', 'info');
|
||||
|
||||
const passed = this.testResults.filter(r => r.status === 'passed').length;
|
||||
const failed = this.testResults.filter(r => r.status === 'failed').length;
|
||||
|
||||
this.log(`✅ Passed: ${passed}`, 'success');
|
||||
this.log(`❌ Failed: ${failed}`, failed > 0 ? 'error' : 'info');
|
||||
|
||||
if (failed > 0) {
|
||||
this.log('\nFailed tests:', 'error');
|
||||
this.testResults
|
||||
.filter(r => r.status === 'failed')
|
||||
.forEach(r => {
|
||||
this.log(` - ${r.name}: ${r.error}`, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
const totalDuration = this.testResults.reduce((sum, r) => sum + (r.duration || 0), 0);
|
||||
this.log(`\nTotal test duration: ${totalDuration}ms`, 'info');
|
||||
|
||||
return failed === 0;
|
||||
}
|
||||
|
||||
async runAllTests() {
|
||||
try {
|
||||
await this.runTest('Node Initialization', () => this.testNodeInitialization());
|
||||
await this.runTest('Peer Connections', () => this.testPeerConnections());
|
||||
await this.runTest('Ring Topology', () => this.testRingTopology());
|
||||
await this.runTest('Oracle Services', () => this.testOracleServices());
|
||||
await this.runTest('Message Routing', () => this.testMessageRouting());
|
||||
await this.runTest('Network Resilience', () => this.testNetworkResilience());
|
||||
} finally {
|
||||
await this.cleanup();
|
||||
}
|
||||
|
||||
return this.printResults();
|
||||
}
|
||||
}
|
||||
|
||||
// Run tests if this file is executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const tester = new NetworkTester();
|
||||
|
||||
tester.runAllTests()
|
||||
.then(success => {
|
||||
console.log(chalk.blue.bold('\n🏁 Test suite completed'));
|
||||
process.exit(success ? 0 : 1);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(chalk.red.bold(`\n💥 Test suite crashed: ${error.message}`));
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { NetworkTester };
|
||||
Referencia en una nueva incidencia
Block a user