Working on Substrate? Must know about Runtime APIs.

Reading Time: 3 minutes

Hello Readers!! In this blog we will see what is a Runtime API and how we can create and use it.

Each Substrate node contains a runtime. The runtime contains the business logic of the chain. It defines what transactions are valid and invalid and determines how the chain’s state changes in response to transactions. To use runtime upgrades, the runtime is compiled to Wasm. Everything other than the runtime is referred as “outer node”, The Outer node does not compile to Wasm, it compiles only to native.

The outer node is responsible for handling peer discovery, transaction pooling, block and transaction gossiping, consensus, and answering RPC calls from the outside world. While performing these tasks, the outer node sometimes needs to query the runtime for information, or provide information to the runtime. A Runtime API facilitates this kind of communication between the outer node and the runtime. In this blog, we will write our own minimal runtime API.

Runtime API Working

Our Example

For this example, we will write a pallet called sum-storage with two storage items, both u32s.


#![allow(unused)]
fn main() {
decl_storage! {
	trait Store for Module<T: Config> as TemplateModule {
		Thing1 get(fn thing1): Option<u32>;
		Thing2 get(fn thing2): Option<u32>;
	}
}
}

Substrate already comes with a runtime API for querying storage values, which is why we can easily query our two storage values from a front-end. Our runtime API will provide a way for the outer node to query the runtime for this sum. Before we define the actual runtime API, let’s write a public helper function in the pallet to do the summing.


#![allow(unused)]
fn main() {
impl<T: Config> Module<T> {
	pub fn get_sum() -> u32 {
		Thing1::get() + Thing2::get()
	}
}
}

So far, nothing we’ve done is specific to runtime APIs. In the coming sections, we will use this helper function in our runtime API’s implementation.

Defining the API

The first step in adding a runtime API to your runtime is defining its interface using a Rust trait. This is done in the sum-storage/runtime-api/src/lib.rs file. This file can live anywhere you like, but because it defines an API that is closely related to a particular pallet, it makes sense to include the API definition in the pallet’s directory.

The code to define the API is quite simple, and looks almost like any old Rust trait. The one addition is that it must be placed in the decl_runtime_apis! macro. This macro allows the outer node to query the runtime API at specific blocks. Although this runtime API only provides a single function, you may write as many as you like.


#![allow(unused)]
fn main() {
sp_api::decl_runtime_apis! {
	pub trait SumStorageApi {
		fn get_sum() -> u32;
	}
}
}

Implementing the API

With our pallet written and our runtime API defined, we may now implement the API for our runtime. This happens in the main runtime aggregation file. In our case we’ve provided the api-runtime in runtimes/api-runtime/src/lib.rs.

As with defining the API, implementing a runtime API looks similar to implementing any old Rust trait with the exception that the implementation must go inside of the impl_runtime_apis! macro. Every runtime must use iml_runtime_apis! because the Core API is required. We will add an implementation for our own API alongside the others in this macro. Our implementation is straight-forward as it merely calls the pallet’s helper function that we wrote previously.


#![allow(unused)]
fn main() {
impl_runtime_apis! {
  // --snip--

  impl sum_storage_rpc_runtime_api::SumStorageApi<Block> for Runtime {
		fn get_sum() -> u32 {
			SumStorage::get_sum()
		}
	}
}
}

You may be wondering about the Block type parameter which is present here, but not in our definition. All runtime APIs have this type parameter to facilitate querying the runtime at arbitrary blocks. Read more about this in the docs for impl_runtime_apis!.

Calling the Runtime API

We’ve now successfully added a runtime API to our runtime. The outer node can now call this API to query the runtime for the sum of two storage values. Given a reference to a ‘client’ we can make the call like this.


#![allow(unused)]
fn main() {
let sum_at_block_fifty = client.runtime_api().get_sum(&50);
}

This blog was about defining and implementing a custom runtime API. Stay connected to explore such exciting topics!!

If you want to read more content like this? Subscribe to Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe to Rust Times Newsletter: https://bit.ly/2Vdlld7.


Knoldus-blog-footer-image

Written by 

Ayushi is a Software Developer having more than 1.5 year of experience in RUST. Her practice area is Rust and Go. She loves to solve daily coding challenges.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading