Skip to main content

quantus_cli/cli/
preimage.rs

1//! `quantus preimage` subcommand - preimage operations
2use crate::{chain::quantus_subxt, error::QuantusError, log_error, log_print, log_verbose};
3use clap::Subcommand;
4use colored::Colorize;
5use std::str::FromStr;
6use subxt::utils::H256;
7
8/// Preimage operations
9#[derive(Subcommand, Debug)]
10pub enum PreimageCommands {
11	/// Check if a preimage exists and get its status
12	#[command(name = "status")]
13	Status {
14		/// Preimage hash (hex format)
15		#[arg(long)]
16		hash: String,
17	},
18	/// Get preimage content
19	#[command(name = "get")]
20	Get {
21		/// Preimage hash (hex format)
22		#[arg(long)]
23		hash: String,
24		/// Preimage length (required for retrieval)
25		#[arg(long)]
26		len: u32,
27	},
28	/// List all preimages
29	#[command(name = "list")]
30	List,
31	/// Request a preimage (no deposit required)
32	#[command(name = "request")]
33	Request {
34		/// Preimage hash (hex format)
35		#[arg(long)]
36		hash: String,
37		/// Wallet to use for the request
38		#[arg(long)]
39		from: String,
40	},
41	/// Note a preimage (requires deposit)
42	#[command(name = "note")]
43	Note {
44		/// Preimage content (hex format)
45		#[arg(long)]
46		content: String,
47		/// Wallet to use for the note
48		#[arg(long)]
49		from: String,
50	},
51	/// Create a preimage from WASM file (like in tech-referenda)
52	#[command(name = "create")]
53	Create {
54		/// WASM file path
55		#[arg(long)]
56		wasm_file: std::path::PathBuf,
57		/// Wallet to use for the preimage
58		#[arg(long)]
59		from: String,
60		/// Password for wallet (optional)
61		#[arg(long)]
62		password: Option<String>,
63		/// Password file path (optional)
64		#[arg(long)]
65		password_file: Option<String>,
66	},
67}
68
69/// Handle preimage commands
70pub async fn handle_preimage_command(
71	command: PreimageCommands,
72	node_url: &str,
73	execution_mode: crate::cli::common::ExecutionMode,
74) -> crate::error::Result<()> {
75	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
76
77	match command {
78		PreimageCommands::Status { hash } => {
79			check_preimage_status(&quantus_client, &hash).await?;
80		},
81		PreimageCommands::Get { hash, len } => {
82			get_preimage_content(&quantus_client, &hash, len).await?;
83		},
84		PreimageCommands::List => {
85			list_preimages(&quantus_client).await?;
86		},
87		PreimageCommands::Request { hash, from } => {
88			request_preimage(&quantus_client, &hash, &from, execution_mode).await?;
89		},
90		PreimageCommands::Note { content, from } => {
91			note_preimage(&quantus_client, &content, &from, execution_mode).await?;
92		},
93		PreimageCommands::Create { wasm_file, from, password, password_file } => {
94			create_preimage(
95				&quantus_client,
96				wasm_file,
97				&from,
98				password,
99				password_file,
100				execution_mode,
101			)
102			.await?;
103		},
104	}
105
106	Ok(())
107}
108
109/// Check preimage status
110async fn check_preimage_status(
111	quantus_client: &crate::chain::client::QuantusClient,
112	hash_str: &str,
113) -> crate::error::Result<()> {
114	let preimage_hash = parse_hash(hash_str)?;
115
116	log_print!("🔍 Checking preimage status for hash: {}", hash_str.bright_cyan());
117
118	let latest_block_hash = quantus_client.get_latest_block().await?;
119	let storage_at = quantus_client.client().storage().at(latest_block_hash);
120
121	// Check StatusFor (old format)
122	let status_addr = quantus_subxt::api::storage().preimage().status_for(preimage_hash);
123	let status_result = storage_at.fetch(&status_addr).await;
124
125	// Check RequestStatusFor (new format)
126	let request_status_addr =
127		quantus_subxt::api::storage().preimage().request_status_for(preimage_hash);
128	let request_status_result = storage_at.fetch(&request_status_addr).await;
129
130	log_print!("📊 Preimage Status Results:");
131	log_print!("   🔗 Hash: {}", hash_str.bright_yellow());
132
133	match status_result {
134		Ok(Some(status)) => {
135			log_print!("   📋 StatusFor (Old): {:?}", status);
136		},
137		Ok(None) => {
138			log_print!("   📋 StatusFor (Old): Not found");
139		},
140		Err(e) => {
141			log_print!("   📋 StatusFor (Old): Error - {:?}", e);
142		},
143	}
144
145	match request_status_result {
146		Ok(Some(request_status)) => {
147			log_print!("   📋 RequestStatusFor (New): {:?}", request_status);
148		},
149		Ok(None) => {
150			log_print!("   📋 RequestStatusFor (New): Not found");
151		},
152		Err(e) => {
153			log_print!("   📋 RequestStatusFor (New): Error - {:?}", e);
154		},
155	}
156
157	// Check if preimage content exists (we need to know the length)
158	// For now, we'll try with a reasonable length
159	let preimage_addr =
160		quantus_subxt::api::storage().preimage().preimage_for((preimage_hash, 0u32));
161	let preimage_result = storage_at.fetch(&preimage_addr).await;
162
163	match preimage_result {
164		Ok(Some(_)) => {
165			log_print!("   📦 PreimageFor: Content exists (length 0)");
166		},
167		Ok(None) => {
168			log_print!("   📦 PreimageFor: No content found (length 0)");
169		},
170		Err(e) => {
171			log_print!("   📦 PreimageFor: Error - {:?}", e);
172		},
173	}
174
175	Ok(())
176}
177
178/// Get preimage content
179async fn get_preimage_content(
180	quantus_client: &crate::chain::client::QuantusClient,
181	hash_str: &str,
182	len: u32,
183) -> crate::error::Result<()> {
184	let preimage_hash = parse_hash(hash_str)?;
185
186	log_print!("📦 Getting preimage content for hash: {}", hash_str.bright_cyan());
187	log_print!("   📏 Length: {} bytes", len);
188
189	let latest_block_hash = quantus_client.get_latest_block().await?;
190	let storage_at = quantus_client.client().storage().at(latest_block_hash);
191
192	let preimage_addr = quantus_subxt::api::storage().preimage().preimage_for((preimage_hash, len));
193	let preimage_result = storage_at.fetch(&preimage_addr).await;
194
195	match preimage_result {
196		Ok(Some(bounded_vec)) => {
197			log_print!("✅ Preimage content found!");
198			log_print!("   📏 Actual length: {} bytes", bounded_vec.0.len());
199
200			// Convert to Vec<u8> for display
201			let content: Vec<u8> = bounded_vec.0;
202
203			// Show first 100 bytes as hex
204			let preview_len = std::cmp::min(100, content.len());
205			let preview = &content[..preview_len];
206			log_print!("   🔍 Preview (first {} bytes):", preview_len);
207			log_print!("      {}", hex::encode(preview).bright_green());
208
209			if content.len() > preview_len {
210				log_print!("   ... ({} more bytes)", content.len() - preview_len);
211			}
212
213			// Try to decode as call data
214			log_verbose!("   🔧 Attempting to decode as call data...");
215			log_print!("   📝 Raw content preview (first 100 bytes):");
216			log_print!(
217				"      {}",
218				hex::encode(&content[..std::cmp::min(100, content.len())]).bright_green()
219			);
220		},
221		Ok(None) => {
222			log_error!("❌ Preimage content not found for hash {} with length {}", hash_str, len);
223		},
224		Err(e) => {
225			log_error!("❌ Error fetching preimage content: {:?}", e);
226		},
227	}
228
229	Ok(())
230}
231
232/// List all preimages
233async fn list_preimages(
234	quantus_client: &crate::chain::client::QuantusClient,
235) -> crate::error::Result<()> {
236	log_print!("📋 Listing all preimages...");
237
238	let latest_block_hash = quantus_client.get_latest_block().await?;
239	let storage_at = quantus_client.client().storage().at(latest_block_hash);
240
241	let mut preimage_count = 0;
242	let mut unrequested_count = 0;
243	let mut requested_count = 0;
244
245	// Iterate PreimageFor keys; extract (hash, len) from key_bytes and optionally fetch status
246	let preimage_for_addr = quantus_subxt::api::storage().preimage().preimage_for_iter();
247	let mut image_stream = storage_at.iter(preimage_for_addr).await.map_err(|e| {
248		QuantusError::Generic(format!("Failed to iterate preimage contents: {:?}", e))
249	})?;
250
251	while let Some(result) = image_stream.next().await {
252		match result {
253			Ok(entry) => {
254				let key = entry.key_bytes;
255				if key.len() >= 36 {
256					let len_le = &key[key.len() - 4..];
257					let len = u32::from_le_bytes([len_le[0], len_le[1], len_le[2], len_le[3]]);
258					let hash = sp_core::H256::from_slice(&key[key.len() - 36..key.len() - 4]);
259
260					let status = storage_at
261						.fetch(&quantus_subxt::api::storage().preimage().request_status_for(hash))
262						.await
263						.ok()
264						.flatten();
265
266					preimage_count += 1;
267					match status {
268						Some(quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Unrequested { ticket: _, len: status_len }) => {
269							unrequested_count += 1;
270							log_print!("   🔗 {} (Unrequested, {} bytes)", hash, status_len);
271						},
272						Some(quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Requested { maybe_ticket: _, count, maybe_len }) => {
273							requested_count += 1;
274							let len_str = match maybe_len { Some(l) => format!("{} bytes", l), None => format!("{} bytes (from key)", len) };
275							log_print!("   🔗 {} (Requested, count: {}, {})", hash, count, len_str);
276						},
277						None => {
278							log_print!("   🔗 {} (Unknown status, {} bytes)", hash, len);
279						},
280					}
281				}
282			},
283			Err(e) => log_verbose!("⚠️  Error reading preimage content entry: {:?}", e),
284		}
285	}
286
287	log_print!("");
288	log_print!("📊 Preimage Summary:");
289	log_print!("   📋 Total preimages: {}", preimage_count);
290	log_print!("   📝 Unrequested: {}", unrequested_count);
291	log_print!("   📋 Requested: {}", requested_count);
292
293	if preimage_count == 0 {
294		log_print!("   💡 No preimages found on chain");
295	}
296
297	Ok(())
298}
299
300/// Request a preimage (no deposit required)
301async fn request_preimage(
302	quantus_client: &crate::chain::client::QuantusClient,
303	hash_str: &str,
304	from_str: &str,
305	execution_mode: crate::cli::common::ExecutionMode,
306) -> crate::error::Result<()> {
307	let preimage_hash = parse_hash(hash_str)?;
308
309	log_print!("🚀 Requesting preimage for hash: {}", hash_str.bright_cyan());
310	log_print!("   👤 From: {}", from_str.bright_yellow());
311
312	// Load wallet keypair
313	let keypair = crate::wallet::load_keypair_from_wallet(from_str, None, None)?;
314
315	// Create request_preimage call
316	let request_call = quantus_subxt::api::tx().preimage().request_preimage(preimage_hash);
317
318	// Submit transaction
319	let tx_hash = crate::cli::common::submit_transaction(
320		quantus_client,
321		&keypair,
322		request_call,
323		None,
324		execution_mode,
325	)
326	.await?;
327	log_print!("✅ Preimage request transaction submitted: {:?}", tx_hash);
328
329	// Wait for confirmation
330	log_print!("⏳ Waiting for preimage request confirmation...");
331	log_print!("✅ Preimage request confirmed!");
332
333	Ok(())
334}
335
336/// Note a preimage (requires deposit)
337async fn note_preimage(
338	quantus_client: &crate::chain::client::QuantusClient,
339	content_str: &str,
340	from_str: &str,
341	execution_mode: crate::cli::common::ExecutionMode,
342) -> crate::error::Result<()> {
343	let content = hex::decode(content_str.trim_start_matches("0x"))
344		.map_err(|e| QuantusError::Generic(format!("Invalid hex content: {}", e)))?;
345
346	log_print!("📝 Noting preimage for content length: {} bytes", content.len());
347	log_print!("   👤 From: {}", from_str.bright_yellow());
348
349	// Load wallet keypair
350	let keypair = crate::wallet::load_keypair_from_wallet(from_str, None, None)?;
351
352	// Create note_preimage call
353	let note_call = quantus_subxt::api::tx().preimage().note_preimage(content);
354
355	// Submit transaction
356	let tx_hash = crate::cli::common::submit_transaction(
357		quantus_client,
358		&keypair,
359		note_call,
360		None,
361		execution_mode,
362	)
363	.await?;
364	log_print!("✅ Preimage note transaction submitted: {:?}", tx_hash);
365
366	// Wait for confirmation
367	log_print!("⏳ Waiting for preimage note confirmation...");
368	log_print!("✅ Preimage note confirmed!");
369
370	Ok(())
371}
372
373/// Create a preimage from WASM file (like in tech-referenda)
374async fn create_preimage(
375	quantus_client: &crate::chain::client::QuantusClient,
376	wasm_file: std::path::PathBuf,
377	from_str: &str,
378	password: Option<String>,
379	password_file: Option<String>,
380	execution_mode: crate::cli::common::ExecutionMode,
381) -> crate::error::Result<()> {
382	use qp_poseidon::PoseidonHasher;
383
384	log_print!("📦 Creating preimage from WASM file: {}", wasm_file.display());
385	log_print!("   👤 From: {}", from_str.bright_yellow());
386
387	if !wasm_file.exists() {
388		return Err(QuantusError::Generic(format!("WASM file not found: {}", wasm_file.display())));
389	}
390
391	// Read WASM file
392	let wasm_code = std::fs::read(&wasm_file)
393		.map_err(|e| QuantusError::Generic(format!("Failed to read WASM file: {}", e)))?;
394
395	log_print!("📊 WASM file size: {} bytes", wasm_code.len());
396
397	// Load wallet keypair
398	let keypair = crate::wallet::load_keypair_from_wallet(from_str, password, password_file)?;
399
400	// Build a static payload for System::set_code and encode full call data (pallet + call + args)
401	let set_code_payload = quantus_subxt::api::tx().system().set_code(wasm_code.clone());
402	let metadata = quantus_client.client().metadata();
403	let encoded_call = <_ as subxt::tx::Payload>::encode_call_data(&set_code_payload, &metadata)
404		.map_err(|e| QuantusError::Generic(format!("Failed to encode call data: {:?}", e)))?;
405
406	log_verbose!("📝 Encoded call size: {} bytes", encoded_call.len());
407
408	// Compute preimage hash using Poseidon (runtime uses PoseidonHasher)
409	let preimage_hash: sp_core::H256 =
410		<PoseidonHasher as sp_runtime::traits::Hash>::hash(&encoded_call);
411
412	log_print!("🔗 Preimage hash: {:?}", preimage_hash);
413
414	// Submit Preimage::note_preimage with bounded bytes
415	type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes;
416	let bounded_bytes: PreimageBytes = encoded_call.clone();
417
418	log_print!("📝 Submitting preimage...");
419	let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes);
420	let preimage_tx_hash = crate::cli::common::submit_transaction(
421		quantus_client,
422		&keypair,
423		note_preimage_tx,
424		None,
425		execution_mode,
426	)
427	.await?;
428	log_print!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash);
429
430	// Wait for preimage transaction confirmation
431	log_print!("⏳ Waiting for preimage transaction confirmation...");
432	log_print!("✅ Preimage transaction confirmed!");
433
434	log_print!("🎯 Preimage created successfully!");
435	log_print!("   🔗 Hash: {:?}", preimage_hash);
436	log_print!("   📏 Size: {} bytes", encoded_call.len());
437
438	Ok(())
439}
440
441/// Parse hash string to H256
442fn parse_hash(hash_str: &str) -> crate::error::Result<H256> {
443	let hash_str = hash_str.trim_start_matches("0x");
444	H256::from_str(hash_str).map_err(|e| {
445		QuantusError::Generic(format!("Invalid hash format: {}. Expected 64 hex characters", e))
446	})
447}