Polkadot consists in a peer-to-peer network of so-called clients that talk to each other. In order for the owner of a node to know what happens on Polkadot, they must then communicate with their node. This is done with what we call the JSON-RPC API. For example, PolkadotJS talks to a node using the JSON-RPC API.
Unfortunately, this JSON-RPC API has many flaws (more details below), and since the end of 2021 we have been trying to fix these flaws by creating a new JSON-RPC API. The most important part of this API, which is the chainHead_
-prefixed functions, is implemented in Substrate (and thus are in the official Polkadot client) and smoldot.
Website: Introduction
Repository: GitHub - paritytech/json-rpc-interface-spec
This post aims to be a reminder that this is curently in progress, and also to share information about this API. I have done this in the form of a Q&A, as I think that this is a good way to organize information.
If you are a JSON-RPC-client-side developer, for example if you are creating a UI, I strongly invite you to read this Q&A and the details of this new API (link to the website above), and maybe trying to use the new JSON-RPC API.
Q&A
General questions
Does this thing concern me?
If you are maintaining code that manually calls JSON-RPC functions, then you are concerned and you should read these information.
If you are a UI/frontend developer or parachain developer, then you are only indirectly concerned and you probably have to do nothing. However, I would still encourage you to get interested in this topic.
Is the new JSON-RPC API stable?
The new JSON-RPC API is not stable, and the details might still change. However, it is unlikely to change in major ways, and it should be possible to look at the list of commits of https://github.com/paritytech/json-rpc-interface-spec/ to follow the changes that happen.
Is this new JSON-RPC API usable?
More or less! It is implemented in Substrate and smoldot, and maybe on other implementations as well. At the time of publication of this text, however, Substrate hasnât backported some recent changes to the JSON-RPC specification and doesnât implement the archive
API. Similarly, smoldot doesnât implement everything.
Since the code is relatively new, there might be accidental mismatches between the specification and what Substrate and smoldot actually do.
For this reason, it would be appreciated for those who want to try the new JSON-RPC API to write their code according to the specification rather than by retro-engineering the data returned by Substrate or smoldot, as doing so would increase the chances of finding a mismatch.
If you notice such as mismatch, please open an issue in the Substrate or smoldot repository.
Since the functions are still marked as unstable, should JSON-RPC client developers wait for them to be stable before upgrading?
The functions are marked as unstable because they are still in the phase where JSON-RPC-clients-side developers should try using them and give feedback. I have personally tried to communicate this over the past year, but I havenât received much help in this communication, and no feedback has yet been received from any client-side developer.
There is a bit of chicken-and-egg problem, which is that JSON-RPC client developers wait for things to be stable in order to look into it, while JSON-RPC spec and server developers would appreciate feedback from client developers before stabilizing the API and code.
This is why this this forum post tries to raise awareness about this new API.
If no feedback was to be received, the plan would be to stabilize all the unstable JSON-RPC functions as-is despite their possible (unknown) flaws, and gather feedback for a version 2 instead.
Why is the specification repository owned by the paritytech
GitHub organization? Who is responsible for this JSON-RPC API?
The design of this new JSON-RPC API is essentially a one-man job (by me, the author of this post), with the support and benediction of the core engineering of Parity. It has received internal feedback and is approved by the Parity core engineering. This API was later implemented in Substrate by @lexnv, another Parity developer. I have left Parity at the end of 2021 but I am still working on this aspect (as you can see by the fact that this post exists) and I am still collaborating with Parity.
The JSON-RPC API is not technically part of the Polkadot specification, as there is no necessity to implement it in order to connect to Polkadot. A Polkadot client could for example implement a completely different JSON-RPC API if they wanted, or could directly integrate a UI without the need for a JSON-RPC layer.
For this reason, there is no straight-forward answer as to where this specification belongs and who owns it. Thatâs just a consequence of decentralization.
The specification was created in the paritytech
GitHub organization for pragmatic reasons, but it shouldnât be seen that Parity âownsâ this specification. I would personally like if it was moved to https://github.com/polkadot-fellows, but it is also unclear who, in polkadot-fellows
, would have the rights to merge pull requests and in general accept changes.
When will the legacy API be deprecated and removed?
My answer to this would be âas soon as realistically possibleâ.
Due to the law of maximum laziness, JSON-RPC-client-side developers will likely never update their code unless they are given the fear that their code will break if they donât. If the Substrate/Polkadot client waits for all JSON-RPC clients to have updated before deprecating the legacy functions, then this will basically never happen. This paragraph might sound snarky and sarcastic, but pragmatically speaking it is true.
My opinion is as soon as possible to start printing a warning in the logs if a legacy JSON-RPC function is called, which is something that smoldot already does. After the new JSON-RPC API is fully stabilized, throttle down the legacy JSON-RPC functions by adding some kind of sleep(5 seconds)
at the beginning of them. Then, a few months later, move to something like 30 seconds. Then, finally remove the functions.
Given that I am no longer at Parity, I can only suggest this path and not actually enforce it.
Why do we need a new JSON-RPC API?
The legacy/current JSON-RPC API suffers from several issues:
-
Many of the functions assume that the implementation is a full node, and light clients werenât taken into account when the legacy JSON-RPC API has been designed. A good example of this is maybe the
state_getKeysPaged
function. On a full node, this function is implemented by simply reading from the database and returning the result. On a light client, this function is implemented by querying all the keys of the given prefix from the networking, then filtering out the keys that arenât in the requested page. If a JSON-RPC client calls this function multiple times, like you are supposed to do, the light client downloads all the keys multiple times, which is incredibly wasteful. Several other functions such asstate_queryStorage
simply canât be implemented by a light client at all. -
The parameters and return value of some functions depend on the details of the runtime of the chain. For example, the
payment_queryInfo
function (which is used to determine the fees of a transaction) is implemented by calling a function from the runtime then decoding what the function returns and returning a JSON object containing the various fields. If the runtime was modified and this runtime function disappeared or returned something else, the JSON-RPC function implementation would break. Because of this, the Substrate/Polkadot client and the runtime must always use the same version, which defeats the point of having an upgradable runtime. -
Several legacy JSON-RPC functions arenât anti-DoS-friendly. Writing a JSON-RPC server that resists DoS attacks can be tricky, and, because we make use of the so-called subscriptions, cannot be done unless the JSON-RPC functions are designed appropriately. Unfortunately this isnât the case, and the Substrate JSON-RPC server has to rely on hacks such as disconnecting clients that seem too slow, which leads to bad user experience. You can read more here: https://paritytech.github.io/json-rpc-interface-spec/dos-attacks-resilience.html.
-
Most functions are badly documented, in particular when it comes to corner cases. For example, when asking for some information about a specific block, and the server doesnât know about this block, some functions return
null
while some others return an error. -
Many of the functions arenât load-balancer-friendly. Due to the logic of some of the JSON-RPC functions, load balancers currently have to rely on âpinningâ clients to specific servers. In other words, once a client has connected to the load balancer, all the requests that it sends have to be directed to the same server, otherwise the information that the client receives could be contradictory. Due to this pinning system, it is not possible to auto-downscale JSON-RPC servers without disconnecting clients. The new JSON-RPC API has been designed so that a load balancer can move a client from one server to another and thus shut down servers that it doesnât need anymore.
When Polkadot/Substrate was first started, the set of JSON-RPC functions was simply copy-pasted from Ethereum (given that their implementations already existed in the Parity Ethereum client), then expanded in a cowboy-y way without being given proper thoughts.
The new JSON-RPC API aims at cleaning up this aspect.
The new JSON-RPC API is very strict. Can I no longer add custom JSON-RPC functions to my node?
From what Iâve noticed in the wild, custom JSON-RPC functions can always be put in one of two categories:
- Functions that are custom to the logic of the runtime (for example interacting with contracts on a contracts chain). I would encourage you to replace these JSON-RPC functions with
chainHead_unstable_call
orarchive_unstable_call
(orstate_call
in the legacy API). - Functions that are used internally for debugging. It is completely okay to leave these functions on the node, provided that they are only used internally by the development team and not part of a publicly-available UI. I would however encourage you to use the new naming scheme and put
unstable
in their name in order to convey that they arenât part of the API.
If you have a custom JSON-RPC function that doesnât belong to one of these two categories, please raise an issue with your use case in the spec repo: https://github.com/paritytech/json-rpc-interface-spec/.
While nothing forces you to collaborate with this specification, keep in mind that creating this new JSON-RPC API isnât so much about solving a technical problem than it is about solving a social problem. Technically you can add any function to the server, but doing so is pointless if thereâs no client that calls it. And clients canât call a function if not all servers implement it or implement it in different ways.
If you distribute a server with custom functions, and distribute a client that calls these custom functions, you put onto the end user the burden of figuring out whether their client and server are compatible with each other in what might end up being what is common named âa clusterfuckâ. Creating a standard for the JSON-RPC API aims at avoiding this problem.
Should I learn the details of this JSON-RPC API?
If you are a UI developer, then you are not expected to directly use the JSON-RPC API. Instead, you are expected to use an intermediary-level library between your UI and the JSON-RPC server. This is already the case right now, as most UIs use PolkadotJS rather than manually send JSON-RPC requests.
This intermediary library is responsible for performing all the complicated aspects such as retrieving the metadata or watching storage items.
Contrary to the legacy JSON-RPC API, the new JSON-RPC API has been designed to be implementable by the node in an unopinionated way. When a request asks the node to do X, the node does precisely X. There is for instance no necessity for the server to cache information that it thinks might be requested later.
This means that all the JSON-RPC functions of the legacy API that were designed to be easy to use in a certain opinionated way have now disappeared. Instead, all these opinionated decisions are now found on the client side.
It is not possible to write a universal intermediary library that fits every use case (unless you turn that library into a huge overcomplicated monster, which isnât desirable either). For example, if you are writing a UI that simply follows some items in the storage, you might use a different intermediary library than if you query historical data or if you are watching the status of your own nodes. For this reason, there is room for several different client-side libraries.
If you are a node operator, you can use the JSON-RPC API in order to investigate and control your node. While some aspects of the API can be a bit complicated (such as following blocks, retrieving storage items, etc.), none of these complicated aspects concern the use case of a node operator. Functions such as sudo_sessionKeys_unstable_generate
or transaction_unstable_submitAndWatch
are rather simple to use.
Does PolkadotJS support the new JSON-RPC API?
Unfortunately no.
Given that PolkadotJS closely lies on top of the legacy JSON-RPC API, it is unlikely to ever fully transition to the new JSON-RPC API. For this reason, I would generally recommend against using PolkadotJS as an intermediary library when creating an application.
New light-client-friendly intermediary libraries (such as capi) need to be developed, and this Q&A is also targeted at potential library developers.
Unfortunately, and much to my disappointment, as far as I know no high-level library has yet committed to fully embracing the new JSON-RPC API at the moment.
Iâm reading the specification and I donât understand something/Iâve found something ambiguous
Please open an issue in the repository.
The specification is meant to be easy to understand, clear, and precise. If it is not the case, then it must be fixed.
Questions about the details of the new API
Can you explain the naming scheme of the new JSON-RPC functions?
All the new JSON-RPC functions are named like this: namespace_function
.
For example, in chainHead_unstable_follow
, the namespace is chainHead_unstable
, and the function name is follow
.
The namespaces currently in the API are: archive_unstable
, chainHead_unstable
, chainSpec_unstable
, rpc
, sudo_unstable
, sudo_sessionKeys_unstable
, and transaction_unstable
.
The word unstable
represents the version of the namespace. unstable
means that the functions implementation might change at any time and thus canât be relied upon. All the functions of the chainHead_unstable
namespace for example will be renamed to chainHead_v1
once weâre happy with their design. After that, their API canât be modified ever again.
All the functions within the same namespace can be grouped together and are isolatable as a group. For example, all the functions of chainHead_unstable
together serve a specific purpose (following the head of the chain) that doesnât require calling any function from a different namespace.
This is the reason why, for example, there exists chainSpec_unstable_genesisHash
but also chainHead_unstable_genesisHash
and archive_unstable_genesisHash
that all do the same thing.
Note that the namespace is chainHead_unstable
and not just chainHead
. This means that for example chainHead_v1
and chainHead_v2
(assuming there exists a v2 some day) donât interact with each other. A JSON-RPC client should either use only chainHead_v1
or only chainHead_v2
depending on what the server supports, and not both at the same time.
What does it mean when a JSON-RPC function is prefixed with sudo_
?
The namespaces that start with sudo
indicate that they contain functions that operate on one specific node and/or are about administering a node.
All JSON-RPC functions of the new API have been designed so that can be implemented by a load balancer that distributes requests between various load-balanced nodes, except for the ones that start with sudo_
.
For example, sudo_unstable_version
returns the version of the node that is being queried. From the point of view of a load balancer, this creates an ambiguity: should it returns its own version? Should it return the version of one of the nodes? In that case, do all the nodes have to use the same version? What if they donât? In order to solve this ambiguity, the load balancer simply shouldnât expose the sudo_unstable_version
function.
Furthermore, functions such as sudo_sessionKeys_unstable_generate
modify the node and thus shouldnât be callable by anyone but the owner of that node. For this reason, they also shouldnât be exposed by proxies and load balancers.
Which functions are safe and which are unsafe?
The legacy JSON-RPC API has a concept of âsafe functionâ and âunsafe functionâ. Unsafe functions are the ones that shouldnât be available to the public but only to the owner of a node.
Unfortunately, it is currently not clearly documented which functions are safe and which are unsafe.
In the new JSON-RPC API, all the functions that start with sudo_
are unsafe. All the others are safe.
Do all JSON-RPC servers support the entire JSON-RPC API?
No.
Some servers might support only some functions. However, the functions of a specific namespace_*
must either be all supported or not be supported at all. For example, it is forbidden for a server to support the chainHead_v1_follow
function but not the chainHead_v1_header
function. This is the reason why functions have been split into namespaces.
In practice:
- Light clients will not support the
archive_
functions, as they canât be implemented reliably. - Public-facing JSON-RPC servers and/or servers behind a load balancer will not support the
sudo_
functions, as these functions act upon specific servers. - While no such thing exists yet, it is possible to imagine an âarchive-onlyâ server that implements only the
archive_
functions but no thechainHead_
functions by reading from a database without being connected to the live chain.
At initialization, JSON-RPC clients should call the rpc_methods
function in order to determine the list of JSON-RPC functions that are supported. All servers must support the rpc_methods
function. In practice, JSON-RPC clients should most likely determine whether they are compatible or not and fail to initialize if they arenât, rather than try to adjust to every possible server type.
How do I do the equivalent of chain_subscribeNewHeads
, chain_subscribeAllHeads
, chain_subscribeFinalizedHeads
, or state_subscribeRuntimeVersion
in the new API?
These four functions have been grouped into one: chainHead_unstable_follow
.
The chainHead_unstable_follow
function starts a subscription, similarly to the other subscriptions-based JSON-RPC functions of the legacy API. Instead of yielding just one information (e.g. just the list of new blocks), this subscription yields everything important about the head of the chain together.
Rather than go into details here, I invite you to look at the documentation: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_unstable_follow.html#notifications-format.
If you are interested only in specific information, then simply discard what you are not interested in.
For example, if you are interested only in finalized blocks, then what you need is the finalizedBlockHash
field in the initialized
event and the finalized
events. Anything else can simply be ignored.
By yielding everything together, chainHead_unstable_follow
makes it possible for the JSON-RPC client to track, if desired, which runtime version is associated with which block or which block might potentially be finalized. This is something that isnât really possible to do properly with the legacy API.
How do I get the current best block, the current finalized block, or the current runtime version in the new API?
This can be done with chainHead_unstable_follow
.
If you want to know the current finalized block: subscribe, then wait for the first initialized
event, then unsubscribe (using chainHead_unstable_unfollow
).
If you want to know the current best block: subscribe, then wait for the first bestBlockChanged
event, then unsubscribe.
If you want to know the runtime version: subscribe with true
as parameter, then wait for the first initialized
event, then unsubscribe.
There is no direct equivalent to the legacy chain_getBlockHash(null)
, chain_getFinalizedHead
, and state_getRuntimeVersion
functions.
It would be correct to point out that if all you need is knowing the current best or finalized block or the runtime version, then subscribing and unsubscribing is less efficient compared to calling a JSON-RPC function that simply returns this information. This would be a problem if a client needs to repeatedly know the best or finalized block or the runtime version, however in that case they should instead simply stay subscribed.
What is this stop
event that chainHead_unstable_follow
can generate?
If a subscription to chainHead_unstable_follow
sends a notification that contains {"event":"stop"}
, it indicates that the subscription can no longer continue. The subscription is now dead and the JSON-RPC client must resubscribe.
This event is generated by the server is a variety of situations, such as a sudden influx of blocks on the node (which can happen if you were disconnected from the Internet for a long time then reconnect), the syncing subsystem of the node crashing and being restarted, or a load balancer killing all active subscriptions before shutting down one of the load-balanced nodes.
A subscription to chainHead_unstable_follow
always generates a consistent view of the chain. This means that JSON-RPC clients can (if desired) build a tree of the blocks that the subscription yields. This stop
event indicates that this consistent view canât be guaranteed anymore by the server, and thus the client must resubscribe and re-build this view from scratch.
Can you explain the parameter that is passed to chainHead_unstable_follow
?
The chainHead_unstable_follow
function accepts a boolean parameter indicating whether information about the runtime should be provided in the notifications in addition to the blocks.
If false
is provided, then it is not possible to call chainHead_unstable_call
with that subscription, and no information about the runtime is provided in the notifications.
From a purely logical point of view, passing true
only gives advantages.
On light clients, however, the initialization of the subscription will take up to a few seconds if true
is passed but only up to a few milliseconds if false
is passed. This is the case because light clients need to download additional data in order to provide information about the runtime.
For the best user experience, a UI can subscribe twice: once with false
and once with true
. Once the subscription with false
is initialized (which takes at most a few milliseconds) it can start displaying some information. Then, once the subscription with true
is initialized (at most a few seconds later), it can display the rest and unsubscribe from the first one.
While this is complicated to implement, UIs are expected to use some kind of library to communicate with a node rather than perform JSON-RPC calls manually.
What is the difference between the archive_
-prefixed JSON-RPC functions and the chainHead_
-prefixed JSON-RPC functions? Can you explain the blocks pinning system of chainHead_unstable_follow
?
Every function that allows querying information about a certain block exists in two versions: one prefixed with archive_
and one prefixed with chainHead_
. For example, to query the header of a block you can use either archive_unstable_header
or chainHead_unstable_header
.
Which one to use depends on the specific use case.
If you want to query information about blocks that are near the head of the chain, then use the chainHead_
-prefixed function. In order to use a chainHead_
-prefixed function, you must first be subscribed through chainHead_unstable_follow
. Only blocks that have been reported through notifications can be queried using one of the other chainHead_
functions. When you call the chainHead_
-prefixed function, you must also pass as parameter the identifier of the follow subscription (the value that chainHead_unstable_follow
has returned).
In other words, the chainHead_
functions can be seen as an extension to chainHead_unstable_follow
. When a block is reported through a follow subscription, the client can then query what it wants from this block.
Once a JSON-RPC client no longer needs to query anything about a block that was reported through chainHead_unstable_follow
, it must unpin the block by calling chainHead_unstable_unpin
. This indicates to the server that it can liberate the resources associated with that block.
Failing to unpin blocks can lead to the follow subscription terminating itself, in which case it must be reopened.
This API design, while a bit complicated, makes it possible for the JSON-RPC server to keep in memory the blocks that havenât been unpinned yet, and throw away entirely the blocks that have been unpinned. It removes the necessity for the server to try to magically guess which blocks to keep in its cache.
If instead you want to query information about any block, even for example blocks from years ago, then you must use the archive_
-prefixed functions.
The archive_
-prefixed functions are slower than their chainHead_
counterparts, as they most likely have to load information from the disk. Furthermore, and importantly, they are not available on light clients.
How do I use the archive_
-prefixed JSON-RPC functions?
The archive_
-prefixed functions allow querying information about a specific block.
The node has a âcurrent finalized block heightâ which can be retrieved by calling archive_unstable_finalizedHeight
. Any block whose height is inferior or equal to the number returned by this function can be queried in an idempotent way.
Any block whose height is superior to this value, in other words blocks that havenât been finalized yet, can maybe be queried. Querying works but might unexpectedly stop working as the block might disappear from the nodeâs storage.
Keep in mind that archive_
-prefixed functions arenât meant to be used for querying blocks that are near the head of the chain. It is still allowed to query recent blocks, but this is expected to be useful mostly for manual debugging and in situations where finality is stuck rather than under normal operation. Please use chainHead_
-prefixed functions if you are interested in the head of the chain.
Note that archive_unstable_finalizedHeight
isnât a subscription but a simple function that returns a value. A JSON-RPC client is expected to call this function once at initialization and/or every 5 minutes or so, rather than continuously. Again, the use case of the archive_
-prefixed functions is query the archive of the chain rather than anything recent.