use super::parse;
use crate::{header, identity::ss58};
use alloc::{
borrow::Cow,
boxed::Box,
format,
string::{String, ToString as _},
vec,
vec::Vec,
};
use core::fmt;
use hashbrown::HashMap;
pub fn parse_jsonrpc_client_to_server(
message: &'_ str,
) -> Result<(&'_ str, MethodCall<'_>), ParseClientToServerError<'_>> {
let call_def = parse::parse_request(message).map_err(ParseClientToServerError::JsonRpcParse)?;
let request_id = match call_def.id_json {
Some(id) => id,
None => {
return Err(ParseClientToServerError::UnknownNotification {
notification_name: call_def.method,
});
}
};
let call = match MethodCall::from_defs(call_def.method, call_def.params_json) {
Ok(c) => c,
Err(error) => return Err(ParseClientToServerError::Method { request_id, error }),
};
Ok((request_id, call))
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum ParseClientToServerError<'a> {
JsonRpcParse(parse::ParseError),
#[display("Call concerns a notification that isn't recognized: {notification_name:?}.")]
UnknownNotification {
notification_name: &'a str,
},
#[display("{error}")]
Method {
request_id: &'a str,
error: MethodError<'a>,
},
}
pub fn parse_notification(
message: &'_ str,
) -> Result<ServerToClient<'_>, ParseNotificationError<'_>> {
let call_def = parse::parse_request(message).map_err(ParseNotificationError::JsonRpcParse)?;
let call = ServerToClient::from_defs(call_def.method, call_def.params_json)
.map_err(ParseNotificationError::Method)?;
Ok(call)
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum ParseNotificationError<'a> {
#[display("{_0}")]
JsonRpcParse(parse::ParseError),
#[display("{_0}")]
Method(#[error(not(source))] MethodError<'a>),
}
pub fn build_json_call_object_parameters(id_json: Option<&str>, method: MethodCall) -> String {
method.to_json_request_object_parameters(id_json)
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum MethodError<'a> {
#[display("Call concerns a method that isn't recognized: {method_name:?}")]
UnknownMethod {
method_name: &'a str,
},
#[display("Invalid parameters format when calling {rpc_method}")]
InvalidParametersFormat {
rpc_method: &'static str,
},
#[display("{rpc_method} expects {expected} parameters, but got {actual}")]
TooManyParameters {
rpc_method: &'static str,
expected: usize,
actual: usize,
},
#[display("Parameter of index {parameter_index} is invalid when calling {rpc_method}: {error}")]
InvalidParameter {
rpc_method: &'static str,
parameter_index: usize,
#[error(source)]
error: InvalidParameterError,
},
MissingParameters {
rpc_method: &'static str,
},
}
impl<'a> MethodError<'a> {
pub fn to_json_error(&self, id_json: &str) -> String {
parse::build_error_response(
id_json,
match self {
MethodError::UnknownMethod { .. } => parse::ErrorResponse::MethodNotFound,
MethodError::InvalidParametersFormat { .. }
| MethodError::TooManyParameters { .. }
| MethodError::InvalidParameter { .. }
| MethodError::MissingParameters { .. } => {
parse::ErrorResponse::InvalidParams(None)
}
},
None,
)
}
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub struct InvalidParameterError(serde_json::Error);
macro_rules! define_methods {
($rq_name:ident, $rp_name:ident $(<$l:lifetime>)*, $(
$(#[$attrs:meta])*
$name:ident ($($(#[rename = $p_rpc_name:expr])* $p_name:ident: $p_ty:ty),*) -> $ret_ty:ty
$([$($alias:ident),*])*
,
)*) => {
#[allow(non_camel_case_types, non_snake_case)]
#[derive(Debug, Clone)]
pub enum $rq_name<'a> {
$(
$(#[$attrs])*
$name {
$($p_name: $p_ty),*
},
)*
}
impl<'a> $rq_name<'a> {
pub fn method_names() -> impl ExactSizeIterator<Item = &'static str> {
[$(stringify!($name)),*].iter().copied()
}
pub fn name(&self) -> &'static str {
match self {
$($rq_name::$name { .. } => stringify!($name),)*
}
}
pub fn params_to_json_object(&self) -> String {
match self {
$($rq_name::$name { $($p_name),* } => {
#[derive(serde::Serialize)]
struct Params<'a> {
$(
$(#[serde(rename = $p_rpc_name)])*
$p_name: &'a $p_ty,
)*
#[serde(skip)]
_dummy: core::marker::PhantomData<&'a ()>,
}
serde_json::to_string(&Params {
$($p_name,)*
_dummy: core::marker::PhantomData
}).unwrap()
},)*
}
}
pub fn to_json_request_object_parameters(&self, id_json: Option<&str>) -> String {
parse::build_request(&parse::Request {
id_json,
method: self.name(),
params_json: Some(&self.params_to_json_object()),
})
}
fn from_defs(name: &'a str, params: Option<&'a str>) -> Result<Self, MethodError<'a>> {
#![allow(unused, unused_mut)]
$(
if name == stringify!($name) $($(|| name == stringify!($alias))*)* {
if params.is_none() {
if !has_params!($($p_name),*) {
return Ok($rq_name::$name {
$($p_name: unreachable!(),)*
})
} else {
return Err(MethodError::MissingParameters {
rpc_method: stringify!($name),
});
}
}
#[derive(serde::Deserialize)]
struct Params<'a> {
$(
$(#[serde(rename = $p_rpc_name)])*
$p_name: $p_ty,
)*
#[serde(borrow, skip)]
_dummy: core::marker::PhantomData<&'a ()>,
}
if let Some(Ok(params)) = params.as_ref().map(|p| serde_json::from_str(p)) {
let Params { _dummy: _, $($p_name),* } = params;
return Ok($rq_name::$name {
$($p_name,)*
})
}
if let Some(Ok(params)) = params.as_ref().map(|p| serde_json::from_str::<Vec<&'a serde_json::value::RawValue>>(p)) {
let mut n = 0;
$(
let $p_name = match params.get(n)
.map(|val| serde_json::from_str(val.get()))
.unwrap_or_else(|| serde_json::from_str("null"))
{
Ok(v) => v,
Err(err) => return Err(MethodError::InvalidParameter {
rpc_method: stringify!($name),
parameter_index: n,
error: InvalidParameterError(err),
})
};
n += 1;
)*
if params.get(n).is_some() {
return Err(MethodError::TooManyParameters {
rpc_method: stringify!($name),
expected: n,
actual: params.len(),
})
}
return Ok($rq_name::$name {
$($p_name,)*
})
}
return Err(MethodError::InvalidParametersFormat {
rpc_method: stringify!($name),
});
}
)*
Err(MethodError::UnknownMethod { method_name: name })
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub enum $rp_name $(<$l>)* {
$(
$name($ret_ty),
)*
}
impl$(<$l>)* $rp_name$(<$l>)* {
pub fn to_json_response(&self, id_json: &str) -> String {
match self {
$(
$rp_name::$name(out) => {
let result_json = serde_json::to_string(&out).unwrap();
parse::build_success_response(id_json, &result_json)
},
)*
}
}
}
};
}
macro_rules! has_params {
() => {
false
};
($p1:ident $(, $p:ident)*) => {
true
};
}
define_methods! {
MethodCall,
Response<'a>,
account_nextIndex() -> (), author_hasKey() -> (), author_hasSessionKeys() -> (), author_insertKey() -> (), author_pendingExtrinsics() -> Vec<HexString>, author_removeExtrinsic() -> (), author_rotateKeys() -> HexString,
author_submitAndWatchExtrinsic(transaction: HexString) -> Cow<'a, str>,
author_submitExtrinsic(transaction: HexString) -> HashHexString,
author_unwatchExtrinsic(subscription: Cow<'a, str>) -> bool,
babe_epochAuthorship() -> (), chain_getBlock(hash: Option<HashHexString>) -> Block,
chain_getBlockHash(height: Option<u64>) -> HashHexString [chain_getHead],
chain_getFinalizedHead() -> HashHexString [chain_getFinalisedHead],
chain_getHeader(hash: Option<HashHexString>) -> Header, chain_subscribeAllHeads() -> Cow<'a, str>,
chain_subscribeFinalizedHeads() -> Cow<'a, str> [chain_subscribeFinalisedHeads],
chain_subscribeNewHeads() -> Cow<'a, str> [subscribe_newHead, chain_subscribeNewHead],
chain_unsubscribeAllHeads(subscription: String) -> bool,
chain_unsubscribeFinalizedHeads(subscription: String) -> bool [chain_unsubscribeFinalisedHeads],
chain_unsubscribeNewHeads(subscription: String) -> bool [unsubscribe_newHead, chain_unsubscribeNewHead],
childstate_getKeys() -> (), childstate_getStorage() -> (), childstate_getStorageHash() -> (), childstate_getStorageSize() -> (), grandpa_roundState() -> (), offchain_localStorageGet() -> (), offchain_localStorageSet() -> (), payment_queryInfo(extrinsic: HexString, hash: Option<HashHexString>) -> RuntimeDispatchInfo,
rpc_methods() -> RpcMethods,
state_call(name: Cow<'a, str>, parameters: HexString, hash: Option<HashHexString>) -> HexString [state_callAt],
state_getKeys(prefix: HexString, hash: Option<HashHexString>) -> Vec<HexString>,
state_getKeysPaged(prefix: Option<HexString>, count: u32, start_key: Option<HexString>, hash: Option<HashHexString>) -> Vec<HexString> [state_getKeysPagedAt],
state_getMetadata(hash: Option<HashHexString>) -> HexString,
state_getPairs() -> (), state_getReadProof(keys: Vec<HexString>, at: Option<HashHexString>) -> ReadProof,
state_getRuntimeVersion(at: Option<HashHexString>) -> RuntimeVersion<'a> [chain_getRuntimeVersion],
state_getStorage(key: HexString, hash: Option<HashHexString>) -> HexString [state_getStorageAt],
state_getStorageHash() -> () [state_getStorageHashAt], state_getStorageSize() -> () [state_getStorageSizeAt], state_queryStorage() -> (), state_queryStorageAt(keys: Vec<HexString>, at: Option<HashHexString>) -> Vec<StorageChangeSet>, state_subscribeRuntimeVersion() -> Cow<'a, str> [chain_subscribeRuntimeVersion],
state_subscribeStorage(list: Vec<HexString>) -> Cow<'a, str>,
state_unsubscribeRuntimeVersion(subscription: Cow<'a, str>) -> bool [chain_unsubscribeRuntimeVersion],
state_unsubscribeStorage(subscription: Cow<'a, str>) -> bool,
system_accountNextIndex(account: AccountId) -> u64,
system_addReservedPeer() -> (), system_chain() -> Cow<'a, str>,
system_chainType() -> Cow<'a, str>,
system_dryRun() -> () [system_dryRunAt], system_health() -> SystemHealth,
system_localListenAddresses() -> Vec<String>,
system_localPeerId() -> Cow<'a, str>,
system_name() -> Cow<'a, str>,
system_networkState() -> (), system_nodeRoles() -> Cow<'a, [NodeRole]>,
system_peers() -> Vec<SystemPeer>,
system_properties() -> Box<serde_json::value::RawValue>,
system_removeReservedPeer() -> (), system_version() -> Cow<'a, str>,
statement_submit(encoded: HexString) -> StatementSubmitResult,
statement_subscribeStatement(filter: TopicFilter) -> Cow<'a, str>,
statement_unsubscribeStatement(subscription: String) -> bool,
chainHead_v1_body(
#[rename = "followSubscription"] follow_subscription: Cow<'a, str>,
hash: HashHexString
) -> ChainHeadBodyCallReturn<'a>,
chainHead_v1_call(
#[rename = "followSubscription"] follow_subscription: Cow<'a, str>,
hash: HashHexString,
function: Cow<'a, str>,
#[rename = "callParameters"] call_parameters: HexString
) -> ChainHeadBodyCallReturn<'a>,
chainHead_v1_follow(
#[rename = "withRuntime"] with_runtime: bool
) -> Cow<'a, str>,
chainHead_v1_header(
#[rename = "followSubscription"] follow_subscription: Cow<'a, str>,
hash: HashHexString
) -> Option<HexString>,
chainHead_v1_stopOperation(
#[rename = "followSubscription"] follow_subscription: Cow<'a, str>,
#[rename = "operationId"] operation_id: Cow<'a, str>
) -> (),
chainHead_v1_storage(
#[rename = "followSubscription"] follow_subscription: Cow<'a, str>,
hash: HashHexString,
items: Vec<ChainHeadStorageRequestItem>,
#[rename = "childTrie"] child_trie: Option<HexString>
) -> ChainHeadStorageReturn<'a>,
chainHead_v1_continue(
#[rename = "followSubscription"] follow_subscription: Cow<'a, str>,
#[rename = "operationId"] operation_id: Cow<'a, str>
) -> (),
chainHead_v1_unfollow(
#[rename = "followSubscription"] follow_subscription: Cow<'a, str>
) -> (),
chainHead_v1_unpin(
#[rename = "followSubscription"] follow_subscription: Cow<'a, str>,
#[rename = "hashOrHashes"] hash_or_hashes: HashHexStringSingleOrArray
) -> (),
chainSpec_v1_chainName() -> Cow<'a, str>,
chainSpec_v1_genesisHash() -> HashHexString,
chainSpec_v1_properties() -> Box<serde_json::value::RawValue>,
sudo_unstable_p2pDiscover(multiaddr: Cow<'a, str>) -> (),
sudo_unstable_version() -> Cow<'a, str>,
transaction_v1_broadcast(transaction: HexString) -> Cow<'a, str>,
transaction_v1_stop(#[rename = "operationId"] operation_id: Cow<'a, str>) -> (),
transactionWatch_v1_submitAndWatch(transaction: HexString) -> Cow<'a, str>,
transactionWatch_v1_unwatch(subscription: Cow<'a, str>) -> (),
bitswap_v1_get(cid: String) -> HexString,
sudo_network_unstable_watch() -> Cow<'a, str>,
sudo_network_unstable_unwatch(subscription: Cow<'a, str>) -> (),
chainHead_unstable_finalizedDatabase(#[rename = "maxSizeBytes"] max_size_bytes: Option<u64>) -> Cow<'a, str>,
}
define_methods! {
ServerToClient,
ServerToClientResponse, author_extrinsicUpdate(subscription: Cow<'a, str>, result: TransactionStatus) -> (),
chain_finalizedHead(subscription: Cow<'a, str>, result: Header) -> (),
chain_newHead(subscription: Cow<'a, str>, result: Header) -> (),
chain_allHead(subscription: Cow<'a, str>, result: Header) -> (),
state_runtimeVersion(subscription: Cow<'a, str>, result: Option<RuntimeVersion<'a>>) -> (), state_storage(subscription: Cow<'a, str>, result: StorageChangeSet) -> (),
chainHead_v1_followEvent(subscription: Cow<'a, str>, result: FollowEvent<'a>) -> (),
transactionWatch_v1_watchEvent(subscription: Cow<'a, str>, result: TransactionWatchEvent<'a>) -> (),
sudo_networkState_event(subscription: Cow<'a, str>, result: NetworkEvent) -> (),
statement_statement(subscription: Cow<'a, str>, result: StatementEvent) -> (),
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct HexString(pub Vec<u8>);
impl AsRef<[u8]> for HexString {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<'a> serde::Deserialize<'a> for HexString {
fn deserialize<D>(deserializer: D) -> Result<HexString, D::Error>
where
D: serde::Deserializer<'a>,
{
let string = String::deserialize(deserializer)?;
if string.is_empty() {
return Ok(HexString(Vec::new()));
}
if !string.starts_with("0x") {
return Err(serde::de::Error::custom(
"hexadecimal string doesn't start with 0x",
));
}
let bytes = hex::decode(&string[2..]).map_err(serde::de::Error::custom)?;
Ok(HexString(bytes))
}
}
impl fmt::Debug for HexString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "0x{}", hex::encode(&self.0))
}
}
#[derive(Debug, Clone)]
pub struct HashHexString(pub [u8; 32]);
impl<'a> serde::Deserialize<'a> for HashHexString {
fn deserialize<D>(deserializer: D) -> Result<HashHexString, D::Error>
where
D: serde::Deserializer<'a>,
{
let string = String::deserialize(deserializer)?;
if !string.starts_with("0x") {
return Err(serde::de::Error::custom("hash doesn't start with 0x"));
}
let bytes = hex::decode(&string[2..]).map_err(serde::de::Error::custom)?;
if bytes.len() != 32 {
return Err(serde::de::Error::invalid_length(
bytes.len(),
&"a 32 bytes hash",
));
}
let mut out = [0; 32];
out.copy_from_slice(&bytes);
Ok(HashHexString(out))
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum HashHexStringSingleOrArray {
Single(HashHexString),
Array(Vec<HashHexString>),
}
pub fn remove_metadata_length_prefix(
metadata: &[u8],
) -> Result<&[u8], RemoveMetadataLengthPrefixError> {
let (after_prefix, length) = crate::util::nom_scale_compact_usize(metadata).map_err(
|_: nom::Err<nom::error::Error<&[u8]>>| {
RemoveMetadataLengthPrefixError::InvalidLengthPrefix
},
)?;
if length != after_prefix.len() {
return Err(RemoveMetadataLengthPrefixError::LengthMismatch);
}
Ok(after_prefix)
}
#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
pub enum RemoveMetadataLengthPrefixError {
InvalidLengthPrefix,
LengthMismatch,
}
#[derive(Debug, Clone)]
pub struct AccountId(pub Vec<u8>);
impl serde::Serialize for AccountId {
fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
todo!() }
}
impl<'a> serde::Deserialize<'a> for AccountId {
fn deserialize<D>(deserializer: D) -> Result<AccountId, D::Error>
where
D: serde::Deserializer<'a>,
{
let string = <&str>::deserialize(deserializer)?;
let decoded = match ss58::decode(string) {
Ok(d) => d,
Err(err) => return Err(serde::de::Error::custom(err.to_string())),
};
Ok(AccountId(decoded.public_key.as_ref().to_vec()))
}
}
#[derive(Debug, Clone)]
pub struct Block {
pub extrinsics: Vec<HexString>,
pub header: Header,
pub justifications: Option<Vec<([u8; 4], Vec<u8>)>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "event")]
pub enum FollowEvent<'a> {
#[serde(rename = "initialized")]
Initialized {
#[serde(rename = "finalizedBlockHashes")]
finalized_block_hashes: Vec<HashHexString>,
#[serde(
rename = "finalizedBlockRuntime",
skip_serializing_if = "Option::is_none"
)]
finalized_block_runtime: Option<MaybeRuntimeSpec<'a>>,
},
#[serde(rename = "newBlock")]
NewBlock {
#[serde(rename = "blockHash")]
block_hash: HashHexString,
#[serde(rename = "parentBlockHash")]
parent_block_hash: HashHexString,
#[serde(rename = "newRuntime")]
new_runtime: Option<MaybeRuntimeSpec<'a>>,
},
#[serde(rename = "bestBlockChanged")]
BestBlockChanged {
#[serde(rename = "bestBlockHash")]
best_block_hash: HashHexString,
},
#[serde(rename = "finalized")]
Finalized {
#[serde(rename = "finalizedBlockHashes")]
finalized_blocks_hashes: Vec<HashHexString>,
#[serde(rename = "prunedBlockHashes")]
pruned_blocks_hashes: Vec<HashHexString>,
},
#[serde(rename = "operationBodyDone")]
OperationBodyDone {
#[serde(rename = "operationId")]
operation_id: Cow<'a, str>,
value: Vec<HexString>,
},
#[serde(rename = "operationCallDone")]
OperationCallDone {
#[serde(rename = "operationId")]
operation_id: Cow<'a, str>,
output: HexString,
},
#[serde(rename = "operationInaccessible")]
OperationInaccessible {
#[serde(rename = "operationId")]
operation_id: Cow<'a, str>,
},
#[serde(rename = "operationStorageItems")]
OperationStorageItems {
#[serde(rename = "operationId")]
operation_id: Cow<'a, str>,
items: Vec<ChainHeadStorageResponseItem>,
},
#[serde(rename = "operationStorageDone")]
OperationStorageDone {
#[serde(rename = "operationId")]
operation_id: Cow<'a, str>,
},
#[serde(rename = "operationWaitingForContinue")]
OperationWaitingForContinue,
#[serde(rename = "operationError")]
OperationError {
#[serde(rename = "operationId")]
operation_id: Cow<'a, str>,
error: Cow<'a, str>,
},
#[serde(rename = "stop")]
Stop {},
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "result")]
pub enum ChainHeadBodyCallReturn<'a> {
#[serde(rename = "started")]
Started {
#[serde(rename = "operationId")]
operation_id: Cow<'a, str>,
},
#[serde(rename = "limitReached")]
LimitReached {},
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "result")]
pub enum ChainHeadStorageReturn<'a> {
#[serde(rename = "started")]
Started {
#[serde(rename = "operationId")]
operation_id: Cow<'a, str>,
#[serde(rename = "discardedItems")]
discarded_items: usize,
},
#[serde(rename = "limitReached")]
LimitReached {},
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ChainHeadStorageRequestItem {
pub key: HexString,
#[serde(rename = "type")]
pub ty: ChainHeadStorageType,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ChainHeadStorageResponseItem {
pub key: HexString,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<HexString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hash: Option<HexString>,
#[serde(
rename = "closestDescendantMerkleValue",
skip_serializing_if = "Option::is_none"
)]
pub closest_descendant_merkle_value: Option<HexString>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum ChainHeadStorageType {
#[serde(rename = "value")]
Value,
#[serde(rename = "hash")]
Hash,
#[serde(rename = "closestDescendantMerkleValue")]
ClosestDescendantMerkleValue,
#[serde(rename = "descendantsValues")]
DescendantsValues,
#[serde(rename = "descendantsHashes")]
DescendantsHashes,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "event")]
pub enum TransactionWatchEvent<'a> {
#[serde(rename = "validated")]
Validated {},
#[serde(rename = "broadcasted")]
Broadcasted {
#[serde(rename = "numPeers")]
num_peers: u32,
},
#[serde(rename = "bestChainBlockIncluded")]
BestChainBlockIncluded {
#[serde(rename = "block")]
block: Option<TransactionWatchEventBlock>,
},
#[serde(rename = "finalized")]
Finalized {
#[serde(rename = "block")]
block: TransactionWatchEventBlock,
},
#[serde(rename = "error")]
Error { error: Cow<'a, str> },
#[serde(rename = "invalid")]
Invalid { error: Cow<'a, str> },
#[serde(rename = "dropped")]
Dropped {
broadcasted: bool,
error: Cow<'a, str>,
},
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TransactionWatchEventBlock {
pub hash: HashHexString,
pub index: u32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "event")]
pub enum NetworkEvent {
#[serde(rename = "connectionState")]
ConnectionState {
#[serde(rename = "connectionId")]
connection_id: u32,
#[serde(rename = "targetPeerId", skip_serializing_if = "Option::is_none")]
target_peer_id: Option<String>,
#[serde(rename = "targetMultiaddr")]
target_multiaddr: String,
status: NetworkEventStatus,
direction: NetworkEventDirection,
when: u64,
},
#[serde(rename = "substreamState")]
SubstreamState {
#[serde(rename = "connectionId")]
connection_id: u32,
#[serde(rename = "substreamId")]
substream_id: u32,
status: NetworkEventStatus,
#[serde(rename = "protocolName")]
protocol_name: String,
direction: NetworkEventDirection,
when: u64,
},
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum NetworkEventStatus {
#[serde(rename = "connecting")]
Connecting,
#[serde(rename = "open")]
Open,
#[serde(rename = "closed")]
Close,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum NetworkEventDirection {
#[serde(rename = "in")]
In,
#[serde(rename = "out")]
Out,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Header {
#[serde(rename = "parentHash")]
pub parent_hash: HashHexString,
#[serde(rename = "extrinsicsRoot")]
pub extrinsics_root: HashHexString,
#[serde(rename = "stateRoot")]
pub state_root: HashHexString,
#[serde(
serialize_with = "hex_num_serialize",
deserialize_with = "hex_num_deserialize"
)]
pub number: u64,
pub digest: HeaderDigest,
}
impl Header {
pub fn from_scale_encoded_header(
header: &[u8],
block_number_bytes: usize,
) -> Result<Header, header::Error> {
let header = header::decode(header, block_number_bytes)?;
Ok(Header {
parent_hash: HashHexString(*header.parent_hash),
extrinsics_root: HashHexString(*header.extrinsics_root),
state_root: HashHexString(*header.state_root),
number: header.number,
digest: HeaderDigest {
logs: header
.digest
.logs()
.map(|log| {
HexString(log.scale_encoding(block_number_bytes).fold(
Vec::new(),
|mut a, b| {
a.extend_from_slice(b.as_ref());
a
},
))
})
.collect(),
},
})
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct HeaderDigest {
pub logs: Vec<HexString>,
}
#[derive(Debug, Clone)]
pub struct RpcMethods {
pub methods: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
pub enum MaybeRuntimeSpec<'a> {
#[serde(rename = "valid")]
Valid { spec: RuntimeSpec<'a> },
#[serde(rename = "invalid")]
Invalid { error: String }, }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum NodeRole {
#[serde(rename = "Light")]
Light,
#[serde(rename = "Full")]
Full,
#[serde(rename = "Authority")]
Authority,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RuntimeSpec<'a> {
#[serde(rename = "specName")]
pub spec_name: Cow<'a, str>,
#[serde(rename = "implName")]
pub impl_name: Cow<'a, str>,
#[serde(rename = "specVersion")]
pub spec_version: u32,
#[serde(rename = "implVersion")]
pub impl_version: u32,
#[serde(rename = "transactionVersion", skip_serializing_if = "Option::is_none")]
pub transaction_version: Option<u32>,
pub apis: HashMap<HexString, u32, fnv::FnvBuildHasher>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RuntimeVersion<'a> {
#[serde(rename = "specName")]
pub spec_name: Cow<'a, str>,
#[serde(rename = "implName")]
pub impl_name: Cow<'a, str>,
#[serde(rename = "authoringVersion")]
pub authoring_version: u64,
#[serde(rename = "specVersion")]
pub spec_version: u64,
#[serde(rename = "implVersion")]
pub impl_version: u64,
#[serde(rename = "transactionVersion", skip_serializing_if = "Option::is_none")]
pub transaction_version: Option<u64>,
#[serde(rename = "stateVersion", skip_serializing_if = "Option::is_none")]
pub state_version: Option<u64>,
pub apis: Vec<(HexString, u32)>,
}
#[derive(Debug, Copy, Clone)]
pub struct RuntimeDispatchInfo {
pub weight: u64,
pub class: DispatchClass,
pub partial_fee: u128,
}
#[derive(Debug, Copy, Clone)]
pub enum DispatchClass {
Normal,
Operational,
Mandatory,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct StorageChangeSet {
pub block: HashHexString,
pub changes: Vec<(HexString, Option<HexString>)>,
}
#[derive(Debug, Clone)]
pub struct SystemHealth {
pub is_syncing: bool,
pub peers: u64,
pub should_have_peers: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ReadProof {
pub at: HashHexString,
pub proof: Vec<HexString>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct SystemPeer {
#[serde(rename = "peerId")]
pub peer_id: String, pub roles: SystemPeerRole,
#[serde(rename = "bestHash")]
pub best_hash: HashHexString,
#[serde(rename = "bestNumber")]
pub best_number: u64,
}
#[derive(Debug, Clone, serde::Serialize)]
pub enum SystemPeerRole {
#[serde(rename = "AUTHORITY")]
Authority,
#[serde(rename = "FULL")]
Full,
#[serde(rename = "LIGHT")]
Light,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "status", rename_all = "camelCase")]
pub enum StatementSubmitResult {
New,
Invalid { reason: String },
InternalError { error: String },
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "event", content = "data", rename_all = "camelCase")]
pub enum StatementEvent {
NewStatements {
statements: Vec<HexString>,
#[serde(default, skip_serializing_if = "Option::is_none")]
remaining: Option<u32>,
},
}
#[derive(Debug, Clone)]
pub enum TopicFilter {
Any,
MatchAll(Vec<crate::network::codec::Topic>),
MatchAny(Vec<crate::network::codec::Topic>),
}
impl serde::Serialize for TopicFilter {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
fn topics_to_hex(topics: &[crate::network::codec::Topic]) -> Vec<alloc::string::String> {
topics
.iter()
.map(|t| alloc::format!("0x{}", hex::encode(t)))
.collect()
}
use serde::ser::SerializeMap;
let (key, topics) = match self {
TopicFilter::Any => return serializer.serialize_str("any"),
TopicFilter::MatchAll(topics) => ("matchAll", topics),
TopicFilter::MatchAny(topics) => ("matchAny", topics),
};
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry(key, &topics_to_hex(topics))?;
map.end()
}
}
impl<'de> serde::Deserialize<'de> for TopicFilter {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
struct TopicFilterVisitor;
impl<'de> serde::de::Visitor<'de> for TopicFilterVisitor {
type Value = TopicFilter;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_str(r#""any" or {"matchAll": [...]} or {"matchAny": [...]}"#)
}
fn visit_str<E: Error>(self, value: &str) -> Result<TopicFilter, E> {
match value {
"any" => Ok(TopicFilter::Any),
other => Err(E::custom(alloc::format!(
"unknown filter type: {other}, expected \"any\""
))),
}
}
fn visit_map<A: serde::de::MapAccess<'de>>(
self,
mut map: A,
) -> Result<TopicFilter, A::Error> {
let key: alloc::string::String = map
.next_key()?
.ok_or_else(|| A::Error::custom("empty object"))?;
let hex_topics: alloc::vec::Vec<alloc::string::String> = map.next_value()?;
let topics: Vec<crate::network::codec::Topic> = hex_topics
.iter()
.map(|s| {
let s = s.strip_prefix("0x").unwrap_or(s);
let bytes =
hex::decode(s).map_err(|e| A::Error::custom(alloc::format!("{e}")))?;
<[u8; 32]>::try_from(bytes.as_slice())
.map_err(|_| A::Error::custom("topic must be exactly 32 bytes"))
})
.collect::<Result<_, _>>()?;
match key.as_str() {
"matchAll" => TopicFilter::match_all(topics).map_err(A::Error::custom),
"matchAny" => TopicFilter::match_any(topics).map_err(A::Error::custom),
other => Err(A::Error::custom(alloc::format!(
"unknown filter key: {other}, expected \"matchAll\" or \"matchAny\""
))),
}
}
}
deserializer.deserialize_any(TopicFilterVisitor)
}
}
impl TopicFilter {
pub fn match_all(
topics: Vec<crate::network::codec::Topic>,
) -> Result<Self, alloc::string::String> {
if topics.len() > crate::network::codec::MAX_TOPICS {
return Err(alloc::format!(
"Too many topics for MatchAll: got {}, max {}",
topics.len(),
crate::network::codec::MAX_TOPICS
));
}
Ok(TopicFilter::MatchAll(topics))
}
pub fn match_any(
topics: Vec<crate::network::codec::Topic>,
) -> Result<Self, alloc::string::String> {
if topics.len() > crate::network::codec::MAX_ANY_TOPICS {
return Err(alloc::format!(
"Too many topics for MatchAny: got {}, max {}",
topics.len(),
crate::network::codec::MAX_ANY_TOPICS
));
}
Ok(TopicFilter::MatchAny(topics))
}
pub fn matches(&self, statement_topics: &[crate::network::codec::Topic]) -> bool {
match self {
TopicFilter::Any => true,
TopicFilter::MatchAny(filter_topics) => {
if filter_topics.is_empty() {
return false;
}
statement_topics.iter().any(|t| filter_topics.contains(t))
}
TopicFilter::MatchAll(filter_topics) => {
filter_topics.iter().all(|t| statement_topics.contains(t))
}
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum TransactionStatus {
#[serde(rename = "future")]
Future,
#[serde(rename = "ready")]
Ready,
#[serde(rename = "broadcast")]
Broadcast(Vec<String>), #[serde(rename = "inBlock")]
InBlock(HashHexString),
#[serde(rename = "retracted")]
Retracted(HashHexString),
#[serde(rename = "finalityTimeout")]
FinalityTimeout(HashHexString),
#[serde(rename = "finalized")]
Finalized(HashHexString),
#[serde(rename = "usurped")]
Usurped(HashHexString),
#[serde(rename = "dropped")]
Dropped,
#[serde(rename = "invalid")]
Invalid,
}
impl serde::Serialize for HashHexString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
format!("0x{}", hex::encode(&self.0[..])).serialize(serializer)
}
}
impl fmt::Display for HexString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "0x{}", hex::encode(&self.0[..]))
}
}
impl serde::Serialize for HexString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.to_string().serialize(serializer)
}
}
impl serde::Serialize for RpcMethods {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(serde::Serialize)]
struct SerdeRpcMethods<'a> {
methods: &'a [String],
}
SerdeRpcMethods {
methods: &self.methods,
}
.serialize(serializer)
}
}
impl serde::Serialize for Block {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(serde::Serialize)]
struct SerdeBlock<'a> {
block: SerdeBlockInner<'a>,
}
#[derive(serde::Serialize)]
struct SerdeBlockInner<'a> {
extrinsics: &'a [HexString],
header: &'a Header,
justifications: Option<Vec<Vec<Vec<u8>>>>,
}
SerdeBlock {
block: SerdeBlockInner {
extrinsics: &self.extrinsics,
header: &self.header,
justifications: self.justifications.as_ref().map(|list| {
list.iter()
.map(|(e, j)| vec![e.to_vec(), j.clone()])
.collect()
}),
},
}
.serialize(serializer)
}
}
impl serde::Serialize for RuntimeDispatchInfo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(serde::Serialize)]
struct SerdeRuntimeDispatchInfo {
weight: u64,
class: &'static str,
#[serde(rename = "partialFee")]
partial_fee: String,
}
SerdeRuntimeDispatchInfo {
weight: self.weight,
class: match self.class {
DispatchClass::Normal => "normal",
DispatchClass::Operational => "operational",
DispatchClass::Mandatory => "mandatory",
},
partial_fee: self.partial_fee.to_string(),
}
.serialize(serializer)
}
}
#[derive(serde::Serialize, serde::Deserialize)]
struct SerdeSystemHealth {
#[serde(rename = "isSyncing")]
is_syncing: bool,
peers: u64,
#[serde(rename = "shouldHavePeers")]
should_have_peers: bool,
}
impl serde::Serialize for SystemHealth {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
SerdeSystemHealth {
is_syncing: self.is_syncing,
peers: self.peers,
should_have_peers: self.should_have_peers,
}
.serialize(serializer)
}
}
impl<'a> serde::Deserialize<'a> for SystemHealth {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
let h: SerdeSystemHealth = serde::Deserialize::deserialize(deserializer)?;
Ok(SystemHealth {
is_syncing: h.is_syncing,
peers: h.peers,
should_have_peers: h.should_have_peers,
})
}
}
fn hex_num_serialize<S>(num: &u64, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serde::Serialize::serialize(&format!("0x{:x}", *num), serializer)
}
fn hex_num_deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: serde::Deserializer<'de>,
{
let mut string: String = serde::Deserialize::deserialize(deserializer)?;
if !string.starts_with("0x") {
return Err(serde::de::Error::custom("number doesn't start with 0x"));
}
if string.len() % 2 != 0 {
string.insert(2, '0');
}
let decoded = hex::decode(&string[2..]).map_err(serde::de::Error::custom)?;
if decoded.len() > 8 {
return Err(serde::de::Error::custom("number overflow"));
}
let mut num = [0u8; 8];
num[..decoded.len()].copy_from_slice(&decoded);
Ok(u64::from_be_bytes(num))
}
#[cfg(test)]
mod tests {
#[test]
fn no_params_accepted() {
let (_, call) = super::parse_jsonrpc_client_to_server(
r#"{"jsonrpc":"2.0","id":2,"method":"chainSpec_v1_chainName"}"#,
)
.unwrap();
assert!(matches!(call, super::MethodCall::chainSpec_v1_chainName {}));
}
#[test]
fn no_params_refused() {
let err = super::parse_jsonrpc_client_to_server(
r#"{"jsonrpc":"2.0","id":2,"method":"chainHead_v1_follow"}"#,
);
assert!(matches!(
err,
Err(super::ParseClientToServerError::Method {
request_id: "2",
error: super::MethodError::MissingParameters {
rpc_method: "chainHead_v1_follow"
}
})
));
}
#[test]
fn statement_submit_parse_valid() {
let (id, call) = super::parse_jsonrpc_client_to_server(
r#"{"jsonrpc":"2.0","id":1,"method":"statement_submit","params":["0x1234"]}"#,
)
.unwrap();
assert_eq!(id, "1");
assert!(matches!(call, super::MethodCall::statement_submit { .. }));
}
#[test]
fn statement_submit_result_serialization() {
let new = super::StatementSubmitResult::New;
assert_eq!(serde_json::to_string(&new).unwrap(), r#"{"status":"new"}"#);
let invalid = super::StatementSubmitResult::Invalid {
reason: "bad encoding".into(),
};
assert_eq!(
serde_json::to_string(&invalid).unwrap(),
r#"{"status":"invalid","reason":"bad encoding"}"#
);
let internal = super::StatementSubmitResult::InternalError {
error: "No connected peers".into(),
};
assert_eq!(
serde_json::to_string(&internal).unwrap(),
r#"{"status":"internalError","error":"No connected peers"}"#
);
}
#[test]
fn statement_event_serialization() {
let event = super::StatementEvent::NewStatements {
statements: vec![super::HexString(vec![0x12, 0x34])],
remaining: None,
};
assert_eq!(
serde_json::to_string(&event).unwrap(),
r#"{"event":"newStatements","data":{"statements":["0x1234"]}}"#
);
let event_with_remaining = super::StatementEvent::NewStatements {
statements: vec![super::HexString(vec![0xab])],
remaining: Some(5),
};
assert_eq!(
serde_json::to_string(&event_with_remaining).unwrap(),
r#"{"event":"newStatements","data":{"statements":["0xab"],"remaining":5}}"#
);
}
#[test]
fn statement_subscribe_parse_any() {
let (id, call) = super::parse_jsonrpc_client_to_server(
r#"{"jsonrpc":"2.0","id":2,"method":"statement_subscribeStatement","params":["any"]}"#,
)
.unwrap();
assert_eq!(id, "2");
assert!(matches!(
call,
super::MethodCall::statement_subscribeStatement {
filter: super::TopicFilter::Any
}
));
}
#[test]
fn statement_subscribe_parse_match_any() {
let (id, call) = super::parse_jsonrpc_client_to_server(
r#"{"jsonrpc":"2.0","id":2,"method":"statement_subscribeStatement","params":[{"matchAny":["0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"]}]}"#,
)
.unwrap();
assert_eq!(id, "2");
assert!(matches!(
call,
super::MethodCall::statement_subscribeStatement {
filter: super::TopicFilter::MatchAny(_)
}
));
}
#[test]
fn statement_subscribe_parse_match_all() {
let (id, call) = super::parse_jsonrpc_client_to_server(
r#"{"jsonrpc":"2.0","id":2,"method":"statement_subscribeStatement","params":[{"matchAll":["0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"]}]}"#,
)
.unwrap();
assert_eq!(id, "2");
assert!(matches!(
call,
super::MethodCall::statement_subscribeStatement {
filter: super::TopicFilter::MatchAll(_)
}
));
}
#[test]
fn statement_unsubscribe_parse_valid() {
let (id, call) = super::parse_jsonrpc_client_to_server(
r#"{"jsonrpc":"2.0","id":4,"method":"statement_unsubscribeStatement","params":["sub123"]}"#,
)
.unwrap();
assert_eq!(id, "4");
assert!(matches!(
call,
super::MethodCall::statement_unsubscribeStatement { .. }
));
}
#[test]
fn topic_filter_any_matches_everything() {
let filter = super::TopicFilter::Any;
assert!(filter.matches(&[]));
let topic = [1u8; 32];
assert!(filter.matches(&[topic]));
}
#[test]
fn topic_filter_match_any_empty_returns_false() {
let filter = super::TopicFilter::match_any(Vec::new()).unwrap();
let topic = [1u8; 32];
assert!(!filter.matches(&[topic]));
assert!(!filter.matches(&[]));
}
#[test]
fn topic_filter_match_any_with_overlap() {
let t1 = [1u8; 32];
let t2 = [2u8; 32];
let t3 = [3u8; 32];
let filter = super::TopicFilter::match_any(vec![t1, t2]).unwrap();
assert!(filter.matches(&[t1]));
assert!(filter.matches(&[t2]));
assert!(filter.matches(&[t3, t1]));
assert!(!filter.matches(&[t3]));
assert!(!filter.matches(&[]));
}
#[test]
fn topic_filter_match_all() {
let t1 = [1u8; 32];
let t2 = [2u8; 32];
let t3 = [3u8; 32];
let filter = super::TopicFilter::match_all(vec![t1, t2]).unwrap();
assert!(filter.matches(&[t1, t2]));
assert!(filter.matches(&[t1, t2, t3]));
assert!(!filter.matches(&[t1]));
assert!(!filter.matches(&[t2]));
assert!(!filter.matches(&[t3]));
assert!(!filter.matches(&[]));
}
}