UNPKG

@57block/stellar-resource-usage

Version:

A library that provides convenient ways to monitor and analyze the resources consumed by smart contracts during execution

128 lines 5.43 kB
import { rpc, StrKey } from '@stellar/stellar-sdk'; import { printTableV2 } from '@/share'; import { handleTxToGetStats } from '@/tasks'; import { updateTxLimits, withExponentialBackoff } from './utils/utils'; export class StellarRpcServer extends rpc.Server { #hash = new Map(); transaction; simTxRes; storedStats = {}; constructor(serverURL, opts) { super(serverURL, opts); } async printTable() { const txPromises = Array.from(this.#hash).map(([hash]) => { return withExponentialBackoff(() => super.getTransaction(hash), (resp) => resp.status === rpc.Api.GetTransactionStatus.NOT_FOUND, 10); }); try { const allData = await Promise.all(txPromises); allData.forEach((data) => { const successDatum = data.find((response) => response.status === rpc.Api.GetTransactionStatus.SUCCESS); if (successDatum) { const { simTxRes, transaction } = this.#hash.get(successDatum.txHash); const stats = handleTxToGetStats(simTxRes, successDatum); this.storeTransactionStats(transaction, stats); } else { console.log('Failed to get transaction'); } }); } catch (error) { console.log('Failed to get transactions', error); } Object.keys(this.storedStats).forEach((contractId) => { printTableV2(contractId, this.storedStats); }); this.#hash.clear(); this.storedStats = {}; } async simulateTransaction(tx, resourceLeeway) { const simTxResponse = await super.simulateTransaction(tx, resourceLeeway); this.transaction = tx; this.simTxRes = simTxResponse; return simTxResponse; } async sendTransaction(transaction) { const sendTxRes = await super.sendTransaction(transaction); this.#hash.set(sendTxRes.hash, { sendTxRes, transaction: this.transaction, simTxRes: this.simTxRes }); this.transaction = undefined; this.simTxRes = undefined; return sendTxRes; } storeTransactionStats(tx, stats) { tx.operations.forEach((operation) => { // A tx can only perform 1 invokeHostFunction operation at most. if (operation.type === 'invokeHostFunction') { const contractId = StrKey.encodeContract(operation.func.invokeContract().contractAddress().value()); const funcName = operation.func.invokeContract().functionName(); if (!this.storedStats[contractId]) { this.storedStats[contractId] = {}; } if (!this.storedStats[contractId][funcName]) { this.storedStats[contractId][funcName] = [stats]; } else { this.storedStats[contractId][funcName].push(stats); } } }); } } const CONSTRUCTOR_FUNC = '__constructor'; export async function ResourceUsageClient(Client, options) { await updateTxLimits(); class ResourceUsage extends Client { contractId; storedStats = {}; constructor() { if (!options.contractId) { throw new Error('contractId is required'); } super(options); this.contractId = options.contractId; const contractFunNames = this.spec.funcs().map((fun) => fun.name().toString()); for (const funName of contractFunNames) { const originalFun = this[funName]; if (funName === CONSTRUCTOR_FUNC) { return; } this[funName] = async (...args) => { try { const assembledTx = await originalFun(...args); return { ...assembledTx, signAndSend: async () => { const res = await assembledTx.signAndSend(); const stats = handleTxToGetStats(assembledTx, res.getTransactionResponse); if (!this.storedStats[this.contractId]) { // init object this.storedStats[this.contractId] = {}; } if (!this.storedStats[this.contractId][funName]) { // init array this.storedStats[this.contractId][funName] = [stats]; } else { this.storedStats[this.contractId][funName].push(stats); } return res; }, }; } catch (error) { console.error(error); throw error; } }; } this.contractId = options.contractId; } printTable() { const storeData = this.storedStats; printTableV2(this.contractId, storeData); } } return new ResourceUsage(); } //# sourceMappingURL=main.js.map