Skip to main content

subalfred_core/node/
mod.rs

1//! Subalfred core node library.
2
3#[cfg(test)] mod test;
4
5// std
6use std::{
7	io::{BufRead, BufReader},
8	process::{Child, Command, Stdio},
9	time::Duration,
10};
11// crates.io
12use array_bytes::TryFromHex;
13use parity_scale_codec::Decode;
14use serde::Serialize;
15// subalfred
16use crate::{jsonrpc::http, prelude::*};
17use submetadatan::{LatestRuntimeMetadata, frame_metadata::RuntimeMetadataPrefixed};
18use subrpcer::state;
19use subversioner::RuntimeVersion;
20
21const E_BLOCK_NUMBER_IS_NON_HEX: &str =
22	"[core::node] block number is non-hex, maybe the Substrate RPC SPEC changed";
23const E_CODEC_METADATA_IS_NON_HEX: &str =
24	"[core::node] `codec_metadata` is non-hex, maybe the Substrate RPC SPEC changed";
25const E_STDERR_IS_EMPTY: &str =
26	"[core::node] `stderr` is empty, , maybe the substrate node behavior changed";
27
28/// Spawn a Substrate-like standard node.
29pub fn spawn(executable: &str, rpc_port: u16, chain: &str) -> Result<Child> {
30	let mut node = Command::new(executable)
31		.stdout(Stdio::null())
32		.stderr(Stdio::piped())
33		.args([&format!("--rpc-port={rpc_port}"), "--chain", chain, "--tmp"])
34		.spawn()
35		.map_err(error::Node::StartNodeFailed)?;
36	let output = BufReader::new(
37		node.stderr.take().ok_or_else(|| error::almost_impossible(E_STDERR_IS_EMPTY))?,
38	);
39
40	// Ensure the node is fully startup.
41	for line in output.lines() {
42		let line = line.map_err(error::Generic::Io)?;
43
44		tracing::trace!("node({rpc_port}) {line}");
45
46		if ["Idle", "Imported", "Syncing"].iter().any(|s| line.contains(s)) {
47			break;
48		}
49	}
50
51	Ok(node)
52}
53
54/// Get runtime version from a nodes's HTTP RPC endpoint.
55pub async fn runtime_version<Hash>(
56	uri: &str,
57	at: Option<Hash>,
58	timeout: Duration,
59) -> Result<RuntimeVersion>
60where
61	Hash: Serialize,
62{
63	Ok(http::send::<_, RuntimeVersion>(uri, &state::get_runtime_version(0, at), timeout)
64		.await?
65		.result)
66}
67
68/// Fetch runtime metadata from a nodes's HTTP RPC endpoint.
69pub async fn runtime_metadata<Hash>(
70	uri: &str,
71	at: Option<Hash>,
72	timeout: Duration,
73) -> Result<LatestRuntimeMetadata>
74where
75	Hash: Serialize,
76{
77	let response = http::send::<_, String>(uri, &state::get_metadata(0, at), timeout).await?;
78
79	parse_raw_runtime_metadata(&response.result)
80}
81/// Parse the raw metadata.
82pub fn parse_raw_runtime_metadata(raw_runtime_metadata: &str) -> Result<LatestRuntimeMetadata> {
83	let codec_metadata = array_bytes::hex2bytes(raw_runtime_metadata)
84		.map_err(|_| error::almost_impossible(E_CODEC_METADATA_IS_NON_HEX))?;
85	let metadata_prefixed =
86		RuntimeMetadataPrefixed::decode(&mut &*codec_metadata).map_err(error::Generic::Codec)?;
87	let metadata = submetadatan::unprefix_metadata(metadata_prefixed)
88		.map_err(error::Node::ParseMetadataFailed)?;
89
90	Ok(metadata)
91}
92
93// TODO: move to somewhere
94/// Find the runtime upgrade that happened at which block with the dichotomy algorithm.
95pub async fn find_runtime_upgrade_block(
96	runtime_version: u32,
97	uri: &str,
98	timeout: Duration,
99) -> Result<Option<(u32, String)>> {
100	// subalfred
101	use crate::{
102		jsonrpc::ws::Initializer,
103		substrate_client::{Apis, Client},
104	};
105
106	let client = Client::initialize(Initializer::new().request_timeout(timeout), uri).await?;
107	let best_finalized_hash = client.get_finalized_head().await?;
108	let mut left = 0;
109	let mut right =
110		u32::try_from_hex(client.get_header::<String, _>(Some(best_finalized_hash)).await?.number)
111			.map_err(|_| error::almost_impossible(E_BLOCK_NUMBER_IS_NON_HEX))?;
112	let mut mid = right / 2;
113
114	loop {
115		let block_hash = client.get_block_hash(Some(mid)).await?;
116		let fetched_runtime_version =
117			client.get_runtime_version(Some(&block_hash)).await?.spec_version;
118
119		tracing::trace!("({left}, {right}) -> {fetched_runtime_version}");
120
121		if left == mid || right == mid {
122			let block_number = mid + 1;
123			let block_hash = client.get_block_hash(Some(block_number)).await?;
124			let fetched_runtime_version =
125				client.get_runtime_version(Some(&block_hash)).await?.spec_version;
126
127			if fetched_runtime_version == runtime_version {
128				return Ok(Some((block_number, block_hash)));
129			} else {
130				return Ok(None);
131			}
132		}
133
134		if fetched_runtime_version >= runtime_version {
135			right = mid;
136			mid -= (mid - left) / 2;
137		} else {
138			left = mid;
139			mid += (right - mid) / 2;
140		}
141	}
142}