@substrate/api-sidecar
Version:
REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.
149 lines • 6.85 kB
JavaScript
;
// Copyright 2017-2025 Parity Technologies (UK) Ltd.
// This file is part of Substrate API Sidecar.
//
// Substrate API Sidecar is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
Object.defineProperty(exports, "__esModule", { value: true });
exports.BlocksTraceService = void 0;
const http_errors_1 = require("http-errors");
const AbstractService_1 = require("../AbstractService");
const trace_1 = require("./trace");
/**
* Substrate tracing targets. These match targets by prefix. To learn more consult
* the [`Params` section][1] of the RPC docs. These are the same as the defaults
* for the RPC.
*
* [1]: https://github.com/paritytech/substrate/blob/aba876001651506f85c14baf26e006b36092e1a0/client/rpc-api/src/state/mod.rs#L206
*/
const DEFAULT_TARGETS = 'pallet,frame,state';
/**
* These are the keys we pass as an arg to the RPC to filter events. We will get
* storage events where they have a key whose prefix matches one of these.
*
* Equivalent of the storage keys for :extrinsic_index & frame_system::Account.
* These are the storage keys we use to filter events to reduce payload size and
* computational complexity for processing the data. For creating operations we
* only need events for storage Get/Put to these two storage items.
*
* Note: frame_system::Account is the storage prefix for items in the storage map. In the
* storage events we will get the actual entries of the map and use the key suffix
* to extract the address.
* ref: https://github.com/paritytech/substrate/blob/a604906c340c90e22fb20a8d77bcb3fee86c73c1/frame/system/src/lib.rs#L530-L538
* learn about transparent keys: https://www.shawntabrizi.com/substrate/transparent-keys-in-substrate/
*
* Note: :extrinisc_index is the key for a single `u32` value that gets updated
* during block execution to reflect the index of the current extrinsic being executed.
* ref: https://github.com/paritytech/substrate/blob/c93ef27486e5f14696e5b6d36edafea7936edbc8/primitives/storage/src/lib.rs#L169
*/
const DEFAULT_KEYS = '3a65787472696e7369635f696e646578,26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9';
class BlocksTraceService extends AbstractService_1.AbstractService {
/**
* Get the state traces for a block.
*
* @param hash `BlockHash` to get traces at.
*/
async traces(hash) {
const { api } = this;
const [{ number }, traceResponse] = await Promise.all([
api.rpc.chain.getHeader(hash),
api.rpc.state.traceBlock(hash, DEFAULT_TARGETS, DEFAULT_KEYS, null),
]);
if (traceResponse.isTraceError) {
throw new http_errors_1.InternalServerError(`${traceResponse.asTraceError.toString()}`);
}
else {
const formattedTrace = BlocksTraceService.formatBlockTrace(traceResponse.asBlockTrace);
return {
at: {
hash,
height: number.unwrap().toString(10),
},
storageKeys: formattedTrace.storageKeys,
tracingTargets: formattedTrace.tracingTargets,
events: formattedTrace.events,
spans: formattedTrace.spans.sort((a, b) => a.id - b.id),
};
}
}
/**
* Get the balance changing operations induced by a block.
*
* @param hash `BlockHash` to get balance transfer operations at.
* @param historicApi ApiDecoration used to retrieve the correct registry
* @param includeActions whether or not to include `actions` field in the response.
*/
async operations(hash, historicApi, includeActions) {
const { api } = this;
const [{ block }, traceResponse] = await Promise.all([
// Note: this should be getHeader, but the type registry on chain_getBlock is the only
// one that actually has the historical types. https://github.com/polkadot-js/api/issues/3487
api.rpc.chain.getBlock(hash),
api.rpc.state.traceBlock(hash, DEFAULT_TARGETS, DEFAULT_KEYS, null),
]);
if (traceResponse.isTraceError) {
throw new http_errors_1.InternalServerError(`${traceResponse.asTraceError.toString()}`);
}
else {
const trace = new trace_1.Trace(api, BlocksTraceService.formatBlockTrace(traceResponse.asBlockTrace), historicApi.registry);
const { operations, actions } = trace.actionsAndOps();
return {
at: {
hash,
height: block.header.number.unwrap().toString(10),
},
operations,
actions: includeActions ? actions : undefined,
};
}
}
/**
* Format the response to the `traceBlock` RPC. Primarily used to normalize data
* for `Trace`. This essentially should return something that closesly resembles
* the raw RPC JSON response result.
*
* @param blockTrace Polkadot-js BlockTrace
*/
static formatBlockTrace(blockTrace) {
const events = blockTrace.events.map(({ parentId, target, data: { stringValues } }) => {
const formattedStringValues = [...stringValues.entries()].reduce((acc, [k, v]) => {
acc[k.toString()] = v.toString();
return acc;
}, {});
return {
parentId: parentId.isSome ? parentId.unwrap().toNumber() : null,
target: target.toString(),
data: {
stringValues: formattedStringValues,
},
};
});
const spans = blockTrace.spans.map(({ id, name, parentId, target, wasm }) => {
return {
id: id.toNumber(),
parentId: parentId.isSome ? parentId.unwrap().toNumber() : null,
name: name.toString(),
target: target.toString(),
wasm: wasm.toJSON(),
};
});
return {
storageKeys: blockTrace.storageKeys.toString(),
tracingTargets: blockTrace.tracingTargets.toString(),
events,
spans,
};
}
}
exports.BlocksTraceService = BlocksTraceService;
//# sourceMappingURL=BlocksTraceService.js.map