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
149 changes: 149 additions & 0 deletions src/rpc/methods/msig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// 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::actors::multisig::MultisigExt;
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)?;

Ok(ms.get_vesting_schedule()?)
}
}
68 changes: 1 addition & 67 deletions src/rpc/methods/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,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 @@ -81,8 +81,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 @@ -1247,70 +1245,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
1 change: 1 addition & 0 deletions src/shim/actors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

pub mod market;
pub mod miner;
pub mod multisig;
11 changes: 11 additions & 0 deletions src/shim/actors/multisig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

mod state;

use crate::rpc::types::MsigVesting;
use fil_actor_interface::multisig::State;

pub trait MultisigExt {
fn get_vesting_schedule(&self) -> anyhow::Result<MsigVesting>;
}
41 changes: 41 additions & 0 deletions src/shim/actors/multisig/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use super::*;

impl MultisigExt for State {
fn get_vesting_schedule(&self) -> anyhow::Result<MsigVesting> {
match self {
State::V8(st) => Ok(MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
}),
State::V9(st) => Ok(MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
}),
State::V10(st) => Ok(MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
}),
State::V11(st) => Ok(MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
}),
State::V12(st) => Ok(MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
}),
State::V13(st) => Ok(MsigVesting {
initial_balance: st.initial_balance.atto().clone(),
start_epoch: st.start_epoch,
unlock_duration: st.unlock_duration,
}),
}
}
}
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