subalfred_core/node/
mod.rs1#[cfg(test)] mod test;
4
5use std::{
7 io::{BufRead, BufReader},
8 process::{Child, Command, Stdio},
9 time::Duration,
10};
11use array_bytes::TryFromHex;
13use parity_scale_codec::Decode;
14use serde::Serialize;
15use 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
28pub 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 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
54pub 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
68pub 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}
81pub 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
93pub async fn find_runtime_upgrade_block(
96 runtime_version: u32,
97 uri: &str,
98 timeout: Duration,
99) -> Result<Option<(u32, String)>> {
100 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}