recall_sdk/
storage.rs

1// Copyright 2025 Recall Contributors
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use anyhow::anyhow;
5use recall_fendermint_actor_blobs_shared::params::GetAccountParams;
6use recall_fendermint_actor_blobs_shared::Method::{GetAccount, GetStats};
7use recall_fendermint_vm_actor_interface::blobs::BLOBS_ACTOR_ADDR;
8
9use serde::{Deserialize, Serialize};
10use tendermint::abci::response::DeliverTx;
11
12use recall_provider::{
13    fvm_ipld_encoding,
14    fvm_shared::address::Address,
15    message::{local_message, RawBytes},
16    query::{FvmQueryHeight, QueryProvider},
17    response::decode_bytes,
18};
19
20// Commands to support:
21//   ✓ recall storage stats (subnet-wide summary)
22//   ✓ recall storage usage --address (see usage by account)
23//   recall storage add (add a blob directly)
24//   recall storage get [hash] (get a blob info directly)
25//   recall storage cat [hash] (get a blob directly)
26//   recall storage ls --address (list blobs by account)
27
28/// Storage usage stats for an account.
29#[derive(Clone, Debug, Serialize, Deserialize)]
30pub struct Usage {
31    // Total size of all blobs managed by the account.
32    pub capacity_used: String,
33}
34
35impl Default for Usage {
36    fn default() -> Self {
37        Self {
38            capacity_used: "0".into(),
39        }
40    }
41}
42
43impl From<recall_fendermint_actor_blobs_shared::state::AccountInfo> for Usage {
44    fn from(v: recall_fendermint_actor_blobs_shared::state::AccountInfo) -> Self {
45        Self {
46            capacity_used: v.capacity_used.to_string(),
47        }
48    }
49}
50
51/// Subnet-wide storage statistics.
52#[derive(Clone, Debug, Serialize, Deserialize)]
53pub struct StorageStats {
54    /// The total free storage capacity of the subnet.
55    pub capacity_free: String,
56    /// The total used storage capacity of the subnet.
57    pub capacity_used: String,
58    /// Total number of actively stored blobs.
59    pub num_blobs: u64,
60    /// Total number of currently resolving blobs.
61    pub num_resolving: u64,
62    /// Total number of debit accounts.
63    pub num_accounts: u64,
64    /// Total bytes of all currently resolving blobs.
65    pub bytes_resolving: u64,
66    /// Total number of blobs that are not yet added to the validator's resolve pool.
67    pub num_added: u64,
68    // Total bytes of all blobs that are not yet added to the validator's resolve pool.
69    pub bytes_added: u64,
70}
71
72impl From<recall_fendermint_actor_blobs_shared::params::GetStatsReturn> for StorageStats {
73    fn from(v: recall_fendermint_actor_blobs_shared::params::GetStatsReturn) -> Self {
74        Self {
75            capacity_free: v.capacity_free.to_string(),
76            capacity_used: v.capacity_used.to_string(),
77            num_blobs: v.num_blobs,
78            num_resolving: v.num_resolving,
79            num_accounts: v.num_accounts,
80            bytes_resolving: v.bytes_resolving,
81            num_added: v.num_added,
82            bytes_added: v.bytes_added,
83        }
84    }
85}
86
87/// A static wrapper around Recall storage methods.
88pub struct Storage {}
89
90impl Storage {
91    pub async fn stats(
92        provider: &impl QueryProvider,
93        height: FvmQueryHeight,
94    ) -> anyhow::Result<StorageStats> {
95        let message = local_message(BLOBS_ACTOR_ADDR, GetStats as u64, Default::default());
96        let response = provider.call(message, height, decode_stats).await?;
97        Ok(response.value)
98    }
99
100    pub async fn usage(
101        provider: &impl QueryProvider,
102        address: Address,
103        height: FvmQueryHeight,
104    ) -> anyhow::Result<Usage> {
105        let params = GetAccountParams(address);
106        let params = RawBytes::serialize(params)?;
107        let message = local_message(BLOBS_ACTOR_ADDR, GetAccount as u64, params);
108        let response = provider.call(message, height, decode_usage).await?;
109        if let Some(account) = response.value {
110            Ok(account)
111        } else {
112            Ok(Usage::default())
113        }
114    }
115}
116
117fn decode_stats(deliver_tx: &DeliverTx) -> anyhow::Result<StorageStats> {
118    let data = decode_bytes(deliver_tx)?;
119    fvm_ipld_encoding::from_slice::<recall_fendermint_actor_blobs_shared::params::GetStatsReturn>(&data)
120        .map(|v| v.into())
121        .map_err(|e| anyhow!("error parsing as StorageStats: {e}"))
122}
123
124fn decode_usage(deliver_tx: &DeliverTx) -> anyhow::Result<Option<Usage>> {
125    let data = decode_bytes(deliver_tx)?;
126    fvm_ipld_encoding::from_slice::<Option<recall_fendermint_actor_blobs_shared::state::AccountInfo>>(
127        &data,
128    )
129    .map(|v| v.map(|v| v.into()))
130    .map_err(|e| anyhow!("error parsing as Option<Usage>: {e}"))
131}