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
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT
use std::path::PathBuf;
use crate::{
libp2p::keypair::get_keypair,
rpc::{
self,
chain::{ChainGetTipSetByHeight, ChainHead},
types::ApiTipsetKey,
ApiPath, RpcMethodExt as _,
},
};
use anyhow::Context as _;
use base64::{prelude::BASE64_STANDARD, Engine};
use clap::Subcommand;
use futures::{StreamExt as _, TryFutureExt as _, TryStreamExt as _};
#[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: ApiPath,
},
}
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 } => {
let include = include.iter().map(String::as_str).collect::<Vec<_>>();
println!(
"{}",
serde_json::to_string_pretty(&crate::rpc::openrpc(
path,
match include.is_empty() {
true => None,
false => Some(&include),
}
))
.unwrap()
);
}
}
Ok(())
}
}