Skip to content

Commit 5339feb

Browse files
committed
feat: forest-tool shed miner fees
1 parent 934a061 commit 5339feb

File tree

15 files changed

+470
-150
lines changed

15 files changed

+470
-150
lines changed

src/blocks/tipset.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
// Copyright 2019-2025 ChainSafe Systems
22
// SPDX-License-Identifier: Apache-2.0, MIT
33

4-
use std::sync::Arc;
5-
use std::{fmt, sync::OnceLock};
4+
use std::{
5+
fmt,
6+
str::FromStr,
7+
sync::{Arc, OnceLock},
8+
};
69

710
use crate::cid_collections::SmallCidNonEmptyVec;
811
use crate::networks::{calibnet, mainnet};
@@ -105,6 +108,33 @@ impl fmt::Display for TipsetKey {
105108
}
106109
}
107110

111+
impl FromStr for TipsetKey {
112+
type Err = anyhow::Error;
113+
114+
fn from_str(s: &str) -> Result<Self, Self::Err> {
115+
let mut s = s.trim();
116+
anyhow::ensure!(!s.is_empty(), "value cannot be empty");
117+
118+
// from display
119+
if let Some(stripped) = s.strip_prefix('[').and_then(|i| i.strip_suffix(']')) {
120+
s = stripped;
121+
}
122+
123+
let mut keys = vec![];
124+
125+
for i in s.split(",") {
126+
let cid: Cid = i.trim().parse()?;
127+
keys.push(cid);
128+
}
129+
130+
if let Ok(keys) = nunny::Vec::new(keys) {
131+
Ok(keys.into())
132+
} else {
133+
anyhow::bail!("no cids found")
134+
}
135+
}
136+
}
137+
108138
impl<'a> IntoIterator for &'a TipsetKey {
109139
type Item = <&'a SmallCidNonEmptyVec as IntoIterator>::Item;
110140

@@ -816,4 +846,28 @@ mod test {
816846
CreateTipsetError::Empty
817847
);
818848
}
849+
850+
#[test]
851+
fn test_parse_tipset_key() {
852+
let expected_tsk: TipsetKey = nunny::vec![
853+
"bafy2bzacear67vciqyqjyzn77pb75rvtvagsmydfhgupbija2kpdtewkvq2gy"
854+
.parse()
855+
.unwrap(),
856+
"bafy2bzacecoyawspvaoevxx6le5ud65euuplg4pq6au7bmv4dd6dhwiiwlzuq"
857+
.parse()
858+
.unwrap()
859+
]
860+
.into();
861+
let tsk: TipsetKey = "bafy2bzacear67vciqyqjyzn77pb75rvtvagsmydfhgupbija2kpdtewkvq2gy,bafy2bzacecoyawspvaoevxx6le5ud65euuplg4pq6au7bmv4dd6dhwiiwlzuq".parse().unwrap();
862+
assert_eq!(expected_tsk, tsk);
863+
864+
let tsk: TipsetKey = "bafy2bzacear67vciqyqjyzn77pb75rvtvagsmydfhgupbija2kpdtewkvq2gy, bafy2bzacecoyawspvaoevxx6le5ud65euuplg4pq6au7bmv4dd6dhwiiwlzuq".parse().unwrap();
865+
assert_eq!(expected_tsk, tsk);
866+
867+
println!("{expected_tsk}");
868+
let tsk_from_display: TipsetKey = expected_tsk.to_string().parse().unwrap();
869+
assert_eq!(expected_tsk, tsk_from_display);
870+
871+
TipsetKey::from_str("").unwrap_err();
872+
}
819873
}

src/db/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod car;
55
mod memory;
66
pub mod parity_db;
77
pub mod parity_db_config;
8+
pub mod rpc_db;
89

910
mod gc;
1011
pub mod ttl;

src/db/rpc_db.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2019-2025 ChainSafe Systems
2+
// SPDX-License-Identifier: Apache-2.0, MIT
3+
4+
use std::sync::Arc;
5+
6+
use ahash::HashMap;
7+
use cid::Cid;
8+
use fvm_ipld_blockstore::Blockstore;
9+
use parking_lot::RwLock;
10+
11+
use crate::rpc::{prelude::ChainReadObj, Client, RpcMethodExt as _};
12+
13+
/// A blocktore backed by Filecoin RPC APIs
14+
pub struct RpcDb {
15+
client: Arc<Client>,
16+
cache: RwLock<HashMap<Cid, Option<Vec<u8>>>>,
17+
}
18+
19+
impl RpcDb {
20+
pub fn new(client: Arc<Client>) -> Self {
21+
Self {
22+
client,
23+
cache: Default::default(),
24+
}
25+
}
26+
}
27+
28+
impl Blockstore for RpcDb {
29+
fn get(&self, k: &Cid) -> anyhow::Result<Option<Vec<u8>>> {
30+
if let Some(v) = self.cache.read().get(k) {
31+
return Ok(v.clone());
32+
}
33+
let bytes = ChainReadObj::call_sync(self.client.clone(), (k.clone(),)).ok();
34+
self.cache.write().insert(*k, bytes.clone());
35+
Ok(bytes)
36+
}
37+
38+
fn put_keyed(&self, k: &Cid, block: &[u8]) -> anyhow::Result<()> {
39+
self.cache.write().insert(*k, Some(block.to_vec()));
40+
Ok(())
41+
}
42+
}

src/rpc/methods/state.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,11 @@ impl RpcMethod<2> for StateMinerActiveSectors {
569569
Ok(())
570570
})
571571
})?;
572-
let sectors =
573-
miner_state.load_sectors_ext(ctx.store(), Some(&BitField::union(&active_sectors)))?;
572+
let sectors = miner_state
573+
.load_sectors_ext(ctx.store(), Some(&BitField::union(&active_sectors)))?
574+
.into_iter()
575+
.map(|(_, s)| s)
576+
.collect();
574577
Ok(sectors)
575578
}
576579
}
@@ -659,7 +662,11 @@ impl RpcMethod<3> for StateMinerSectors {
659662
let miner_state: miner::State = ctx
660663
.state_manager
661664
.get_actor_state_from_address(&ts, &address)?;
662-
Ok(miner_state.load_sectors_ext(ctx.store(), sectors.as_ref())?)
665+
Ok(miner_state
666+
.load_sectors_ext(ctx.store(), sectors.as_ref())?
667+
.into_iter()
668+
.map(|(_, s)| s)
669+
.collect())
663670
}
664671
}
665672

src/rpc/reflect/mod.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,11 @@ pub enum ApiPath {
132132

133133
/// Utility methods, defined as an extension trait to avoid having to specify
134134
/// `ARITY` in user code.
135-
pub trait RpcMethodExt<const ARITY: usize>: RpcMethod<ARITY> {
135+
pub trait RpcMethodExt<const ARITY: usize>: RpcMethod<ARITY>
136+
where
137+
Self::Params: Send + Sync + 'static,
138+
Self::Ok: Send + Sync + 'static,
139+
{
136140
/// Convert from typed handler parameters to un-typed JSON-RPC parameters.
137141
///
138142
/// Exposes errors from [`Params::unparse`]
@@ -292,8 +296,9 @@ pub trait RpcMethodExt<const ARITY: usize>: RpcMethod<ARITY> {
292296
fn call_raw(
293297
client: &crate::rpc::client::Client,
294298
params: Self::Params,
295-
) -> impl Future<Output = Result<<Self::Ok as HasLotusJson>::LotusJson, jsonrpsee::core::ClientError>>
296-
{
299+
) -> impl Future<
300+
Output = Result<<Self::Ok as HasLotusJson>::LotusJson, jsonrpsee::core::ClientError>,
301+
> + Send {
297302
async {
298303
// TODO(forest): https://github.com/ChainSafe/forest/issues/4032
299304
// Client::call has an inappropriate HasLotusJson
@@ -305,15 +310,33 @@ pub trait RpcMethodExt<const ARITY: usize>: RpcMethod<ARITY> {
305310
fn call(
306311
client: &crate::rpc::client::Client,
307312
params: Self::Params,
308-
) -> impl Future<Output = Result<Self::Ok, jsonrpsee::core::ClientError>> {
313+
) -> impl Future<Output = Result<Self::Ok, jsonrpsee::core::ClientError>> + Send {
309314
async {
310315
Self::call_raw(client, params)
311316
.await
312317
.map(Self::Ok::from_lotus_json)
313318
}
314319
}
320+
fn call_sync(
321+
client: Arc<crate::rpc::client::Client>,
322+
params: Self::Params,
323+
) -> Result<Self::Ok, jsonrpsee::core::ClientError> {
324+
let (tx, rx) = flume::bounded(1);
325+
tokio::task::spawn(async move {
326+
let r = Self::call(&client, params).await;
327+
tx.send(r)
328+
});
329+
rx.recv()
330+
.map_err(|e| jsonrpsee::core::ClientError::Custom(e.to_string()))?
331+
}
332+
}
333+
impl<const ARITY: usize, T> RpcMethodExt<ARITY> for T
334+
where
335+
T: RpcMethod<ARITY>,
336+
<T as RpcMethod<ARITY>>::Params: Send + Sync + 'static,
337+
<T as RpcMethod<ARITY>>::Ok: Send + Sync + 'static,
338+
{
315339
}
316-
impl<const ARITY: usize, T> RpcMethodExt<ARITY> for T where T: RpcMethod<ARITY> {}
317340

318341
/// A tuple of `ARITY` arguments.
319342
///

src/rpc/types/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ pub struct SectorOnChainInfo {
280280
#[schemars(with = "LotusJson<Option<Cid>>")]
281281
#[serde(with = "crate::lotus_json", rename = "SectorKeyCID")]
282282
pub sector_key_cid: Option<Cid>,
283+
284+
#[schemars(with = "LotusJson<TokenAmount>")]
285+
#[serde(with = "crate::lotus_json")]
286+
pub daily_fee: TokenAmount,
283287
}
284288

285289
lotus_json_with_self!(SectorOnChainInfo);

src/rpc/types/sector_impl.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ impl From<fil_actor_miner_state::v8::SectorOnChainInfo> for SectorOnChainInfo {
2323
replaced_day_reward: TokenAmount::default(),
2424
sector_key_cid: info.sector_key_cid,
2525
power_base_epoch: info.activation,
26+
daily_fee: Default::default(),
2627
}
2728
}
2829
}
@@ -49,6 +50,7 @@ impl From<fil_actor_miner_state::v9::SectorOnChainInfo> for SectorOnChainInfo {
4950
replaced_day_reward: info.replaced_day_reward.into(),
5051
sector_key_cid: info.sector_key_cid,
5152
power_base_epoch: info.activation,
53+
daily_fee: Default::default(),
5254
}
5355
}
5456
}
@@ -75,6 +77,7 @@ impl From<fil_actor_miner_state::v10::SectorOnChainInfo> for SectorOnChainInfo {
7577
replaced_day_reward: info.replaced_day_reward.into(),
7678
sector_key_cid: info.sector_key_cid,
7779
power_base_epoch: info.activation,
80+
daily_fee: Default::default(),
7881
}
7982
}
8083
}
@@ -101,6 +104,7 @@ impl From<fil_actor_miner_state::v11::SectorOnChainInfo> for SectorOnChainInfo {
101104
replaced_day_reward: info.replaced_day_reward.into(),
102105
sector_key_cid: info.sector_key_cid,
103106
power_base_epoch: info.activation,
107+
daily_fee: Default::default(),
104108
}
105109
}
106110
}
@@ -123,6 +127,7 @@ impl From<fil_actor_miner_state::v12::SectorOnChainInfo> for SectorOnChainInfo {
123127
replaced_day_reward: info.replaced_day_reward.into(),
124128
sector_key_cid: info.sector_key_cid,
125129
power_base_epoch: info.power_base_epoch,
130+
daily_fee: Default::default(),
126131
}
127132
}
128133
}
@@ -145,6 +150,7 @@ impl From<fil_actor_miner_state::v13::SectorOnChainInfo> for SectorOnChainInfo {
145150
replaced_day_reward: info.replaced_day_reward.into(),
146151
sector_key_cid: info.sector_key_cid,
147152
power_base_epoch: info.power_base_epoch,
153+
daily_fee: Default::default(),
148154
}
149155
}
150156
}
@@ -167,6 +173,7 @@ impl From<fil_actor_miner_state::v14::SectorOnChainInfo> for SectorOnChainInfo {
167173
replaced_day_reward: info.replaced_day_reward.into(),
168174
sector_key_cid: info.sector_key_cid,
169175
power_base_epoch: info.power_base_epoch,
176+
daily_fee: Default::default(),
170177
}
171178
}
172179
}
@@ -189,6 +196,7 @@ impl From<fil_actor_miner_state::v15::SectorOnChainInfo> for SectorOnChainInfo {
189196
replaced_day_reward: info.replaced_day_reward.into(),
190197
sector_key_cid: info.sector_key_cid,
191198
power_base_epoch: info.power_base_epoch,
199+
daily_fee: Default::default(),
192200
}
193201
}
194202
}
@@ -220,6 +228,7 @@ impl From<fil_actor_miner_state::v16::SectorOnChainInfo> for SectorOnChainInfo {
220228
.into(),
221229
sector_key_cid: info.sector_key_cid,
222230
power_base_epoch: info.power_base_epoch,
231+
daily_fee: info.daily_fee.into(),
223232
}
224233
}
225234
}

src/shim/actors/builtin/miner/ext/deadline.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
use super::*;
55
use num::Zero;
6+
use Deadline::*;
67

78
impl DeadlineExt for Deadline {
89
fn daily_fee(&self) -> TokenAmount {
9-
use Deadline::*;
1010
match self {
1111
V8(_) => Zero::zero(),
1212
V9(_) => Zero::zero(),
@@ -19,4 +19,18 @@ impl DeadlineExt for Deadline {
1919
V16(d) => (&d.daily_fee).into(),
2020
}
2121
}
22+
23+
fn live_power_qa(&self) -> BigInt {
24+
match self {
25+
V8(d) => Zero::zero(),
26+
V9(d) => Zero::zero(),
27+
V10(d) => Zero::zero(),
28+
V11(d) => Zero::zero(),
29+
V12(d) => Zero::zero(),
30+
V13(d) => Zero::zero(),
31+
V14(d) => Zero::zero(),
32+
V15(d) => Zero::zero(),
33+
V16(d) => d.live_power.qa.clone(),
34+
}
35+
}
2236
}

src/shim/actors/builtin/miner/ext/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,22 @@ use crate::shim::{
1515
use cid::Cid;
1616
use fil_actors_shared::fvm_ipld_bitfield::BitField;
1717
use fvm_ipld_blockstore::Blockstore;
18+
use num::BigInt;
1819

1920
use crate::rpc::types::{SectorOnChainInfo, SectorPreCommitOnChainInfo};
2021
use crate::shim::clock::ChainEpoch;
2122
use crate::utils::db::CborStoreExt as _;
2223

2324
pub trait MinerStateExt {
25+
fn sectors(&self) -> &Cid;
26+
2427
/// Loads sectors corresponding to the bitfield. If no bitfield is passed
2528
/// in, return all.
2629
fn load_sectors_ext<BS: Blockstore>(
2730
&self,
2831
store: &BS,
2932
sectors: Option<&BitField>,
30-
) -> anyhow::Result<Vec<SectorOnChainInfo>>;
33+
) -> anyhow::Result<Vec<(u64, SectorOnChainInfo)>>;
3134

3235
/// Loads the allocated sector numbers
3336
fn load_allocated_sector_numbers<BS: Blockstore>(&self, store: &BS)
@@ -55,4 +58,6 @@ pub trait PartitionExt {
5558

5659
pub trait DeadlineExt {
5760
fn daily_fee(&self) -> TokenAmount;
61+
62+
fn live_power_qa(&self) -> BigInt;
5863
}

0 commit comments

Comments
 (0)