|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { Blockchain, Network, Storage } = require('decentralized-internet');
|
|
|
const Web3 = require('web3');
|
|
|
const EventEmitter = require('events');
|
|
|
const fs = require('fs').promises;
|
|
|
const path = require('path');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DecentralizedCoordinator extends EventEmitter {
|
|
|
constructor(config = {}) {
|
|
|
super();
|
|
|
|
|
|
this.config = {
|
|
|
blockchainProvider: config.blockchainProvider || 'http://localhost:8545',
|
|
|
networkPort: config.networkPort || 8080,
|
|
|
nodeId: config.nodeId || this.generateNodeId(),
|
|
|
storageDir: config.storageDir || './storage',
|
|
|
...config
|
|
|
};
|
|
|
|
|
|
this.blockchain = null;
|
|
|
this.network = null;
|
|
|
this.web3 = null;
|
|
|
this.taskRegistry = new Map();
|
|
|
this.nodeRegistry = new Map();
|
|
|
this.isInitialized = false;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async initialize() {
|
|
|
try {
|
|
|
console.log('Initializing Decentralized Coordinator (Localhost)...');
|
|
|
console.log(`Node ID: ${this.config.nodeId}`);
|
|
|
|
|
|
|
|
|
await fs.mkdir(this.config.storageDir, { recursive: true });
|
|
|
|
|
|
|
|
|
this.web3 = new Web3(this.config.blockchainProvider);
|
|
|
|
|
|
|
|
|
this.blockchain = new Blockchain({
|
|
|
nodeId: this.config.nodeId,
|
|
|
difficulty: 2
|
|
|
});
|
|
|
|
|
|
this.network = new Network({
|
|
|
port: this.config.networkPort,
|
|
|
nodeId: this.config.nodeId
|
|
|
});
|
|
|
|
|
|
|
|
|
this.setupEventListeners();
|
|
|
|
|
|
|
|
|
await this.network.start();
|
|
|
|
|
|
this.isInitialized = true;
|
|
|
console.log('Decentralized Coordinator initialized successfully');
|
|
|
|
|
|
this.emit('initialized', { nodeId: this.config.nodeId });
|
|
|
|
|
|
return true;
|
|
|
} catch (error) {
|
|
|
console.error('Failed to initialize Decentralized Coordinator:', error);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async registerTask(task) {
|
|
|
if (!this.isInitialized) {
|
|
|
throw new Error('Coordinator not initialized');
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
const taskId = this.generateTaskId();
|
|
|
|
|
|
|
|
|
const taskData = {
|
|
|
id: taskId,
|
|
|
ligandFile: task.ligandFile,
|
|
|
receptorFile: task.receptorFile,
|
|
|
parameters: task.parameters,
|
|
|
status: 'pending',
|
|
|
submittedBy: this.config.nodeId,
|
|
|
timestamp: Date.now(),
|
|
|
requiredCompute: task.requiredCompute || 1,
|
|
|
priority: task.priority || 'normal'
|
|
|
};
|
|
|
|
|
|
|
|
|
const ligandPath = await this.storeLocally(task.ligandContent, `ligand_${taskId}.pdbqt`);
|
|
|
const receptorPath = await this.storeLocally(task.receptorContent, `receptor_${taskId}.pdbqt`);
|
|
|
|
|
|
taskData.ligandPath = ligandPath;
|
|
|
taskData.receptorPath = receptorPath;
|
|
|
|
|
|
|
|
|
const block = {
|
|
|
type: 'TASK_REGISTRATION',
|
|
|
data: taskData,
|
|
|
timestamp: Date.now(),
|
|
|
nodeId: this.config.nodeId
|
|
|
};
|
|
|
|
|
|
this.blockchain.addBlock(block);
|
|
|
|
|
|
|
|
|
this.taskRegistry.set(taskId, taskData);
|
|
|
|
|
|
|
|
|
await this.network.broadcast({
|
|
|
type: 'NEW_TASK',
|
|
|
taskId: taskId,
|
|
|
task: taskData
|
|
|
});
|
|
|
|
|
|
console.log(`Task registered: ${taskId}`);
|
|
|
this.emit('taskRegistered', taskData);
|
|
|
|
|
|
return taskId;
|
|
|
} catch (error) {
|
|
|
console.error('Failed to register task:', error);
|
|
|
throw error;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async claimTask(taskId) {
|
|
|
if (!this.isInitialized) {
|
|
|
throw new Error('Coordinator not initialized');
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
const task = this.taskRegistry.get(taskId);
|
|
|
|
|
|
if (!task) {
|
|
|
throw new Error(`Task not found: ${taskId}`);
|
|
|
}
|
|
|
|
|
|
if (task.status !== 'pending') {
|
|
|
throw new Error(`Task already claimed: ${taskId}`);
|
|
|
}
|
|
|
|
|
|
|
|
|
task.status = 'processing';
|
|
|
task.claimedBy = this.config.nodeId;
|
|
|
task.claimedAt = Date.now();
|
|
|
|
|
|
|
|
|
const block = {
|
|
|
type: 'TASK_CLAIM',
|
|
|
data: {
|
|
|
taskId: taskId,
|
|
|
nodeId: this.config.nodeId,
|
|
|
timestamp: Date.now()
|
|
|
}
|
|
|
};
|
|
|
|
|
|
this.blockchain.addBlock(block);
|
|
|
|
|
|
|
|
|
await this.network.broadcast({
|
|
|
type: 'TASK_CLAIMED',
|
|
|
taskId: taskId,
|
|
|
nodeId: this.config.nodeId
|
|
|
});
|
|
|
|
|
|
console.log(`Task claimed: ${taskId}`);
|
|
|
this.emit('taskClaimed', task);
|
|
|
|
|
|
|
|
|
const ligandContent = await this.retrieveLocally(task.ligandPath);
|
|
|
const receptorContent = await this.retrieveLocally(task.receptorPath);
|
|
|
|
|
|
return {
|
|
|
...task,
|
|
|
ligandContent,
|
|
|
receptorContent
|
|
|
};
|
|
|
} catch (error) {
|
|
|
console.error('Failed to claim task:', error);
|
|
|
throw error;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async submitResults(taskId, results) {
|
|
|
if (!this.isInitialized) {
|
|
|
throw new Error('Coordinator not initialized');
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
const task = this.taskRegistry.get(taskId);
|
|
|
|
|
|
if (!task) {
|
|
|
throw new Error(`Task not found: ${taskId}`);
|
|
|
}
|
|
|
|
|
|
|
|
|
const resultsPath = await this.storeLocally(JSON.stringify(results), `results_${taskId}.json`);
|
|
|
|
|
|
|
|
|
task.status = 'completed';
|
|
|
task.resultsPath = resultsPath;
|
|
|
task.completedAt = Date.now();
|
|
|
task.computationTime = results.computationTime;
|
|
|
|
|
|
|
|
|
const block = {
|
|
|
type: 'TASK_COMPLETION',
|
|
|
data: {
|
|
|
taskId: taskId,
|
|
|
nodeId: this.config.nodeId,
|
|
|
resultsPath: resultsPath,
|
|
|
timestamp: Date.now()
|
|
|
}
|
|
|
};
|
|
|
|
|
|
this.blockchain.addBlock(block);
|
|
|
|
|
|
|
|
|
await this.network.broadcast({
|
|
|
type: 'TASK_COMPLETED',
|
|
|
taskId: taskId,
|
|
|
resultsPath: resultsPath,
|
|
|
nodeId: this.config.nodeId
|
|
|
});
|
|
|
|
|
|
console.log(`Results submitted for task: ${taskId}`);
|
|
|
this.emit('resultsSubmitted', { taskId, resultsPath });
|
|
|
|
|
|
return true;
|
|
|
} catch (error) {
|
|
|
console.error('Failed to submit results:', error);
|
|
|
throw error;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getResults(taskId) {
|
|
|
try {
|
|
|
const task = this.taskRegistry.get(taskId);
|
|
|
|
|
|
if (!task) {
|
|
|
throw new Error(`Task not found: ${taskId}`);
|
|
|
}
|
|
|
|
|
|
if (task.status !== 'completed') {
|
|
|
throw new Error(`Task not completed: ${taskId}`);
|
|
|
}
|
|
|
|
|
|
|
|
|
const resultsContent = await this.retrieveLocally(task.resultsPath);
|
|
|
const results = JSON.parse(resultsContent);
|
|
|
|
|
|
return {
|
|
|
taskId: taskId,
|
|
|
results: results,
|
|
|
completedAt: task.completedAt,
|
|
|
processedBy: task.claimedBy
|
|
|
};
|
|
|
} catch (error) {
|
|
|
console.error('Failed to retrieve results:', error);
|
|
|
throw error;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getBlockchainStatus() {
|
|
|
if (!this.blockchain) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
chainLength: this.blockchain.chain.length,
|
|
|
difficulty: this.blockchain.difficulty,
|
|
|
isValid: this.blockchain.isChainValid(),
|
|
|
lastBlock: this.blockchain.getLatestBlock()
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getNetworkStatus() {
|
|
|
return {
|
|
|
nodeId: this.config.nodeId,
|
|
|
isInitialized: this.isInitialized,
|
|
|
connectedPeers: this.nodeRegistry.size,
|
|
|
pendingTasks: Array.from(this.taskRegistry.values()).filter(t => t.status === 'pending').length,
|
|
|
processingTasks: Array.from(this.taskRegistry.values()).filter(t => t.status === 'processing').length,
|
|
|
completedTasks: Array.from(this.taskRegistry.values()).filter(t => t.status === 'completed').length
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async storeLocally(content, filename) {
|
|
|
try {
|
|
|
const filePath = path.join(this.config.storageDir, filename);
|
|
|
await fs.writeFile(filePath, content);
|
|
|
return filePath;
|
|
|
} catch (error) {
|
|
|
console.error('Failed to store locally:', error);
|
|
|
throw error;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async retrieveLocally(filePath) {
|
|
|
try {
|
|
|
const content = await fs.readFile(filePath, 'utf8');
|
|
|
return content;
|
|
|
} catch (error) {
|
|
|
console.error('Failed to retrieve locally:', error);
|
|
|
throw error;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupEventListeners() {
|
|
|
|
|
|
this.network.on('message', (message) => {
|
|
|
this.handleNetworkMessage(message);
|
|
|
});
|
|
|
|
|
|
|
|
|
this.network.on('peer:connected', (peerId) => {
|
|
|
console.log(`Peer connected: ${peerId}`);
|
|
|
this.nodeRegistry.set(peerId, { id: peerId, connectedAt: Date.now() });
|
|
|
this.emit('peerConnected', peerId);
|
|
|
});
|
|
|
|
|
|
this.network.on('peer:disconnected', (peerId) => {
|
|
|
console.log(`Peer disconnected: ${peerId}`);
|
|
|
this.nodeRegistry.delete(peerId);
|
|
|
this.emit('peerDisconnected', peerId);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleNetworkMessage(message) {
|
|
|
switch (message.type) {
|
|
|
case 'NEW_TASK':
|
|
|
if (!this.taskRegistry.has(message.taskId)) {
|
|
|
this.taskRegistry.set(message.taskId, message.task);
|
|
|
this.emit('newTask', message.task);
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 'TASK_CLAIMED':
|
|
|
const task = this.taskRegistry.get(message.taskId);
|
|
|
if (task) {
|
|
|
task.status = 'processing';
|
|
|
task.claimedBy = message.nodeId;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 'TASK_COMPLETED':
|
|
|
const completedTask = this.taskRegistry.get(message.taskId);
|
|
|
if (completedTask) {
|
|
|
completedTask.status = 'completed';
|
|
|
completedTask.resultsPath = message.resultsPath;
|
|
|
this.emit('taskCompleted', { taskId: message.taskId, resultsPath: message.resultsPath });
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
generateNodeId() {
|
|
|
return `NODE_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
generateTaskId() {
|
|
|
return `TASK_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async shutdown() {
|
|
|
if (this.network) {
|
|
|
await this.network.stop();
|
|
|
}
|
|
|
this.isInitialized = false;
|
|
|
console.log('Decentralized Coordinator shut down');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
module.exports = DecentralizedCoordinator;
|
|
|
|