@broxus/locklift-network
Version:
In-memory TVM-blockchain emulator for locklift
260 lines (259 loc) • 10.3 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 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;