1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Copyright 2019-2026 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT
mod migration;
use migration::*;
use crate::{
libp2p::keypair::get_keypair,
rpc::{
self, ApiPaths, RpcMethodExt as _,
chain::{ChainGetTipSetByHeight, ChainHead},
types::ApiTipsetKey,
},
};
use anyhow::Context as _;
use base64::{Engine, prelude::BASE64_STANDARD};
use clap::Subcommand;
use clap::ValueEnum;
use futures::{StreamExt as _, TryFutureExt as _, TryStreamExt as _};
use itertools::Itertools as _;
use openrpc_types::ReferenceOr;
use std::path::PathBuf;
#[derive(Subcommand)]
pub enum ShedCommands {
/// Enumerate the tipset CIDs for a span of epochs starting at `height` and working backwards.
///
/// Useful for getting blocks to live test an RPC endpoint.
SummarizeTipsets {
/// If omitted, defaults to the HEAD of the node.
#[arg(long)]
height: Option<u32>,
#[arg(long)]
ancestors: u32,
},
/// Generate a `PeerId` from the given key-pair file.
PeerIdFromKeyPair {
/// Path to the key-pair file.
keypair: PathBuf,
},
/// Generate a base64-encoded private key from the given key-pair file.
/// This effectively transforms Forest's key-pair file into a Lotus-compatible private key.
PrivateKeyFromKeyPair {
/// Path to the key-pair file.
keypair: PathBuf,
},
/// Generate a key-pair file from the given base64-encoded private key.
/// This effectively transforms Lotus's private key into a Forest-compatible key-pair file.
/// If `output` is not provided, the key-pair is printed to stdout as a base64-encoded string.
KeyPairFromPrivateKey {
/// Base64-encoded private key.
private_key: String,
/// Path to save the key-pair file.
#[arg(short, long)]
output: Option<PathBuf>,
},
/// Dump the OpenRPC definition for the node.
Openrpc {
include: Vec<String>,
/// Which API path to dump.
#[arg(long)]
path: ApiPaths,
/// A comma-separated list of fields to omit from the output (e.g., "summary,description").
#[arg(long, value_delimiter = ',')]
omit: Option<Vec<OmitField>>,
},
/// Run a network upgrade migration
MigrateState(MigrateStateCommand),
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum OmitField {
Summary,
Description,
}
impl ShedCommands {
pub async fn run(self, client: rpc::Client) -> anyhow::Result<()> {
match self {
ShedCommands::SummarizeTipsets { height, ancestors } => {
let head = ChainHead::call(&client, ()).await?;
let end_height = match height {
Some(it) => it,
None => head
.epoch()
.try_into()
.context("HEAD epoch out-of-bounds")?,
};
let start_height = end_height
.checked_sub(ancestors)
.context("couldn't set start height")?;
let mut epoch2cids =
futures::stream::iter((start_height..=end_height).map(|epoch| {
ChainGetTipSetByHeight::call(
&client,
(i64::from(epoch), ApiTipsetKey(Some(head.key().clone()))),
)
.map_ok(|tipset| {
let cids = tipset.block_headers().iter().map(|it| *it.cid());
(tipset.epoch(), cids.collect_vec())
})
}))
.buffered(12);
while let Some((epoch, cids)) = epoch2cids.try_next().await? {
println!("{epoch}:");
for cid in cids {
println!("- {cid}");
}
}
}
ShedCommands::PeerIdFromKeyPair { keypair } => {
let keypair = get_keypair(&keypair)
.with_context(|| format!("couldn't get keypair from {}", keypair.display()))?;
println!("{}", keypair.public().to_peer_id());
}
ShedCommands::PrivateKeyFromKeyPair { keypair } => {
let keypair = get_keypair(&keypair)
.with_context(|| format!("couldn't get keypair from {}", keypair.display()))?;
let encoded = BASE64_STANDARD.encode(keypair.to_protobuf_encoding()?);
println!("{encoded}");
}
ShedCommands::KeyPairFromPrivateKey {
private_key,
output,
} => {
let private_key = BASE64_STANDARD.decode(private_key)?;
let keypair_data = libp2p::identity::Keypair::from_protobuf_encoding(&private_key)?
// While a keypair can be any type, Forest only supports Ed25519.
.try_into_ed25519()?
.to_bytes();
if let Some(output) = output {
std::fs::write(output, keypair_data)?;
} else {
println!("{}", BASE64_STANDARD.encode(keypair_data));
}
}
ShedCommands::Openrpc {
include,
path,
omit,
} => {
let include = include.iter().map(String::as_str).collect_vec();
let mut openrpc_doc = crate::rpc::openrpc(
path,
match include.is_empty() {
true => None,
false => Some(&include),
},
);
if let Some(omit_fields) = omit {
for method in &mut openrpc_doc.methods {
if let ReferenceOr::Item(m) = method {
if omit_fields.contains(&OmitField::Summary) {
m.summary = None;
}
if omit_fields.contains(&OmitField::Description) {
m.description = None;
}
}
}
}
openrpc_doc.methods.sort_by(|a, b| match (a, b) {
(ReferenceOr::Item(a), ReferenceOr::Item(b)) => a.name.cmp(&b.name),
_ => std::cmp::Ordering::Equal,
});
println!("{}", serde_json::to_string_pretty(&openrpc_doc)?);
}
ShedCommands::MigrateState(cmd) => cmd.run(client).await?,
}
Ok(())
}
}