iota_tool/db_tool/
index_search.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{fmt::Debug, path::PathBuf, str::FromStr};
6
7use anyhow::anyhow;
8use iota_storage::IndexStoreTables;
9use iota_types::{
10    Identifier, TypeTag,
11    base_types::{IotaAddress, ObjectID, TxSequenceNumber},
12    digests::TransactionDigest,
13};
14use move_core_types::language_storage::ModuleId;
15use serde::{Serialize, de::DeserializeOwned};
16use typed_store::{
17    rocks::{DBMap, MetricConf},
18    traits::Map,
19};
20
21use crate::get_db_entries;
22
23#[derive(Clone, Debug)]
24pub enum SearchRange<T: Serialize + Clone + Debug> {
25    ExclusiveLastKey(T),
26    Count(u64),
27}
28
29impl<T: Serialize + Clone + Debug + FromStr> FromStr for SearchRange<T>
30where
31    <T as std::str::FromStr>::Err: std::fmt::Debug,
32{
33    type Err = anyhow::Error;
34
35    fn from_str(s: &str) -> Result<Self, Self::Err> {
36        let last_key = T::from_str(s).map_err(|e| anyhow!("Failed to parse last_key: {:?}", e))?;
37        Ok(SearchRange::ExclusiveLastKey(last_key))
38    }
39}
40
41/// Until we use a proc macro to auto derive this, we have to make sure to
42/// update the `search_index` function below when adding new tables.
43pub fn search_index(
44    db_path: PathBuf,
45    table_name: String,
46    start: String,
47    termination: SearchRange<String>,
48) -> Result<Vec<(String, String)>, anyhow::Error> {
49    let start = start.as_str();
50    println!("Opening db at {:?} ...", db_path);
51    let db_read_only_handle =
52        IndexStoreTables::get_read_only_handle(db_path, None, None, MetricConf::default());
53    match table_name.as_str() {
54        "transactions_from_addr" => {
55            get_db_entries!(
56                db_read_only_handle.transactions_from_addr,
57                from_addr_seq,
58                start,
59                termination
60            )
61        }
62        "transactions_to_addr" => {
63            get_db_entries!(
64                db_read_only_handle.transactions_to_addr,
65                from_addr_seq,
66                start,
67                termination
68            )
69        }
70        "transactions_by_input_object_id" => {
71            get_db_entries!(
72                db_read_only_handle.transactions_by_input_object_id,
73                from_id_seq,
74                start,
75                termination
76            )
77        }
78        "transactions_by_mutated_object_id" => {
79            get_db_entries!(
80                db_read_only_handle.transactions_by_mutated_object_id,
81                from_id_seq,
82                start,
83                termination
84            )
85        }
86        "transactions_by_move_function" => {
87            get_db_entries!(
88                db_read_only_handle.transactions_by_move_function,
89                from_id_module_function_txseq,
90                start,
91                termination
92            )
93        }
94        "transaction_order" => {
95            get_db_entries!(
96                db_read_only_handle.transaction_order,
97                u64::from_str,
98                start,
99                termination
100            )
101        }
102        "transactions_seq" => {
103            get_db_entries!(
104                db_read_only_handle.transactions_seq,
105                TransactionDigest::from_str,
106                start,
107                termination
108            )
109        }
110        "owner_index" => {
111            get_db_entries!(
112                db_read_only_handle.owner_index,
113                from_addr_oid,
114                start,
115                termination
116            )
117        }
118        "coin_index" => {
119            get_db_entries!(
120                db_read_only_handle.coin_index,
121                from_addr_str_oid,
122                start,
123                termination
124            )
125        }
126        "dynamic_field_index" => {
127            get_db_entries!(
128                db_read_only_handle.dynamic_field_index,
129                from_oid_oid,
130                start,
131                termination
132            )
133        }
134        "event_by_event_module" => {
135            get_db_entries!(
136                db_read_only_handle.event_by_event_module,
137                from_module_id_and_event_id,
138                start,
139                termination
140            )
141        }
142        "event_by_move_module" => {
143            get_db_entries!(
144                db_read_only_handle.event_by_move_module,
145                from_module_id_and_event_id,
146                start,
147                termination
148            )
149        }
150        "event_order" => {
151            get_db_entries!(
152                db_read_only_handle.event_order,
153                from_event_id,
154                start,
155                termination
156            )
157        }
158        "event_by_sender" => {
159            get_db_entries!(
160                db_read_only_handle.event_by_sender,
161                from_address_and_event_id,
162                start,
163                termination
164            )
165        }
166        _ => Err(anyhow!("Invalid or unsupported table: {}", table_name)),
167    }
168}
169
170#[macro_export]
171macro_rules! get_db_entries {
172    ($db_map:expr, $key_converter:expr, $start:expr, $term:expr) => {{
173        let key = $key_converter($start)?;
174        println!("Searching from key: {:?}", key);
175        let termination = match $term {
176            SearchRange::ExclusiveLastKey(last_key) => {
177                println!(
178                    "Retrieving all keys up to (but not including) key: {:?}",
179                    key
180                );
181                SearchRange::ExclusiveLastKey($key_converter(last_key.as_str())?)
182            }
183            SearchRange::Count(count) => {
184                println!("Retrieving up to {} keys", count);
185                SearchRange::Count(count)
186            }
187        };
188
189        $db_map.try_catch_up_with_primary().unwrap();
190        get_entries_to_str(&$db_map, key, termination)
191    }};
192}
193
194fn get_entries_to_str<K, V>(
195    db_map: &DBMap<K, V>,
196    start: K,
197    termination: SearchRange<K>,
198) -> Result<Vec<(String, String)>, anyhow::Error>
199where
200    K: Serialize + serde::de::DeserializeOwned + Clone + Debug,
201    V: serde::Serialize + DeserializeOwned + Clone + Debug,
202{
203    get_entries(db_map, start, termination).map(|entries| {
204        entries
205            .into_iter()
206            .map(|(k, v)| (format!("{:?}", k), format!("{:?}", v)))
207            .collect()
208    })
209}
210
211fn get_entries<K, V>(
212    db_map: &DBMap<K, V>,
213    start: K,
214    termination: SearchRange<K>,
215) -> Result<Vec<(K, V)>, anyhow::Error>
216where
217    K: Serialize + serde::de::DeserializeOwned + Clone + std::fmt::Debug,
218    V: serde::Serialize + DeserializeOwned + Clone,
219{
220    let mut entries = Vec::new();
221    match termination {
222        SearchRange::ExclusiveLastKey(exclusive_last_key) => {
223            let iter = db_map.safe_iter_with_bounds(Some(start), Some(exclusive_last_key));
224
225            for result in iter {
226                let (key, value) = result?;
227                entries.push((key.clone(), value.clone()));
228            }
229        }
230        SearchRange::Count(mut count) => {
231            let mut iter = db_map.safe_iter_with_bounds(Some(start), None);
232
233            while count > 0 {
234                if let Some(result) = iter.next() {
235                    let (key, value) = result?;
236                    entries.push((key.clone(), value.clone()));
237                } else {
238                    break;
239                }
240                count -= 1;
241            }
242        }
243    }
244    Ok(entries)
245}
246
247fn from_addr_seq(s: &str) -> Result<(IotaAddress, TxSequenceNumber), anyhow::Error> {
248    // Remove whitespaces
249    let s = s.trim();
250    let tokens = s.split(',').collect::<Vec<&str>>();
251    if tokens.len() != 2 {
252        return Err(anyhow!("Invalid address, sequence number pair"));
253    }
254    let address = IotaAddress::from_str(tokens[0].trim())?;
255    let sequence_number = TxSequenceNumber::from_str(tokens[1].trim())?;
256
257    Ok((address, sequence_number))
258}
259
260fn from_id_seq(s: &str) -> Result<(ObjectID, TxSequenceNumber), anyhow::Error> {
261    // Remove whitespaces
262    let s = s.trim();
263    let tokens = s.split(',').collect::<Vec<&str>>();
264    if tokens.len() != 2 {
265        return Err(anyhow!("Invalid object id, sequence number pair"));
266    }
267    let oid = ObjectID::from_str(tokens[0].trim())?;
268    let sequence_number = TxSequenceNumber::from_str(tokens[1].trim())?;
269
270    Ok((oid, sequence_number))
271}
272
273fn from_id_module_function_txseq(
274    s: &str,
275) -> Result<(ObjectID, String, String, TxSequenceNumber), anyhow::Error> {
276    // Remove whitespaces
277    let s = s.trim();
278    let tokens = s.split(',').collect::<Vec<&str>>();
279    if tokens.len() != 4 {
280        return Err(anyhow!(
281            "Invalid object id, module name, function name, TX sequence number quad"
282        ));
283    }
284    let pid = ObjectID::from_str(tokens[0].trim())?;
285    let module: Identifier = Identifier::from_str(tokens[1].trim())?;
286    let func: Identifier = Identifier::from_str(tokens[2].trim())?;
287    let seq: TxSequenceNumber = TxSequenceNumber::from_str(tokens[3].trim())?;
288
289    Ok((pid, module.to_string(), func.to_string(), seq))
290}
291
292fn from_addr_oid(s: &str) -> Result<(IotaAddress, ObjectID), anyhow::Error> {
293    // Remove whitespaces
294    let s = s.trim();
295    let tokens = s.split(',').collect::<Vec<&str>>();
296    if tokens.len() != 2 {
297        return Err(anyhow!("Invalid address, object id pair"));
298    }
299    let addr = IotaAddress::from_str(tokens[0].trim())?;
300    let oid = ObjectID::from_str(tokens[1].trim())?;
301
302    Ok((addr, oid))
303}
304
305fn from_addr_str_oid(s: &str) -> Result<(IotaAddress, String, ObjectID), anyhow::Error> {
306    // Remove whitespaces
307    let s = s.trim();
308    let tokens = s.split(',').collect::<Vec<&str>>();
309    if tokens.len() != 3 {
310        return Err(anyhow!("Invalid addr, type tag object id triplet"));
311    }
312    let address = IotaAddress::from_str(tokens[0].trim())?;
313    let tag: TypeTag = TypeTag::from_str(tokens[1].trim())?;
314    let oid: ObjectID = ObjectID::from_str(tokens[2].trim())?;
315
316    Ok((address, tag.to_string(), oid))
317}
318
319fn from_oid_oid(s: &str) -> Result<(ObjectID, ObjectID), anyhow::Error> {
320    // Remove whitespaces
321    let s = s.trim();
322    let tokens = s.split(',').collect::<Vec<&str>>();
323    if tokens.len() != 2 {
324        return Err(anyhow!("Invalid object id, object id triplet"));
325    }
326    let oid1 = ObjectID::from_str(tokens[0].trim())?;
327    let oid2: ObjectID = ObjectID::from_str(tokens[1].trim())?;
328
329    Ok((oid1, oid2))
330}
331
332fn from_module_id_and_event_id(
333    s: &str,
334) -> Result<(ModuleId, (TxSequenceNumber, usize)), anyhow::Error> {
335    // Example: "0x1::Event 1234 5"
336    let tokens = s.split(' ').collect::<Vec<&str>>();
337    if tokens.len() != 3 {
338        return Err(anyhow!("Invalid input"));
339    }
340    let tx_seq = TxSequenceNumber::from_str(tokens[1])?;
341    let event_seq = usize::from_str(tokens[2])?;
342    let tokens = tokens[0].split("::").collect::<Vec<&str>>();
343    if tokens.len() != 2 {
344        return Err(anyhow!("Invalid module id"));
345    }
346    let package = ObjectID::from_str(tokens[0].trim())?;
347
348    Ok((
349        ModuleId::new(package.into(), Identifier::from_str(tokens[1].trim())?),
350        (tx_seq, event_seq),
351    ))
352}
353
354fn from_event_id(s: &str) -> Result<(TxSequenceNumber, usize), anyhow::Error> {
355    // Example: "1234 5"
356    let tokens = s.split(' ').collect::<Vec<&str>>();
357    if tokens.len() != 2 {
358        return Err(anyhow!("Invalid input"));
359    }
360    let tx_seq = TxSequenceNumber::from_str(tokens[0])?;
361    let event_seq = usize::from_str(tokens[1])?;
362    Ok((tx_seq, event_seq))
363}
364
365fn from_address_and_event_id(
366    s: &str,
367) -> Result<(IotaAddress, (TxSequenceNumber, usize)), anyhow::Error> {
368    // Example: "0x1 1234 5"
369    let tokens = s.split(' ').collect::<Vec<&str>>();
370    if tokens.len() != 3 {
371        return Err(anyhow!("Invalid input"));
372    }
373    let tx_seq = TxSequenceNumber::from_str(tokens[1])?;
374    let event_seq = usize::from_str(tokens[2])?;
375    let address = IotaAddress::from_str(tokens[0].trim())?;
376    Ok((address, (tx_seq, event_seq)))
377}