Blocks
The building blocks for each blockchain. As such there are a number of examples for working with blocks and headers, that could be useful.
How do I retrieve the header/extrinsic hash from blocks?
A block hash refers to the hash over the header, the extrinsic hash refers to the hash of the encoded extrinsic. Since all objects returned by the API implements the .hash => Hash
getter, we can simply use this to view the actual hash.
// returns Hash
const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
// returns SignedBlock
const signedBlock = await api.rpc.chain.getBlock(blockHash);
// the hash for the block, always via header (Hash -> toHex()) - will be
// the same as blockHash above (also available on any header retrieved,
// subscription or once-off)
console.log(signedBlock.block.header.hash.toHex());
// the hash for each extrinsic in the block
signedBlock.block.extrinsics.forEach((ex, index) => {
console.log(index, ex.hash.toHex());
});
How do I extract the block author?
The block author is encoded inside the consensus logs for the block. To extract, you need to decode the log (which the API does do) and then map the index of the validator to the list of session validators. This extraction is however available on the api derive for new head subscriptions, which returns an extended header with the author populated (assuming that the digest logs are known).
// subscribe to all new headers (with extended info)
api.derive.chain.subscribeNewHeads((header) => {
console.log(`#${header.number}: ${header.author}`);
});
For a single header only, the derives also contain a getHeader
, which once again returns a header extended with the author -
// retrieve the last header (hash optional)
const header = await api.derive.chain.getHeader();
console.log(`#${header.number}: ${header.author}`);
How do I view extrinsic information?
The transactions are included in a signed block as part of the extrinsics - some of these will be unsigned and generated by the block author and some of these may be submitted from external sources and be signed. (Some palettes do use unsigned transactions, so signed/unsigned is not an indication of origin). To retrieve the block and display the transaction information, we can do the following -
// no blockHash is specified, so we retrieve the latest
const signedBlock = await api.rpc.chain.getBlock();
// the information for each of the contained extrinsics
signedBlock.block.extrinsics.forEach((ex, index) => {
// the extrinsics are decoded by the API, human-like view
console.log(index, ex.toHuman());
const { isSigned, meta, method: { args, method, section } } = ex;
// explicit display of name, args & documentation
console.log(`${section}.${method}(${args.map((a) => a.toString()).join(', ')})`);
console.log(meta.documentation.map((d) => d.toString()).join('\n'));
// signer/nonce info
if (isSigned) {
console.log(`signer=${ex.signer.toString()}, nonce=${ex.nonce.toString()}`);
}
});
In the above .toHuman()
is used to format into a human-readable representation. You can inspect/extract specific fields from the decoded extrinsic as required, for instance ex.method.section
would return the pallete that executed this transaction.
How do I map extrinsics to their events?
While the blocks contain the extrinsics, the system event storage will contain the events and the details needed to allow for a mapping between. For events the phase
is an enum that would be isApplyExtrinsic
with the index in the cases where it refers to an extrinsic in a block. This index maps through the order of the extrinsics as found.
To perform a mapping between the two, we need information from both sources.
// no blockHash is specified, so we retrieve the latest
const signedBlock = await api.rpc.chain.getBlock();
const apiAt = await api.at(signedBlock.block.header.hash);
const allRecords = await apiAt.query.system.events();
// map between the extrinsics and events
signedBlock.block.extrinsics.forEach(({ method: { method, section } }, index) => {
// filter the specific events based on the phase and then the
// index of our extrinsic in the block
const events = allRecords
.filter(({ phase }) =>
phase.isApplyExtrinsic &&
phase.asApplyExtrinsic.eq(index)
)
.map(({ event }) => `${event.section}.${event.method}`);
console.log(`${section}.${method}:: ${events.join(', ') || 'no events'}`);
});
How do I determine if an extrinsic succeeded/failed?
This is an extension of the above example where extrinsics are mapped to their blocks. However in this example, we will look for specific extrinsic events, in this case the system.ExtrinsicSuccess
and system.ExtrinsicFailed
events. The same logic can be applied to inspect any other type of expected event.
// no blockHash is specified, so we retrieve the latest
const signedBlock = await api.rpc.chain.getBlock();
// get the api and events at a specific block
const apiAt = await api.at(signedBlock.block.header.hash);
const allRecords = await apiAt.query.system.events();
// map between the extrinsics and events
signedBlock.block.extrinsics.forEach(({ method: { method, section } }, index) => {
allRecords
// filter the specific events based on the phase and then the
// index of our extrinsic in the block
.filter(({ phase }) =>
phase.isApplyExtrinsic &&
phase.asApplyExtrinsic.eq(index)
)
// test the events against the specific types we are looking for
.forEach(({ event }) => {
if (api.events.system.ExtrinsicSuccess.is(event)) {
// extract the data for this event
// (In TS, because of the guard above, these will be typed)
const [dispatchInfo] = event.data;
console.log(`${section}.${method}:: ExtrinsicSuccess:: ${JSON.stringify(dispatchInfo.toHuman())}`);
} else if (api.events.system.ExtrinsicFailed.is(event)) {
// extract the data for this event
const [dispatchError, dispatchInfo] = event.data;
let errorInfo;
// decode the error
if (dispatchError.isModule) {
// for module errors, we have the section indexed, lookup
// (For specific known errors, we can also do a check against the
// api.errors.<module>.<ErrorName>.is(dispatchError.asModule) guard)
const decoded = api.registry.findMetaError(dispatchError.asModule);
errorInfo = `${decoded.section}.${decoded.name}`;
} else {
// Other, CannotLookup, BadOrigin, no extra info
errorInfo = dispatchError.toString();
}
console.log(`${section}.${method}:: ExtrinsicFailed:: ${errorInfo}`);
}
});
});