Skip to content

Commit 9335984

Browse files
authored
Add function to requeue cron tasks, store schedule task on cron job (#45)
1 parent 067a3da commit 9335984

File tree

21 files changed

+483
-118
lines changed

21 files changed

+483
-118
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.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ spl-token = "4.0.0"
5858
itertools = "0.13"
5959
tokio-graceful-shutdown = "0.15"
6060
solana-transaction-utils = { version = "0.3.4", path = "./solana-transaction-utils" }
61-
tuktuk-sdk = { version = "0.3.3", path = "./tuktuk-sdk" }
62-
tuktuk-program = { version = "0.3.1", path = "./tuktuk-program" }
61+
tuktuk-sdk = { version = "0.3.4", path = "./tuktuk-sdk" }
62+
tuktuk-program = { version = "0.3.2", path = "./tuktuk-program" }
6363
solana-account-decoder = { version = "2.2.3" }
6464
solana-clock = { version = "2.2.1" }
6565
solana-transaction-status = "2.2.3"

solana-programs/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/packages/tuktuk-sdk/src/transaction.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,12 @@ export async function runTask({
251251
task,
252252
crankTurner,
253253
fetcher = defaultFetcher,
254+
nextAvailableTaskIds: argsNextAvailableTaskIds,
254255
}: {
255256
program: Program<Tuktuk>;
256257
task: PublicKey;
257258
crankTurner: PublicKey;
259+
nextAvailableTaskIds?: number[],
258260
fetcher?: ({
259261
task,
260262
taskQueuedAt,
@@ -287,7 +289,7 @@ export async function runTask({
287289
};
288290
});
289291

290-
const nextAvailable = nextAvailableTaskIds(
292+
const nextAvailable = argsNextAvailableTaskIds || nextAvailableTaskIds(
291293
taskQueueAcc.taskBitmap,
292294
freeTasks
293295
);

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.1"
3+
version = "0.2.3"
44
description = "Created with Anchor"
55
edition = "2021"
66

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ pub fn handler(ctx: Context<InitializeCronJobV0>, args: InitializeCronJobArgsV0)
152152
bump_seed: ctx.bumps.cron_job,
153153
removed_from_queue: false,
154154
num_transactions: 0,
155+
next_schedule_task: ctx.accounts.task.key(),
155156
});
156157
ctx.accounts.user_cron_jobs.next_cron_job_id += 1;
157158
ctx.accounts

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ pub mod close_cron_job_v0;
55
pub mod initialize_cron_job_v0;
66
pub mod queue_cron_tasks_v0;
77
pub mod remove_cron_transaction_v0;
8+
pub mod requeue_cron_task_v0;
89

910
pub use add_cron_transaction_v0::*;
1011
pub use close_cron_job_v0::*;
1112
pub use initialize_cron_job_v0::*;
1213
pub use queue_cron_tasks_v0::*;
1314
pub use remove_cron_transaction_v0::*;
15+
pub use requeue_cron_task_v0::*;
1416

1517
pub fn hash_name(name: &str) -> [u8; 32] {
1618
hash(name.as_bytes()).to_bytes()

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

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -138,31 +138,34 @@ pub fn handler(ctx: Context<QueueCronTasksV0>) -> Result<RunTaskReturnV0> {
138138
.chars()
139139
.take(32)
140140
.collect::<String>();
141-
let tasks = (0..num_tasks_to_queue as usize)
142-
.filter_map(|i| {
143-
let transaction = ctx.remaining_accounts[i].clone();
144-
if transaction.data_is_empty() {
145-
return None;
146-
}
141+
let tasks = std::iter::once(TaskReturnV0 {
142+
trigger: TriggerV0::Timestamp(ctx.accounts.cron_job.current_exec_ts - QUEUE_TASK_DELAY),
143+
transaction: TransactionSourceV0::CompiledV0(queue_tx),
144+
crank_reward: None,
145+
free_tasks: ctx.accounts.cron_job.num_tasks_per_queue_call + 1,
146+
description: format!("queue {}", trunc_name),
147+
})
148+
.chain((0..num_tasks_to_queue as usize).filter_map(|i| {
149+
let transaction = ctx.remaining_accounts[i].clone();
150+
if transaction.data_is_empty() {
151+
return None;
152+
}
147153

148-
let parsed_transaction: CronJobTransactionV0 =
149-
AccountDeserialize::try_deserialize(&mut &transaction.data.borrow()[..]).ok()?;
154+
let parsed_transaction: CronJobTransactionV0 =
155+
AccountDeserialize::try_deserialize(&mut &transaction.data.borrow()[..]).ok()?;
150156

151-
Some(TaskReturnV0 {
152-
trigger,
153-
transaction: parsed_transaction.transaction,
154-
crank_reward: None,
155-
free_tasks: free_tasks_per_transaction,
156-
description: format!("{} {}", trunc_name, parsed_transaction.id),
157-
})
158-
})
159-
.chain(std::iter::once(TaskReturnV0 {
160-
trigger: TriggerV0::Timestamp(ctx.accounts.cron_job.current_exec_ts - QUEUE_TASK_DELAY),
161-
transaction: TransactionSourceV0::CompiledV0(queue_tx),
157+
Some(TaskReturnV0 {
158+
trigger,
159+
transaction: parsed_transaction.transaction,
162160
crank_reward: None,
163-
free_tasks: ctx.accounts.cron_job.num_tasks_per_queue_call + 1,
164-
description: format!("queue {}", trunc_name),
165-
}));
161+
free_tasks: free_tasks_per_transaction,
162+
description: format!("{} {}", trunc_name, parsed_transaction.id),
163+
})
164+
}));
165+
166+
// Past all the CronJobTransaction are the free tasks
167+
ctx.accounts.cron_job.next_schedule_task =
168+
ctx.remaining_accounts[num_tasks_to_queue as usize].key();
166169

167170
let res = write_return_tasks(WriteReturnTasksArgs {
168171
program_id: crate::ID,
@@ -206,6 +209,7 @@ pub fn handler(ctx: Context<QueueCronTasksV0>) -> Result<RunTaskReturnV0> {
206209
cron_job_info.lamports()
207210
);
208211
ctx.accounts.cron_job.removed_from_queue = true;
212+
ctx.accounts.cron_job.next_schedule_task = Pubkey::default();
209213
Ok(RunTaskReturnV0 {
210214
tasks: vec![],
211215
accounts: vec![],
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use std::str::FromStr;
2+
3+
use anchor_lang::{prelude::*, solana_program::instruction::Instruction, InstructionData};
4+
use chrono::{DateTime, Utc};
5+
use clockwork_cron::Schedule;
6+
use tuktuk_program::{
7+
compile_transaction,
8+
tuktuk::{
9+
cpi::{accounts::QueueTaskV0, queue_task_v0},
10+
program::Tuktuk,
11+
},
12+
types::QueueTaskArgsV0,
13+
TaskQueueAuthorityV0, TaskQueueV0, TransactionSourceV0, TriggerV0,
14+
};
15+
16+
use super::QUEUE_TASK_DELAY;
17+
use crate::{error::ErrorCode, state::CronJobV0};
18+
19+
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
20+
pub struct RequeueCronTaskArgsV0 {
21+
pub task_id: u16,
22+
}
23+
24+
#[derive(Accounts)]
25+
#[instruction(args: RequeueCronTaskArgsV0)]
26+
pub struct RequeueCronTaskV0<'info> {
27+
#[account(mut)]
28+
pub payer: Signer<'info>,
29+
pub authority: Signer<'info>,
30+
pub queue_authority: Signer<'info>,
31+
#[account(
32+
seeds = [b"task_queue_authority", task_queue.key().as_ref(), queue_authority.key().as_ref()],
33+
bump = task_queue_authority.bump_seed,
34+
seeds::program = tuktuk_program.key(),
35+
)]
36+
pub task_queue_authority: Box<Account<'info, TaskQueueAuthorityV0>>,
37+
#[account(
38+
mut,
39+
has_one = authority,
40+
constraint = cron_job.removed_from_queue || cron_job.next_schedule_task == Pubkey::default()
41+
)]
42+
pub cron_job: Box<Account<'info, CronJobV0>>,
43+
#[account(mut)]
44+
pub task_queue: Box<Account<'info, TaskQueueV0>>,
45+
/// CHECK: Initialized in CPI
46+
#[account(mut)]
47+
pub task: AccountInfo<'info>,
48+
/// CHECK: Used to write return data
49+
#[account(
50+
mut,
51+
seeds = [b"task_return_account_1", cron_job.key().as_ref()],
52+
bump
53+
)]
54+
pub task_return_account_1: AccountInfo<'info>,
55+
/// CHECK: Used to write return data
56+
#[account(
57+
mut,
58+
seeds = [b"task_return_account_2", cron_job.key().as_ref()],
59+
bump
60+
)]
61+
pub task_return_account_2: AccountInfo<'info>,
62+
pub system_program: Program<'info, System>,
63+
pub tuktuk_program: Program<'info, Tuktuk>,
64+
}
65+
66+
pub fn handler(ctx: Context<RequeueCronTaskV0>, args: RequeueCronTaskArgsV0) -> Result<()> {
67+
let schedule = Schedule::from_str(&ctx.accounts.cron_job.schedule);
68+
if let Err(e) = schedule {
69+
msg!("Invalid schedule: {}", e);
70+
return Err(error!(ErrorCode::InvalidSchedule));
71+
}
72+
73+
let ts = Clock::get().unwrap().unix_timestamp;
74+
let now = &DateTime::<Utc>::from_naive_utc_and_offset(
75+
DateTime::from_timestamp(ts, 0).unwrap().naive_utc(),
76+
Utc,
77+
);
78+
79+
ctx.accounts.cron_job.next_schedule_task = ctx.accounts.task.key();
80+
ctx.accounts.cron_job.removed_from_queue = false;
81+
ctx.accounts.cron_job.current_exec_ts = schedule.unwrap().next_after(now).unwrap().timestamp();
82+
83+
let remaining_accounts = (ctx.accounts.cron_job.current_transaction_id
84+
..ctx.accounts.cron_job.current_transaction_id
85+
+ ctx.accounts.cron_job.num_tasks_per_queue_call as u32)
86+
.map(|i| {
87+
Pubkey::find_program_address(
88+
&[
89+
b"cron_job_transaction",
90+
ctx.accounts.cron_job.key().as_ref(),
91+
&i.to_le_bytes(),
92+
],
93+
&crate::ID,
94+
)
95+
.0
96+
})
97+
.collect::<Vec<Pubkey>>();
98+
let (queue_tx, _) = compile_transaction(
99+
vec![Instruction {
100+
program_id: crate::ID,
101+
accounts: [
102+
crate::__cpi_client_accounts_queue_cron_tasks_v0::QueueCronTasksV0 {
103+
cron_job: ctx.accounts.cron_job.to_account_info(),
104+
task_queue: ctx.accounts.task_queue.to_account_info(),
105+
task_return_account_1: ctx.accounts.task_return_account_1.to_account_info(),
106+
task_return_account_2: ctx.accounts.task_return_account_2.to_account_info(),
107+
system_program: ctx.accounts.system_program.to_account_info(),
108+
}
109+
.to_account_metas(None),
110+
remaining_accounts
111+
.iter()
112+
.map(|pubkey| AccountMeta::new_readonly(*pubkey, false))
113+
.collect::<Vec<AccountMeta>>(),
114+
]
115+
.concat(),
116+
data: crate::instruction::QueueCronTasksV0.data(),
117+
}],
118+
vec![],
119+
)?;
120+
121+
let trunc_name = ctx
122+
.accounts
123+
.cron_job
124+
.name
125+
.chars()
126+
.take(32)
127+
.collect::<String>();
128+
queue_task_v0(
129+
CpiContext::new(
130+
ctx.accounts.tuktuk_program.to_account_info(),
131+
QueueTaskV0 {
132+
payer: ctx.accounts.payer.to_account_info(),
133+
queue_authority: ctx.accounts.queue_authority.to_account_info(),
134+
task_queue_authority: ctx.accounts.task_queue_authority.to_account_info(),
135+
task_queue: ctx.accounts.task_queue.to_account_info(),
136+
task: ctx.accounts.task.to_account_info(),
137+
system_program: ctx.accounts.system_program.to_account_info(),
138+
},
139+
),
140+
QueueTaskArgsV0 {
141+
trigger: TriggerV0::Timestamp(ctx.accounts.cron_job.current_exec_ts - QUEUE_TASK_DELAY),
142+
transaction: TransactionSourceV0::CompiledV0(queue_tx),
143+
crank_reward: None,
144+
free_tasks: ctx.accounts.cron_job.num_tasks_per_queue_call + 1,
145+
id: args.task_id,
146+
description: format!("queue {}", trunc_name),
147+
},
148+
)?;
149+
150+
Ok(())
151+
}

solana-programs/programs/cron/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,11 @@ pub mod cron {
4242
pub fn close_cron_job_v0(ctx: Context<CloseCronJobV0>) -> Result<()> {
4343
close_cron_job_v0::handler(ctx)
4444
}
45+
46+
pub fn requeue_cron_task_v0(
47+
ctx: Context<RequeueCronTaskV0>,
48+
args: RequeueCronTaskArgsV0,
49+
) -> Result<()> {
50+
requeue_cron_task_v0::handler(ctx, args)
51+
}
4552
}

0 commit comments

Comments
 (0)