12

RPC to call a Runtime API easily in Substrate

 3 years ago
source link: https://blog.knoldus.com/rpc-to-call-a-runtime-api-easily-in-substrate/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

RPC to call a Runtime API easily in Substrate

Reading Time: 4 minutes

RPC or Remote Procedural Call in Substrate is a way to interact with a Substrate node. They can be used for checking storage values, submitting transactions or querying the current consensus authorities. Substrate is a next-generation framework for blockchain innovation. It comes with everything you need to build your blockchain. Substrate is a completely free and open-source project. It is built using Rust and WebAssembly. Rust is designed for creating fast and inherently safe software. The substrate is like a template from which you can make your own application-specific blockchain to add custom functionalities specific to one use case or group of use cases. Substrate comes with many default RPC calls. But we can add our own RPCs to communicate with the node. In this blog, we will see how we can make an RPC call to a Runtime API.

Intro to Polkadot & Substrate

The Runtime API

For those, who don’t know what are Runtime APIs in Substrate. Here is a short introduction to Runtime APIs.

Each Substrate node has a runtime. The runtime contains the business logic of the chain. It defines what transactions are valid or invalid and determines how the chain’s state changes in response to transactions. The runtime is compiled to Wasm to allows runtime upgrades. Everything other than the runtime is called the outer node. The outer node is responsible for answering RPC calls from the outside world.

First, let’s see what Runtime API will be called by our RPC. We suppose there is a pallet named trial-pallet and we have a Runtime API defined by the name get-something in it.

Defining the get-something RPC

We could have defined our RPC in a different folder but since it is closely related to our pallet, so making a crate inside the trial_pallet to store the RPC makes sense. Let us create the crate to store our RPCs by the name rpc. We will now add the rpc crate to the Cargo.toml of the pallet. To avoid any confusion, we will store our RPCs in pallets/trial_pallet/rpc.

Now we add this path in the Cargo.toml by adding the following line.

getter-rpc = { path = "../../pallets/trial_pallet/rpc" }

Writing the RPC

If you know how to write Silly RPCs, it would be easier to understand what is happening below. But if you don’t know what silly RPCs are, then don’t worry I will explain everything. First, the struct Get_RPC that implements the RPC needs a reference to the client as a parameter. This is important so we can actually call into the runtime. Another thing to notice is that the struct is generic over the BlockHash type. The reason behind it is that this RPC will call a Runtime API, and Runtime APIs are always called at a specific block.

The following code will be included in the lib.rs file in the src folder of the rpc crate.

///pallets/trial_pallet/rpc/src/lib.rs

#![allow(unused)]
fn main() {
#[rpc]
pub trait GetSomethingAPI<BlockHash> {
	#[rpc(name = "get_something_rpc")]
	fn get_something(
		&self,
		at: Option<BlockHash>
	) -> Result<u32>;
}

/// A struct that implements the `GetterApi`.
pub struct Get_RPC<C, M> {
	client: Arc<C>,
	_marker: std::marker::PhantomData<M>,
}

impl<C, M> Get_RPC<C, M> {
	pub fn new(client: Arc<C>) -> Self {
		Self { client, _marker: Default::default() }
	}
}
}

In the code above we defined a trait called GetSomethingAPI which looks similar to a trait that we would have created while defining the Runtime API. It contains theget_something_rpc RPC that would call the Runtime API.

Get_RPC is a Structure that would implement the GetSomethingAPI trait to define the get_something_rpc RPC. But before that, we will define the new method to initialize the client for the API.

Now we will insert the following code.

impl<C, Block> GetterApi<<Block as BlockT>::Hash>
	for Get_RPC<C, Block>
where
	Block: BlockT
	C: Send + Sync + 'static,
	C: ProvideRuntimeApi,
	C: HeaderBackend<Block>,
	C::Api: GetSomethingRuntimeApi<Block>,
{
	fn get_something(
		&self,
		at: Option<<Block as BlockT>::Hash>
	) -> Result<u32> {

		let api = self.client.runtime_api();
		let at = BlockId::hash(at.unwrap_or_else(||
			// If the block hash is not supplied assume the best block.
			self.client.info().best_hash
		));

		let runtime_api_result = api.get_something(&at);
		runtime_api_result.map_err(|e| RpcError {
			code: ErrorCode::ServerError(9876), // No real reason for this value
			message: "Something wrong".into(),
			data: Some(format!("{:?}", e).into()),
		})
	}
}
}

The above code does nothing but defines the get_something method that we declared in the GetSomethingAPI trait which ultimately calls our Runtime API. It uses the client to access the Runtime API defined earlier.

Installing the RPC

To install this RPC, we add the following code in the create_full function from node/rpc-node/src/rpc.rs.

pub fn create_full<C, P>(
	deps: FullDeps<C, P>,
) -> jsonrpc_core::IoHandler<sc_rpc::Metadata> where
	// --snip--
{

	let mut io = jsonrpc_core::IoHandler::default();

	#![allow(unused)]
        fn main() {
            io.extend_with(getter_rpc::GetSomethingApi::to_delegate(
	         getter_rpc::Get_RPC::new(client),
        ));
        }

	// --snip--

	io
}

This would install our custom RPC that is ready to perform a call to the Runtime API. This was all about creating and adding an RPC to call Runtime APIs.


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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK