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