@broxus/locklift-network
Version:
In-memory TVM-blockchain emulator for locklift
185 lines (184 loc) • 7.63 kB
JavaScript
"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;