From 0308bb9268f13d36fbc3f92a374abb17287e17e5 Mon Sep 17 00:00:00 2001 From: Kriptikz Date: Wed, 24 Jul 2024 21:22:35 -0500 Subject: [PATCH 01/18] add DeclareProof instruction --- api/src/instruction.rs | 4 ++++ program/src/declare_proof.rs | 34 ++++++++++++++++++++++++++++++++++ program/src/lib.rs | 3 +++ 3 files changed, 41 insertions(+) create mode 100644 program/src/declare_proof.rs diff --git a/api/src/instruction.rs b/api/src/instruction.rs index fdf0469..7d46f32 100644 --- a/api/src/instruction.rs +++ b/api/src/instruction.rs @@ -89,6 +89,10 @@ pub enum OreInstruction { #[account(6, name = "mint_v1", desc = "Ore v1 token mint account", writable)] #[account(7, name = "token_program", desc = "SPL token program")] Upgrade = 7, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "proof", desc = "Ore proof account")] + DeclareProof = 8, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] diff --git a/program/src/declare_proof.rs b/program/src/declare_proof.rs new file mode 100644 index 0000000..3786155 --- /dev/null +++ b/program/src/declare_proof.rs @@ -0,0 +1,34 @@ +use solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + program_error::ProgramError, + pubkey::Pubkey, +}; + + +/// DeclareProof is used by other instructions in the same transaction. +/// - Other instructions will use transaction introspection to ensure they +/// only process the declared proof. +/// - Other instructions that need to validate the declared proof will only +/// look at the first and second instructions in the transaction +/// +/// Safety requirements: +/// - No safety requirements are required in this instruction to keep cu's as +/// low as possible. Other instructions that use the declared proof handle +/// validation via the loader. +pub fn process_declare_proof<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + _data: &[u8], +) -> ProgramResult { + // Ensure one account info is provided + // Validation of this proof is handled by the mine ix + let [_proof_info] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + Ok(()) +} + diff --git a/program/src/lib.rs b/program/src/lib.rs index 909a87a..1846e63 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,6 +1,7 @@ mod claim; mod close; mod initialize; +mod declare_proof; mod mine; mod open; mod reset; @@ -11,6 +12,7 @@ mod upgrade; use claim::*; use close::*; use initialize::*; +use declare_proof::*; use mine::*; use open::*; use reset::*; @@ -45,6 +47,7 @@ pub fn process_instruction( match OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? { OreInstruction::Claim => process_claim(program_id, accounts, data)?, OreInstruction::Close => process_close(program_id, accounts, data)?, + OreInstruction::DeclareProof => process_declare_proof(program_id, accounts, data)?, OreInstruction::Mine => process_mine(program_id, accounts, data)?, OreInstruction::Open => process_open(program_id, accounts, data)?, OreInstruction::Reset => process_reset(program_id, accounts, data)?, From ef675df3ea74fdedb7b2bb3ed0624ef498197f43 Mon Sep 17 00:00:00 2001 From: Kriptikz Date: Wed, 24 Jul 2024 22:21:08 -0500 Subject: [PATCH 02/18] add declare_proof instruction builder --- api/src/instruction.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/api/src/instruction.rs b/api/src/instruction.rs index 7d46f32..f461785 100644 --- a/api/src/instruction.rs +++ b/api/src/instruction.rs @@ -228,6 +228,19 @@ pub fn close(signer: Pubkey) -> Instruction { } } +/// Builds a declare proof instruction. +pub fn declare_proof( + proof: Pubkey, +) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new_readonly(proof, false), + ], + data: OreInstruction::DeclareProof.to_vec(), + } +} + /// Builds a mine instruction. pub fn mine( signer: Pubkey, From 67383ec8c09bc2f895d98c97e4bbba25bee78607 Mon Sep 17 00:00:00 2001 From: Kriptikz Date: Wed, 24 Jul 2024 22:22:25 -0500 Subject: [PATCH 03/18] add find_and_parse_declared_proof transaction introspection helper --- program/src/declare_proof.rs | 50 +++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/program/src/declare_proof.rs b/program/src/declare_proof.rs index 3786155..b99fb23 100644 --- a/program/src/declare_proof.rs +++ b/program/src/declare_proof.rs @@ -3,26 +3,27 @@ use solana_program::{ entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, + sanitize::SanitizeError, + serialize_utils::{read_pubkey, read_u16}, }; /// DeclareProof is used by other instructions in the same transaction. /// - Other instructions will use transaction introspection to ensure they /// only process the declared proof. -/// - Other instructions that need to validate the declared proof will only -/// look at the first and second instructions in the transaction +/// - Other instructions that need to get the declared proof for validation +/// will only look at the first and second instructions in the transaction /// /// Safety requirements: /// - No safety requirements are required in this instruction to keep cu's as /// low as possible. Other instructions that use the declared proof handle /// validation via the loader. +/// - Only one account should be provided. pub fn process_declare_proof<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], _data: &[u8], ) -> ProgramResult { - // Ensure one account info is provided - // Validation of this proof is handled by the mine ix let [_proof_info] = accounts else { @@ -32,3 +33,44 @@ pub fn process_declare_proof<'a, 'info>( Ok(()) } +/// Errors if: +/// - Fails to find and parse the declared proof pubkey in the second instruction +/// of the transaction +pub fn find_and_parse_declared_proof(data: &[u8]) -> Result { + // start the current byte index at 0 + let mut current = 0; + let num_instructions = read_u16(&mut current, data)?; + + if num_instructions < 2 { + // Not enough instructions in this transaction. + // The declare_proof instruction must be included with + // the other instruction in the same transaction. + return Err(SanitizeError::IndexOutOfBounds) + } + + // The first ix should be a set compute budget ix + // The second ix should be a declare_proof ix + // The starting index is the second ix + let index = 1; + + current += index * 2; + let start = read_u16(&mut current, data)?; + + current = start as usize; + + current += 2; // skip the accounts length variable + current += 1; // skip the meta_byte + + // The only account provided should be the proof pubkey + let proof_pubkey = read_pubkey(&mut current, data)?; + + // There shouldn't be any more accounts, + // this should be the Ore program ID + let program_pubkey = read_pubkey(&mut current, data)?; + if program_pubkey.eq(&ore_api::ID) { + return Ok(proof_pubkey); + } else { + return Err(SanitizeError::InvalidValue); + } +} + From 49d5e95dbb72712f7b8367874b1e3820b6179dbe Mon Sep 17 00:00:00 2001 From: Kriptikz Date: Wed, 24 Jul 2024 22:26:07 -0500 Subject: [PATCH 04/18] update mine transaction introspection to use declared proof --- api/src/error.rs | 4 ++++ program/src/mine.rs | 30 ++++++++++++++++-------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/api/src/error.rs b/api/src/error.rs index e3d8ee0..f334a77 100644 --- a/api/src/error.rs +++ b/api/src/error.rs @@ -27,6 +27,10 @@ pub enum OreError { CannotClose = 9, #[error("This account cannot be crowned because its last stake was too recent")] CannotCrown = 10, + #[error("Only the declared proof can be processed in this transaction")] + DeclaredProofMissmatch = 11, + #[error("Failed to find and parse the declared proof from the transaction")] + FindAndParseDeclaredProofFailed = 12, } impl From for ProgramError { diff --git a/program/src/mine.rs b/program/src/mine.rs index ea7673a..b8a12ce 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -25,7 +25,7 @@ use solana_program::{ sysvar::{self, instructions::load_current_index, Sysvar}, }; -use crate::utils::AccountDeserialize; +use crate::{find_and_parse_declared_proof, utils::AccountDeserialize}; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: /// 1. Calculate the hash from the provided nonce. @@ -61,10 +61,21 @@ pub fn process_mine<'a, 'info>( load_sysvar(instructions_sysvar, sysvar::instructions::id())?; load_sysvar(slot_hashes_sysvar, sysvar::slot_hashes::id())?; - // Validate this is the only mine ix in the transaction. - if !introspect_transaction(&instructions_sysvar.data.borrow()).unwrap_or(false) { - return Err(OreError::TransactionInvalid.into()); - } + /// Require that only the declared proof can be processed in this transaction. + /// + /// The intent here is to disincentivize sybil. As long as a user can fit multiple hashes in a single + /// transaction, there is a financial incentive to sybil multiple keypairs and pack as many hashes + /// as possible into each transaction to minimize fee / hash. + /// + /// If each transaction is limited to one hash only, then a user will minimize their fee / hash + /// by allocating all their hashpower to finding the single most difficult hash they can. + if let Ok(pubkey) = find_and_parse_declared_proof(&instructions_sysvar.data.borrow()) { + if !pubkey.eq(proof_info.key) { + return Err(OreError::DeclaredProofMissmatch.into()); + } + } else { + return Err(OreError::FindAndParseDeclaredProofFailed.into()); + }; // Validate epoch is active. let config_data = config_info.data.borrow(); @@ -182,15 +193,6 @@ pub fn process_mine<'a, 'info>( Ok(()) } -/// Require that there is only one `mine` instruction per transaction and it is called from the -/// top level of the transaction. -/// -/// The intent here is to disincentivize sybil. As long as a user can fit multiple hashes in a single -/// transaction, there is a financial incentive to sybil multiple keypairs and pack as many hashes -/// as possible into each transaction to minimize fee / hash. -/// -/// If each transaction is limited to one hash only, then a user will minimize their fee / hash -/// by allocating all their hashpower to finding the single most difficult hash they can. fn introspect_transaction(msg: &[u8]) -> Result { #[allow(deprecated)] let idx = load_current_index(msg); From 97a7d0cb28359cccd214c8a63471818162293ae3 Mon Sep 17 00:00:00 2001 From: Kriptikz Date: Wed, 24 Jul 2024 22:38:29 -0500 Subject: [PATCH 05/18] update comments --- program/src/declare_proof.rs | 13 +++++++++++-- program/src/mine.rs | 9 +-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/program/src/declare_proof.rs b/program/src/declare_proof.rs index b99fb23..9f4e458 100644 --- a/program/src/declare_proof.rs +++ b/program/src/declare_proof.rs @@ -11,8 +11,8 @@ use solana_program::{ /// DeclareProof is used by other instructions in the same transaction. /// - Other instructions will use transaction introspection to ensure they /// only process the declared proof. -/// - Other instructions that need to get the declared proof for validation -/// will only look at the first and second instructions in the transaction +/// - Other instructions will use find_and_parse_declared_proof with the +/// introspection data /// /// Safety requirements: /// - No safety requirements are required in this instruction to keep cu's as @@ -33,6 +33,15 @@ pub fn process_declare_proof<'a, 'info>( Ok(()) } +/// Require that only the declared proof can be processed in this transaction. +/// +/// The intent here is to disincentivize sybil. As long as a user can fit multiple hashes in a single +/// transaction, there is a financial incentive to sybil multiple keypairs and pack as many hashes +/// as possible into each transaction to minimize fee / hash. +/// +/// If each transaction is limited to one hash only, then a user will minimize their fee / hash +/// by allocating all their hashpower to finding the single most difficult hash they can. +/// /// Errors if: /// - Fails to find and parse the declared proof pubkey in the second instruction /// of the transaction diff --git a/program/src/mine.rs b/program/src/mine.rs index b8a12ce..6f70ea8 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -38,6 +38,7 @@ use crate::{find_and_parse_declared_proof, utils::AccountDeserialize}; /// - Can only succeed if mining is not paused. /// - Can only succeed if the last reset was less than 60 seconds ago. /// - Can only succeed if the provided hash satisfies the minimum difficulty requirement. +/// - Can only succeed if the miners proof pubkey matches the declared proof pubkey. /// - The provided proof account must be associated with the signer. /// - The provided bus, config, noise, stake, and slot hash sysvar must be valid. pub fn process_mine<'a, 'info>( @@ -61,14 +62,6 @@ pub fn process_mine<'a, 'info>( load_sysvar(instructions_sysvar, sysvar::instructions::id())?; load_sysvar(slot_hashes_sysvar, sysvar::slot_hashes::id())?; - /// Require that only the declared proof can be processed in this transaction. - /// - /// The intent here is to disincentivize sybil. As long as a user can fit multiple hashes in a single - /// transaction, there is a financial incentive to sybil multiple keypairs and pack as many hashes - /// as possible into each transaction to minimize fee / hash. - /// - /// If each transaction is limited to one hash only, then a user will minimize their fee / hash - /// by allocating all their hashpower to finding the single most difficult hash they can. if let Ok(pubkey) = find_and_parse_declared_proof(&instructions_sysvar.data.borrow()) { if !pubkey.eq(proof_info.key) { return Err(OreError::DeclaredProofMissmatch.into()); From ae6b98253a6f7fe687fd8c9f26704e6c93f01600 Mon Sep 17 00:00:00 2001 From: Kriptikz Date: Wed, 24 Jul 2024 23:03:20 -0500 Subject: [PATCH 06/18] update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9159df8..938c80e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ - [`Claim`](program/src/claim.rs) – Distributes claimable rewards as tokens from the treasury to a miner. - [`Close`](program/src/close.rs) – Closes a proof account returns the rent to the owner. - [`Open`](program/src/open.rs) – Creates a new proof account for a prospective miner. +- [`DeclareProof`](program/src/declare_proof.rs) – Declares a single proof pubkey for processing. - [`Mine`](program/src/mine.rs) – Verifies a hash provided by a miner and issues claimable rewards. - [`Stake`](program/src/stake.rs) – Stakes ORE with a miner to increase their multiplier. - [`Reset`](program/src/reset.rs) – Resets the program for a new epoch. From e890e9c3124c21e93433022a156eca59564a02d2 Mon Sep 17 00:00:00 2001 From: Kriptikz Date: Wed, 24 Jul 2024 23:08:07 -0500 Subject: [PATCH 07/18] remove old mine fn introspect_transaction --- program/src/mine.rs | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 6f70ea8..001b18b 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -186,41 +186,3 @@ pub fn process_mine<'a, 'info>( Ok(()) } -fn introspect_transaction(msg: &[u8]) -> Result { - #[allow(deprecated)] - let idx = load_current_index(msg); - let mut c = 0; - let num_instructions = read_u16(&mut c, msg)?; - let pc = c; - for i in 0..num_instructions as usize { - c = pc + i * 2; - c = read_u16(&mut c, msg)? as usize; - let num_accounts = read_u16(&mut c, msg)? as usize; - c += num_accounts * 33; - let program_id = read_pubkey(&mut c, msg)?; - if i.eq(&(idx as usize)) { - // Require top-level instruction at current index is a `mine` - if program_id.ne(&ore_api::ID) { - return Ok(false); - } - c += 2; - if let Ok(ix) = OreInstruction::try_from(read_u8(&mut c, msg)?) { - if ix.ne(&OreInstruction::Mine) { - return Ok(false); - } - } - } else { - // Require no other instructions in the transaction are a `mine` - if program_id.eq(&ore_api::ID) { - c += 2; - if let Ok(ix) = OreInstruction::try_from(read_u8(&mut c, msg)?) { - if ix.eq(&OreInstruction::Mine) { - return Ok(false); - } - } - } - } - } - - Ok(true) -} From 3bfe8506f44b8748f265944adf3717e8822403aa Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 26 Jul 2024 21:26:47 +0000 Subject: [PATCH 08/18] auth --- api/src/error.rs | 10 +---- api/src/instruction.rs | 36 +++++++--------- program/src/declare_proof.rs | 82 ++++++++++++++++++------------------ program/src/lib.rs | 6 +-- program/src/mine.rs | 20 ++++----- 5 files changed, 70 insertions(+), 84 deletions(-) diff --git a/api/src/error.rs b/api/src/error.rs index f334a77..d4873b6 100644 --- a/api/src/error.rs +++ b/api/src/error.rs @@ -23,14 +23,8 @@ pub enum OreError { ToleranceOverflow = 7, #[error("The maximum supply has been reached")] MaxSupply = 8, - #[error("This account cannot be closed because it's the top staker")] - CannotClose = 9, - #[error("This account cannot be crowned because its last stake was too recent")] - CannotCrown = 10, - #[error("Only the declared proof can be processed in this transaction")] - DeclaredProofMissmatch = 11, - #[error("Failed to find and parse the declared proof from the transaction")] - FindAndParseDeclaredProofFailed = 12, + #[error("The proof does not match the expected account")] + AuthFailed = 9, } impl From for ProgramError { diff --git a/api/src/instruction.rs b/api/src/instruction.rs index f461785..5ccb7db 100644 --- a/api/src/instruction.rs +++ b/api/src/instruction.rs @@ -17,6 +17,10 @@ use crate::{ #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] #[rustfmt::skip] pub enum OreInstruction { + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "proof", desc = "Ore proof account")] + Auth = 0, + #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "beneficiary", desc = "Beneficiary token account", writable)] @@ -24,13 +28,13 @@ pub enum OreInstruction { #[account(4, name = "treasury", desc = "Ore treasury account", writable)] #[account(5, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(6, name = "token_program", desc = "SPL token program")] - Claim = 0, + Claim = 1, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "proof", desc = "Ore proof account", writable)] #[account(3, name = "system_program", desc = "Solana system program")] - Close = 1, + Close = 2, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -39,7 +43,7 @@ pub enum OreInstruction { #[account(4, name = "noise", desc = "Ore noise account")] #[account(5, name = "proof", desc = "Ore proof account", writable)] #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] - Mine = 2, + Mine = 3, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -48,7 +52,7 @@ pub enum OreInstruction { #[account(4, name = "proof", desc = "Ore proof account", writable)] #[account(5, name = "system_program", desc = "Solana system program")] #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] - Open = 3, + Open = 4, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -65,7 +69,7 @@ pub enum OreInstruction { #[account(12, name = "treasury", desc = "Ore treasury account", writable)] #[account(13, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(14, name = "token_program", desc = "SPL token program")] - Reset = 4, + Reset = 5, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -73,12 +77,12 @@ pub enum OreInstruction { #[account(3, name = "sender", desc = "Signer token account", writable)] #[account(4, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(5, name = "token_program", desc = "SPL token program")] - Stake = 5, + Stake = 6, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "proof", desc = "Ore proof account", writable)] - Update = 6, + Update = 7, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -88,11 +92,7 @@ pub enum OreInstruction { #[account(5, name = "mint", desc = "Ore token mint account", writable)] #[account(6, name = "mint_v1", desc = "Ore v1 token mint account", writable)] #[account(7, name = "token_program", desc = "SPL token program")] - Upgrade = 7, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "proof", desc = "Ore proof account")] - DeclareProof = 8, + Upgrade = 8, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] @@ -228,16 +228,12 @@ pub fn close(signer: Pubkey) -> Instruction { } } -/// Builds a declare proof instruction. -pub fn declare_proof( - proof: Pubkey, -) -> Instruction { +/// Builds an auth instruction. +pub fn auth(proof: Pubkey) -> Instruction { Instruction { program_id: crate::id(), - accounts: vec![ - AccountMeta::new_readonly(proof, false), - ], - data: OreInstruction::DeclareProof.to_vec(), + accounts: vec![AccountMeta::new_readonly(proof, false)], + data: OreInstruction::Auth.to_vec(), } } diff --git a/program/src/declare_proof.rs b/program/src/declare_proof.rs index 9f4e458..c46d45e 100644 --- a/program/src/declare_proof.rs +++ b/program/src/declare_proof.rs @@ -1,17 +1,18 @@ +use ore_api::instruction::OreInstruction; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey, pubkey::Pubkey, sanitize::SanitizeError, - serialize_utils::{read_pubkey, read_u16}, + serialize_utils::{read_pubkey, read_u16, read_u8}, }; - /// DeclareProof is used by other instructions in the same transaction. /// - Other instructions will use transaction introspection to ensure they /// only process the declared proof. -/// - Other instructions will use find_and_parse_declared_proof with the +/// - Other instructions will use find_and_parse_declared_proof with the /// introspection data /// /// Safety requirements: @@ -19,20 +20,20 @@ use solana_program::{ /// low as possible. Other instructions that use the declared proof handle /// validation via the loader. /// - Only one account should be provided. -pub fn process_declare_proof<'a, 'info>( +pub fn process_auth<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], _data: &[u8], ) -> ProgramResult { - let [_proof_info] = - accounts - else { + let [_proof_info] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; Ok(()) } +const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = pubkey!("ComputeBudget111111111111111111111111111111"); + /// Require that only the declared proof can be processed in this transaction. /// /// The intent here is to disincentivize sybil. As long as a user can fit multiple hashes in a single @@ -45,41 +46,38 @@ pub fn process_declare_proof<'a, 'info>( /// Errors if: /// - Fails to find and parse the declared proof pubkey in the second instruction /// of the transaction -pub fn find_and_parse_declared_proof(data: &[u8]) -> Result { - // start the current byte index at 0 - let mut current = 0; - let num_instructions = read_u16(&mut current, data)?; - - if num_instructions < 2 { - // Not enough instructions in this transaction. - // The declare_proof instruction must be included with - // the other instruction in the same transaction. - return Err(SanitizeError::IndexOutOfBounds) +pub fn authenticate(data: &[u8]) -> Result, SanitizeError> { + // Start the current byte index at 0 + let mut curr = 0; + let num_instructions = read_u16(&mut curr, data)?; + let pc = curr; + for i in 0..num_instructions as usize { + curr = pc + i * 2; + curr = read_u16(&mut curr, data)? as usize; + let num_accounts = read_u16(&mut curr, data)? as usize; + let mut ac = curr; + curr += num_accounts * 33; + let program_id = read_pubkey(&mut curr, data)?; + if program_id.eq(&ore_api::ID) { + curr += 2; + if let Ok(ix) = OreInstruction::try_from(read_u8(&mut curr, data)?) { + // Validate ix is an auth + if ix.ne(&OreInstruction::Auth) { + return Ok(None); + } + // Valid num accounts is correct + if num_accounts.ne(&1) { + return Ok(None); + } + // Return provided pubkey + let address = read_pubkey(&mut ac, data)?; + return Ok(Some(address)); + } else { + // Otherwise return + return Ok(None); + } + } } - // The first ix should be a set compute budget ix - // The second ix should be a declare_proof ix - // The starting index is the second ix - let index = 1; - - current += index * 2; - let start = read_u16(&mut current, data)?; - - current = start as usize; - - current += 2; // skip the accounts length variable - current += 1; // skip the meta_byte - - // The only account provided should be the proof pubkey - let proof_pubkey = read_pubkey(&mut current, data)?; - - // There shouldn't be any more accounts, - // this should be the Ore program ID - let program_pubkey = read_pubkey(&mut current, data)?; - if program_pubkey.eq(&ore_api::ID) { - return Ok(proof_pubkey); - } else { - return Err(SanitizeError::InvalidValue); - } + Ok(None) } - diff --git a/program/src/lib.rs b/program/src/lib.rs index 1846e63..977b051 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,7 +1,7 @@ mod claim; mod close; -mod initialize; mod declare_proof; +mod initialize; mod mine; mod open; mod reset; @@ -11,8 +11,8 @@ mod upgrade; use claim::*; use close::*; -use initialize::*; use declare_proof::*; +use initialize::*; use mine::*; use open::*; use reset::*; @@ -45,9 +45,9 @@ pub fn process_instruction( .ok_or(ProgramError::InvalidInstructionData)?; match OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? { + OreInstruction::Auth => process_auth(program_id, accounts, data)?, OreInstruction::Claim => process_claim(program_id, accounts, data)?, OreInstruction::Close => process_close(program_id, accounts, data)?, - OreInstruction::DeclareProof => process_declare_proof(program_id, accounts, data)?, OreInstruction::Mine => process_mine(program_id, accounts, data)?, OreInstruction::Open => process_open(program_id, accounts, data)?, OreInstruction::Reset => process_reset(program_id, accounts, data)?, diff --git a/program/src/mine.rs b/program/src/mine.rs index 8749399..569f24c 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -5,7 +5,7 @@ use ore_api::{ consts::*, error::OreError, event::MineEvent, - instruction::{MineArgs, OreInstruction}, + instruction::MineArgs, loaders::*, state::{Bus, Config, Proof}, }; @@ -19,13 +19,11 @@ use solana_program::{ log::sol_log, program_error::ProgramError, pubkey::Pubkey, - sanitize::SanitizeError, - serialize_utils::{read_pubkey, read_u16, read_u8}, slot_hashes::SlotHash, - sysvar::{self, instructions::load_current_index, Sysvar}, + sysvar::{self, Sysvar}, }; -use crate::{find_and_parse_declared_proof, utils::AccountDeserialize}; +use crate::{authenticate, utils::AccountDeserialize}; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: /// 1. Calculate the hash from the provided nonce. @@ -62,13 +60,14 @@ pub fn process_mine<'a, 'info>( load_sysvar(instructions_sysvar, sysvar::instructions::id())?; load_sysvar(slot_hashes_sysvar, sysvar::slot_hashes::id())?; - if let Ok(pubkey) = find_and_parse_declared_proof(&instructions_sysvar.data.borrow()) { - if !pubkey.eq(proof_info.key) { - return Err(OreError::DeclaredProofMissmatch.into()); + // Authenticate the proof account + if let Ok(Some(auth_address)) = authenticate(&instructions_sysvar.data.borrow()) { + if auth_address.ne(proof_info.key) { + return Err(OreError::AuthFailed.into()); } } else { - return Err(OreError::FindAndParseDeclaredProofFailed.into()); - }; + return Err(OreError::AuthFailed.into()); + } // Validate epoch is active. let config_data = config_info.data.borrow(); @@ -185,4 +184,3 @@ pub fn process_mine<'a, 'info>( Ok(()) } - From 698192329b17b2049f648ea324728ef25afa210e Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 26 Jul 2024 21:30:35 +0000 Subject: [PATCH 09/18] move file --- program/src/{declare_proof.rs => auth.rs} | 0 program/src/lib.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename program/src/{declare_proof.rs => auth.rs} (100%) diff --git a/program/src/declare_proof.rs b/program/src/auth.rs similarity index 100% rename from program/src/declare_proof.rs rename to program/src/auth.rs diff --git a/program/src/lib.rs b/program/src/lib.rs index 977b051..1b48e8b 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,6 +1,6 @@ +mod auth; mod claim; mod close; -mod declare_proof; mod initialize; mod mine; mod open; @@ -9,9 +9,9 @@ mod stake; mod update; mod upgrade; +use auth::*; use claim::*; use close::*; -use declare_proof::*; use initialize::*; use mine::*; use open::*; From 8dbb6a7180d04c9ba4a15e8016dcead626521697 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 26 Jul 2024 21:30:57 +0000 Subject: [PATCH 10/18] remove unneeded id --- program/src/auth.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/program/src/auth.rs b/program/src/auth.rs index c46d45e..8872939 100644 --- a/program/src/auth.rs +++ b/program/src/auth.rs @@ -32,8 +32,6 @@ pub fn process_auth<'a, 'info>( Ok(()) } -const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = pubkey!("ComputeBudget111111111111111111111111111111"); - /// Require that only the declared proof can be processed in this transaction. /// /// The intent here is to disincentivize sybil. As long as a user can fit multiple hashes in a single From 54b3d9d91b521da490ab14e6c6cadde423bf5df1 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 26 Jul 2024 21:34:24 +0000 Subject: [PATCH 11/18] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 938c80e..f98259e 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ - [`Instruction`](api/src/instruction.rs) – Declared instructions and arguments. ## Instructions +- [`Auth`](program/src/auth.rs) – Declares a proof pubkey for processing for mining. - [`Claim`](program/src/claim.rs) – Distributes claimable rewards as tokens from the treasury to a miner. - [`Close`](program/src/close.rs) – Closes a proof account returns the rent to the owner. - [`Open`](program/src/open.rs) – Creates a new proof account for a prospective miner. -- [`DeclareProof`](program/src/declare_proof.rs) – Declares a single proof pubkey for processing. - [`Mine`](program/src/mine.rs) – Verifies a hash provided by a miner and issues claimable rewards. - [`Stake`](program/src/stake.rs) – Stakes ORE with a miner to increase their multiplier. - [`Reset`](program/src/reset.rs) – Resets the program for a new epoch. From e46ebcfdfc2dc8b1fd8188340f77b71cb265a491 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 26 Jul 2024 22:18:20 +0000 Subject: [PATCH 12/18] fix args --- program/src/auth.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/program/src/auth.rs b/program/src/auth.rs index 8872939..0cab01b 100644 --- a/program/src/auth.rs +++ b/program/src/auth.rs @@ -3,7 +3,6 @@ use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey, pubkey::Pubkey, sanitize::SanitizeError, serialize_utils::{read_pubkey, read_u16, read_u8}, @@ -20,11 +19,7 @@ use solana_program::{ /// low as possible. Other instructions that use the declared proof handle /// validation via the loader. /// - Only one account should be provided. -pub fn process_auth<'a, 'info>( - _program_id: &Pubkey, - accounts: &'a [AccountInfo<'info>], - _data: &[u8], -) -> ProgramResult { +pub fn process_auth<'a, 'info>(accounts: &'a [AccountInfo<'info>], _data: &[u8]) -> ProgramResult { let [_proof_info] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; From 722398f1371d99e51d5980773bed57d4bf5f9e0a Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 26 Jul 2024 22:52:19 +0000 Subject: [PATCH 13/18] update comment --- program/src/auth.rs | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/program/src/auth.rs b/program/src/auth.rs index 0cab01b..cc6fa10 100644 --- a/program/src/auth.rs +++ b/program/src/auth.rs @@ -27,48 +27,68 @@ pub fn process_auth<'a, 'info>(accounts: &'a [AccountInfo<'info>], _data: &[u8]) Ok(()) } -/// Require that only the declared proof can be processed in this transaction. +/// Get the authenticated pubkey. /// -/// The intent here is to disincentivize sybil. As long as a user can fit multiple hashes in a single +/// The intent here is to disincentivize sybil. If a user can fit multiple hashes into a single /// transaction, there is a financial incentive to sybil multiple keypairs and pack as many hashes /// as possible into each transaction to minimize fee / hash. /// /// If each transaction is limited to one hash only, then a user will minimize their fee / hash /// by allocating all their hashpower to finding the single most difficult hash they can. /// +/// We solve this by "authenticating" the proof account on every mine instruction. That is, +/// every transaction with a `mine` instruction needs to include an `auth` instruction that +/// specifies the proof account that will be used. The `auth` instruction must be first ORE +/// instruction in the transaction. The `mine` instruction should error out if the provided proof +/// account doesn't match the authenticated address. +/// /// Errors if: -/// - Fails to find and parse the declared proof pubkey in the second instruction -/// of the transaction +/// - Fails to find and parse an authentication address. pub fn authenticate(data: &[u8]) -> Result, SanitizeError> { // Start the current byte index at 0 let mut curr = 0; let num_instructions = read_u16(&mut curr, data)?; let pc = curr; + + // Iterate through the top-level instructions for i in 0..num_instructions as usize { + // Get byte counter curr = pc + i * 2; curr = read_u16(&mut curr, data)? as usize; + + // Read num accounts on this ix let num_accounts = read_u16(&mut curr, data)? as usize; - let mut ac = curr; + + // Hold a pointer to the first account in the accounts array + let mut ac = curr + 1; + + // Read the instruction program id curr += num_accounts * 33; let program_id = read_pubkey(&mut curr, data)?; + + // We only need to introspect on the first ore ix if program_id.eq(&ore_api::ID) { curr += 2; + + // Parse the instruction data if let Ok(ix) = OreInstruction::try_from(read_u8(&mut curr, data)?) { - // Validate ix is an auth + // Return immediately if the ix is not an auth if ix.ne(&OreInstruction::Auth) { return Ok(None); } - // Valid num accounts is correct + + // Valid the num accounts is expected if num_accounts.ne(&1) { return Ok(None); } - // Return provided pubkey + + // Return the address of the authenticated account let address = read_pubkey(&mut ac, data)?; return Ok(Some(address)); - } else { - // Otherwise return - return Ok(None); } + + // Return if instruction data can't be parsed + return Ok(None); } } From c26fde97ccce566dc8ea4b40bad2dec88cbd7694 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 26 Jul 2024 22:58:12 +0000 Subject: [PATCH 14/18] update comment --- program/src/auth.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/program/src/auth.rs b/program/src/auth.rs index cc6fa10..057441e 100644 --- a/program/src/auth.rs +++ b/program/src/auth.rs @@ -8,16 +8,11 @@ use solana_program::{ serialize_utils::{read_pubkey, read_u16, read_u8}, }; -/// DeclareProof is used by other instructions in the same transaction. -/// - Other instructions will use transaction introspection to ensure they -/// only process the declared proof. -/// - Other instructions will use find_and_parse_declared_proof with the -/// introspection data +/// Auth is used to authenticate a proof account address via transaction introspection. /// /// Safety requirements: -/// - No safety requirements are required in this instruction to keep cu's as -/// low as possible. Other instructions that use the declared proof handle -/// validation via the loader. +/// - No safety requirements are required in this instruction to keep CUs low. +/// - Other instructions are expected to validate the provided account is a valid proof. /// - Only one account should be provided. pub fn process_auth<'a, 'info>(accounts: &'a [AccountInfo<'info>], _data: &[u8]) -> ProgramResult { let [_proof_info] = accounts else { From 47a242f87ab62053c7f5bbb6a4e890a2916b502b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 29 Jul 2024 00:18:48 +0000 Subject: [PATCH 15/18] simplify based on feedback --- api/src/consts.rs | 8 ++++++ api/src/instruction.rs | 26 ++++++++----------- program/src/auth.rs | 58 ++++++++---------------------------------- program/src/lib.rs | 1 - 4 files changed, 29 insertions(+), 64 deletions(-) diff --git a/api/src/consts.rs b/api/src/consts.rs index 5ae12b9..d7d0470 100644 --- a/api/src/consts.rs +++ b/api/src/consts.rs @@ -150,3 +150,11 @@ pub const TREASURY_TOKENS_ADDRESS: Pubkey = Pubkey::new_from_array( ) .0, ); + +/// The address of the Solana compute budget program. +pub const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = + pubkey!("ComputeBudget111111111111111111111111111111"); + +/// The address of the CU-optimized Solana noop program. +// pub const NOOP_PROGRAM_ID: Pubkey = pubkey!("noop8ytexvkpCuqbf6FB89BSuNemHtPRqaNC31GWivW"); +pub const NOOP_PROGRAM_ID: Pubkey = pubkey!("noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV"); diff --git a/api/src/instruction.rs b/api/src/instruction.rs index 5ccb7db..ff4f5fa 100644 --- a/api/src/instruction.rs +++ b/api/src/instruction.rs @@ -17,10 +17,6 @@ use crate::{ #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] #[rustfmt::skip] pub enum OreInstruction { - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "proof", desc = "Ore proof account")] - Auth = 0, - #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "beneficiary", desc = "Beneficiary token account", writable)] @@ -28,13 +24,13 @@ pub enum OreInstruction { #[account(4, name = "treasury", desc = "Ore treasury account", writable)] #[account(5, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(6, name = "token_program", desc = "SPL token program")] - Claim = 1, + Claim = 0, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "proof", desc = "Ore proof account", writable)] #[account(3, name = "system_program", desc = "Solana system program")] - Close = 2, + Close = 1, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -43,7 +39,7 @@ pub enum OreInstruction { #[account(4, name = "noise", desc = "Ore noise account")] #[account(5, name = "proof", desc = "Ore proof account", writable)] #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] - Mine = 3, + Mine = 2, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -52,7 +48,7 @@ pub enum OreInstruction { #[account(4, name = "proof", desc = "Ore proof account", writable)] #[account(5, name = "system_program", desc = "Solana system program")] #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] - Open = 4, + Open = 3, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -69,7 +65,7 @@ pub enum OreInstruction { #[account(12, name = "treasury", desc = "Ore treasury account", writable)] #[account(13, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(14, name = "token_program", desc = "SPL token program")] - Reset = 5, + Reset = 4, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -77,12 +73,12 @@ pub enum OreInstruction { #[account(3, name = "sender", desc = "Signer token account", writable)] #[account(4, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(5, name = "token_program", desc = "SPL token program")] - Stake = 6, + Stake = 5, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "proof", desc = "Ore proof account", writable)] - Update = 7, + Update = 6, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -92,7 +88,7 @@ pub enum OreInstruction { #[account(5, name = "mint", desc = "Ore token mint account", writable)] #[account(6, name = "mint_v1", desc = "Ore v1 token mint account", writable)] #[account(7, name = "token_program", desc = "SPL token program")] - Upgrade = 8, + Upgrade = 7, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] @@ -231,9 +227,9 @@ pub fn close(signer: Pubkey) -> Instruction { /// Builds an auth instruction. pub fn auth(proof: Pubkey) -> Instruction { Instruction { - program_id: crate::id(), - accounts: vec![AccountMeta::new_readonly(proof, false)], - data: OreInstruction::Auth.to_vec(), + program_id: NOOP_PROGRAM_ID, + accounts: vec![], + data: proof.to_bytes().to_vec(), } } diff --git a/program/src/auth.rs b/program/src/auth.rs index 057441e..2489262 100644 --- a/program/src/auth.rs +++ b/program/src/auth.rs @@ -1,27 +1,11 @@ -use ore_api::instruction::OreInstruction; +use ore_api::consts::COMPUTE_BUDGET_PROGRAM_ID; use solana_program::{ - account_info::AccountInfo, - entrypoint::ProgramResult, - program_error::ProgramError, + pubkey, pubkey::Pubkey, sanitize::SanitizeError, - serialize_utils::{read_pubkey, read_u16, read_u8}, + serialize_utils::{read_pubkey, read_u16}, }; -/// Auth is used to authenticate a proof account address via transaction introspection. -/// -/// Safety requirements: -/// - No safety requirements are required in this instruction to keep CUs low. -/// - Other instructions are expected to validate the provided account is a valid proof. -/// - Only one account should be provided. -pub fn process_auth<'a, 'info>(accounts: &'a [AccountInfo<'info>], _data: &[u8]) -> ProgramResult { - let [_proof_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - Ok(()) -} - /// Get the authenticated pubkey. /// /// The intent here is to disincentivize sybil. If a user can fit multiple hashes into a single @@ -45,45 +29,23 @@ pub fn authenticate(data: &[u8]) -> Result, SanitizeError> { let num_instructions = read_u16(&mut curr, data)?; let pc = curr; - // Iterate through the top-level instructions + // Iterate through the transaction instructions for i in 0..num_instructions as usize { // Get byte counter curr = pc + i * 2; curr = read_u16(&mut curr, data)? as usize; - // Read num accounts on this ix - let num_accounts = read_u16(&mut curr, data)? as usize; - - // Hold a pointer to the first account in the accounts array - let mut ac = curr + 1; - // Read the instruction program id + let num_accounts = read_u16(&mut curr, data)? as usize; curr += num_accounts * 33; let program_id = read_pubkey(&mut curr, data)?; - // We only need to introspect on the first ore ix - if program_id.eq(&ore_api::ID) { + // Introspect on the first non compute budget instruction + if program_id.ne(&COMPUTE_BUDGET_PROGRAM_ID) { + // Read address from ix data curr += 2; - - // Parse the instruction data - if let Ok(ix) = OreInstruction::try_from(read_u8(&mut curr, data)?) { - // Return immediately if the ix is not an auth - if ix.ne(&OreInstruction::Auth) { - return Ok(None); - } - - // Valid the num accounts is expected - if num_accounts.ne(&1) { - return Ok(None); - } - - // Return the address of the authenticated account - let address = read_pubkey(&mut ac, data)?; - return Ok(Some(address)); - } - - // Return if instruction data can't be parsed - return Ok(None); + let address = read_pubkey(&mut curr, data)?; + return Ok(Some(address)); } } diff --git a/program/src/lib.rs b/program/src/lib.rs index fb5f5f9..1f51675 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -45,7 +45,6 @@ pub fn process_instruction( .ok_or(ProgramError::InvalidInstructionData)?; match OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? { - OreInstruction::Auth => process_auth(accounts, data)?, OreInstruction::Claim => process_claim(accounts, data)?, OreInstruction::Close => process_close(accounts, data)?, OreInstruction::Mine => process_mine(accounts, data)?, From d7e38f72d8d1ac7a261174ba5c66743a941c2e1b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 29 Jul 2024 00:19:15 +0000 Subject: [PATCH 16/18] readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f98259e..9159df8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ - [`Instruction`](api/src/instruction.rs) – Declared instructions and arguments. ## Instructions -- [`Auth`](program/src/auth.rs) – Declares a proof pubkey for processing for mining. - [`Claim`](program/src/claim.rs) – Distributes claimable rewards as tokens from the treasury to a miner. - [`Close`](program/src/close.rs) – Closes a proof account returns the rent to the owner. - [`Open`](program/src/open.rs) – Creates a new proof account for a prospective miner. From c9ccf96c169df37ec6038be4f088bf3e30dd0df7 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 29 Jul 2024 00:20:57 +0000 Subject: [PATCH 17/18] cleanup --- program/src/auth.rs | 53 --------------------------------------------- program/src/lib.rs | 2 -- program/src/mine.rs | 51 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 56 deletions(-) delete mode 100644 program/src/auth.rs diff --git a/program/src/auth.rs b/program/src/auth.rs deleted file mode 100644 index 2489262..0000000 --- a/program/src/auth.rs +++ /dev/null @@ -1,53 +0,0 @@ -use ore_api::consts::COMPUTE_BUDGET_PROGRAM_ID; -use solana_program::{ - pubkey, - pubkey::Pubkey, - sanitize::SanitizeError, - serialize_utils::{read_pubkey, read_u16}, -}; - -/// Get the authenticated pubkey. -/// -/// The intent here is to disincentivize sybil. If a user can fit multiple hashes into a single -/// transaction, there is a financial incentive to sybil multiple keypairs and pack as many hashes -/// as possible into each transaction to minimize fee / hash. -/// -/// If each transaction is limited to one hash only, then a user will minimize their fee / hash -/// by allocating all their hashpower to finding the single most difficult hash they can. -/// -/// We solve this by "authenticating" the proof account on every mine instruction. That is, -/// every transaction with a `mine` instruction needs to include an `auth` instruction that -/// specifies the proof account that will be used. The `auth` instruction must be first ORE -/// instruction in the transaction. The `mine` instruction should error out if the provided proof -/// account doesn't match the authenticated address. -/// -/// Errors if: -/// - Fails to find and parse an authentication address. -pub fn authenticate(data: &[u8]) -> Result, SanitizeError> { - // Start the current byte index at 0 - let mut curr = 0; - let num_instructions = read_u16(&mut curr, data)?; - let pc = curr; - - // Iterate through the transaction instructions - for i in 0..num_instructions as usize { - // Get byte counter - curr = pc + i * 2; - curr = read_u16(&mut curr, data)? as usize; - - // Read the instruction program id - let num_accounts = read_u16(&mut curr, data)? as usize; - curr += num_accounts * 33; - let program_id = read_pubkey(&mut curr, data)?; - - // Introspect on the first non compute budget instruction - if program_id.ne(&COMPUTE_BUDGET_PROGRAM_ID) { - // Read address from ix data - curr += 2; - let address = read_pubkey(&mut curr, data)?; - return Ok(Some(address)); - } - } - - Ok(None) -} diff --git a/program/src/lib.rs b/program/src/lib.rs index 1f51675..bdb31b0 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,4 +1,3 @@ -mod auth; mod claim; mod close; mod initialize; @@ -9,7 +8,6 @@ mod stake; mod update; mod upgrade; -use auth::*; use claim::*; use close::*; use initialize::*; diff --git a/program/src/mine.rs b/program/src/mine.rs index 0346828..bc8f50a 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -18,11 +18,14 @@ use solana_program::{ entrypoint::ProgramResult, log::sol_log, program_error::ProgramError, + pubkey::Pubkey, + sanitize::SanitizeError, + serialize_utils::{read_pubkey, read_u16}, slot_hashes::SlotHash, sysvar::{self, Sysvar}, }; -use crate::{authenticate, utils::AccountDeserialize}; +use crate::utils::AccountDeserialize; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: /// 1. Calculate the hash from the provided nonce. @@ -179,3 +182,49 @@ pub fn process_mine<'a, 'info>(accounts: &'a [AccountInfo<'info>], data: &[u8]) Ok(()) } + +/// Get the authenticated pubkey. +/// +/// The intent here is to disincentivize sybil. If a user can fit multiple hashes into a single +/// transaction, there is a financial incentive to sybil multiple keypairs and pack as many hashes +/// as possible into each transaction to minimize fee / hash. +/// +/// If each transaction is limited to one hash only, then a user will minimize their fee / hash +/// by allocating all their hashpower to finding the single most difficult hash they can. +/// +/// We solve this by "authenticating" the proof account on every mine instruction. That is, +/// every transaction with a `mine` instruction needs to include an `auth` instruction that +/// specifies the proof account that will be used. The `auth` instruction must be first ORE +/// instruction in the transaction. The `mine` instruction should error out if the provided proof +/// account doesn't match the authenticated address. +/// +/// Errors if: +/// - Fails to find and parse an authentication address. +fn authenticate(data: &[u8]) -> Result, SanitizeError> { + // Start the current byte index at 0 + let mut curr = 0; + let num_instructions = read_u16(&mut curr, data)?; + let pc = curr; + + // Iterate through the transaction instructions + for i in 0..num_instructions as usize { + // Get byte counter + curr = pc + i * 2; + curr = read_u16(&mut curr, data)? as usize; + + // Read the instruction program id + let num_accounts = read_u16(&mut curr, data)? as usize; + curr += num_accounts * 33; + let program_id = read_pubkey(&mut curr, data)?; + + // Introspect on the first non compute budget instruction + if program_id.ne(&COMPUTE_BUDGET_PROGRAM_ID) { + // Read address from ix data + curr += 2; + let address = read_pubkey(&mut curr, data)?; + return Ok(Some(address)); + } + } + + Ok(None) +} From 64912abe8ccb419ce6f2afd994d45c5a2f5f781b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 29 Jul 2024 00:46:26 +0000 Subject: [PATCH 18/18] update version --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab24045..f4d3deb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1287,7 +1287,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "ore-api" -version = "2.0.0-beta.4" +version = "2.0.0-beta.5" dependencies = [ "array-const-fn-init", "bytemuck", @@ -1306,7 +1306,7 @@ dependencies = [ [[package]] name = "ore-program" -version = "2.0.0-beta.4" +version = "2.0.0-beta.5" dependencies = [ "drillx", "mpl-token-metadata", @@ -1320,7 +1320,7 @@ dependencies = [ [[package]] name = "ore-utils" -version = "2.0.0-beta.4" +version = "2.0.0-beta.5" dependencies = [ "bytemuck", "solana-program", diff --git a/Cargo.toml b/Cargo.toml index e78e68e..95b762e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["api", "program", "utils"] [workspace.package] -version = "2.0.0-beta.4" +version = "2.0.0-beta.5" edition = "2021" license = "Apache-2.0" homepage = "https://ore.supply"