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	finalized: bool,
110) -> Result<()> {
111	use quantus_subxt::api;
112
113	log_print!("🗓️  Scheduling System::remark after {} blocks", after);
114
115	// Build call as RuntimeCall
116	let system_remark = quantus_subxt::api::runtime_types::frame_system::pallet::Call::remark {
117		remark: Vec::new(),
118	};
119	let runtime_call =
120		quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall::System(system_remark);
121
122	// When: after N blocks (u32)
123	let when_u32: u32 = after;
124	let maybe_periodic = None;
125	let priority: u8 = 0;
126
127	// Submit schedule extrinsic
128	let keypair = crate::wallet::load_keypair_from_wallet(from, None, None)?;
129	let schedule_tx =
130		api::tx().scheduler().schedule(when_u32, maybe_periodic, priority, runtime_call);
131	let tx_hash = crate::cli::common::submit_transaction(
132		quantus_client,
133		&keypair,
134		schedule_tx,
135		None,
136		finalized,
137	)
138	.await?;
139	log_success!("📩 Schedule extrinsic submitted: {:?}", tx_hash);
140
141	Ok(())
142}
143
144/// Handle scheduler commands
145pub async fn handle_scheduler_command(
146	command: SchedulerCommands,
147	node_url: &str,
148	finalized: bool,
149) -> Result<()> {
150	log_print!("🗓️  Scheduler");
151
152	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
153
154	match command {
155		SchedulerCommands::GetLastProcessedTimestamp => {
156			match get_last_processed_timestamp(&quantus_client).await? {
157				Some(timestamp) => {
158					log_success!("🎉 Last processed timestamp: {}", timestamp);
159				},
160				None => {
161					log_print!(
162						"🤷 No last processed timestamp found. The scheduler may not have run yet."
163					);
164				},
165			}
166			Ok(())
167		},
168		SchedulerCommands::Agenda { range } => list_agenda_range(&quantus_client, &range).await,
169		SchedulerCommands::ScheduleRemark { after, from } =>
170			schedule_remark(&quantus_client, after, &from, finalized).await,
171	}
172}