use std::sync::Arc;
use clockwork_client::{
network::state::Worker,
thread::state::{Thread, Trigger},
Client as ClockworkClient,
};
use log::info;
use solana_account_decoder::UiAccountEncoding;
use solana_client::rpc_config::{
RpcSimulateTransactionAccountsConfig, RpcSimulateTransactionConfig,
};
use solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
use solana_sdk::{
account::Account, commitment_config::CommitmentConfig,
compute_budget::ComputeBudgetInstruction, transaction::Transaction,
};
static TRANSACTION_MESSAGE_SIZE_LIMIT: usize = 1_232;
static TRANSACTION_COMPUTE_UNIT_LIMIT: u32 = 1_400_000;
static TRANSACTION_COMPUTE_UNIT_BUFFER: u32 = 100;
pub fn build_thread_exec_tx(
client: Arc<ClockworkClient>,
thread: Thread,
thread_pubkey: Pubkey,
worker_id: u64,
) -> Option<Transaction> {
let blockhash = client.get_latest_blockhash().unwrap();
let signatory_pubkey = client.payer_pubkey();
let first_instruction = if thread.next_instruction.is_some() {
build_exec_ix(thread, signatory_pubkey, worker_id)
} else {
build_kickoff_ix(thread, signatory_pubkey, worker_id)
};
let mut ixs: Vec<Instruction> = vec![
ComputeBudgetInstruction::set_compute_unit_limit(TRANSACTION_COMPUTE_UNIT_LIMIT),
first_instruction,
];
let mut successful_ixs: Vec<Instruction> = vec![];
let mut units_consumed: Option<u64> = None;
let now = std::time::Instant::now();
loop {
let mut sim_tx = Transaction::new_with_payer(&ixs, Some(&signatory_pubkey));
sim_tx.sign(&[client.payer()], blockhash);
if sim_tx.message_data().len() > TRANSACTION_MESSAGE_SIZE_LIMIT {
break;
}
match client.simulate_transaction_with_config(
&sim_tx,
RpcSimulateTransactionConfig {
replace_recent_blockhash: true,
commitment: Some(CommitmentConfig::processed()),
accounts: Some(RpcSimulateTransactionAccountsConfig {
encoding: Some(UiAccountEncoding::Base64Zstd),
addresses: vec![thread_pubkey.to_string()],
}),
..RpcSimulateTransactionConfig::default()
},
) {
Err(_err) => {
break;
}
Ok(response) => {
if response.value.err.is_some() {
if successful_ixs.is_empty() {
info!(
"thread: {} simulation_error: \"{}\" logs: {:?}",
thread_pubkey,
response.value.err.unwrap(),
response.value.logs.unwrap_or(vec![])
);
}
break;
}
successful_ixs = ixs.clone();
if response.value.units_consumed.is_some() {
units_consumed = response.value.units_consumed;
}
if let Some(ui_accounts) = response.value.accounts {
if let Some(Some(ui_account)) = ui_accounts.get(0) {
if let Some(account) = ui_account.decode::<Account>() {
if let Ok(sim_thread) = Thread::try_from(account.data) {
if sim_thread.next_instruction.is_some() {
if let Some(exec_context) = sim_thread.exec_context {
if exec_context.execs_since_slot.lt(&sim_thread.rate_limit)
{
ixs.push(build_exec_ix(
sim_thread,
signatory_pubkey,
worker_id,
));
} else {
break;
}
}
} else {
break;
}
}
}
}
}
}
}
}
if successful_ixs.is_empty() {
return None;
}
if let Some(units_consumed) = units_consumed {
let units_committed = std::cmp::min(
(units_consumed as u32) + TRANSACTION_COMPUTE_UNIT_BUFFER,
TRANSACTION_COMPUTE_UNIT_LIMIT,
);
_ = std::mem::replace(
&mut successful_ixs[0],
ComputeBudgetInstruction::set_compute_unit_limit(units_committed),
);
}
let mut tx = Transaction::new_with_payer(&successful_ixs, Some(&signatory_pubkey));
tx.sign(&[client.payer()], blockhash);
info!(
"thread: {:?} sim_duration: {:?} instruction_count: {:?} compute_units: {:?} tx_sig: {:?}",
thread_pubkey,
now.elapsed(),
successful_ixs.len(),
units_consumed,
tx.signatures[0]
);
Some(tx)
}
fn build_kickoff_ix(thread: Thread, signatory_pubkey: Pubkey, worker_id: u64) -> Instruction {
let thread_pubkey = Thread::pubkey(thread.authority, thread.id);
let mut kickoff_ix = clockwork_client::thread::instruction::thread_kickoff(
signatory_pubkey,
thread_pubkey,
Worker::pubkey(worker_id),
);
match thread.trigger {
Trigger::Account {
address,
offset: _,
size: _,
} => kickoff_ix.accounts.push(AccountMeta {
pubkey: address,
is_signer: false,
is_writable: false,
}),
_ => {}
}
kickoff_ix
}
fn build_exec_ix(thread: Thread, signatory_pubkey: Pubkey, worker_id: u64) -> Instruction {
let thread_pubkey = Thread::pubkey(thread.authority, thread.id);
let mut exec_ix = clockwork_client::thread::instruction::thread_exec(
signatory_pubkey,
thread_pubkey,
Worker::pubkey(worker_id),
);
if let Some(next_instruction) = thread.next_instruction {
exec_ix.accounts.push(AccountMeta::new_readonly(
next_instruction.program_id,
false,
));
for acc in next_instruction.clone().accounts {
let acc_pubkey = if acc.pubkey == clockwork_utils::PAYER_PUBKEY {
signatory_pubkey
} else {
acc.pubkey
};
exec_ix.accounts.push(match acc.is_writable {
true => AccountMeta::new(acc_pubkey, false),
false => AccountMeta::new_readonly(acc_pubkey, false),
})
}
}
exec_ix
}