Skip to content

Commit 60f6a3c

Browse files
committed
feat(shed): add forest-tool shed f3 check-activation-raw
1 parent ec7d267 commit 60f6a3c

File tree

9 files changed

+146
-21
lines changed

9 files changed

+146
-21
lines changed

Cargo.lock

+13-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ fil_actor_reward_state = { version = "19" }
7878
fil_actor_system_state = { version = "19" }
7979
fil_actor_verifreg_state = { version = "19" }
8080
fil_actors_shared = { version = "19", features = ["json"] }
81+
flate2 = "1"
8182
flume = { workspace = true }
8283
fs_extra = "1"
8384
futures = { workspace = true }

scripts/tests/calibnet_no_discovery_check.sh

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ FOREST_NODE_PID=$!
1212
FULLNODE_API_INFO="$(cat admin_token):/ip4/127.0.0.1/tcp/2345/http"
1313
export FULLNODE_API_INFO
1414

15+
forest_wait_api
16+
1517
# Verify that one of the seed nodes has been connected to
1618
until $FOREST_CLI_PATH net peers | grep "calib"; do
1719
sleep 1s;

src/cli/subcommands/f3_cmd.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,10 @@ impl F3PowerTableCommands {
354354
if instance < manifest.initial_instance + manifest.committee_lookback {
355355
let epoch = manifest.bootstrap_epoch - manifest.ec.finality;
356356
let ts = ChainGetTipSetByHeight::call(client, (epoch, None.into())).await?;
357-
return Ok((ts.key().clone(), manifest.initial_power_table));
357+
return Ok((
358+
ts.key().clone(),
359+
manifest.initial_power_table.unwrap_or_default(),
360+
));
358361
}
359362

360363
let previous = F3GetCertificate::call(client, (instance.saturating_sub(1),)).await?;
@@ -377,10 +380,11 @@ fn render_manifest_template(template: &F3Manifest) -> anyhow::Result<String> {
377380
let mut context = tera::Context::from_serialize(template)?;
378381
context.insert(
379382
"initial_power_table_cid",
380-
&if template.initial_power_table != Cid::default() {
381-
Cow::Owned(template.initial_power_table.to_string())
382-
} else {
383-
Cow::Borrowed("unknown")
383+
&match template.initial_power_table {
384+
Some(initial_power_table) if initial_power_table != Cid::default() => {
385+
Cow::Owned(initial_power_table.to_string())
386+
}
387+
_ => Cow::Borrowed("unknown"),
384388
},
385389
);
386390
Ok(TEMPLATES

src/daemon/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use crate::cli_shared::{
1414
chain_path,
1515
cli::{CliOpts, Config},
1616
};
17-
1817
use crate::daemon::db_util::{
1918
import_chain_as_forest_car, load_all_forest_cars, populate_eth_mappings,
2019
};

src/rpc/methods/eth/types.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ pub struct GasReward {
281281
pub premium: TokenAmount,
282282
}
283283

284-
#[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
284+
#[derive(PartialEq, Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
285285
#[serde(rename_all = "camelCase")]
286286
pub struct EthCallMessage {
287287
pub from: Option<EthAddress>,

src/rpc/methods/f3/types.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ pub struct F3Manifest {
279279
pub ignore_ec_power: bool,
280280
#[schemars(with = "String")]
281281
#[serde(with = "crate::lotus_json")]
282-
pub initial_power_table: Cid,
282+
pub initial_power_table: Option<Cid>,
283283
pub committee_lookback: u64,
284284
#[schemars(with = "u64")]
285285
#[serde(with = "crate::lotus_json")]

src/tool/subcommands/shed_cmd.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Copyright 2019-2025 ChainSafe Systems
22
// SPDX-License-Identifier: Apache-2.0, MIT
33

4+
mod f3;
5+
use f3::*;
6+
47
use std::path::PathBuf;
58

69
use crate::{
@@ -58,6 +61,9 @@ pub enum ShedCommands {
5861
#[arg(long)]
5962
path: ApiPath,
6063
},
64+
/// F3 related commands.
65+
#[command(subcommand)]
66+
F3(F3Commands),
6167
}
6268

6369
impl ShedCommands {
@@ -137,8 +143,9 @@ impl ShedCommands {
137143
_ => std::cmp::Ordering::Equal,
138144
});
139145

140-
println!("{}", serde_json::to_string_pretty(&openrpc_doc).unwrap());
146+
println!("{}", serde_json::to_string_pretty(&openrpc_doc)?);
141147
}
148+
ShedCommands::F3(cmd) => cmd.run(client).await?,
142149
}
143150
Ok(())
144151
}

src/tool/subcommands/shed_cmd/f3.rs

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2019-2025 ChainSafe Systems
2+
// SPDX-License-Identifier: Apache-2.0, MIT
3+
4+
use crate::{
5+
rpc::{
6+
self,
7+
eth::types::{EthAddress, EthBytes, EthCallMessage},
8+
f3::F3Manifest,
9+
state::StateCall,
10+
RpcMethodExt,
11+
},
12+
shim::message::Message,
13+
};
14+
use clap::Subcommand;
15+
use flate2::read::DeflateDecoder;
16+
use std::{io::Read, str::FromStr as _};
17+
18+
const MAX_PAYLOAD_LEN: usize = 4 << 10;
19+
20+
#[derive(Debug, Subcommand)]
21+
pub enum F3Commands {
22+
CheckActivation {
23+
/// Contract address
24+
#[arg(long, required = true)]
25+
contract: String,
26+
},
27+
CheckActivationRaw {
28+
/// Contract address
29+
#[arg(long, required = true)]
30+
contract: String,
31+
},
32+
}
33+
34+
impl F3Commands {
35+
#[allow(clippy::indexing_slicing)]
36+
pub async fn run(self, client: rpc::Client) -> anyhow::Result<()> {
37+
match self {
38+
Self::CheckActivation { contract: _ } => {
39+
unimplemented!("Will be done in a subsequent PR");
40+
}
41+
Self::CheckActivationRaw { contract } => {
42+
let eth_call_message = EthCallMessage {
43+
to: Some(EthAddress::from_str(&contract)?),
44+
data: EthBytes::from_str("0x2587660d")?, // method ID of activationInformation()
45+
..Default::default()
46+
};
47+
let filecoin_message = Message::try_from(eth_call_message)?;
48+
let api_invoc_result =
49+
StateCall::call(&client, (filecoin_message, None.into())).await?;
50+
let Some(message_receipt) = api_invoc_result.msg_rct else {
51+
anyhow::bail!("No message receipt")
52+
};
53+
anyhow::ensure!(
54+
message_receipt.exit_code().is_success(),
55+
"unsuccessful exit code {}",
56+
message_receipt.exit_code()
57+
);
58+
let return_data = message_receipt.return_data();
59+
let eth_return =
60+
fvm_ipld_encoding::from_slice::<fvm_ipld_encoding::BytesDe>(&return_data)?.0;
61+
println!("Raw data: {}", hex::encode(eth_return.as_slice()));
62+
// 3*32 because there should be 3 slots minimum
63+
anyhow::ensure!(eth_return.len() >= 3 * 32, "no activation information");
64+
let mut activation_epoch_bytes: [u8; 8] = [0; 8];
65+
activation_epoch_bytes.copy_from_slice(&eth_return[24..32]);
66+
let activation_epoch = u64::from_be_bytes(activation_epoch_bytes);
67+
for (i, &v) in eth_return[32..63].iter().enumerate() {
68+
anyhow::ensure!(
69+
v == 0,
70+
"wrong value for offset (padding): slot[{i}] = 0x{v:x} != 0x00"
71+
);
72+
}
73+
anyhow::ensure!(
74+
eth_return[63] == 0x40,
75+
"wrong value for offest : slot[31] = 0x{:x} != 0x40",
76+
eth_return[63]
77+
);
78+
let mut payload_len_bytes: [u8; 8] = [0; 8];
79+
payload_len_bytes.copy_from_slice(&eth_return[88..96]);
80+
let payload_len = u64::from_be_bytes(payload_len_bytes) as usize;
81+
anyhow::ensure!(
82+
payload_len <= MAX_PAYLOAD_LEN,
83+
"too long declared payload: {payload_len} > {MAX_PAYLOAD_LEN}"
84+
);
85+
let payload_bytes = &eth_return[96..];
86+
anyhow::ensure!(
87+
payload_len <= payload_bytes.len(),
88+
"not enough remaining bytes: {payload_len} > {}",
89+
payload_bytes.len()
90+
);
91+
anyhow::ensure!(
92+
activation_epoch < u64::MAX && payload_len > 0,
93+
"no active activation"
94+
);
95+
let compressed_manifest_bytes = &payload_bytes[..payload_len];
96+
let mut deflater = DeflateDecoder::new(compressed_manifest_bytes);
97+
let mut manifest_bytes = vec![];
98+
deflater.read_to_end(&mut manifest_bytes)?;
99+
let manifest: F3Manifest = serde_json::from_slice(&manifest_bytes)?;
100+
anyhow::ensure!(
101+
manifest.bootstrap_epoch >= 0
102+
&& manifest.bootstrap_epoch as u64 == activation_epoch,
103+
"bootstrap epoch does not match: {} != {activation_epoch}",
104+
manifest.bootstrap_epoch
105+
);
106+
println!("{}", serde_json::to_string_pretty(&manifest)?);
107+
}
108+
}
109+
Ok(())
110+
}
111+
}

0 commit comments

Comments
 (0)