UNPKG

@broxus/locklift-network

Version:

In-memory TVM-blockchain emulator for locklift

260 lines (259 loc) 10.3 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 emulator_1 = require("@tychosdk/emulator"); const core_1 = require("@ton/core"); const utils_1 = require("./utils"); const messageComparator = (a, b) => everscale_inpage_provider_1.LT_COLLATOR.compare(a.lt || "0", b.lt || "0"); const emptyShardAccount = (0, core_1.beginCell)() .store((0, core_1.storeShardAccount)({ account: null, lastTransactionHash: 0n, lastTransactionLt: 0n, })) .endCell() .toBoc() .toString("base64"); class LockliftExecutor { transport; accountFetcherCallback; state = {}; snapshots = {}; nonce = 0; blockchainConfig; globalId; clock; tychoExecutor; blockchainLt = 1000n; 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), libs: core_1.Dictionary.empty(core_1.Dictionary.Keys.BigInt(256), core_1.Dictionary.Values.Cell()), }; // set this in order to pass standalone-client checks this.state.accounts[constants_1.ZERO_ADDRESS.toString()] = (0, utils_1.bocFromShardAccount)({ account: null, lastTransactionHash: 0n, lastTransactionLt: 0n, }); this.state.accounts[constants_1.GIVER_ADDRESS] = (0, utils_1.bocFromShardAccount)((0, utils_1.shardAccountFromBoc)(nt.makeFullAccountBoc(constants_1.GIVER_BOC), 0n)); } async initialize() { const config = await this.transport.getBlockchainConfig(); this.blockchainConfig = core_1.Cell.fromBase64(config.boc).asSlice().loadRef().toBoc().toString("base64"); this.globalId = Number(config.globalId); this.tychoExecutor = await emulator_1.TychoExecutor.create(); } setClock(clock) { if (this.clock !== undefined) throw new Error("Clock already set"); this.clock = clock; } _setAccount1(address, boc) { this.state.accounts[address.toString()] = boc; } _setLibrary(key, cell) { this.state.libs.set(key, cell); } setAccount(address, boc, type) { const newBoc = type === "accountStuffBoc" ? nt.makeFullAccountBoc(boc) : boc; this.state.accounts[address.toString()] = (0, utils_1.bocFromShardAccount)((0, utils_1.shardAccountFromBoc)(newBoc, 0n)); } async _getAccount(address) { return this.state.accounts[address.toString()]; // || // this.accountFetcherCallback?.(address instanceof Address ? address : new 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 Object.entries(this.state.accounts).reduce((acc, next) => { const [address, account] = next; const fullContractState = nt.parseShardAccountBoc(account); if (!fullContractState) { return acc; } acc[address] = fullContractState; return acc; }, {}); } 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)) || emptyShardAccount; const messageCell = core_1.Cell.fromBase64(message.boc); const now = Math.floor(this.clock.nowMs / 1000); let res = await this.tychoExecutor.runTransaction({ config: this.blockchainConfig, message: messageCell, lt: this.blockchainLt, shardAccount: receiverAcc, now, libs: this.state.libs.size > 0 ? (0, core_1.beginCell)().storeDictDirect(this.state.libs).endCell() : null, debugEnabled: true, randomSeed: null, verbosity: "short", ignoreChksig: true, }); if (!res.result.success) { console.log("Error in executor: ", res.result.error); return; } const decodedTx = nt.decodeRawTransaction(res.result.transaction); let trace = []; if (decodedTx.description.aborted) { // run 1 more time with trace on res = await this.tychoExecutor.runTransaction({ config: this.blockchainConfig, message: messageCell, lt: this.blockchainLt, shardAccount: receiverAcc, now, libs: this.state.libs.size > 0 ? (0, core_1.beginCell)().storeDictDirect(this.state.libs).endCell() : null, debugEnabled: true, randomSeed: null, verbosity: "full_location_stack_verbose", ignoreChksig: true, }); if (res.result.success && res.result.vmLog) { trace = (0, utils_1.parseBlocks)(res.result.vmLog); } } if (res.logs || res.debugLogs) { console.log("Debug logs: ", res.debugLogs); } if (!res.result.success) { console.log("Error in executor: ", res.result.error); return; } this.blockchainLt += 1000n; if (res.result.shardAccount) { if (message.dst?.startsWith("-1")) { const loadedShardAccount = (0, core_1.loadShardAccount)(core_1.Cell.fromBase64(res.result.shardAccount).asSlice()).account; if (loadedShardAccount?.storage.state.type === "active") { const libraries = loadedShardAccount.storage.state.state.libraries; libraries?.keys().map(el => { const lib = libraries.get(el); if (lib && lib.public && lib.root) { this._setLibrary(el, lib.root); } }); } } this._setAccount1(message.dst, res.result.shardAccount); this.saveTransaction(decodedTx, trace); decodedTx.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;