Skip to content
1 change: 1 addition & 0 deletions fendermint/actors/blobs/shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub enum Method {
GetPendingBlobs = frc42_dispatch::method_hash!("GetPendingBlobs"),
FinalizeBlob = frc42_dispatch::method_hash!("FinalizeBlob"),
DeleteBlob = frc42_dispatch::method_hash!("DeleteBlob"),
UpdatePowerTable = frc42_dispatch::method_hash!("UpdatePowerTable"),
}

pub fn buy_credit(rt: &impl Runtime, recipient: Address) -> Result<Account, ActorError> {
Expand Down
7 changes: 6 additions & 1 deletion fendermint/actors/blobs/shared/src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ use fvm_shared::clock::ChainEpoch;
use fvm_shared::econ::TokenAmount;
use serde::{Deserialize, Serialize};

use crate::state::{BlobStatus, Hash, PublicKey};
use crate::state::{BlobStatus, Hash, PowerTableUpdates, PublicKey};

/// Params for buying credits.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct BuyCreditParams(pub Address);

/// Push the power table to the actor.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct UpdatePowerTableParams(pub PowerTableUpdates);

/// Params for approving credit.
#[derive(Clone, Debug, Serialize_tuple, Deserialize_tuple)]
pub struct ApproveCreditParams {
Expand Down
22 changes: 22 additions & 0 deletions fendermint/actors/blobs/shared/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,25 @@ pub struct Subscription {
/// The delegate origin and caller that may have created the subscription via a credit approval.
pub delegate: Option<(Address, Address)>,
}

// --- Copied from fendermint_vm_interpreter: PowerTable and friends --- //

/// Total voting power of a validator.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Power(pub u64);

/// Power table weights stored in Blobs actor state
pub type PowerTable = HashMap<Address, Power>;

/// Updates of the power table:
/// - new entries,
/// - changed entries contain the current value for `power`,
/// - deleted entries contain zero for `power`.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PowerTableUpdates(pub Vec<Validator>);

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Validator {
pub address: Address,
pub power: Power,
}
11 changes: 10 additions & 1 deletion fendermint/actors/blobs/src/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::collections::HashSet;
use fendermint_actor_blobs_shared::params::{
AddBlobParams, ApproveCreditParams, BuyCreditParams, DeleteBlobParams, FinalizeBlobParams,
GetAccountParams, GetBlobParams, GetBlobStatusParams, GetPendingBlobsParams, GetStatsReturn,
RevokeCreditParams,
RevokeCreditParams, UpdatePowerTableParams,
};
use fendermint_actor_blobs_shared::state::{
Account, Blob, BlobStatus, CreditApproval, Hash, PublicKey, Subscription,
Expand Down Expand Up @@ -116,6 +116,14 @@ impl BlobsActor {
})
}

fn update_power_table(
rt: &impl Runtime,
params: UpdatePowerTableParams,
) -> Result<(), ActorError> {
rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?;
rt.transaction(|st: &mut State, _rt| st.update_power_table(params.0))
}

fn revoke_credit(rt: &impl Runtime, params: RevokeCreditParams) -> Result<(), ActorError> {
rt.validate_immediate_caller_accept_any()?;
let (from, actor_type) = resolve_external(rt, params.from)?;
Expand Down Expand Up @@ -328,6 +336,7 @@ impl ActorCode for BlobsActor {
GetPendingBlobs => get_pending_blobs,
FinalizeBlob => finalize_blob,
DeleteBlob => delete_blob,
UpdatePowerTable => update_power_table,
_ => fallback,
}
}
Expand Down
23 changes: 22 additions & 1 deletion fendermint/actors/blobs/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use std::ops::Bound::{Included, Unbounded};

use fendermint_actor_blobs_shared::params::GetStatsReturn;
use fendermint_actor_blobs_shared::state::{
Account, Blob, BlobStatus, CreditApproval, Hash, PublicKey, Subscription,
Account, Blob, BlobStatus, CreditApproval, Hash, PowerTable, PowerTableUpdates, PublicKey,
Subscription,
};
use fil_actors_runtime::ActorError;
use fvm_ipld_encoding::tuple::*;
Expand Down Expand Up @@ -47,6 +48,8 @@ pub struct State {
pub expiries: BTreeMap<ChainEpoch, HashMap<Address, HashMap<Hash, bool>>>,
/// Map of currently pending blob hashes to account and source Iroh node IDs.
pub pending: BTreeMap<Hash, HashSet<(Address, PublicKey)>>,
/// Power table cache
pub power_table: PowerTable,
}

/// Helper for handling credit approvals.
Expand Down Expand Up @@ -77,6 +80,7 @@ impl State {
blobs: HashMap::new(),
expiries: BTreeMap::new(),
pending: BTreeMap::new(),
power_table: HashMap::new(),
}
}

Expand Down Expand Up @@ -166,6 +170,23 @@ impl State {
Ok(approval.clone())
}

pub fn update_power_table(
&mut self,
power_table_updates: PowerTableUpdates,
) -> anyhow::Result<(), ActorError> {
power_table_updates.0.iter().for_each(|validator| {
if validator.power.0.is_zero() {
// Remove if zero
self.power_table.remove(&validator.address);
} else {
// Update or insert if nonzero
self.power_table
.insert(validator.address, validator.power.clone());
}
});
Ok(())
}

pub fn revoke_credit(
&mut self,
from: Address,
Expand Down
63 changes: 59 additions & 4 deletions fendermint/vm/interpreter/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ use crate::{
use anyhow::{anyhow, bail, Context};
use async_stm::atomically;
use async_trait::async_trait;
use fendermint_actor_blobs_shared::params::{GetBlobStatusParams, GetPendingBlobsParams};
use fendermint_actor_blobs_shared::state::BlobStatus;
use fendermint_actor_blobs_shared::Method::DebitAccounts;
use fendermint_actor_blobs_shared::params::{
GetBlobStatusParams, GetPendingBlobsParams, UpdatePowerTableParams,
};
use fendermint_actor_blobs_shared::state::{BlobStatus, Power, PowerTableUpdates, Validator};
use fendermint_actor_blobs_shared::Method::{DebitAccounts, UpdatePowerTable};
use fendermint_actor_blobs_shared::{
params::FinalizeBlobParams,
Method::{FinalizeBlob, GetBlobStatus, GetPendingBlobs},
};
use fendermint_tracing::emit;
use fendermint_vm_actor_interface::eam::{EthAddress, EAM_ACTOR_ID};
use fendermint_vm_actor_interface::{blobs, ipc, system};
use fendermint_vm_event::ParentFinalityMissingQuorum;
use fendermint_vm_iroh_resolver::pool::{
Expand Down Expand Up @@ -746,10 +749,17 @@ where
&self,
(env, state): Self::State,
) -> anyhow::Result<(Self::State, Self::EndOutput)> {
let (state, out) = self.inner.end(state).await?;
let (mut state, out) = self.inner.end(state).await?;

// Update any component that needs to know about changes in the power table.
if !out.0 .0.is_empty() {
state.state_tree_mut().begin_transaction();
update_blobs_power_table(&mut state, &out.0)?;
state
.state_tree_mut()
.end_transaction(true)
.expect("we just started a transaction");

let power_updates = out
.0
.0
Expand Down Expand Up @@ -873,6 +883,51 @@ fn relayed_bottom_up_ckpt_to_fvm(
Ok(msg)
}

fn update_blobs_power_table<DB>(
state: &mut FvmExecState<DB>,
input_power_updates: &PowerUpdates,
) -> anyhow::Result<()>
where
DB: Blockstore + Clone + 'static + Send + Sync,
{
let blobs_power_table_updates = PowerTableUpdates(
input_power_updates
.0
.iter()
.filter_map(|validator| {
let power = validator.power.0;
let public_key = validator.public_key.0.serialize();
EthAddress::new_secp256k1(&public_key)
.and_then(|eth_address| Address::new_delegated(EAM_ACTOR_ID, &eth_address.0))
.map(Some)
.unwrap_or_else(|_error| {
tracing::debug!("can not construct delegated address from public key");
None
})
.map(|address| Validator {
power: Power(power),
address,
})
})
.collect::<Vec<_>>(),
);
let params = RawBytes::serialize(UpdatePowerTableParams(blobs_power_table_updates))?;
let msg = Message {
version: Default::default(),
from: system::SYSTEM_ACTOR_ADDR,
to: blobs::BLOBS_ACTOR_ADDR,
sequence: 0,
value: Default::default(),
method_num: UpdatePowerTable as u64,
params,
gas_limit: fvm_shared::BLOCK_GAS_LIMIT,
gas_fee_cap: Default::default(),
gas_premium: Default::default(),
};
state.execute_implicit(msg)?;
Ok(())
}

/// Get pending blobs from on chain state.
/// This approach uses an implicit FVM transaction to query a read-only blockstore.
fn get_pending_blobs<DB>(
Expand Down
3 changes: 1 addition & 2 deletions fendermint/vm/interpreter/src/fvm/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@

use anyhow::Context;
use async_trait::async_trait;
use std::collections::HashMap;

use fendermint_vm_actor_interface::{chainmetadata, cron, system};
use fvm::executor::ApplyRet;
use fvm_ipld_blockstore::Blockstore;
use fvm_shared::{address::Address, ActorID, MethodNum, BLOCK_GAS_LIMIT};
use ipc_observability::{emit, measure_time, observe::TracingError, Traceable};
use std::collections::HashMap;
use tendermint_rpc::Client;

use crate::ExecInterpreter;
Expand Down