reth_cli_commands/db/
get.rs

1use alloy_consensus::Header;
2use alloy_primitives::{hex, BlockHash};
3use clap::Parser;
4use reth_db::{
5    static_file::{
6        ColumnSelectorOne, ColumnSelectorTwo, HeaderWithHashMask, ReceiptMask, TransactionMask,
7    },
8    RawDupSort,
9};
10use reth_db_api::{
11    table::{Decompress, DupSort, Table},
12    tables, RawKey, RawTable, Receipts, TableViewer, Transactions,
13};
14use reth_db_common::DbTool;
15use reth_node_api::{ReceiptTy, TxTy};
16use reth_node_builder::NodeTypesWithDB;
17use reth_provider::{providers::ProviderNodeTypes, StaticFileProviderFactory};
18use reth_static_file_types::StaticFileSegment;
19use tracing::error;
20
21/// The arguments for the `reth db get` command
22#[derive(Parser, Debug)]
23pub struct Command {
24    #[command(subcommand)]
25    subcommand: Subcommand,
26}
27
28#[derive(clap::Subcommand, Debug)]
29enum Subcommand {
30    /// Gets the content of a database table for the given key
31    Mdbx {
32        table: tables::Tables,
33
34        /// The key to get content for
35        #[arg(value_parser = maybe_json_value_parser)]
36        key: String,
37
38        /// The subkey to get content for
39        #[arg(value_parser = maybe_json_value_parser)]
40        subkey: Option<String>,
41
42        /// Output bytes instead of human-readable decoded value
43        #[arg(long)]
44        raw: bool,
45    },
46    /// Gets the content of a static file segment for the given key
47    StaticFile {
48        segment: StaticFileSegment,
49
50        /// The key to get content for
51        #[arg(value_parser = maybe_json_value_parser)]
52        key: String,
53
54        /// Output bytes instead of human-readable decoded value
55        #[arg(long)]
56        raw: bool,
57    },
58}
59
60impl Command {
61    /// Execute `db get` command
62    pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
63        match self.subcommand {
64            Subcommand::Mdbx { table, key, subkey, raw } => {
65                table.view(&GetValueViewer { tool, key, subkey, raw })?
66            }
67            Subcommand::StaticFile { segment, key, raw } => {
68                let (key, mask): (u64, _) = match segment {
69                    StaticFileSegment::Headers => {
70                        (table_key::<tables::Headers>(&key)?, <HeaderWithHashMask<Header>>::MASK)
71                    }
72                    StaticFileSegment::Transactions => {
73                        (table_key::<tables::Transactions>(&key)?, <TransactionMask<TxTy<N>>>::MASK)
74                    }
75                    StaticFileSegment::Receipts => {
76                        (table_key::<tables::Receipts>(&key)?, <ReceiptMask<ReceiptTy<N>>>::MASK)
77                    }
78                };
79
80                let content = tool.provider_factory.static_file_provider().find_static_file(
81                    segment,
82                    |provider| {
83                        let mut cursor = provider.cursor()?;
84                        cursor.get(key.into(), mask).map(|result| {
85                            result.map(|vec| {
86                                vec.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>()
87                            })
88                        })
89                    },
90                )?;
91
92                match content {
93                    Some(content) => {
94                        if raw {
95                            println!("{}", hex::encode_prefixed(&content[0]));
96                        } else {
97                            match segment {
98                                StaticFileSegment::Headers => {
99                                    let header = Header::decompress(content[0].as_slice())?;
100                                    let block_hash = BlockHash::decompress(content[1].as_slice())?;
101                                    println!(
102                                        "Header\n{}\n\nBlockHash\n{}",
103                                        serde_json::to_string_pretty(&header)?,
104                                        serde_json::to_string_pretty(&block_hash)?
105                                    );
106                                }
107                                StaticFileSegment::Transactions => {
108                                    let transaction = <<Transactions as Table>::Value>::decompress(
109                                        content[0].as_slice(),
110                                    )?;
111                                    println!("{}", serde_json::to_string_pretty(&transaction)?);
112                                }
113                                StaticFileSegment::Receipts => {
114                                    let receipt = <<Receipts as Table>::Value>::decompress(
115                                        content[0].as_slice(),
116                                    )?;
117                                    println!("{}", serde_json::to_string_pretty(&receipt)?);
118                                }
119                            }
120                        }
121                    }
122                    None => {
123                        error!(target: "reth::cli", "No content for the given table key.");
124                    }
125                };
126            }
127        }
128
129        Ok(())
130    }
131}
132
133/// Get an instance of key for given table
134pub(crate) fn table_key<T: Table>(key: &str) -> Result<T::Key, eyre::Error> {
135    serde_json::from_str(key).map_err(|e| eyre::eyre!(e))
136}
137
138/// Get an instance of subkey for given dupsort table
139fn table_subkey<T: DupSort>(subkey: Option<&str>) -> Result<T::SubKey, eyre::Error> {
140    serde_json::from_str(subkey.unwrap_or_default()).map_err(|e| eyre::eyre!(e))
141}
142
143struct GetValueViewer<'a, N: NodeTypesWithDB> {
144    tool: &'a DbTool<N>,
145    key: String,
146    subkey: Option<String>,
147    raw: bool,
148}
149
150impl<N: ProviderNodeTypes> TableViewer<()> for GetValueViewer<'_, N> {
151    type Error = eyre::Report;
152
153    fn view<T: Table>(&self) -> Result<(), Self::Error> {
154        let key = table_key::<T>(&self.key)?;
155
156        let content = if self.raw {
157            self.tool
158                .get::<RawTable<T>>(RawKey::from(key))?
159                .map(|content| hex::encode_prefixed(content.raw_value()))
160        } else {
161            self.tool.get::<T>(key)?.as_ref().map(serde_json::to_string_pretty).transpose()?
162        };
163
164        match content {
165            Some(content) => {
166                println!("{content}");
167            }
168            None => {
169                error!(target: "reth::cli", "No content for the given table key.");
170            }
171        };
172
173        Ok(())
174    }
175
176    fn view_dupsort<T: DupSort>(&self) -> Result<(), Self::Error> {
177        // get a key for given table
178        let key = table_key::<T>(&self.key)?;
179
180        // process dupsort table
181        let subkey = table_subkey::<T>(self.subkey.as_deref())?;
182
183        let content = if self.raw {
184            self.tool
185                .get_dup::<RawDupSort<T>>(RawKey::from(key), RawKey::from(subkey))?
186                .map(|content| hex::encode_prefixed(content.raw_value()))
187        } else {
188            self.tool
189                .get_dup::<T>(key, subkey)?
190                .as_ref()
191                .map(serde_json::to_string_pretty)
192                .transpose()?
193        };
194
195        match content {
196            Some(content) => {
197                println!("{content}");
198            }
199            None => {
200                error!(target: "reth::cli", "No content for the given table subkey.");
201            }
202        };
203        Ok(())
204    }
205}
206
207/// Map the user input value to json
208pub(crate) fn maybe_json_value_parser(value: &str) -> Result<String, eyre::Error> {
209    if serde_json::from_str::<serde::de::IgnoredAny>(value).is_ok() {
210        Ok(value.to_string())
211    } else {
212        serde_json::to_string(&value).map_err(|e| eyre::eyre!(e))
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use alloy_primitives::{address, B256};
220    use clap::{Args, Parser};
221    use reth_db_api::{
222        models::{storage_sharded_key::StorageShardedKey, ShardedKey},
223        AccountsHistory, HashedAccounts, Headers, StageCheckpoints, StoragesHistory,
224    };
225    use std::str::FromStr;
226
227    /// A helper type to parse Args more easily
228    #[derive(Parser)]
229    struct CommandParser<T: Args> {
230        #[command(flatten)]
231        args: T,
232    }
233
234    #[test]
235    fn parse_numeric_key_args() {
236        assert_eq!(table_key::<Headers>("123").unwrap(), 123);
237        assert_eq!(
238            table_key::<HashedAccounts>(
239                "\"0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac\""
240            )
241            .unwrap(),
242            B256::from_str("0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac")
243                .unwrap()
244        );
245    }
246
247    #[test]
248    fn parse_string_key_args() {
249        assert_eq!(
250            table_key::<StageCheckpoints>("\"MerkleExecution\"").unwrap(),
251            "MerkleExecution"
252        );
253    }
254
255    #[test]
256    fn parse_json_key_args() {
257        assert_eq!(
258            table_key::<StoragesHistory>(r#"{ "address": "0x01957911244e546ce519fbac6f798958fafadb41", "sharded_key": { "key": "0x0000000000000000000000000000000000000000000000000000000000000003", "highest_block_number": 18446744073709551615 } }"#).unwrap(),
259            StorageShardedKey::new(
260                address!("0x01957911244e546ce519fbac6f798958fafadb41"),
261                B256::from_str(
262                    "0x0000000000000000000000000000000000000000000000000000000000000003"
263                )
264                .unwrap(),
265                18446744073709551615
266            )
267        );
268    }
269
270    #[test]
271    fn parse_json_key_for_account_history() {
272        assert_eq!(
273            table_key::<AccountsHistory>(r#"{ "key": "0x4448e1273fd5a8bfdb9ed111e96889c960eee145", "highest_block_number": 18446744073709551615 }"#).unwrap(),
274            ShardedKey::new(
275                address!("0x4448e1273fd5a8bfdb9ed111e96889c960eee145"),
276                18446744073709551615
277            )
278        );
279    }
280}