Query extras
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 api.query
interfaces.
#
State at a specific blockQuite 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 -
The .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)
#
State for a range of blocksIn 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 -
#
Map keys & entriesWhen 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
api.query.staking.validators(validatorId: AccountId)
viaentries
, thekey.args
would be[AccountId]
- if we are querying
api.query.staking.erasStakers(era: EraIndex, validatorId: AccountId)
viaentries
, thekey.args
would be[EraIndex, AccountId]
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) -
#
State entriesIn 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.
#
Entry metadataIt 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 -
The section
& 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.
The 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.
#
Let's transact already!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.