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