RPCs
Remote Procedure Calls, or RPCs, are a way for an external program (eg. a frontend) to communicate with a Substrate node. They are used for checking storage values, submitting transactions, and querying the current consensus authorities. Substrate comes with several default RPCs. In many cases it is useful to add custom RPCs to your node.
The RPC extensions builder
In order to connect custom RPCs you must provide a function known as an "RPC extension builder".
This function takes a parameter for whether the node should deny unsafe RPC calls, and returns
an IoHandler
that the node needs to create a
JSON RPC. For more context, read more by looking at the
RpcExtensionBuilder
trait API
documentation.
RPC types
RPCs can be interfaces to a node's consensus mechanisms, or interfaces with any outside user to submit transactions to the blockchain. In all cases, it's important to consider what endpoints RPCs expose.
Launch a node and run this command to see a full list of your node's RPC APIs:
curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "rpc_methods"}' http://localhost:9933/
Public RPCs
Substrate nodes contain a set of CLI flags that allow you to expose the RPC interfaces publicly, namely:
--ws-external
--rpc-external
Each have additional --unsafe-*
versions. By default the node will refuse to start if you try to expose
an RPC interface and run a validator node at the same time. --unsafe-*
flags allow to suppress this security measure.
Exposing RPC interfaces may open up a huge surface of attacks and has to be carefully reviewed.
There are quite a few RPC methods that can be used to control the node's behaviour and should never (or rarely) be exposed:
author_submitExtrinsic
- allows submitting transactions to local pool.author_insertKey
- allows inserting private keys to local keystore.author_rotateKeys
- session keys rotation.author_removeExtrinsic
- remove and ban extrinsic from the pool.system_addReservedPeer
- add reserved node.system_removeReservedPeer
- removed reserved node.
Other RPCs are not so much dangerous but can take a long time to execute, potentially blocking the client from syncing. These include:
storage_keys_paged
- get all the keys in the state with a particular prefix and pagination support.state_getPairs
- get all the keys in the state with a particular prefix together with their values.
These RPCs are declared by using the #[rpc(name = "rpc_method")]
macro, where "rpc_method
" would be the name of the function — author_submitExtrinsic
for example,
corresponding to submit_extrinsic
.
It's critical to filter out these kind of calls if the requests are coming from untrusted users. The way to do it is through a JSON-RPC proxy that is able to inspect calls and only pass allowed-set of APIs.
RPCs for remote_externalities
There exists a special type of using RPCs in the context of remote_externalities
.
This rpc_api
allows you to make
one-off RPC calls to a Substrate node, useful for testing purposes with tools like
try-runtime
for example.
Endpoints
When starting any Substrate node, these two endpoints are made available to you:
- HTTP Endpoint: http://localhost:9933/
- Websocket Endpoint: ws://localhost:9944/
Most of the Substrate front-end libraries and tools use the more powerful WebSocket endpoint to interact with the blockchain. Through WebSockets, you can subscribe to the chain states, such as events, and receive push notifications whenever changes in your blockchain occur.
To call the Metadata
endpoint, run this command alongside a running node:
curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "state_getMetadata"}' http://localhost:9933/
The return value of this command is not in human readable format. For that, it needs to use the SCALE codec encoding scheme.
Each storage item has a relative storage key associated to it which is used to query storage. This is how RPC endpoints know where to look.
Examples
state_getMetadata
RPC request:
function get_metadata_request(endpoint) {
let request = new Request(endpoint, {
method: 'POST',
body: JSON.stringify({
id: 1,
jsonrpc: '2.0',
method: 'state_getMetadata',
}),
headers: { 'Content-Type': 'application/json' },
})
return request
}
Naive text decoding:
function decode_metadata(metadata) {
return new TextDecoder().decode(util.hexToU8a(metadata))
}
state_getStorage
RPC request:
Request: {"id":1,"jsonrpc":"2.0","method":"state_getStorage",["{storage_key}"]}
Where storage_key
is a parameter generated by the name of a pallet, function and key (optionally):
function get_runtime_storage_parameter_with_key(
module_name,
function_name,
key
) {
// We use xxhash 128 for strings the runtime developer can control
let module_hash = util_crypto.xxhashAsU8a(module_name, 128)
let function_hash = util_crypto.xxhashAsU8a(function_name, 128)
// We use blake2 256 for strings the end user can control
let key_hash = util_crypto.blake2AsU8a(keyToBytes(key))
// Special syntax to concatenate Uint8Array
let final_key = new Uint8Array([
...module_hash,
...function_hash,
...key_hash,
])
// Return a hex string
return util.u8aToHex(final_key)
}