@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
JavaScript
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