diff --git a/crates/cli/commands/src/db/checksum/mod.rs b/crates/cli/commands/src/db/checksum/mod.rs index 37181a5d9be..fa702c37afa 100644 --- a/crates/cli/commands/src/db/checksum/mod.rs +++ b/crates/cli/commands/src/db/checksum/mod.rs @@ -24,6 +24,8 @@ use tracing::{info, warn}; #[cfg(all(unix, feature = "edge"))] mod rocksdb; +#[cfg(all(unix, feature = "edge"))] +pub use rocksdb::RocksDbTable; /// Interval for logging progress during checksum computation. const PROGRESS_LOG_INTERVAL: usize = 100_000; diff --git a/crates/cli/commands/src/db/get.rs b/crates/cli/commands/src/db/get.rs index 1b3739bb462..f3376d569a6 100644 --- a/crates/cli/commands/src/db/get.rs +++ b/crates/cli/commands/src/db/get.rs @@ -24,7 +24,9 @@ use reth_static_file_types::StaticFileSegment; use reth_storage_api::StorageChangeSetReader; use tracing::error; -/// The arguments for the `reth db get` command +#[cfg(all(unix, feature = "edge"))] +use {crate::db::checksum::RocksDbTable, reth_provider::RocksDBProviderFactory}; + #[derive(Parser, Debug)] pub struct Command { #[command(subcommand)] @@ -69,6 +71,21 @@ enum Subcommand { #[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 RocksDB table for the given key + #[cfg(all(unix, feature = "edge"))] + Rocksdb { + /// The RocksDB table + #[arg(value_enum)] + table: RocksDbTable, + + /// 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, @@ -76,174 +93,225 @@ enum Subcommand { } impl Command { - /// Execute `db get` command pub fn execute(self, tool: &DbTool) -> eyre::Result<()> { match self.subcommand { Subcommand::Mdbx { table, key, subkey, end_key, end_subkey, raw } => { table.view(&GetValueViewer { tool, key, subkey, end_key, end_subkey, raw })? } - Subcommand::StaticFile { segment, key, subkey, raw } => { - if let StaticFileSegment::StorageChangeSets = segment { - let storage_key = - table_subkey::(subkey.as_deref()).ok(); - let key = table_key::(&key)?; - - let provider = tool.provider_factory.static_file_provider(); - - if let Some(storage_key) = storage_key { - let entry = provider.get_storage_before_block( - key.block_number(), - key.address(), - storage_key, - )?; - - if let Some(entry) = entry { - println!("{}", serde_json::to_string_pretty(&entry)?); - } else { - error!(target: "reth::cli", "No content for the given table key."); - } - return Ok(()); - } + Subcommand::StaticFile { segment, ref key, ref subkey, raw } => { + self.execute_static_file(tool, segment, key.clone(), subkey.clone(), raw)? + } + #[cfg(all(unix, feature = "edge"))] + Subcommand::Rocksdb { table, ref key, raw } => { + get_rocksdb(tool, table, key, raw)?; + } + } + + Ok(()) + } - let changesets = provider.storage_changeset(key.block_number())?; - println!("{}", serde_json::to_string_pretty(&changesets)?); - return Ok(()); + fn execute_static_file( + &self, + tool: &DbTool, + segment: StaticFileSegment, + key: String, + subkey: Option, + raw: bool, + ) -> eyre::Result<()> { + if let StaticFileSegment::StorageChangeSets = segment { + let storage_key = table_subkey::(subkey.as_deref()).ok(); + let key = table_key::(&key)?; + + let provider = tool.provider_factory.static_file_provider(); + + if let Some(storage_key) = storage_key { + let entry = provider.get_storage_before_block( + key.block_number(), + key.address(), + storage_key, + )?; + + if let Some(entry) = entry { + println!("{}", serde_json::to_string_pretty(&entry)?); + } else { + error!(target: "reth::cli", "No content for the given table key."); } + return Ok(()); + } - let (key, subkey, mask): (u64, _, _) = match segment { - StaticFileSegment::Headers => ( - table_key::(&key)?, - None, - >>::MASK, - ), - StaticFileSegment::Transactions => ( - table_key::(&key)?, - None, - >>::MASK, - ), - StaticFileSegment::Receipts => ( - table_key::(&key)?, - None, - >>::MASK, - ), - StaticFileSegment::TransactionSenders => ( - table_key::(&key)?, - None, - TransactionSenderMask::MASK, - ), - StaticFileSegment::AccountChangeSets => { - let subkey = - table_subkey::(subkey.as_deref()).ok(); - ( - table_key::(&key)?, - subkey, - AccountChangesetMask::MASK, - ) - } - StaticFileSegment::StorageChangeSets => { - unreachable!("storage changesets handled above"); - } - }; - - // handle account changesets differently if a subkey is provided. - if let StaticFileSegment::AccountChangeSets = segment { - let Some(subkey) = subkey else { - // get all changesets for the block - let changesets = tool - .provider_factory - .static_file_provider() - .account_block_changeset(key)?; - - println!("{}", serde_json::to_string_pretty(&changesets)?); - return Ok(()) - }; + let changesets = provider.storage_changeset(key.block_number())?; + println!("{}", serde_json::to_string_pretty(&changesets)?); + return Ok(()); + } + + let (key, subkey, mask): (u64, _, _) = match segment { + StaticFileSegment::Headers => { + (table_key::(&key)?, None, >>::MASK) + } + StaticFileSegment::Transactions => { + (table_key::(&key)?, None, >>::MASK) + } + StaticFileSegment::Receipts => { + (table_key::(&key)?, None, >>::MASK) + } + StaticFileSegment::TransactionSenders => { + (table_key::(&key)?, None, TransactionSenderMask::MASK) + } + StaticFileSegment::AccountChangeSets => { + let subkey = table_subkey::(subkey.as_deref()).ok(); + (table_key::(&key)?, subkey, AccountChangesetMask::MASK) + } + StaticFileSegment::StorageChangeSets => { + unreachable!("storage changesets handled above"); + } + }; - let account = tool - .provider_factory - .static_file_provider() - .get_account_before_block(key, subkey)?; + if let StaticFileSegment::AccountChangeSets = segment { + let Some(subkey) = subkey else { + let changesets = + tool.provider_factory.static_file_provider().account_block_changeset(key)?; - if let Some(account) = account { - println!("{}", serde_json::to_string_pretty(&account)?); - } else { - error!(target: "reth::cli", "No content for the given table key."); - } + println!("{}", serde_json::to_string_pretty(&changesets)?); + return Ok(()); + }; - return Ok(()) - } + let account = tool + .provider_factory + .static_file_provider() + .get_account_before_block(key, subkey)?; - 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::>() - }) - }) - }, - )?; + if let Some(account) = account { + println!("{}", serde_json::to_string_pretty(&account)?); + } else { + error!(target: "reth::cli", "No content for the given table key."); + } - match content { - Some(content) => { - if raw { - println!("{}", hex::encode_prefixed(&content[0])); - } else { - match segment { - StaticFileSegment::Headers => { - let header = HeaderTy::::decompress(content[0].as_slice())?; - let block_hash = BlockHash::decompress(content[1].as_slice())?; - println!( - "Header\n{}\n\nBlockHash\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)?); - } - StaticFileSegment::TransactionSenders => { - let sender = - <::Value>::decompress( - content[0].as_slice(), - )?; - println!("{}", serde_json::to_string_pretty(&sender)?); - } - StaticFileSegment::AccountChangeSets => { - unreachable!("account changeset static files are special cased before this match") - } - StaticFileSegment::StorageChangeSets => { - unreachable!("storage changeset static files are special cased before this match") - } - } + return Ok(()); + } + + 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!("{}", hex::encode_prefixed(&content[0])); + } else { + match segment { + StaticFileSegment::Headers => { + let header = HeaderTy::::decompress(content[0].as_slice())?; + let block_hash = BlockHash::decompress(content[1].as_slice())?; + println!( + "Header\n{}\n\nBlockHash\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)?); + } + StaticFileSegment::TransactionSenders => { + let sender = + <::Value>::decompress( + content[0].as_slice(), + )?; + println!("{}", serde_json::to_string_pretty(&sender)?); + } + StaticFileSegment::AccountChangeSets => { + unreachable!( + "account changeset static files are special cased before this match" + ) + } + StaticFileSegment::StorageChangeSets => { + unreachable!( + "storage changeset static files are special cased before this match" + ) } } - None => { - error!(target: "reth::cli", "No content for the given table key."); - } - }; + } } - } + None => { + error!(target: "reth::cli", "No content for the given table key."); + } + }; Ok(()) } } -/// Get an instance of key for given table +#[cfg(all(unix, feature = "edge"))] +fn get_rocksdb( + tool: &DbTool, + table: RocksDbTable, + key_str: &str, + raw: bool, +) -> eyre::Result<()> { + match table { + RocksDbTable::TransactionHashNumbers => { + get_rocksdb_table::(tool, key_str, raw) + } + RocksDbTable::AccountsHistory => { + get_rocksdb_table::(tool, key_str, raw) + } + RocksDbTable::StoragesHistory => { + get_rocksdb_table::(tool, key_str, raw) + } + } +} + +#[cfg(all(unix, feature = "edge"))] +fn get_rocksdb_table( + tool: &DbTool, + key_str: &str, + raw: bool, +) -> eyre::Result<()> +where + T::Key: serde::Serialize, + T::Value: serde::Serialize, +{ + let key = table_key::(key_str)?; + let rocksdb = tool.provider_factory.rocksdb_provider(); + + let value = rocksdb.get::(key.clone())?; + + match value { + Some(value) => { + if raw { + let compressed = value.compress(); + println!("{}", hex::encode_prefixed(compressed.as_ref())); + } else { + let output = serde_json::json!({ + "key": key, + "val": value, + }); + println!("{}", serde_json::to_string_pretty(&output)?); + } + } + None => { + error!(target: "reth::cli", "No content for the given table key."); + } + } + + Ok(()) +} + 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<&str>) -> Result { serde_json::from_str(subkey.unwrap_or_default()).map_err(|e| eyre::eyre!(e)) } @@ -263,20 +331,15 @@ impl TableViewer<()> for GetValueViewer<'_, N> { fn view(&self) -> Result<(), Self::Error> { let key = table_key::(&self.key)?; - // A non-dupsort table cannot have subkeys. The `subkey` arg becomes the `end_key`. First we - // check that `end_key` and `end_subkey` weren't previously given, as that wouldn't be - // valid. if self.end_key.is_some() || self.end_subkey.is_some() { return Err(eyre::eyre!("Only END_KEY can be given for non-DUPSORT tables")); } let end_key = self.subkey.clone(); - // Check if we're doing a range query if let Some(ref end_key_str) = end_key { let end_key = table_key::(end_key_str)?; - // Use walk_range to iterate over the range self.tool.provider_factory.db_ref().view(|tx| { let mut cursor = tx.cursor_read::()?; let walker = cursor.walk_range(key..end_key)?; @@ -302,7 +365,6 @@ impl TableViewer<()> for GetValueViewer<'_, N> { Ok::<_, eyre::Report>(()) })??; } else { - // Single key lookup let content = if self.raw { self.tool .get::>(RawKey::from(key))? @@ -328,10 +390,8 @@ impl TableViewer<()> for GetValueViewer<'_, N> { where T::Value: reth_primitives_traits::ValueWithSubKey, { - // get a key for given table let key = table_key::(&self.key)?; - // Check if we're doing a range query if let Some(ref end_key_str) = self.end_key { let end_key = table_key::(end_key_str)?; let start_subkey = table_subkey::(Some( @@ -346,29 +406,23 @@ impl TableViewer<()> for GetValueViewer<'_, N> { self.tool.provider_factory.db_ref().view(|tx| { let mut cursor = tx.cursor_dup_read::()?; - // Seek to the starting key. If there is actually a key at the starting key then - // seek to the subkey within it. if let Some((decoded_key, _)) = cursor.seek(key.clone())? && decoded_key == key { cursor.seek_by_key_subkey(key.clone(), start_subkey.clone())?; } - // Get the current position to start iteration let mut current = cursor.current()?; while let Some((decoded_key, decoded_value)) = current { - // Extract the subkey using the ValueWithSubKey trait let decoded_subkey = decoded_value.get_subkey(); - // Check if we've reached the end (exclusive) if (&decoded_key, Some(&decoded_subkey)) >= (&end_key, end_subkey_parsed.as_ref()) { break; } - // Output the entry with both key and subkey let json_val = if self.raw { let raw_key = RawKey::from(decoded_key.clone()); serde_json::json!({ @@ -384,14 +438,12 @@ impl TableViewer<()> for GetValueViewer<'_, N> { println!("{}", serde_json::to_string_pretty(&json_val)?); - // Move to next entry current = cursor.next()?; } Ok::<_, eyre::Report>(()) })??; } else { - // Single key/subkey lookup let subkey = table_subkey::(self.subkey.as_deref())?; let content = if self.raw { @@ -419,7 +471,6 @@ impl TableViewer<()> for GetValueViewer<'_, N> { } } -/// 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()) @@ -439,7 +490,6 @@ mod tests { }; use std::str::FromStr; - /// A helper type to parse Args more easily #[derive(Parser)] struct CommandParser { #[command(flatten)]