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