@activeledger/activeprotocol
Version:
Underlying protocol which handles consensus and the smart contract virtual machine of Activeledger
412 lines • 14.8 kB
JavaScript
import * as events from "events";
import { ActiveOptions } from "@activeledger/activeoptions";
import { EventEngine } from "@activeledger/activequery";
import { ActiveLogger } from "@activeledger/activelogger";
import { ActiveCrypto } from "@activeledger/activecrypto";
import { NodeVM, VMScript } from "@activeledger/vm2";
import { setTimeout } from "timers";
import * as fs from "fs";
import { EventEmitter } from "events";
import { createInterface } from "readline";
export class VirtualMachine extends events.EventEmitter {
constructor(selfHost, secured, db, dbev) {
super();
this.selfHost = selfHost;
this.secured = secured;
this.db = db;
this.dbev = dbev;
this.scriptFinishedExec = false;
this.emitter = new EventEmitter();
this.listenForVolatile();
this.listenForFetch();
}
initialiseVirtualMachine(extraBuiltins, extraExternals, extraMocks) {
let toolkitAvailable = true;
try {
require.resolve("@activeledger/activetoolkits");
}
catch (error) {
toolkitAvailable = false;
}
let external = ["@activeledger/activecontracts"];
let builtin = ["buffer"];
let mock = {};
if (toolkitAvailable) {
external.push("@activeledger/activeutilities", "@activeledger/activetoolkits");
builtin.push("http", "https", "url", "zlib");
}
if (extraExternals) {
external = [...external, ...extraExternals];
}
if (extraBuiltins) {
builtin = [...builtin, ...extraBuiltins];
}
if (extraMocks) {
extraMocks.forEach((libPackage) => {
mock[libPackage] = MockBuiltinSecurity;
});
}
this.virtual = new NodeVM({
sandbox: {
logger: ActiveLogger,
crypto: ActiveCrypto,
secured: this.secured,
self: this.selfHost,
},
require: {
context: "sandbox",
builtin,
external,
mock,
},
wasm: false,
});
const script = new VMScript(fs.readFileSync(__dirname + "/vmscript.js", "utf-8"));
this.virtualInstance = this.virtual.run(script);
}
getActivityStreamsFromVM(umid) {
let activities = this.virtualInstance.getActivityStreams(umid);
let streams = Object.keys(activities);
let i = streams.length;
let contractData;
contractData = this.virtualInstance.getContractData(umid);
let exported = [];
if (contractData) {
exported.push(contractData);
}
while (i--) {
if (activities[streams[i]].updated) {
exported.push(activities[streams[i]].export2Ledger(this.contractReferences[umid].key));
}
}
return exported;
}
destroy(umid) {
this.virtualInstance.destroy(umid);
if (this.contractReferences && this.contractReferences[umid]) {
delete this.contractReferences[umid];
}
}
getInternodeCommsFromVM(umid) {
return this.virtualInstance.getInternodeComms(umid);
}
clearingInternodeCommsFromVM(umid) {
return this.virtualInstance.clearInternodeComms(umid);
}
getReturnContractData(umid) {
return this.virtualInstance.returnContractData(umid);
}
getThrowsFromVM(umid) {
return this.virtualInstance.throwFrom(umid);
}
getInputs(umid) {
return this.contractReferences[umid].inputs;
}
initialise(payload, contractName) {
return new Promise(async (resolve, reject) => {
if (!this.contractReferences) {
this.contractReferences = {};
}
this.contractReferences[payload.umid] = {
contractName,
contractLocation: payload.contractLocation,
inputs: payload.inputs,
tx: payload.transaction,
key: payload.key,
};
this.event = new EventEngine(this.dbev, payload.transaction.$contract);
try {
this.virtualInstance.initialiseContract(payload, this.event, this.emitter);
if (payload.transaction.$namespace === "default") {
this.virtualInstance.setSysConfig(payload.umid, JSON.stringify(ActiveOptions.fetch(false)));
}
this.maxTimeout = new Date();
this.maxTimeout.setMilliseconds(ActiveOptions.get("contractMaxTimeout", 20) * 60 * 1000);
resolve();
}
catch (e) {
if (e instanceof Error) {
reject(await this.catchException(e, payload.umid));
}
}
});
}
setPhase(phase) {
if (this.event) {
this.event.setPhase(phase);
}
}
read(umid, readMethod) {
return new Promise(async (resolve, reject) => {
this.scriptFinishedExec = false;
this.setPhase("read");
this.checkTimeout(readMethod, () => {
reject("VM Error : Read phase timeout");
}, umid);
try {
resolve(await this.virtualInstance.runRead(umid, readMethod));
}
catch (error) {
ActiveLogger.debug(error, `VM Contract Read - Error`);
if (error instanceof Error) {
reject(await this.catchException(error, umid));
}
else {
reject(error);
}
}
finally {
this.scriptFinishedExec = true;
}
});
}
verify(sigless, umid) {
return new Promise(async (resolve, reject) => {
this.scriptFinishedExec = false;
this.setPhase("verify");
this.checkTimeout("verify", () => {
reject("VM Error : Verify phase timeout");
}, umid);
try {
await this.virtualInstance.runVerify(umid, sigless);
resolve(true);
}
catch (error) {
ActiveLogger.debug(error, `VM Contract Verify - Error`);
if (error instanceof Error) {
reject(await this.catchException(error, umid));
}
else {
reject(error);
}
}
finally {
this.scriptFinishedExec = true;
}
});
}
vote(nodes, umid) {
return new Promise(async (resolve, reject) => {
this.incMarshel(nodes, umid);
this.scriptFinishedExec = false;
this.setPhase("vote");
this.checkTimeout("vote", () => {
reject("VM Error : Vote phase timeout");
}, umid);
try {
resolve(await this.virtualInstance.runVote(umid));
}
catch (error) {
ActiveLogger.debug(error, `VM Contract Vote - Error`);
if (error instanceof Error) {
reject(await this.catchException(error, umid));
}
else {
reject(error);
}
}
finally {
this.scriptFinishedExec = true;
}
});
}
commit(nodes, possibleTerritoriality = false, umid) {
return new Promise(async (resolve, reject) => {
this.incMarshel(nodes, umid);
this.scriptFinishedExec = false;
this.setPhase("commit");
this.checkTimeout("commit", () => {
reject("VM Error : Commit phase timeout");
}, umid);
try {
await this.virtualInstance.runCommit(umid, possibleTerritoriality);
resolve(true);
}
catch (error) {
ActiveLogger.debug(error, `VM Contract Commit - Error`);
if (error instanceof Error) {
reject(await this.catchException(error, umid));
}
else {
reject(error);
}
}
finally {
this.scriptFinishedExec = true;
}
});
}
reconcile(nodes, umid) {
return new Promise(async (resolve, reject) => {
try {
this.incMarshel(nodes, umid);
this.scriptFinishedExec = false;
this.setPhase("reconcile");
this.checkTimeout("reconcile", () => {
reject("VM Error : Reconcile phase timeout");
}, umid);
await this.virtualInstance.reconcile(umid);
resolve(true);
}
catch (error) {
ActiveLogger.debug(error, `VM Contract Reconcile - Error`);
if (error instanceof Error) {
reject(await this.catchException(error, umid));
}
else {
reject(error);
}
}
finally {
this.scriptFinishedExec = true;
}
});
}
postProcess(territoriality, who, umid) {
return new Promise(async (resolve, reject) => {
this.scriptFinishedExec = false;
this.setPhase("post");
this.checkTimeout("post", () => {
reject("VM Error : Post phase timeout");
}, umid);
try {
const postProcess = this.virtualInstance.postProcess(umid, territoriality, who);
if (this.contractReferences[umid].tx.$namespace == "default") {
if (this.virtualInstance.reloadSysConfig(umid)) {
ActiveLogger.info("Reloading Configuration Request");
this.emit("reload");
}
}
resolve(postProcess);
}
catch (error) {
if (error instanceof Error) {
reject(await this.catchException(error, umid));
}
else {
reject(error);
}
}
finally {
this.scriptFinishedExec = true;
}
});
}
incMarshel(nodes, umid) {
let keys = Object.keys(nodes);
let i = keys.length;
if (i) {
let comms = {};
let sendComms = false;
while (i--) {
if (nodes[keys[i]].incomms) {
comms[keys[i]] = nodes[keys[i]].incomms;
if (!sendComms)
sendComms = true;
}
}
if (sendComms) {
return this.virtualInstance.setInternodeComms(umid, comms, this.contractReferences[umid].key);
}
}
}
listenForVolatile() {
this.emitter.on("getVolatile", async (umid, streamId) => {
try {
const volatile = await this.db.get(`${streamId}:volatile`);
this.emitter.emit(`volatileFetched-${umid}${streamId}`, null, volatile);
}
catch (error) {
this.emitter.emit(`volatileFetched-${umid}${streamId}`, error);
}
});
}
listenForFetch() {
this.emitter.on("getStreamData", async (umid, streamId) => {
try {
const data = await this.db.get(streamId);
this.emitter.emit(`getStreamDataFetched-${umid}${streamId}`, null, data);
}
catch (error) {
this.emitter.emit(`getStreamDataFetched-${umid}${streamId}`, error);
}
});
}
checkTimeout(type, timedout, umid) {
setTimeout(() => {
if (!this.scriptFinishedExec) {
!this.hasBeenExtended(umid)
?
timedout()
:
this.checkTimeout(type, timedout, umid);
}
}, ActiveOptions.get("contractCheckTimeout", 10000));
}
hasBeenExtended(umid) {
let timeoutRequestTime = this.virtualInstance.getTimeout(umid);
if (timeoutRequestTime) {
if (this.maxTimeout > timeoutRequestTime &&
timeoutRequestTime > new Date()) {
return true;
}
}
return false;
}
async catchException(e, umid) {
var _a;
if (e.stack &&
umid &&
this.contractReferences &&
((_a = this.contractReferences[umid]) === null || _a === void 0 ? void 0 : _a.contractName)) {
const contract = this.contractReferences[umid].contractName;
const contractErrorLine = e.stack.match(new RegExp(`^.*${contract}.*$`, "m"));
if (contractErrorLine && contractErrorLine.length) {
const contractLastCallLine = contractErrorLine[0].trim();
const lastIndexFolder = contractLastCallLine.indexOf(contract);
let contractErrorInfo = contractLastCallLine.substring(lastIndexFolder, contractLastCallLine.length);
let line;
if (ActiveOptions.get("debug", false)) {
line = (await this.readNthLine(this.contractReferences[umid].contractLocation, parseInt(contractErrorInfo.substring(contractErrorInfo.indexOf(":") + 1, contractErrorInfo.lastIndexOf(":"))))).trim();
}
return {
error: e.message,
at: contractErrorInfo,
line,
};
}
else {
let msg = e.stack
.split("\n", 2)[1]
.trim()
.replace(/.*\(|\)/gi, "");
msg = contract + ":" + msg.substr(msg.indexOf(".js") + 4);
return {
error: e.message,
at: msg,
};
}
}
else {
return e.message;
}
}
async readNthLine(file, nthLine) {
const rl = createInterface({
input: fs.createReadStream(file),
});
let lineNumber = 0;
for await (const line of rl) {
lineNumber++;
if (lineNumber === nthLine) {
rl.close();
return line;
}
}
rl.close();
return "";
}
}
class MockBuiltinSecurity {
}
//# sourceMappingURL=vm.js.map