Skip to main content

reth_cli_commands/db/
account_storage.rs

1use alloy_primitives::{keccak256, Address};
2use clap::Parser;
3use human_bytes::human_bytes;
4use reth_codecs::Compact;
5use reth_db_api::{cursor::DbDupCursorRO, database::Database, tables, transaction::DbTx};
6use reth_db_common::DbTool;
7use reth_node_builder::NodeTypesWithDB;
8use reth_storage_api::StorageSettingsCache;
9use std::time::{Duration, Instant};
10use tracing::info;
11
12/// Log progress every 5 seconds
13const LOG_INTERVAL: Duration = Duration::from_secs(5);
14
15/// The arguments for the `reth db account-storage` command
16#[derive(Parser, Debug)]
17pub struct Command {
18    /// The account address to check storage for
19    address: Address,
20}
21
22impl Command {
23    /// Execute `db account-storage` command
24    pub fn execute<N: NodeTypesWithDB>(self, tool: &DbTool<N>) -> eyre::Result<()> {
25        let address = self.address;
26        let use_hashed_state = tool.provider_factory.cached_storage_settings().use_hashed_state();
27
28        let (slot_count, storage_size) = if use_hashed_state {
29            let hashed_address = keccak256(address);
30            tool.provider_factory.db_ref().view(|tx| {
31                let mut cursor = tx.cursor_dup_read::<tables::HashedStorages>()?;
32                let mut count = 0usize;
33                let mut total_value_bytes = 0usize;
34                let mut last_log = Instant::now();
35
36                let walker = cursor.walk_dup(Some(hashed_address), None)?;
37                for entry in walker {
38                    let (_, storage_entry) = entry?;
39                    count += 1;
40                    let mut buf = Vec::new();
41                    let entry_len = storage_entry.to_compact(&mut buf);
42                    total_value_bytes += entry_len;
43
44                    if last_log.elapsed() >= LOG_INTERVAL {
45                        info!(
46                            target: "reth::cli",
47                            address = %address,
48                            slots = count,
49                            key = %storage_entry.key,
50                            "Processing hashed storage slots"
51                        );
52                        last_log = Instant::now();
53                    }
54                }
55
56                let total_size = if count > 0 { 32 + total_value_bytes } else { 0 };
57
58                Ok::<_, eyre::Report>((count, total_size))
59            })??
60        } else {
61            tool.provider_factory.db_ref().view(|tx| {
62                let mut cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
63                let mut count = 0usize;
64                let mut total_value_bytes = 0usize;
65                let mut last_log = Instant::now();
66
67                // Walk all storage entries for this address
68                let walker = cursor.walk_dup(Some(address), None)?;
69                for entry in walker {
70                    let (_, storage_entry) = entry?;
71                    count += 1;
72                    let mut buf = Vec::new();
73                    // StorageEntry encodes as: 32 bytes (key/subkey uncompressed) + compressed U256
74                    let entry_len = storage_entry.to_compact(&mut buf);
75                    total_value_bytes += entry_len;
76
77                    if last_log.elapsed() >= LOG_INTERVAL {
78                        info!(
79                            target: "reth::cli",
80                            address = %address,
81                            slots = count,
82                            key = %storage_entry.key,
83                            "Processing storage slots"
84                        );
85                        last_log = Instant::now();
86                    }
87                }
88
89                // Add 20 bytes for the Address key (stored once per account in dupsort)
90                let total_size = if count > 0 { 20 + total_value_bytes } else { 0 };
91
92                Ok::<_, eyre::Report>((count, total_size))
93            })??
94        };
95
96        let hashed_address = keccak256(address);
97
98        println!("Account: {address}");
99        println!("Hashed address: {hashed_address}");
100        println!("Storage slots: {slot_count}");
101        if use_hashed_state {
102            println!("Hashed storage size: {} (estimated)", human_bytes(storage_size as f64));
103        } else {
104            // Estimate hashed storage size: 32-byte B256 key instead of 20-byte Address
105            let hashed_size_estimate = if slot_count > 0 { storage_size + 12 } else { 0 };
106            let total_estimate = storage_size + hashed_size_estimate;
107            println!("Plain storage size: {} (estimated)", human_bytes(storage_size as f64));
108            println!(
109                "Hashed storage size: {} (estimated)",
110                human_bytes(hashed_size_estimate as f64)
111            );
112            println!("Total estimated size: {}", human_bytes(total_estimate as f64));
113        }
114
115        Ok(())
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn parse_address_arg() {
125        let cmd = Command::try_parse_from([
126            "account-storage",
127            "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
128        ])
129        .unwrap();
130        assert_eq!(
131            cmd.address,
132            "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse::<Address>().unwrap()
133        );
134    }
135}