use alloy_consensus::Header;
use alloy_primitives::{hex, BlockHash};
use clap::Parser;
use reth_db::{
static_file::{ColumnSelectorOne, ColumnSelectorTwo, HeaderMask, ReceiptMask, TransactionMask},
tables, RawKey, RawTable, Receipts, TableViewer, Transactions,
};
use reth_db_api::table::{Decompress, DupSort, Table};
use reth_db_common::DbTool;
use reth_node_builder::NodeTypesWithDB;
use reth_provider::{providers::ProviderNodeTypes, StaticFileProviderFactory};
use reth_static_file_types::StaticFileSegment;
use tracing::error;
#[derive(Parser, Debug)]
pub struct Command {
#[command(subcommand)]
subcommand: Subcommand,
}
#[derive(clap::Subcommand, Debug)]
enum Subcommand {
Mdbx {
table: tables::Tables,
#[arg(value_parser = maybe_json_value_parser)]
key: String,
#[arg(value_parser = maybe_json_value_parser)]
subkey: Option<String>,
#[arg(long)]
raw: bool,
},
StaticFile {
segment: StaticFileSegment,
#[arg(value_parser = maybe_json_value_parser)]
key: String,
#[arg(long)]
raw: bool,
},
}
impl Command {
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> 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::<tables::Headers>(&key)?, <HeaderMask<Header, BlockHash>>::MASK)
}
StaticFileSegment::Transactions => (
table_key::<tables::Transactions>(&key)?,
<TransactionMask<<Transactions as Table>::Value>>::MASK,
),
StaticFileSegment::Receipts => (
table_key::<tables::Receipts>(&key)?,
<ReceiptMask<<Receipts as Table>::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::<Vec<_>>()
})
})
},
)?;
match content {
Some(content) => {
if raw {
println!("{}", hex::encode_prefixed(&content[0]));
} else {
match segment {
StaticFileSegment::Headers => {
let header = Header::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 = <<Transactions as Table>::Value>::decompress(
content[0].as_slice(),
)?;
println!("{}", serde_json::to_string_pretty(&transaction)?);
}
StaticFileSegment::Receipts => {
let receipt = <<Receipts as Table>::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(())
}
}
pub(crate) fn table_key<T: Table>(key: &str) -> Result<T::Key, eyre::Error> {
serde_json::from_str::<T::Key>(key).map_err(|e| eyre::eyre!(e))
}
fn table_subkey<T: DupSort>(subkey: Option<&str>) -> Result<T::SubKey, eyre::Error> {
serde_json::from_str::<T::SubKey>(subkey.unwrap_or_default()).map_err(|e| eyre::eyre!(e))
}
struct GetValueViewer<'a, N: NodeTypesWithDB> {
tool: &'a DbTool<N>,
key: String,
subkey: Option<String>,
raw: bool,
}
impl<N: ProviderNodeTypes> TableViewer<()> for GetValueViewer<'_, N> {
type Error = eyre::Report;
fn view<T: Table>(&self) -> Result<(), Self::Error> {
let key = table_key::<T>(&self.key)?;
let content = if self.raw {
self.tool
.get::<RawTable<T>>(RawKey::from(key))?
.map(|content| hex::encode_prefixed(content.raw_value()))
} else {
self.tool.get::<T>(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<T: DupSort>(&self) -> Result<(), Self::Error> {
let key = table_key::<T>(&self.key)?;
let subkey = table_subkey::<T>(self.subkey.as_deref())?;
match self.tool.get_dup::<T>(key, subkey)? {
Some(content) => {
println!("{}", serde_json::to_string_pretty(&content)?);
}
None => {
error!(target: "reth::cli", "No content for the given table subkey.");
}
};
Ok(())
}
}
pub(crate) fn maybe_json_value_parser(value: &str) -> Result<String, eyre::Error> {
if serde_json::from_str::<serde::de::IgnoredAny>(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 alloy_primitives::{Address, B256};
use clap::{Args, Parser};
use reth_db::{AccountsHistory, HashedAccounts, Headers, StageCheckpoints, StoragesHistory};
use reth_db_api::models::{storage_sharded_key::StorageShardedKey, ShardedKey};
use std::str::FromStr;
#[derive(Parser)]
struct CommandParser<T: Args> {
#[command(flatten)]
args: T,
}
#[test]
fn parse_numeric_key_args() {
assert_eq!(table_key::<Headers>("123").unwrap(), 123);
assert_eq!(
table_key::<HashedAccounts>(
"\"0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac\""
)
.unwrap(),
B256::from_str("0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac")
.unwrap()
);
}
#[test]
fn parse_string_key_args() {
assert_eq!(
table_key::<StageCheckpoints>("\"MerkleExecution\"").unwrap(),
"MerkleExecution"
);
}
#[test]
fn parse_json_key_args() {
assert_eq!(
table_key::<StoragesHistory>(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::<AccountsHistory>(r#"{ "key": "0x4448e1273fd5a8bfdb9ed111e96889c960eee145", "highest_block_number": 18446744073709551615 }"#).unwrap(),
ShardedKey::new(
Address::from_str("0x4448e1273fd5a8bfdb9ed111e96889c960eee145").unwrap(),
18446744073709551615
)
);
}
}