quantus_cli/cli/
runtime.rs

1//! `quantus runtime` subcommand - runtime management
2use crate::{
3	chain::quantus_subxt, error::QuantusError, log_print, log_success, log_verbose,
4	wallet::QuantumKeyPair,
5};
6use clap::Subcommand;
7use colored::Colorize;
8
9use crate::chain::client::ChainConfig;
10use std::{fs, path::PathBuf};
11use subxt::OnlineClient;
12
13#[derive(Subcommand, Debug)]
14pub enum RuntimeCommands {
15	/// Update the runtime using a WASM file (requires root permissions)
16	Update {
17		/// Path to the runtime WASM file
18		#[arg(short, long)]
19		wasm_file: PathBuf,
20
21		/// Wallet name to sign with (must have root/sudo permissions)
22		#[arg(short, long)]
23		from: String,
24
25		/// Password for the wallet
26		#[arg(short, long)]
27		password: Option<String>,
28
29		/// Read password from file
30		#[arg(long)]
31		password_file: Option<String>,
32
33		/// Force the update without confirmation
34		#[arg(long)]
35		force: bool,
36	},
37
38	/// Compare local WASM file with current runtime
39	Compare {
40		/// Path to the runtime WASM file to compare
41		#[arg(short, long)]
42		wasm_file: PathBuf,
43	},
44}
45
46/// Update runtime with sudo wrapper
47pub async fn update_runtime(
48	quantus_client: &crate::chain::client::QuantusClient,
49	wasm_code: Vec<u8>,
50	from_keypair: &QuantumKeyPair,
51	force: bool,
52	finalized: bool,
53) -> crate::error::Result<subxt::utils::H256> {
54	log_verbose!("🔄 Updating runtime...");
55
56	log_print!("📋 Current runtime version:");
57	log_print!("   • Use 'quantus system --runtime' to see current version");
58
59	// Show confirmation prompt unless force is used
60	if !force {
61		log_print!("");
62		log_print!(
63			"⚠️  {} {}",
64			"WARNING:".bright_red().bold(),
65			"Runtime update is a critical operation!"
66		);
67		log_print!("   • This will update the blockchain runtime immediately");
68		log_print!("   • All nodes will need to upgrade to stay in sync");
69		log_print!("   • This operation cannot be easily reversed");
70		log_print!("");
71
72		// Simple confirmation prompt
73		print!("Do you want to proceed with the runtime update? (yes/no): ");
74		use std::io::{self, Write};
75		io::stdout().flush().unwrap();
76
77		let mut input = String::new();
78		io::stdin().read_line(&mut input).unwrap();
79
80		if input.trim().to_lowercase() != "yes" {
81			log_print!("❌ Runtime update cancelled");
82			return Err(QuantusError::Generic("Runtime update cancelled".to_string()));
83		}
84	}
85
86	// Create the System::set_code call using RuntimeCall type alias
87	let set_code_call =
88		quantus_subxt::api::Call::System(quantus_subxt::api::system::Call::set_code {
89			code: wasm_code,
90		});
91
92	// Wrap with sudo for root permissions
93	let sudo_call = quantus_subxt::api::tx().sudo().sudo(set_code_call);
94
95	// Submit transaction
96	log_print!("📡 Submitting runtime update transaction...");
97	log_print!("⏳ This may take longer than usual due to WASM size...");
98
99	if !finalized {
100		log_print!(
101			"💡 Note: Waiting for best block (not finalized) due to PoW chain characteristics"
102		);
103	}
104
105	let tx_hash = crate::cli::common::submit_transaction(
106		quantus_client,
107		from_keypair,
108		sudo_call,
109		None,
110		finalized,
111	)
112	.await?;
113
114	log_success!(
115		"✅ SUCCESS Runtime update transaction submitted! Hash: 0x{}",
116		hex::encode(tx_hash)
117	);
118
119	Ok(tx_hash)
120}
121
122/// Runtime version information structure (internal use)
123#[derive(Debug, Clone)]
124pub struct RuntimeVersionInfo {
125	pub spec_version: u32,
126	pub impl_version: u32,
127	pub transaction_version: u32,
128}
129
130/// Get runtime version information (internal use)
131pub async fn get_runtime_version(
132	client: &OnlineClient<ChainConfig>,
133) -> crate::error::Result<RuntimeVersionInfo> {
134	log_verbose!("🔍 Getting runtime version...");
135
136	let runtime_version = client.runtime_version();
137
138	// SubXT RuntimeVersion only has spec_version and transaction_version
139	// We'll use defaults for missing fields
140	Ok(RuntimeVersionInfo {
141		spec_version: runtime_version.spec_version,
142		impl_version: 1, // Default impl version since not available in SubXT
143		transaction_version: runtime_version.transaction_version,
144	})
145}
146
147/// Calculate WASM file hash
148pub async fn calculate_wasm_hash(wasm_code: &[u8]) -> crate::error::Result<String> {
149	use sha2::{Digest, Sha256};
150	let mut hasher = Sha256::new();
151	hasher.update(wasm_code);
152	let local_hash = hasher.finalize();
153
154	Ok(format!("0x{}", hex::encode(local_hash)))
155}
156
157/// Handle runtime subxt command
158pub async fn handle_runtime_command(
159	command: RuntimeCommands,
160	node_url: &str,
161	finalized: bool,
162) -> crate::error::Result<()> {
163	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
164
165	match command {
166		RuntimeCommands::Update { wasm_file, from, password, password_file, force } => {
167			log_print!("🚀 Runtime Management");
168			log_print!("🔄 Runtime Update");
169			log_print!("   📂 WASM file: {}", wasm_file.display().to_string().bright_cyan());
170			log_print!("   🔑 Signed by: {}", from.bright_yellow());
171
172			// Check if WASM file exists
173			if !wasm_file.exists() {
174				return Err(QuantusError::Generic(format!(
175					"WASM file not found: {}",
176					wasm_file.display()
177				)));
178			}
179
180			// Check file extension
181			if let Some(ext) = wasm_file.extension() {
182				if ext != "wasm" {
183					log_print!("⚠️  Warning: File doesn't have .wasm extension");
184				}
185			}
186
187			// Load keypair
188			let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
189
190			// Read WASM file
191			log_verbose!("📖 Reading WASM file...");
192			let wasm_code = fs::read(&wasm_file)
193				.map_err(|e| QuantusError::Generic(format!("Failed to read WASM file: {e}")))?;
194
195			log_print!("📊 WASM file size: {} bytes", wasm_code.len());
196
197			// Update runtime
198			update_runtime(&quantus_client, wasm_code, &keypair, force, finalized).await?;
199
200			log_success!("🎉 Runtime update completed!");
201			log_print!(
202				"💡 Note: It may take a few moments for the new runtime version to be reflected."
203			);
204			log_print!("💡 Use 'quantus runtime check-version' to verify the new version.");
205
206			Ok(())
207		},
208
209		RuntimeCommands::Compare { wasm_file } => {
210			log_print!("🚀 Runtime Management");
211			log_print!("🔍 Comparing WASM file with current runtime...");
212			log_print!("   📂 Local file: {}", wasm_file.display().to_string().bright_cyan());
213
214			// Check if WASM file exists
215			if !wasm_file.exists() {
216				return Err(QuantusError::Generic(format!(
217					"WASM file not found: {}",
218					wasm_file.display()
219				)));
220			}
221
222			// Read local WASM file
223			let local_wasm = fs::read(&wasm_file)
224				.map_err(|e| QuantusError::Generic(format!("Failed to read WASM file: {e}")))?;
225
226			log_print!("📊 Local WASM size: {} bytes", local_wasm.len());
227
228			// Get current runtime version
229			let current_version = get_runtime_version(quantus_client.client()).await?;
230			log_print!("📋 Current chain runtime:");
231			log_print!("   • Spec version: {}", current_version.spec_version);
232			log_print!("   • Impl version: {}", current_version.impl_version);
233			log_print!("   • Transaction version: {}", current_version.transaction_version);
234
235			// Calculate hash of local file
236			let local_hash = calculate_wasm_hash(&local_wasm).await?;
237			log_print!("🔐 Local WASM SHA256: {}", local_hash.bright_blue());
238
239			// Try to get runtime hash from chain
240			if let Ok(Some(chain_runtime_hash)) = quantus_client.get_runtime_hash().await {
241				log_print!("🔐 Chain runtime hash: {}", chain_runtime_hash.bright_yellow());
242
243				// Compare hashes
244				if local_hash == chain_runtime_hash {
245					log_success!("✅ Runtime hashes match! The WASM file is identical to the current runtime.");
246				} else {
247					log_print!("⚠️  Runtime hashes differ. The WASM file is different from the current runtime.");
248				}
249			} else {
250				log_print!("💡 Chain runtime hash not available for comparison");
251			}
252
253			// Try to extract version from filename
254			let filename = wasm_file.file_name().unwrap().to_string_lossy();
255			log_verbose!("🔍 Parsing filename: {}", filename);
256
257			if let Some(version_str) = filename.split('-').nth(2) {
258				log_verbose!("🔍 Version part: {}", version_str);
259				if let Some(version_num) = version_str.split('.').next() {
260					log_verbose!("🔍 Version number: {}", version_num);
261					// Remove 'v' prefix if present
262					let clean_version = version_num.trim_start_matches('v');
263					log_verbose!("🔍 Clean version: {}", clean_version);
264					if let Ok(wasm_version) = clean_version.parse::<u32>() {
265						log_print!("📋 Version comparison:");
266						log_print!(
267							"   • Local WASM version: {}",
268							wasm_version.to_string().bright_green()
269						);
270						log_print!(
271							"   • Chain runtime version: {}",
272							current_version.spec_version.to_string().bright_yellow()
273						);
274
275						match wasm_version.cmp(&current_version.spec_version) {
276							std::cmp::Ordering::Equal => {
277								log_success!("✅ Versions match! The WASM file is compatible with the current runtime.");
278							},
279							std::cmp::Ordering::Greater => {
280								log_print!("🔄 The WASM file is newer than the current runtime.");
281								log_print!("   • This would be an upgrade");
282							},
283							std::cmp::Ordering::Less => {
284								log_print!("⚠️  The WASM file is older than the current runtime.");
285								log_print!("   • This would be a downgrade");
286							},
287						}
288					} else {
289						log_print!("⚠️  Could not parse version number from filename");
290					}
291				} else {
292					log_print!("⚠️  Could not extract version number from filename");
293				}
294			} else {
295				log_print!("⚠️  Could not extract version from filename format");
296			}
297
298			log_print!("💡 Use 'quantus system --runtime' for detailed runtime information");
299
300			Ok(())
301		},
302	}
303}