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