quantus_cli/cli/
scheduler.rs

1use crate::{chain::quantus_subxt, error::Result, log_print, log_success};
2use clap::Subcommand;
3
4/// Scheduler-related commands
5#[derive(Subcommand, Debug)]
6pub enum SchedulerCommands {
7	/// Get the last processed timestamp from the scheduler
8	GetLastProcessedTimestamp,
9
10	/// List Scheduler::Agenda entries over a block range (e.g., --range 80..100)
11	Agenda {
12		/// Range in form from..to (inclusive)
13		#[arg(long)]
14		range: String,
15	},
16
17	/// Schedule a test System::remark after N blocks
18	ScheduleRemark {
19		/// Blocks after current to schedule
20		#[arg(long)]
21		after: u32,
22		/// Wallet name to sign with
23		#[arg(long)]
24		from: String,
25	},
26}
27
28/// Get the last processed timestamp from the scheduler
29pub async fn get_last_processed_timestamp(
30	quantus_client: &crate::chain::client::QuantusClient,
31) -> Result<Option<u64>> {
32	use quantus_subxt::api;
33
34	log_print!("🕒 Getting last processed timestamp from the scheduler");
35
36	// Build the storage key for Scheduler::LastProcessedTimestamp
37	let storage_addr = api::storage().scheduler().last_processed_timestamp();
38
39	// Get the latest block hash to read from the latest state (not finalized)
40	let latest_block_hash = quantus_client.get_latest_block().await?;
41
42	let storage_at = quantus_client.client().storage().at(latest_block_hash);
43
44	let timestamp = storage_at.fetch(&storage_addr).await.map_err(|e| {
45		crate::error::QuantusError::NetworkError(format!(
46			"Failed to fetch last processed timestamp: {e:?}"
47		))
48	})?;
49
50	Ok(timestamp)
51}
52
53async fn list_agenda_range(
54	quantus_client: &crate::chain::client::QuantusClient,
55	range: &str,
56) -> Result<()> {
57	use quantus_subxt::api;
58
59	// Parse range: from..to (inclusive)
60	let parts: Vec<&str> = range.split("..").collect();
61	if parts.len() != 2 {
62		return Err(crate::error::QuantusError::Generic(
63			"Invalid range format. Use --range <from>..<to>".to_string(),
64		));
65	}
66	let start: u32 = parts[0].trim().parse().map_err(|_| {
67		crate::error::QuantusError::Generic("Invalid start block in range".to_string())
68	})?;
69	let end: u32 = parts[1].trim().parse().map_err(|_| {
70		crate::error::QuantusError::Generic("Invalid end block in range".to_string())
71	})?;
72	if start > end {
73		return Err(crate::error::QuantusError::Generic("Range start must be <= end".to_string()));
74	}
75
76	// Work at latest state
77	let latest_block_hash = quantus_client.get_latest_block().await?;
78	let storage_at = quantus_client.client().storage().at(latest_block_hash);
79
80	log_print!("🗓️  Scheduler::Agenda entries for blocks {}..={} (inclusive)", start, end);
81
82	for bn in start..=end {
83		let addr = api::storage().scheduler().agenda(
84			quantus_subxt::api::runtime_types::qp_scheduler::BlockNumberOrTimestamp::BlockNumber(
85				bn,
86			),
87		);
88		match storage_at.fetch(&addr).await {
89			Ok(Some(agenda)) => {
90				log_print!("#{}: {:?}", bn, agenda);
91			},
92			Ok(None) => {
93				log_print!("#{}: <empty>", bn);
94			},
95			Err(e) => {
96				log_print!("#{}: error fetching agenda: {:?}", bn, e);
97			},
98		}
99	}
100
101	log_success!("Finished scanning Scheduler::Agenda");
102	Ok(())
103}
104
105async fn schedule_remark(
106	quantus_client: &crate::chain::client::QuantusClient,
107	after: u32,
108	from: &str,
109) -> Result<()> {
110	use quantus_subxt::api;
111
112	log_print!("🗓️  Scheduling System::remark after {} blocks", after);
113
114	// Build call as RuntimeCall
115	let system_remark = quantus_subxt::api::runtime_types::frame_system::pallet::Call::remark {
116		remark: Vec::new(),
117	};
118	let runtime_call =
119		quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall::System(system_remark);
120
121	// When: after N blocks (u32)
122	let when_u32: u32 = after;
123	let maybe_periodic = None;
124	let priority: u8 = 0;
125
126	// Submit schedule extrinsic
127	let keypair = crate::wallet::load_keypair_from_wallet(from, None, None)?;
128	let schedule_tx =
129		api::tx().scheduler().schedule(when_u32, maybe_periodic, priority, runtime_call);
130	let tx_hash =
131		crate::cli::common::submit_transaction(quantus_client, &keypair, schedule_tx, None).await?;
132	log_success!("📩 Schedule extrinsic submitted: {:?}", tx_hash);
133
134	// Wait for inclusion/finalization (lightweight)
135	let _ =
136		crate::cli::progress_spinner::wait_for_tx_confirmation(quantus_client.client(), tx_hash)
137			.await?;
138	log_success!("✅ Schedule confirmed");
139
140	Ok(())
141}
142
143/// Handle scheduler commands
144pub async fn handle_scheduler_command(command: SchedulerCommands, node_url: &str) -> Result<()> {
145	log_print!("🗓️  Scheduler");
146
147	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
148
149	match command {
150		SchedulerCommands::GetLastProcessedTimestamp => {
151			match get_last_processed_timestamp(&quantus_client).await? {
152				Some(timestamp) => {
153					log_success!("🎉 Last processed timestamp: {}", timestamp);
154				},
155				None => {
156					log_print!(
157						"🤷 No last processed timestamp found. The scheduler may not have run yet."
158					);
159				},
160			}
161			Ok(())
162		},
163		SchedulerCommands::Agenda { range } => list_agenda_range(&quantus_client, &range).await,
164		SchedulerCommands::ScheduleRemark { after, from } =>
165			schedule_remark(&quantus_client, after, &from).await,
166	}
167}