diff --git a/Cargo.lock b/Cargo.lock index 88faf017..ef526ca1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -691,9 +691,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "tinyvec", ] @@ -2424,9 +2424,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "ore-cli" -version = "0.3.2" +version = "0.3.3" dependencies = [ "bincode", + "bs58 0.5.1", "cached", "chrono", "clap 4.4.12", @@ -2445,9 +2446,11 @@ dependencies = [ [[package]] name = "ore-program" -version = "1.1.0" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1438154357d3952bd1d8f683d7ded7977a317b86495031313a6a8799afe2ec8" dependencies = [ - "bs58 0.5.0", + "bs58 0.5.1", "bytemuck", "mpl-token-metadata", "num_enum 0.7.2", diff --git a/Cargo.toml b/Cargo.toml index 9fd33c49..7a2178fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,13 @@ admin = [] [dependencies] bincode = "1.3.3" +bs58 = "0.5.1" cached = "0.46.1" chrono = "0.4.34" clap = { version = "4.4.12", features = ["derive"] } futures = "0.3.30" log = "0.4" -ore = { version = "1.1.0", package = "ore-program" } +ore = { version = "1.2.0", package = "ore-program" } solana-cli-config = "1.18.5" solana-client = "^1.16" solana-program = "^1.16" diff --git a/src/mine.rs b/src/mine.rs index 4e497d97..362a3ffe 100644 --- a/src/mine.rs +++ b/src/mine.rs @@ -65,6 +65,7 @@ impl Miner { // Submit mine tx. let mut bus_id = 0; let mut invalid_busses: Vec = vec![]; + let mut needs_reset = false; 'submit: loop { // Find a valid bus. if invalid_busses.len().eq(&(BUS_COUNT as usize)) { @@ -75,6 +76,7 @@ impl Miner { println!("Bus {} is empty... ", bus_id); bus_id += 1; if bus_id.ge(&(BUS_COUNT as u8)) { + std::thread:sleep(Duration::from_secs(1)); bus_id = 0; } } @@ -83,11 +85,12 @@ impl Miner { let treasury = get_treasury(self.cluster.clone()).await; let clock = get_clock_account(self.cluster.clone()).await; let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); - if clock.unix_timestamp.ge(&threshold) { + if clock.unix_timestamp.ge(&threshold) || needs_reset { let reset_ix = ore::instruction::reset(signer.pubkey()); self.send_and_confirm(&[reset_ix]) .await .expect("Transaction failed"); + needs_reset = false; } // Submit request. @@ -102,33 +105,26 @@ impl Miner { stdout.write(format!("Success: {}", sig).as_bytes()).ok(); break; } - Err(err) => { - match err.kind { - ClientErrorKind::RpcError(err) => { - // TODO Why is BusInsufficientFunds an RpcError but EpochNeedsReset is a TransactionError ? - // Unhandled error Error { request: None, kind: TransactionError(InstructionError(0, Custom(6003))) } - // thread 'main' panicked at 'Failed to submit transaction: SolanaClientError(Error { request: None, kind: TransactionError(InstructionError(0, Custom(6000))) })', src/main.rs:193:26 - if err.to_string().contains("custom program error: 0x5") { - // Bus has no remaining funds. Use a different one. - invalid_busses.push(bus_id); - } else if err - .to_string() - .contains("This transaction has already been processed") - { - break 'submit; - } else { - stdout - .write_all(format!("\n{:?} \n", err.to_string()).as_bytes()) - .ok(); - } - } - _ => { + Err(err) => match err.kind { + ClientErrorKind::Custom(msg) => { + if msg.contains("Bus insufficient") { + invalid_busses.push(bus_id); + } else if msg.contains("Needs reset") { + needs_reset = true; + } else if msg.contains("Hash invalid") { + break 'submit; + } else { stdout - .write_all(format!("\nUnhandled error {:?} \n", err).as_bytes()) + .write_all(format!("\n{:?} \n", msg.to_string()).as_bytes()) .ok(); } } - } + _ => { + stdout + .write_all(format!("\nUnhandled error {:?} \n", err).as_bytes()) + .ok(); + } + }, } } } diff --git a/src/send_and_confirm.rs b/src/send_and_confirm.rs index cff54a7b..d12b3fa4 100644 --- a/src/send_and_confirm.rs +++ b/src/send_and_confirm.rs @@ -6,18 +6,22 @@ use std::{ use solana_client::{ client_error::{ClientError, ClientErrorKind, Result as ClientResult}, nonblocking::rpc_client::RpcClient, + rpc_config::{RpcSendTransactionConfig, RpcSimulateTransactionConfig}, }; -use solana_program::instruction::Instruction; +use solana_program::instruction::{Instruction, InstructionError}; use solana_sdk::{ - commitment_config::CommitmentConfig, + commitment_config::{CommitmentConfig, CommitmentLevel}, compute_budget::ComputeBudgetInstruction, signature::{Signature, Signer}, - transaction::Transaction, + transaction::{Transaction, TransactionError}, }; +use solana_transaction_status::UiTransactionEncoding; use crate::Miner; +const RPC_RETRIES: usize = 1; const GATEWAY_RETRIES: usize = 10; +const CONFIRM_RETRIES: usize = 10; impl Miner { pub async fn send_and_confirm(&self, ixs: &[Instruction]) -> ClientResult { @@ -27,21 +31,84 @@ impl Miner { RpcClient::new_with_commitment(self.cluster.clone(), CommitmentConfig::confirmed()); // Build tx - let mut hash = client.get_latest_blockhash().await.unwrap(); + let (mut hash, mut slot) = client + .get_latest_blockhash_with_commitment(CommitmentConfig::confirmed()) + .await + .unwrap(); + let mut send_cfg = RpcSendTransactionConfig { + skip_preflight: true, + preflight_commitment: Some(CommitmentLevel::Confirmed), + encoding: Some(UiTransactionEncoding::Base64), + max_retries: Some(RPC_RETRIES), + min_context_slot: Some(slot), + }; + let mut tx = Transaction::new_with_payer(ixs, Some(&signer.pubkey())); tx.sign(&[&signer], hash); // Sim and prepend cu ixs - let sim_res = client.simulate_transaction(&tx).await; - let final_ixs = if let Ok(sim_res) = sim_res { - let cu_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit( - sim_res.value.units_consumed.unwrap() as u32 + 1000, - ); - let cu_price_ix = ComputeBudgetInstruction::set_compute_unit_price(self.priority_fee); - let mut final_ixs = vec![]; - final_ixs.extend_from_slice(&[cu_budget_ix, cu_price_ix]); - final_ixs.extend_from_slice(ixs); - final_ixs + let sim_res = client + .simulate_transaction_with_config( + &tx, + RpcSimulateTransactionConfig { + sig_verify: false, + replace_recent_blockhash: false, + commitment: Some(CommitmentConfig::confirmed()), + encoding: Some(UiTransactionEncoding::Base64), + accounts: None, + min_context_slot: Some(slot), + inner_instructions: false, + }, + ) + .await; + if let Ok(sim_res) = sim_res { + match sim_res.value.err { + Some(err) => match err { + TransactionError::InstructionError(_, InstructionError::Custom(e)) => { + if e == 1 { + log::info!("Needs reset!"); + return Err(ClientError { + request: None, + kind: ClientErrorKind::Custom("Needs reset".into()), + }); + } else if e == 3 { + log::info!("Hash invalid!"); + return Err(ClientError { + request: None, + kind: ClientErrorKind::Custom("Hash invalid".into()), + }); + } else if e == 5 { + return Err(ClientError { + request: None, + kind: ClientErrorKind::Custom("Bus insufficient".into()), + }); + } else { + return Err(ClientError { + request: None, + kind: ClientErrorKind::Custom("Sim failed".into()), + }); + } + } + _ => { + return Err(ClientError { + request: None, + kind: ClientErrorKind::Custom("Sim failed".into()), + }) + } + }, + None => { + let cu_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit( + sim_res.value.units_consumed.unwrap() as u32 + 1000, + ); + let cu_price_ix = + ComputeBudgetInstruction::set_compute_unit_price(self.priority_fee); + let mut final_ixs = vec![]; + final_ixs.extend_from_slice(&[cu_budget_ix, cu_price_ix]); + final_ixs.extend_from_slice(ixs); + tx = Transaction::new_with_payer(&final_ixs, Some(&signer.pubkey())); + tx.sign(&[&signer], hash); + } + } } else { return Err(ClientError { request: None, @@ -49,18 +116,44 @@ impl Miner { }); }; - // Rebuild tx with cu ixs - tx = Transaction::new_with_payer(&final_ixs, Some(&signer.pubkey())); - tx.sign(&[&signer], hash); - // Loop let mut attempts = 0; loop { println!("Attempt: {:?}", attempts); - match client.send_and_confirm_transaction(&tx).await { + match client.send_transaction_with_config(&tx, send_cfg).await { Ok(sig) => { - println!("Confirmed: {:?}", sig); - return Ok(sig); + log::info!("{:?}", sig); + let mut confirm_check = 0; + 'confirm: loop { + match client + .confirm_transaction_with_commitment( + &sig, + CommitmentConfig::confirmed(), + ) + .await + { + Ok(confirmed) => { + log::info!( + "Confirm check {:?}: {:?}", + confirm_check, + confirmed.value + ); + if confirmed.value { + return Ok(sig); + } + } + Err(err) => { + log::error!("Err: {:?}", err); + } + } + + // Retry confirm + std::thread::sleep(Duration::from_millis(500)); + confirm_check += 1; + if confirm_check.gt(&CONFIRM_RETRIES) { + break 'confirm; + } + } } Err(err) => { println!("Error {:?}", err); @@ -70,7 +163,17 @@ impl Miner { // Retry with new hash std::thread::sleep(Duration::from_millis(1000)); - hash = client.get_latest_blockhash().await.unwrap(); + (hash, slot) = client + .get_latest_blockhash_with_commitment(CommitmentConfig::confirmed()) + .await + .unwrap(); + send_cfg = RpcSendTransactionConfig { + skip_preflight: true, + preflight_commitment: Some(CommitmentLevel::Confirmed), + encoding: Some(UiTransactionEncoding::Base64), + max_retries: Some(RPC_RETRIES), + min_context_slot: Some(slot), + }; tx.sign(&[&signer], hash); attempts += 1; if attempts > GATEWAY_RETRIES { diff --git a/src/update_difficulty.rs b/src/update_difficulty.rs index 676a01eb..1deee4bc 100644 --- a/src/update_difficulty.rs +++ b/src/update_difficulty.rs @@ -6,11 +6,17 @@ use crate::Miner; impl Miner { pub async fn update_difficulty(&self) { let signer = self.signer(); + // let new_difficulty = KeccakHash::new_from_array([ + // 0, 0, 0, 64, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + // 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + // ]); let new_difficulty = KeccakHash::new_from_array([ - 0, 0, 0, 32, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ]); let ix = ore::instruction::update_difficulty(signer.pubkey(), new_difficulty.into()); + // let bs58data = bs58::encode(ix.data).into_string(); + // println!("Data: {:?}", bs58data); self.send_and_confirm(&[ix]) .await .expect("Transaction failed");