Skip to content

Commit a4944c8

Browse files
authored
Merge pull request #62 from helium/feat/cron-failures
Succeed duplicate or deleted cron tasks to remove from queue
2 parents c417daa + 3e164bb commit a4944c8

File tree

8 files changed

+84
-63
lines changed

8 files changed

+84
-63
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solana-programs/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solana-programs/programs/cron/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cron"
3-
version = "0.2.4"
3+
version = "0.2.5"
44
description = "Created with Anchor"
55
edition = "2021"
66

solana-programs/programs/cron/src/instructions/queue_cron_tasks_v0.rs

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,18 @@ use tuktuk_program::{
1212
RunTaskReturnV0, TaskQueueV0, TaskReturnV0, TransactionSourceV0, TriggerV0,
1313
};
1414

15-
use crate::{
16-
error::ErrorCode,
17-
state::{CronJobTransactionV0, CronJobV0},
18-
};
15+
use crate::state::{CronJobTransactionV0, CronJobV0};
1916

20-
pub const QUEUED_TASKS_PER_QUEUE: u8 = 3;
2117
// Queue tasks 5 minutes before the cron job is scheduled to run
2218
// This way we don't take up space in the task queue for tasks that are running
2319
// A long time from now
2420
pub const QUEUE_TASK_DELAY: i64 = 60 * 5;
2521

2622
#[derive(Accounts)]
2723
pub struct QueueCronTasksV0<'info> {
28-
#[account(
29-
mut,
30-
has_one = task_queue
31-
)]
32-
pub cron_job: Box<Account<'info, CronJobV0>>,
24+
/// CHECK: Checked via require in handler
25+
#[account(mut)]
26+
pub cron_job: UncheckedAccount<'info>,
3327
pub task_queue: Box<Account<'info, TaskQueueV0>>,
3428
/// CHECK: Used to write return data
3529
#[account(
@@ -48,58 +42,82 @@ pub struct QueueCronTasksV0<'info> {
4842
pub system_program: Program<'info, System>,
4943
}
5044

45+
#[macro_export]
46+
macro_rules! try_from {
47+
($ty: ty, $acc: expr) => {{
48+
let account_info = $acc.as_ref();
49+
<$ty>::try_from(unsafe {
50+
core::mem::transmute::<
51+
&anchor_lang::prelude::AccountInfo<'_>,
52+
&anchor_lang::prelude::AccountInfo<'_>,
53+
>(account_info)
54+
})
55+
}};
56+
}
57+
5158
pub fn handler(ctx: Context<QueueCronTasksV0>) -> Result<RunTaskReturnV0> {
5259
let stale_task_age = ctx.accounts.task_queue.stale_task_age;
5360
let now = Clock::get()?.unix_timestamp;
61+
if ctx.accounts.cron_job.data_is_empty() {
62+
msg!("Cron job was closed, completing task");
63+
return Ok(RunTaskReturnV0 {
64+
tasks: vec![],
65+
accounts: vec![],
66+
});
67+
}
68+
let mut cron_job = try_from!(Account<CronJobV0>, &ctx.accounts.cron_job)?;
69+
require_eq!(cron_job.task_queue, ctx.accounts.task_queue.key());
5470

5571
// Only proceed if we're within the queue window of the next execution
56-
if (now + QUEUE_TASK_DELAY) < ctx.accounts.cron_job.current_exec_ts {
72+
if (now + QUEUE_TASK_DELAY) < cron_job.current_exec_ts {
5773
msg!("Too early to queue tasks, current time {} is not within {} seconds of next execution {}",
58-
now, QUEUE_TASK_DELAY, ctx.accounts.cron_job.current_exec_ts);
59-
return Err(error!(ErrorCode::TooEarly));
74+
now, QUEUE_TASK_DELAY, cron_job.current_exec_ts);
75+
76+
// Return Ok so this task closes
77+
return Ok(RunTaskReturnV0 {
78+
tasks: vec![],
79+
accounts: vec![],
80+
});
6081
}
6182

62-
if now - ctx.accounts.cron_job.current_exec_ts > stale_task_age as i64 {
83+
if now - cron_job.current_exec_ts > stale_task_age as i64 {
6384
msg!("Cron job is stale, resetting");
64-
ctx.accounts.cron_job.current_exec_ts = now;
65-
ctx.accounts.cron_job.current_transaction_id = 0;
85+
cron_job.current_exec_ts = now;
86+
cron_job.current_transaction_id = 0;
6687
}
6788

68-
let max_num_tasks_remaining =
69-
ctx.accounts.cron_job.next_transaction_id - ctx.accounts.cron_job.current_transaction_id;
89+
let max_num_tasks_remaining = cron_job.next_transaction_id - cron_job.current_transaction_id;
7090
let num_tasks_to_queue =
71-
(ctx.accounts.cron_job.num_tasks_per_queue_call as u32).min(max_num_tasks_remaining);
72-
ctx.accounts.cron_job.current_transaction_id += num_tasks_to_queue;
91+
(cron_job.num_tasks_per_queue_call as u32).min(max_num_tasks_remaining);
92+
cron_job.current_transaction_id += num_tasks_to_queue;
7393

74-
let trigger = TriggerV0::Timestamp(ctx.accounts.cron_job.current_exec_ts);
94+
let trigger = TriggerV0::Timestamp(cron_job.current_exec_ts);
7595

7696
// If we reached the end this time, reset to 0 and move the next execution time forward
77-
if ctx.accounts.cron_job.current_transaction_id == ctx.accounts.cron_job.next_transaction_id {
78-
ctx.accounts.cron_job.current_transaction_id = 0;
79-
let schedule = Schedule::from_str(&ctx.accounts.cron_job.schedule).unwrap();
97+
if cron_job.current_transaction_id == cron_job.next_transaction_id {
98+
cron_job.current_transaction_id = 0;
99+
let schedule = Schedule::from_str(&cron_job.schedule).unwrap();
80100
// Find the next execution time after the last one
81-
let ts = ctx.accounts.cron_job.current_exec_ts;
101+
let ts = cron_job.current_exec_ts;
82102
let date_time_ts = &DateTime::<Utc>::from_naive_utc_and_offset(
83103
DateTime::from_timestamp(ts, 0).unwrap().naive_utc(),
84104
Utc,
85105
);
86-
ctx.accounts.cron_job.current_exec_ts =
87-
schedule.next_after(date_time_ts).unwrap().timestamp();
106+
cron_job.current_exec_ts = schedule.next_after(date_time_ts).unwrap().timestamp();
88107
msg!(
89108
"Will have finished execution ts: {}, moving to {}",
90109
ts,
91-
ctx.accounts.cron_job.current_exec_ts
110+
cron_job.current_exec_ts
92111
);
93112
}
94113

95-
let remaining_accounts = (ctx.accounts.cron_job.current_transaction_id
96-
..ctx.accounts.cron_job.current_transaction_id
97-
+ ctx.accounts.cron_job.num_tasks_per_queue_call as u32)
114+
let remaining_accounts = (cron_job.current_transaction_id
115+
..cron_job.current_transaction_id + cron_job.num_tasks_per_queue_call as u32)
98116
.map(|i| {
99117
Pubkey::find_program_address(
100118
&[
101119
b"cron_job_transaction",
102-
ctx.accounts.cron_job.key().as_ref(),
120+
cron_job.key().as_ref(),
103121
&i.to_le_bytes(),
104122
],
105123
&crate::ID,
@@ -113,7 +131,7 @@ pub fn handler(ctx: Context<QueueCronTasksV0>) -> Result<RunTaskReturnV0> {
113131
program_id: crate::ID,
114132
accounts: [
115133
crate::__cpi_client_accounts_queue_cron_tasks_v0::QueueCronTasksV0 {
116-
cron_job: ctx.accounts.cron_job.to_account_info(),
134+
cron_job: cron_job.to_account_info(),
117135
task_queue: ctx.accounts.task_queue.to_account_info(),
118136
task_return_account_1: ctx.accounts.task_return_account_1.to_account_info(),
119137
task_return_account_2: ctx.accounts.task_return_account_2.to_account_info(),
@@ -130,19 +148,13 @@ pub fn handler(ctx: Context<QueueCronTasksV0>) -> Result<RunTaskReturnV0> {
130148
}],
131149
vec![],
132150
)?;
133-
let free_tasks_per_transaction = ctx.accounts.cron_job.free_tasks_per_transaction;
134-
let trunc_name = ctx
135-
.accounts
136-
.cron_job
137-
.name
138-
.chars()
139-
.take(32)
140-
.collect::<String>();
151+
let free_tasks_per_transaction = cron_job.free_tasks_per_transaction;
152+
let trunc_name = cron_job.name.chars().take(32).collect::<String>();
141153
let tasks = std::iter::once(TaskReturnV0 {
142-
trigger: TriggerV0::Timestamp(ctx.accounts.cron_job.current_exec_ts - QUEUE_TASK_DELAY),
154+
trigger: TriggerV0::Timestamp(cron_job.current_exec_ts - QUEUE_TASK_DELAY),
143155
transaction: TransactionSourceV0::CompiledV0(queue_tx),
144156
crank_reward: None,
145-
free_tasks: ctx.accounts.cron_job.num_tasks_per_queue_call + 1,
157+
free_tasks: cron_job.num_tasks_per_queue_call + 1,
146158
description: format!("queue {}", trunc_name),
147159
})
148160
.chain((0..num_tasks_to_queue as usize).filter_map(|i| {
@@ -164,26 +176,26 @@ pub fn handler(ctx: Context<QueueCronTasksV0>) -> Result<RunTaskReturnV0> {
164176
}));
165177

166178
// Past all the CronJobTransaction are the free tasks
167-
ctx.accounts.cron_job.next_schedule_task =
168-
ctx.remaining_accounts[ctx.accounts.cron_job.num_tasks_per_queue_call as usize].key();
179+
cron_job.next_schedule_task =
180+
ctx.remaining_accounts[cron_job.num_tasks_per_queue_call as usize].key();
169181

170182
let res = write_return_tasks(WriteReturnTasksArgs {
171183
program_id: crate::ID,
172-
payer_info: PayerInfo::PdaPayer(ctx.accounts.cron_job.to_account_info()),
184+
payer_info: PayerInfo::PdaPayer(cron_job.to_account_info()),
173185
accounts: vec![
174186
AccountWithSeeds {
175187
account: ctx.accounts.task_return_account_1.to_account_info(),
176188
seeds: vec![
177189
b"task_return_account_1".to_vec(),
178-
ctx.accounts.cron_job.key().as_ref().to_vec(),
190+
cron_job.key().as_ref().to_vec(),
179191
vec![ctx.bumps.task_return_account_1],
180192
],
181193
},
182194
AccountWithSeeds {
183195
account: ctx.accounts.task_return_account_2.to_account_info(),
184196
seeds: vec![
185197
b"task_return_account_2".to_vec(),
186-
ctx.accounts.cron_job.key().as_ref().to_vec(),
198+
cron_job.key().as_ref().to_vec(),
187199
vec![ctx.bumps.task_return_account_2],
188200
],
189201
},
@@ -200,28 +212,30 @@ pub fn handler(ctx: Context<QueueCronTasksV0>) -> Result<RunTaskReturnV0> {
200212
msg!("Queued {} tasks", total_tasks);
201213

202214
// Transfer needed lamports from the cron job to the task queue
203-
let cron_job_info = ctx.accounts.cron_job.to_account_info();
215+
let cron_job_info = cron_job.to_account_info();
204216
let cron_job_min_lamports = Rent::get()?.minimum_balance(cron_job_info.data_len());
205217
let lamports = ctx.accounts.task_queue.min_crank_reward * total_tasks as u64;
206218
if cron_job_info.lamports() < cron_job_min_lamports + lamports {
207219
msg!(
208220
"Not enough lamports to fund tasks. Please requeue cron job when you have enough lamports. {}",
209221
cron_job_info.lamports()
210222
);
211-
ctx.accounts.cron_job.removed_from_queue = true;
212-
ctx.accounts.cron_job.next_schedule_task = Pubkey::default();
223+
cron_job.removed_from_queue = true;
224+
cron_job.next_schedule_task = Pubkey::default();
225+
cron_job.exit(&crate::ID)?;
213226
Ok(RunTaskReturnV0 {
214227
tasks: vec![],
215228
accounts: vec![],
216229
})
217230
} else {
218-
ctx.accounts.cron_job.removed_from_queue = false;
231+
cron_job.removed_from_queue = false;
219232
cron_job_info.sub_lamports(lamports)?;
220233
ctx.accounts
221234
.task_queue
222235
.to_account_info()
223236
.add_lamports(lamports)?;
224237

238+
cron_job.exit(&crate::ID)?;
225239
Ok(RunTaskReturnV0 {
226240
tasks: vec![],
227241
accounts: used_accounts,
@@ -231,10 +245,11 @@ pub fn handler(ctx: Context<QueueCronTasksV0>) -> Result<RunTaskReturnV0> {
231245
Err(e) if e.to_string().contains("rent exempt") => {
232246
msg!(
233247
"Not enough lamports to fund tasks. Please requeue cron job when you have enough lamports. {}",
234-
ctx.accounts.cron_job.to_account_info().lamports()
248+
cron_job.to_account_info().lamports()
235249
);
236-
ctx.accounts.cron_job.removed_from_queue = true;
237-
ctx.accounts.cron_job.next_schedule_task = Pubkey::default();
250+
cron_job.removed_from_queue = true;
251+
cron_job.next_schedule_task = Pubkey::default();
252+
cron_job.exit(&crate::ID)?;
238253
Ok(RunTaskReturnV0 {
239254
tasks: vec![],
240255
accounts: vec![],

tuktuk-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tuktuk-cli"
3-
version = "0.2.10"
3+
version = "0.2.11"
44
description = "A cli for tuktuk"
55
homepage.workspace = true
66
repository.workspace = true

tuktuk-cli/src/cmd/task.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ async fn simulate_task(client: &CliClient, task_key: Pubkey) -> Result<Option<Si
137137
}
138138
}
139139
Err(tuktuk_sdk::error::Error::AccountNotFound) => Ok(None),
140-
Err(e) => Err(e.into()),
140+
Err(e) => Ok(Some(SimulationResult {
141+
error: Some(e.to_string()),
142+
logs: None,
143+
compute_units: None,
144+
})),
141145
}
142146
}
143147

tuktuk-crank-turner/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tuktuk-crank-turner"
3-
version = "0.2.21"
3+
version = "0.2.22"
44
authors.workspace = true
55
edition.workspace = true
66
license.workspace = true

tuktuk-crank-turner/src/task_processor.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ impl TimedTask {
147147
..self.clone()
148148
})
149149
.await?;
150-
150+
TASKS_IN_PROGRESS
151+
.with_label_values(&[self.task_queue_name.as_str()])
152+
.dec();
151153
return Ok(());
152154
}
153155
};

0 commit comments

Comments
 (0)