1use alloy_primitives::{hex, BlockHash};
2use clap::Parser;
3use reth_db::{
4 static_file::{
5 AccountChangesetMask, ColumnSelectorOne, ColumnSelectorTwo, HeaderWithHashMask,
6 ReceiptMask, TransactionMask, 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, ChangeSetReader, StaticFileProviderFactory};
23use reth_static_file_types::StaticFileSegment;
24use reth_storage_api::StorageChangeSetReader;
25use tracing::error;
26
27#[derive(Parser, Debug)]
29pub struct Command {
30 #[command(subcommand)]
31 subcommand: Subcommand,
32}
33
34#[derive(clap::Subcommand, Debug)]
35enum Subcommand {
36 Mdbx {
38 table: tables::Tables,
39
40 #[arg(value_parser = maybe_json_value_parser)]
42 key: String,
43
44 #[arg(value_parser = maybe_json_value_parser)]
46 subkey: Option<String>,
47
48 #[arg(value_parser = maybe_json_value_parser)]
50 end_key: Option<String>,
51
52 #[arg(value_parser = maybe_json_value_parser)]
54 end_subkey: Option<String>,
55
56 #[arg(long)]
58 raw: bool,
59 },
60 StaticFile {
62 segment: StaticFileSegment,
63
64 #[arg(value_parser = maybe_json_value_parser)]
66 key: String,
67
68 #[arg(value_parser = maybe_json_value_parser)]
70 subkey: Option<String>,
71
72 #[arg(long)]
74 raw: bool,
75 },
76}
77
78impl Command {
79 pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
81 match self.subcommand {
82 Subcommand::Mdbx { table, key, subkey, end_key, end_subkey, raw } => {
83 table.view(&GetValueViewer { tool, key, subkey, end_key, end_subkey, raw })?
84 }
85 Subcommand::StaticFile { segment, key, subkey, raw } => {
86 if let StaticFileSegment::StorageChangeSets = segment {
87 let storage_key =
88 table_subkey::<tables::StorageChangeSets>(subkey.as_deref()).ok();
89 let key = table_key::<tables::StorageChangeSets>(&key)?;
90
91 let provider = tool.provider_factory.static_file_provider();
92
93 if let Some(storage_key) = storage_key {
94 let entry = provider.get_storage_before_block(
95 key.block_number(),
96 key.address(),
97 storage_key,
98 )?;
99
100 if let Some(entry) = entry {
101 let se: reth_primitives_traits::StorageEntry = entry.into();
102 println!("{}", serde_json::to_string_pretty(&se)?);
103 } else {
104 error!(target: "reth::cli", "No content for the given table key.");
105 }
106 return Ok(());
107 }
108
109 let changesets = provider.storage_changeset(key.block_number())?;
110 let serializable: Vec<_> = changesets
111 .into_iter()
112 .map(|(addr, entry)| {
113 let se: reth_primitives_traits::StorageEntry = entry.into();
114 (addr, se)
115 })
116 .collect();
117 println!("{}", serde_json::to_string_pretty(&serializable)?);
118 return Ok(());
119 }
120
121 let (key, subkey, mask): (u64, _, _) = match segment {
122 StaticFileSegment::Headers => (
123 table_key::<tables::Headers>(&key)?,
124 None,
125 <HeaderWithHashMask<HeaderTy<N>>>::MASK,
126 ),
127 StaticFileSegment::Transactions => (
128 table_key::<tables::Transactions>(&key)?,
129 None,
130 <TransactionMask<TxTy<N>>>::MASK,
131 ),
132 StaticFileSegment::Receipts => (
133 table_key::<tables::Receipts>(&key)?,
134 None,
135 <ReceiptMask<ReceiptTy<N>>>::MASK,
136 ),
137 StaticFileSegment::TransactionSenders => (
138 table_key::<tables::TransactionSenders>(&key)?,
139 None,
140 TransactionSenderMask::MASK,
141 ),
142 StaticFileSegment::AccountChangeSets => {
143 let subkey =
144 table_subkey::<tables::AccountChangeSets>(subkey.as_deref()).ok();
145 (
146 table_key::<tables::AccountChangeSets>(&key)?,
147 subkey,
148 AccountChangesetMask::MASK,
149 )
150 }
151 StaticFileSegment::StorageChangeSets => {
152 unreachable!("storage changesets handled above");
153 }
154 };
155
156 if let StaticFileSegment::AccountChangeSets = segment {
158 let Some(subkey) = subkey else {
159 let changesets = tool
161 .provider_factory
162 .static_file_provider()
163 .account_block_changeset(key)?;
164
165 println!("{}", serde_json::to_string_pretty(&changesets)?);
166 return Ok(())
167 };
168
169 let account = tool
170 .provider_factory
171 .static_file_provider()
172 .get_account_before_block(key, subkey)?;
173
174 if let Some(account) = account {
175 println!("{}", serde_json::to_string_pretty(&account)?);
176 } else {
177 error!(target: "reth::cli", "No content for the given table key.");
178 }
179
180 return Ok(())
181 }
182
183 let content = tool.provider_factory.static_file_provider().find_static_file(
184 segment,
185 |provider| {
186 let mut cursor = provider.cursor()?;
187 cursor.get(key.into(), mask).map(|result| {
188 result.map(|vec| {
189 vec.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>()
190 })
191 })
192 },
193 )?;
194
195 match content {
196 Some(content) => {
197 if raw {
198 println!("{}", hex::encode_prefixed(&content[0]));
199 } else {
200 match segment {
201 StaticFileSegment::Headers => {
202 let header = HeaderTy::<N>::decompress(content[0].as_slice())?;
203 let block_hash = BlockHash::decompress(content[1].as_slice())?;
204 println!(
205 "Header\n{}\n\nBlockHash\n{}",
206 serde_json::to_string_pretty(&header)?,
207 serde_json::to_string_pretty(&block_hash)?
208 );
209 }
210 StaticFileSegment::Transactions => {
211 let transaction = <<Transactions as Table>::Value>::decompress(
212 content[0].as_slice(),
213 )?;
214 println!("{}", serde_json::to_string_pretty(&transaction)?);
215 }
216 StaticFileSegment::Receipts => {
217 let receipt = <<Receipts as Table>::Value>::decompress(
218 content[0].as_slice(),
219 )?;
220 println!("{}", serde_json::to_string_pretty(&receipt)?);
221 }
222 StaticFileSegment::TransactionSenders => {
223 let sender =
224 <<tables::TransactionSenders as Table>::Value>::decompress(
225 content[0].as_slice(),
226 )?;
227 println!("{}", serde_json::to_string_pretty(&sender)?);
228 }
229 StaticFileSegment::AccountChangeSets => {
230 unreachable!("account changeset static files are special cased before this match")
231 }
232 StaticFileSegment::StorageChangeSets => {
233 unreachable!("storage changeset static files are special cased before this match")
234 }
235 }
236 }
237 }
238 None => {
239 error!(target: "reth::cli", "No content for the given table key.");
240 }
241 };
242 }
243 }
244
245 Ok(())
246 }
247}
248
249pub(crate) fn table_key<T: Table>(key: &str) -> Result<T::Key, eyre::Error> {
251 serde_json::from_str(key).map_err(|e| eyre::eyre!(e))
252}
253
254fn table_subkey<T: DupSort>(subkey: Option<&str>) -> Result<T::SubKey, eyre::Error> {
256 serde_json::from_str(subkey.unwrap_or_default()).map_err(|e| eyre::eyre!(e))
257}
258
259struct GetValueViewer<'a, N: NodeTypesWithDB> {
260 tool: &'a DbTool<N>,
261 key: String,
262 subkey: Option<String>,
263 end_key: Option<String>,
264 end_subkey: Option<String>,
265 raw: bool,
266}
267
268impl<N: ProviderNodeTypes> TableViewer<()> for GetValueViewer<'_, N> {
269 type Error = eyre::Report;
270
271 fn view<T: Table>(&self) -> Result<(), Self::Error> {
272 let key = table_key::<T>(&self.key)?;
273
274 if self.end_key.is_some() || self.end_subkey.is_some() {
278 return Err(eyre::eyre!("Only END_KEY can be given for non-DUPSORT tables"));
279 }
280
281 let end_key = self.subkey.clone();
282
283 if let Some(ref end_key_str) = end_key {
285 let end_key = table_key::<T>(end_key_str)?;
286
287 self.tool.provider_factory.db_ref().view(|tx| {
289 let mut cursor = tx.cursor_read::<T>()?;
290 let walker = cursor.walk_range(key..end_key)?;
291
292 for result in walker {
293 let (k, v) = result?;
294 let json_val = if self.raw {
295 let raw_key = RawKey::from(k);
296 serde_json::json!({
297 "key": hex::encode_prefixed(raw_key.raw_key()),
298 "val": hex::encode_prefixed(v.compress().as_ref()),
299 })
300 } else {
301 serde_json::json!({
302 "key": &k,
303 "val": &v,
304 })
305 };
306
307 println!("{}", serde_json::to_string_pretty(&json_val)?);
308 }
309
310 Ok::<_, eyre::Report>(())
311 })??;
312 } else {
313 let content = if self.raw {
315 self.tool
316 .get::<RawTable<T>>(RawKey::from(key))?
317 .map(|content| hex::encode_prefixed(content.raw_value()))
318 } else {
319 self.tool.get::<T>(key)?.as_ref().map(serde_json::to_string_pretty).transpose()?
320 };
321
322 match content {
323 Some(content) => {
324 println!("{content}");
325 }
326 None => {
327 error!(target: "reth::cli", "No content for the given table key.");
328 }
329 };
330 }
331
332 Ok(())
333 }
334
335 fn view_dupsort<T: DupSort>(&self) -> Result<(), Self::Error>
336 where
337 T::Value: reth_primitives_traits::ValueWithSubKey<SubKey = T::SubKey>,
338 {
339 let key = table_key::<T>(&self.key)?;
341
342 if let Some(ref end_key_str) = self.end_key {
344 let end_key = table_key::<T>(end_key_str)?;
345 let start_subkey = table_subkey::<T>(Some(
346 self.subkey.as_ref().expect("must have been given if end_key is given").as_str(),
347 ))?;
348 let end_subkey_parsed = self
349 .end_subkey
350 .as_ref()
351 .map(|s| table_subkey::<T>(Some(s.as_str())))
352 .transpose()?;
353
354 self.tool.provider_factory.db_ref().view(|tx| {
355 let mut cursor = tx.cursor_dup_read::<T>()?;
356
357 if let Some((decoded_key, _)) = cursor.seek(key.clone())? &&
360 decoded_key == key
361 {
362 cursor.seek_by_key_subkey(key.clone(), start_subkey.clone())?;
363 }
364
365 let mut current = cursor.current()?;
367
368 while let Some((decoded_key, decoded_value)) = current {
369 let decoded_subkey = decoded_value.get_subkey();
371
372 if (&decoded_key, Some(&decoded_subkey)) >=
374 (&end_key, end_subkey_parsed.as_ref())
375 {
376 break;
377 }
378
379 let json_val = if self.raw {
381 let raw_key = RawKey::from(decoded_key.clone());
382 serde_json::json!({
383 "key": hex::encode_prefixed(raw_key.raw_key()),
384 "val": hex::encode_prefixed(decoded_value.compress().as_ref()),
385 })
386 } else {
387 serde_json::json!({
388 "key": &decoded_key,
389 "val": &decoded_value,
390 })
391 };
392
393 println!("{}", serde_json::to_string_pretty(&json_val)?);
394
395 current = cursor.next()?;
397 }
398
399 Ok::<_, eyre::Report>(())
400 })??;
401 } else {
402 let subkey = table_subkey::<T>(self.subkey.as_deref())?;
404
405 let content = if self.raw {
406 self.tool
407 .get_dup::<RawDupSort<T>>(RawKey::from(key), RawKey::from(subkey))?
408 .map(|content| hex::encode_prefixed(content.raw_value()))
409 } else {
410 self.tool
411 .get_dup::<T>(key, subkey)?
412 .as_ref()
413 .map(serde_json::to_string_pretty)
414 .transpose()?
415 };
416
417 match content {
418 Some(content) => {
419 println!("{content}");
420 }
421 None => {
422 error!(target: "reth::cli", "No content for the given table subkey.");
423 }
424 };
425 }
426 Ok(())
427 }
428}
429
430pub(crate) fn maybe_json_value_parser(value: &str) -> Result<String, eyre::Error> {
432 if serde_json::from_str::<serde::de::IgnoredAny>(value).is_ok() {
433 Ok(value.to_string())
434 } else {
435 serde_json::to_string(&value).map_err(|e| eyre::eyre!(e))
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442 use alloy_primitives::{address, B256};
443 use clap::{Args, Parser};
444 use reth_db_api::{
445 models::{storage_sharded_key::StorageShardedKey, ShardedKey},
446 AccountsHistory, HashedAccounts, Headers, StageCheckpoints, StoragesHistory,
447 };
448 use std::str::FromStr;
449
450 #[derive(Parser)]
452 struct CommandParser<T: Args> {
453 #[command(flatten)]
454 args: T,
455 }
456
457 #[test]
458 fn parse_numeric_key_args() {
459 assert_eq!(table_key::<Headers>("123").unwrap(), 123);
460 assert_eq!(
461 table_key::<HashedAccounts>(
462 "\"0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac\""
463 )
464 .unwrap(),
465 B256::from_str("0x0ac361fe774b78f8fc4e86c1916930d150865c3fc2e21dca2e58833557608bac")
466 .unwrap()
467 );
468 }
469
470 #[test]
471 fn parse_string_key_args() {
472 assert_eq!(
473 table_key::<StageCheckpoints>("\"MerkleExecution\"").unwrap(),
474 "MerkleExecution"
475 );
476 }
477
478 #[test]
479 fn parse_json_key_args() {
480 assert_eq!(
481 table_key::<StoragesHistory>(r#"{ "address": "0x01957911244e546ce519fbac6f798958fafadb41", "sharded_key": { "key": "0x0000000000000000000000000000000000000000000000000000000000000003", "highest_block_number": 18446744073709551615 } }"#).unwrap(),
482 StorageShardedKey::new(
483 address!("0x01957911244e546ce519fbac6f798958fafadb41"),
484 B256::from_str(
485 "0x0000000000000000000000000000000000000000000000000000000000000003"
486 )
487 .unwrap(),
488 18446744073709551615
489 )
490 );
491 }
492
493 #[test]
494 fn parse_json_key_for_account_history() {
495 assert_eq!(
496 table_key::<AccountsHistory>(r#"{ "key": "0x4448e1273fd5a8bfdb9ed111e96889c960eee145", "highest_block_number": 18446744073709551615 }"#).unwrap(),
497 ShardedKey::new(
498 address!("0x4448e1273fd5a8bfdb9ed111e96889c960eee145"),
499 18446744073709551615
500 )
501 );
502 }
503}