In previous sections we took a walk through queries, showing how to use one-shot queries, how to subscribe to results and how to combine multiple queries into one. This section will aim to extend that knowledge showing some other features and utilities that are available on the
Quite often it is useful (taking pruning into account, more on this later) to retrieve the state at a specific block. For instance we may wish to retrieve the current balance as well as the balance at a previous block for a specific account -
In the above example, we introduce the
.at(<hash>[, ...params]): Type query. For all
.at queries, the first parameter is always the block hash at which we want to make the query, in our example we use both the last retrieved block and the parent thereof. The params are optional as per the type of query made, for instance to retrieve the timestamp for a previous block, it would be -
.at queries are all single-shot, i.e. there are no subscription option to these, since the state for a previous block should be static. (This is true to a certain extent, i.e. when blocks have been finalized).
An additional point to take care of (briefly mentioned above), is state pruning. By default a Polkadot/Substrate node will only keep state for the last 256 blocks, unless it is explicitly run in archive mode. This means that querying state further back than the pruning period will result in an error returned from the Node. (Generally most public RPC nodes only run with default settings, which includes aggressive state pruning)
In addition to the
.at queries, you can also query state starting at a specific historic block and up to either a specified or the latest blocks. This is done via the
.range([from, to?], <...opt params>): [Hash, Type] query. As an example -
When working maps and double-maps, it is possible to retrieve a list of all the keys and entries for the map. For this we can use the
.entries(<args>): [StorageKey, Type] queries. For example we may want to know the current list of validator exposures at a current era in the staking module -
To understand the usage of the
key.args, you need to understand that map/doublemap keys are stored alongside their lookups. This means that the raw key has hashed parts as well as the raw data. The API will decode the keys and provide the raw key arguments in args. This would mean -
- if we are querying
- if we are querying
api.query.staking.erasStakers(era: EraIndex, validatorId: AccountId)via
the same applies to
.keys() - here the list of keys also have the decoded args, as specified. You can think of
.args as a tuple with the same types as the types required to retrieve a single entry in the map.
In the first example we are querying a double-map, so we supply 1 argument. No arguments on double-maps will be very costly, retrieving all the eras and associated entries. In the same way as above we can simply do
.keys(activeEra.index): StorageKey to retrieve all the keys here, including the individual keys args (available on maps with decodable hashing functions) -
In addition to using
api.query to make actual on-chain queries, it can also be used to retrieve some information on the state entries. For instance to retrieve both the hash and size of an existing entry, we can make the following calls -
As per the previous examples, the params here apply explicitly to the actual needed values to identify an entry. As with
.at queries, there are no subscription versions for these queries, rather they are seen as one-shot values at a specific point in time.
It has been explained that the
api.query interfaces are decorated from the metadata. This also means that there is some information that we can gather from the entry, as decorated -
method is an indication of where it is exposed on the API. In addition the
meta holds an array with the metadata documentation for the entry.
key endpoint requires some explanation. In the chain state, the key values (identified by the module, method & params) are hashed and this is used as a lookup. So underlying a single-shot query would utilize the
api.rpc.state.getStorage entry, passing the output of
key (which is a hashed representation of the values). Apart from the hashing, the API also takes care of type formatting, handling optional values and merging results across multiple subscriptions.
At this point you are already burning to actually make some transactions. Making queries is cool, but just how do you actually submit transactions on-chain.