use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
use sp_api::ApiError;
use sp_version::RuntimeVersion;
use std::num::NonZeroUsize;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NetworkConfig {
total_attempts: u64,
max_parallel: NonZeroUsize,
timeout_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorEvent {
pub error: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeVersionEvent {
pub spec: RuntimeVersion,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type")]
pub enum RuntimeEvent {
Valid(RuntimeVersionEvent),
Invalid(ErrorEvent),
}
impl From<ApiError> for RuntimeEvent {
fn from(err: ApiError) -> Self {
RuntimeEvent::Invalid(ErrorEvent { error: format!("Api error: {}", err) })
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Initialized<Hash> {
pub finalized_block_hash: Hash,
pub finalized_block_runtime: Option<RuntimeEvent>,
#[serde(default)]
pub(crate) runtime_updates: bool,
}
impl<Hash: Serialize> Serialize for Initialized<Hash> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if self.runtime_updates {
let mut state = serializer.serialize_struct("Initialized", 2)?;
state.serialize_field("finalizedBlockHash", &self.finalized_block_hash)?;
state.serialize_field("finalizedBlockRuntime", &self.finalized_block_runtime)?;
state.end()
} else {
let mut state = serializer.serialize_struct("Initialized", 1)?;
state.serialize_field("finalizedBlockHash", &self.finalized_block_hash)?;
state.end()
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NewBlock<Hash> {
pub block_hash: Hash,
pub parent_block_hash: Hash,
pub new_runtime: Option<RuntimeEvent>,
#[serde(default)]
pub(crate) runtime_updates: bool,
}
impl<Hash: Serialize> Serialize for NewBlock<Hash> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if self.runtime_updates {
let mut state = serializer.serialize_struct("NewBlock", 3)?;
state.serialize_field("blockHash", &self.block_hash)?;
state.serialize_field("parentBlockHash", &self.parent_block_hash)?;
state.serialize_field("newRuntime", &self.new_runtime)?;
state.end()
} else {
let mut state = serializer.serialize_struct("NewBlock", 2)?;
state.serialize_field("blockHash", &self.block_hash)?;
state.serialize_field("parentBlockHash", &self.parent_block_hash)?;
state.end()
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BestBlockChanged<Hash> {
pub best_block_hash: Hash,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Finalized<Hash> {
pub finalized_block_hashes: Vec<Hash>,
pub pruned_block_hashes: Vec<Hash>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "event")]
pub enum FollowEvent<Hash> {
Initialized(Initialized<Hash>),
NewBlock(NewBlock<Hash>),
BestBlockChanged(BestBlockChanged<Hash>),
Finalized(Finalized<Hash>),
Stop,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChainHeadResult<T> {
pub result: T,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "event")]
pub enum ChainHeadEvent<T> {
Done(ChainHeadResult<T>),
Inaccessible(ErrorEvent),
Error(ErrorEvent),
Disjoint,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn follow_initialized_event_no_updates() {
let event: FollowEvent<String> = FollowEvent::Initialized(Initialized {
finalized_block_hash: "0x1".into(),
finalized_block_runtime: None,
runtime_updates: false,
});
let ser = serde_json::to_string(&event).unwrap();
let exp = r#"{"event":"initialized","finalizedBlockHash":"0x1"}"#;
assert_eq!(ser, exp);
let event_dec: FollowEvent<String> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, event);
}
#[test]
fn follow_initialized_event_with_updates() {
let runtime = RuntimeVersion {
spec_name: "ABC".into(),
impl_name: "Impl".into(),
spec_version: 1,
..Default::default()
};
let runtime_event = RuntimeEvent::Valid(RuntimeVersionEvent { spec: runtime });
let mut initialized = Initialized {
finalized_block_hash: "0x1".into(),
finalized_block_runtime: Some(runtime_event),
runtime_updates: true,
};
let event: FollowEvent<String> = FollowEvent::Initialized(initialized.clone());
let ser = serde_json::to_string(&event).unwrap();
let exp = concat!(
r#"{"event":"initialized","finalizedBlockHash":"0x1","#,
r#""finalizedBlockRuntime":{"type":"valid","spec":{"specName":"ABC","implName":"Impl","authoringVersion":0,"#,
r#""specVersion":1,"implVersion":0,"apis":[],"transactionVersion":0,"stateVersion":0}}}"#,
);
assert_eq!(ser, exp);
let event_dec: FollowEvent<String> = serde_json::from_str(exp).unwrap();
initialized.runtime_updates = false;
assert!(matches!(
event_dec, FollowEvent::Initialized(ref dec) if dec == &initialized
));
}
#[test]
fn follow_new_block_event_no_updates() {
let event: FollowEvent<String> = FollowEvent::NewBlock(NewBlock {
block_hash: "0x1".into(),
parent_block_hash: "0x2".into(),
new_runtime: None,
runtime_updates: false,
});
let ser = serde_json::to_string(&event).unwrap();
let exp = r#"{"event":"newBlock","blockHash":"0x1","parentBlockHash":"0x2"}"#;
assert_eq!(ser, exp);
let event_dec: FollowEvent<String> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, event);
}
#[test]
fn follow_new_block_event_with_updates() {
let runtime = RuntimeVersion {
spec_name: "ABC".into(),
impl_name: "Impl".into(),
spec_version: 1,
..Default::default()
};
let runtime_event = RuntimeEvent::Valid(RuntimeVersionEvent { spec: runtime });
let mut new_block = NewBlock {
block_hash: "0x1".into(),
parent_block_hash: "0x2".into(),
new_runtime: Some(runtime_event),
runtime_updates: true,
};
let event: FollowEvent<String> = FollowEvent::NewBlock(new_block.clone());
let ser = serde_json::to_string(&event).unwrap();
let exp = concat!(
r#"{"event":"newBlock","blockHash":"0x1","parentBlockHash":"0x2","#,
r#""newRuntime":{"type":"valid","spec":{"specName":"ABC","implName":"Impl","authoringVersion":0,"#,
r#""specVersion":1,"implVersion":0,"apis":[],"transactionVersion":0,"stateVersion":0}}}"#,
);
assert_eq!(ser, exp);
let event_dec: FollowEvent<String> = serde_json::from_str(exp).unwrap();
new_block.runtime_updates = false;
assert!(matches!(
event_dec, FollowEvent::NewBlock(ref dec) if dec == &new_block
));
let mut new_block = NewBlock {
block_hash: "0x1".into(),
parent_block_hash: "0x2".into(),
new_runtime: None,
runtime_updates: true,
};
let event: FollowEvent<String> = FollowEvent::NewBlock(new_block.clone());
let ser = serde_json::to_string(&event).unwrap();
let exp =
r#"{"event":"newBlock","blockHash":"0x1","parentBlockHash":"0x2","newRuntime":null}"#;
assert_eq!(ser, exp);
new_block.runtime_updates = false;
let event_dec: FollowEvent<String> = serde_json::from_str(exp).unwrap();
assert!(matches!(
event_dec, FollowEvent::NewBlock(ref dec) if dec == &new_block
));
}
#[test]
fn follow_best_block_changed_event() {
let event: FollowEvent<String> =
FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash: "0x1".into() });
let ser = serde_json::to_string(&event).unwrap();
let exp = r#"{"event":"bestBlockChanged","bestBlockHash":"0x1"}"#;
assert_eq!(ser, exp);
let event_dec: FollowEvent<String> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, event);
}
#[test]
fn follow_finalized_event() {
let event: FollowEvent<String> = FollowEvent::Finalized(Finalized {
finalized_block_hashes: vec!["0x1".into()],
pruned_block_hashes: vec!["0x2".into()],
});
let ser = serde_json::to_string(&event).unwrap();
let exp =
r#"{"event":"finalized","finalizedBlockHashes":["0x1"],"prunedBlockHashes":["0x2"]}"#;
assert_eq!(ser, exp);
let event_dec: FollowEvent<String> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, event);
}
#[test]
fn follow_stop_event() {
let event: FollowEvent<String> = FollowEvent::Stop;
let ser = serde_json::to_string(&event).unwrap();
let exp = r#"{"event":"stop"}"#;
assert_eq!(ser, exp);
let event_dec: FollowEvent<String> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, event);
}
#[test]
fn chain_head_done_event() {
let event: ChainHeadEvent<String> =
ChainHeadEvent::Done(ChainHeadResult { result: "A".into() });
let ser = serde_json::to_string(&event).unwrap();
let exp = r#"{"event":"done","result":"A"}"#;
assert_eq!(ser, exp);
let event_dec: ChainHeadEvent<String> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, event);
}
#[test]
fn chain_head_inaccessible_event() {
let event: ChainHeadEvent<String> =
ChainHeadEvent::Inaccessible(ErrorEvent { error: "A".into() });
let ser = serde_json::to_string(&event).unwrap();
let exp = r#"{"event":"inaccessible","error":"A"}"#;
assert_eq!(ser, exp);
let event_dec: ChainHeadEvent<String> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, event);
}
#[test]
fn chain_head_error_event() {
let event: ChainHeadEvent<String> = ChainHeadEvent::Error(ErrorEvent { error: "A".into() });
let ser = serde_json::to_string(&event).unwrap();
let exp = r#"{"event":"error","error":"A"}"#;
assert_eq!(ser, exp);
let event_dec: ChainHeadEvent<String> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, event);
}
#[test]
fn chain_head_disjoint_event() {
let event: ChainHeadEvent<String> = ChainHeadEvent::Disjoint;
let ser = serde_json::to_string(&event).unwrap();
let exp = r#"{"event":"disjoint"}"#;
assert_eq!(ser, exp);
let event_dec: ChainHeadEvent<String> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, event);
}
#[test]
fn chain_head_network_config() {
let conf = NetworkConfig {
total_attempts: 1,
max_parallel: NonZeroUsize::new(2).expect("Non zero number; qed"),
timeout_ms: 3,
};
let ser = serde_json::to_string(&conf).unwrap();
let exp = r#"{"totalAttempts":1,"maxParallel":2,"timeoutMs":3}"#;
assert_eq!(ser, exp);
let conf_dec: NetworkConfig = serde_json::from_str(exp).unwrap();
assert_eq!(conf_dec, conf);
}
}