Skip to content

Commit 619631a

Browse files
committed
feat: experimental apis for f05 state dump
1 parent 644df8e commit 619631a

File tree

8 files changed

+218
-13
lines changed

8 files changed

+218
-13
lines changed

Cargo.lock

Lines changed: 5 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ multihash-codetable = { version = "0.1", features = ["blake2b", "blake2s", "blak
2222
rust2go = "0.4"
2323
tokio = "1"
2424

25+
# TODO: Remove this once the `fvm_ipld_amt` crate is published to crates.io.
26+
[patch.crates-io]
27+
# fvm_ipld_amt = { path = "../ref-fvm/ipld/amt" }
28+
# fvm_ipld_bitfield = { path = "../ref-fvm/ipld/bitfield" }
29+
# fvm_ipld_blockstore = { path = "../ref-fvm/ipld/blockstore" }
30+
# fvm_ipld_encoding = { path = "../ref-fvm/ipld/encoding" }
31+
# fvm_ipld_hamt = { path = "../ref-fvm/ipld/hamt" }
32+
fvm_ipld_amt = { git = "https://github.com/filecoin-project/ref-fvm", rev = "a633547ae414a333b2d076beef87d4d30cdb7fb4" }
33+
fvm_ipld_bitfield = { git = "https://github.com/filecoin-project/ref-fvm", rev = "a633547ae414a333b2d076beef87d4d30cdb7fb4" }
34+
fvm_ipld_blockstore = { git = "https://github.com/filecoin-project/ref-fvm", rev = "a633547ae414a333b2d076beef87d4d30cdb7fb4" }
35+
fvm_ipld_encoding = { git = "https://github.com/filecoin-project/ref-fvm", rev = "a633547ae414a333b2d076beef87d4d30cdb7fb4" }
36+
fvm_ipld_hamt = { git = "https://github.com/filecoin-project/ref-fvm", rev = "a633547ae414a333b2d076beef87d4d30cdb7fb4" }
37+
2538
[dependencies]
2639
ahash = "0.8"
2740
anes = "0.2"

src/rpc/client.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub struct Client {
3737
v0: tokio::sync::OnceCell<UrlClient>,
3838
v1: tokio::sync::OnceCell<UrlClient>,
3939
v2: tokio::sync::OnceCell<UrlClient>,
40+
experimental: tokio::sync::OnceCell<UrlClient>,
4041
}
4142

4243
impl Client {
@@ -84,6 +85,7 @@ impl Client {
8485
v0: Default::default(),
8586
v1: Default::default(),
8687
v2: Default::default(),
88+
experimental: Default::default(),
8789
}
8890
}
8991
pub fn base_url(&self) -> &Url {
@@ -162,6 +164,7 @@ impl Client {
162164
ApiPaths::V0 => &self.v0,
163165
ApiPaths::V1 => &self.v1,
164166
ApiPaths::V2 => &self.v2,
167+
ApiPaths::Experimental => &self.experimental,
165168
}
166169
.get_or_try_init(|| async {
167170
let url = self
@@ -170,6 +173,7 @@ impl Client {
170173
ApiPaths::V0 => "rpc/v0",
171174
ApiPaths::V1 => "rpc/v1",
172175
ApiPaths::V2 => "rpc/v2",
176+
ApiPaths::Experimental => "rpc/experimental",
173177
})
174178
.map_err(|it| {
175179
ClientError::Custom(format!("creating url for endpoint failed: {it}"))

src/rpc/methods/state.rs

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::cid_collections::CidHashSet;
1010
use crate::eth::EthChainId;
1111
use crate::interpreter::{MessageCallbackCtx, VMTrace};
1212
use crate::libp2p::NetworkMessage;
13-
use crate::lotus_json::lotus_json_with_self;
13+
use crate::lotus_json::{LotusJson, lotus_json_with_self};
1414
use crate::networks::ChainConfig;
1515
use crate::rpc::registry::actors_reg::load_and_serialize_actor_state;
1616
use crate::shim::actors::init;
@@ -68,6 +68,7 @@ use nunny::vec as nonempty;
6868
use parking_lot::Mutex;
6969
use schemars::JsonSchema;
7070
use serde::{Deserialize, Serialize};
71+
use std::io::Write;
7172
use std::ops::Mul;
7273
use std::path::PathBuf;
7374
use std::{sync::Arc, time::Duration};
@@ -497,7 +498,7 @@ impl RpcMethod<1> for StateMarketDeals {
497498
let sa = market_state.states(ctx.store())?;
498499

499500
let mut out = HashMap::new();
500-
da.for_each(|deal_id, d| {
501+
da.for_each_cacheless(|deal_id, d| {
501502
let s = sa.get(deal_id)?.unwrap_or(market::DealState {
502503
sector_start_epoch: -1,
503504
last_updated_epoch: -1,
@@ -519,6 +520,125 @@ impl RpcMethod<1> for StateMarketDeals {
519520
}
520521
}
521522

523+
pub enum StateMarketDealsDump {}
524+
525+
impl RpcMethod<2> for StateMarketDealsDump {
526+
const NAME: &'static str = "Forest.StateMarketDealsDump";
527+
const PARAM_NAMES: [&'static str; 2] = ["tipsetKey", "outputFile"];
528+
const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::Experimental);
529+
const PERMISSION: Permission = Permission::Read;
530+
const DESCRIPTION: Option<&'static str> =
531+
Some("Dumps information about every deal in the Storage Market to a NDJSON file.");
532+
533+
type Params = (ApiTipsetKey, String);
534+
type Ok = ();
535+
536+
async fn handle(
537+
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
538+
(ApiTipsetKey(tsk), output_file): Self::Params,
539+
) -> Result<Self::Ok, ServerError> {
540+
let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
541+
let market_state: market::State = ctx.state_manager.get_actor_state(&ts)?;
542+
543+
let da = market_state.proposals(ctx.store())?;
544+
let sa = market_state.states(ctx.store())?;
545+
546+
let output_path = PathBuf::from(&output_file);
547+
if let Some(parent) = output_path.parent() {
548+
std::fs::create_dir_all(parent)
549+
.context("Failed to create output directory for market deals")?;
550+
}
551+
let file = std::fs::File::create(&output_path)
552+
.context("Failed to create market deals output file")?;
553+
let mut writer = std::io::BufWriter::new(file);
554+
555+
da.for_each_cacheless(|deal_id, d| {
556+
let s = sa.get(deal_id)?.unwrap_or_else(DealState::empty);
557+
let market_deal = ApiMarketDeal {
558+
proposal: d?.into(),
559+
state: s.into(),
560+
};
561+
write!(
562+
writer,
563+
"{}\n",
564+
crate::lotus_json::HasLotusJson::into_lotus_json_string(market_deal)?
565+
)?;
566+
Ok(())
567+
})?;
568+
Ok(())
569+
}
570+
}
571+
572+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
573+
#[serde(rename_all = "PascalCase")]
574+
pub struct StateMarketDealsFilter {
575+
#[schemars(with = "LotusJson<Option<Address>>")]
576+
#[serde(with = "crate::lotus_json")]
577+
pub allowed_clients: Option<Vec<Address>>,
578+
#[schemars(with = "LotusJson<Option<Address>>")]
579+
#[serde(with = "crate::lotus_json")]
580+
pub allowed_providers: Option<Vec<Address>>,
581+
}
582+
583+
lotus_json_with_self!(StateMarketDealsFilter);
584+
585+
pub enum StateMarketDealsFiltered {}
586+
587+
impl RpcMethod<2> for StateMarketDealsFiltered {
588+
const NAME: &'static str = "Forest.StateMarketDealsFiltered";
589+
const PARAM_NAMES: [&'static str; 2] = ["tipsetKey", "filter"];
590+
const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::Experimental);
591+
const PERMISSION: Permission = Permission::Read;
592+
const DESCRIPTION: Option<&'static str> = Some(
593+
"Returns information about every deal in the Storage Market, optionally filtered by client and provider addresses.",
594+
);
595+
596+
type Params = (ApiTipsetKey, StateMarketDealsFilter);
597+
type Ok = HashMap<u64, ApiMarketDeal>;
598+
599+
async fn handle(
600+
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
601+
(
602+
ApiTipsetKey(tsk),
603+
StateMarketDealsFilter {
604+
allowed_clients,
605+
allowed_providers,
606+
},
607+
): Self::Params,
608+
) -> Result<Self::Ok, ServerError> {
609+
let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
610+
let market_state: market::State = ctx.state_manager.get_actor_state(&ts)?;
611+
612+
let da = market_state.proposals(ctx.store())?;
613+
let sa = market_state.states(ctx.store())?;
614+
615+
let mut out = HashMap::default();
616+
617+
let allowed_clients = allowed_clients
618+
.into_iter()
619+
.flatten()
620+
.collect::<HashSet<_>>();
621+
let allowed_providers = allowed_providers
622+
.into_iter()
623+
.flatten()
624+
.collect::<HashSet<_>>();
625+
da.for_each_cacheless(|deal_id, d| {
626+
let state = sa.get(deal_id)?.unwrap_or_else(DealState::empty);
627+
628+
let proposal = d?;
629+
if !allowed_clients.contains(&proposal.client.into())
630+
&& !allowed_providers.contains(&proposal.provider.into())
631+
{
632+
return Ok(());
633+
}
634+
635+
out.insert(deal_id, MarketDeal { proposal, state }.into());
636+
Ok(())
637+
})?;
638+
Ok(out)
639+
}
640+
}
641+
522642
/// looks up the miner info of the given address.
523643
pub enum StateMinerInfo {}
524644

src/rpc/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ macro_rules! for_each_rpc_method {
207207
$callback!($crate::rpc::state::StateLookupRobustAddress);
208208
$callback!($crate::rpc::state::StateMarketBalance);
209209
$callback!($crate::rpc::state::StateMarketDeals);
210+
$callback!($crate::rpc::state::StateMarketDealsFiltered);
211+
$callback!($crate::rpc::state::StateMarketDealsDump);
210212
$callback!($crate::rpc::state::StateMarketParticipants);
211213
$callback!($crate::rpc::state::StateMarketStorageDeal);
212214
$callback!($crate::rpc::state::StateMinerActiveSectors);

src/rpc/reflect/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ pub enum ApiPaths {
124124
/// Only expose this method on `/rpc/v2`
125125
#[strum(ascii_case_insensitive)]
126126
V2 = 0b00000100,
127+
/// Experimental methods, subject to change
128+
#[strum(ascii_case_insensitive)]
129+
Experimental = 0b10000000,
127130
}
128131

129132
impl ApiPaths {
@@ -436,6 +439,9 @@ mod tests {
436439
assert_eq!(v1, ApiPaths::V1);
437440
let v2 = ApiPaths::from_uri(&"http://127.0.0.1:2345/rpc/v2".parse().unwrap()).unwrap();
438441
assert_eq!(v2, ApiPaths::V2);
442+
let experimental =
443+
ApiPaths::from_uri(&"http://127.0.0.1:2345/rpc/experimental".parse().unwrap()).unwrap();
444+
assert_eq!(experimental, ApiPaths::Experimental);
439445

440446
ApiPaths::from_uri(&"http://127.0.0.1:2345/rpc/v3".parse().unwrap()).unwrap_err();
441447
}

src/rpc/segregation_layer.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ use tower::Layer;
1616
static VERSION_METHODS_MAPPINGS: LazyLock<HashMap<ApiPaths, HashSet<&'static str>>> =
1717
LazyLock::new(|| {
1818
let mut map = HashMap::default();
19-
for version in [ApiPaths::V0, ApiPaths::V1, ApiPaths::V2] {
19+
for version in [
20+
ApiPaths::V0,
21+
ApiPaths::V1,
22+
ApiPaths::V2,
23+
ApiPaths::Experimental,
24+
] {
2025
let mut supported = HashSet::default();
2126

2227
macro_rules! insert {

src/shim/actors/builtin/market/mod.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,53 @@ where
386386
.for_each(|key, deal_proposal| f(key, DealProposal::try_from(deal_proposal)))?),
387387
}
388388
}
389+
pub fn for_each_cacheless(
390+
&self,
391+
mut f: impl FnMut(u64, Result<DealProposal, anyhow::Error>) -> anyhow::Result<(), anyhow::Error>,
392+
) -> anyhow::Result<()> {
393+
match self {
394+
DealProposals::V9(deal_array) => {
395+
Ok(deal_array.for_each_cacheless(|key, deal_proposal| {
396+
f(key, DealProposal::try_from(deal_proposal))
397+
})?)
398+
}
399+
DealProposals::V10(deal_array) => {
400+
Ok(deal_array.for_each_cacheless(|key, deal_proposal| {
401+
f(key, DealProposal::try_from(deal_proposal))
402+
})?)
403+
}
404+
DealProposals::V11(deal_array) => {
405+
Ok(deal_array.for_each_cacheless(|key, deal_proposal| {
406+
f(key, DealProposal::try_from(deal_proposal))
407+
})?)
408+
}
409+
DealProposals::V12(deal_array) => {
410+
Ok(deal_array.for_each_cacheless(|key, deal_proposal| {
411+
f(key, DealProposal::try_from(deal_proposal))
412+
})?)
413+
}
414+
DealProposals::V13(deal_array) => {
415+
Ok(deal_array.for_each_cacheless(|key, deal_proposal| {
416+
f(key, DealProposal::try_from(deal_proposal))
417+
})?)
418+
}
419+
DealProposals::V14(deal_array) => {
420+
Ok(deal_array.for_each_cacheless(|key, deal_proposal| {
421+
f(key, DealProposal::try_from(deal_proposal))
422+
})?)
423+
}
424+
DealProposals::V15(deal_array) => {
425+
Ok(deal_array.for_each_cacheless(|key, deal_proposal| {
426+
f(key, DealProposal::try_from(deal_proposal))
427+
})?)
428+
}
429+
DealProposals::V16(deal_array) => {
430+
Ok(deal_array.for_each_cacheless(|key, deal_proposal| {
431+
f(key, DealProposal::try_from(deal_proposal))
432+
})?)
433+
}
434+
}
435+
}
389436

390437
pub fn get(&self, key: u64) -> anyhow::Result<Option<DealProposal>> {
391438
match self {
@@ -400,6 +447,19 @@ where
400447
}
401448
.transpose()
402449
}
450+
451+
pub fn count(&self) -> u64 {
452+
match self {
453+
DealProposals::V9(deal_array) => deal_array.count(),
454+
DealProposals::V10(deal_array) => deal_array.count(),
455+
DealProposals::V11(deal_array) => deal_array.count(),
456+
DealProposals::V12(deal_array) => deal_array.count(),
457+
DealProposals::V13(deal_array) => deal_array.count(),
458+
DealProposals::V14(deal_array) => deal_array.count(),
459+
DealProposals::V15(deal_array) => deal_array.count(),
460+
DealProposals::V16(deal_array) => deal_array.count(),
461+
}
462+
}
403463
}
404464

405465
#[derive(Clone, Serialize, Deserialize, PartialEq)]

0 commit comments

Comments
 (0)