forest/tool/subcommands/
shed_cmd.rs1mod migration;
5use migration::*;
6
7use crate::{
8 libp2p::keypair::get_keypair,
9 rpc::{
10 self, ApiPaths, RpcMethodExt as _,
11 chain::{ChainGetTipSetByHeight, ChainHead},
12 types::ApiTipsetKey,
13 },
14};
15use anyhow::Context as _;
16use base64::{Engine, prelude::BASE64_STANDARD};
17use clap::Subcommand;
18use clap::ValueEnum;
19use futures::{StreamExt as _, TryFutureExt as _, TryStreamExt as _};
20use openrpc_types::ReferenceOr;
21use std::path::PathBuf;
22
23#[derive(Subcommand)]
24pub enum ShedCommands {
25 SummarizeTipsets {
29 #[arg(long)]
31 height: Option<u32>,
32 #[arg(long)]
33 ancestors: u32,
34 },
35 PeerIdFromKeyPair {
37 keypair: PathBuf,
39 },
40 PrivateKeyFromKeyPair {
43 keypair: PathBuf,
45 },
46 KeyPairFromPrivateKey {
50 private_key: String,
52 #[arg(short, long)]
54 output: Option<PathBuf>,
55 },
56 Openrpc {
58 include: Vec<String>,
59 #[arg(long)]
61 path: ApiPaths,
62 #[arg(long, value_delimiter = ',')]
64 omit: Option<Vec<OmitField>>,
65 },
66 MigrateState(MigrateStateCommand),
68}
69
70#[derive(Debug, Clone, ValueEnum, PartialEq)]
71pub enum OmitField {
72 Summary,
73 Description,
74}
75
76impl ShedCommands {
77 pub async fn run(self, client: rpc::Client) -> anyhow::Result<()> {
78 match self {
79 ShedCommands::SummarizeTipsets { height, ancestors } => {
80 let head = ChainHead::call(&client, ()).await?;
81 let end_height = match height {
82 Some(it) => it,
83 None => head
84 .epoch()
85 .try_into()
86 .context("HEAD epoch out-of-bounds")?,
87 };
88 let start_height = end_height
89 .checked_sub(ancestors)
90 .context("couldn't set start height")?;
91
92 let mut epoch2cids =
93 futures::stream::iter((start_height..=end_height).map(|epoch| {
94 ChainGetTipSetByHeight::call(
95 &client,
96 (i64::from(epoch), ApiTipsetKey(Some(head.key().clone()))),
97 )
98 .map_ok(|tipset| {
99 let cids = tipset.block_headers().iter().map(|it| *it.cid());
100 (tipset.epoch(), cids.collect::<Vec<_>>())
101 })
102 }))
103 .buffered(12);
104
105 while let Some((epoch, cids)) = epoch2cids.try_next().await? {
106 println!("{epoch}:");
107 for cid in cids {
108 println!("- {cid}");
109 }
110 }
111 }
112 ShedCommands::PeerIdFromKeyPair { keypair } => {
113 let keypair = get_keypair(&keypair)
114 .with_context(|| format!("couldn't get keypair from {}", keypair.display()))?;
115 println!("{}", keypair.public().to_peer_id());
116 }
117 ShedCommands::PrivateKeyFromKeyPair { keypair } => {
118 let keypair = get_keypair(&keypair)
119 .with_context(|| format!("couldn't get keypair from {}", keypair.display()))?;
120 let encoded = BASE64_STANDARD.encode(keypair.to_protobuf_encoding()?);
121 println!("{encoded}");
122 }
123 ShedCommands::KeyPairFromPrivateKey {
124 private_key,
125 output,
126 } => {
127 let private_key = BASE64_STANDARD.decode(private_key)?;
128 let keypair_data = libp2p::identity::Keypair::from_protobuf_encoding(&private_key)?
129 .try_into_ed25519()?
131 .to_bytes();
132 if let Some(output) = output {
133 std::fs::write(output, keypair_data)?;
134 } else {
135 println!("{}", BASE64_STANDARD.encode(keypair_data));
136 }
137 }
138 ShedCommands::Openrpc {
139 include,
140 path,
141 omit,
142 } => {
143 let include = include.iter().map(String::as_str).collect::<Vec<_>>();
144
145 let mut openrpc_doc = crate::rpc::openrpc(
146 path,
147 match include.is_empty() {
148 true => None,
149 false => Some(&include),
150 },
151 );
152 if let Some(omit_fields) = omit {
153 for method in &mut openrpc_doc.methods {
154 if let ReferenceOr::Item(m) = method {
155 if omit_fields.contains(&OmitField::Summary) {
156 m.summary = None;
157 }
158 if omit_fields.contains(&OmitField::Description) {
159 m.description = None;
160 }
161 }
162 }
163 }
164 openrpc_doc.methods.sort_by(|a, b| match (a, b) {
165 (ReferenceOr::Item(a), ReferenceOr::Item(b)) => a.name.cmp(&b.name),
166 _ => std::cmp::Ordering::Equal,
167 });
168
169 println!("{}", serde_json::to_string_pretty(&openrpc_doc)?);
170 }
171 ShedCommands::MigrateState(cmd) => cmd.run(client).await?,
172 }
173 Ok(())
174 }
175}