quantus_cli/cli/
preimage.rs

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