1use alloy::{primitives::ChainId, transports::http::reqwest::Url};
2use anyhow::Result;
3use hdp::preprocessor::{
4 compile::config::CompilerConfig, module_registry::ModuleRegistry, PreProcessor,
5};
6use hdp::primitives::{
7 aggregate_fn::{AggregationFunction, FunctionContext},
8 processed_types::cairo_format::AsCairoFormat,
9 task::{
10 datalake::{
11 block_sampled::BlockSampledDatalake, compute::Computation, envelope::DatalakeEnvelope,
12 transactions::TransactionsInBlockDatalake, DatalakeCompute,
13 },
14 TaskEnvelope,
15 },
16};
17use std::{env, fs, path::PathBuf};
18use tracing_subscriber::{EnvFilter, FmtSubscriber};
19
20use clap::Parser;
21use hdp::processor::Processor;
22
23use tracing::{debug, info};
24
25use crate::{
26 commands::{DataLakeCommands, HDPCli, HDPCliCommands},
27 config::Config,
28 interactive,
29 query::{SubmitBatchQuery, Task},
30};
31
32pub async fn run() -> anyhow::Result<()> {
33 let start_run = std::time::Instant::now();
34 let cli = init_cli()?;
35 match cli.command {
36 HDPCliCommands::Start => {
37 interactive::run_interactive().await?;
38 }
39 HDPCliCommands::RunDatalake {
40 rpc_url,
41 chain_id,
42 preprocessor_output_file,
43 sound_run_cairo_file,
44 output_file,
45 cairo_pie_file,
46 aggregate_fn_id,
47 aggregate_fn_ctx,
48 datalake,
49 } => {
50 datalake_entry_run(
51 aggregate_fn_id,
52 aggregate_fn_ctx,
53 datalake,
54 rpc_url,
55 chain_id,
56 preprocessor_output_file,
57 sound_run_cairo_file,
58 output_file,
59 cairo_pie_file,
60 )
61 .await?
62 }
63
64 HDPCliCommands::RunModule {
65 program_hash,
66 local_class_path,
67 save_fetch_keys_file,
68 module_inputs,
69 rpc_url,
70 chain_id,
71 dry_run_cairo_file,
72 preprocessor_output_file,
73 sound_run_cairo_file,
74 output_file,
75 cairo_pie_file,
76 } => {
77 module_entry_run(
78 program_hash,
79 local_class_path,
80 save_fetch_keys_file,
81 module_inputs,
82 rpc_url,
83 chain_id,
84 dry_run_cairo_file,
85 preprocessor_output_file,
86 sound_run_cairo_file,
87 output_file,
88 cairo_pie_file,
89 )
90 .await?;
91 }
92 HDPCliCommands::Run {
93 request_file,
94 rpc_url,
95 dry_run_cairo_file,
96 preprocessor_output_file,
97 sound_run_cairo_file,
98 output_file,
99 cairo_pie_file,
100 } => {
101 entry_run(
102 request_file,
103 rpc_url,
104 dry_run_cairo_file,
105 preprocessor_output_file,
106 sound_run_cairo_file,
107 output_file,
108 cairo_pie_file,
109 )
110 .await?;
111 }
112 }
113 let duration_run = start_run.elapsed();
114 info!("HDP Cli Finished in: {:?}", duration_run);
115 Ok(())
116}
117
118fn init_cli() -> Result<HDPCli> {
120 let rust_log = env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string());
121 let subscriber = FmtSubscriber::builder()
122 .with_env_filter(EnvFilter::new(&rust_log))
123 .finish();
124 tracing::subscriber::set_global_default(subscriber).expect("Failed to set subscriber");
125 debug!("running on log level: {}", rust_log);
126 let cli = HDPCli::parse();
127 dotenv::dotenv().ok();
128 Ok(cli)
129}
130
131#[allow(clippy::too_many_arguments)]
132pub async fn module_entry_run(
133 class_hash: Option<String>,
134 local_class_path: Option<PathBuf>,
135 save_fetch_keys_file: Option<PathBuf>,
136 module_inputs: Vec<String>,
137 rpc_url: Option<Url>,
138 chain_id: Option<ChainId>,
139 dry_run_cairo_file: Option<PathBuf>,
140 preprocessor_output_file: Option<PathBuf>,
141 sound_run_cairo_file: Option<PathBuf>,
142 output_file: Option<PathBuf>,
143 cairo_pie_file: Option<PathBuf>,
144) -> Result<()> {
145 let config = Config::init(
146 rpc_url,
147 chain_id,
148 dry_run_cairo_file,
149 sound_run_cairo_file,
150 save_fetch_keys_file,
151 )
152 .await;
153 let module_registry = ModuleRegistry::new();
154 let module = module_registry
155 .get_extended_module_from_class_source_string(class_hash, local_class_path, module_inputs)
156 .await?;
157 let tasks = vec![TaskEnvelope::Module(module)];
159
160 handle_running_tasks(
161 config,
162 tasks,
163 preprocessor_output_file,
164 output_file,
165 cairo_pie_file,
166 )
167 .await?;
168 Ok(())
169}
170
171#[allow(clippy::too_many_arguments)]
172pub async fn datalake_entry_run(
173 aggregate_fn_id: AggregationFunction,
174 aggregate_fn_ctx: Option<FunctionContext>,
175 datalake: DataLakeCommands,
176 rpc_url: Option<Url>,
177 chain_id: Option<ChainId>,
178 pre_processor_output: Option<PathBuf>,
179 sound_run_cairo_file: Option<PathBuf>,
180 output_file: Option<PathBuf>,
181 cairo_pie_file: Option<PathBuf>,
182) -> Result<()> {
183 let config = Config::init(rpc_url, chain_id, None, sound_run_cairo_file, None).await;
184 let parsed_datalake = match datalake {
185 DataLakeCommands::BlockSampled {
186 block_range_start,
187 block_range_end,
188 sampled_property,
189 increment,
190 } => DatalakeEnvelope::BlockSampled(BlockSampledDatalake::new(
191 11155111,
192 block_range_start,
193 block_range_end,
194 increment,
195 sampled_property,
196 )),
197 DataLakeCommands::TransactionsInBlock {
198 target_block,
199 sampled_property,
200 start_index,
201 end_index,
202 increment,
203 included_types,
204 } => DatalakeEnvelope::TransactionsInBlock(TransactionsInBlockDatalake::new(
205 11155111,
206 target_block,
207 sampled_property,
208 start_index,
209 end_index,
210 increment,
211 included_types,
212 )),
213 };
214 let tasks = vec![TaskEnvelope::DatalakeCompute(DatalakeCompute::new(
215 parsed_datalake,
216 Computation::new(aggregate_fn_id, aggregate_fn_ctx),
217 ))];
218
219 handle_running_tasks(
220 config,
221 tasks,
222 pre_processor_output,
223 output_file,
224 cairo_pie_file,
225 )
226 .await?;
227 Ok(())
228}
229
230pub async fn handle_running_tasks(
231 config: &Config,
232 tasks: Vec<TaskEnvelope>,
233 pre_processor_output_file: Option<PathBuf>,
234 output_file: Option<PathBuf>,
235 cairo_pie_file: Option<PathBuf>,
236) -> Result<()> {
237 let compiler_config = CompilerConfig {
238 dry_run_program_path: config.dry_run_program_path.clone(),
239 provider_config: config.evm_provider.clone(),
240 save_fetch_keys_file: config.save_fetch_keys_file.clone(),
241 };
242 let preprocessor = PreProcessor::new_with_config(compiler_config);
243 let preprocessor_result = preprocessor.process(tasks).await?;
244
245 if pre_processor_output_file.is_none() {
246 info!("Finished pre processing the data");
247 Ok(())
248 } else {
249 let input_string = serde_json::to_string_pretty(&preprocessor_result.as_cairo_format())
250 .map_err(|e| anyhow::anyhow!("Failed to serialize preprocessor result: {}", e))?;
251 if let Some(input_file_path) = pre_processor_output_file {
252 fs::write(&input_file_path, input_string)
253 .map_err(|e| anyhow::anyhow!("Unable to write input file: {}", e))?;
254 info!(
255 "Finished pre processing the data, saved the input file in {}",
256 input_file_path.display()
257 );
258 if output_file.is_none() && cairo_pie_file.is_none() {
259 Ok(())
260 } else {
261 info!("Starting processing the data... ");
262 let output_file_path = output_file
263 .ok_or_else(|| anyhow::anyhow!("Output file path should be specified"))?;
264 let pie_file_path = cairo_pie_file
265 .ok_or_else(|| anyhow::anyhow!("PIE path should be specified"))?;
266 let processor = Processor::new(config.sound_run_program_path.clone());
267 let processor_result = processor
268 .process(preprocessor_result, &pie_file_path)
269 .await?;
270 fs::write(
271 &output_file_path,
272 serde_json::to_string_pretty(&processor_result).map_err(|e| {
273 anyhow::anyhow!("Failed to serialize processor result: {}", e)
274 })?,
275 )
276 .map_err(|e| anyhow::anyhow!("Unable to write output file: {}", e))?;
277
278 info!(
279 "Finished processing the data, saved the output file in {} and pie file in {}",
280 output_file_path.display(),
281 pie_file_path.display()
282 );
283 Ok(())
284 }
285 } else {
286 Err(anyhow::anyhow!("Cairo input path should be specified"))
287 }
288 }
289}
290
291pub async fn entry_run(
292 request_file: PathBuf,
293 rpc_url: Option<Url>,
294 dry_run_cairo_file: Option<PathBuf>,
295 pre_processor_output_file: PathBuf,
296 sound_run_cairo_file: Option<PathBuf>,
297 output_file: Option<PathBuf>,
298 cairo_pie_file: Option<PathBuf>,
299) -> Result<()> {
300 let request_context =
301 fs::read_to_string(request_file).expect("No request file exist in the path");
302 let parsed: SubmitBatchQuery = serde_json::from_str(&request_context)
303 .expect("Invalid format of request. Cannot parse it.");
304 let config = Config::init(
305 rpc_url,
306 Some(parsed.destination_chain_id),
307 dry_run_cairo_file,
308 sound_run_cairo_file,
309 None,
310 )
311 .await;
312 let module_registry = ModuleRegistry::new();
313 let mut task_envelopes = Vec::new();
314 for task in parsed.tasks {
315 match task {
316 Task::DatalakeCompute(task) => {
317 task_envelopes.push(TaskEnvelope::DatalakeCompute(task));
318 }
319 Task::Module(task) => {
320 let module = module_registry
321 .get_extended_module_from_class_source(
322 Some(task.program_hash),
323 None,
324 task.inputs,
325 )
326 .await?;
327 task_envelopes.push(TaskEnvelope::Module(module));
328 }
329 }
330 }
331 handle_running_tasks(
332 config,
333 task_envelopes,
334 Some(pre_processor_output_file),
335 output_file,
336 cairo_pie_file,
337 )
338 .await?;
339 Ok(())
340}