reth_cli_commands/db/
get.rs

1use alloy_consensus::Header;
2use alloy_primitives::{hex, BlockHash};
3use clap::Parser;
4use reth_db::static_file::{
5    ColumnSelectorOne, ColumnSelectorTwo, HeaderWithHashMask, ReceiptMask, TransactionMask,
6};
7use reth_db_api::{
8    table::{Decompress, DupSort, Table},
9    tables, RawKey, RawTable, Receipts, TableViewer, Transactions,
10};
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                    StaticFileSegment::BlockMeta => todo!(),
76                };
77
78                let content = tool.provider_factory.static_file_provider().find_static_file(
79                    segment,
80                    |provider| {
81                        let mut cursor = provider.cursor()?;
82                        cursor.get(key.into(), mask).map(|result| {
83                            result.map(|vec| {
84                                vec.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>()
85                            })
86                        })
87                    },
88                )?;
89
90                match content {
91                    Some(content) => {
92                        if raw {
93                            println!("{}", hex::encode_prefixed(&content[0]));
94                        } else {
95                            match segment {
96                                StaticFileSegment::Headers => {
97                                    let header = Header::decompress(content[0].as_slice())?;
98                                    let block_hash = BlockHash::decompress(content[1].as_slice())?;
99                                    println!(
100                                        "Header\n{}\n\nBlockHash\n{}",
101                                        serde_json::to_string_pretty(&header)?,
102                                        serde_json::to_string_pretty(&block_hash)?
103                                    );
104                                }
105                                StaticFileSegment::Transactions => {
106                                    let transaction = <<Transactions as Table>::Value>::decompress(
107                                        content[0].as_slice(),
108                                    )?;
109                                    println!("{}", serde_json::to_string_pretty(&transaction)?);
110                                }
111                                StaticFileSegment::Receipts => {
112                                    let receipt = <<Receipts as Table>::Value>::decompress(
113                                        content[0].as_slice(),
114                                    )?;
115                                    println!("{}", serde_json::to_string_pretty(&receipt)?);
116                                }
117                                StaticFileSegment::BlockMeta => {
118                                    todo!()
119                                }
120                            }
121                        }
122                    }
123                    None => {
124                        error!(target: "reth::cli", "No content for the given table key.");
125                    }
126                };
127            }
128        }
129
130        Ok(())
131    }
132}
133
134/// Get an instance of key for given table
135pub(crate) fn table_key<T: Table>(key: &str) -> Result<T::Key, eyre::Error> {
136    serde_json::from_str(key).map_err(|e| eyre::eyre!(e))
137}
138
139/// Get an instance of subkey for given dupsort table
140fn table_subkey<T: DupSort>(subkey: Option<&str>) -> Result<T::SubKey, eyre::Error> {
141    serde_json::from_str(subkey.unwrap_or_default()).map_err(|e| eyre::eyre!(e))
142}
143
144struct GetValueViewer<'a, N: NodeTypesWithDB> {
145    tool: &'a DbTool<N>,
146    key: String,
147    subkey: Option<String>,
148    raw: bool,
149}
150
151impl<N: ProviderNodeTypes> TableViewer<()> for GetValueViewer<'_, N> {
152    type Error = eyre::Report;
153
154    fn view<T: Table>(&self) -> Result<(), Self::Error> {
155        let key = table_key::<T>(&self.key)?;
156
157        let content = if self.raw {
158            self.tool
159                .get::<RawTable<T>>(RawKey::from(key))?
160                .map(|content| hex::encode_prefixed(content.raw_value()))
161        } else {
162            self.tool.get::<T>(key)?.as_ref().map(serde_json::to_string_pretty).transpose()?
163        };
164
165        match content {
166            Some(content) => {
167                println!("{content}");
168            }
169            None => {
170                error!(target: "reth::cli", "No content for the given table key.");
171            }
172        };
173
174        Ok(())
175    }
176
177    fn view_dupsort<T: DupSort>(&self) -> Result<(), Self::Error> {
178        // get a key for given table
179        let key = table_key::<T>(&self.key)?;
180
181        // process dupsort table
182        let subkey = table_subkey::<T>(self.subkey.as_deref())?;
183
184        match self.tool.get_dup::<T>(key, subkey)? {
185            Some(content) => {
186                println!("{}", serde_json::to_string_pretty(&content)?);
187            }
188            None => {
189                error!(target: "reth::cli", "No content for the given table subkey.");
190            }
191        };
192        Ok(())
193    }
194}
195
196/// Map the user input value to json
197pub(crate) fn maybe_json_value_parser(value: &str) -> Result<String, eyre::Error> {
198    if serde_json::from_str::<serde::de::IgnoredAny>(value).is_ok() {
199        Ok(value.to_string())
200    } else {
201        serde_json::to_string(&value).map_err(|e| eyre::eyre!(e))
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use alloy_primitives::{address, B256};
209    use clap::{Args, Parser};
210    use reth_db_api::{
211        models::{storage_sharded_key::StorageShardedKey, ShardedKey},
212        AccountsHistory, HashedAccounts, Headers, StageCheckpoints, StoragesHistory,
213    };
214    use std::str::FromStr;
215
216    /// A helper type to parse Args more easily
217    #[derive(Parser)]
218    struct CommandParser<T: Args> {
219        #[command(flatten)]
220        args: T,
221    }
222
223    #[test]
224    fn parse_numeric_key_args() {
225        assert_eq!(table_key::<Headers>("123").unwrap(), 123);
226        assert_eq!(
227            table_key::<HashedAccounts>(
228                "\"0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac\""
229            )
230            .unwrap(),
231            B256::from_str("0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac")
232                .unwrap()
233        );
234    }
235
236    #[test]
237    fn parse_string_key_args() {
238        assert_eq!(
239            table_key::<StageCheckpoints>("\"MerkleExecution\"").unwrap(),
240            "MerkleExecution"
241        );
242    }
243
244    #[test]
245    fn parse_json_key_args() {
246        assert_eq!(
247            table_key::<StoragesHistory>(r#"{ "address": "0x01957911244e546ce519fbac6f798958fafadb41", "sharded_key": { "key": "0x0000000000000000000000000000000000000000000000000000000000000003", "highest_block_number": 18446744073709551615 } }"#).unwrap(),
248            StorageShardedKey::new(
249                address!("0x01957911244e546ce519fbac6f798958fafadb41"),
250                B256::from_str(
251                    "0x0000000000000000000000000000000000000000000000000000000000000003"
252                )
253                .unwrap(),
254                18446744073709551615
255            )
256        );
257    }
258
259    #[test]
260    fn parse_json_key_for_account_history() {
261        assert_eq!(
262            table_key::<AccountsHistory>(r#"{ "key": "0x4448e1273fd5a8bfdb9ed111e96889c960eee145", "highest_block_number": 18446744073709551615 }"#).unwrap(),
263            ShardedKey::new(
264                address!("0x4448e1273fd5a8bfdb9ed111e96889c960eee145"),
265                18446744073709551615
266            )
267        );
268    }
269}