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