1mod types;
11mod util;
12
13pub use self::types::{
14 F3InstanceProgress, F3LeaseManager, F3Manifest, F3PowerEntry, FinalityCertificate,
15};
16use self::{types::*, util::*};
17use super::wallet::WalletSign;
18use crate::shim::actors::{miner, power};
19use crate::{
20 blocks::Tipset,
21 chain::index::ResolveNullTipset,
22 chain_sync::TipsetValidator,
23 db::{
24 BlockstoreReadCacheStats as _, BlockstoreWithReadCache, DefaultBlockstoreReadCacheStats,
25 LruBlockstoreReadCache,
26 },
27 libp2p::{NetRPCMethods, NetworkMessage},
28 lotus_json::{HasLotusJson as _, LotusJson},
29 rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError, types::ApiTipsetKey},
30 shim::{
31 address::{Address, Protocol},
32 clock::ChainEpoch,
33 crypto::Signature,
34 },
35 utils::misc::env::is_env_set_and_truthy,
36};
37use ahash::{HashMap, HashSet};
38use anyhow::Context as _;
39use cid::Cid;
40use enumflags2::BitFlags;
41use fvm_ipld_blockstore::Blockstore;
42use jsonrpsee::core::{client::ClientT as _, params::ArrayParams};
43use libp2p::PeerId;
44use num::Signed as _;
45use parking_lot::RwLock;
46use std::{
47 borrow::Cow,
48 fmt::Display,
49 str::FromStr as _,
50 sync::{Arc, LazyLock, OnceLock},
51};
52
53pub static F3_LEASE_MANAGER: OnceLock<F3LeaseManager> = OnceLock::new();
54
55pub enum GetRawNetworkName {}
56
57impl RpcMethod<0> for GetRawNetworkName {
58 const NAME: &'static str = "F3.GetRawNetworkName";
59 const PARAM_NAMES: [&'static str; 0] = [];
60 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
61 const PERMISSION: Permission = Permission::Read;
62
63 type Params = ();
64 type Ok = String;
65
66 async fn handle(ctx: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
67 Ok(ctx.chain_config().network.genesis_name().into())
68 }
69}
70
71pub enum GetTipsetByEpoch {}
72impl RpcMethod<1> for GetTipsetByEpoch {
73 const NAME: &'static str = "F3.GetTipsetByEpoch";
74 const PARAM_NAMES: [&'static str; 1] = ["epoch"];
75 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
76 const PERMISSION: Permission = Permission::Read;
77
78 type Params = (ChainEpoch,);
79 type Ok = F3TipSet;
80
81 async fn handle(
82 ctx: Ctx<impl Blockstore>,
83 (epoch,): Self::Params,
84 ) -> Result<Self::Ok, ServerError> {
85 let ts = ctx.chain_index().tipset_by_height(
86 epoch,
87 ctx.chain_store().heaviest_tipset(),
88 ResolveNullTipset::TakeOlder,
89 )?;
90 Ok(ts.into())
91 }
92}
93
94pub enum GetTipset {}
95impl RpcMethod<1> for GetTipset {
96 const NAME: &'static str = "F3.GetTipset";
97 const PARAM_NAMES: [&'static str; 1] = ["tipset_key"];
98 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
99 const PERMISSION: Permission = Permission::Read;
100
101 type Params = (F3TipSetKey,);
102 type Ok = F3TipSet;
103
104 async fn handle(
105 ctx: Ctx<impl Blockstore>,
106 (f3_tsk,): Self::Params,
107 ) -> Result<Self::Ok, ServerError> {
108 let tsk = f3_tsk.try_into()?;
109 let ts = ctx.chain_index().load_required_tipset(&tsk)?;
110 Ok(ts.into())
111 }
112}
113
114pub enum GetHead {}
115impl RpcMethod<0> for GetHead {
116 const NAME: &'static str = "F3.GetHead";
117 const PARAM_NAMES: [&'static str; 0] = [];
118 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
119 const PERMISSION: Permission = Permission::Read;
120
121 type Params = ();
122 type Ok = F3TipSet;
123
124 async fn handle(ctx: Ctx<impl Blockstore>, _: Self::Params) -> Result<Self::Ok, ServerError> {
125 Ok(ctx.chain_store().heaviest_tipset().into())
126 }
127}
128
129pub enum GetParent {}
130impl RpcMethod<1> for GetParent {
131 const NAME: &'static str = "F3.GetParent";
132 const PARAM_NAMES: [&'static str; 1] = ["tipset_key"];
133 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
134 const PERMISSION: Permission = Permission::Read;
135
136 type Params = (F3TipSetKey,);
137 type Ok = F3TipSet;
138
139 async fn handle(
140 ctx: Ctx<impl Blockstore>,
141 (f3_tsk,): Self::Params,
142 ) -> Result<Self::Ok, ServerError> {
143 let tsk = f3_tsk.try_into()?;
144 let ts = ctx.chain_index().load_required_tipset(&tsk)?;
145 let parent = ctx.chain_index().load_required_tipset(ts.parents())?;
146 Ok(parent.into())
147 }
148}
149
150pub enum GetPowerTable {}
151
152impl GetPowerTable {
153 async fn compute(
154 ctx: &Ctx<impl Blockstore + Send + Sync + 'static>,
155 ts: &Tipset,
156 ) -> anyhow::Result<Vec<F3PowerEntry>> {
157 const BLOCKSTORE_CACHE_CAP: usize = 65536;
159 static BLOCKSTORE_CACHE: LazyLock<LruBlockstoreReadCache> = LazyLock::new(|| {
160 LruBlockstoreReadCache::new_with_metrics(
161 "get_powertable".into(),
162 BLOCKSTORE_CACHE_CAP.try_into().expect("Infallible"),
163 )
164 });
165 let db = BlockstoreWithReadCache::new(
166 ctx.store_owned(),
167 BLOCKSTORE_CACHE.clone(),
168 Some(DefaultBlockstoreReadCacheStats::default()),
169 );
170
171 macro_rules! handle_miner_state_v12_on {
172 ($version:tt, $id_power_worker_mappings:ident, $ts:expr, $state:expr, $policy:expr) => {
173 fn map_err<E: Display>(e: E) -> fil_actors_shared::$version::ActorError {
174 fil_actors_shared::$version::ActorError::unspecified(e.to_string())
175 }
176
177 let claims = $state.load_claims(&db)?;
178 claims.for_each(|miner, claim| {
179 if !claim.quality_adj_power.is_positive() {
180 return Ok(());
181 }
182
183 let id = miner.id().map_err(map_err)?;
184 let (_, ok) =
185 $state.miner_nominal_power_meets_consensus_minimum($policy, &db, id)?;
186 if !ok {
187 return Ok(());
188 }
189 let power = claim.quality_adj_power.clone();
190 let miner_state: miner::State = ctx
191 .state_manager
192 .get_actor_state_from_address($ts, &miner.into())
193 .map_err(map_err)?;
194 let debt = miner_state.fee_debt();
195 if !debt.is_zero() {
196 return Ok(());
198 }
199 let miner_info = miner_state.info(&db).map_err(map_err)?;
200 if $ts.epoch() <= miner_info.consensus_fault_elapsed {
202 return Ok(());
203 }
204 $id_power_worker_mappings.push((id, power, miner_info.worker.into()));
205 Ok(())
206 })?;
207 };
208 }
209
210 let state: power::State = ctx.state_manager.get_actor_state(ts)?;
211 let mut id_power_worker_mappings = vec![];
212 let policy = &ctx.chain_config().policy;
213 match &state {
214 power::State::V8(s) => {
215 fn map_err<E: Display>(e: E) -> fil_actors_shared::v8::ActorError {
216 fil_actors_shared::v8::ActorError::unspecified(e.to_string())
217 }
218
219 let claims = fil_actors_shared::v8::make_map_with_root::<
220 _,
221 fil_actor_power_state::v8::Claim,
222 >(&s.claims, &db)?;
223 claims.for_each(|key, claim| {
224 let miner = Address::from_bytes(key)?;
225 if !claim.quality_adj_power.is_positive() {
226 return Ok(());
227 }
228
229 let id = miner.id().map_err(map_err)?;
230 let ok = s.miner_nominal_power_meets_consensus_minimum(
231 &policy.into(),
232 &db,
233 &miner.into(),
234 )?;
235 if !ok {
236 return Ok(());
237 }
238 let power = claim.quality_adj_power.clone();
239 let miner_state: miner::State = ctx
240 .state_manager
241 .get_actor_state_from_address(ts, &miner)
242 .map_err(map_err)?;
243 let debt = miner_state.fee_debt();
244 if !debt.is_zero() {
245 return Ok(());
247 }
248 let miner_info = miner_state.info(&db).map_err(map_err)?;
249 if ts.epoch() <= miner_info.consensus_fault_elapsed {
251 return Ok(());
252 }
253 id_power_worker_mappings.push((id, power, miner_info.worker));
254 Ok(())
255 })?;
256 }
257 power::State::V9(s) => {
258 fn map_err<E: Display>(e: E) -> fil_actors_shared::v9::ActorError {
259 fil_actors_shared::v9::ActorError::unspecified(e.to_string())
260 }
261
262 let claims = fil_actors_shared::v9::make_map_with_root::<
263 _,
264 fil_actor_power_state::v9::Claim,
265 >(&s.claims, &db)?;
266 claims.for_each(|key, claim| {
267 let miner = Address::from_bytes(key)?;
268 if !claim.quality_adj_power.is_positive() {
269 return Ok(());
270 }
271
272 let id = miner.id().map_err(map_err)?;
273 let ok = s.miner_nominal_power_meets_consensus_minimum(
274 &policy.into(),
275 &db,
276 &miner.into(),
277 )?;
278 if !ok {
279 return Ok(());
280 }
281 let power = claim.quality_adj_power.clone();
282 let miner_state: miner::State = ctx
283 .state_manager
284 .get_actor_state_from_address(ts, &miner)
285 .map_err(map_err)?;
286 let debt = miner_state.fee_debt();
287 if !debt.is_zero() {
288 return Ok(());
290 }
291 let miner_info = miner_state.info(&db).map_err(map_err)?;
292 if ts.epoch() <= miner_info.consensus_fault_elapsed {
294 return Ok(());
295 }
296 id_power_worker_mappings.push((id, power, miner_info.worker));
297 Ok(())
298 })?;
299 }
300 power::State::V10(s) => {
301 fn map_err<E: Display>(e: E) -> fil_actors_shared::v10::ActorError {
302 fil_actors_shared::v10::ActorError::unspecified(e.to_string())
303 }
304
305 let claims = fil_actors_shared::v10::make_map_with_root::<
306 _,
307 fil_actor_power_state::v10::Claim,
308 >(&s.claims, &db)?;
309 claims.for_each(|key, claim| {
310 let miner = Address::from_bytes(key)?;
311 if !claim.quality_adj_power.is_positive() {
312 return Ok(());
313 }
314
315 let id = miner.id().map_err(map_err)?;
316 let (_, ok) =
317 s.miner_nominal_power_meets_consensus_minimum(&policy.into(), &db, id)?;
318 if !ok {
319 return Ok(());
320 }
321 let power = claim.quality_adj_power.clone();
322 let miner_state: miner::State = ctx
323 .state_manager
324 .get_actor_state_from_address(ts, &miner)
325 .map_err(map_err)?;
326 let debt = miner_state.fee_debt();
327 if !debt.is_zero() {
328 return Ok(());
330 }
331 let miner_info = miner_state.info(&db).map_err(map_err)?;
332 if ts.epoch() <= miner_info.consensus_fault_elapsed {
334 return Ok(());
335 }
336 id_power_worker_mappings.push((id, power, miner_info.worker));
337 Ok(())
338 })?;
339 }
340 power::State::V11(s) => {
341 fn map_err<E: Display>(e: E) -> fil_actors_shared::v11::ActorError {
342 fil_actors_shared::v11::ActorError::unspecified(e.to_string())
343 }
344
345 let claims = fil_actors_shared::v11::make_map_with_root::<
346 _,
347 fil_actor_power_state::v11::Claim,
348 >(&s.claims, &db)?;
349 claims.for_each(|key, claim| {
350 let miner = Address::from_bytes(key)?;
351 if !claim.quality_adj_power.is_positive() {
352 return Ok(());
353 }
354
355 let id = miner.id().map_err(map_err)?;
356 let (_, ok) =
357 s.miner_nominal_power_meets_consensus_minimum(&policy.into(), &db, id)?;
358 if !ok {
359 return Ok(());
360 }
361 let power = claim.quality_adj_power.clone();
362 let miner_state: miner::State = ctx
363 .state_manager
364 .get_actor_state_from_address(ts, &miner)
365 .map_err(map_err)?;
366 let debt = miner_state.fee_debt();
367 if !debt.is_zero() {
368 return Ok(());
370 }
371 let miner_info = miner_state.info(&db).map_err(map_err)?;
372 if ts.epoch() <= miner_info.consensus_fault_elapsed {
374 return Ok(());
375 }
376 id_power_worker_mappings.push((id, power, miner_info.worker));
377 Ok(())
378 })?;
379 }
380 power::State::V12(s) => {
381 handle_miner_state_v12_on!(v12, id_power_worker_mappings, &ts, s, &policy.into());
382 }
383 power::State::V13(s) => {
384 handle_miner_state_v12_on!(v13, id_power_worker_mappings, &ts, s, &policy.into());
385 }
386 power::State::V14(s) => {
387 handle_miner_state_v12_on!(v14, id_power_worker_mappings, &ts, s, &policy.into());
388 }
389 power::State::V15(s) => {
390 handle_miner_state_v12_on!(v15, id_power_worker_mappings, &ts, s, &policy.into());
391 }
392 power::State::V16(s) => {
393 handle_miner_state_v12_on!(v16, id_power_worker_mappings, &ts, s, &policy.into());
394 }
395 power::State::V17(s) => {
396 handle_miner_state_v12_on!(v17, id_power_worker_mappings, &ts, s, &policy.into());
397 }
398 }
399 let mut power_entries = vec![];
400 for (id, power, worker) in id_power_worker_mappings {
401 let waddr = ctx
402 .state_manager
403 .resolve_to_deterministic_address(worker, ts)
404 .await?;
405 if waddr.protocol() != Protocol::BLS {
406 anyhow::bail!("wrong type of worker address");
407 }
408 let pub_key = waddr.payload_bytes();
409 power_entries.push(F3PowerEntry { id, power, pub_key });
410 }
411 power_entries.sort();
412
413 if let Some(stats) = db.stats() {
414 tracing::debug!(epoch=%ts.epoch(), hit=%stats.hit(), miss=%stats.miss(),cache_len=%BLOCKSTORE_CACHE.len(), "F3.GetPowerTable blockstore read cache");
415 }
416
417 Ok(power_entries)
418 }
419}
420
421impl RpcMethod<1> for GetPowerTable {
422 const NAME: &'static str = "F3.GetPowerTable";
423 const PARAM_NAMES: [&'static str; 1] = ["tipset_key"];
424 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
425 const PERMISSION: Permission = Permission::Read;
426
427 type Params = (F3TipSetKey,);
428 type Ok = Vec<F3PowerEntry>;
429
430 async fn handle(
431 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
432 (f3_tsk,): Self::Params,
433 ) -> Result<Self::Ok, ServerError> {
434 let tsk = f3_tsk.try_into()?;
435 let start = std::time::Instant::now();
436 let ts = ctx.chain_index().load_required_tipset(&tsk)?;
437 let power_entries = Self::compute(&ctx, &ts).await?;
438 tracing::debug!(epoch=%ts.epoch(), %tsk, "F3.GetPowerTable, took {}", humantime::format_duration(start.elapsed()));
439 Ok(power_entries)
440 }
441}
442
443pub enum ProtectPeer {}
444impl RpcMethod<1> for ProtectPeer {
445 const NAME: &'static str = "F3.ProtectPeer";
446 const PARAM_NAMES: [&'static str; 1] = ["peer_id"];
447 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
448 const PERMISSION: Permission = Permission::Read;
449
450 type Params = (String,);
451 type Ok = bool;
452
453 async fn handle(
454 ctx: Ctx<impl Blockstore>,
455 (peer_id,): Self::Params,
456 ) -> Result<Self::Ok, ServerError> {
457 let peer_id = PeerId::from_str(&peer_id)?;
458 let (tx, rx) = flume::bounded(1);
459 ctx.network_send()
460 .send_async(NetworkMessage::JSONRPCRequest {
461 method: NetRPCMethods::ProtectPeer(tx, std::iter::once(peer_id).collect()),
462 })
463 .await?;
464 rx.recv_async().await?;
465 Ok(true)
466 }
467}
468
469pub enum GetParticipatingMinerIDs {}
470
471impl RpcMethod<0> for GetParticipatingMinerIDs {
472 const NAME: &'static str = "F3.GetParticipatingMinerIDs";
473 const PARAM_NAMES: [&'static str; 0] = [];
474 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
475 const PERMISSION: Permission = Permission::Read;
476
477 type Params = ();
478 type Ok = Vec<u64>;
479
480 async fn handle(_: Ctx<impl Blockstore>, _: Self::Params) -> Result<Self::Ok, ServerError> {
481 let participants = F3ListParticipants::run().await?;
482 let mut ids: HashSet<u64> = participants.into_iter().map(|p| p.miner_id).collect();
483 if let Some(permanent_miner_ids) = (*F3_PERMANENT_PARTICIPATING_MINER_IDS).clone() {
484 ids.extend(permanent_miner_ids);
485 }
486 Ok(ids.into_iter().collect())
487 }
488}
489
490pub enum Finalize {}
491impl RpcMethod<1> for Finalize {
492 const NAME: &'static str = "F3.Finalize";
493 const PARAM_NAMES: [&'static str; 1] = ["tipset_key"];
494 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
495 const PERMISSION: Permission = Permission::Write;
496
497 type Params = (F3TipSetKey,);
498 type Ok = ();
499
500 async fn handle(
501 ctx: Ctx<impl Blockstore>,
502 (f3_tsk,): Self::Params,
503 ) -> Result<Self::Ok, ServerError> {
504 let enabled = is_env_set_and_truthy("FOREST_F3_CONSENSUS_ENABLED")
506 .unwrap_or(ctx.chain_config().f3_consensus);
507 if !enabled {
508 return Ok(());
509 }
510
511 let tsk = f3_tsk.try_into()?;
512 let finalized_ts = match ctx.chain_index().load_tipset(&tsk)? {
513 Some(ts) => ts,
514 None => ctx
515 .sync_network_context
516 .chain_exchange_headers(None, &tsk, 1.try_into().expect("Infallible"))
517 .await?
518 .first()
519 .cloned()
520 .with_context(|| format!("failed to get tipset via chain exchange. tsk: {tsk}"))?,
521 };
522 let head = ctx.chain_store().heaviest_tipset();
523 if head.epoch() >= finalized_ts.epoch()
528 && head.epoch() <= finalized_ts.epoch() + ctx.chain_config().policy.chain_finality
529 {
530 tracing::debug!(
531 "F3 finalized tsk {} at epoch {}",
532 finalized_ts.key(),
533 finalized_ts.epoch()
534 );
535 if !head
536 .chain(ctx.store())
537 .take_while(|ts| ts.epoch() >= finalized_ts.epoch())
538 .any(|ts| ts == finalized_ts)
539 {
540 tracing::info!(
541 "F3 reset chain head to tsk {} at epoch {}",
542 finalized_ts.key(),
543 finalized_ts.epoch()
544 );
545 let fts = ctx
546 .sync_network_context
547 .chain_exchange_full_tipset(None, &tsk)
548 .await?;
549 fts.persist(ctx.store())?;
550 let validator = TipsetValidator(&fts);
551 validator.validate(
552 ctx.chain_store(),
553 None,
554 &ctx.chain_store().genesis_tipset(),
555 ctx.chain_config().block_delay_secs,
556 )?;
557 let ts = Arc::new(Tipset::from(fts));
558 ctx.chain_store().put_tipset(&ts)?;
559 ctx.chain_store().set_heaviest_tipset(finalized_ts)?;
560 }
561 }
562 Ok(())
563 }
564}
565
566pub enum SignMessage {}
567impl RpcMethod<2> for SignMessage {
568 const NAME: &'static str = "F3.SignMessage";
569 const PARAM_NAMES: [&'static str; 2] = ["pubkey", "message"];
570 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
571 const PERMISSION: Permission = Permission::Sign;
572
573 type Params = (Vec<u8>, Vec<u8>);
574 type Ok = Signature;
575
576 async fn handle(
577 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
578 (pubkey, message): Self::Params,
579 ) -> Result<Self::Ok, ServerError> {
580 let addr = Address::new_bls(&pubkey)?;
581 WalletSign::handle(ctx, (addr, message)).await
583 }
584}
585
586pub enum F3ExportLatestSnapshot {}
587
588impl F3ExportLatestSnapshot {
589 pub async fn run(path: String) -> anyhow::Result<Cid> {
590 let client = get_rpc_http_client()?;
591 let mut params = ArrayParams::new();
592 params.insert(path)?;
593 let LotusJson(cid): LotusJson<Cid> = client
594 .request("Filecoin.F3ExportLatestSnapshot", params)
595 .await?;
596 Ok(cid)
597 }
598}
599
600impl RpcMethod<1> for F3ExportLatestSnapshot {
601 const NAME: &'static str = "F3.ExportLatestSnapshot";
602 const PARAM_NAMES: [&'static str; 1] = ["path"];
603 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
604 const PERMISSION: Permission = Permission::Read;
605 const DESCRIPTION: Option<&'static str> =
606 Some("Exports the latest F3 snapshot to the specified path and returns its CID");
607
608 type Params = (String,);
609 type Ok = Cid;
610
611 async fn handle(
612 _ctx: Ctx<impl Blockstore>,
613 (path,): Self::Params,
614 ) -> Result<Self::Ok, ServerError> {
615 Ok(Self::run(path).await?)
616 }
617}
618
619pub enum F3GetCertificate {}
621impl RpcMethod<1> for F3GetCertificate {
622 const NAME: &'static str = "Filecoin.F3GetCertificate";
623 const PARAM_NAMES: [&'static str; 1] = ["instance"];
624 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
625 const PERMISSION: Permission = Permission::Read;
626
627 type Params = (u64,);
628 type Ok = FinalityCertificate;
629
630 async fn handle(
631 _: Ctx<impl Blockstore>,
632 (instance,): Self::Params,
633 ) -> Result<Self::Ok, ServerError> {
634 let client = get_rpc_http_client()?;
635 let mut params = ArrayParams::new();
636 params.insert(instance)?;
637 let response: LotusJson<Self::Ok> = client.request(Self::NAME, params).await?;
638 Ok(response.into_inner())
639 }
640}
641
642pub enum F3GetLatestCertificate {}
644impl RpcMethod<0> for F3GetLatestCertificate {
645 const NAME: &'static str = "Filecoin.F3GetLatestCertificate";
646 const PARAM_NAMES: [&'static str; 0] = [];
647 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
648 const PERMISSION: Permission = Permission::Read;
649
650 type Params = ();
651 type Ok = FinalityCertificate;
652
653 async fn handle(
654 _: Ctx<impl Blockstore + Send + Sync + 'static>,
655 _: Self::Params,
656 ) -> Result<Self::Ok, ServerError> {
657 let client = get_rpc_http_client()?;
658 let response: LotusJson<Self::Ok> = client.request(Self::NAME, ArrayParams::new()).await?;
659 Ok(response.into_inner())
660 }
661}
662
663pub enum F3GetECPowerTable {}
664impl RpcMethod<1> for F3GetECPowerTable {
665 const NAME: &'static str = "Filecoin.F3GetECPowerTable";
666 const PARAM_NAMES: [&'static str; 1] = ["tipset_key"];
667 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
668 const PERMISSION: Permission = Permission::Read;
669
670 type Params = (ApiTipsetKey,);
671 type Ok = Vec<F3PowerEntry>;
672
673 async fn handle(
674 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
675 (ApiTipsetKey(tsk_opt),): Self::Params,
676 ) -> Result<Self::Ok, ServerError> {
677 let tsk = tsk_opt.unwrap_or_else(|| ctx.chain_store().heaviest_tipset().key().clone());
678 GetPowerTable::handle(ctx, (tsk.into(),)).await
679 }
680}
681
682pub enum F3GetF3PowerTable {}
683impl RpcMethod<1> for F3GetF3PowerTable {
684 const NAME: &'static str = "Filecoin.F3GetF3PowerTable";
685 const PARAM_NAMES: [&'static str; 1] = ["tipset_key"];
686 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
687 const PERMISSION: Permission = Permission::Read;
688
689 type Params = (ApiTipsetKey,);
690 type Ok = Vec<F3PowerEntry>;
691
692 async fn handle(
693 ctx: Ctx<impl Blockstore>,
694 (ApiTipsetKey(tsk_opt),): Self::Params,
695 ) -> Result<Self::Ok, ServerError> {
696 let tsk: F3TipSetKey = tsk_opt
697 .unwrap_or_else(|| ctx.chain_store().heaviest_tipset().key().clone())
698 .into();
699 let client = get_rpc_http_client()?;
700 let mut params = ArrayParams::new();
701 params.insert(tsk.into_lotus_json())?;
702 let response: LotusJson<Self::Ok> = client.request(Self::NAME, params).await?;
703 Ok(response.into_inner())
704 }
705}
706
707pub enum F3GetF3PowerTableByInstance {}
708impl RpcMethod<1> for F3GetF3PowerTableByInstance {
709 const NAME: &'static str = "Filecoin.F3GetF3PowerTableByInstance";
710 const PARAM_NAMES: [&'static str; 1] = ["instance"];
711 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
712 const PERMISSION: Permission = Permission::Read;
713 const DESCRIPTION: Option<&'static str> =
714 Some("Gets the power table (committee) used to validate the specified instance");
715
716 type Params = (u64,);
717 type Ok = Vec<F3PowerEntry>;
718
719 async fn handle(
720 _ctx: Ctx<impl Blockstore>,
721 (instance,): Self::Params,
722 ) -> Result<Self::Ok, ServerError> {
723 let client = get_rpc_http_client()?;
724 let mut params = ArrayParams::new();
725 params.insert(instance)?;
726 let response: LotusJson<Self::Ok> = client.request(Self::NAME, params).await?;
727 Ok(response.into_inner())
728 }
729}
730
731pub enum F3IsRunning {}
732
733impl F3IsRunning {
734 pub async fn is_f3_running() -> anyhow::Result<bool> {
735 let client = get_rpc_http_client()?;
736 let response = client.request(Self::NAME, ArrayParams::new()).await?;
737 Ok(response)
738 }
739}
740
741impl RpcMethod<0> for F3IsRunning {
742 const NAME: &'static str = "Filecoin.F3IsRunning";
743 const PARAM_NAMES: [&'static str; 0] = [];
744 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
745 const PERMISSION: Permission = Permission::Read;
746
747 type Params = ();
748 type Ok = bool;
749
750 async fn handle(_: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
751 Ok(Self::is_f3_running().await?)
752 }
753}
754
755pub enum F3GetProgress {}
757
758impl F3GetProgress {
759 async fn run() -> anyhow::Result<F3InstanceProgress> {
760 let client = get_rpc_http_client()?;
761 let response: LotusJson<F3InstanceProgress> =
762 client.request(Self::NAME, ArrayParams::new()).await?;
763 Ok(response.into_inner())
764 }
765}
766
767impl RpcMethod<0> for F3GetProgress {
768 const NAME: &'static str = "Filecoin.F3GetProgress";
769 const PARAM_NAMES: [&'static str; 0] = [];
770 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
771 const PERMISSION: Permission = Permission::Read;
772
773 type Params = ();
774 type Ok = F3InstanceProgress;
775
776 async fn handle(_: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
777 Ok(Self::run().await?)
778 }
779}
780
781pub enum F3GetManifest {}
783
784impl F3GetManifest {
785 async fn run() -> anyhow::Result<F3Manifest> {
786 let client = get_rpc_http_client()?;
787 let response: LotusJson<F3Manifest> =
788 client.request(Self::NAME, ArrayParams::new()).await?;
789 Ok(response.into_inner())
790 }
791}
792
793impl RpcMethod<0> for F3GetManifest {
794 const NAME: &'static str = "Filecoin.F3GetManifest";
795 const PARAM_NAMES: [&'static str; 0] = [];
796 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
797 const PERMISSION: Permission = Permission::Read;
798
799 type Params = ();
800 type Ok = F3Manifest;
801
802 async fn handle(_: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
803 Ok(Self::run().await?)
804 }
805}
806
807pub enum F3ListParticipants {}
809impl RpcMethod<0> for F3ListParticipants {
810 const NAME: &'static str = "Filecoin.F3ListParticipants";
811 const PARAM_NAMES: [&'static str; 0] = [];
812 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
813 const PERMISSION: Permission = Permission::Read;
814
815 type Params = ();
816 type Ok = Vec<F3Participant>;
817
818 async fn handle(_: Ctx<impl Blockstore>, _: Self::Params) -> Result<Self::Ok, ServerError> {
819 Ok(Self::run().await?)
820 }
821}
822
823impl F3ListParticipants {
824 async fn run() -> anyhow::Result<Vec<F3Participant>> {
825 let current_instance = F3GetProgress::run().await?.id;
826 Ok(F3_LEASE_MANAGER
827 .get()
828 .context("F3 lease manager is not initialized")?
829 .get_active_participants(current_instance)
830 .values()
831 .map(F3Participant::from)
832 .collect())
833 }
834}
835
836pub enum F3GetOrRenewParticipationTicket {}
839impl RpcMethod<3> for F3GetOrRenewParticipationTicket {
840 const NAME: &'static str = "Filecoin.F3GetOrRenewParticipationTicket";
841 const PARAM_NAMES: [&'static str; 3] = ["miner_address", "previous_lease_ticket", "instances"];
842 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
843 const PERMISSION: Permission = Permission::Sign;
844
845 type Params = (Address, Vec<u8>, u64);
846 type Ok = Vec<u8>;
847
848 async fn handle(
849 _: Ctx<impl Blockstore>,
850 (miner, previous_lease_ticket, instances): Self::Params,
851 ) -> Result<Self::Ok, ServerError> {
852 let id = miner.id()?;
853 let previous_lease = if previous_lease_ticket.is_empty() {
854 None
855 } else {
856 Some(
857 fvm_ipld_encoding::from_slice::<F3ParticipationLease>(&previous_lease_ticket)
858 .context("the previous lease ticket is invalid")?,
859 )
860 };
861 let lease = F3_LEASE_MANAGER
862 .get()
863 .context("F3 lease manager is not initialized")?
864 .get_or_renew_participation_lease(id, previous_lease, instances)
865 .await?;
866 Ok(fvm_ipld_encoding::to_vec(&lease)?)
867 }
868}
869
870pub enum F3Participate {}
874impl RpcMethod<1> for F3Participate {
875 const NAME: &'static str = "Filecoin.F3Participate";
876 const PARAM_NAMES: [&'static str; 1] = ["lease_ticket"];
877 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
878 const PERMISSION: Permission = Permission::Sign;
879
880 type Params = (Vec<u8>,);
881 type Ok = F3ParticipationLease;
882
883 async fn handle(
884 _: Ctx<impl Blockstore>,
885 (lease_ticket,): Self::Params,
886 ) -> Result<Self::Ok, ServerError> {
887 let lease: F3ParticipationLease =
888 fvm_ipld_encoding::from_slice(&lease_ticket).context("invalid lease ticket")?;
889 let current_instance = F3GetProgress::run().await?.id;
890 F3_LEASE_MANAGER
891 .get()
892 .context("F3 lease manager is not initialized")?
893 .participate(&lease, current_instance)?;
894 Ok(lease)
895 }
896}
897
898pub fn get_f3_rpc_endpoint() -> Cow<'static, str> {
899 if let Ok(host) = std::env::var("FOREST_F3_SIDECAR_RPC_ENDPOINT") {
900 Cow::Owned(host)
901 } else {
902 Cow::Borrowed("127.0.0.1:23456")
903 }
904}
905
906pub fn get_rpc_http_client() -> anyhow::Result<jsonrpsee::http_client::HttpClient> {
907 let client = jsonrpsee::http_client::HttpClientBuilder::new()
908 .build(format!("http://{}", get_f3_rpc_endpoint()))?;
909 Ok(client)
910}