UNPKG

@broxus/locklift-network

Version:

In-memory TVM-blockchain emulator for locklift

185 lines (184 loc) 7.63 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LockliftExecutor = void 0; const nt = __importStar(require("nekoton-wasm")); const everscale_inpage_provider_1 = require("everscale-inpage-provider"); const heap_js_1 = require("heap-js"); const lodash_1 = __importDefault(require("lodash")); const constants_1 = require("./constants"); const messageComparator = (a, b) => everscale_inpage_provider_1.LT_COLLATOR.compare(a.lt || "0", b.lt || "0"); class LockliftExecutor { transport; accountFetcherCallback; state = {}; snapshots = {}; nonce = 0; blockchainConfig; globalId; clock; constructor(transport, accountFetcherCallback) { this.transport = transport; this.accountFetcherCallback = accountFetcherCallback; this.createInitialBlockchainState(); transport.setExecutor(this); } createInitialBlockchainState() { this.state = { accounts: {}, transactions: {}, msgToTransaction: {}, addrToTransactions: {}, traces: {}, messageQueue: new heap_js_1.Heap(messageComparator), }; // set this in order to pass standalone-client checks this.state.accounts[constants_1.ZERO_ADDRESS.toString()] = nt.parseFullAccountBoc(nt.makeFullAccountBoc(constants_1.GIVER_BOC)); this.state.accounts[constants_1.ZERO_ADDRESS.toString()].codeHash = constants_1.TEST_CODE_HASH; // manually add giver account this.state.accounts[constants_1.GIVER_ADDRESS] = nt.parseFullAccountBoc(nt.makeFullAccountBoc(constants_1.GIVER_BOC)); } async initialize() { const config = await this.transport.getBlockchainConfig(); this.blockchainConfig = config.boc; this.globalId = Number(config.globalId); } setClock(clock) { if (this.clock !== undefined) throw new Error("Clock already set"); this.clock = clock; } _setAccount(address, boc) { this.state.accounts[address.toString()] = nt.parseFullAccountBoc(boc); } setAccount(address, boc, type) { this.state.accounts[address.toString()] = nt.parseFullAccountBoc(type === "accountStuffBoc" ? nt.makeFullAccountBoc(boc) : boc); } async getAccount(address) { return (this.state.accounts[address.toString()] || this.accountFetcherCallback?.(address instanceof everscale_inpage_provider_1.Address ? address : new everscale_inpage_provider_1.Address(address)) .then(({ boc, type }) => { if (!boc) throw new Error("Account not found"); this.setAccount(address, boc, type); return this.state.accounts[address.toString()]; }) .catch(e => { console.error(`Failed to fetch account ${address.toString()}: ${e.trace}`); return undefined; })); } getAccounts() { return this.state.accounts; } getTxTrace(txId) { return this.state.traces[txId]; } saveTransaction(tx, trace) { this.state.transactions[tx.hash] = tx; this.state.msgToTransaction[tx.inMessage.hash] = tx.hash; this.state.addrToTransactions[tx.inMessage.dst] = [tx.hash].concat(this.state.addrToTransactions[tx.inMessage.dst] || []); this.state.traces[tx.hash] = trace; } getDstTransaction(msgHash) { return this.state.transactions[this.state.msgToTransaction[msgHash]]; } getTransaction(id) { return this.state.transactions[id]; } getTransactions(address, fromLt, count) { const result = []; for (const txId of this.state.addrToTransactions[address.toString()] || []) { const rawTx = this.state.transactions[txId]; if (Number(rawTx.lt) > Number(fromLt)) continue; result.push(rawTx); if (result.length >= count) return result; } return result; } saveSnapshot() { this.snapshots[this.nonce] = lodash_1.default.cloneDeep(this.state); // postincrement! return this.nonce++; } loadSnapshot(id) { if (this.snapshots[id] === undefined) { throw new Error(`Snapshot ${id} not found`); } this.state = this.snapshots[id]; } clearSnapshots() { this.snapshots = {}; } resetBlockchainState() { this.createInitialBlockchainState(); } // process all msgs in queue async processQueue() { while (this.state.messageQueue.size() > 0) { await this.processNextMsg(); } } // process msg with lowest lt in queue async processNextMsg() { const message = this.state.messageQueue.pop(); // everything is processed if (!message) return; const receiverAcc = await this.getAccount(message.dst); let res = nt.executeLocalExtended( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.blockchainConfig, receiverAcc ? nt.makeFullAccountBoc(receiverAcc.boc) : constants_1.EMPTY_STATE, message.boc, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion Math.floor(this.clock.nowMs / 1000), false, undefined, undefined, this.globalId, false); if ("account" in res && res.transaction.description.aborted) { // run 1 more time with trace on res = nt.executeLocalExtended( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.blockchainConfig, receiverAcc ? nt.makeFullAccountBoc(receiverAcc.boc) : constants_1.EMPTY_STATE, message.boc, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion Math.floor(this.clock.nowMs / 1000), false, undefined, undefined, this.globalId, true); } if ("account" in res) { this._setAccount(message.dst, res.account); this.saveTransaction(res.transaction, res.trace); res.transaction.outMessages.map((msg) => { if (msg.msgType === "ExtOut") return; // event this.enqueueMsg(msg); }); } } // push new message to queue enqueueMsg(message) { this.state.messageQueue.push(message); } } exports.LockliftExecutor = LockliftExecutor;