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