4.4.6 RPC Interface

A worker provides a JSON RPC interface that runs on a secure websocket server inside the enclave. The web-socket connection is secured by a TLS connection with a certificate signed with the enclave signing key.

The rpc method names of the worker are chosen such that they match the naming scheme of the rpc calls of substrate: https://docs.substrate.io/v3/runtime/custom-rpcs/

Available RPC calls

General

  • rpc_methods (no params): List all available (though maybe unimplemented) rpc methods. Example: {"jsonrpc": "2.0", "method": "rpc_methods","id": 1}
  • author_getShieldingKey (no params): Retrieves the public shielding key, which the client can use to encrypt the messages before sending it to the worker. Beware: unlike openssl, intel sgx encryption uses little endian (le) for interpreting the shielding key.
  • author_getMuRaUrl (no params): Retrieves the mutual remote attestation url of the worker (Only needed by fellow validateers).
  • author_getUntrustedUrl (no params): Retrieves the untrusted ws url of the worker (Only needed by fellow validateers).

The server response is a json rpc response containing a hex encoded RpcReturnValue as param:

#![allow(unused)]
fn main() {
pub struct RpcReturnValue {
	pub value: Vec<u8>, // Encoded return value, depends on the called function and status.
	pub do_watch: bool, // If not true, the connection is meant to be closed after this answer.
	pub status: DirectRequestStatus, // Indicates the status of the request.
}
}

The DirectRequestState indicates the following: If the status is an Error, the value is an encoded String error message. If Ok is returned, the value will also contain an encoded String, containing the requested response, be it an url or a shielding key. TrustedOperationStatus is used for a TrustedCall submission response (see the following chapter).

Trusted Calls

Like in substrate, the state changing calls are first stored in a pool. The following rpc methods are available:

  • author_submitAndWatchExtrinsic, params: Vec<String>(hex encoded Request). The server will keep the wss connection open and send status updates.
  • author_submitExtrinsic, params: Vec<String> (hex encoded Request). The server will close the connection immediately after the first response.
  • author_pendingExtrinsics, params: Vec<String> (Vector of base58 shards as strings). Returns all pending operations of the listed shards in the top pool.

Request

All rpc params are expected to be a hex encoded (with substrate codec) Request:

#![allow(unused)]
fn main() {
pub struct Request {
	pub shard: ShardIdentifier, // Hash (H256) of a state
	pub cyphertext: Vec<u8>, // With the shielding key encrypted `TrustedOperation`.
}
}

To be able to support multiple states within one worker, each call submissions needs to be accompanied by a state defining shard. Currently, all calls need to be wrapped in a TrustedOperation wrapper. To be able to support fully featured privacy, all trusted operations are expected to be encrypted with the shielding key that can be retrieved with author_getShieldingKey.

The TrustedOperation is a wrapper around a TrustedCallSigned which consists of three components:

#![allow(unused)]
fn main() {
pub struct TrustedCallSigned {
	pub call: TrustedCall,
	pub nonce: Index, // To prevent replay attacks.
	pub signature: Signature, // Consists TrustedCall, the nonce, mrenclave and the shard.
}
}

The current implementation of the TrustedCall supports the following calls:

  • balance_set_balance set the balance of an account (signer, beneficary, free balance, reserved balance)
  • balance_transfer transfer balance from first account to second account
  • balance_unshield transfer balance from sidechain (incognito) account to parentchain (public) account.
  • balance_shield transfer balance from parentchain (public) account to sidechain (incognito) account. and several evm smart contract calls.

This struct can be easily extended to fit each individual use case.

Response

For the response for a TrustedCall what has been explained in chapter General hold true. However, the DirectRequestStatus holds a slightly different meaning:

  • DirectRequestStatus::Error indicates an value that is an encoded Error response String.
  • DirectRequestStatus::TrustedOperationStatus indicates the current status of the Operation. E.g. Submitted or InSidechainBlock. The value field then contains the encoded Hash of the corresponding TrustedOperation.

Getter

A Getter is submitted to an RPC function called state_executeGetter. It expects a Request which consists of the chosen shard and a codec encoded Getter. There is no encryption with the shielding key necessary, because the RPC server - client connection is protected with TLS. The RPC function call will immediately return the result of the getter as

#![allow(unused)]
fn main() {
RpcReturnValue {
    do_watch: false,
    value: state_getter_value.encode(),
    status: DirectRequestStatus::Ok,
}
}

where the the value is an encoded Option containing the encoded expected return value. The balance getter for example would be an encoded Balance.

Extend the RPC interface

You can, of course, also add your own RPC functions by extending the IoHandler here. Note that all JSON strings, incoming parameters and outgoing results, are expected to be hex encoded.

An example of a new RPC method called my_own_rpc_method, added to the IoHandler io :

#![allow(unused)]
fn main() {
io.add_sync_method("my_own_rpc_method", move |params: Params| {

    // Get the hex encoded parameters (if any, otherwise skip this).
    let hex_encoded_params = params.parse::<Vec<String>>().map_err(|e| format!("{:?}", e))?;

    // Decode the parameters to your desired concrete object (`Request` in this example),
    // taken from the first parameter that was provided.
	let request =
	    Request::from_hex(&hex_encoded_params[0].clone()).map_err(|e| format!("{:?}", e))?;

    // Process the request and generate response
    let result = call_my_function(request);

    // Return
	Ok(json!(result.to_hex()))
});
}