use crate::Hash;
use crate::client::{RpcClient, RpcSubscription, rpc_params};
use crate::{Error, RpcConfig};
use derive_where::derive_where;
use futures::{Stream, StreamExt};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::{HashMap, VecDeque};
use std::task::Poll;
#[derive_where(Clone, Debug)]
pub struct ChainHeadRpcMethods<T> {
client: RpcClient,
_marker: std::marker::PhantomData<T>,
}
impl<T: RpcConfig> ChainHeadRpcMethods<T> {
pub fn new(client: RpcClient) -> Self {
ChainHeadRpcMethods {
client,
_marker: std::marker::PhantomData,
}
}
pub async fn chainhead_v1_follow(
&self,
with_runtime: bool,
) -> Result<FollowSubscription<T::Hash>, Error> {
let sub = self
.client
.subscribe(
"chainHead_v1_follow",
rpc_params![with_runtime],
"chainHead_v1_unfollow",
)
.await?;
Ok(FollowSubscription { sub, done: false })
}
pub async fn chainhead_v1_continue(
&self,
follow_subscription: &str,
operation_id: &str,
) -> Result<(), Error> {
self.client
.request(
"chainHead_v1_continue",
rpc_params![follow_subscription, operation_id],
)
.await
}
pub async fn chainhead_v1_stop_operation(
&self,
follow_subscription: &str,
operation_id: &str,
) -> Result<(), Error> {
self.client
.request(
"chainHead_v1_stopOperation",
rpc_params![follow_subscription, operation_id],
)
.await
}
pub async fn chainhead_v1_body(
&self,
subscription_id: &str,
hash: T::Hash,
) -> Result<MethodResponse, Error> {
let response = self
.client
.request("chainHead_v1_body", rpc_params![subscription_id, hash])
.await?;
Ok(response)
}
pub async fn chainhead_v1_header(
&self,
subscription_id: &str,
hash: T::Hash,
) -> Result<Option<T::Header>, Error> {
let header: Option<Bytes> = self
.client
.request("chainHead_v1_header", rpc_params![subscription_id, hash])
.await?;
let header = header
.map(|h| codec::Decode::decode(&mut &*h.0))
.transpose()
.map_err(Error::Decode)?;
Ok(header)
}
pub async fn chainhead_v1_storage(
&self,
subscription_id: &str,
hash: T::Hash,
items: impl IntoIterator<Item = StorageQuery<&[u8]>>,
child_key: Option<&[u8]>,
) -> Result<MethodResponse, Error> {
let items: Vec<StorageQuery<String>> = items
.into_iter()
.map(|item| StorageQuery {
key: to_hex(item.key),
query_type: item.query_type,
})
.collect();
let response = self
.client
.request(
"chainHead_v1_storage",
rpc_params![subscription_id, hash, items, child_key.map(to_hex)],
)
.await?;
Ok(response)
}
pub async fn chainhead_v1_call(
&self,
subscription_id: &str,
hash: T::Hash,
function: &str,
call_parameters: &[u8],
) -> Result<MethodResponse, Error> {
let response = self
.client
.request(
"chainHead_v1_call",
rpc_params![subscription_id, hash, function, to_hex(call_parameters)],
)
.await?;
Ok(response)
}
pub async fn chainhead_v1_unpin(
&self,
subscription_id: &str,
hash: T::Hash,
) -> Result<(), Error> {
self.client
.request("chainHead_v1_unpin", rpc_params![subscription_id, hash])
.await
}
pub async fn chainspec_v1_genesis_hash(&self) -> Result<T::Hash, Error> {
self.client
.request("chainSpec_v1_genesisHash", rpc_params![])
.await
}
pub async fn chainspec_v1_chain_name(&self) -> Result<String, Error> {
self.client
.request("chainSpec_v1_chainName", rpc_params![])
.await
}
pub async fn chainspec_v1_properties<Props: serde::de::DeserializeOwned>(
&self,
) -> Result<Props, Error> {
self.client
.request("chainSpec_v1_properties", rpc_params![])
.await
}
pub async fn rpc_methods(&self) -> Result<Vec<String>, Error> {
self.client.request("rpc_methods", rpc_params![]).await
}
pub async fn transactionwatch_v1_submit_and_watch(
&self,
tx: &[u8],
) -> Result<TransactionSubscription<T::Hash>, Error> {
let sub = self
.client
.subscribe(
"transactionWatch_v1_submitAndWatch",
rpc_params![to_hex(tx)],
"transactionWatch_v1_unwatch",
)
.await?;
Ok(TransactionSubscription { sub, done: false })
}
pub async fn transaction_v1_broadcast(&self, tx: &[u8]) -> Result<Option<String>, Error> {
self.client
.request("transaction_v1_broadcast", rpc_params![to_hex(tx)])
.await
}
pub async fn transaction_v1_stop(&self, operation_id: &str) -> Result<(), Error> {
self.client
.request("transaction_v1_stop", rpc_params![operation_id])
.await
}
pub async fn archive_v1_body(&self, block_hash: T::Hash) -> Result<Option<Vec<Bytes>>, Error> {
self.client
.request("archive_v1_body", rpc_params![block_hash])
.await
}
pub async fn archive_v1_call(
&self,
block_hash: T::Hash,
function: &str,
call_parameters: &[u8],
) -> Result<ArchiveCallResult, Error> {
use serde::de::Error as _;
#[derive(Deserialize)]
struct Response {
success: bool,
value: Option<Bytes>,
error: Option<String>,
result: Option<Bytes>,
}
let res: Response = self
.client
.request(
"archive_v1_call",
rpc_params![block_hash, function, to_hex(call_parameters)],
)
.await?;
let value = res.value.or(res.result);
match (res.success, value, res.error) {
(true, Some(value), _) => Ok(ArchiveCallResult::Success(value)),
(false, _, err) => Ok(ArchiveCallResult::Error(err.unwrap_or(String::new()))),
(true, None, _) => {
let m = "archive_v1_call: 'success: true' response should have `value: 0x1234` alongside it";
Err(Error::Deserialization(serde_json::Error::custom(m)))
}
}
}
pub async fn archive_v1_finalized_height(&self) -> Result<usize, Error> {
self.client
.request("archive_v1_finalizedHeight", rpc_params![])
.await
}
pub async fn archive_v1_genesis_hash(&self) -> Result<T::Hash, Error> {
self.client
.request("archive_v1_genesisHash", rpc_params![])
.await
}
pub async fn archive_v1_hash_by_height(&self, height: usize) -> Result<Vec<T::Hash>, Error> {
self.client
.request("archive_v1_hashByHeight", rpc_params![height])
.await
}
pub async fn archive_v1_header(&self, block_hash: T::Hash) -> Result<Option<T::Header>, Error> {
let maybe_encoded_header: Option<Bytes> = self
.client
.request("archive_v1_header", rpc_params![block_hash])
.await?;
let Some(encoded_header) = maybe_encoded_header else {
return Ok(None);
};
let header =
<T::Header as codec::Decode>::decode(&mut &*encoded_header.0).map_err(Error::Decode)?;
Ok(Some(header))
}
pub async fn archive_v1_storage(
&self,
block_hash: T::Hash,
items: impl IntoIterator<Item = ArchiveStorageQuery<&[u8]>>,
child_key: Option<&[u8]>,
) -> Result<ArchiveStorageSubscription<T::Hash>, Error> {
let items: Vec<ArchiveStorageQuery<String>> = items
.into_iter()
.map(|item| ArchiveStorageQuery {
key: to_hex(item.key),
query_type: item.query_type,
pagination_start_key: item.pagination_start_key.map(to_hex),
})
.collect();
let sub = self
.client
.subscribe(
"archive_v1_storage",
rpc_params![block_hash, items, child_key.map(to_hex)],
"archive_v1_stopStorage",
)
.await?;
Ok(ArchiveStorageSubscription { sub, done: false })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "event")]
pub enum FollowEvent<Hash> {
Initialized(Initialized<Hash>),
NewBlock(NewBlock<Hash>),
BestBlockChanged(BestBlockChanged<Hash>),
Finalized(Finalized<Hash>),
OperationBodyDone(OperationBodyDone),
OperationCallDone(OperationCallDone),
OperationStorageItems(OperationStorageItems),
OperationWaitingForContinue(OperationId),
OperationStorageDone(OperationId),
OperationInaccessible(OperationId),
OperationError(OperationError),
Stop,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Initialized<Hash> {
pub finalized_block_hashes: Vec<Hash>,
pub finalized_block_runtime: Option<RuntimeEvent>,
}
impl<'de, Hash: Deserialize<'de>> Deserialize<'de> for Initialized<Hash> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
struct InitializedIR<Hash> {
finalized_block_hashes: Option<Vec<Hash>>,
finalized_block_hash: Option<Hash>,
finalized_block_runtime: Option<RuntimeEvent>,
}
let ir = InitializedIR::deserialize(deserializer)?;
let finalized_block_hashes = ir
.finalized_block_hashes
.or_else(|| ir.finalized_block_hash.map(|hash| vec![hash]))
.ok_or_else(|| serde::de::Error::custom("Missing finalized block hashes"))?;
Ok(Initialized {
finalized_block_hashes,
finalized_block_runtime: ir.finalized_block_runtime,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type")]
pub enum RuntimeEvent {
Valid(RuntimeVersionEvent),
Invalid(ErrorEvent),
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeVersionEvent {
pub spec: RuntimeSpec,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeSpec {
pub spec_name: String,
pub impl_name: String,
pub spec_version: u32,
pub impl_version: u32,
pub transaction_version: u32,
#[serde(with = "hashmap_as_tuple_list")]
pub apis: HashMap<String, u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorEvent {
pub error: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NewBlock<Hash> {
pub block_hash: Hash,
pub parent_block_hash: Hash,
pub new_runtime: Option<RuntimeEvent>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BestBlockChanged<Hash> {
pub best_block_hash: Hash,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Finalized<Hash> {
pub finalized_block_hashes: Vec<Hash>,
pub pruned_block_hashes: Vec<Hash>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OperationId {
pub operation_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OperationBodyDone {
pub operation_id: String,
pub value: Vec<Bytes>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OperationCallDone {
pub operation_id: String,
pub output: Bytes,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OperationStorageItems {
pub operation_id: String,
pub items: VecDeque<StorageResult>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OperationError {
pub operation_id: String,
pub error: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StorageResult {
pub key: Bytes,
#[serde(flatten)]
pub result: StorageResultType,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum StorageResultType {
Value(Bytes),
Hash(Bytes),
ClosestDescendantMerkleValue(Bytes),
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "result")]
pub enum MethodResponse {
Started(MethodResponseStarted),
LimitReached,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MethodResponseStarted {
pub operation_id: String,
pub discarded_items: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StorageQuery<Key> {
pub key: Key,
#[serde(rename = "type")]
pub query_type: StorageQueryType,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArchiveStorageQuery<Key> {
pub key: Key,
#[serde(rename = "type")]
pub query_type: StorageQueryType,
#[serde(skip_serializing_if = "Option::is_none")]
pub pagination_start_key: Option<Key>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum StorageQueryType {
Value,
Hash,
ClosestDescendantMerkleValue,
DescendantsValues,
DescendantsHashes,
}
pub struct FollowSubscription<Hash> {
sub: RpcSubscription<FollowEvent<Hash>>,
done: bool,
}
impl<H: Hash> FollowSubscription<H> {
pub async fn next(&mut self) -> Option<<Self as Stream>::Item> {
<Self as StreamExt>::next(self).await
}
pub fn subscription_id(&self) -> Option<&str> {
self.sub.subscription_id()
}
}
impl<H: Hash> Stream for FollowSubscription<H> {
type Item = <RpcSubscription<FollowEvent<H>> as Stream>::Item;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
if self.done {
return Poll::Ready(None);
}
let res = self.sub.poll_next_unpin(cx);
if let Poll::Ready(Some(Ok(FollowEvent::Stop))) = &res {
self.done = true;
}
res
}
}
pub struct TransactionSubscription<Hash> {
sub: RpcSubscription<TransactionStatus<Hash>>,
done: bool,
}
impl<H: Hash> TransactionSubscription<H> {
pub async fn next(&mut self) -> Option<<Self as Stream>::Item> {
<Self as StreamExt>::next(self).await
}
}
impl<H: Hash> Stream for TransactionSubscription<H> {
type Item = <RpcSubscription<TransactionStatus<H>> as Stream>::Item;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
if self.done {
return Poll::Ready(None);
}
let res = self.sub.poll_next_unpin(cx);
if let Poll::Ready(Some(Ok(res))) = &res {
if matches!(
res,
TransactionStatus::Dropped { .. }
| TransactionStatus::Error { .. }
| TransactionStatus::Invalid { .. }
| TransactionStatus::Finalized { .. }
) {
self.done = true
}
}
res
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "event")]
pub enum TransactionStatus<Hash> {
Validated,
Broadcasted,
BestChainBlockIncluded {
block: Option<TransactionBlockDetails<Hash>>,
},
Finalized {
block: TransactionBlockDetails<Hash>,
},
Error {
error: String,
},
Invalid {
error: String,
},
Dropped {
error: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct TransactionBlockDetails<Hash> {
pub hash: Hash,
#[serde(with = "unsigned_number_as_string")]
pub index: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArchiveCallResult {
Success(Bytes),
Error(String),
}
impl ArchiveCallResult {
pub fn as_success(self) -> Option<Bytes> {
match self {
ArchiveCallResult::Success(bytes) => Some(bytes),
_ => None,
}
}
pub fn as_error(self) -> Option<String> {
match self {
ArchiveCallResult::Success(_) => None,
ArchiveCallResult::Error(e) => Some(e),
}
}
}
pub struct ArchiveStorageSubscription<Hash> {
sub: RpcSubscription<ArchiveStorageEvent<Hash>>,
done: bool,
}
impl<H: Hash> ArchiveStorageSubscription<H> {
pub async fn next(&mut self) -> Option<<Self as Stream>::Item> {
<Self as StreamExt>::next(self).await
}
pub fn subscription_id(&self) -> Option<&str> {
self.sub.subscription_id()
}
}
impl<H: Hash> Stream for ArchiveStorageSubscription<H> {
type Item = <RpcSubscription<ArchiveStorageEvent<H>> as Stream>::Item;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
if self.done {
return Poll::Ready(None);
}
let res = self.sub.poll_next_unpin(cx);
if let Poll::Ready(Some(Ok(ArchiveStorageEvent::Done | ArchiveStorageEvent::Error(..)))) =
&res
{
self.done = true;
}
res
}
}
#[derive(Debug, Deserialize)]
#[serde(tag = "event")]
pub enum ArchiveStorageEvent<Hash> {
#[serde(rename = "storage")]
Item(ArchiveStorageEventItem<Hash>),
#[serde(rename = "storageError")]
Error(ArchiveStorageEventError),
#[serde(rename = "storageDone")]
Done,
}
impl<Hash> ArchiveStorageEvent<Hash> {
pub fn as_item(self) -> Option<ArchiveStorageEventItem<Hash>> {
match self {
ArchiveStorageEvent::Item(item) => Some(item),
_ => None,
}
}
pub fn as_error(self) -> Option<ArchiveStorageEventError> {
match self {
ArchiveStorageEvent::Error(e) => Some(e),
_ => None,
}
}
pub fn is_done(self) -> bool {
matches!(self, ArchiveStorageEvent::Done)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArchiveStorageEventError {
pub error: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArchiveStorageEventItem<Hash> {
pub key: Bytes,
pub value: Option<Bytes>,
pub hash: Option<Hash>,
pub closest_descendant_merkle_value: Option<Bytes>,
pub child_trie_key: Option<Bytes>,
}
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize, Hash, PartialOrd, Ord, Debug)]
pub struct Bytes(#[serde(with = "impl_serde::serialize")] pub Vec<u8>);
impl std::ops::Deref for Bytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0[..]
}
}
impl From<Vec<u8>> for Bytes {
fn from(s: Vec<u8>) -> Self {
Bytes(s)
}
}
fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
}
pub(crate) mod unsigned_number_as_string {
use serde::de::{Deserializer, Visitor};
use std::fmt;
pub fn deserialize<'de, N: From<u64>, D>(deserializer: D) -> Result<N, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(NumberVisitor(std::marker::PhantomData))
}
struct NumberVisitor<N>(std::marker::PhantomData<N>);
impl<N: From<u64>> Visitor<'_> for NumberVisitor<N> {
type Value = N;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an unsigned integer or a string containing one")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
let n: u64 = v.parse().map_err(serde::de::Error::custom)?;
Ok(n.into())
}
fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> {
Ok(v.into())
}
}
use serde::ser::Serializer;
pub fn serialize<S>(item: &u64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&item.to_string())
}
}
pub(crate) mod hashmap_as_tuple_list {
use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
use std::collections::HashMap;
use std::fmt;
use std::hash::{BuildHasher, Hash};
use std::marker::PhantomData;
pub fn deserialize<'de, K, V, BH, D>(deserializer: D) -> Result<HashMap<K, V, BH>, D::Error>
where
D: Deserializer<'de>,
K: Eq + Hash + Deserialize<'de>,
V: Deserialize<'de>,
BH: BuildHasher + Default,
{
deserializer.deserialize_any(HashMapVisitor(PhantomData))
}
#[allow(clippy::type_complexity)]
struct HashMapVisitor<K, V, BH>(PhantomData<fn() -> HashMap<K, V, BH>>);
impl<'de, K, V, BH> Visitor<'de> for HashMapVisitor<K, V, BH>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
BH: BuildHasher + Default,
{
type Value = HashMap<K, V, BH>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a list of key-value pairs")
}
fn visit_map<A>(self, mut m: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut map =
HashMap::with_capacity_and_hasher(m.size_hint().unwrap_or(0), BH::default());
while let Some((key, value)) = m.next_entry()? {
map.insert(key, value);
}
Ok(map)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut map =
HashMap::with_capacity_and_hasher(seq.size_hint().unwrap_or(0), BH::default());
while let Some((key, value)) = seq.next_element()? {
map.insert(key, value);
}
Ok(map)
}
}
use serde::ser::{Serialize, SerializeSeq, Serializer};
pub fn serialize<S, K: Eq + Hash + Serialize, V: Serialize>(
item: &HashMap<K, V>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(None)?;
for i in item {
seq.serialize_element(&i)?;
}
seq.end()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn can_deserialize_apis_from_tuple_or_object() {
let old_response = serde_json::json!({
"authoringVersion": 10,
"specName": "westend",
"implName": "parity-westend",
"specVersion": 9122,
"implVersion": 0,
"stateVersion": 1,
"transactionVersion": 7,
"apis": [
["0xdf6acb689907609b", 3],
["0x37e397fc7c91f5e4", 1],
["0x40fe3ad401f8959a", 5],
["0xd2bc9897eed08f15", 3],
["0xf78b278be53f454c", 2],
["0xaf2c0297a23e6d3d", 1],
["0x49eaaf1b548a0cb0", 1],
["0x91d5df18b0d2cf58", 1],
["0xed99c5acb25eedf5", 3],
["0xcbca25e39f142387", 2],
["0x687ad44ad37f03c2", 1],
["0xab3c0572291feb8b", 1],
["0xbc9d89904f5b923f", 1],
["0x37c8bb1350a9a2a8", 1]
]
});
let old_spec: RuntimeSpec = serde_json::from_value(old_response).unwrap();
let new_response = serde_json::json!({
"specName": "westend",
"implName": "parity-westend",
"specVersion": 9122,
"implVersion": 0,
"transactionVersion": 7,
"apis": {
"0xdf6acb689907609b": 3,
"0x37e397fc7c91f5e4": 1,
"0x40fe3ad401f8959a": 5,
"0xd2bc9897eed08f15": 3,
"0xf78b278be53f454c": 2,
"0xaf2c0297a23e6d3d": 1,
"0x49eaaf1b548a0cb0": 1,
"0x91d5df18b0d2cf58": 1,
"0xed99c5acb25eedf5": 3,
"0xcbca25e39f142387": 2,
"0x687ad44ad37f03c2": 1,
"0xab3c0572291feb8b": 1,
"0xbc9d89904f5b923f": 1,
"0x37c8bb1350a9a2a8": 1
}
});
let new_spec: RuntimeSpec = serde_json::from_value(new_response).unwrap();
assert_eq!(old_spec, new_spec);
}
#[test]
fn can_deserialize_from_number_or_string() {
#[derive(Debug, Deserialize)]
struct Foo64 {
#[serde(with = "super::unsigned_number_as_string")]
num: u64,
}
#[derive(Debug, Deserialize)]
struct Foo32 {
#[serde(with = "super::unsigned_number_as_string")]
num: u128,
}
let from_string = serde_json::json!({
"num": "123"
});
let from_num = serde_json::json!({
"num": 123
});
let from_err = serde_json::json!({
"num": "123a"
});
let f1: Foo64 =
serde_json::from_value(from_string.clone()).expect("can deser string into u64");
let f2: Foo32 = serde_json::from_value(from_string).expect("can deser string into u32");
let f3: Foo64 = serde_json::from_value(from_num.clone()).expect("can deser num into u64");
let f4: Foo32 = serde_json::from_value(from_num).expect("can deser num into u32");
assert_eq!(f1.num, 123);
assert_eq!(f2.num, 123);
assert_eq!(f3.num, 123);
assert_eq!(f4.num, 123);
let _ = serde_json::from_value::<Foo32>(from_err)
.expect_err("can't deser invalid num into u32");
}
#[test]
fn chain_head_initialized() {
let event = serde_json::json!({
"finalizedBlockHashes": ["0x1", "0x2"],
});
let decoded: Initialized<String> = serde_json::from_value(event).unwrap();
assert_eq!(
decoded.finalized_block_hashes,
vec!["0x1".to_string(), "0x2".to_string()]
);
let event = serde_json::json!({
"finalizedBlockHash": "0x1",
});
let decoded: Initialized<String> = serde_json::from_value(event).unwrap();
assert_eq!(decoded.finalized_block_hashes, vec!["0x1".to_string()]);
let event = serde_json::json!({
"finalizedBlockHash": ["0x1"],
});
let _ = serde_json::from_value::<Initialized<String>>(event).unwrap_err();
}
}