Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 109 additions & 21 deletions neuron/nodes/buyer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
triggerCacheUpdate
} = require('./global-contract-monitor.js');
const { getConnectionMonitor, removeConnectionMonitor } = require('./connection-monitor.js');
const ContractRegistryService = require('../services/ContractRegistryService');

// At the top of the file, add a global mapping
const templateToInstanceMap = new Map();
Expand Down Expand Up @@ -188,7 +189,7 @@ module.exports = function (RED) {
let hederaServiceError = null;

try {
waitForEnvReady(() => {
waitForEnvReady(async () => {
console.log("Hedera credentials loaded for buyer");

const operatorId = process.env.HEDERA_OPERATOR_ID;
Expand All @@ -201,19 +202,22 @@ module.exports = function (RED) {
}

try {
// Initialize contract registry
if (!ContractRegistryService.initialized) {
await ContractRegistryService.initialize();
}

// Get contracts from registry
const contractsMap = ContractRegistryService.getContractsMapForHedera();

hederaService = new HederaAccountService({
network: process.env.HEDERA_NETWORK || 'testnet',
operatorId: process.env.HEDERA_OPERATOR_ID,
operatorKey: process.env.HEDERA_OPERATOR_KEY,
contracts: {
"jetvision": process.env.JETVISION_CONTRACT_ID,
"chat": process.env.CHAT_CONTRACT_ID,
"challenges": process.env.CHALLENGES_CONTRACT_ID,
//"radiation": process.env.RADIATION_CONTRACT_ID
}
contracts: contractsMap
});
hederaServiceInitialized = true;
console.log("HederaAccountService initialized successfully for buyer");
console.log("HederaAccountService initialized successfully for buyer with", Object.keys(contractsMap).length, "contracts");
} catch (error) {
console.error("Failed to initialize HederaAccountService for buyer:", error.message);
hederaServiceError = error;
Expand Down Expand Up @@ -377,13 +381,12 @@ module.exports = function (RED) {
loadedDeviceInfo.deviceType = config.deviceType;
loadedDeviceInfo.price = config.price;

// Set smart contract from configuration
const contracts = {
"jetvision": process.env.JETVISION_CONTRACT_EVM,
"chat": process.env.CHAT_CONTRACT_EVM,
"challenges": process.env.CHALLENGES_CONTRACT_EVM,
};
loadedDeviceInfo.smartContract = contracts[config.smartContract.toLowerCase()];
// Set smart contract from configuration - get from registry
if (!ContractRegistryService.initialized) {
await ContractRegistryService.initialize();
}
const contractsMap = ContractRegistryService.getContractsMapForEvm();
loadedDeviceInfo.smartContract = contractsMap[config.smartContract.toLowerCase()];

node.deviceInfo = loadedDeviceInfo;
node.deviceInfo.nodeType = "buyer";
Expand Down Expand Up @@ -449,12 +452,11 @@ module.exports = function (RED) {

console.log(`Node ${node.id}: Validated ${sellerDevices.length} seller device(s)`);

const contracts = {
"jetvision": process.env.JETVISION_CONTRACT_EVM,
"chat": process.env.CHAT_CONTRACT_EVM,
"challenges": process.env.CHALLENGES_CONTRACT_EVM,
//"radiation": process.env.RADIATION_CONTRACT_EVM
};
// Get contracts from registry
if (!ContractRegistryService.initialized) {
await ContractRegistryService.initialize();
}
const contracts = ContractRegistryService.getContractsMapForEvm();
const deviceRole = 'buyer';
const serialNumber = 'buyer device';
const deviceName = 'buyer device';
Expand Down Expand Up @@ -586,6 +588,9 @@ module.exports = function (RED) {
const connected = await connectionMonitor.connect();

if (connected) {
// Set initial status immediately after connection
node.status({ fill: "yellow", shape: "ring", text: "Connected - no peers" });

// Set up status update callback to update node status
connectionMonitor.onStatusUpdate((status) => {
if (status.isConnected) {
Expand Down Expand Up @@ -678,6 +683,89 @@ module.exports = function (RED) {
return obj;
}

// ============================================
// Contract Registry API Endpoints
// ============================================

// Get all contracts from the registry
RED.httpAdmin.get('/registry/contracts', async function (req, res) {
try {
// Initialize registry if not already done
if (!ContractRegistryService.initialized) {
await ContractRegistryService.initialize();
}

const contracts = ContractRegistryService.getAllContracts();
res.json({
success: true,
contracts,
count: contracts.length
});
} catch (error) {
console.error('[Registry API] Error getting contracts:', error);
res.json({
success: false,
error: error.message
});
}
});

// Get a specific contract by name
RED.httpAdmin.get('/registry/contract/:name', async function (req, res) {
try {
const { name } = req.params;

// Initialize registry if not already done
if (!ContractRegistryService.initialized) {
await ContractRegistryService.initialize();
}

const contract = ContractRegistryService.getContract(name);

if (contract) {
res.json({
success: true,
contract
});
} else {
res.json({
success: false,
error: `Contract '${name}' not found`
});
}
} catch (error) {
console.error('[Registry API] Error getting contract:', error);
res.json({
success: false,
error: error.message
});
}
});

// Refresh contracts from source (will trigger re-fetch from mother contract in future)
RED.httpAdmin.post('/registry/refresh', async function (req, res) {
try {
await ContractRegistryService.refreshContracts();
const contracts = ContractRegistryService.getAllContracts();

res.json({
success: true,
message: 'Contracts refreshed successfully',
count: contracts.length
});
} catch (error) {
console.error('[Registry API] Error refreshing contracts:', error);
res.json({
success: false,
error: error.message
});
}
});

// ============================================
// Buyer Device API Endpoints
// ============================================

RED.httpAdmin.get('/buyer/devices', function (req, res) {
const contract = req.query.contract || 'jetvision';
const devices = getGlobalAllDevices(contract);
Expand Down
157 changes: 108 additions & 49 deletions neuron/nodes/global-contract-monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,13 @@ require('../services/NeuronEnvironment').load();
const path = require('path');
const fs = require('fs');
const { HederaContractService } = require('neuron-js-registration-sdk');
const ContractRegistryService = require('../services/ContractRegistryService');

// --- GLOBAL CONTRACT MONITORING SERVICE (Singleton) ---
// Separate data structures for each contract
let globalPeerCounts = {
jetvision: 0,
chat: 0,
challenges: 0
};
let globalAllDevices = {
jetvision: [],
chat: [],
challenges: []
};
let contractLoadingStates = {
jetvision: false,
chat: false,
challenges: false
};
// Separate data structures for each contract (will be dynamically populated)
let globalPeerCounts = {};
let globalAllDevices = {};
let contractLoadingStates = {};
let contractMonitoringInterval = null;
let isContractMonitoringActive = false;
let contractServices = {};
Expand All @@ -31,44 +20,111 @@ const cacheDir = path.join(require('../services/NeuronUserHome').load(), 'cache'
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true });
}
const cacheFiles = {
jetvision: 'contract-data-jetvision.json',
chat: 'contract-data-chat.json',
challenges: 'contract-data-challenges.json'
};
for (let cacheFile in cacheFiles) {
const cacheFilePath = path.join(cacheDir, cacheFiles[cacheFile]);

if (!fs.existsSync(cacheFilePath)) {
let cacheSampleData = {};

if (fs.existsSync(path.join(__dirname, 'cache', cacheFiles[cacheFile]))) {
cacheSampleData = fs.readFileSync(path.join(__dirname, 'cache', cacheFiles[cacheFile]), 'utf-8');
cacheSampleData = JSON.parse(cacheSampleData);

// Cache files will be dynamically created based on contracts from registry
let cacheFiles = {};

// Contract configuration - will be dynamically loaded from registry
let contracts = [];
let contractConfigs = {};

/**
* Initialize contracts from the registry
* Called during initializeGlobalContractMonitoring
*/
async function initializeContractsFromRegistry() {
try {
// Initialize registry if needed
if (!ContractRegistryService.initialized) {
await ContractRegistryService.initialize();
}

fs.writeFileSync(cacheFilePath, JSON.stringify(cacheSampleData, null, 2));
}
// Get all contracts from registry
const registryContracts = ContractRegistryService.getAllContracts();

// Filter to only include contracts that have both ID and EVM address
// and are in the monitoring list (jetvision, chat, challenges)
const monitoredContractNames = ['jetvision', 'chat', 'challenges'];
const filteredContracts = registryContracts.filter(c =>
monitoredContractNames.includes(c.name) && c.contractId && c.contractEvm
);

cacheFiles[cacheFile] = cacheFilePath;
}
// Update contracts array
contracts = filteredContracts.map(c => c.name);

// Update contract configs
contractConfigs = {};
filteredContracts.forEach(contract => {
contractConfigs[contract.name] = {
contractId: contract.contractId,
contractEvm: contract.contractEvm
};

// Contract configuration
const contracts = ['jetvision', 'chat', 'challenges'];
const contractConfigs = {
jetvision: {
contractId: process.env.JETVISION_CONTRACT_ID,
contractEvm: process.env.JETVISION_CONTRACT_EVM
},
chat: {
contractId: process.env.CHAT_CONTRACT_ID,
contractEvm: process.env.CHAT_CONTRACT_EVM
},
challenges: {
contractId: process.env.CHALLENGES_CONTRACT_ID,
contractEvm: process.env.CHALLENGES_CONTRACT_EVM
// Initialize data structures for this contract
globalPeerCounts[contract.name] = globalPeerCounts[contract.name] || 0;
globalAllDevices[contract.name] = globalAllDevices[contract.name] || [];
contractLoadingStates[contract.name] = contractLoadingStates[contract.name] || false;

// Setup cache file for this contract
const cacheFileName = `contract-data-${contract.name}.json`;
const cacheFilePath = path.join(cacheDir, cacheFileName);

if (!fs.existsSync(cacheFilePath)) {
let cacheSampleData = {};
const sampleCachePath = path.join(__dirname, 'cache', cacheFileName);

if (fs.existsSync(sampleCachePath)) {
cacheSampleData = JSON.parse(fs.readFileSync(sampleCachePath, 'utf-8'));
}

fs.writeFileSync(cacheFilePath, JSON.stringify(cacheSampleData, null, 2));
}

cacheFiles[contract.name] = cacheFilePath;
});

console.log(`[GlobalContractMonitor] Initialized ${contracts.length} contracts from registry:`, contracts);
return true;
} catch (error) {
console.error('[GlobalContractMonitor] Failed to initialize contracts from registry:', error);

// Fallback to hardcoded contracts if registry fails
console.warn('[GlobalContractMonitor] Falling back to environment variable contracts');
contracts = ['jetvision', 'chat', 'challenges'];
contractConfigs = {
jetvision: {
contractId: process.env.JETVISION_CONTRACT_ID,
contractEvm: process.env.JETVISION_CONTRACT_EVM
},
chat: {
contractId: process.env.CHAT_CONTRACT_ID,
contractEvm: process.env.CHAT_CONTRACT_EVM
},
challenges: {
contractId: process.env.CHALLENGES_CONTRACT_ID,
contractEvm: process.env.CHALLENGES_CONTRACT_EVM
}
};

// Initialize data structures for fallback contracts
contracts.forEach(contractName => {
globalPeerCounts[contractName] = 0;
globalAllDevices[contractName] = [];
contractLoadingStates[contractName] = false;

const cacheFileName = `contract-data-${contractName}.json`;
const cacheFilePath = path.join(cacheDir, cacheFileName);

if (!fs.existsSync(cacheFilePath)) {
fs.writeFileSync(cacheFilePath, JSON.stringify({}, null, 2));
}

cacheFiles[contractName] = cacheFilePath;
});

return false;
}
};
}

// Load cached contract data for a specific contract
function loadCachedContractData(contract) {
Expand Down Expand Up @@ -190,6 +246,9 @@ async function initializeGlobalContractMonitoring() {

console.log('Initializing global contract monitoring service for all contracts...');

// Step 0: Initialize contracts from registry
await initializeContractsFromRegistry();

// Step 1: Load cached data immediately for fast startup
const hasCachedData = {};
contracts.forEach(contract => {
Expand Down
Loading
Loading