Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc): implement Filecoin.MsigGetVested and Filecoin.MsigGetVestingSchedule #4304

Merged
merged 11 commits into from
May 10, 2024
3 changes: 2 additions & 1 deletion src/rpc/auth_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use crate::auth::{verify_token, JWT_IDENTIFIER};
use crate::key_management::KeyStore;
use crate::rpc::{
auth, beacon, chain, common, eth, gas, miner, mpool, net, node, state, sync, wallet,
auth, beacon, chain, common, eth, gas, miner, mpool, msig, net, node, state, sync, wallet,
Permission, RpcMethod as _, CANCEL_METHOD_NAME,
};
use ahash::{HashMap, HashMapExt as _};
Expand Down Expand Up @@ -43,6 +43,7 @@ static METHOD_NAME2REQUIRED_PERMISSION: Lazy<HashMap<&str, Permission>> = Lazy::
sync::for_each_method!(insert);
wallet::for_each_method!(insert);
eth::for_each_method!(insert);
msig::for_each_method!(insert);

access.insert(chain::CHAIN_NOTIFY, Permission::Read);
access.insert(CANCEL_METHOD_NAME, Permission::Read);
Expand Down
181 changes: 181 additions & 0 deletions src/rpc/methods/msig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use crate::rpc::error::ServerError;
use crate::rpc::types::ApiTipsetKey;
use crate::rpc::types::*;
use crate::rpc::{ApiVersion, Ctx, Permission, RpcMethod};
use crate::shim::{address::Address, econ::TokenAmount};
use fil_actor_interface::multisig;
use fvm_ipld_blockstore::Blockstore;
use num_bigint::BigInt;

macro_rules! for_each_method {
($callback:ident) => {
$callback!(crate::rpc::msig::MsigGetAvailableBalance);
$callback!(crate::rpc::msig::MsigGetPending);
$callback!(crate::rpc::msig::MsigGetVested);
$callback!(crate::rpc::msig::MsigGetVestingSchedule);
};
}
pub(crate) use for_each_method;

pub enum MsigGetAvailableBalance {}

impl RpcMethod<2> for MsigGetAvailableBalance {
const NAME: &'static str = "Filecoin.MsigGetAvailableBalance";
const PARAM_NAMES: [&'static str; 2] = ["address", "tipset_key"];
const API_VERSION: ApiVersion = ApiVersion::V0;
const PERMISSION: Permission = Permission::Read;

type Params = (Address, ApiTipsetKey);
type Ok = TokenAmount;

async fn handle(
ctx: Ctx<impl Blockstore>,
(address, ApiTipsetKey(tsk)): Self::Params,
) -> Result<Self::Ok, ServerError> {
let ts = ctx.chain_store.load_required_tipset_or_heaviest(&tsk)?;
let height = ts.epoch();
let actor = ctx
.state_manager
.get_required_actor(&address, *ts.parent_state())?;
let actor_balance = TokenAmount::from(&actor.balance);
let ms = multisig::State::load(ctx.store(), actor.code, actor.state)?;
let locked_balance = ms.locked_balance(height)?.into();
let avail_balance = &actor_balance - locked_balance;
Ok(avail_balance)
}
}

pub enum MsigGetPending {}

impl RpcMethod<2> for MsigGetPending {
const NAME: &'static str = "Filecoin.MsigGetPending";
const PARAM_NAMES: [&'static str; 2] = ["address", "tipset_key"];
const API_VERSION: ApiVersion = ApiVersion::V0;
const PERMISSION: Permission = Permission::Read;

type Params = (Address, ApiTipsetKey);
type Ok = Vec<Transaction>;

async fn handle(
ctx: Ctx<impl Blockstore>,
(address, ApiTipsetKey(tsk)): Self::Params,
) -> Result<Self::Ok, ServerError> {
let ts = ctx.chain_store.load_required_tipset_or_heaviest(&tsk)?;
let actor = ctx
.state_manager
.get_required_actor(&address, *ts.parent_state())?;
let ms = multisig::State::load(ctx.store(), actor.code, actor.state)?;
let txns = ms
.get_pending_txn(ctx.store())?
.iter()
.map(|txn| Transaction {
id: txn.id,
to: txn.to.into(),
value: txn.value.clone().into(),
method: txn.method,
params: txn.params.clone(),
approved: txn.approved.iter().map(|item| item.into()).collect(),
})
.collect();
Ok(txns)
}
}

pub enum MsigGetVested {}
impl RpcMethod<3> for MsigGetVested {
const NAME: &'static str = "Filecoin.MsigGetVested";
const PARAM_NAMES: [&'static str; 3] = ["address", "start_tsk", "end_tsk"];
const API_VERSION: ApiVersion = ApiVersion::V0;
const PERMISSION: Permission = Permission::Read;

type Params = (Address, ApiTipsetKey, ApiTipsetKey);
type Ok = BigInt;

async fn handle(
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
(addr, ApiTipsetKey(start_tsk), ApiTipsetKey(end_tsk)): Self::Params,
) -> Result<Self::Ok, ServerError> {
let start_ts = ctx
.chain_store
.load_required_tipset_or_heaviest(&start_tsk)?;
let end_ts = ctx.chain_store.load_required_tipset_or_heaviest(&end_tsk)?;

match start_ts.epoch().cmp(&end_ts.epoch()) {
std::cmp::Ordering::Greater => Err(ServerError::internal_error(
"start tipset is after end tipset",
None,
)),
std::cmp::Ordering::Equal => Ok(BigInt::from(0)),
std::cmp::Ordering::Less => {
let msig_actor = ctx
.state_manager
.get_required_actor(&addr, *end_ts.parent_state())?;
let ms = multisig::State::load(ctx.store(), msig_actor.code, msig_actor.state)?;
let start_lb: TokenAmount = ms.locked_balance(start_ts.epoch())?.into();
let end_lb: TokenAmount = ms.locked_balance(end_ts.epoch())?.into();
Ok(start_lb.atto() - end_lb.atto())
}
}
}
}

pub enum MsigGetVestingSchedule {}
impl RpcMethod<2> for MsigGetVestingSchedule {
const NAME: &'static str = "Filecoin.MsigGetVestingSchedule";
const PARAM_NAMES: [&'static str; 2] = ["address", "tsk"];
const API_VERSION: ApiVersion = ApiVersion::V0;
const PERMISSION: Permission = Permission::Read;

type Params = (Address, ApiTipsetKey);
type Ok = MsigVesting;

async fn handle(
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
(addr, ApiTipsetKey(tsk)): Self::Params,
) -> Result<Self::Ok, ServerError> {
let ts = ctx.chain_store.load_required_tipset_or_heaviest(&tsk)?;

let msig_actor = ctx
.state_manager
.get_required_actor(&addr, *ts.parent_state())?;
let ms = multisig::State::load(ctx.store(), msig_actor.code, msig_actor.state)?;

let msig_vesting = match &ms {
multisig::State::V8(st) => MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
},
multisig::State::V9(st) => MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
},
multisig::State::V10(st) => MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
},
multisig::State::V11(st) => MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
},
multisig::State::V12(st) => MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
},
multisig::State::V13(st) => MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
},
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a good place for this? @hanabi1224 implemented it src/shim/actors/market/balance_table.rs, which allows re-usability.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I remember we usually have this state matching code in fil-actor-states, I guess we should either keep it that way or move all of that logic out to shim. It's much better to have all the similar logic in predictable places, otherwise it's hard to navigate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, we have a custom struct here. Then perhaps it belongs to forest.

The stuff I was talking about before:

https://github.com/ChainSafe/fil-actor-states/blob/main/fil_actor_interface/src/builtin/market/mod.rs#L171

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved inside src/shim/actors/multisig


Ok(msig_vesting)
}
}
68 changes: 1 addition & 67 deletions src/rpc/methods/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use fil_actor_interface::market::DealState;
use fil_actor_interface::{
market, miner,
miner::{MinerInfo, MinerPower},
multisig, power, reward,
power, reward,
};
use fil_actor_miner_state::v10::{qa_power_for_weight, qa_power_max};
use fil_actors_shared::fvm_ipld_bitfield::BitField;
Expand Down Expand Up @@ -82,8 +82,6 @@ macro_rules! for_each_method {
$callback!(crate::rpc::state::StateGetRandomnessFromBeacon);
$callback!(crate::rpc::state::StateReadState);
$callback!(crate::rpc::state::StateCirculatingSupply);
$callback!(crate::rpc::state::MsigGetAvailableBalance);
$callback!(crate::rpc::state::MsigGetPending);
$callback!(crate::rpc::state::StateVerifiedClientStatus);
$callback!(crate::rpc::state::StateVMCirculatingSupplyInternal);
$callback!(crate::rpc::state::StateListMiners);
Expand Down Expand Up @@ -1248,70 +1246,6 @@ impl RpcMethod<1> for StateCirculatingSupply {
}
}

pub enum MsigGetAvailableBalance {}

impl RpcMethod<2> for MsigGetAvailableBalance {
const NAME: &'static str = "Filecoin.MsigGetAvailableBalance";
const PARAM_NAMES: [&'static str; 2] = ["address", "tipset_key"];
const API_VERSION: ApiVersion = ApiVersion::V0;
const PERMISSION: Permission = Permission::Read;

type Params = (Address, ApiTipsetKey);
type Ok = TokenAmount;

async fn handle(
ctx: Ctx<impl Blockstore>,
(address, ApiTipsetKey(tsk)): Self::Params,
) -> Result<Self::Ok, ServerError> {
let ts = ctx.chain_store.load_required_tipset_or_heaviest(&tsk)?;
let height = ts.epoch();
let actor = ctx
.state_manager
.get_required_actor(&address, *ts.parent_state())?;
let actor_balance = TokenAmount::from(&actor.balance);
let ms = multisig::State::load(ctx.store(), actor.code, actor.state)?;
let locked_balance = ms.locked_balance(height)?.into();
let avail_balance = &actor_balance - locked_balance;
Ok(avail_balance)
}
}

pub enum MsigGetPending {}

impl RpcMethod<2> for MsigGetPending {
const NAME: &'static str = "Filecoin.MsigGetPending";
const PARAM_NAMES: [&'static str; 2] = ["address", "tipset_key"];
const API_VERSION: ApiVersion = ApiVersion::V0;
const PERMISSION: Permission = Permission::Read;

type Params = (Address, ApiTipsetKey);
type Ok = Vec<Transaction>;

async fn handle(
ctx: Ctx<impl Blockstore>,
(address, ApiTipsetKey(tsk)): Self::Params,
) -> Result<Self::Ok, ServerError> {
let ts = ctx.chain_store.load_required_tipset_or_heaviest(&tsk)?;
let actor = ctx
.state_manager
.get_required_actor(&address, *ts.parent_state())?;
let ms = multisig::State::load(ctx.store(), actor.code, actor.state)?;
let txns = ms
.get_pending_txn(ctx.store())?
.iter()
.map(|txn| Transaction {
id: txn.id,
to: txn.to.into(),
value: txn.value.clone().into(),
method: txn.method,
params: txn.params.clone(),
approved: txn.approved.iter().map(|item| item.into()).collect(),
})
.collect();
Ok(txns)
}
}

pub enum StateVerifiedClientStatus {}

impl RpcMethod<2> for StateVerifiedClientStatus {
Expand Down
3 changes: 3 additions & 0 deletions src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub mod prelude {
sync::for_each_method!(export);
wallet::for_each_method!(export);
eth::for_each_method!(export);
msig::for_each_method!(export);
}

/// All the methods live in their own folder
Expand Down Expand Up @@ -109,6 +110,7 @@ mod methods {
pub mod gas;
pub mod miner;
pub mod mpool;
pub mod msig;
pub mod net;
pub mod node;
pub mod state;
Expand Down Expand Up @@ -276,6 +278,7 @@ where
sync::for_each_method!(register);
wallet::for_each_method!(register);
eth::for_each_method!(register);
msig::for_each_method!(register);
module.finish()
}

Expand Down
12 changes: 12 additions & 0 deletions src/rpc/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ pub struct ApiDealState {

lotus_json_with_self!(ApiDealState);

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "PascalCase")]
pub struct MsigVesting {
#[schemars(with = "LotusJson<BigInt>")]
#[serde(with = "crate::lotus_json")]
pub initial_balance: BigInt,
pub start_epoch: ChainEpoch,
pub unlock_duration: ChainEpoch,
}

lotus_json_with_self!(MsigVesting);

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "PascalCase")]
pub struct ApiDealProposal {
Expand Down
9 changes: 9 additions & 0 deletions src/tool/subcommands/api_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,15 @@ fn state_tests_with_tipset<DB: Blockstore>(
Address::new_id(18101), // msig address id
tipset.key().into(),
))?),
RpcTest::identity(MsigGetVested::request((
Address::new_id(18101), // msig address id
tipset.parents().into(),
tipset.key().into(),
))?),
RpcTest::identity(MsigGetVestingSchedule::request((
Address::new_id(18101), // msig address id
tipset.key().into(),
))?),
RpcTest::identity(StateGetBeaconEntry::request((tipset.epoch(),))?),
// Not easily verifiable by using addresses extracted from blocks as most of those yield `null`
// for both Lotus and Forest. Therefore the actor addresses are hardcoded to values that allow
Expand Down
Loading