Skip to main content

reth_cli_commands/db/
get.rs

1use alloy_primitives::{hex, BlockHash};
2use clap::Parser;
3use reth_db::{
4    static_file::{
5        AccountChangesetMask, ColumnSelectorOne, ColumnSelectorTwo, HeaderWithHashMask,
6        ReceiptMask, TransactionMask, TransactionSenderMask,
7    },
8    RawDupSort,
9};
10use reth_db_api::{
11    cursor::{DbCursorRO, DbDupCursorRO},
12    database::Database,
13    table::{Compress, Decompress, DupSort, Table},
14    tables,
15    transaction::DbTx,
16    RawKey, RawTable, Receipts, TableViewer, Transactions,
17};
18use reth_db_common::DbTool;
19use reth_node_api::{HeaderTy, ReceiptTy, TxTy};
20use reth_node_builder::NodeTypesWithDB;
21use reth_primitives_traits::ValueWithSubKey;
22use reth_provider::{providers::ProviderNodeTypes, ChangeSetReader, StaticFileProviderFactory};
23use reth_static_file_types::StaticFileSegment;
24use reth_storage_api::StorageChangeSetReader;
25use tracing::error;
26
27/// The arguments for the `reth db get` command
28#[derive(Parser, Debug)]
29pub struct Command {
30    #[command(subcommand)]
31    subcommand: Subcommand,
32}
33
34#[derive(clap::Subcommand, Debug)]
35enum Subcommand {
36    /// Gets the content of a database table for the given key
37    Mdbx {
38        table: tables::Tables,
39
40        /// The key to get content for
41        #[arg(value_parser = maybe_json_value_parser)]
42        key: String,
43
44        /// The subkey to get content for
45        #[arg(value_parser = maybe_json_value_parser)]
46        subkey: Option<String>,
47
48        /// Optional end key for range query (exclusive upper bound)
49        #[arg(value_parser = maybe_json_value_parser)]
50        end_key: Option<String>,
51
52        /// Optional end subkey for range query (exclusive upper bound)
53        #[arg(value_parser = maybe_json_value_parser)]
54        end_subkey: Option<String>,
55
56        /// Output bytes instead of human-readable decoded value
57        #[arg(long)]
58        raw: bool,
59    },
60    /// Gets the content of a static file segment for the given key
61    StaticFile {
62        segment: StaticFileSegment,
63
64        /// The key to get content for
65        #[arg(value_parser = maybe_json_value_parser)]
66        key: String,
67
68        /// The subkey to get content for, for example address in changeset
69        #[arg(value_parser = maybe_json_value_parser)]
70        subkey: Option<String>,
71
72        /// Output bytes instead of human-readable decoded value
73        #[arg(long)]
74        raw: bool,
75    },
76}
77
78impl Command {
79    /// Execute `db get` command
80    pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
81        match self.subcommand {
82            Subcommand::Mdbx { table, key, subkey, end_key, end_subkey, raw } => {
83                table.view(&GetValueViewer { tool, key, subkey, end_key, end_subkey, raw })?
84            }
85            Subcommand::StaticFile { segment, key, subkey, raw } => {
86                if let StaticFileSegment::StorageChangeSets = segment {
87                    let storage_key =
88                        table_subkey::<tables::StorageChangeSets>(subkey.as_deref()).ok();
89                    let key = table_key::<tables::StorageChangeSets>(&key)?;
90
91                    let provider = tool.provider_factory.static_file_provider();
92
93                    if let Some(storage_key) = storage_key {
94                        let entry = provider.get_storage_before_block(
95                            key.block_number(),
96                            key.address(),
97                            storage_key,
98                        )?;
99
100                        if let Some(entry) = entry {
101                            let se: reth_primitives_traits::StorageEntry = entry.into();
102                            println!("{}", serde_json::to_string_pretty(&se)?);
103                        } else {
104                            error!(target: "reth::cli", "No content for the given table key.");
105                        }
106                        return Ok(());
107                    }
108
109                    let changesets = provider.storage_changeset(key.block_number())?;
110                    let serializable: Vec<_> = changesets
111                        .into_iter()
112                        .map(|(addr, entry)| {
113                            let se: reth_primitives_traits::StorageEntry = entry.into();
114                            (addr, se)
115                        })
116                        .collect();
117                    println!("{}", serde_json::to_string_pretty(&serializable)?);
118                    return Ok(());
119                }
120
121                let (key, subkey, mask): (u64, _, _) = match segment {
122                    StaticFileSegment::Headers => (
123                        table_key::<tables::Headers>(&key)?,
124                        None,
125                        <HeaderWithHashMask<HeaderTy<N>>>::MASK,
126                    ),
127                    StaticFileSegment::Transactions => (
128                        table_key::<tables::Transactions>(&key)?,
129                        None,
130                        <TransactionMask<TxTy<N>>>::MASK,
131                    ),
132                    StaticFileSegment::Receipts => (
133                        table_key::<tables::Receipts>(&key)?,
134                        None,
135                        <ReceiptMask<ReceiptTy<N>>>::MASK,
136                    ),
137                    StaticFileSegment::TransactionSenders => (
138                        table_key::<tables::TransactionSenders>(&key)?,
139                        None,
140                        TransactionSenderMask::MASK,
141                    ),
142                    StaticFileSegment::AccountChangeSets => {
143                        let subkey =
144                            table_subkey::<tables::AccountChangeSets>(subkey.as_deref()).ok();
145                        (
146                            table_key::<tables::AccountChangeSets>(&key)?,
147                            subkey,
148                            AccountChangesetMask::MASK,
149                        )
150                    }
151                    StaticFileSegment::StorageChangeSets => {
152                        unreachable!("storage changesets handled above");
153                    }
154                };
155
156                // handle account changesets differently if a subkey is provided.
157                if let StaticFileSegment::AccountChangeSets = segment {
158                    let Some(subkey) = subkey else {
159                        // get all changesets for the block
160                        let changesets = tool
161                            .provider_factory
162                            .static_file_provider()
163                            .account_block_changeset(key)?;
164
165                        println!("{}", serde_json::to_string_pretty(&changesets)?);
166                        return Ok(())
167                    };
168
169                    let account = tool
170                        .provider_factory
171                        .static_file_provider()
172                        .get_account_before_block(key, subkey)?;
173
174                    if let Some(account) = account {
175                        println!("{}", serde_json::to_string_pretty(&account)?);
176                    } else {
177                        error!(target: "reth::cli", "No content for the given table key.");
178                    }
179
180                    return Ok(())
181                }
182
183                let content = tool.provider_factory.static_file_provider().find_static_file(
184                    segment,
185                    |provider| {
186                        let mut cursor = provider.cursor()?;
187                        cursor.get(key.into(), mask).map(|result| {
188                            result.map(|vec| {
189                                vec.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>()
190                            })
191                        })
192                    },
193                )?;
194
195                match content {
196                    Some(content) => {
197                        if raw {
198                            println!("{}", hex::encode_prefixed(&content[0]));
199                        } else {
200                            match segment {
201                                StaticFileSegment::Headers => {
202                                    let header = HeaderTy::<N>::decompress(content[0].as_slice())?;
203                                    let block_hash = BlockHash::decompress(content[1].as_slice())?;
204                                    println!(
205                                        "Header\n{}\n\nBlockHash\n{}",
206                                        serde_json::to_string_pretty(&header)?,
207                                        serde_json::to_string_pretty(&block_hash)?
208                                    );
209                                }
210                                StaticFileSegment::Transactions => {
211                                    let transaction = <<Transactions as Table>::Value>::decompress(
212                                        content[0].as_slice(),
213                                    )?;
214                                    println!("{}", serde_json::to_string_pretty(&transaction)?);
215                                }
216                                StaticFileSegment::Receipts => {
217                                    let receipt = <<Receipts as Table>::Value>::decompress(
218                                        content[0].as_slice(),
219                                    )?;
220                                    println!("{}", serde_json::to_string_pretty(&receipt)?);
221                                }
222                                StaticFileSegment::TransactionSenders => {
223                                    let sender =
224                                        <<tables::TransactionSenders as Table>::Value>::decompress(
225                                            content[0].as_slice(),
226                                        )?;
227                                    println!("{}", serde_json::to_string_pretty(&sender)?);
228                                }
229                                StaticFileSegment::AccountChangeSets => {
230                                    unreachable!("account changeset static files are special cased before this match")
231                                }
232                                StaticFileSegment::StorageChangeSets => {
233                                    unreachable!("storage changeset static files are special cased before this match")
234                                }
235                            }
236                        }
237                    }
238                    None => {
239                        error!(target: "reth::cli", "No content for the given table key.");
240                    }
241                };
242            }
243        }
244
245        Ok(())
246    }
247}
248
249/// Get an instance of key for given table
250pub(crate) fn table_key<T: Table>(key: &str) -> Result<T::Key, eyre::Error> {
251    serde_json::from_str(key).map_err(|e| eyre::eyre!(e))
252}
253
254/// Get an instance of subkey for given dupsort table
255fn table_subkey<T: DupSort>(subkey: Option<&str>) -> Result<T::SubKey, eyre::Error> {
256    serde_json::from_str(subkey.unwrap_or_default()).map_err(|e| eyre::eyre!(e))
257}
258
259struct GetValueViewer<'a, N: NodeTypesWithDB> {
260    tool: &'a DbTool<N>,
261    key: String,
262    subkey: Option<String>,
263    end_key: Option<String>,
264    end_subkey: Option<String>,
265    raw: bool,
266}
267
268impl<N: ProviderNodeTypes> TableViewer<()> for GetValueViewer<'_, N> {
269    type Error = eyre::Report;
270
271    fn view<T: Table>(&self) -> Result<(), Self::Error> {
272        let key = table_key::<T>(&self.key)?;
273
274        // A non-dupsort table cannot have subkeys. The `subkey` arg becomes the `end_key`. First we
275        // check that `end_key` and `end_subkey` weren't previously given, as that wouldn't be
276        // valid.
277        if self.end_key.is_some() || self.end_subkey.is_some() {
278            return Err(eyre::eyre!("Only END_KEY can be given for non-DUPSORT tables"));
279        }
280
281        let end_key = self.subkey.clone();
282
283        // Check if we're doing a range query
284        if let Some(ref end_key_str) = end_key {
285            let end_key = table_key::<T>(end_key_str)?;
286
287            // Use walk_range to iterate over the range
288            self.tool.provider_factory.db_ref().view(|tx| {
289                let mut cursor = tx.cursor_read::<T>()?;
290                let walker = cursor.walk_range(key..end_key)?;
291
292                for result in walker {
293                    let (k, v) = result?;
294                    let json_val = if self.raw {
295                        let raw_key = RawKey::from(k);
296                        serde_json::json!({
297                            "key": hex::encode_prefixed(raw_key.raw_key()),
298                            "val": hex::encode_prefixed(v.compress().as_ref()),
299                        })
300                    } else {
301                        serde_json::json!({
302                            "key": &k,
303                            "val": &v,
304                        })
305                    };
306
307                    println!("{}", serde_json::to_string_pretty(&json_val)?);
308                }
309
310                Ok::<_, eyre::Report>(())
311            })??;
312        } else {
313            // Single key lookup
314            let content = if self.raw {
315                self.tool
316                    .get::<RawTable<T>>(RawKey::from(key))?
317                    .map(|content| hex::encode_prefixed(content.raw_value()))
318            } else {
319                self.tool.get::<T>(key)?.as_ref().map(serde_json::to_string_pretty).transpose()?
320            };
321
322            match content {
323                Some(content) => {
324                    println!("{content}");
325                }
326                None => {
327                    error!(target: "reth::cli", "No content for the given table key.");
328                }
329            };
330        }
331
332        Ok(())
333    }
334
335    fn view_dupsort<T: DupSort>(&self) -> Result<(), Self::Error>
336    where
337        T::Value: reth_primitives_traits::ValueWithSubKey<SubKey = T::SubKey>,
338    {
339        // get a key for given table
340        let key = table_key::<T>(&self.key)?;
341
342        // Check if we're doing a range query
343        if let Some(ref end_key_str) = self.end_key {
344            let end_key = table_key::<T>(end_key_str)?;
345            let start_subkey = table_subkey::<T>(Some(
346                self.subkey.as_ref().expect("must have been given if end_key is given").as_str(),
347            ))?;
348            let end_subkey_parsed = self
349                .end_subkey
350                .as_ref()
351                .map(|s| table_subkey::<T>(Some(s.as_str())))
352                .transpose()?;
353
354            self.tool.provider_factory.db_ref().view(|tx| {
355                let mut cursor = tx.cursor_dup_read::<T>()?;
356
357                // Seek to the starting key. If there is actually a key at the starting key then
358                // seek to the subkey within it.
359                if let Some((decoded_key, _)) = cursor.seek(key.clone())? &&
360                    decoded_key == key
361                {
362                    cursor.seek_by_key_subkey(key.clone(), start_subkey.clone())?;
363                }
364
365                // Get the current position to start iteration
366                let mut current = cursor.current()?;
367
368                while let Some((decoded_key, decoded_value)) = current {
369                    // Extract the subkey using the ValueWithSubKey trait
370                    let decoded_subkey = decoded_value.get_subkey();
371
372                    // Check if we've reached the end (exclusive)
373                    if (&decoded_key, Some(&decoded_subkey)) >=
374                        (&end_key, end_subkey_parsed.as_ref())
375                    {
376                        break;
377                    }
378
379                    // Output the entry with both key and subkey
380                    let json_val = if self.raw {
381                        let raw_key = RawKey::from(decoded_key.clone());
382                        serde_json::json!({
383                            "key": hex::encode_prefixed(raw_key.raw_key()),
384                            "val": hex::encode_prefixed(decoded_value.compress().as_ref()),
385                        })
386                    } else {
387                        serde_json::json!({
388                            "key": &decoded_key,
389                            "val": &decoded_value,
390                        })
391                    };
392
393                    println!("{}", serde_json::to_string_pretty(&json_val)?);
394
395                    // Move to next entry
396                    current = cursor.next()?;
397                }
398
399                Ok::<_, eyre::Report>(())
400            })??;
401        } else {
402            // Single key/subkey lookup
403            let subkey = table_subkey::<T>(self.subkey.as_deref())?;
404
405            let content = if self.raw {
406                self.tool
407                    .get_dup::<RawDupSort<T>>(RawKey::from(key), RawKey::from(subkey))?
408                    .map(|content| hex::encode_prefixed(content.raw_value()))
409            } else {
410                self.tool
411                    .get_dup::<T>(key, subkey)?
412                    .as_ref()
413                    .map(serde_json::to_string_pretty)
414                    .transpose()?
415            };
416
417            match content {
418                Some(content) => {
419                    println!("{content}");
420                }
421                None => {
422                    error!(target: "reth::cli", "No content for the given table subkey.");
423                }
424            };
425        }
426        Ok(())
427    }
428}
429
430/// Map the user input value to json
431pub(crate) fn maybe_json_value_parser(value: &str) -> Result<String, eyre::Error> {
432    if serde_json::from_str::<serde::de::IgnoredAny>(value).is_ok() {
433        Ok(value.to_string())
434    } else {
435        serde_json::to_string(&value).map_err(|e| eyre::eyre!(e))
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442    use alloy_primitives::{address, B256};
443    use clap::{Args, Parser};
444    use reth_db_api::{
445        models::{storage_sharded_key::StorageShardedKey, ShardedKey},
446        AccountsHistory, HashedAccounts, Headers, StageCheckpoints, StoragesHistory,
447    };
448    use std::str::FromStr;
449
450    /// A helper type to parse Args more easily
451    #[derive(Parser)]
452    struct CommandParser<T: Args> {
453        #[command(flatten)]
454        args: T,
455    }
456
457    #[test]
458    fn parse_numeric_key_args() {
459        assert_eq!(table_key::<Headers>("123").unwrap(), 123);
460        assert_eq!(
461            table_key::<HashedAccounts>(
462                "\"0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac\""
463            )
464            .unwrap(),
465            B256::from_str("0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac")
466                .unwrap()
467        );
468    }
469
470    #[test]
471    fn parse_string_key_args() {
472        assert_eq!(
473            table_key::<StageCheckpoints>("\"MerkleExecution\"").unwrap(),
474            "MerkleExecution"
475        );
476    }
477
478    #[test]
479    fn parse_json_key_args() {
480        assert_eq!(
481            table_key::<StoragesHistory>(r#"{ "address": "0x01957911244e546ce519fbac6f798958fafadb41", "sharded_key": { "key": "0x0000000000000000000000000000000000000000000000000000000000000003", "highest_block_number": 18446744073709551615 } }"#).unwrap(),
482            StorageShardedKey::new(
483                address!("0x01957911244e546ce519fbac6f798958fafadb41"),
484                B256::from_str(
485                    "0x0000000000000000000000000000000000000000000000000000000000000003"
486                )
487                .unwrap(),
488                18446744073709551615
489            )
490        );
491    }
492
493    #[test]
494    fn parse_json_key_for_account_history() {
495        assert_eq!(
496            table_key::<AccountsHistory>(r#"{ "key": "0x4448e1273fd5a8bfdb9ed111e96889c960eee145", "highest_block_number": 18446744073709551615 }"#).unwrap(),
497            ShardedKey::new(
498                address!("0x4448e1273fd5a8bfdb9ed111e96889c960eee145"),
499                18446744073709551615
500            )
501        );
502    }
503}