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