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