|
| 1 | +use crate::Pnt; |
| 2 | +use ajj::serde_json; |
| 3 | +use eyre::WrapErr; |
| 4 | +use reth::providers::ProviderFactory; |
| 5 | +use reth_db::{Database, TableViewer, table::Table}; |
| 6 | +use reth_db_common::{DbTool, ListFilter}; |
| 7 | +use std::sync::OnceLock; |
| 8 | +use tracing::instrument; |
| 9 | + |
| 10 | +/// Modeled on the `Command` struct from `reth/crates/cli/commands/src/db/list.rs` |
| 11 | +#[derive(Debug, serde::Deserialize)] |
| 12 | +pub(crate) struct DbArgs( |
| 13 | + /// The table name |
| 14 | + String, // 0 |
| 15 | + /// Skip first N entries |
| 16 | + #[serde(default)] |
| 17 | + usize, // 1 |
| 18 | + /// How many items to take from the walker |
| 19 | + #[serde(default)] |
| 20 | + Option<usize>, // 2 |
| 21 | + /// Search parameter for both keys and values. Prefix it with `0x` to search for binary data, |
| 22 | + /// and text otherwise. |
| 23 | + /// |
| 24 | + /// ATTENTION! For compressed tables (`Transactions` and `Receipts`), there might be |
| 25 | + /// missing results since the search uses the raw uncompressed value from the database. |
| 26 | + #[serde(default)] |
| 27 | + Option<String>, // 3 |
| 28 | +); |
| 29 | + |
| 30 | +impl DbArgs { |
| 31 | + /// Get the table name. |
| 32 | + pub(crate) fn table_name(&self) -> &str { |
| 33 | + &self.0 |
| 34 | + } |
| 35 | + |
| 36 | + /// Parse the table name into a [`reth_db::Tables`] enum. |
| 37 | + pub(crate) fn table(&self) -> Result<reth_db::Tables, String> { |
| 38 | + self.table_name().parse() |
| 39 | + } |
| 40 | + |
| 41 | + /// Get the skip value. |
| 42 | + pub(crate) fn skip(&self) -> usize { |
| 43 | + self.1 |
| 44 | + } |
| 45 | + |
| 46 | + /// Get the length value. |
| 47 | + pub(crate) fn len(&self) -> usize { |
| 48 | + self.2.unwrap_or(5) |
| 49 | + } |
| 50 | + |
| 51 | + /// Get the search value. |
| 52 | + pub(crate) fn search(&self) -> Vec<u8> { |
| 53 | + self.3 |
| 54 | + .as_ref() |
| 55 | + .map(|search| { |
| 56 | + if let Some(search) = search.strip_prefix("0x") { |
| 57 | + return alloy::primitives::hex::decode(search).unwrap(); |
| 58 | + } |
| 59 | + search.as_bytes().to_vec() |
| 60 | + }) |
| 61 | + .unwrap_or_default() |
| 62 | + } |
| 63 | + |
| 64 | + /// Generate [`ListFilter`] from command. |
| 65 | + pub(crate) fn list_filter(&self) -> ListFilter { |
| 66 | + ListFilter { |
| 67 | + skip: self.skip(), |
| 68 | + len: self.len(), |
| 69 | + search: self.search(), |
| 70 | + min_row_size: 0, |
| 71 | + min_key_size: 0, |
| 72 | + min_value_size: 0, |
| 73 | + reverse: false, |
| 74 | + only_count: false, |
| 75 | + } |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +pub(crate) struct ListTableViewer<'a, 'b, N: Pnt> { |
| 80 | + pub(crate) factory: &'b ProviderFactory<N>, |
| 81 | + pub(crate) args: &'a DbArgs, |
| 82 | + |
| 83 | + pub(crate) output: OnceLock<Box<serde_json::value::RawValue>>, |
| 84 | +} |
| 85 | + |
| 86 | +impl<'a, 'b, N: Pnt> ListTableViewer<'a, 'b, N> { |
| 87 | + /// Create a new `ListTableViewer`. |
| 88 | + pub(crate) fn new(factory: &'b ProviderFactory<N>, args: &'a DbArgs) -> Self { |
| 89 | + Self { factory, args, output: Default::default() } |
| 90 | + } |
| 91 | + |
| 92 | + /// Take the output if it has been initialized, otherwise return `None`. |
| 93 | + pub(crate) fn take_output(self) -> Option<Box<serde_json::value::RawValue>> { |
| 94 | + self.output.into_inner() |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +impl<N: Pnt> TableViewer<()> for ListTableViewer<'_, '_, N> { |
| 99 | + type Error = eyre::Report; |
| 100 | + |
| 101 | + #[instrument(skip(self), err)] |
| 102 | + fn view<T: Table>(&self) -> eyre::Result<()> { |
| 103 | + let tool = DbTool { provider_factory: self.factory.clone() }; |
| 104 | + |
| 105 | + self.factory.db_ref().view(|tx| { |
| 106 | + let table_db = |
| 107 | + tx.inner.open_db(Some(&self.args.table_name())).wrap_err("Could not open db.")?; |
| 108 | + let stats = tx |
| 109 | + .inner |
| 110 | + .db_stat(&table_db) |
| 111 | + .wrap_err(format!("Could not find table: {}", stringify!($table)))?; |
| 112 | + let total_entries = stats.entries(); |
| 113 | + let final_entry_idx = total_entries.saturating_sub(1); |
| 114 | + eyre::ensure!( |
| 115 | + self.args.skip() >= final_entry_idx, |
| 116 | + "Skip value {} is greater than total entries {}", |
| 117 | + self.args.skip(), |
| 118 | + total_entries |
| 119 | + ); |
| 120 | + |
| 121 | + let list_filter = self.args.list_filter(); |
| 122 | + |
| 123 | + let (list, _) = tool.list::<T>(&list_filter)?; |
| 124 | + |
| 125 | + let json = |
| 126 | + serde_json::value::to_raw_value(&list).wrap_err("Failed to serialize list")?; |
| 127 | + |
| 128 | + self.output.get_or_init(|| json); |
| 129 | + |
| 130 | + Ok(()) |
| 131 | + })??; |
| 132 | + |
| 133 | + Ok(()) |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +// Some code in this file is adapted from github.com/paradigmxyz/reth. |
| 138 | +// |
| 139 | +// Particularly the `reth/crates/cli/commands/src/db/list.rs` file. It is |
| 140 | +// reproduced here under the terms of the MIT license, |
| 141 | +// |
| 142 | +// The MIT License (MIT) |
| 143 | +// |
| 144 | +// Copyright (c) 2022-2025 Reth Contributors |
| 145 | +// |
| 146 | +// Permission is hereby granted, free of charge, to any person obtaining a copy |
| 147 | +// of this software and associated documentation files (the "Software"), to deal |
| 148 | +// in the Software without restriction, including without limitation the rights |
| 149 | +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 150 | +// copies of the Software, and to permit persons to whom the Software is |
| 151 | +// furnished to do so, subject to the following conditions: |
| 152 | +// |
| 153 | +// The above copyright notice and this permission notice shall be included in |
| 154 | +// all copies or substantial portions of the Software. |
| 155 | +// |
| 156 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 157 | +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 158 | +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 159 | +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 160 | +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 161 | +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 162 | +// THE SOFTWARE. |
0 commit comments