From 6f4ef8d7e6ef09902a0d5acc3da5fffaaaed32c2 Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Tue, 25 Apr 2023 21:56:35 +0100 Subject: [PATCH 1/8] update program and sdk --- programs/thread/src/instructions/mod.rs | 8 ++ .../instructions/thread_lookup_tables_add.rs | 82 +++++++++++++++++++ .../thread_lookup_tables_create.rs | 72 ++++++++++++++++ .../thread_lookup_tables_delete.rs | 59 +++++++++++++ .../thread_lookup_tables_remove.rs | 46 +++++++++++ programs/thread/src/lib.rs | 21 +++++ programs/thread/src/state/lookup_tables.rs | 53 ++++++++++++ programs/thread/src/state/mod.rs | 2 + sdk/src/lib.rs | 31 ++++++- 9 files changed, 372 insertions(+), 2 deletions(-) create mode 100644 programs/thread/src/instructions/thread_lookup_tables_add.rs create mode 100644 programs/thread/src/instructions/thread_lookup_tables_create.rs create mode 100644 programs/thread/src/instructions/thread_lookup_tables_delete.rs create mode 100644 programs/thread/src/instructions/thread_lookup_tables_remove.rs create mode 100644 programs/thread/src/state/lookup_tables.rs diff --git a/programs/thread/src/instructions/mod.rs b/programs/thread/src/instructions/mod.rs index 533d94503..c214418f2 100644 --- a/programs/thread/src/instructions/mod.rs +++ b/programs/thread/src/instructions/mod.rs @@ -4,6 +4,10 @@ pub mod thread_delete; pub mod thread_exec; pub mod thread_instruction_add; pub mod thread_instruction_remove; +pub mod thread_lookup_tables_create; +pub mod thread_lookup_tables_delete; +pub mod thread_lookup_tables_add; +pub mod thread_lookup_tables_remove; pub mod thread_kickoff; pub mod thread_pause; pub mod thread_reset; @@ -17,6 +21,10 @@ pub use thread_delete::*; pub use thread_exec::*; pub use thread_instruction_add::*; pub use thread_instruction_remove::*; +pub use thread_lookup_tables_create::*; +pub use thread_lookup_tables_delete::*; +pub use thread_lookup_tables_add::*; +pub use thread_lookup_tables_remove::*; pub use thread_kickoff::*; pub use thread_pause::*; pub use thread_reset::*; diff --git a/programs/thread/src/instructions/thread_lookup_tables_add.rs b/programs/thread/src/instructions/thread_lookup_tables_add.rs new file mode 100644 index 000000000..baf9d9ac1 --- /dev/null +++ b/programs/thread/src/instructions/thread_lookup_tables_add.rs @@ -0,0 +1,82 @@ +use anchor_lang::{ + prelude::*, + solana_program::system_program, + system_program::{transfer, Transfer}, +}; + +use crate::state::*; + +/// Accounts required by the `thread_address_lookup_tables_add` instruction. +#[derive(Accounts)] +#[instruction(address_lookup_tables: Vec)] +pub struct LookupTablesAdd<'info> { + /// The authority (owner) of the thread. + #[account(mut)] + pub authority: Signer<'info>, + + /// The Solana system program + #[account(address = system_program::ID)] + pub system_program: Program<'info, System>, + + /// The thread to be paused. + #[account( + seeds = [ + SEED_THREAD, + thread.authority.as_ref(), + thread.id.as_slice(), + ], + bump = thread.bump, + has_one = authority + )] + pub thread: Account<'info, Thread>, + + /// The lookup tables to be edited. + #[account( + mut, + seeds = [ + SEED_LOOKUP, + lookup_tables.authority.as_ref(), + lookup_tables.thread.as_ref(), + ], + bump = lookup_tables.bump, + has_one = authority, + has_one = thread, + )] + pub lookup_tables: Account<'info, LookupTables>, +} + +pub fn handler( + ctx: Context, + address_lookup_tables: Vec, +) -> Result<()> { + // Get accounts + let authority = &ctx.accounts.authority; + let lookup_tables = &mut ctx.accounts.lookup_tables; + let system_program = &ctx.accounts.system_program; + + // Append the address lookup tables. + lookup_tables.lookup_tables.extend(address_lookup_tables.iter()); + + // Reallocate mem for the thread account. + lookup_tables.realloc()?; + + // If lamports are required to maintain rent-exemption, pay them. + let data_len = 8 + lookup_tables.try_to_vec()?.len(); + let minimum_rent = Rent::get().unwrap().minimum_balance(data_len); + if minimum_rent > lookup_tables.to_account_info().lamports() { + transfer( + CpiContext::new( + system_program.to_account_info(), + Transfer { + from: authority.to_account_info(), + to: lookup_tables.to_account_info(), + }, + ), + minimum_rent + .checked_sub(lookup_tables.to_account_info().lamports()) + .unwrap(), + )?; + } + + Ok(()) +} diff --git a/programs/thread/src/instructions/thread_lookup_tables_create.rs b/programs/thread/src/instructions/thread_lookup_tables_create.rs new file mode 100644 index 000000000..0731239fb --- /dev/null +++ b/programs/thread/src/instructions/thread_lookup_tables_create.rs @@ -0,0 +1,72 @@ +use std::mem::size_of; + +use anchor_lang::{ + prelude::*, + solana_program::system_program, +}; + +use crate::state::*; + + +/// Accounts required by the `thread_create` instruction. +#[derive(Accounts)] +#[instruction(address_lookup_tables: Vec)] +pub struct LookupTablesCreate<'info> { + /// The authority (owner) of the thread. + #[account()] + pub authority: Signer<'info>, + + /// The payer for account initializations. + #[account(mut)] + pub payer: Signer<'info>, + + /// The Solana system program. + #[account(address = system_program::ID)] + pub system_program: Program<'info, System>, + + /// The thread to be paused. + #[account( + seeds = [ + SEED_THREAD, + thread.authority.as_ref(), + thread.id.as_slice(), + ], + bump = thread.bump, + has_one = authority + )] + pub thread: Account<'info, Thread>, + + /// The thread to be created. + #[account( + init, + seeds = [ + SEED_LOOKUP, + authority.key().as_ref(), + thread.key().as_ref(), + ], + bump, + payer= payer, + space = vec![ + 8, + size_of::(), + address_lookup_tables.try_to_vec()?.len(), + ].iter().sum() + )] + pub lookup_tables: Account<'info, LookupTables>, +} + +pub fn handler(ctx: Context, address_lookup_tables: Vec) -> Result<()> { + // Get accounts + let authority = &ctx.accounts.authority; + let thread = &ctx.accounts.thread; + let lookup_tables = &mut ctx.accounts.lookup_tables; + + // Initialize the thread + let bump = *ctx.bumps.get("lookup_tables").unwrap(); + lookup_tables.authority = authority.key(); + lookup_tables.bump = bump; + lookup_tables.thread = thread.key(); + lookup_tables.lookup_tables = address_lookup_tables; + + Ok(()) +} diff --git a/programs/thread/src/instructions/thread_lookup_tables_delete.rs b/programs/thread/src/instructions/thread_lookup_tables_delete.rs new file mode 100644 index 000000000..e109b3b0b --- /dev/null +++ b/programs/thread/src/instructions/thread_lookup_tables_delete.rs @@ -0,0 +1,59 @@ +use {crate::state::*, anchor_lang::prelude::*}; + +/// Accounts required by the `thread_delete` instruction. +#[derive(Accounts)] +pub struct LookupTablesDelete<'info> { + /// The authority (owner) of the thread. + #[account( + constraint = authority.key().eq(&thread.authority) || authority.key().eq(&thread.key()) + )] + pub authority: Signer<'info>, + + /// The address to return the data rent lamports to. + #[account(mut)] + pub close_to: SystemAccount<'info>, + + /// The thread whose lookup tables is to be closed. + #[account( + seeds = [ + SEED_THREAD, + thread.authority.as_ref(), + thread.id.as_slice(), + ], + bump = thread.bump, + )] + pub thread: Account<'info, Thread>, + + /// The lookup tables to be deleted + #[account( + mut, + seeds = [ + SEED_LOOKUP, + lookup_tables.authority.as_ref(), + lookup_tables.thread.as_ref(), + ], + bump = lookup_tables.bump, + has_one = authority, + has_one = thread, + )] + pub lookup_tables: Account<'info, LookupTables>, +} + +pub fn handler(ctx: Context) -> Result<()> { + let lookup_tables = &ctx.accounts.lookup_tables; + let close_to = &ctx.accounts.close_to; + + let lookup_tables_lamports = lookup_tables.to_account_info().lamports(); + **lookup_tables.to_account_info().try_borrow_mut_lamports()? = lookup_tables + .to_account_info() + .lamports() + .checked_sub(lookup_tables_lamports) + .unwrap(); + **close_to.to_account_info().try_borrow_mut_lamports()? = close_to + .to_account_info() + .lamports() + .checked_add(lookup_tables_lamports) + .unwrap(); + + Ok(()) +} diff --git a/programs/thread/src/instructions/thread_lookup_tables_remove.rs b/programs/thread/src/instructions/thread_lookup_tables_remove.rs new file mode 100644 index 000000000..731f1ed55 --- /dev/null +++ b/programs/thread/src/instructions/thread_lookup_tables_remove.rs @@ -0,0 +1,46 @@ +use {crate::state::*, anchor_lang::prelude::*}; + +/// Accounts required by the `thread_instruction_remove` instruction. +#[derive(Accounts)] +#[instruction(index: u64)] +pub struct LookupTablesRemove<'info> { + /// The authority (owner) of the thread. + #[account()] + pub authority: Signer<'info>, + + // the thread owner of the lookup tables + #[account( + seeds = [ + SEED_THREAD, + thread.authority.as_ref(), + thread.id.as_slice(), + ], + bump = thread.bump, + has_one = authority + )] + pub thread: Account<'info, Thread>, + + /// The lookup tables to be edited. + #[account( + mut, + seeds = [ + SEED_LOOKUP, + lookup_tables.authority.as_ref(), + lookup_tables.thread.as_ref(), + ], + bump = lookup_tables.bump, + has_one = authority, + has_one = thread, + )] + pub lookup_tables: Account<'info, LookupTables>, +} + +pub fn handler(ctx: Context, index: u64) -> Result<()> { + // Get accounts + let lookup_tables = &mut ctx.accounts.lookup_tables; + + // remove the lookup table key + lookup_tables.lookup_tables.remove(index as usize); + + Ok(()) +} diff --git a/programs/thread/src/lib.rs b/programs/thread/src/lib.rs index 3eb47de99..859b9e670 100644 --- a/programs/thread/src/lib.rs +++ b/programs/thread/src/lib.rs @@ -95,4 +95,25 @@ pub mod thread_program { pub fn thread_withdraw(ctx: Context, amount: u64) -> Result<()> { thread_withdraw::handler(ctx, amount) } + + /// Allows the owner to create an account to hold lookup tables + pub fn thread_lookup_tables_create(ctx: Context, address_lookup_tables: Vec) -> Result<()> { + thread_lookup_tables_create::handler(ctx, address_lookup_tables) + } + + /// Allows the owner to add lookup tables + pub fn thread_lookup_tables_add(ctx: Context, address_lookup_tables: Vec) -> Result<()> { + thread_lookup_tables_add::handler(ctx, address_lookup_tables) + } + + /// Allows the owner to remove a lookup table + pub fn thread_lookup_tables_remove(ctx: Context, index: u64) -> Result<()> { + thread_lookup_tables_remove::handler(ctx, index) + } + + /// Allows the owner to delete the lookup account + pub fn thread_lookup_tables_delete(ctx: Context) -> Result<()> { + thread_lookup_tables_delete::handler(ctx) + } + } diff --git a/programs/thread/src/state/lookup_tables.rs b/programs/thread/src/state/lookup_tables.rs new file mode 100644 index 000000000..aa0a6ae86 --- /dev/null +++ b/programs/thread/src/state/lookup_tables.rs @@ -0,0 +1,53 @@ +use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize}; + +pub const SEED_LOOKUP: &[u8] = b"lookup"; + +#[account] +#[derive(Debug)] +pub struct LookupTables { + pub thread: Pubkey, + pub authority: Pubkey, + pub lookup_tables: Vec, + pub bump: u8, +} + +impl LookupTables { + /// Derive the pubkey of a lookup account. + pub fn pubkey(authority: Pubkey, thread: Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[SEED_LOOKUP, authority.as_ref(), thread.as_ref()], + &crate::ID, + ) + .0 + } +} + +impl PartialEq for LookupTables { + fn eq(&self, other: &Self) -> bool { + self.authority.eq(&other.authority) && self.thread.eq(&other.thread) + } +} + +impl Eq for LookupTables {} + +/// Trait for reading and writing to a lookuptables account. +pub trait LookupTablesAccount { + /// Get the pubkey of the lookuptables account. + fn pubkey(&self) -> Pubkey; + + /// Allocate more memory for the account. + fn realloc(&mut self) -> Result<()>; +} + +impl LookupTablesAccount for Account<'_, LookupTables> { + fn pubkey(&self) -> Pubkey { + LookupTables::pubkey(self.authority, self.thread) + } + + fn realloc(&mut self) -> Result<()> { + // Realloc memory for the lookuptables account + let data_len = 8 + self.try_to_vec()?.len(); + self.to_account_info().realloc(data_len, false)?; + Ok(()) + } +} \ No newline at end of file diff --git a/programs/thread/src/state/mod.rs b/programs/thread/src/state/mod.rs index 368b9b325..abdb6af05 100644 --- a/programs/thread/src/state/mod.rs +++ b/programs/thread/src/state/mod.rs @@ -2,7 +2,9 @@ mod thread; mod versioned_thread; +mod lookup_tables; pub use clockwork_utils::thread::*; pub use thread::*; pub use versioned_thread::*; +pub use lookup_tables::*; diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 74e0ae8d2..dbab56c2c 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -15,11 +15,11 @@ pub mod utils { } pub mod cpi { - use anchor_lang::prelude::{CpiContext, Result}; + use anchor_lang::prelude::{CpiContext, Result, Pubkey}; pub use clockwork_thread_program::cpi::accounts::{ ThreadCreate, ThreadDelete, ThreadPause, ThreadReset, ThreadResume, ThreadUpdate, - ThreadWithdraw, + ThreadWithdraw, LookupTablesAdd, LookupTablesCreate, LookupTablesDelete, LookupTablesRemove }; pub fn thread_create<'info>( @@ -69,4 +69,31 @@ pub mod cpi { ) -> Result<()> { clockwork_thread_program::cpi::thread_withdraw(ctx, amount) } + + pub fn thread_lookup_tables_create<'info>( + ctx: CpiContext<'_, '_, '_, 'info, LookupTablesCreate<'info>>, + address_lookup_tables: Vec, + ) -> Result<()> { + clockwork_thread_program::cpi::thread_lookup_tables_create(ctx, address_lookup_tables) + } + + pub fn thread_lookup_tables_add<'info>( + ctx: CpiContext<'_, '_, '_, 'info, LookupTablesAdd<'info>>, + address_lookup_tables: Vec, + ) -> Result<()> { + clockwork_thread_program::cpi::thread_lookup_tables_add(ctx, address_lookup_tables) + } + + pub fn thread_lookup_tables_remove<'info>( + ctx: CpiContext<'_, '_, '_, 'info, LookupTablesRemove<'info>>, + index: u64, + ) -> Result<()> { + clockwork_thread_program::cpi::thread_lookup_tables_remove(ctx, index) + } + + pub fn thread_lookup_tables_delete<'info>( + ctx: CpiContext<'_, '_, '_, 'info, LookupTablesDelete<'info>>, + ) -> Result<()> { + clockwork_thread_program::cpi::thread_lookup_tables_delete(ctx) + } } From b2dbf0a2df1922c9bcf97b69c7cb23bf998545cf Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Wed, 26 Apr 2023 22:18:50 +0100 Subject: [PATCH 2/8] update plugin --- Cargo.lock | 1 + plugin/Cargo.toml | 1 + plugin/src/builders/thread_exec.rs | 226 +++++++++++++++++++++++++++-- plugin/src/executors/mod.rs | 60 ++++++++ plugin/src/executors/tx.rs | 13 +- 5 files changed, 289 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e118db190..47ac7336d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1275,6 +1275,7 @@ dependencies = [ "serde_json", "simple-error", "solana-account-decoder", + "solana-address-lookup-table-program", "solana-client", "solana-geyser-plugin-interface", "solana-logger", diff --git a/plugin/Cargo.toml b/plugin/Cargo.toml index 55e3dd2c5..31655f65f 100644 --- a/plugin/Cargo.toml +++ b/plugin/Cargo.toml @@ -43,6 +43,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" simple-error = "0.2.3" solana-account-decoder = "=1.14.16" +solana-address-lookup-table-program = "1.10.1" solana-client = "=1.14.16" solana-geyser-plugin-interface = "=1.14.16" solana-logger = "=1.14.16" diff --git a/plugin/src/builders/thread_exec.rs b/plugin/src/builders/thread_exec.rs index e0ac02df7..8421c2a76 100644 --- a/plugin/src/builders/thread_exec.rs +++ b/plugin/src/builders/thread_exec.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use anchor_lang::{InstructionData, ToAccountMetas}; use clockwork_thread_program::state::{VersionedThread, Trigger}; use clockwork_network_program::state::Worker; +use bincode::serialize; use clockwork_utils::thread::PAYER_PUBKEY; use log::info; use solana_account_decoder::UiAccountEncoding; @@ -16,12 +17,16 @@ use solana_geyser_plugin_interface::geyser_plugin_interface::{ }; use solana_program::{ instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, + pubkey::Pubkey, address_lookup_table_account::AddressLookupTableAccount, }; use solana_sdk::{ - account::Account, commitment_config::CommitmentConfig, - compute_budget::ComputeBudgetInstruction, signature::Keypair, signer::Signer, - transaction::Transaction, + account::Account, + commitment_config::CommitmentConfig, + compute_budget::ComputeBudgetInstruction, + message::{v0, VersionedMessage}, + signature::Keypair, + signer::Signer, + transaction::{VersionedTransaction}, }; /// Max byte size of a serialized transaction. @@ -40,7 +45,8 @@ pub async fn build_thread_exec_tx( thread: VersionedThread, thread_pubkey: Pubkey, worker_id: u64, -) -> PluginResult> { + address_lookup_tables: Vec +) -> PluginResult> { // Grab the thread and relevant data. let now = std::time::Instant::now(); let blockhash = client.get_latest_blockhash().await.unwrap(); @@ -73,11 +79,21 @@ pub async fn build_thread_exec_tx( let mut successful_ixs: Vec = vec![]; let mut units_consumed: Option = None; loop { - let mut sim_tx = Transaction::new_with_payer(&ixs, Some(&signatory_pubkey)); - sim_tx.sign(&[payer], blockhash); + // let mut sim_tx = Transaction::new_with_payer(&ixs, Some(&signatory_pubkey)); + // sim_tx.sign(&[payer], blockhash); + + let sim_tx = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &signatory_pubkey, + &ixs, + &address_lookup_tables, + blockhash, + ).expect("error compiling to v0 message")), + &[payer], + ).expect("error creating new versioned transaction"); // Exit early if the transaction exceeds the size limit. - if sim_tx.message_data().len() > TRANSACTION_MESSAGE_SIZE_LIMIT { + if serialize(&sim_tx).unwrap().len() > TRANSACTION_MESSAGE_SIZE_LIMIT { break; } @@ -198,9 +214,19 @@ pub async fn build_thread_exec_tx( ); } + // let mut tx = Transaction::new_with_payer(&successful_ixs, Some(&signatory_pubkey)); + // tx.sign(&[payer], blockhash); + // Build and return the signed transaction. - let mut tx = Transaction::new_with_payer(&successful_ixs, Some(&signatory_pubkey)); - tx.sign(&[payer], blockhash); + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &signatory_pubkey, + &ixs, + &address_lookup_tables, + blockhash, + ).expect("error compiling to v0 message")), + &[payer], + ).expect("error creating new versioned transaction"); info!( "slot: {:?} thread: {:?} sim_duration: {:?} instruction_count: {:?} compute_units: {:?} tx_sig: {:?}", slot, @@ -212,6 +238,186 @@ pub async fn build_thread_exec_tx( ); Ok(Some(tx)) } +// pub async fn build_thread_exec_tx( +// client: Arc, +// payer: &Keypair, +// slot: u64, +// thread: VersionedThread, +// thread_pubkey: Pubkey, +// worker_id: u64, +// ) -> PluginResult> { +// // Grab the thread and relevant data. +// let now = std::time::Instant::now(); +// let blockhash = client.get_latest_blockhash().await.unwrap(); +// let signatory_pubkey = payer.pubkey(); +// let worker_pubkey = Worker::pubkey(worker_id); + +// // Build the first instruction of the transaction. +// let first_instruction = if thread.next_instruction().is_some() { +// build_exec_ix( +// thread.clone(), +// thread_pubkey, +// signatory_pubkey, +// worker_pubkey, +// ) +// } else { +// build_kickoff_ix( +// thread.clone(), +// thread_pubkey, +// signatory_pubkey, +// worker_pubkey, +// ) +// }; + +// // Simulate the transaction and pack as many instructions as possible until we hit mem/cpu limits. +// // TODO Migrate to versioned transactions. +// let mut ixs: Vec = vec![ +// ComputeBudgetInstruction::set_compute_unit_limit(TRANSACTION_COMPUTE_UNIT_LIMIT), +// first_instruction, +// ]; +// let mut successful_ixs: Vec = vec![]; +// let mut units_consumed: Option = None; +// loop { + +// let mut sim_tx = Transaction::new_with_payer(&ixs, Some(&signatory_pubkey)); +// sim_tx.sign(&[payer], blockhash); + +// // Exit early if the transaction exceeds the size limit. +// if sim_tx.message_data().len() > TRANSACTION_MESSAGE_SIZE_LIMIT { +// break; +// } + +// // Run the simulation. +// match client +// .simulate_transaction_with_config( +// &sim_tx, +// RpcSimulateTransactionConfig { +// sig_verify: false, +// replace_recent_blockhash: true, +// commitment: Some(CommitmentConfig::processed()), +// accounts: Some(RpcSimulateTransactionAccountsConfig { +// encoding: Some(UiAccountEncoding::Base64Zstd), +// addresses: vec![thread_pubkey.to_string()], +// }), +// min_context_slot: Some(slot), +// ..RpcSimulateTransactionConfig::default() +// }, +// ) +// .await +// { +// // If there was a simulation error, stop packing and exit now. +// Err(err) => { +// match err.kind { +// solana_client::client_error::ClientErrorKind::RpcError(rpc_err) => { +// match rpc_err { +// solana_client::rpc_request::RpcError::RpcResponseError { +// code, +// message: _, +// data: _, +// } => { +// if code.eq(&JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED) { +// return Err(GeyserPluginError::Custom( +// format!("RPC client has not reached min context slot") +// .into(), +// )); +// } +// } +// _ => {} +// } +// } +// _ => {} +// } +// break; +// } + +// // If the simulation was successful, pack the ix into the tx. +// Ok(response) => { +// if response.value.err.is_some() { +// if successful_ixs.is_empty() { +// info!( +// "slot: {} thread: {} simulation_error: \"{}\" logs: {:?}", +// slot, +// thread_pubkey, +// response.value.err.unwrap(), +// response.value.logs.unwrap_or(vec![]), +// ); +// } +// break; +// } + +// // Update flag tracking if at least one instruction succeed. +// successful_ixs = ixs.clone(); + +// // Record the compute units consumed by the simulation. +// if response.value.units_consumed.is_some() { +// units_consumed = response.value.units_consumed; +// } + +// // Parse the resulting thread account for the next instruction to simulate. +// 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::() { +// if let Ok(sim_thread) = VersionedThread::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, +// thread_pubkey, +// signatory_pubkey, +// worker_pubkey, +// )); +// } else { +// // Exit early if the thread has reached its rate limit. +// break; +// } +// } +// } else { +// break; +// } +// } +// } +// } +// } +// } +// } +// } + +// // If there were no successful instructions, then exit early. There is nothing to do. +// // Alternatively, exit early if only the kickoff instruction (and no execs) succeeded. +// if successful_ixs.is_empty() { +// return Ok(None); +// } + +// // Set the transaction's compute unit limit to be exactly the amount that was used in simulation. +// 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), +// ); +// } + +// // Build and return the signed transaction. +// let mut tx = Transaction::new_with_payer(&successful_ixs, Some(&signatory_pubkey)); +// tx.sign(&[payer], blockhash); +// info!( +// "slot: {:?} thread: {:?} sim_duration: {:?} instruction_count: {:?} compute_units: {:?} tx_sig: {:?}", +// slot, +// thread_pubkey, +// now.elapsed(), +// successful_ixs.len(), +// units_consumed, +// tx.signatures[0] +// ); +// Ok(Some(tx)) +// } fn build_kickoff_ix( thread: VersionedThread, diff --git a/plugin/src/executors/mod.rs b/plugin/src/executors/mod.rs index f47c3464a..8baa28465 100644 --- a/plugin/src/executors/mod.rs +++ b/plugin/src/executors/mod.rs @@ -11,12 +11,15 @@ use std::{ use anchor_lang::{prelude::Pubkey, AccountDeserialize}; use async_trait::async_trait; +use clockwork_thread_program::state::LookupTables; use log::info; +use solana_address_lookup_table_program::state::AddressLookupTable; use solana_client::{ client_error::{ClientError, ClientErrorKind, Result as ClientResult}, nonblocking::rpc_client::RpcClient, }; use solana_geyser_plugin_interface::geyser_plugin_interface::Result as PluginResult; +use solana_program::address_lookup_table_account::AddressLookupTableAccount; use solana_sdk::commitment_config::CommitmentConfig; use tokio::runtime::Runtime; use tx::TxExecutor; @@ -137,3 +140,60 @@ impl AccountGet for RpcClient { }) } } + +#[async_trait] +pub trait LookupTablesGet { + async fn get_lookup_tables( + &self, + pubkey: &Pubkey, + ) -> ClientResult>; +} + +#[async_trait] +impl LookupTablesGet for RpcClient { + async fn get_lookup_tables( + &self, + pubkey: &Pubkey, + ) -> ClientResult> { + let lookup_account = self + .get_account_with_commitment(pubkey, self.commitment()) // returns Ok(None) if lookup account is not initialized + .await + .expect("error getting lookup account") + .value; + match lookup_account { + // return empty vec if lookup account has not been initialized + None => Ok(vec![]), + + // get lookup tables in lookup accounts if account has been initialized + Some(lookup) => { + let lookup_keys = LookupTables::try_deserialize(&mut lookup.data.as_slice()) + .map_err(|_| { + ClientError::from(ClientErrorKind::Custom(format!( + "Failed to deserialize account data" + ))) + }) + .expect("Failed to deserialize lookup data") + .lookup_tables; + + let mut lookup_tables: Vec = vec![]; + + for key in lookup_keys { + let raw_account = self + .get_account(&key) + .await + .expect("Could not fetch Address Lookup Table account"); + let address_lookup_table = AddressLookupTable::deserialize(&raw_account.data) + .expect("Could not deserialise Address Lookup Table"); + let address_lookup_table_account = AddressLookupTableAccount { + key, + addresses: address_lookup_table.addresses.to_vec(), + }; + + lookup_tables.push(address_lookup_table_account) + } + + return Ok(lookup_tables); + } + } + } +} diff --git a/plugin/src/executors/tx.rs b/plugin/src/executors/tx.rs index 4f54e7812..f48c37a00 100644 --- a/plugin/src/executors/tx.rs +++ b/plugin/src/executors/tx.rs @@ -10,7 +10,7 @@ use std::{ use async_once::AsyncOnce; use bincode::serialize; use clockwork_network_program::state::{Pool, Registry, Snapshot, SnapshotFrame, Worker}; -use clockwork_thread_program::state::VersionedThread; +use clockwork_thread_program::state::{LookupTables, VersionedThread}; use lazy_static::lazy_static; use log::info; use solana_client::{ @@ -31,7 +31,7 @@ use tokio::{runtime::Runtime, sync::RwLock}; use crate::{config::PluginConfig, pool_position::PoolPosition, utils::read_or_new_keypair}; -use super::AccountGet; +use super::{AccountGet, LookupTablesGet}; /// Number of slots to wait before checking for a confirmed transaction. static TRANSACTION_CONFIRMATION_PERIOD: u64 = 24; @@ -443,6 +443,14 @@ impl TxExecutor { return None; } } + let lookup_tables_key = LookupTables::pubkey(thread.authority(), thread.pubkey()); + + let address_lookup_tables = match client.clone().get_lookup_tables(&lookup_tables_key).await { + Err(_err) => { + return None; + } + Ok(address_lookup_tables) => address_lookup_tables, + }; if let Ok(tx) = crate::builders::build_thread_exec_tx( client.clone(), @@ -451,6 +459,7 @@ impl TxExecutor { thread, thread_pubkey, self.config.worker_id, + address_lookup_tables ) .await { From 3c3417b894c15b4ec9c3d5a2b7bc4d6b3b80c5ae Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Thu, 27 Apr 2023 23:06:52 +0100 Subject: [PATCH 3/8] update plugin, client --- plugin/src/builders/pool_rotation.rs | 90 ++++++++++++++++++- plugin/src/executors/tx.rs | 35 ++++++-- .../thread_lookup_tables_create.rs | 4 +- 3 files changed, 114 insertions(+), 15 deletions(-) diff --git a/plugin/src/builders/pool_rotation.rs b/plugin/src/builders/pool_rotation.rs index 85c2f504b..65abc4ca5 100644 --- a/plugin/src/builders/pool_rotation.rs +++ b/plugin/src/builders/pool_rotation.rs @@ -7,7 +7,8 @@ use anchor_lang::{ use clockwork_network_program::state::{Config, Pool, Registry, Snapshot, SnapshotFrame, Worker}; use log::info; use solana_client::nonblocking::rpc_client::RpcClient; -use solana_sdk::{signature::Keypair, signer::Signer, transaction::Transaction}; +use solana_program::message::{VersionedMessage, v0}; +use solana_sdk::{signature::Keypair, signer::Signer, transaction::{VersionedTransaction}}; use crate::pool_position::PoolPosition; @@ -19,7 +20,7 @@ pub async fn build_pool_rotation_tx<'a>( snapshot: Snapshot, snapshot_frame: SnapshotFrame, worker_id: u64, -) -> Option { +) -> Option { info!("nonce: {:?} total_stake: {:?} current_position: {:?} stake_offset: {:?} stake_amount: {:?}", registry.nonce.checked_rem(snapshot.total_stake), snapshot.total_stake, @@ -80,8 +81,89 @@ pub async fn build_pool_rotation_tx<'a>( data: clockwork_network_program::instruction::PoolRotate {}.data(), }; + // let mut tx = Transaction::new_with_payer(&[ix.clone()], Some(&keypair.pubkey())); + // tx.sign(&[keypair], client.get_latest_blockhash().await.unwrap()); + // Build and sign tx. - let mut tx = Transaction::new_with_payer(&[ix.clone()], Some(&keypair.pubkey())); - tx.sign(&[keypair], client.get_latest_blockhash().await.unwrap()); + let blockhash = client.get_latest_blockhash().await.unwrap(); + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &keypair.pubkey(), + &[ix.clone()], + &[], + blockhash, + ).expect("error compiling to v0 message")), + &[keypair], + ).expect("error creating new versioned transaction"); return Some(tx); } + +// pub async fn build_pool_rotation_tx<'a>( +// client: Arc, +// keypair: &Keypair, +// pool_position: PoolPosition, +// registry: Registry, +// snapshot: Snapshot, +// snapshot_frame: SnapshotFrame, +// worker_id: u64, +// ) -> Option { +// info!("nonce: {:?} total_stake: {:?} current_position: {:?} stake_offset: {:?} stake_amount: {:?}", +// registry.nonce.checked_rem(snapshot.total_stake), +// snapshot.total_stake, +// pool_position.current_position, +// snapshot_frame.stake_offset, +// snapshot_frame.stake_amount, +// ); + +// // Exit early if the rotator is not intialized +// if registry.nonce == 0 { +// return None; +// } + +// // Exit early the snapshot has no stake +// if snapshot.total_stake == 0 { +// return None; +// } + +// // Exit early if the worker is already in the pool. +// if pool_position.current_position.is_some() { +// return None; +// } + +// // Exit early if the snapshot frame is none or the worker has no delegated stake. +// if snapshot_frame.stake_amount.eq(&0) { +// return None; +// } + +// // Check if the rotation window is open for this worker. +// let is_rotation_window_open = match registry.nonce.checked_rem(snapshot.total_stake) { +// None => false, +// Some(sample) => { +// sample >= snapshot_frame.stake_offset +// && sample +// < snapshot_frame +// .stake_offset +// .checked_add(snapshot_frame.stake_amount) +// .unwrap() +// } +// }; +// if !is_rotation_window_open { +// return None; +// } + +// // Build rotation instruction to rotate the worker into pool 0. +// let snapshot_pubkey = Snapshot::pubkey(snapshot.id); +// let ix = clockwork_client::network::instruction::pool_rotate( +// Pool::pubkey(0), +// keypair.pubkey(), +// snapshot_pubkey, +// SnapshotFrame::pubkey(snapshot_pubkey, worker_id), +// Worker::pubkey(worker_id), +// ); + +// // Build and sign tx. +// let mut tx = Transaction::new_with_payer(&[ix.clone()], Some(&keypair.pubkey())); +// tx.sign(&[keypair], client.get_latest_blockhash().await.unwrap()); +// return Some(tx); +// } diff --git a/plugin/src/executors/tx.rs b/plugin/src/executors/tx.rs index f48c37a00..3cf696225 100644 --- a/plugin/src/executors/tx.rs +++ b/plugin/src/executors/tx.rs @@ -25,7 +25,7 @@ use solana_program::pubkey::Pubkey; use solana_sdk::{ commitment_config::CommitmentConfig, signature::{Keypair, Signature}, - transaction::Transaction, + transaction::{VersionedTransaction}, }; use tokio::{runtime::Runtime, sync::RwLock}; @@ -428,7 +428,7 @@ impl TxExecutor { observed_slot: u64, due_slot: u64, thread_pubkey: Pubkey, - ) -> Option<(Pubkey, Transaction, u64)> { + ) -> Option<(Pubkey, VersionedTransaction, u64)> { let thread = match client.clone().get::(&thread_pubkey).await { Err(_err) => { self.increment_simulation_failure(thread_pubkey).await; @@ -444,8 +444,9 @@ impl TxExecutor { } } let lookup_tables_key = LookupTables::pubkey(thread.authority(), thread.pubkey()); - - let address_lookup_tables = match client.clone().get_lookup_tables(&lookup_tables_key).await { + + let address_lookup_tables = match client.clone().get_lookup_tables(&lookup_tables_key).await + { Err(_err) => { return None; } @@ -459,7 +460,7 @@ impl TxExecutor { thread, thread_pubkey, self.config.worker_id, - address_lookup_tables + address_lookup_tables, ) .await { @@ -495,7 +496,7 @@ impl TxExecutor { self: Arc, slot: u64, thread_pubkey: Pubkey, - tx: &Transaction, + tx: &VersionedTransaction, ) -> PluginResult<()> { let r_transaction_history = self.transaction_history.read().await; if let Some(metadata) = r_transaction_history.get(&thread_pubkey) { @@ -507,7 +508,10 @@ impl TxExecutor { Ok(()) } - async fn simulate_tx(self: Arc, tx: &Transaction) -> PluginResult { + async fn simulate_tx( + self: Arc, + tx: &VersionedTransaction, + ) -> PluginResult { TPU_CLIENT .get() .await @@ -536,14 +540,27 @@ impl TxExecutor { })? } - async fn submit_tx(self: Arc, tx: &Transaction) -> PluginResult { - if !TPU_CLIENT.get().await.send_transaction(tx).await { + async fn submit_tx( + self: Arc, + tx: &VersionedTransaction, + ) -> PluginResult { + let serialized_tx = serialize(&tx).unwrap(); + + if !TPU_CLIENT.get().await.send_wire_transaction(serialized_tx).await { return Err(GeyserPluginError::Custom( "Failed to send transaction".into(), )); } Ok(tx.clone()) } + // async fn submit_tx(self: Arc, tx: &Transaction) -> PluginResult { + // if !TPU_CLIENT.get().await.send_transaction(tx).await { + // return Err(GeyserPluginError::Custom( + // "Failed to send transaction".into(), + // )); + // } + // Ok(tx.clone()) + // } } impl Debug for TxExecutor { diff --git a/programs/thread/src/instructions/thread_lookup_tables_create.rs b/programs/thread/src/instructions/thread_lookup_tables_create.rs index 0731239fb..1a6565347 100644 --- a/programs/thread/src/instructions/thread_lookup_tables_create.rs +++ b/programs/thread/src/instructions/thread_lookup_tables_create.rs @@ -8,7 +8,7 @@ use anchor_lang::{ use crate::state::*; -/// Accounts required by the `thread_create` instruction. +/// Accounts required by the `thread_lookup_tables_create` instruction. #[derive(Accounts)] #[instruction(address_lookup_tables: Vec)] pub struct LookupTablesCreate<'info> { @@ -36,7 +36,7 @@ pub struct LookupTablesCreate<'info> { )] pub thread: Account<'info, Thread>, - /// The thread to be created. + /// The lookup_tables account to be created. #[account( init, seeds = [ From 3c8966393cd0c5095c7d9ef59f3d8631b5ca9e3e Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Sat, 29 Apr 2023 18:56:17 +0100 Subject: [PATCH 4/8] little fix @ plugin --- plugin/src/executors/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/executors/tx.rs b/plugin/src/executors/tx.rs index 3cf696225..8ec46fafe 100644 --- a/plugin/src/executors/tx.rs +++ b/plugin/src/executors/tx.rs @@ -544,7 +544,7 @@ impl TxExecutor { self: Arc, tx: &VersionedTransaction, ) -> PluginResult { - let serialized_tx = serialize(&tx).unwrap(); + let serialized_tx = serialize(tx).unwrap(); if !TPU_CLIENT.get().await.send_wire_transaction(serialized_tx).await { return Err(GeyserPluginError::Custom( From 77f38f0d4f25f3703c0919ceb84d4592ecbbdfef Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Sun, 30 Apr 2023 00:27:17 +0100 Subject: [PATCH 5/8] refactor --- plugin/src/executors/mod.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/plugin/src/executors/mod.rs b/plugin/src/executors/mod.rs index 8baa28465..3cb4f1ab0 100644 --- a/plugin/src/executors/mod.rs +++ b/plugin/src/executors/mod.rs @@ -175,22 +175,18 @@ impl LookupTablesGet for RpcClient { .expect("Failed to deserialize lookup data") .lookup_tables; - let mut lookup_tables: Vec = vec![]; - - for key in lookup_keys { + let lookup_tables = futures::future::join_all(lookup_keys.iter().map(|key| async move { let raw_account = self - .get_account(&key) + .get_account(key) .await .expect("Could not fetch Address Lookup Table account"); let address_lookup_table = AddressLookupTable::deserialize(&raw_account.data) .expect("Could not deserialise Address Lookup Table"); - let address_lookup_table_account = AddressLookupTableAccount { - key, + AddressLookupTableAccount { + key: *key, addresses: address_lookup_table.addresses.to_vec(), - }; - - lookup_tables.push(address_lookup_table_account) - } + } + })).await; return Ok(lookup_tables); } From bb770a97984f69e11ecb89910f70615e4caa5cdc Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Tue, 6 Jun 2023 17:02:23 +0100 Subject: [PATCH 6/8] multiple accounts, single instruction --- Cargo.lock | 4 +- plugin/src/builders/pool_rotation.rs | 109 ++------- plugin/src/builders/thread_exec.rs | 216 ++---------------- plugin/src/executors/mod.rs | 33 +-- plugin/src/executors/tx.rs | 8 - programs/thread/src/errors.rs | 4 + .../instructions/big_instruction_create.rs | 75 ++++++ programs/thread/src/instructions/mod.rs | 6 + .../thread_big_instruction_add.rs | 82 +++++++ .../src/instructions/thread_dummy_ix.rs | 13 ++ programs/thread/src/lib.rs | 19 ++ programs/thread/src/state/big_instruction.rs | 47 ++++ programs/thread/src/state/mod.rs | 2 + programs/thread/src/state/thread.rs | 2 +- sdk/src/lib.rs | 41 +++- 15 files changed, 345 insertions(+), 316 deletions(-) create mode 100644 programs/thread/src/instructions/big_instruction_create.rs create mode 100644 programs/thread/src/instructions/thread_big_instruction_add.rs create mode 100644 programs/thread/src/instructions/thread_dummy_ix.rs create mode 100644 programs/thread/src/state/big_instruction.rs diff --git a/Cargo.lock b/Cargo.lock index f15fd6089..8e783328b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -489,9 +489,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "arrayref" diff --git a/plugin/src/builders/pool_rotation.rs b/plugin/src/builders/pool_rotation.rs index 65abc4ca5..49309d6d8 100644 --- a/plugin/src/builders/pool_rotation.rs +++ b/plugin/src/builders/pool_rotation.rs @@ -1,14 +1,12 @@ use std::sync::Arc; -use anchor_lang::{ - solana_program::instruction::Instruction, - InstructionData, ToAccountMetas -}; +use anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}; use clockwork_network_program::state::{Config, Pool, Registry, Snapshot, SnapshotFrame, Worker}; use log::info; use solana_client::nonblocking::rpc_client::RpcClient; -use solana_program::message::{VersionedMessage, v0}; -use solana_sdk::{signature::Keypair, signer::Signer, transaction::{VersionedTransaction}}; +use solana_geyser_plugin_interface::geyser_plugin_interface::GeyserPluginError; +use solana_program::message::{v0, VersionedMessage}; +use solana_sdk::{signature::Keypair, signer::Signer, transaction::VersionedTransaction}; use crate::pool_position::PoolPosition; @@ -77,93 +75,26 @@ pub async fn build_pool_rotation_tx<'a>( snapshot: snapshot_pubkey, snapshot_frame: SnapshotFrame::pubkey(snapshot_pubkey, worker_id), worker: Worker::pubkey(worker_id), - }.to_account_metas(Some(false)), + } + .to_account_metas(Some(false)), data: clockwork_network_program::instruction::PoolRotate {}.data(), }; - // let mut tx = Transaction::new_with_payer(&[ix.clone()], Some(&keypair.pubkey())); - // tx.sign(&[keypair], client.get_latest_blockhash().await.unwrap()); - // Build and sign tx. let blockhash = client.get_latest_blockhash().await.unwrap(); - let tx = VersionedTransaction::try_new( - VersionedMessage::V0(v0::Message::try_compile( - &keypair.pubkey(), - &[ix.clone()], - &[], - blockhash, - ).expect("error compiling to v0 message")), - &[keypair], - ).expect("error creating new versioned transaction"); - return Some(tx); + let tx = match v0::Message::try_compile(&keypair.pubkey(), &[ix.clone()], &[], blockhash) { + Err(_) => Err(GeyserPluginError::Custom( + format!("Failed to compile to v0 message ").into(), + )), + Ok(message) => { + match VersionedTransaction::try_new(VersionedMessage::V0(message), &[keypair]) { + Err(_) => Err(GeyserPluginError::Custom( + format!("Failed to create versioned transaction ").into(), + )), + Ok(tx) => Ok(tx), + } + } + }; + return tx.ok(); } - -// pub async fn build_pool_rotation_tx<'a>( -// client: Arc, -// keypair: &Keypair, -// pool_position: PoolPosition, -// registry: Registry, -// snapshot: Snapshot, -// snapshot_frame: SnapshotFrame, -// worker_id: u64, -// ) -> Option { -// info!("nonce: {:?} total_stake: {:?} current_position: {:?} stake_offset: {:?} stake_amount: {:?}", -// registry.nonce.checked_rem(snapshot.total_stake), -// snapshot.total_stake, -// pool_position.current_position, -// snapshot_frame.stake_offset, -// snapshot_frame.stake_amount, -// ); - -// // Exit early if the rotator is not intialized -// if registry.nonce == 0 { -// return None; -// } - -// // Exit early the snapshot has no stake -// if snapshot.total_stake == 0 { -// return None; -// } - -// // Exit early if the worker is already in the pool. -// if pool_position.current_position.is_some() { -// return None; -// } - -// // Exit early if the snapshot frame is none or the worker has no delegated stake. -// if snapshot_frame.stake_amount.eq(&0) { -// return None; -// } - -// // Check if the rotation window is open for this worker. -// let is_rotation_window_open = match registry.nonce.checked_rem(snapshot.total_stake) { -// None => false, -// Some(sample) => { -// sample >= snapshot_frame.stake_offset -// && sample -// < snapshot_frame -// .stake_offset -// .checked_add(snapshot_frame.stake_amount) -// .unwrap() -// } -// }; -// if !is_rotation_window_open { -// return None; -// } - -// // Build rotation instruction to rotate the worker into pool 0. -// let snapshot_pubkey = Snapshot::pubkey(snapshot.id); -// let ix = clockwork_client::network::instruction::pool_rotate( -// Pool::pubkey(0), -// keypair.pubkey(), -// snapshot_pubkey, -// SnapshotFrame::pubkey(snapshot_pubkey, worker_id), -// Worker::pubkey(worker_id), -// ); - -// // Build and sign tx. -// let mut tx = Transaction::new_with_payer(&[ix.clone()], Some(&keypair.pubkey())); -// tx.sign(&[keypair], client.get_latest_blockhash().await.unwrap()); -// return Some(tx); -// } diff --git a/plugin/src/builders/thread_exec.rs b/plugin/src/builders/thread_exec.rs index 8421c2a76..3a1015a9e 100644 --- a/plugin/src/builders/thread_exec.rs +++ b/plugin/src/builders/thread_exec.rs @@ -79,18 +79,21 @@ pub async fn build_thread_exec_tx( let mut successful_ixs: Vec = vec![]; let mut units_consumed: Option = None; loop { - // let mut sim_tx = Transaction::new_with_payer(&ixs, Some(&signatory_pubkey)); - // sim_tx.sign(&[payer], blockhash); - - let sim_tx = VersionedTransaction::try_new( - VersionedMessage::V0(v0::Message::try_compile( + let sim_tx = match v0::Message::try_compile( &signatory_pubkey, &ixs, &address_lookup_tables, blockhash, - ).expect("error compiling to v0 message")), - &[payer], - ).expect("error creating new versioned transaction"); + ) { + Err(_) => Err(GeyserPluginError::Custom(format!("Failed to compile to v0 message ").into())), + Ok(message) => match VersionedTransaction::try_new( + VersionedMessage::V0(message), + &[payer] + ) { + Err(_) => Err(GeyserPluginError::Custom(format!("Failed to create versioned transaction ").into())), + Ok(tx) => Ok(tx) + } + }?; // Exit early if the transaction exceeds the size limit. if serialize(&sim_tx).unwrap().len() > TRANSACTION_MESSAGE_SIZE_LIMIT { @@ -218,15 +221,22 @@ pub async fn build_thread_exec_tx( // tx.sign(&[payer], blockhash); // Build and return the signed transaction. - let tx = VersionedTransaction::try_new( - VersionedMessage::V0(v0::Message::try_compile( + let tx = match v0::Message::try_compile( &signatory_pubkey, &ixs, &address_lookup_tables, blockhash, - ).expect("error compiling to v0 message")), - &[payer], - ).expect("error creating new versioned transaction"); + ) { + Err(_) => Err(GeyserPluginError::Custom(format!("Failed to compile to v0 message ").into())), + Ok(message) => match VersionedTransaction::try_new( + VersionedMessage::V0(message), + &[payer] + ) { + Err(_) => Err(GeyserPluginError::Custom(format!("Failed to create versioned transaction ").into())), + Ok(tx) => Ok(tx) + } + + }?; info!( "slot: {:?} thread: {:?} sim_duration: {:?} instruction_count: {:?} compute_units: {:?} tx_sig: {:?}", slot, @@ -238,186 +248,6 @@ pub async fn build_thread_exec_tx( ); Ok(Some(tx)) } -// pub async fn build_thread_exec_tx( -// client: Arc, -// payer: &Keypair, -// slot: u64, -// thread: VersionedThread, -// thread_pubkey: Pubkey, -// worker_id: u64, -// ) -> PluginResult> { -// // Grab the thread and relevant data. -// let now = std::time::Instant::now(); -// let blockhash = client.get_latest_blockhash().await.unwrap(); -// let signatory_pubkey = payer.pubkey(); -// let worker_pubkey = Worker::pubkey(worker_id); - -// // Build the first instruction of the transaction. -// let first_instruction = if thread.next_instruction().is_some() { -// build_exec_ix( -// thread.clone(), -// thread_pubkey, -// signatory_pubkey, -// worker_pubkey, -// ) -// } else { -// build_kickoff_ix( -// thread.clone(), -// thread_pubkey, -// signatory_pubkey, -// worker_pubkey, -// ) -// }; - -// // Simulate the transaction and pack as many instructions as possible until we hit mem/cpu limits. -// // TODO Migrate to versioned transactions. -// let mut ixs: Vec = vec![ -// ComputeBudgetInstruction::set_compute_unit_limit(TRANSACTION_COMPUTE_UNIT_LIMIT), -// first_instruction, -// ]; -// let mut successful_ixs: Vec = vec![]; -// let mut units_consumed: Option = None; -// loop { - -// let mut sim_tx = Transaction::new_with_payer(&ixs, Some(&signatory_pubkey)); -// sim_tx.sign(&[payer], blockhash); - -// // Exit early if the transaction exceeds the size limit. -// if sim_tx.message_data().len() > TRANSACTION_MESSAGE_SIZE_LIMIT { -// break; -// } - -// // Run the simulation. -// match client -// .simulate_transaction_with_config( -// &sim_tx, -// RpcSimulateTransactionConfig { -// sig_verify: false, -// replace_recent_blockhash: true, -// commitment: Some(CommitmentConfig::processed()), -// accounts: Some(RpcSimulateTransactionAccountsConfig { -// encoding: Some(UiAccountEncoding::Base64Zstd), -// addresses: vec![thread_pubkey.to_string()], -// }), -// min_context_slot: Some(slot), -// ..RpcSimulateTransactionConfig::default() -// }, -// ) -// .await -// { -// // If there was a simulation error, stop packing and exit now. -// Err(err) => { -// match err.kind { -// solana_client::client_error::ClientErrorKind::RpcError(rpc_err) => { -// match rpc_err { -// solana_client::rpc_request::RpcError::RpcResponseError { -// code, -// message: _, -// data: _, -// } => { -// if code.eq(&JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED) { -// return Err(GeyserPluginError::Custom( -// format!("RPC client has not reached min context slot") -// .into(), -// )); -// } -// } -// _ => {} -// } -// } -// _ => {} -// } -// break; -// } - -// // If the simulation was successful, pack the ix into the tx. -// Ok(response) => { -// if response.value.err.is_some() { -// if successful_ixs.is_empty() { -// info!( -// "slot: {} thread: {} simulation_error: \"{}\" logs: {:?}", -// slot, -// thread_pubkey, -// response.value.err.unwrap(), -// response.value.logs.unwrap_or(vec![]), -// ); -// } -// break; -// } - -// // Update flag tracking if at least one instruction succeed. -// successful_ixs = ixs.clone(); - -// // Record the compute units consumed by the simulation. -// if response.value.units_consumed.is_some() { -// units_consumed = response.value.units_consumed; -// } - -// // Parse the resulting thread account for the next instruction to simulate. -// 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::() { -// if let Ok(sim_thread) = VersionedThread::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, -// thread_pubkey, -// signatory_pubkey, -// worker_pubkey, -// )); -// } else { -// // Exit early if the thread has reached its rate limit. -// break; -// } -// } -// } else { -// break; -// } -// } -// } -// } -// } -// } -// } -// } - -// // If there were no successful instructions, then exit early. There is nothing to do. -// // Alternatively, exit early if only the kickoff instruction (and no execs) succeeded. -// if successful_ixs.is_empty() { -// return Ok(None); -// } - -// // Set the transaction's compute unit limit to be exactly the amount that was used in simulation. -// 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), -// ); -// } - -// // Build and return the signed transaction. -// let mut tx = Transaction::new_with_payer(&successful_ixs, Some(&signatory_pubkey)); -// tx.sign(&[payer], blockhash); -// info!( -// "slot: {:?} thread: {:?} sim_duration: {:?} instruction_count: {:?} compute_units: {:?} tx_sig: {:?}", -// slot, -// thread_pubkey, -// now.elapsed(), -// successful_ixs.len(), -// units_consumed, -// tx.signatures[0] -// ); -// Ok(Some(tx)) -// } fn build_kickoff_ix( thread: VersionedThread, diff --git a/plugin/src/executors/mod.rs b/plugin/src/executors/mod.rs index 3cb4f1ab0..417f335f7 100644 --- a/plugin/src/executors/mod.rs +++ b/plugin/src/executors/mod.rs @@ -157,8 +157,7 @@ impl LookupTablesGet for RpcClient { ) -> ClientResult> { let lookup_account = self .get_account_with_commitment(pubkey, self.commitment()) // returns Ok(None) if lookup account is not initialized - .await - .expect("error getting lookup account") + .await? .value; match lookup_account { // return empty vec if lookup account has not been initialized @@ -175,20 +174,22 @@ impl LookupTablesGet for RpcClient { .expect("Failed to deserialize lookup data") .lookup_tables; - let lookup_tables = futures::future::join_all(lookup_keys.iter().map(|key| async move { - let raw_account = self - .get_account(key) - .await - .expect("Could not fetch Address Lookup Table account"); - let address_lookup_table = AddressLookupTable::deserialize(&raw_account.data) - .expect("Could not deserialise Address Lookup Table"); - AddressLookupTableAccount { - key: *key, - addresses: address_lookup_table.addresses.to_vec(), - } - })).await; - - return Ok(lookup_tables); + let lookup_tables = + futures::future::join_all(lookup_keys.iter().map(|key| async move { + let raw_account = self.get_account(key).await?; + let address_lookup_table = + AddressLookupTable::deserialize(&raw_account.data).map_err(|_| { + ClientError::from(ClientErrorKind::Custom(format!( + "Could not deserialise Address Lookup Table" + ))) + })?; + Ok(AddressLookupTableAccount { + key: *key, + addresses: address_lookup_table.addresses.to_vec(), + }) + })) + .await; + lookup_tables.into_iter().collect() } } } diff --git a/plugin/src/executors/tx.rs b/plugin/src/executors/tx.rs index 3de9bcfae..5d64e6a91 100644 --- a/plugin/src/executors/tx.rs +++ b/plugin/src/executors/tx.rs @@ -557,14 +557,6 @@ impl TxExecutor { } Ok(tx.clone()) } - // async fn submit_tx(self: Arc, tx: &Transaction) -> PluginResult { - // if !TPU_CLIENT.get().await.send_transaction(tx).await { - // return Err(GeyserPluginError::Custom( - // "Failed to send transaction".into(), - // )); - // } - // Ok(tx.clone()) - // } } impl Debug for TxExecutor { diff --git a/programs/thread/src/errors.rs b/programs/thread/src/errors.rs index 3bd0aee73..90d36f60f 100644 --- a/programs/thread/src/errors.rs +++ b/programs/thread/src/errors.rs @@ -43,4 +43,8 @@ pub enum ClockworkError { /// Thrown if the user attempts to withdraw SOL that would put a thread below it's minimum rent threshold. #[msg("Withdrawing this amount would leave the thread with less than the minimum required SOL for rent exemption")] WithdrawalTooLarge, + + /// Thrown if number of accounts passed into big instruction does not match the number of account specified when creating big instruction + #[msg("Number of accounts specified does not match the number of accounts passed ")] + AccountsLengthMismatch, } diff --git a/programs/thread/src/instructions/big_instruction_create.rs b/programs/thread/src/instructions/big_instruction_create.rs new file mode 100644 index 000000000..d47a513e3 --- /dev/null +++ b/programs/thread/src/instructions/big_instruction_create.rs @@ -0,0 +1,75 @@ +use std::mem::size_of; + +use anchor_lang::{ + prelude::*, + solana_program::system_program, +}; + +use crate::{errors::ClockworkError, state::*}; + +/// Accounts required by the `thread_lookup_tables_create` instruction. +#[derive(Accounts)] +#[instruction(id: Vec, instruction_data: Vec, no_of_accounts: u8)] +pub struct BigInstructionCreate<'info> { + /// The authority (owner) of the thread. + #[account()] + pub authority: Signer<'info>, + + /// The payer for account initializations. + #[account(mut)] + pub payer: Signer<'info>, + + /// The Solana system program. + #[account(address = system_program::ID)] + pub system_program: Program<'info, System>, + + /// CHECK: program_id of the instruction to build + #[account(executable)] + pub instruction_program_id: UncheckedAccount<'info>, + + /// The lookup_tables account to be created. + #[account( + init, + seeds = [ + SEED_BIG_INST, + authority.key().as_ref(), + instruction_program_id.key().as_ref(), + id.as_slice(), + ], + bump, + payer= payer, + space = vec![ + 8, + size_of::(), + id.len(), + instruction_data.try_to_vec()?.len(), + (no_of_accounts as usize) * size_of::(), + ].iter().sum() + )] + pub big_instruction: Account<'info, BigInstruction>, +} + +pub fn handler(ctx: Context, id: Vec, instruction_data: Vec, no_of_accounts: u8) -> Result<()> { + // Get accounts + require!((no_of_accounts as usize) == ctx.remaining_accounts.len(), ClockworkError::AccountsLengthMismatch); + + let authority = &ctx.accounts.authority; + let program_id = &ctx.accounts.instruction_program_id; + let big_instruction = &mut ctx.accounts.big_instruction; + let accounts = ctx.remaining_accounts.into_iter().map(|acct_info| SerializableAccount { + is_signer: acct_info.is_signer, // false + is_writable: acct_info.is_writable, + pubkey: acct_info.key() + }).collect::>(); + + // Initialize the thread + let bump = *ctx.bumps.get("big_instruction").unwrap(); + big_instruction.authority = authority.key(); + big_instruction.bump = bump; + big_instruction.id = id; + big_instruction.data = instruction_data; + big_instruction.program_id = program_id.key(); + big_instruction.accounts = accounts; + + Ok(()) +} diff --git a/programs/thread/src/instructions/mod.rs b/programs/thread/src/instructions/mod.rs index c214418f2..f6a2474e0 100644 --- a/programs/thread/src/instructions/mod.rs +++ b/programs/thread/src/instructions/mod.rs @@ -14,6 +14,9 @@ pub mod thread_reset; pub mod thread_resume; pub mod thread_update; pub mod thread_withdraw; +pub mod big_instruction_create; +pub mod thread_big_instruction_add; +pub mod thread_dummy_ix; pub use get_crate_info::*; pub use thread_create::*; @@ -31,3 +34,6 @@ pub use thread_reset::*; pub use thread_resume::*; pub use thread_update::*; pub use thread_withdraw::*; +pub use big_instruction_create::*; +pub use thread_big_instruction_add::*; +pub use thread_dummy_ix::*; diff --git a/programs/thread/src/instructions/thread_big_instruction_add.rs b/programs/thread/src/instructions/thread_big_instruction_add.rs new file mode 100644 index 000000000..1cf23d822 --- /dev/null +++ b/programs/thread/src/instructions/thread_big_instruction_add.rs @@ -0,0 +1,82 @@ +use anchor_lang::{ + prelude::*, + solana_program::system_program, + system_program::{transfer, Transfer}, +}; + +use crate::state::*; + +/// Accounts required by the `thread_instruction_add` instruction. +#[derive(Accounts)] +pub struct ThreadBigInstructionAdd<'info> { + /// The authority (owner) of the thread. + #[account(mut)] + pub authority: Signer<'info>, + + /// The Solana system program + #[account(address = system_program::ID)] + pub system_program: Program<'info, System>, + + /// The thread to be paused. + #[account( + mut, + seeds = [ + SEED_THREAD, + thread.authority.as_ref(), + thread.id.as_slice(), + ], + bump = thread.bump, + has_one = authority + )] + pub thread: Account<'info, Thread>, + + #[account( + has_one=authority + )] + pub big_instruction: Account<'info, BigInstruction>, +} + +pub fn handler( + ctx: Context, +) -> Result<()> { + // Get accounts + let authority = &ctx.accounts.authority; + let thread = &mut ctx.accounts.thread; + let system_program = &ctx.accounts.system_program; + + let ix_data = &ctx.accounts.big_instruction.data; + let ix_program_id = &ctx.accounts.big_instruction.program_id; + let ix_accounts = &ctx.accounts.big_instruction.accounts; + + let build_ix = SerializableInstruction { + accounts: ix_accounts.clone(), + data: ix_data.clone(), + program_id: ix_program_id.clone(), + }; + + // Append the instruction. + thread.instructions.push(build_ix); + + // Reallocate mem for the thread account. + thread.realloc()?; + + // If lamports are required to maintain rent-exemption, pay them. + let data_len = 8 + thread.try_to_vec()?.len(); + let minimum_rent = Rent::get().unwrap().minimum_balance(data_len); + if minimum_rent > thread.to_account_info().lamports() { + transfer( + CpiContext::new( + system_program.to_account_info(), + Transfer { + from: authority.to_account_info(), + to: thread.to_account_info(), + }, + ), + minimum_rent + .checked_sub(thread.to_account_info().lamports()) + .unwrap(), + )?; + } + + Ok(()) +} diff --git a/programs/thread/src/instructions/thread_dummy_ix.rs b/programs/thread/src/instructions/thread_dummy_ix.rs new file mode 100644 index 000000000..e555fb058 --- /dev/null +++ b/programs/thread/src/instructions/thread_dummy_ix.rs @@ -0,0 +1,13 @@ +use anchor_lang::prelude::*; + +#[derive(Accounts)] +pub struct ThreadDummyIx<'info> { + /// CHECK: thread + pub thread: UncheckedAccount<'info>, +} + +pub fn handler (ctx: Context) -> Result<()> { + msg!("Hello, Clockwork Thread: {:#?}", ctx.accounts.thread.key); + Ok(()) +} + diff --git a/programs/thread/src/lib.rs b/programs/thread/src/lib.rs index 859b9e670..8d4816666 100644 --- a/programs/thread/src/lib.rs +++ b/programs/thread/src/lib.rs @@ -116,4 +116,23 @@ pub mod thread_program { thread_lookup_tables_delete::handler(ctx) } + /// Allows the user create big instruction + pub fn big_instruction_create( + ctx: Context, + id: Vec, + instruction_data: Vec, + no_of_accounts: u8, + ) -> Result<()> { + big_instruction_create::handler(ctx, id, instruction_data, no_of_accounts) + } + + /// Allows the user add big instrucion to the thread + pub fn thread_big_instruction_add(ctx: Context) -> Result<()> { + thread_big_instruction_add::handler(ctx) + } + + /// Dummy instruction + pub fn thread_dummy_ix(ctx: Context) -> Result<()> { + thread_dummy_ix::handler(ctx) + } } diff --git a/programs/thread/src/state/big_instruction.rs b/programs/thread/src/state/big_instruction.rs new file mode 100644 index 000000000..879af9220 --- /dev/null +++ b/programs/thread/src/state/big_instruction.rs @@ -0,0 +1,47 @@ +use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize}; +use clockwork_utils::thread::SerializableAccount; + +pub const SEED_BIG_INST: &[u8] = b"big-instruction"; + +#[account] +#[derive(Debug)] +pub struct BigInstruction { + pub authority: Pubkey, + pub id: Vec, + pub program_id: Pubkey, + pub accounts: Vec, + pub data: Vec, + pub bump: u8, +} + +impl BigInstruction { + /// Derive the pubkey of a BigInstruction. + pub fn pubkey(authority: Pubkey, program_id: Pubkey, id: Vec) -> Pubkey { + Pubkey::find_program_address( + &[SEED_BIG_INST, authority.as_ref(), program_id.as_ref(), id.as_slice()], + &crate::ID, + ) + .0 + } +} + +// todo: implement partialeq +impl PartialEq for BigInstruction { + fn eq(&self, other: &Self) -> bool { + self.authority.eq(&other.authority) && self.program_id.eq(&other.program_id) && self.id.eq(&other.id) + } +} + +impl Eq for BigInstruction {} + +/// Trait for reading and writing to BigInstruction. +pub trait BigInstructionAccount { + /// Get the pubkey of the big instruction. + fn pubkey(&self) -> Pubkey; +} + +impl BigInstructionAccount for Account<'_, BigInstruction> { + fn pubkey(&self) -> Pubkey { + BigInstruction::pubkey(self.authority, self.program_id, self.id.clone()) + } +} \ No newline at end of file diff --git a/programs/thread/src/state/mod.rs b/programs/thread/src/state/mod.rs index abdb6af05..ee46cfc05 100644 --- a/programs/thread/src/state/mod.rs +++ b/programs/thread/src/state/mod.rs @@ -3,8 +3,10 @@ mod thread; mod versioned_thread; mod lookup_tables; +mod big_instruction; pub use clockwork_utils::thread::*; pub use thread::*; pub use versioned_thread::*; pub use lookup_tables::*; +pub use big_instruction::*; diff --git a/programs/thread/src/state/thread.rs b/programs/thread/src/state/thread.rs index d01f7b126..f463bc498 100644 --- a/programs/thread/src/state/thread.rs +++ b/programs/thread/src/state/thread.rs @@ -8,7 +8,7 @@ pub use clockwork_utils::thread::Equality; pub const SEED_THREAD: &[u8] = b"thread"; /// Static space for next_instruction field. -pub const NEXT_INSTRUCTION_SIZE: usize = 1232; +pub const NEXT_INSTRUCTION_SIZE: usize = 3200; /// Tracks the current state of a transaction thread on Solana. #[account] diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index dbab56c2c..e1f3b3efb 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -10,16 +10,17 @@ pub mod state { } pub mod utils { - pub use clockwork_thread_program::state::PAYER_PUBKEY; pub use clockwork_thread_program::state::Equality; + pub use clockwork_thread_program::state::PAYER_PUBKEY; } pub mod cpi { - use anchor_lang::prelude::{CpiContext, Result, Pubkey}; + use anchor_lang::prelude::{CpiContext, Pubkey, Result}; pub use clockwork_thread_program::cpi::accounts::{ - ThreadCreate, ThreadDelete, ThreadPause, ThreadReset, ThreadResume, ThreadUpdate, - ThreadWithdraw, LookupTablesAdd, LookupTablesCreate, LookupTablesDelete, LookupTablesRemove + BigInstructionCreate, LookupTablesAdd, LookupTablesCreate, LookupTablesDelete, + LookupTablesRemove, ThreadBigInstructionAdd, ThreadCreate, ThreadDelete, ThreadDummyIx, + ThreadPause, ThreadReset, ThreadResume, ThreadUpdate, ThreadWithdraw, }; pub fn thread_create<'info>( @@ -69,7 +70,7 @@ pub mod cpi { ) -> Result<()> { clockwork_thread_program::cpi::thread_withdraw(ctx, amount) } - + pub fn thread_lookup_tables_create<'info>( ctx: CpiContext<'_, '_, '_, 'info, LookupTablesCreate<'info>>, address_lookup_tables: Vec, @@ -83,17 +84,43 @@ pub mod cpi { ) -> Result<()> { clockwork_thread_program::cpi::thread_lookup_tables_add(ctx, address_lookup_tables) } - + pub fn thread_lookup_tables_remove<'info>( ctx: CpiContext<'_, '_, '_, 'info, LookupTablesRemove<'info>>, index: u64, ) -> Result<()> { clockwork_thread_program::cpi::thread_lookup_tables_remove(ctx, index) } - + pub fn thread_lookup_tables_delete<'info>( ctx: CpiContext<'_, '_, '_, 'info, LookupTablesDelete<'info>>, ) -> Result<()> { clockwork_thread_program::cpi::thread_lookup_tables_delete(ctx) } + + pub fn big_instruction_create<'info>( + ctx: CpiContext<'_, '_, '_, 'info, BigInstructionCreate<'info>>, + id: Vec, + instruction_data: Vec, + no_of_accounts: u8, + ) -> Result<()> { + clockwork_thread_program::cpi::big_instruction_create( + ctx, + id, + instruction_data, + no_of_accounts, + ) + } + + pub fn thread_big_instruction_add<'info>( + ctx: CpiContext<'_, '_, '_, 'info, ThreadBigInstructionAdd<'info>>, + ) -> Result<()> { + clockwork_thread_program::cpi::thread_big_instruction_add(ctx) + } + + pub fn thread_dummy_ix<'info>( + ctx: CpiContext<'_, '_, '_, 'info, ThreadDummyIx<'info>>, + ) -> Result<()> { + clockwork_thread_program::cpi::thread_dummy_ix(ctx) + } } From 5ba86052a52957f3ec7f0ebb4ba60ba55e6e2fb8 Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Wed, 7 Jun 2023 04:30:49 +0100 Subject: [PATCH 7/8] big instruction size limit --- Cargo.lock | 1 + programs/thread/Cargo.toml | 1 + programs/thread/src/errors.rs | 4 ++++ .../thread/src/instructions/thread_big_instruction_add.rs | 8 +++++++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8e783328b..94af61752 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1206,6 +1206,7 @@ name = "clockwork-thread-program" version = "2.0.17" dependencies = [ "anchor-lang", + "bincode", "chrono", "clockwork-cron", "clockwork-network-program", diff --git a/programs/thread/Cargo.toml b/programs/thread/Cargo.toml index 26444929c..899448ef2 100644 --- a/programs/thread/Cargo.toml +++ b/programs/thread/Cargo.toml @@ -31,3 +31,4 @@ clockwork-utils = { path = "../../utils", version = "=2.0.17" } pyth-sdk-solana = "0.7.1" static-pubkey = "1.0.3" version = "3.0.0" +bincode = "1.3.3" diff --git a/programs/thread/src/errors.rs b/programs/thread/src/errors.rs index 90d36f60f..6abcce4c7 100644 --- a/programs/thread/src/errors.rs +++ b/programs/thread/src/errors.rs @@ -47,4 +47,8 @@ pub enum ClockworkError { /// Thrown if number of accounts passed into big instruction does not match the number of account specified when creating big instruction #[msg("Number of accounts specified does not match the number of accounts passed ")] AccountsLengthMismatch, + + /// Thrown if the size of the instruction to be added to the thread is larger than the next instruction size + #[msg("Instruction too large for thread")] + InstructionTooLarge, } diff --git a/programs/thread/src/instructions/thread_big_instruction_add.rs b/programs/thread/src/instructions/thread_big_instruction_add.rs index 1cf23d822..8d0f7a315 100644 --- a/programs/thread/src/instructions/thread_big_instruction_add.rs +++ b/programs/thread/src/instructions/thread_big_instruction_add.rs @@ -1,10 +1,12 @@ +use bincode::serialize; + use anchor_lang::{ prelude::*, solana_program::system_program, system_program::{transfer, Transfer}, }; -use crate::state::*; +use crate::{errors::ClockworkError, state::*}; /// Accounts required by the `thread_instruction_add` instruction. #[derive(Accounts)] @@ -54,6 +56,10 @@ pub fn handler( program_id: ix_program_id.clone(), }; + // Check if the instruction hit next instruction size limit + let ix_size = serialize(&build_ix).unwrap().len(); + require!(ix_size <= NEXT_INSTRUCTION_SIZE, ClockworkError::InstructionTooLarge); + // Append the instruction. thread.instructions.push(build_ix); From 1bc5964fe69f6b9007ed2bbe98c7b8d0ccedd23e Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Wed, 7 Jun 2023 15:29:40 +0100 Subject: [PATCH 8/8] BigInstruction unnecessary --- programs/thread/src/errors.rs | 4 - .../instructions/big_instruction_create.rs | 75 ------------------- programs/thread/src/instructions/mod.rs | 2 - .../thread_big_instruction_add.rs | 22 +++--- programs/thread/src/lib.rs | 14 +--- programs/thread/src/state/big_instruction.rs | 47 ------------ programs/thread/src/state/mod.rs | 2 - sdk/src/lib.rs | 19 +---- 8 files changed, 17 insertions(+), 168 deletions(-) delete mode 100644 programs/thread/src/instructions/big_instruction_create.rs delete mode 100644 programs/thread/src/state/big_instruction.rs diff --git a/programs/thread/src/errors.rs b/programs/thread/src/errors.rs index 6abcce4c7..1212f6c47 100644 --- a/programs/thread/src/errors.rs +++ b/programs/thread/src/errors.rs @@ -44,10 +44,6 @@ pub enum ClockworkError { #[msg("Withdrawing this amount would leave the thread with less than the minimum required SOL for rent exemption")] WithdrawalTooLarge, - /// Thrown if number of accounts passed into big instruction does not match the number of account specified when creating big instruction - #[msg("Number of accounts specified does not match the number of accounts passed ")] - AccountsLengthMismatch, - /// Thrown if the size of the instruction to be added to the thread is larger than the next instruction size #[msg("Instruction too large for thread")] InstructionTooLarge, diff --git a/programs/thread/src/instructions/big_instruction_create.rs b/programs/thread/src/instructions/big_instruction_create.rs deleted file mode 100644 index d47a513e3..000000000 --- a/programs/thread/src/instructions/big_instruction_create.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::mem::size_of; - -use anchor_lang::{ - prelude::*, - solana_program::system_program, -}; - -use crate::{errors::ClockworkError, state::*}; - -/// Accounts required by the `thread_lookup_tables_create` instruction. -#[derive(Accounts)] -#[instruction(id: Vec, instruction_data: Vec, no_of_accounts: u8)] -pub struct BigInstructionCreate<'info> { - /// The authority (owner) of the thread. - #[account()] - pub authority: Signer<'info>, - - /// The payer for account initializations. - #[account(mut)] - pub payer: Signer<'info>, - - /// The Solana system program. - #[account(address = system_program::ID)] - pub system_program: Program<'info, System>, - - /// CHECK: program_id of the instruction to build - #[account(executable)] - pub instruction_program_id: UncheckedAccount<'info>, - - /// The lookup_tables account to be created. - #[account( - init, - seeds = [ - SEED_BIG_INST, - authority.key().as_ref(), - instruction_program_id.key().as_ref(), - id.as_slice(), - ], - bump, - payer= payer, - space = vec![ - 8, - size_of::(), - id.len(), - instruction_data.try_to_vec()?.len(), - (no_of_accounts as usize) * size_of::(), - ].iter().sum() - )] - pub big_instruction: Account<'info, BigInstruction>, -} - -pub fn handler(ctx: Context, id: Vec, instruction_data: Vec, no_of_accounts: u8) -> Result<()> { - // Get accounts - require!((no_of_accounts as usize) == ctx.remaining_accounts.len(), ClockworkError::AccountsLengthMismatch); - - let authority = &ctx.accounts.authority; - let program_id = &ctx.accounts.instruction_program_id; - let big_instruction = &mut ctx.accounts.big_instruction; - let accounts = ctx.remaining_accounts.into_iter().map(|acct_info| SerializableAccount { - is_signer: acct_info.is_signer, // false - is_writable: acct_info.is_writable, - pubkey: acct_info.key() - }).collect::>(); - - // Initialize the thread - let bump = *ctx.bumps.get("big_instruction").unwrap(); - big_instruction.authority = authority.key(); - big_instruction.bump = bump; - big_instruction.id = id; - big_instruction.data = instruction_data; - big_instruction.program_id = program_id.key(); - big_instruction.accounts = accounts; - - Ok(()) -} diff --git a/programs/thread/src/instructions/mod.rs b/programs/thread/src/instructions/mod.rs index f6a2474e0..f3b58552e 100644 --- a/programs/thread/src/instructions/mod.rs +++ b/programs/thread/src/instructions/mod.rs @@ -14,7 +14,6 @@ pub mod thread_reset; pub mod thread_resume; pub mod thread_update; pub mod thread_withdraw; -pub mod big_instruction_create; pub mod thread_big_instruction_add; pub mod thread_dummy_ix; @@ -34,6 +33,5 @@ pub use thread_reset::*; pub use thread_resume::*; pub use thread_update::*; pub use thread_withdraw::*; -pub use big_instruction_create::*; pub use thread_big_instruction_add::*; pub use thread_dummy_ix::*; diff --git a/programs/thread/src/instructions/thread_big_instruction_add.rs b/programs/thread/src/instructions/thread_big_instruction_add.rs index 8d0f7a315..d2b7f6740 100644 --- a/programs/thread/src/instructions/thread_big_instruction_add.rs +++ b/programs/thread/src/instructions/thread_big_instruction_add.rs @@ -32,28 +32,30 @@ pub struct ThreadBigInstructionAdd<'info> { )] pub thread: Account<'info, Thread>, - #[account( - has_one=authority - )] - pub big_instruction: Account<'info, BigInstruction>, + /// CHECK: program_id of the instruction to build + #[account(executable)] + pub instruction_program_id: UncheckedAccount<'info>, } pub fn handler( ctx: Context, + instruction_data: Vec, ) -> Result<()> { // Get accounts let authority = &ctx.accounts.authority; let thread = &mut ctx.accounts.thread; let system_program = &ctx.accounts.system_program; - let ix_data = &ctx.accounts.big_instruction.data; - let ix_program_id = &ctx.accounts.big_instruction.program_id; - let ix_accounts = &ctx.accounts.big_instruction.accounts; + let ix_accounts = ctx.remaining_accounts.into_iter().map(|acct_info| SerializableAccount { + is_signer: acct_info.is_signer, // false + is_writable: acct_info.is_writable, + pubkey: acct_info.key() + }).collect::>(); let build_ix = SerializableInstruction { - accounts: ix_accounts.clone(), - data: ix_data.clone(), - program_id: ix_program_id.clone(), + accounts: ix_accounts, + data: instruction_data, + program_id: ctx.accounts.instruction_program_id.key(), }; // Check if the instruction hit next instruction size limit diff --git a/programs/thread/src/lib.rs b/programs/thread/src/lib.rs index 8d4816666..750d31f39 100644 --- a/programs/thread/src/lib.rs +++ b/programs/thread/src/lib.rs @@ -116,19 +116,9 @@ pub mod thread_program { thread_lookup_tables_delete::handler(ctx) } - /// Allows the user create big instruction - pub fn big_instruction_create( - ctx: Context, - id: Vec, - instruction_data: Vec, - no_of_accounts: u8, - ) -> Result<()> { - big_instruction_create::handler(ctx, id, instruction_data, no_of_accounts) - } - /// Allows the user add big instrucion to the thread - pub fn thread_big_instruction_add(ctx: Context) -> Result<()> { - thread_big_instruction_add::handler(ctx) + pub fn thread_big_instruction_add(ctx: Context, instruction_data: Vec) -> Result<()> { + thread_big_instruction_add::handler(ctx, instruction_data) } /// Dummy instruction diff --git a/programs/thread/src/state/big_instruction.rs b/programs/thread/src/state/big_instruction.rs deleted file mode 100644 index 879af9220..000000000 --- a/programs/thread/src/state/big_instruction.rs +++ /dev/null @@ -1,47 +0,0 @@ -use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize}; -use clockwork_utils::thread::SerializableAccount; - -pub const SEED_BIG_INST: &[u8] = b"big-instruction"; - -#[account] -#[derive(Debug)] -pub struct BigInstruction { - pub authority: Pubkey, - pub id: Vec, - pub program_id: Pubkey, - pub accounts: Vec, - pub data: Vec, - pub bump: u8, -} - -impl BigInstruction { - /// Derive the pubkey of a BigInstruction. - pub fn pubkey(authority: Pubkey, program_id: Pubkey, id: Vec) -> Pubkey { - Pubkey::find_program_address( - &[SEED_BIG_INST, authority.as_ref(), program_id.as_ref(), id.as_slice()], - &crate::ID, - ) - .0 - } -} - -// todo: implement partialeq -impl PartialEq for BigInstruction { - fn eq(&self, other: &Self) -> bool { - self.authority.eq(&other.authority) && self.program_id.eq(&other.program_id) && self.id.eq(&other.id) - } -} - -impl Eq for BigInstruction {} - -/// Trait for reading and writing to BigInstruction. -pub trait BigInstructionAccount { - /// Get the pubkey of the big instruction. - fn pubkey(&self) -> Pubkey; -} - -impl BigInstructionAccount for Account<'_, BigInstruction> { - fn pubkey(&self) -> Pubkey { - BigInstruction::pubkey(self.authority, self.program_id, self.id.clone()) - } -} \ No newline at end of file diff --git a/programs/thread/src/state/mod.rs b/programs/thread/src/state/mod.rs index ee46cfc05..abdb6af05 100644 --- a/programs/thread/src/state/mod.rs +++ b/programs/thread/src/state/mod.rs @@ -3,10 +3,8 @@ mod thread; mod versioned_thread; mod lookup_tables; -mod big_instruction; pub use clockwork_utils::thread::*; pub use thread::*; pub use versioned_thread::*; pub use lookup_tables::*; -pub use big_instruction::*; diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index e1f3b3efb..77a994885 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -18,7 +18,7 @@ pub mod cpi { use anchor_lang::prelude::{CpiContext, Pubkey, Result}; pub use clockwork_thread_program::cpi::accounts::{ - BigInstructionCreate, LookupTablesAdd, LookupTablesCreate, LookupTablesDelete, + LookupTablesAdd, LookupTablesCreate, LookupTablesDelete, LookupTablesRemove, ThreadBigInstructionAdd, ThreadCreate, ThreadDelete, ThreadDummyIx, ThreadPause, ThreadReset, ThreadResume, ThreadUpdate, ThreadWithdraw, }; @@ -98,24 +98,11 @@ pub mod cpi { clockwork_thread_program::cpi::thread_lookup_tables_delete(ctx) } - pub fn big_instruction_create<'info>( - ctx: CpiContext<'_, '_, '_, 'info, BigInstructionCreate<'info>>, - id: Vec, - instruction_data: Vec, - no_of_accounts: u8, - ) -> Result<()> { - clockwork_thread_program::cpi::big_instruction_create( - ctx, - id, - instruction_data, - no_of_accounts, - ) - } - pub fn thread_big_instruction_add<'info>( ctx: CpiContext<'_, '_, '_, 'info, ThreadBigInstructionAdd<'info>>, + instruction_data: Vec ) -> Result<()> { - clockwork_thread_program::cpi::thread_big_instruction_add(ctx) + clockwork_thread_program::cpi::thread_big_instruction_add(ctx, instruction_data) } pub fn thread_dummy_ix<'info>(