zebra_checkpoints/
main.rs1use std::{ffi::OsString, process::Stdio};
12
13#[cfg(unix)]
14use std::os::unix::process::ExitStatusExt;
15
16use color_eyre::{
17 eyre::{ensure, eyre, Result},
18 Help,
19};
20use itertools::Itertools;
21use serde_json::Value;
22use structopt::StructOpt;
23
24use zebra_chain::{
25 block::{self, Block, Height, HeightDiff, TryIntoHeight},
26 serialization::ZcashDeserializeInto,
27 transparent::MIN_TRANSPARENT_COINBASE_MATURITY,
28};
29use zebra_node_services::{
30 constants::{MAX_CHECKPOINT_BYTE_COUNT, MAX_CHECKPOINT_HEIGHT_GAP},
31 rpc_client::RpcRequestClient,
32};
33use zebra_utils::init_tracing;
34
35pub mod args;
36
37use args::{Args, Backend, Transport};
38
39async fn rpc_output<M, I>(our_args: &Args, method: M, params: I) -> Result<Value>
41where
42 M: AsRef<str>,
43 I: IntoIterator<Item = String>,
44{
45 match our_args.transport {
46 Transport::Cli => cli_output(our_args, method, params),
47 Transport::Direct => direct_output(our_args, method, params).await,
48 }
49}
50
51async fn direct_output<M, I>(our_args: &Args, method: M, params: I) -> Result<Value>
55where
56 M: AsRef<str>,
57 I: IntoIterator<Item = String>,
58{
59 let addr = our_args
61 .addr
62 .unwrap_or_else(|| "127.0.0.1:8232".parse().expect("valid address"));
63 let client = RpcRequestClient::new(addr);
64
65 let params = format!("[{}]", params.into_iter().join(", "));
70 let response = client.text_from_call(method, params).await?;
71
72 let mut response: Value = serde_json::from_str(&response)?;
74 let response = response["result"].take();
75
76 Ok(response)
77}
78
79fn cli_output<M, I>(our_args: &Args, method: M, params: I) -> Result<Value>
83where
84 M: AsRef<str>,
85 I: IntoIterator<Item = String>,
86{
87 let mut cmd = std::process::Command::new(&our_args.cli);
90 cmd.args(&our_args.zcli_args);
91
92 if let Some(addr) = our_args.addr {
94 cmd.arg(format!("-rpcconnect={}", addr.ip()));
95 cmd.arg(format!("-rpcport={}", addr.port()));
96 }
97
98 let method: OsString = method.as_ref().into();
100 cmd.arg(method);
101
102 for param in params {
103 let param = param.trim_matches('"');
106 let param: OsString = param.into();
107 cmd.arg(param);
108 }
109
110 let output = cmd.stderr(Stdio::inherit()).output()?;
112
113 #[cfg(unix)]
115 ensure!(
116 output.status.success(),
117 "Process failed: exit status {:?}, signal: {:?}",
118 output.status.code(),
119 output.status.signal()
120 );
121 #[cfg(not(unix))]
122 ensure!(
123 output.status.success(),
124 "Process failed: exit status {:?}",
125 output.status.code()
126 );
127
128 let response = String::from_utf8(output.stdout)?;
130 let response: Value = serde_json::from_str(&response)
133 .unwrap_or_else(|_error| Value::String(response.trim().to_string()));
134
135 Ok(response)
136}
137
138#[tokio::main]
140#[allow(clippy::print_stdout, clippy::print_stderr)]
141async fn main() -> Result<()> {
142 eprintln!("zebra-checkpoints launched");
143
144 init_tracing();
146 color_eyre::install()?;
147
148 let args = args::Args::from_args();
149
150 eprintln!("Command-line arguments: {args:?}");
151 eprintln!("Fetching block info and calculating checkpoints...\n\n");
152
153 let get_block_chain_info = rpc_output(&args, "getblockchaininfo", None)
155 .await
156 .with_suggestion(|| {
157 "Is the RPC server address and port correct? Is authentication configured correctly?"
158 })?;
159
160 let height_limit = get_block_chain_info["blocks"]
162 .try_into_height()
163 .expect("height: unexpected invalid value, missing field, or field type");
164
165 let height_limit = height_limit - HeightDiff::from(MIN_TRANSPARENT_COINBASE_MATURITY);
168 let height_limit = height_limit
169 .ok_or_else(|| {
170 eyre!(
171 "checkpoint generation needs at least {:?} blocks",
172 MIN_TRANSPARENT_COINBASE_MATURITY
173 )
174 })
175 .with_suggestion(|| "Hint: wait for the node to sync more blocks")?;
176
177 let starting_height = if let Some(last_checkpoint) = args.last_checkpoint {
180 (last_checkpoint + 1)
181 .expect("invalid last checkpoint height, must be less than the max height")
182 } else {
183 Height::MIN
184 };
185
186 assert!(
187 starting_height < height_limit,
188 "checkpoint generation needs more blocks than the starting height {starting_height:?}. \
189 Hint: wait for the node to sync more blocks"
190 );
191
192 let mut cumulative_bytes: u64 = 0;
194 let mut last_checkpoint_height = args.last_checkpoint.unwrap_or(Height::MIN);
195 let max_checkpoint_height_gap =
196 HeightDiff::try_from(MAX_CHECKPOINT_HEIGHT_GAP).expect("constant fits in HeightDiff");
197
198 for request_height in starting_height.0..height_limit.0 {
200 let (hash, response_height, size) = match args.backend {
203 Backend::Zcashd => {
204 let get_block = rpc_output(
206 &args,
207 "getblock",
208 [format!(r#""{request_height}""#), 1.to_string()],
209 )
210 .await?;
211
212 let hash: block::Hash = get_block["hash"]
214 .as_str()
215 .expect("hash: unexpected missing field or field type")
216 .parse()?;
217 let response_height: Height = get_block["height"]
218 .try_into_height()
219 .expect("height: unexpected invalid value, missing field, or field type");
220
221 let size = get_block["size"]
222 .as_u64()
223 .expect("size: unexpected invalid value, missing field, or field type");
224
225 (hash, response_height, size)
226 }
227 Backend::Zebrad => {
228 let block_bytes = rpc_output(
230 &args,
231 "getblock",
232 [format!(r#""{request_height}""#), 0.to_string()],
233 )
234 .await?;
235 let block_bytes = block_bytes
236 .as_str()
237 .expect("block bytes: unexpected missing field or field type");
238
239 let block_bytes: Vec<u8> = hex::decode(block_bytes)?;
240
241 let block: Block = block_bytes.zcash_deserialize_into()?;
248
249 (
250 block.hash(),
251 block
252 .coinbase_height()
253 .expect("valid blocks always have a coinbase height"),
254 block_bytes.len().try_into()?,
255 )
256 }
257 };
258
259 assert_eq!(
260 request_height, response_height.0,
261 "node returned a different block than requested"
262 );
263
264 cumulative_bytes += size;
266
267 let height_gap = response_height - last_checkpoint_height;
268
269 if response_height == Height::MIN
271 || cumulative_bytes >= MAX_CHECKPOINT_BYTE_COUNT
272 || height_gap >= max_checkpoint_height_gap
273 {
274 println!("{} {hash}", response_height.0);
276
277 cumulative_bytes = 0;
279 last_checkpoint_height = response_height;
280 }
281 }
282
283 Ok(())
284}