use ore_api::prelude::*; use solana_nostd_keccak::hash; use solana_program::slot_hashes::SlotHashes; use steel::*; /// Mine a block. pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { // Parse data. let args = Mine::try_from_bytes(data)?; let amount = u64::from_le_bytes(args.amount); // Load accounts. let clock = Clock::get()?; let [signer_info, authority_info, block_info, commitment_info, market_info, miner_info, mint_hash_info, mint_ore_info, permit_info, recipient_info, treasury_info, system_program, token_program, slot_hashes_sysvar] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?; authority_info.is_writable()?; let block = block_info .as_account_mut::(&ore_api::ID)? .assert_mut(|b| clock.slot >= b.start_slot)? .assert_mut(|b| clock.slot < b.start_slot + 1500)?; commitment_info .is_writable()? .as_associated_token_account(block_info.key, mint_hash_info.key)?; let market = market_info .as_account::(&ore_api::ID)? .assert(|m| m.id == block.id)?; mint_hash_info.has_address(&market.base.mint)?.as_mint()?; mint_ore_info.has_address(&MINT_ADDRESS)?.as_mint()?; let miner = miner_info .as_account_mut::(&ore_api::ID)? .assert_mut(|m| m.authority == *authority_info.key)?; let permit = permit_info .as_account_mut::(&ore_api::ID)? .assert_mut(|p| p.authority == miner.authority)? .assert_mut(|p| p.block_id == block.id)? .assert_mut(|p| p.executor == *signer_info.key || p.executor == Pubkey::default())?; recipient_info .is_writable()? .as_associated_token_account(&miner.authority, &MINT_ADDRESS)?; treasury_info.has_address(&TREASURY_ADDRESS)?; system_program.is_program(&system_program::ID)?; token_program.is_program(&spl_token::ID)?; slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; // Reduce permit amount. let amount = permit.amount.min(amount); permit.amount -= amount; // Pay executor fee. if permit.fee > 0 { permit_info.send(permit.fee * amount, signer_info); } // Close permit account, if empty. if permit.amount == 0 { permit_info.close(authority_info)?; } // Burn hash tokens. burn_signed( commitment_info, mint_hash_info, block_info, token_program, amount, &[BLOCK, &block.id.to_le_bytes()], )?; // Set block slot hash. if block.slot_hash == [0; 32] { let slot_hashes = bincode::deserialize::(slot_hashes_sysvar.data.borrow().as_ref()).unwrap(); let Some(slot_hash) = slot_hashes.get(&block.start_slot) else { // If mine is not called within ~2.5 minutes of the block starting, // then the slot hash will be unavailable and secure hashes cannot be generated. return Ok(()); }; block.slot_hash = slot_hash.to_bytes(); } // Reset miner hash if mining new block. if miner.block_id != block.id { miner.block_id = block.id; let mut args = [0u8; 96]; args[..32].copy_from_slice(&block.id.to_le_bytes()); args[32..64].copy_from_slice(&block.slot_hash); args[64..].copy_from_slice(&miner.authority.to_bytes()); miner.hash = hash(&args); } // Mine and accumulate rewards. let mut miner_reward = 0; for _ in 0..amount { // Update stats block.total_hashes += 1; miner.total_hashes += 1; // Generate hash. miner.hash = hash(miner.hash.as_ref()); // Score and increment rewards. let score = difficulty(miner.hash) as u64; if score >= block.reward.difficulty_threshold { block.winning_hashes += 1; miner.winning_hashes += 1; miner_reward += block.reward.difficulty_reward; } // If hash is best hash, update best hash. if miner.hash < block.reward.best_hash { block.reward.best_hash = miner.hash; block.reward.best_hash_authority = miner.authority; } } // Payout ORE. block.total_rewards += miner_reward; miner.total_rewards += miner_reward; mint_to_signed( mint_ore_info, recipient_info, treasury_info, token_program, miner_reward, &[TREASURY], )?; Ok(()) } /// Returns the number of leading zeros on a 32 byte buffer. pub fn difficulty(hash: [u8; 32]) -> u32 { let mut count = 0; for &byte in &hash { let lz = byte.leading_zeros(); count += lz; if lz < 8 { break; } } count }