Skip to content

Commit

Permalink
refactor: hamt-ify credit approvals
Browse files Browse the repository at this point in the history
Signed-off-by: Bruno Calza <[email protected]>
  • Loading branch information
brunocalza committed Feb 24, 2025
1 parent 4643146 commit 2f78dea
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 106 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions fendermint/actors/blobs/shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ crate-type = ["cdylib", "lib"]
[dependencies]
anyhow = { workspace = true }
data-encoding = { workspace = true }
fendermint_actor_machine = { path = "../../machine" }
fil_actors_runtime = { workspace = true }
frc42_dispatch = { workspace = true }
fvm_ipld_blockstore = { workspace = true }
fvm_ipld_encoding = { workspace = true }
fvm_shared = { workspace = true }
num-derive = { workspace = true }
Expand Down
123 changes: 116 additions & 7 deletions fendermint/actors/blobs/shared/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ use std::collections::HashMap;
use std::fmt;
use std::ops::{Div, Mul};

use fendermint_actor_machine::util::to_delegated_address;
use fil_actors_runtime::runtime::Runtime;
use fil_actors_runtime::ActorError;
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::tuple::*;
use fvm_shared::address::Address;
use fvm_shared::bigint::BigInt;
use fvm_shared::clock::ChainEpoch;
use fvm_shared::econ::TokenAmount;
use recall_ipld::hamt;
use recall_ipld::hamt::map::TrackedFlushResult;
use recall_ipld::hamt::MapKey;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -73,7 +78,7 @@ impl Ord for TokenCreditRate {
}

/// The stored representation of a credit account.
#[derive(Clone, Debug, Default, PartialEq, Serialize_tuple, Deserialize_tuple)]
#[derive(Clone, Debug, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct Account {
/// Total size of all blobs managed by the account.
pub capacity_used: u64,
Expand All @@ -86,22 +91,32 @@ pub struct Account {
/// The chain epoch of the last debit.
pub last_debit_epoch: ChainEpoch,
/// Credit approvals to other accounts from this account, keyed by receiver.
pub approvals_to: HashMap<String, CreditApproval>,
pub approvals_to: CreditApprovals,
/// Credit approvals to this account from other accounts, keyed by sender.
pub approvals_from: HashMap<String, CreditApproval>,
pub approvals_from: CreditApprovals,
/// The maximum allowed TTL for actor's blobs.
pub max_ttl: ChainEpoch,
/// The total token value an account has used to buy credits.
pub gas_allowance: TokenAmount,
}

impl Account {
pub fn new(current_epoch: ChainEpoch, max_ttl: ChainEpoch) -> Self {
Self {
pub fn new<BS: Blockstore>(
store: &BS,
current_epoch: ChainEpoch,
max_ttl: ChainEpoch,
) -> Result<Self, ActorError> {
Ok(Self {
last_debit_epoch: current_epoch,
max_ttl,
..Default::default()
}
capacity_used: 0,
credit_free: Credit::default(),
credit_committed: Credit::default(),
credit_sponsor: None,
approvals_to: CreditApprovals::new(store)?,
approvals_from: CreditApprovals::new(store)?,
gas_allowance: TokenAmount::default(),
})
}
}

Expand Down Expand Up @@ -415,6 +430,100 @@ impl fmt::Display for TtlStatus {
}
}

#[derive(Debug, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct CreditApprovals {
pub root: hamt::Root<Address, CreditApproval>,
size: u64,
}

impl CreditApprovals {
pub fn new<BS: Blockstore>(store: &BS) -> Result<Self, ActorError> {
let root = hamt::Root::<Address, CreditApproval>::new(store, "credit_approvals")?;
Ok(Self { root, size: 0 })
}

pub fn hamt<BS: Blockstore>(
&self,
store: BS,
) -> Result<hamt::map::Hamt<BS, Address, CreditApproval>, ActorError> {
self.root.hamt(store, self.size)
}
pub fn save_tracked(
&mut self,
tracked_flush_result: TrackedFlushResult<Address, CreditApproval>,
) {
self.root = tracked_flush_result.root;
self.size = tracked_flush_result.size
}

pub fn len(&self) -> u64 {
self.size
}

pub fn is_empty(&self) -> bool {
self.size == 0
}
}

/// The return type used for Account.
#[derive(Debug, Serialize_tuple, Deserialize_tuple)]
pub struct AccountInfo {
/// Total size of all blobs managed by the account.
pub capacity_used: u64,
/// Current free credit in byte-blocks that can be used for new commitments.
pub credit_free: Credit,
/// Current committed credit in byte-blocks that will be used for debits.
pub credit_committed: Credit,
/// Optional default sponsor account address.
pub credit_sponsor: Option<Address>,
/// The chain epoch of the last debit.
pub last_debit_epoch: ChainEpoch,
/// Credit approvals to other accounts from this account, keyed by receiver.
pub approvals_to: HashMap<String, CreditApproval>,
/// Credit approvals to this account from other accounts, keyed by sender.
pub approvals_from: HashMap<String, CreditApproval>,
/// The maximum allowed TTL for actor's blobs.
pub max_ttl: ChainEpoch,
/// The total token value an account has used to buy credits.
pub gas_allowance: TokenAmount,
}

impl AccountInfo {
pub fn from(account: Account, rt: &impl Runtime) -> Result<Self, ActorError> {
let mut approvals_to = HashMap::new();
account
.approvals_to
.hamt(rt.store())?
.for_each(|address, approval| {
let external_account_address = to_delegated_address(rt, address)?;
approvals_to.insert(external_account_address.to_string(), approval.clone());
Ok(())
})?;

let mut approvals_from = HashMap::new();
account
.approvals_from
.hamt(rt.store())?
.for_each(|address, approval| {
let external_account_address = to_delegated_address(rt, address)?;
approvals_from.insert(external_account_address.to_string(), approval.clone());
Ok(())
})?;

Ok(AccountInfo {
capacity_used: account.capacity_used,
credit_free: account.credit_free,
credit_committed: account.credit_committed,
credit_sponsor: account.credit_sponsor,
last_debit_epoch: account.last_debit_epoch,
approvals_to,
approvals_from,
max_ttl: account.max_ttl,
gas_allowance: account.gas_allowance,
})
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
46 changes: 10 additions & 36 deletions fendermint/actors/blobs/src/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// Copyright 2021-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT

use std::collections::{HashMap, HashSet};
use std::str::FromStr;
use std::collections::HashSet;

use fendermint_actor_blobs_shared::params::{
AddBlobParams, ApproveCreditParams, BuyCreditParams, DeleteBlobParams, FinalizeBlobParams,
Expand All @@ -13,8 +12,8 @@ use fendermint_actor_blobs_shared::params::{
SetSponsorParams, TrimBlobExpiriesParams, UpdateGasAllowanceParams,
};
use fendermint_actor_blobs_shared::state::{
Account, Blob, BlobStatus, Credit, CreditApproval, GasAllowance, Hash, PublicKey, Subscription,
SubscriptionId,
AccountInfo, Blob, BlobStatus, Credit, CreditApproval, GasAllowance, Hash, PublicKey,
Subscription, SubscriptionId,
};
use fendermint_actor_blobs_shared::Method;
use fendermint_actor_machine::events::emit_evm_event;
Expand Down Expand Up @@ -82,7 +81,7 @@ impl BlobsActor {
/// Buy credit with token.
///
/// The recipient address must be delegated (only delegated addresses can own credit).
fn buy_credit(rt: &impl Runtime, params: BuyCreditParams) -> Result<Account, ActorError> {
fn buy_credit(rt: &impl Runtime, params: BuyCreditParams) -> Result<AccountInfo, ActorError> {
rt.validate_immediate_caller_accept_any()?;

let (id_addr, delegated_addr) = to_id_and_delegated_address(rt, params.0)?;
Expand All @@ -108,7 +107,7 @@ impl BlobsActor {
credit_purchased(delegated_addr, token_to_biguint(Some(credit_amount))),
)?;

Ok(account)
AccountInfo::from(account, rt)
}

/// Updates gas allowance for the `from` address.
Expand Down Expand Up @@ -306,7 +305,7 @@ impl BlobsActor {
fn get_account(
rt: &impl Runtime,
params: GetAccountParams,
) -> Result<Option<Account>, ActorError> {
) -> Result<Option<AccountInfo>, ActorError> {
rt.validate_immediate_caller_accept_any()?;

let from = to_id_address(rt, params.0, false)?;
Expand All @@ -315,16 +314,13 @@ impl BlobsActor {
.state::<State>()?
.get_account(rt.store(), from)?
.map(|mut account| {
// Iterate over the approvals maps and resolve all the addresses to their external form
account.approvals_to = resolve_approvals_addresses(rt, account.approvals_to)?;
account.approvals_from = resolve_approvals_addresses(rt, account.approvals_from)?;
// Resolve the credit sponsor
account.credit_sponsor = account
.credit_sponsor
.map(|sponsor| to_delegated_address(rt, sponsor))
.transpose()?;

Ok(account)
AccountInfo::from(account, rt)
});

account.transpose()
Expand Down Expand Up @@ -793,28 +789,6 @@ fn delete_from_disc(hash: Hash) -> Result<(), ActorError> {
}
}

/// Takes a map of credit approvals keyed by account ID addresses and resolves the keys into
/// external addresses instead.
fn resolve_approvals_addresses(
rt: &impl Runtime,
approvals: HashMap<String, CreditApproval>,
) -> Result<HashMap<String, CreditApproval>, ActorError> {
let mut resolved_approvals = HashMap::new();
for (account_key, approval) in approvals.into_iter() {
let account_address = Address::from_str(&account_key).map_err(|err| {
ActorError::serialization(format!(
"Failed to parse address {} from approvals map: {:?}",
account_key, err
))
})?;

let external_account_address = to_delegated_address(rt, account_address)?;
resolved_approvals.insert(external_account_address.to_string(), approval);
}

Ok(resolved_approvals)
}

impl ActorCode for BlobsActor {
type Methods = Method;

Expand Down Expand Up @@ -988,7 +962,7 @@ mod tests {
)
.unwrap()
.unwrap()
.deserialize::<Account>()
.deserialize::<AccountInfo>()
.unwrap();
assert_eq!(result.credit_free, expected_credits);
assert_eq!(result.gas_allowance, expected_gas_allowance);
Expand All @@ -1009,7 +983,7 @@ mod tests {
)
.unwrap()
.unwrap()
.deserialize::<Account>()
.deserialize::<AccountInfo>()
.unwrap();
assert_eq!(result.credit_free, expected_credits);
assert_eq!(result.gas_allowance, expected_gas_allowance);
Expand All @@ -1030,7 +1004,7 @@ mod tests {
)
.unwrap()
.unwrap()
.deserialize::<Account>()
.deserialize::<AccountInfo>()
.unwrap();
assert_eq!(result.credit_free, expected_credits);
assert_eq!(result.gas_allowance, expected_gas_allowance);
Expand Down
Loading

0 comments on commit 2f78dea

Please sign in to comment.