hdp_cli/
interactive.rs

1use alloy::{primitives::U256, transports::http::reqwest::Url};
2use anyhow::bail;
3use hdp::preprocessor::module_registry::ModuleRegistry;
4use hdp::primitives::{
5    aggregate_fn::{integer::Operator, FunctionContext},
6    task::{
7        datalake::{
8            block_sampled::{
9                AccountField, BlockSampledCollection, BlockSampledCollectionType,
10                BlockSampledDatalake, HeaderField,
11            },
12            compute::Computation,
13            datalake_type::DatalakeType,
14            envelope::DatalakeEnvelope,
15            transactions::{
16                IncludedTypes, TransactionField, TransactionReceiptField, TransactionsCollection,
17                TransactionsCollectionType, TransactionsInBlockDatalake,
18            },
19            DatalakeCompute,
20        },
21        TaskEnvelope,
22    },
23};
24use inquire::{InquireError, Select};
25use std::{path::PathBuf, str::FromStr};
26use tracing::error;
27
28use crate::{common::handle_running_tasks, config::Config};
29
30pub async fn run_interactive() -> anyhow::Result<()> {
31    println!("Welcome to Herodotus Data Processor interactive CLI! 🛰️");
32    println!(
33        r"
34        _   _   ____    ____  
35        | | | | |  _ \  |  _ \ 
36        | |_| | | | | | | |_) |
37        |  _  | | |_| | |  __/ 
38        |_| |_| |____/  |_|    
39"
40    );
41
42    let variants = TaskEnvelope::variants();
43    let task_opts: Vec<&str> = variants.iter().map(AsRef::as_ref).collect();
44    let task_ans: &str =
45        Select::new("Step 1. What type of task you want to process?", task_opts).with_help_message("1. Datalake Compute : You can use defined type of datalake and computation functions to compute data. More easier to use, more faster to compute.\n 2. Module : Define your own logic of data and computation over cairo contract. More flexible, but more difficult to use.").prompt()?;
46
47    let tasks = match task_ans {
48        "DATALAKE_COMPUTE" => {
49            let variants = DatalakeType::variants();
50            let datalake_opts: Vec<&str> = variants.iter().map(AsRef::as_ref).collect();
51
52            println!("Let's try to generate datalake and compute! ");
53
54            let ans: Result<&str, InquireError> =
55                Select::new("Step 2. What's your datalake type?", datalake_opts).prompt();
56
57            let datalake_envelope: DatalakeEnvelope = match ans {
58                Ok(choice) => {
59                    let datalake_type = DatalakeType::from_str(choice)?;
60                    match datalake_type {
61                        DatalakeType::BlockSampled => {
62                            // ================== Block Sampled Datalake Fields ==================
63                            // 0. Chain ID
64                            let chain_id: u64 = inquire::Text::new("Chain ID")
65                                .with_help_message("What is the chain ID? (Enter to set default)")
66                                .with_default("11155111")
67                                .prompt()?
68                                .parse()?;
69                            // 1. Block range start
70                            let block_range_start: u64 = inquire::Text::new("Block range start")
71                                .with_help_message(
72                                    "What is the block range start? (Enter to set default)",
73                                )
74                                .with_default("4952200")
75                                .prompt()?
76                                .parse()?;
77                            // 2. Block range end
78                            let block_range_end: u64 = inquire::Text::new("Block range end")
79                                .with_help_message(
80                                    "What is the block range end? (Enter to set default)",
81                                )
82                                .with_default("4952229")
83                                .prompt()?
84                                .parse()?;
85                            // 3. Increment
86                            let increment: u64 = inquire::Text::new("Increment")
87                                .with_help_message(
88                                    "How many blocks to skip in the range? (Enter to set default)",
89                                )
90                                .with_default("1")
91                                .prompt()?
92                                .parse()?;
93                            // 4. Sampled Property
94                            // 4.1. Block Sampled Collection Type
95                            let variants: Vec<String> = BlockSampledCollectionType::variants();
96                            let collection_opts: Vec<&str> =
97                                variants.iter().map(AsRef::as_ref).collect();
98                            let collection_ans: &str = Select::new(
99                                "Sample Property: Select block sample type",
100                                collection_opts,
101                            )
102                            .with_help_message("What type of block sample do you want to process?")
103                            .prompt()?;
104                            let collection_type =
105                                BlockSampledCollectionType::from_str(collection_ans)?;
106                            // 4.2. Detail Sampled Property
107                            let sampled_property = match collection_type {
108                                BlockSampledCollectionType::Header => {
109                                    let variants: Vec<String> = HeaderField::variants();
110                                    let header_opts: Vec<&str> =
111                                        variants.iter().map(AsRef::as_ref).collect();
112                                    let header_ans: &str =
113                                        Select::new("Select detail header property", header_opts).with_help_message("What header property do you want to sample? (all properties are decodable from rlp encoded data)")
114                                            .prompt()?;
115                                    format!("header.{}", header_ans)
116                                }
117                                BlockSampledCollectionType::Account => {
118                                    let address = inquire::Text::new("Enter target address")
119                                        .with_help_message("Enter target address")
120                                        .prompt()?;
121                                    let variants: Vec<String> = AccountField::variants();
122                                    let account_opts: Vec<&str> =
123                                        variants.iter().map(AsRef::as_ref).collect();
124                                    let account_ans: &str =
125                                        Select::new("Select detail account property", account_opts)
126                                        .with_help_message("What account property do you want to sample? (all properties are decodable from rlp encoded data)")
127                                            .prompt()?;
128                                    format!("account.{}.{}", address, account_ans)
129                                }
130                                BlockSampledCollectionType::Storage => {
131                                    let address = inquire::Text::new("Enter target address")
132                                        .with_help_message("Enter target address")
133                                        .prompt()?;
134                                    let storage_key =
135                                        inquire::Text::new("Enter target storage key")
136                                            .with_help_message("Enter the storage key")
137                                            .prompt()?;
138                                    format!("storage.{}.{}", address, storage_key)
139                                }
140                            };
141                            let block_sampled_datalake = BlockSampledDatalake::new(
142                                chain_id,
143                                block_range_start,
144                                block_range_end,
145                                increment,
146                                BlockSampledCollection::from_str(&sampled_property)?,
147                            );
148                            DatalakeEnvelope::BlockSampled(block_sampled_datalake)
149                        }
150                        DatalakeType::TransactionsInBlock => {
151                            // 0. Chain ID
152                            let chain_id: u64 = inquire::Text::new("Chain ID")
153                                .with_help_message("What is the chain ID? (Enter to set default)")
154                                .with_default("11155111")
155                                .prompt()?
156                                .parse()?;
157                            let target_block: u64 = inquire::Text::new("Enter target block number")
158                                .with_help_message(
159                                    "What block you target to get transactions? (Enter to set default)",
160                                )
161                                .with_default("4952200")
162                                .prompt()?
163                                .parse()?;
164                            let start_index: u64 = inquire::Text::new("Start index")
165                            .with_help_message(
166                                "What is the start index of transactions in the block? (Enter to set default)",
167                            )
168                            .with_default("0")
169                            .prompt()?
170                            .parse()?;
171                            // TODO: have end index dynamically fetch by block number
172                            let end_index: u64 = inquire::Text::new("End index")
173                            .with_help_message(
174                                "What is the end index of transactions in the block? (Enter to set default)",
175                            )
176                            .with_default("10")
177                            .prompt()?
178                            .parse()?;
179                            let increment: u64 = inquire::Text::new("Increment")
180                                .with_help_message(
181                                    "How many transactions to skip in the range? (Enter to set default)",
182                                )
183                                .with_default("1")
184                                .prompt()?
185                                .parse()?;
186                            let included_types: Vec<u8> = inquire::Text::new("Included types")
187                                .with_help_message(
188                                    "What type of transactions to include? (Enter to set default)",
189                                )
190                                .with_default("1,1,1,1")
191                                .prompt()?
192                                .split(',')
193                                .map(|s| s.parse().unwrap())
194                                .collect();
195                            let variants = TransactionsCollectionType::variants();
196                            let collection_opts: Vec<&str> =
197                                variants.iter().map(AsRef::as_ref).collect();
198                            let collection_ans: &str = Select::new(
199                                "Sample Property: Select block sample type",
200                                collection_opts,
201                            )
202                            .with_help_message("What type of block sample do you want to process?")
203                            .prompt()?;
204                            let collection_type =
205                                TransactionsCollectionType::from_str(collection_ans)?;
206                            let sampled_property = match collection_type {
207                                TransactionsCollectionType::Transactions => {
208                                    let variants: Vec<String> = TransactionField::variants();
209                                    let transaction_opts: Vec<&str> =
210                                        variants.iter().map(AsRef::as_ref).collect();
211                                    let transaction_ans: &str =
212                                        Select::new("Select detail transaction property", transaction_opts)
213                                        .with_help_message("What transaction property do you want to sample? (all properties are decodable from rlp encoded data)")
214                                            .prompt()?;
215                                    format!("tx.{}", transaction_ans)
216                                }
217                                TransactionsCollectionType::TransactionReceipts => {
218                                    let variants = TransactionReceiptField::variants();
219                                    let transaction_receipt_opts: Vec<&str> =
220                                        variants.iter().map(AsRef::as_ref).collect();
221                                    let transaction_receipt_ans: &str =
222                                        Select::new("Select detail transaction receipt property", transaction_receipt_opts)
223                                        .with_help_message("What transaction receipt property do you want to sample? (all properties are decodable from rlp encoded data)")
224                                            .prompt()?;
225                                    format!("tx_receipt.{}", transaction_receipt_ans)
226                                }
227                            };
228                            let transactions_datalake = TransactionsInBlockDatalake::new(
229                                chain_id,
230                                target_block,
231                                TransactionsCollection::from_str(&sampled_property)?,
232                                start_index,
233                                end_index,
234                                increment,
235                                IncludedTypes::from(&included_types),
236                            );
237                            DatalakeEnvelope::TransactionsInBlock(transactions_datalake)
238                        }
239                    }
240                }
241                Err(e) => {
242                    error!("Error: {:?}", e);
243                    bail!(e);
244                }
245            };
246
247            let task_opts: Vec<&str> = vec!["AVG", "SUM", "MIN", "MAX", "COUNT", "SLR"];
248
249            let aggregate_fn_id = Select::new("Select the aggregation function", task_opts)
250                .with_help_message(
251                    "Step 3. What type of aggregation do you want to perform on the datalake?",
252                )
253                .prompt()?;
254
255            let aggregate_fn_ctx = match aggregate_fn_id {
256                "COUNT" => {
257                    let operator_ans: String = Select::new(
258                        "Select the COURNIF operator",
259                        vec!["=", "!=", ">", ">=", "<", "<="],
260                    )
261                    .with_help_message("How would like to set opersation case?")
262                    .prompt()?
263                    .into();
264                    let value_to_compare: String = inquire::Text::new("Enter the value to compare")
265                        .with_help_message("Make sure to input Uint256 value")
266                        .prompt()?;
267                    Some(FunctionContext::new(
268                        Operator::from_symbol(&operator_ans)?,
269                        U256::from_str(&value_to_compare)?,
270                    ))
271                }
272                "SLR" => {
273                    let target_index: String =
274                        inquire::Text::new("Enter the target index to compute SLR")
275                            .with_help_message("We will get y index over provided this x index")
276                            .prompt()?;
277                    Some(FunctionContext::new(
278                        Operator::None,
279                        U256::from_str(&target_index)?,
280                    ))
281                }
282                _ => None,
283            };
284
285            let target_datalake_compute = DatalakeCompute::new(
286                datalake_envelope,
287                Computation::new(aggregate_fn_id.parse()?, aggregate_fn_ctx),
288            );
289            vec![TaskEnvelope::DatalakeCompute(target_datalake_compute)]
290        }
291        "MODULE" => {
292            println!("Let's try to generate module! ");
293            let module_program_hash: String =
294                inquire::Text::new("Enter Program Hash of Module. Make sure you had registered:")
295                    .with_default(
296                        "0x064041a339b1edd10de83cf031cfa938645450f971d2527c90d4c2ce68d7d412",
297                    )
298                    .prompt()?;
299            let module_inputs: Vec<String> = inquire::Text::new(
300                "Enter Module inputs. Split them using comma : ",
301            )
302            .with_help_message(
303                "This values will goes through Cairo Contract's input. (Enter to set default)",
304            )
305            .with_default("0x5222a4,0x13cb6ae34a13a0977f4d7101ebc24b87bb23f0d5")
306            .prompt()?
307            .split(',')
308            .map(|s| s.parse().unwrap())
309            .collect();
310
311            let module_registry = ModuleRegistry::new();
312            let module = module_registry
313                .get_extended_module_from_class_source_string(
314                    Some(module_program_hash),
315                    None,
316                    module_inputs,
317                )
318                .await?;
319
320            vec![TaskEnvelope::Module(module)]
321        }
322        _ => {
323            bail!("Invalid task");
324        }
325    };
326
327    let allow_run: bool = inquire::Confirm::new("Do you want to run the full processor? Running the processor will generate input for Cairo program and PIE file")
328    .with_default(true)
329    .prompt()?;
330    if allow_run {
331        let rpc_url: Option<Url> = match inquire::Text::new("Enter RPC URL: ")
332            .with_help_message("Skip if you have it in your .env file")
333            .prompt()
334        {
335            Ok(url) => match url.as_str() {
336                "" => None,
337                _ => Some(url.parse()?),
338            },
339            Err(_) => None,
340        };
341        let chain_id: Option<u64> = match inquire::Text::new("Enter Chain ID: ")
342            .with_help_message("Skip if you have it in your .env file")
343            .prompt()
344        {
345            Ok(chain_id) => match chain_id.as_str() {
346                "" => None,
347                _ => Some(chain_id.parse()?),
348            },
349            Err(_) => None,
350        };
351        let config = Config::init(rpc_url, chain_id, None, None, None).await;
352        let output_file: PathBuf = inquire::Text::new("Enter Output file path: ")
353            .with_default("output.json")
354            .prompt()?
355            .into();
356        let cairo_input: PathBuf = inquire::Text::new("Enter Cairo input file path:")
357            .with_default("input.json")
358            .prompt()?
359            .into();
360        let pie_file: PathBuf = inquire::Text::new("Enter PIE output file path:")
361            .with_default("hdp_pie.zip")
362            .prompt()?
363            .into();
364
365        handle_running_tasks(
366            config,
367            tasks,
368            Some(output_file),
369            Some(cairo_input),
370            Some(pie_file),
371        )
372        .await?
373    }
374    Ok(())
375}