diff --git a/Cargo.lock b/Cargo.lock index 60888a6ee3..36e07c219a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,7 +402,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi 0.3.9", ] @@ -934,8 +934,8 @@ checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" dependencies = [ "atty", "bitflags", - "clap_derive", - "clap_lex", + "clap_derive 3.2.18", + "clap_lex 0.2.4", "indexmap", "once_cell", "strsim 0.10.0", @@ -943,6 +943,30 @@ dependencies = [ "textwrap 0.15.1", ] +[[package]] +name = "clap" +version = "4.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "906f7fe1da4185b7a282b2bc90172a496f9def1aca4545fe7526810741591e14" +dependencies = [ + "clap_builder", + "clap_derive 4.1.14", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351f9ad9688141ed83dfd8f5fb998a06225ef444b48ff4dc43de6d409b7fd10b" +dependencies = [ + "bitflags", + "clap_lex 0.4.0", + "is-terminal", + "strsim 0.10.0", + "termcolor", +] + [[package]] name = "clap_derive" version = "3.2.18" @@ -956,6 +980,18 @@ dependencies = [ "syn 1.0.103", ] +[[package]] +name = "clap_derive" +version = "4.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d7dc0031c3a59a04fc2ba395c8e2dd463cba1859275f065d225f6122221b45" +dependencies = [ + "heck 0.4.0", + "proc-macro2 1.0.53", + "quote 1.0.26", + "syn 2.0.9", +] + [[package]] name = "clap_lex" version = "0.2.4" @@ -965,6 +1001,12 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f0807fb6f644c83f3e4ec014fec9858c1c8b26a7db8eb5f0bde5817df9c1df7" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1653,7 +1695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517" dependencies = [ "cfg-if 1.0.0", - "rustix", + "rustix 0.35.11", "windows-sys 0.36.1", ] @@ -2072,6 +2114,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -2371,12 +2419,35 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "ipnet" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +[[package]] +name = "is-terminal" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes 1.0.9", + "rustix 0.36.11", + "windows-sys 0.45.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -2713,6 +2784,12 @@ version = "0.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -3062,7 +3139,7 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -4253,12 +4330,26 @@ checksum = "fbb2fda4666def1433b1b05431ab402e42a1084285477222b72d6c564c417cef" dependencies = [ "bitflags", "errno", - "io-lifetimes", + "io-lifetimes 0.7.3", "libc", - "linux-raw-sys", + "linux-raw-sys 0.0.46", "windows-sys 0.36.1", ] +[[package]] +name = "rustix" +version = "0.36.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes 1.0.9", + "libc", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", +] + [[package]] name = "rustls" version = "0.19.1" @@ -6643,7 +6734,7 @@ name = "solana-tip-distributor" version = "1.14.17" dependencies = [ "anchor-lang", - "clap 3.2.22", + "clap 4.1.14", "env_logger", "futures 0.3.25", "im", @@ -8338,19 +8429,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" @@ -8360,9 +8475,9 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" @@ -8372,9 +8487,9 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" @@ -8384,9 +8499,9 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" @@ -8396,15 +8511,15 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" @@ -8414,9 +8529,9 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "winreg" diff --git a/tip-distributor/Cargo.toml b/tip-distributor/Cargo.toml index 90245d4751..e49e6e5c4f 100644 --- a/tip-distributor/Cargo.toml +++ b/tip-distributor/Cargo.toml @@ -7,7 +7,7 @@ description = "Collection of binaries used to distribute MEV rewards to delegato [dependencies] anchor-lang = { path = "../anchor/lang" } -clap = { version = "3.2.5", features = ["derive", "env"] } +clap = { version = "4.1.11", features = ["derive", "env"] } env_logger = "0.9.0" futures = "0.3.21" im = "15.1.0" @@ -45,3 +45,7 @@ path = "src/bin/merkle-root-uploader.rs" [[bin]] name = "solana-claim-mev-tips" path = "src/bin/claim-mev-tips.rs" + +[[bin]] +name = "solana-reclaim-rent" +path = "src/bin/reclaim-rent.rs" diff --git a/tip-distributor/src/bin/claim-mev-tips.rs b/tip-distributor/src/bin/claim-mev-tips.rs index 45b515dbd1..4a9a789509 100644 --- a/tip-distributor/src/bin/claim-mev-tips.rs +++ b/tip-distributor/src/bin/claim-mev-tips.rs @@ -9,22 +9,22 @@ use { }; #[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] +#[command(author, version, about, long_about = None)] struct Args { /// Path to JSON file containing the [GeneratedMerkleTreeCollection] object. - #[clap(long, env)] + #[arg(long, env)] merkle_trees_path: PathBuf, /// RPC to send transactions through - #[clap(long, env)] + #[arg(long, env)] rpc_url: String, /// Tip distribution program ID - #[clap(long, env)] + #[arg(long, env)] tip_distribution_program_id: String, /// Path to keypair - #[clap(long, env)] + #[arg(long, env)] keypair_path: PathBuf, } diff --git a/tip-distributor/src/bin/merkle-root-generator.rs b/tip-distributor/src/bin/merkle-root-generator.rs index f9e9072bed..18c5d4e09a 100644 --- a/tip-distributor/src/bin/merkle-root-generator.rs +++ b/tip-distributor/src/bin/merkle-root-generator.rs @@ -8,14 +8,14 @@ use { }; #[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] +#[command(author, version, about, long_about = None)] struct Args { /// Path to JSON file containing the [StakeMetaCollection] object. - #[clap(long, env)] + #[arg(long, env)] stake_meta_coll_path: PathBuf, /// Path to JSON file to get populated with tree node data. - #[clap(long, env)] + #[arg(long, env)] out_path: PathBuf, } diff --git a/tip-distributor/src/bin/merkle-root-uploader.rs b/tip-distributor/src/bin/merkle-root-uploader.rs index f23e8f74b2..9fcd7b8ed7 100644 --- a/tip-distributor/src/bin/merkle-root-uploader.rs +++ b/tip-distributor/src/bin/merkle-root-uploader.rs @@ -7,22 +7,22 @@ use { }; #[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] +#[command(author, version, about, long_about = None)] struct Args { /// Path to JSON file containing the [StakeMetaCollection] object. - #[clap(long, env)] + #[arg(long, env)] merkle_root_path: PathBuf, /// The path to the keypair used to sign and pay for the `upload_merkle_root` transactions. - #[clap(long, env)] + #[arg(long, env)] keypair_path: PathBuf, /// The RPC to send transactions to. - #[clap(long, env)] + #[arg(long, env)] rpc_url: String, /// Tip distribution program ID - #[clap(long, env)] + #[arg(long, env)] tip_distribution_program_id: String, } diff --git a/tip-distributor/src/bin/reclaim-rent.rs b/tip-distributor/src/bin/reclaim-rent.rs new file mode 100644 index 0000000000..96e9b0a96a --- /dev/null +++ b/tip-distributor/src/bin/reclaim-rent.rs @@ -0,0 +1,59 @@ +//! Reclaims rent from TDAs and Claim Status accounts. + +use { + clap::Parser, + log::*, + solana_client::nonblocking::rpc_client::RpcClient, + solana_sdk::{ + commitment_config::CommitmentConfig, pubkey::Pubkey, signature::read_keypair_file, + }, + solana_tip_distributor::reclaim_rent_workflow::reclaim_rent, + std::{path::PathBuf, str::FromStr, time::Duration}, + tokio::runtime::Runtime, +}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// RPC to send transactions through. + /// NOTE: This script uses getProgramAccounts, make sure you have added an account index + /// for the tip_distribution_program_id on the RPC node. + #[arg(long, env)] + rpc_url: String, + + /// Tip distribution program ID. + #[arg(long, env, value_parser = Pubkey::from_str)] + tip_distribution_program_id: Pubkey, + + /// The keypair signing and paying for transactions. + #[arg(long, env)] + keypair_path: PathBuf, + + /// Specifies whether to reclaim rent on behalf of validators from respective TDAs. + #[arg(long, env)] + should_reclaim_tdas: bool, +} + +fn main() { + env_logger::init(); + + info!("Starting to claim mev tips..."); + let args: Args = Args::parse(); + + let runtime = Runtime::new().unwrap(); + if let Err(e) = runtime.block_on(reclaim_rent( + RpcClient::new_with_timeout_and_commitment( + args.rpc_url, + // High timeout b/c of get_program_accounts call + Duration::from_secs(60), + CommitmentConfig::confirmed(), + ), + args.tip_distribution_program_id, + read_keypair_file(&args.keypair_path).expect("read keypair file"), + args.should_reclaim_tdas, + )) { + panic!("error reclaiming rent: {e:?}"); + } + + info!("done reclaiming all rent",); +} diff --git a/tip-distributor/src/bin/stake-meta-generator.rs b/tip-distributor/src/bin/stake-meta-generator.rs index 365f82290e..be7993be02 100644 --- a/tip-distributor/src/bin/stake-meta-generator.rs +++ b/tip-distributor/src/bin/stake-meta-generator.rs @@ -15,26 +15,26 @@ use { }; #[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] +#[command(author, version, about, long_about = None)] struct Args { /// Ledger path, where you created the snapshot. - #[clap(long, env, parse(try_from_str = Args::ledger_path_parser))] + #[arg(long, env, value_parser = Args::ledger_path_parser)] ledger_path: PathBuf, /// The tip-distribution program id. - #[clap(long, env)] + #[arg(long, env)] tip_distribution_program_id: Pubkey, /// The tip-payment program id. - #[clap(long, env)] + #[arg(long, env)] tip_payment_program_id: Pubkey, /// Path to JSON file populated with the [StakeMetaCollection] object. - #[clap(long, env)] + #[arg(long, env)] out_path: String, /// The expected snapshot slot. - #[clap(long, env)] + #[arg(long, env)] snapshot_slot: Slot, } diff --git a/tip-distributor/src/claim_mev_workflow.rs b/tip-distributor/src/claim_mev_workflow.rs index cc991c78da..e5551ee944 100644 --- a/tip-distributor/src/claim_mev_workflow.rs +++ b/tip-distributor/src/claim_mev_workflow.rs @@ -141,7 +141,10 @@ pub fn claim_mev_tips( info!("Sending {} tip claim transactions. {} tried sending zero lamports, {} would be below minimum rent", &transactions.len(), zero_lamports_count, below_min_rent_count); - send_transactions_with_retry(&rpc_client, &transactions, MAX_RETRY_DURATION).await; + let num_failed_txs = send_transactions_with_retry(&rpc_client, &transactions, MAX_RETRY_DURATION).await; + if num_failed_txs != 0 { + panic!("failed to send {num_failed_txs} transactions"); + } }); Ok(()) diff --git a/tip-distributor/src/lib.rs b/tip-distributor/src/lib.rs index a576b33587..14036cc927 100644 --- a/tip-distributor/src/lib.rs +++ b/tip-distributor/src/lib.rs @@ -1,6 +1,7 @@ pub mod claim_mev_workflow; pub mod merkle_root_generator_workflow; pub mod merkle_root_upload_workflow; +pub mod reclaim_rent_workflow; pub mod stake_meta_generator_workflow; use { @@ -411,7 +412,7 @@ pub async fn send_transactions_with_retry( rpc_client: &RpcClient, transactions: &[Transaction], max_send_duration: Duration, -) { +) -> usize { let mut transactions_to_send: HashMap = transactions .iter() .map(|tx| (tx.signatures[0], tx.clone())) @@ -459,13 +460,7 @@ pub async fn send_transactions_with_retry( } } - if !transactions_to_send.is_empty() { - panic!( - "failed to send {} of {} transactions", - transactions_to_send.len(), - transactions.len() - ); - } + transactions_to_send.len() } mod pubkey_string_conversion { diff --git a/tip-distributor/src/merkle_root_upload_workflow.rs b/tip-distributor/src/merkle_root_upload_workflow.rs index 24d4ab62dd..447a621418 100644 --- a/tip-distributor/src/merkle_root_upload_workflow.rs +++ b/tip-distributor/src/merkle_root_upload_workflow.rs @@ -132,7 +132,10 @@ pub fn upload_merkle_root( ) }) .collect(); - send_transactions_with_retry(&rpc_client, &transactions, MAX_RETRY_DURATION).await; + let num_failed_txs = send_transactions_with_retry(&rpc_client, &transactions, MAX_RETRY_DURATION).await; + if num_failed_txs != 0 { + panic!("failed to send {num_failed_txs} transactions"); + } }); Ok(()) diff --git a/tip-distributor/src/reclaim_rent_workflow.rs b/tip-distributor/src/reclaim_rent_workflow.rs new file mode 100644 index 0000000000..12928e357d --- /dev/null +++ b/tip-distributor/src/reclaim_rent_workflow.rs @@ -0,0 +1,174 @@ +use { + crate::send_transactions_with_retry, + anchor_lang::AccountDeserialize, + log::info, + solana_client::nonblocking::rpc_client::RpcClient, + solana_program::pubkey::Pubkey, + solana_sdk::{ + signature::{Keypair, Signer}, + transaction::Transaction, + }, + std::{ + error::Error, + time::{Duration, Instant}, + }, + tip_distribution::{ + sdk::{ + derive_config_account_address, + instruction::{ + close_claim_status_ix, close_tip_distribution_account_ix, CloseClaimStatusAccounts, + CloseClaimStatusArgs, CloseTipDistributionAccountArgs, + CloseTipDistributionAccounts, + }, + }, + state::{ClaimStatus, Config, TipDistributionAccount}, + }, +}; + +pub async fn reclaim_rent( + rpc_client: RpcClient, + tip_distribution_program_id: Pubkey, + signer: Keypair, + // Optionally reclaim TipDistributionAccount rents on behalf of validators. + should_reclaim_tdas: bool, +) -> Result<(), Box> { + info!("fetching program accounts..."); + let now = Instant::now(); + let accounts = rpc_client + .get_program_accounts(&tip_distribution_program_id) + .await?; + info!( + "get_program_accounts took {}ms and fetched {} accounts", + now.elapsed().as_millis(), + accounts.len() + ); + + info!("fetching current_epoch..."); + let current_epoch = rpc_client.get_epoch_info().await?.epoch; + info!("current_epoch: {current_epoch}"); + + info!("fetching config_account..."); + let now = Instant::now(); + let config_pubkey = derive_config_account_address(&tip_distribution_program_id).0; + let config_account = rpc_client.get_account(&config_pubkey).await?; + let config_account: Config = Config::try_deserialize(&mut config_account.data.as_slice())?; + info!("fetch config_account took {}ms", now.elapsed().as_millis()); + + info!("filtering for claim_status accounts"); + let claim_status_accounts: Vec<(Pubkey, ClaimStatus)> = accounts + .iter() + .filter_map(|(pubkey, account)| { + let claim_status = ClaimStatus::try_deserialize(&mut account.data.as_slice()).ok()?; + Some((*pubkey, claim_status)) + }) + .filter(|(_, claim_status): &(Pubkey, ClaimStatus)| { + // Only return claim statuses that we've paid for and ones that are expired to avoid transaction failures. + claim_status.claim_status_payer == signer.pubkey() + && current_epoch > claim_status.expires_at + }) + .collect::>(); + info!( + "{} claim_status accounts eligible for rent reclaim", + claim_status_accounts.len() + ); + + info!("fetching recent_blockhash"); + let now = Instant::now(); + let recent_blockhash = rpc_client.get_latest_blockhash().await?; + info!( + "fetch recent_blockhash took {}ms, hash={recent_blockhash:?}", + now.elapsed().as_millis() + ); + + info!("creating close_claim_status_account transactions"); + let now = Instant::now(); + let mut transactions = claim_status_accounts + .into_iter() + .map(|(claim_status_pubkey, claim_status)| { + close_claim_status_ix( + tip_distribution_program_id, + CloseClaimStatusArgs, + CloseClaimStatusAccounts { + config: config_pubkey, + claim_status: claim_status_pubkey, + claim_status_payer: claim_status.claim_status_payer, + }, + ) + }) + .collect::>() + .chunks(4) + .into_iter() + .map(|instructions| { + Transaction::new_signed_with_payer( + instructions, + Some(&signer.pubkey()), + &[&signer], + recent_blockhash, + ) + }) + .collect::>(); + + info!( + "create close_claim_status_account transactions took {}us", + now.elapsed().as_micros() + ); + + if should_reclaim_tdas { + let tip_distribution_accounts: Vec<(Pubkey, TipDistributionAccount)> = accounts + .iter() + .filter_map(|(pubkey, account)| { + let tda = + TipDistributionAccount::try_deserialize(&mut account.data.as_slice()).ok()?; + Some((*pubkey, tda)) + }) + .filter(|(_, tda): &(Pubkey, TipDistributionAccount)| current_epoch > tda.expires_at) + .collect::>(); + + info!("creating close_tip_distribution_account transactions"); + let now = Instant::now(); + let close_tda_txs = tip_distribution_accounts + .into_iter() + .map(|(tda_pubkey, tda)| { + close_tip_distribution_account_ix( + tip_distribution_program_id, + CloseTipDistributionAccountArgs { + _epoch: tda.epoch_created_at, + }, + CloseTipDistributionAccounts { + config: config_pubkey, + tip_distribution_account: tda_pubkey, + validator_vote_account: tda.validator_vote_account, + expired_funds_account: config_account.expired_funds_account, + signer: signer.pubkey(), + }, + ) + }) + .collect::>() + .chunks(4) + .map(|instructions| { + Transaction::new_signed_with_payer( + instructions, + Some(&signer.pubkey()), + &[&signer], + recent_blockhash, + ) + }) + .collect::>(); + info!("create close_tip_distribution_account transactions took {}us, closing {} tip distribution accounts", now.elapsed().as_micros(), close_tda_txs.len()); + + transactions.extend(close_tda_txs); + } + + info!("sending {} transactions", transactions.len()); + let num_failed_txs = send_transactions_with_retry( + &rpc_client, + transactions.as_slice(), + Duration::from_secs(60), + ) + .await; + if num_failed_txs != 0 { + panic!("failed to send {num_failed_txs} transactions"); + } + + Ok(()) +}