sui_tool/db_tool/
index_search.rs

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