txtracer-core-test-dev
Version:
Core TxTracer library for collecting transaction information
133 lines • 6.97 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.retraceBaseTx = exports.retrace = void 0;
const methods_1 = require("./methods");
const buffer_1 = require("buffer");
/**
* Fully reproduce (re‑trace) a TON transaction inside a local TON Sandbox
* and return a structured report with VM logs, money flow, generated
* actions and other data.
*
* Workflow (high level)
* 1. Locate the base transaction (`txLink`) on either mainnet or testnet.
* 2. Load its shard‑block and the enclosing master‑block; extract
* `rand_seed`, config‑cell and the account snapshot *prior* to the block.
* 3. Re‑create the exact pre‑tx state by sequentially emulating all earlier
* account transactions that happened inside the same master‑block.
* 4. Emulate the target transaction itself with full VM verbosity.
* 5. Parse the resulting VM log (`c5`, action list, stack trace), compare the
* calculated state‑hash with the on‑chain one and assemble a
* `TraceResult` object for the caller.
*
* @param testnet When `true`, work against testnet endpoints; otherwise mainnet.
* @param txLink Hex hash that uniquely identifies the transaction to retrace.
*
* @returns A {@link TraceResult} containing:
* 1. an integrity flag `stateUpdateHashOk`
* 2. decoded an incoming message (sender / contract / amount)
* 3. balance delta, gas and fees
* 4. full emulated transaction (`emulatedTx`) with
* compute‑phase info, `c5`, action list and raw VM log
* 5. version of the sandbox executor used for emulation
*
* @throws Error If any network lookup fails; if the corresponding shard‑ /
* master‑block cannot be found; if deterministic replay
* diverges (TVM returns non‑success); or if state‑hash
* mismatch is detected after replay.
*/
const retrace = async (testnet, txLink) => {
const baseTx = await (0, methods_1.findBaseTxByHash)(testnet, txLink);
if (baseTx === undefined) {
throw new Error("Cannot find transaction info");
}
return (0, exports.retraceBaseTx)(testnet, baseTx);
};
exports.retrace = retrace;
/**
* Fully reproduce (re‑trace) a TON transaction inside a local TON Sandbox
* and return a structured report with VM logs, money flow, generated
* actions and other data.
*
* See {@link retrace} for the full description of the workflow.
*/
const retraceBaseTx = async (testnet, baseTx) => {
const [tx] = await (0, methods_1.findRawTxByHash)(testnet, baseTx);
// eslint bug
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (tx === undefined) {
throw new Error("Cannot find transaction info");
}
const shard = tx.block;
const block = await (0, methods_1.findShardBlockForTx)(testnet, tx);
if (block === undefined) {
throw new Error("Cannot find shard block for transaction");
}
// check if we correctly select master-block
if (block.root_hash !== shard.rootHash) {
throw new Error(`root_hash mismatch in mc_seqno getter: ${shard.rootHash} != ${block.root_hash}`);
}
// master‑block sequence number that references our shard‑block
const mcSeqno = block.masterchain_block_ref.seqno;
// pseudorandom seed from the master‑block header — TVM needs it for deterministic RNG
const randSeed = buffer_1.Buffer.from(block.rand_seed, "base64");
// load the complete master‑block object (includes the list of shard‑blocks)
const fullBlock = await (0, methods_1.findFullBlockForSeqno)(testnet, mcSeqno);
// determine the earliest logical‑time (lt) for this account in the same master‑block
const minLt = (0, methods_1.computeMinLt)(tx.tx, baseTx.address, fullBlock);
// find all transactions between the earliest one and the emulated transaction to correctly
// recreate all state before execution of the emulated transaction
const [ourTx, ...prevTxsInBlock] = await (0, methods_1.findAllTransactionsBetween)(testnet, baseTx, minLt);
prevTxsInBlock.reverse(); // allTxs contains txs from last to first one
// retrieve block config to pass it to emulator
const blockConfig = await (0, methods_1.getBlockConfig)(testnet, fullBlock);
const shardAccountBeforeTx = await (0, methods_1.getBlockAccount)(testnet, baseTx.address, fullBlock);
const [libs, loadedCode] = await (0, methods_1.collectUsedLibraries)(testnet, shardAccountBeforeTx, tx.tx);
// retrieve code cell if an account in active mode
const state = shardAccountBeforeTx.account?.storage.state;
const codeCell = state?.type === "active"
? (state.state.code ?? undefined)
: (tx.tx.inMessage?.init?.code ?? undefined);
const { emulatorVersion, emulate } = await (0, methods_1.prepareEmulator)(blockConfig, libs, randSeed);
// for the first transaction (executor doesn't know about last tx)
shardAccountBeforeTx.lastTransactionLt = 0n;
shardAccountBeforeTx.lastTransactionHash = 0n;
// emulator accepts and returns a shard account in base64 string, so prepare it for sending
const initialShardAccountBase64 = (0, methods_1.shardAccountToBase64)(shardAccountBeforeTx);
// first we emulate all transactions before to get a state that is equal to actual
// state in blockchain before transaction to emulate
const balance = shardAccountBeforeTx.account?.storage.balance.coins ?? 0n;
const { prevBalance, shardAccountBase64 } = await (0, methods_1.emulatePreviousTransactions)(balance, prevTxsInBlock, emulate, initialShardAccountBase64);
// and then we emulate the target transaction
const txRes = await emulate(ourTx, shardAccountBase64);
if (!txRes.result.success) {
throw new Error(`Transaction failed: ${txRes.result.error}`);
}
// extract out actions from the c5 control register
const { finalActions, c5 } = (0, methods_1.findFinalActions)(txRes.result);
const { sender, contract, amount, money, emulatedTx, computeInfo } = (0, methods_1.computeFinalData)(txRes.result, prevBalance);
// check if the emulated transaction hash is equal to one from the real blockchain
const stateUpdateHashOk = emulatedTx.stateUpdate.newHash.equals(ourTx.stateUpdate.newHash);
return {
stateUpdateHashOk,
codeCell: loadedCode ?? codeCell,
originalCodeCell: codeCell,
inMsg: {
sender,
contract,
amount,
},
money,
emulatedTx: {
utime: emulatedTx.now,
lt: emulatedTx.lt,
computeInfo,
executorLogs: txRes.logs,
actions: finalActions,
c5: c5,
vmLogs: txRes.result.vmLog,
},
emulatorVersion,
};
};
exports.retraceBaseTx = retraceBaseTx;
//# sourceMappingURL=runner.js.map