UNPKG

@activeledger/activeprotocol

Version:

Underlying protocol which handles consensus and the smart contract virtual machine of Activeledger

412 lines 14.8 kB
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