Skip to content

Commit

Permalink
Feat: implemented the cross-chain multi-contract monitor #139
Browse files Browse the repository at this point in the history
  • Loading branch information
mojtaba-eshghie committed Jun 24, 2024
1 parent e283a88 commit cce5321
Showing 1 changed file with 54 additions and 93 deletions.
147 changes: 54 additions & 93 deletions monitor/multi-chain-monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,16 @@ let { getLastSimulationId, getPendingActivities } = require('@lib/dcr/info');
const { json } = require('express');
const { default: chalk } = require('chalk');


class Monitor extends EventEmitter {
constructor(configs) {
super();
this.executedActivities = [];
this.configs = configs;

this._status = 'IDLE';
this.setStatus('IDLE');


// Initialize the contract watcher, translator, and executor for this instance

// The following is just here as an example correct format of how configs should be passed to monitor
// let configs = {
// "contracts": [{
Expand All @@ -48,136 +45,106 @@ class Monitor extends EventEmitter {
// hasResponseRelation: true,
// };

// 2 watchers

this.contractWatcherA = new ContractWatcher(
this.configs.A.web3,
this.configs.A.contractAddress,
this.configs.A.contractName,
this.configs.A.contractABI
);
this.contractWatcherB = new ContractWatcher(
this.configs.B.web3,
this.configs.B.contractAddress,
this.configs.B.contractName,
this.configs.B.contractABI
);

// 2 translators
this.dcrTranslatorA = new DCRTranslator(
this.configs.A.contractABI,
this.configs.A.modelFunctionParams,
this.configs.A.web3
);
this.dcrTranslatorB = new DCRTranslator(
this.configs.B.contractABI,
this.configs.B.modelFunctionParams,
this.configs.B.web3
);

// Initialize the contract watchers and translators arrays
this.contractWatchers = [];
this.dcrTranslators = [];

// Loop through the contracts array in configs to create watchers and translators
this.configs.contracts.forEach((contractConfig, index) => {
const watcher = new ContractWatcher(
contractConfig.web3,
contractConfig.contractAddress,
contractConfig.contractName,
contractConfig.contractABI
);
this.contractWatchers.push(watcher);

const translator = new DCRTranslator(
contractConfig.contractABI,
contractConfig.modelFunctionParams,
contractConfig.web3
);
this.dcrTranslators.push(translator);
});

// executor
this.dcrExecutor = new DCRExecutor();

// Decide if we need the middleware for handling response relation semantics or not
this.hasResponse = this.configs.hasResponseRelation;



// The trace the monitor is watching can either be violating or not;
this.violating = false;

// Setting up a new simulation for the model
this.simulate().catch(err => {
monitorLogger.error(`Initialization failed: ${err}`);
this.status = 'ERROR';
this.status = 'ERROR';
});

}

async simulate() {
await this.dcrExecutor.makeSimulation(this.configs.modelId);
let simId = await getLastSimulationId(this.configs.modelId);
this.simId = simId;
this.setStatus('INITIALIZED');
monitorLogger.debug(`The simulation id for the monitor: ${this.simId}`);
} catch (error) {
this.setStatus('ERROR');
monitorLogger.error(`Simulation setup failed: ${error}`);
try {
await this.dcrExecutor.makeSimulation(this.configs.modelId);
let simId = await getLastSimulationId(this.configs.modelId);
this.simId = simId;
this.setStatus('INITIALIZED');
monitorLogger.debug(`The simulation id for the monitor: ${this.simId}`);
} catch (error) {
this.setStatus('ERROR');
monitorLogger.error(`Simulation setup failed: ${error}`);
}
}

setStatus(newStatus) {
if (this._status !== newStatus) {
this._status = newStatus;
this.emit('statusChange', this._status);
this.emit('statusChange', this._status);
}
}

start() {
const handleEventFromA = (tx) => this.handleContractEvent(tx, 'A');
const handleEventFromB = (tx) => this.handleContractEvent(tx, 'B');

// Bind the event handlers to this instance to ensure they have the correct `this` context
this.contractWatcherA.on('newTransaction', handleEventFromA);
this.contractWatcherA.on('error', this.handleError.bind(this));

// Start watching for contract events
this.contractWatcherA.startWatching();

this.contractWatcherB.on('newTransaction', handleEventFromB);
this.contractWatcherB.on('error', this.handleError.bind(this));

// Start watching for contract events
this.contractWatcherB.startWatching();
this.contractWatchers.forEach((watcher, index) => {
const handleEvent = (tx) => this.handleContractEvent(tx, index);
watcher.on('newTransaction', handleEvent);
watcher.on('error', this.handleError.bind(this));
watcher.startWatching();
});

this.setStatus('RUNNING');
}

async handleContractEvent(tx, source) {
async handleContractEvent(tx, index) {
let violates = false;
// Process the transaction and translate it into DCR activities
monitorLogger.debug(chalk.cyan(`config activities are: ${this.configs.activities}`));
let dcrActivities;
if (source == 'A') {
dcrActivities = this.dcrTranslatorA.getDCRFromTX(tx, this.configs.activities);
} else if (source == 'B') {
dcrActivities = this.dcrTranslatorB.getDCRFromTX(tx, this.configs.activities);
} else {
throw new Error("The source in multi-chain monitor is not recognizable");
}

let dcrActivities = this.dcrTranslators[index].getDCRFromTX(tx, this.configs.activities);

monitorLogger.debug(`dcrActivities: ${dcrActivities}`);
if (dcrActivities) {
const promises = dcrActivities.map(async activity => {
if (this.hasResponse) {
let pendingActivities = await getPendingActivities(this.configs.modelId, this.simId);
let pendingActivity = pendingActivities.find(a => a.id === activity["activityId"]);
if (pendingActivity){
if (pendingActivity) {
let deadline = pendingActivity.deadline;
if (deadline) {
// This is where we can use this deadlined pending relation;
deadline = new Date(deadline);
const now = new Date();
//const futureTime = new Date(now.getTime() + 24 * 60 * 60 * 1000); // Add 24 hours in milliseconds
if (now > deadline) {
violates = true;
} else {

}
}
}
}
}

await this.executeDCRActivity(activity, violates);
});
await Promise.all(promises); // Waits for all activities to finish executing
await Promise.all(promises);
this.writeMarkdownFile();
}
}


async executeDCRActivity(dcrActivity, violates) {
// Execute the DCR activity
// Here you would need the simulation ID and other details to execute the activity
// Assuming you have a method to get or create a simulation ID
try {
const result = await this.dcrExecutor.executeActivity(
this.configs.modelId,
Expand All @@ -186,9 +153,7 @@ class Monitor extends EventEmitter {
dcrActivity.dcrValue,
dcrActivity.dcrType
);
//monitorLogger.debug(`Activity execution result: ${result}`);
this.executedActivities.push(result);
//result.violation = violates;
result.violation = violates ? violates : result.violation;
this.violating = result.violation ? result.violation : this.violating;
monitorLogger.debug(`This time, violates is: ${violates}`);
Expand All @@ -199,21 +164,18 @@ class Monitor extends EventEmitter {
}

handleError(error) {
// Handle any errors that occur within the contract watcher
console.error('Error in ContractWatcher:', error);
}


writeMarkdownFile() {
const headers = ['Activity ID', 'Time', 'Violation', 'Simulation'];
const rows = this.executedActivities.map(activity => [
activity.name || '',
activity.time || '',
String(activity.violation) || '',
this.simId || ''
activity.name || '',
activity.time || '',
String(activity.violation) || '',
this.simId || ''
]);

// Construct markdown content from the headers and rows
const headerLine = `| ${headers.join(' | ')} |`;
const separatorLine = `| ${headers.map(() => '---').join(' | ')} |`;
const tableRows = rows.map(row => `| ${row.join(' | ')} |`).join('\n');
Expand All @@ -226,7 +188,6 @@ class Monitor extends EventEmitter {
fs.writeFileSync(filePath, markdownContent, 'utf8');
monitorLogger.debug(`Markdown file written: ${filePath}`);
}

}

module.exports = Monitor;
module.exports = Monitor;

0 comments on commit cce5321

Please sign in to comment.