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