use clap::Parser; use reth_db::{ static_file::{ColumnSelectorOne, ColumnSelectorTwo, HeaderMask, ReceiptMask, TransactionMask}, tables, RawKey, RawTable, Receipts, TableViewer, Transactions, }; use reth_db_api::{ database::Database, table::{Decompress, DupSort, Table}, }; use reth_db_common::DbTool; use reth_primitives::{BlockHash, Header}; use reth_provider::StaticFileProviderFactory; use reth_static_file_types::StaticFileSegment; use tracing::error; /// The arguments for the `reth db get` command #[derive(Parser, Debug)] pub struct Command { #[command(subcommand)] subcommand: Subcommand, } #[derive(clap::Subcommand, Debug)] enum Subcommand { /// Gets the content of a database table for the given key Mdbx { table: tables::Tables, /// The key to get content for #[arg(value_parser = maybe_json_value_parser)] key: String, /// The subkey to get content for #[arg(value_parser = maybe_json_value_parser)] subkey: Option, /// Output bytes instead of human-readable decoded value #[arg(long)] raw: bool, }, /// Gets the content of a static file segment for the given key StaticFile { segment: StaticFileSegment, /// The key to get content for #[arg(value_parser = maybe_json_value_parser)] key: String, /// Output bytes instead of human-readable decoded value #[arg(long)] raw: bool, }, } impl Command { /// Execute `db get` command pub fn execute(self, tool: &DbTool) -> eyre::Result<()> { match self.subcommand { Subcommand::Mdbx { table, key, subkey, raw } => { table.view(&GetValueViewer { tool, key, subkey, raw })? } Subcommand::StaticFile { segment, key, raw } => { let (key, mask): (u64, _) = match segment { StaticFileSegment::Headers => { (table_key::(&key)?, >::MASK) } StaticFileSegment::Transactions => ( table_key::(&key)?, ::Value>>::MASK, ), StaticFileSegment::Receipts => ( table_key::(&key)?, ::Value>>::MASK, ), }; let content = tool.provider_factory.static_file_provider().find_static_file( segment, |provider| { let mut cursor = provider.cursor()?; cursor.get(key.into(), mask).map(|result| { result.map(|vec| { vec.iter().map(|slice| slice.to_vec()).collect::>() }) }) }, )?; match content { Some(content) => { if raw { println!("{content:?}"); } else { match segment { StaticFileSegment::Headers => { let header = Header::decompress(content[0].as_slice())?; let block_hash = BlockHash::decompress(content[1].as_slice())?; println!( "{}\n{}", serde_json::to_string_pretty(&header)?, serde_json::to_string_pretty(&block_hash)? ); } StaticFileSegment::Transactions => { let transaction = <::Value>::decompress( content[0].as_slice(), )?; println!("{}", serde_json::to_string_pretty(&transaction)?); } StaticFileSegment::Receipts => { let receipt = <::Value>::decompress( content[0].as_slice(), )?; println!("{}", serde_json::to_string_pretty(&receipt)?); } } } } None => { error!(target: "reth::cli", "No content for the given table key."); } }; } } Ok(()) } } /// Get an instance of key for given table pub(crate) fn table_key(key: &str) -> Result { serde_json::from_str::(key).map_err(|e| eyre::eyre!(e)) } /// Get an instance of subkey for given dupsort table fn table_subkey(subkey: &Option) -> Result { serde_json::from_str::(&subkey.clone().unwrap_or_default()) .map_err(|e| eyre::eyre!(e)) } struct GetValueViewer<'a, DB: Database> { tool: &'a DbTool, key: String, subkey: Option, raw: bool, } impl TableViewer<()> for GetValueViewer<'_, DB> { type Error = eyre::Report; fn view(&self) -> Result<(), Self::Error> { let key = table_key::(&self.key)?; let content = if self.raw { self.tool .get::>(RawKey::from(key))? .map(|content| format!("{:?}", content.raw_value())) } else { self.tool.get::(key)?.as_ref().map(serde_json::to_string_pretty).transpose()? }; match content { Some(content) => { println!("{content}"); } None => { error!(target: "reth::cli", "No content for the given table key."); } }; Ok(()) } fn view_dupsort(&self) -> Result<(), Self::Error> { // get a key for given table let key = table_key::(&self.key)?; // process dupsort table let subkey = table_subkey::(&self.subkey)?; match self.tool.get_dup::(key, subkey)? { Some(content) => { println!("{}", serde_json::to_string_pretty(&content)?); } None => { error!(target: "reth::cli", "No content for the given table subkey."); } }; Ok(()) } } /// Map the user input value to json pub(crate) fn maybe_json_value_parser(value: &str) -> Result { if serde_json::from_str::(value).is_ok() { Ok(value.to_string()) } else { serde_json::to_string(&value).map_err(|e| eyre::eyre!(e)) } } #[cfg(test)] mod tests { use super::*; use clap::{Args, Parser}; use reth_db::{AccountsHistory, HashedAccounts, Headers, StageCheckpoints, StoragesHistory}; use reth_db_api::models::{storage_sharded_key::StorageShardedKey, ShardedKey}; use reth_primitives::{Address, B256}; use std::str::FromStr; /// A helper type to parse Args more easily #[derive(Parser)] struct CommandParser { #[command(flatten)] args: T, } #[test] fn parse_numeric_key_args() { assert_eq!(table_key::("123").unwrap(), 123); assert_eq!( table_key::( "\"0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac\"" ) .unwrap(), B256::from_str("0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac") .unwrap() ); } #[test] fn parse_string_key_args() { assert_eq!( table_key::("\"MerkleExecution\"").unwrap(), "MerkleExecution" ); } #[test] fn parse_json_key_args() { assert_eq!( table_key::(r#"{ "address": "0x01957911244e546ce519fbac6f798958fafadb41", "sharded_key": { "key": "0x0000000000000000000000000000000000000000000000000000000000000003", "highest_block_number": 18446744073709551615 } }"#).unwrap(), StorageShardedKey::new( Address::from_str("0x01957911244e546ce519fbac6f798958fafadb41").unwrap(), B256::from_str( "0x0000000000000000000000000000000000000000000000000000000000000003" ) .unwrap(), 18446744073709551615 ) ); } #[test] fn parse_json_key_for_account_history() { assert_eq!( table_key::(r#"{ "key": "0x4448e1273fd5a8bfdb9ed111e96889c960eee145", "highest_block_number": 18446744073709551615 }"#).unwrap(), ShardedKey::new( Address::from_str("0x4448e1273fd5a8bfdb9ed111e96889c960eee145").unwrap(), 18446744073709551615 ) ); } }