1use alloy_primitives::{hex, BlockHash};
2use clap::Parser;
3use reth_db::{
4 static_file::{
5 ColumnSelectorOne, ColumnSelectorTwo, HeaderWithHashMask, ReceiptMask, TransactionMask,
6 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, StaticFileProviderFactory};
23use reth_static_file_types::StaticFileSegment;
24use tracing::error;
25
26#[derive(Parser, Debug)]
28pub struct Command {
29 #[command(subcommand)]
30 subcommand: Subcommand,
31}
32
33#[derive(clap::Subcommand, Debug)]
34enum Subcommand {
35 Mdbx {
37 table: tables::Tables,
38
39 #[arg(value_parser = maybe_json_value_parser)]
41 key: String,
42
43 #[arg(value_parser = maybe_json_value_parser)]
45 subkey: Option<String>,
46
47 #[arg(value_parser = maybe_json_value_parser)]
49 end_key: Option<String>,
50
51 #[arg(value_parser = maybe_json_value_parser)]
53 end_subkey: Option<String>,
54
55 #[arg(long)]
57 raw: bool,
58 },
59 StaticFile {
61 segment: StaticFileSegment,
62
63 #[arg(value_parser = maybe_json_value_parser)]
65 key: String,
66
67 #[arg(long)]
69 raw: bool,
70 },
71}
72
73impl Command {
74 pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
76 match self.subcommand {
77 Subcommand::Mdbx { table, key, subkey, end_key, end_subkey, raw } => {
78 table.view(&GetValueViewer { tool, key, subkey, end_key, end_subkey, raw })?
79 }
80 Subcommand::StaticFile { segment, key, raw } => {
81 let (key, mask): (u64, _) = match segment {
82 StaticFileSegment::Headers => (
83 table_key::<tables::Headers>(&key)?,
84 <HeaderWithHashMask<HeaderTy<N>>>::MASK,
85 ),
86 StaticFileSegment::Transactions => {
87 (table_key::<tables::Transactions>(&key)?, <TransactionMask<TxTy<N>>>::MASK)
88 }
89 StaticFileSegment::Receipts => {
90 (table_key::<tables::Receipts>(&key)?, <ReceiptMask<ReceiptTy<N>>>::MASK)
91 }
92 StaticFileSegment::TransactionSenders => (
93 table_key::<tables::TransactionSenders>(&key)?,
94 <TransactionSenderMask>::MASK,
95 ),
96 };
97
98 let content = tool
99 .provider_factory
100 .static_file_provider()
101 .get_segment_provider(segment, key)?
102 .cursor()?
103 .get(key.into(), mask)
104 .map(|result| {
105 result.map(|vec| vec.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>())
106 })?;
107
108 match content {
109 Some(content) => {
110 if raw {
111 println!("{}", hex::encode_prefixed(&content[0]));
112 } else {
113 match segment {
114 StaticFileSegment::Headers => {
115 let header = HeaderTy::<N>::decompress(content[0].as_slice())?;
116 let block_hash = BlockHash::decompress(content[1].as_slice())?;
117 println!(
118 "Header\n{}\n\nBlockHash\n{}",
119 serde_json::to_string_pretty(&header)?,
120 serde_json::to_string_pretty(&block_hash)?
121 );
122 }
123 StaticFileSegment::Transactions => {
124 let transaction = <<Transactions as Table>::Value>::decompress(
125 content[0].as_slice(),
126 )?;
127 println!("{}", serde_json::to_string_pretty(&transaction)?);
128 }
129 StaticFileSegment::Receipts => {
130 let receipt = <<Receipts as Table>::Value>::decompress(
131 content[0].as_slice(),
132 )?;
133 println!("{}", serde_json::to_string_pretty(&receipt)?);
134 }
135 StaticFileSegment::TransactionSenders => {
136 let sender =
137 <<tables::TransactionSenders as Table>::Value>::decompress(
138 content[0].as_slice(),
139 )?;
140 println!("{}", serde_json::to_string_pretty(&sender)?);
141 }
142 }
143 }
144 }
145 None => {
146 error!(target: "reth::cli", "No content for the given table key.");
147 }
148 };
149 }
150 }
151
152 Ok(())
153 }
154}
155
156pub(crate) fn table_key<T: Table>(key: &str) -> Result<T::Key, eyre::Error> {
158 serde_json::from_str(key).map_err(|e| eyre::eyre!(e))
159}
160
161fn table_subkey<T: DupSort>(subkey: Option<&str>) -> Result<T::SubKey, eyre::Error> {
163 serde_json::from_str(subkey.unwrap_or_default()).map_err(|e| eyre::eyre!(e))
164}
165
166struct GetValueViewer<'a, N: NodeTypesWithDB> {
167 tool: &'a DbTool<N>,
168 key: String,
169 subkey: Option<String>,
170 end_key: Option<String>,
171 end_subkey: Option<String>,
172 raw: bool,
173}
174
175impl<N: ProviderNodeTypes> TableViewer<()> for GetValueViewer<'_, N> {
176 type Error = eyre::Report;
177
178 fn view<T: Table>(&self) -> Result<(), Self::Error> {
179 let key = table_key::<T>(&self.key)?;
180
181 if self.end_key.is_some() || self.end_subkey.is_some() {
185 return Err(eyre::eyre!("Only END_KEY can be given for non-DUPSORT tables"));
186 }
187
188 let end_key = self.subkey.clone();
189
190 if let Some(ref end_key_str) = end_key {
192 let end_key = table_key::<T>(end_key_str)?;
193
194 self.tool.provider_factory.db_ref().view(|tx| {
196 let mut cursor = tx.cursor_read::<T>()?;
197 let walker = cursor.walk_range(key..end_key)?;
198
199 for result in walker {
200 let (k, v) = result?;
201 let json_val = if self.raw {
202 let raw_key = RawKey::from(k);
203 serde_json::json!({
204 "key": hex::encode_prefixed(raw_key.raw_key()),
205 "val": hex::encode_prefixed(v.compress().as_ref()),
206 })
207 } else {
208 serde_json::json!({
209 "key": &k,
210 "val": &v,
211 })
212 };
213
214 println!("{}", serde_json::to_string_pretty(&json_val)?);
215 }
216
217 Ok::<_, eyre::Report>(())
218 })??;
219 } else {
220 let content = if self.raw {
222 self.tool
223 .get::<RawTable<T>>(RawKey::from(key))?
224 .map(|content| hex::encode_prefixed(content.raw_value()))
225 } else {
226 self.tool.get::<T>(key)?.as_ref().map(serde_json::to_string_pretty).transpose()?
227 };
228
229 match content {
230 Some(content) => {
231 println!("{content}");
232 }
233 None => {
234 error!(target: "reth::cli", "No content for the given table key.");
235 }
236 };
237 }
238
239 Ok(())
240 }
241
242 fn view_dupsort<T: DupSort>(&self) -> Result<(), Self::Error>
243 where
244 T::Value: reth_primitives_traits::ValueWithSubKey<SubKey = T::SubKey>,
245 {
246 let key = table_key::<T>(&self.key)?;
248
249 if let Some(ref end_key_str) = self.end_key {
251 let end_key = table_key::<T>(end_key_str)?;
252 let start_subkey = table_subkey::<T>(Some(
253 self.subkey.as_ref().expect("must have been given if end_key is given").as_str(),
254 ))?;
255 let end_subkey_parsed = self
256 .end_subkey
257 .as_ref()
258 .map(|s| table_subkey::<T>(Some(s.as_str())))
259 .transpose()?;
260
261 self.tool.provider_factory.db_ref().view(|tx| {
262 let mut cursor = tx.cursor_dup_read::<T>()?;
263
264 if let Some((decoded_key, _)) = cursor.seek(key.clone())? &&
267 decoded_key == key
268 {
269 cursor.seek_by_key_subkey(key.clone(), start_subkey.clone())?;
270 }
271
272 let mut current = cursor.current()?;
274
275 while let Some((decoded_key, decoded_value)) = current {
276 let decoded_subkey = decoded_value.get_subkey();
278
279 if (&decoded_key, Some(&decoded_subkey)) >=
281 (&end_key, end_subkey_parsed.as_ref())
282 {
283 break;
284 }
285
286 let json_val = if self.raw {
288 let raw_key = RawKey::from(decoded_key.clone());
289 serde_json::json!({
290 "key": hex::encode_prefixed(raw_key.raw_key()),
291 "val": hex::encode_prefixed(decoded_value.compress().as_ref()),
292 })
293 } else {
294 serde_json::json!({
295 "key": &decoded_key,
296 "val": &decoded_value,
297 })
298 };
299
300 println!("{}", serde_json::to_string_pretty(&json_val)?);
301
302 current = cursor.next()?;
304 }
305
306 Ok::<_, eyre::Report>(())
307 })??;
308 } else {
309 let subkey = table_subkey::<T>(self.subkey.as_deref())?;
311
312 let content = if self.raw {
313 self.tool
314 .get_dup::<RawDupSort<T>>(RawKey::from(key), RawKey::from(subkey))?
315 .map(|content| hex::encode_prefixed(content.raw_value()))
316 } else {
317 self.tool
318 .get_dup::<T>(key, subkey)?
319 .as_ref()
320 .map(serde_json::to_string_pretty)
321 .transpose()?
322 };
323
324 match content {
325 Some(content) => {
326 println!("{content}");
327 }
328 None => {
329 error!(target: "reth::cli", "No content for the given table subkey.");
330 }
331 };
332 }
333 Ok(())
334 }
335}
336
337pub(crate) fn maybe_json_value_parser(value: &str) -> Result<String, eyre::Error> {
339 if serde_json::from_str::<serde::de::IgnoredAny>(value).is_ok() {
340 Ok(value.to_string())
341 } else {
342 serde_json::to_string(&value).map_err(|e| eyre::eyre!(e))
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use alloy_primitives::{address, B256};
350 use clap::{Args, Parser};
351 use reth_db_api::{
352 models::{storage_sharded_key::StorageShardedKey, ShardedKey},
353 AccountsHistory, HashedAccounts, Headers, StageCheckpoints, StoragesHistory,
354 };
355 use std::str::FromStr;
356
357 #[derive(Parser)]
359 struct CommandParser<T: Args> {
360 #[command(flatten)]
361 args: T,
362 }
363
364 #[test]
365 fn parse_numeric_key_args() {
366 assert_eq!(table_key::<Headers>("123").unwrap(), 123);
367 assert_eq!(
368 table_key::<HashedAccounts>(
369 "\"0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac\""
370 )
371 .unwrap(),
372 B256::from_str("0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac")
373 .unwrap()
374 );
375 }
376
377 #[test]
378 fn parse_string_key_args() {
379 assert_eq!(
380 table_key::<StageCheckpoints>("\"MerkleExecution\"").unwrap(),
381 "MerkleExecution"
382 );
383 }
384
385 #[test]
386 fn parse_json_key_args() {
387 assert_eq!(
388 table_key::<StoragesHistory>(r#"{ "address": "0x01957911244e546ce519fbac6f798958fafadb41", "sharded_key": { "key": "0x0000000000000000000000000000000000000000000000000000000000000003", "highest_block_number": 18446744073709551615 } }"#).unwrap(),
389 StorageShardedKey::new(
390 address!("0x01957911244e546ce519fbac6f798958fafadb41"),
391 B256::from_str(
392 "0x0000000000000000000000000000000000000000000000000000000000000003"
393 )
394 .unwrap(),
395 18446744073709551615
396 )
397 );
398 }
399
400 #[test]
401 fn parse_json_key_for_account_history() {
402 assert_eq!(
403 table_key::<AccountsHistory>(r#"{ "key": "0x4448e1273fd5a8bfdb9ed111e96889c960eee145", "highest_block_number": 18446744073709551615 }"#).unwrap(),
404 ShardedKey::new(
405 address!("0x4448e1273fd5a8bfdb9ed111e96889c960eee145"),
406 18446744073709551615
407 )
408 );
409 }
410}