diff --git a/fendermint/actors/blobs/shared/src/lib.rs b/fendermint/actors/blobs/shared/src/lib.rs index 1a26225ab..a0ff9dc53 100644 --- a/fendermint/actors/blobs/shared/src/lib.rs +++ b/fendermint/actors/blobs/shared/src/lib.rs @@ -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 { diff --git a/fendermint/actors/blobs/shared/src/params.rs b/fendermint/actors/blobs/shared/src/params.rs index d48c02788..fec25ead6 100644 --- a/fendermint/actors/blobs/shared/src/params.rs +++ b/fendermint/actors/blobs/shared/src/params.rs @@ -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 { diff --git a/fendermint/actors/blobs/shared/src/state.rs b/fendermint/actors/blobs/shared/src/state.rs index 99b54ed34..c8326c452 100644 --- a/fendermint/actors/blobs/shared/src/state.rs +++ b/fendermint/actors/blobs/shared/src/state.rs @@ -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; + +/// 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); + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct Validator { + pub address: Address, + pub power: Power, +} diff --git a/fendermint/actors/blobs/src/actor.rs b/fendermint/actors/blobs/src/actor.rs index 45f22c075..7a2b5abfe 100644 --- a/fendermint/actors/blobs/src/actor.rs +++ b/fendermint/actors/blobs/src/actor.rs @@ -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, @@ -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)?; @@ -328,6 +336,7 @@ impl ActorCode for BlobsActor { GetPendingBlobs => get_pending_blobs, FinalizeBlob => finalize_blob, DeleteBlob => delete_blob, + UpdatePowerTable => update_power_table, _ => fallback, } } diff --git a/fendermint/actors/blobs/src/state.rs b/fendermint/actors/blobs/src/state.rs index 05c86bc48..a2371576a 100644 --- a/fendermint/actors/blobs/src/state.rs +++ b/fendermint/actors/blobs/src/state.rs @@ -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::*; @@ -47,6 +48,8 @@ pub struct State { pub expiries: BTreeMap>>, /// Map of currently pending blob hashes to account and source Iroh node IDs. pub pending: BTreeMap>, + /// Power table cache + pub power_table: PowerTable, } /// Helper for handling credit approvals. @@ -77,6 +80,7 @@ impl State { blobs: HashMap::new(), expiries: BTreeMap::new(), pending: BTreeMap::new(), + power_table: HashMap::new(), } } @@ -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, diff --git a/fendermint/vm/interpreter/src/chain.rs b/fendermint/vm/interpreter/src/chain.rs index 5664cbd08..c25a84d5a 100644 --- a/fendermint/vm/interpreter/src/chain.rs +++ b/fendermint/vm/interpreter/src/chain.rs @@ -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::{ @@ -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 @@ -873,6 +883,51 @@ fn relayed_bottom_up_ckpt_to_fvm( Ok(msg) } +fn update_blobs_power_table( + state: &mut FvmExecState, + 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, ð_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::>(), + ); + 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( diff --git a/fendermint/vm/interpreter/src/fvm/exec.rs b/fendermint/vm/interpreter/src/fvm/exec.rs index 0d84e888d..cb686f309 100644 --- a/fendermint/vm/interpreter/src/fvm/exec.rs +++ b/fendermint/vm/interpreter/src/fvm/exec.rs @@ -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;