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 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 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 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 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 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 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 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 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}