diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index 92d3711bbd..070498f378 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -5,10 +5,10 @@ //use crate::{check_id, id}; //use log::*; -use bincode::{deserialize, serialize_into, ErrorKind}; use serde_derive::{Deserialize, Serialize}; use solana_sdk::account::KeyedAccount; use solana_sdk::instruction::InstructionError; +use solana_sdk::instruction_processor_utils::State; use solana_sdk::pubkey::Pubkey; use solana_vote_api::vote_state::VoteState; @@ -39,26 +39,6 @@ pub trait StakeAccount { ) -> Result<(), InstructionError>; } -pub trait State { - fn state(&self) -> Result; - fn set_state(&mut self, state: &T) -> Result<(), InstructionError>; -} - -impl<'a, T> State for KeyedAccount<'a> -where - T: serde::Serialize + serde::de::DeserializeOwned, -{ - fn state(&self) -> Result { - deserialize(&self.account.data).map_err(|_| InstructionError::InvalidAccountData) - } - fn set_state(&mut self, state: &T) -> Result<(), InstructionError> { - serialize_into(&mut self.account.data[..], state).map_err(|err| match *err { - ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall, - _ => InstructionError::GenericError, - }) - } -} - impl<'a> StakeAccount for KeyedAccount<'a> { fn delegate_stake(&mut self, vote_account: &KeyedAccount) -> Result<(), InstructionError> { if self.signer_key().is_none() { diff --git a/programs/storage_api/src/storage_contract.rs b/programs/storage_api/src/storage_contract.rs index e144211f5f..7353408e96 100644 --- a/programs/storage_api/src/storage_contract.rs +++ b/programs/storage_api/src/storage_contract.rs @@ -1,7 +1,16 @@ +use crate::{get_segment_from_entry, ENTRIES_PER_SEGMENT}; +use log::*; use serde_derive::{Deserialize, Serialize}; +use solana_sdk::account::Account; use solana_sdk::hash::Hash; +use solana_sdk::instruction::InstructionError; +use solana_sdk::instruction_processor_utils::State; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Signature; +use std::cmp; + +pub const TOTAL_VALIDATOR_REWARDS: u64 = 1; +pub const TOTAL_REPLICATOR_REWARDS: u64 = 1; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub enum ProofStatus { @@ -45,3 +54,359 @@ pub enum StorageContract { reward_validations: Vec>, }, } + +pub struct StorageAccount<'a> { + account: &'a mut Account, +} + +impl<'a> StorageAccount<'a> { + pub fn new(account: &'a mut Account) -> Self { + Self { account } + } + + pub fn submit_mining_proof( + &mut self, + id: Pubkey, + sha_state: Hash, + entry_height: u64, + signature: Signature, + ) -> Result<(), InstructionError> { + let mut storage_contract = &mut self.account.state()?; + if let StorageContract::Default = storage_contract { + *storage_contract = StorageContract::ReplicatorStorage { + proofs: vec![], + reward_validations: vec![], + }; + }; + + if let StorageContract::ReplicatorStorage { proofs, .. } = &mut storage_contract { + let segment_index = get_segment_from_entry(entry_height); + if segment_index > proofs.len() || proofs.is_empty() { + proofs.resize(cmp::max(1, segment_index), Proof::default()); + } + + if segment_index > proofs.len() { + // only possible if usize max < u64 max + return Err(InstructionError::InvalidArgument); + } + + debug!( + "Mining proof submitted with contract {:?} entry_height: {}", + sha_state, entry_height + ); + + let proof_info = Proof { + id, + sha_state, + signature, + }; + proofs[segment_index] = proof_info; + self.account.set_state(storage_contract) + } else { + Err(InstructionError::InvalidArgument)? + } + } + + pub fn advertise_storage_recent_blockhash( + &mut self, + hash: Hash, + entry_height: u64, + ) -> Result<(), InstructionError> { + let mut storage_contract = &mut self.account.state()?; + if let StorageContract::Default = storage_contract { + *storage_contract = StorageContract::ValidatorStorage { + entry_height: 0, + hash: Hash::default(), + lockout_validations: vec![], + reward_validations: vec![], + }; + }; + + if let StorageContract::ValidatorStorage { + entry_height: state_entry_height, + hash: state_hash, + reward_validations, + lockout_validations, + } = &mut storage_contract + { + let original_segments = *state_entry_height / ENTRIES_PER_SEGMENT; + let segments = entry_height / ENTRIES_PER_SEGMENT; + debug!( + "advertise new last id segments: {} orig: {}", + segments, original_segments + ); + if segments <= original_segments { + return Err(InstructionError::InvalidArgument); + } + + *state_entry_height = entry_height; + *state_hash = hash; + + // move lockout_validations to reward_validations + *reward_validations = lockout_validations.clone(); + lockout_validations.clear(); + lockout_validations.resize(segments as usize, Vec::new()); + self.account.set_state(storage_contract) + } else { + Err(InstructionError::InvalidArgument)? + } + } + + pub fn proof_validation( + &mut self, + entry_height: u64, + proofs: Vec, + replicator_accounts: &mut [StorageAccount], + ) -> Result<(), InstructionError> { + let mut storage_contract = &mut self.account.state()?; + if let StorageContract::Default = storage_contract { + *storage_contract = StorageContract::ValidatorStorage { + entry_height: 0, + hash: Hash::default(), + lockout_validations: vec![], + reward_validations: vec![], + }; + }; + + if let StorageContract::ValidatorStorage { + entry_height: current_entry_height, + lockout_validations, + .. + } = &mut storage_contract + { + if entry_height >= *current_entry_height { + return Err(InstructionError::InvalidArgument); + } + + let segment_index = get_segment_from_entry(entry_height); + let mut previous_proofs = replicator_accounts + .iter_mut() + .filter_map(|account| { + account + .account + .state() + .ok() + .map(move |contract| match contract { + StorageContract::ReplicatorStorage { proofs, .. } => { + Some((account, proofs[segment_index].clone())) + } + _ => None, + }) + }) + .flatten() + .collect::>(); + + if previous_proofs.len() != proofs.len() { + // don't have all the accounts to validate the proofs against + return Err(InstructionError::InvalidArgument); + } + + let mut valid_proofs: Vec<_> = proofs + .into_iter() + .enumerate() + .filter_map(|(i, entry)| { + let (account, proof) = &mut previous_proofs[i]; + if process_validation(account, segment_index, &proof, &entry).is_ok() { + Some(entry) + } else { + None + } + }) + .collect(); + + // allow validators to store successful validations + lockout_validations[segment_index].append(&mut valid_proofs); + self.account.set_state(storage_contract) + } else { + Err(InstructionError::InvalidArgument)? + } + } + + pub fn claim_storage_reward( + &mut self, + entry_height: u64, + tick_height: u64, + ) -> Result<(), InstructionError> { + let mut storage_contract = &mut self.account.state()?; + if let StorageContract::Default = storage_contract { + Err(InstructionError::InvalidArgument)? + }; + + if let StorageContract::ValidatorStorage { + reward_validations, .. + } = &mut storage_contract + { + let claims_index = get_segment_from_entry(entry_height); + let _num_validations = count_valid_proofs(&reward_validations[claims_index]); + // TODO can't just create lamports out of thin air + // self.account.lamports += TOTAL_VALIDATOR_REWARDS * num_validations; + reward_validations.clear(); + self.account.set_state(storage_contract) + } else if let StorageContract::ReplicatorStorage { + reward_validations, .. + } = &mut storage_contract + { + // if current tick height is a full segment away? then allow reward collection + // storage needs to move to tick heights too, until then this makes little sense + let current_index = get_segment_from_entry(tick_height); + let claims_index = get_segment_from_entry(entry_height); + if current_index <= claims_index || claims_index >= reward_validations.len() { + debug!( + "current {:?}, claim {:?}, rewards {:?}", + current_index, + claims_index, + reward_validations.len() + ); + return Err(InstructionError::InvalidArgument); + } + let _num_validations = count_valid_proofs(&reward_validations[claims_index]); + // TODO can't just create lamports out of thin air + // self.account.lamports += num_validations + // * TOTAL_REPLICATOR_REWARDS + // * (num_validations / reward_validations[claims_index].len() as u64); + reward_validations.clear(); + self.account.set_state(storage_contract) + } else { + Err(InstructionError::InvalidArgument)? + } + } +} + +/// Store the result of a proof validation into the replicator account +fn store_validation_result( + storage_account: &mut StorageAccount, + segment_index: usize, + status: ProofStatus, +) -> Result<(), InstructionError> { + let mut storage_contract = storage_account.account.state()?; + match &mut storage_contract { + StorageContract::ReplicatorStorage { + proofs, + reward_validations, + .. + } => { + if segment_index >= proofs.len() { + return Err(InstructionError::InvalidAccountData); + } + if segment_index > reward_validations.len() || reward_validations.is_empty() { + reward_validations.resize(cmp::max(1, segment_index), vec![]); + } + let result = proofs[segment_index].clone(); + reward_validations[segment_index].push(CheckedProof { + proof: result, + status, + }); + } + _ => return Err(InstructionError::InvalidAccountData), + } + storage_account.account.set_state(&storage_contract) +} + +fn count_valid_proofs(proofs: &[CheckedProof]) -> u64 { + let mut num = 0; + for proof in proofs { + if let ProofStatus::Valid = proof.status { + num += 1; + } + } + num +} + +fn process_validation( + account: &mut StorageAccount, + segment_index: usize, + proof: &Proof, + checked_proof: &CheckedProof, +) -> Result<(), InstructionError> { + store_validation_result(account, segment_index, checked_proof.status.clone())?; + if proof.signature != checked_proof.proof.signature + || checked_proof.status != ProofStatus::Valid + { + return Err(InstructionError::GenericError); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::id; + + #[test] + fn test_account_data() { + solana_logger::setup(); + let mut account = Account::default(); + account.data.resize(4 * 1024, 0); + let storage_account = StorageAccount::new(&mut account); + // pretend it's a validator op code + let mut contract = storage_account.account.state().unwrap(); + if let StorageContract::ValidatorStorage { .. } = contract { + assert!(true) + } + if let StorageContract::ReplicatorStorage { .. } = &mut contract { + panic!("Contract should not decode into two types"); + } + + contract = StorageContract::ValidatorStorage { + entry_height: 0, + hash: Hash::default(), + lockout_validations: vec![], + reward_validations: vec![], + }; + storage_account.account.set_state(&contract).unwrap(); + if let StorageContract::ReplicatorStorage { .. } = contract { + panic!("Wrong contract type"); + } + contract = StorageContract::ReplicatorStorage { + proofs: vec![], + reward_validations: vec![], + }; + storage_account.account.set_state(&contract).unwrap(); + if let StorageContract::ValidatorStorage { .. } = contract { + panic!("Wrong contract type"); + } + } + + #[test] + fn test_process_validation() { + let mut account = StorageAccount { + account: &mut Account { + lamports: 0, + data: vec![], + owner: id(), + executable: false, + }, + }; + let segment_index = 0_usize; + let proof = Proof { + id: Pubkey::default(), + signature: Signature::default(), + sha_state: Hash::default(), + }; + let mut checked_proof = CheckedProof { + proof: proof.clone(), + status: ProofStatus::Valid, + }; + + // account has no space + process_validation(&mut account, segment_index, &proof, &checked_proof).unwrap_err(); + + account.account.data.resize(4 * 1024, 0); + let storage_contract = &mut account.account.state().unwrap(); + if let StorageContract::Default = storage_contract { + *storage_contract = StorageContract::ReplicatorStorage { + proofs: vec![proof.clone()], + reward_validations: vec![], + }; + }; + account.account.set_state(storage_contract).unwrap(); + + // proof is valid + process_validation(&mut account, segment_index, &proof, &checked_proof).unwrap(); + + checked_proof.status = ProofStatus::NotValid; + + // proof failed verification + process_validation(&mut account, segment_index, &proof, &checked_proof).unwrap_err(); + } +} diff --git a/programs/storage_api/src/storage_processor.rs b/programs/storage_api/src/storage_processor.rs index badae36d65..86686fc497 100644 --- a/programs/storage_api/src/storage_processor.rs +++ b/programs/storage_api/src/storage_processor.rs @@ -2,107 +2,12 @@ //! Receive mining proofs from miners, validate the answers //! and give reward for good proofs. -use crate::storage_contract::{CheckedProof, Proof, ProofStatus, StorageContract}; +use crate::storage_contract::StorageAccount; use crate::storage_instruction::StorageInstruction; -use crate::{get_segment_from_entry, ENTRIES_PER_SEGMENT}; use log::*; -use serde::Serialize; -use solana_sdk::account::{Account, KeyedAccount}; -use solana_sdk::hash::Hash; +use solana_sdk::account::KeyedAccount; use solana_sdk::instruction::InstructionError; -use solana_sdk::instruction::InstructionError::InvalidArgument; use solana_sdk::pubkey::Pubkey; -use std::cmp; - -pub const TOTAL_VALIDATOR_REWARDS: u64 = 1; -pub const TOTAL_REPLICATOR_REWARDS: u64 = 1; - -fn count_valid_proofs(proofs: &[CheckedProof]) -> u64 { - let mut num = 0; - for proof in proofs { - if let ProofStatus::Valid = proof.status { - num += 1; - } - } - num -} - -/// Serialize account data -fn store_contract(account: &mut Account, contract: &T) -> Result<(), InstructionError> -where - T: Serialize, -{ - if bincode::serialize_into(&mut account.data[..], contract).is_err() { - return Err(InstructionError::AccountDataTooSmall); - } - Ok(()) -} - -/// Deserialize account data -fn read_contract(account: &Account) -> Result { - if let Ok(storage_contract) = bincode::deserialize(&account.data) { - Ok(storage_contract) - } else { - Err(InstructionError::InvalidAccountData) - } -} - -/// Deserialize account data but handle uninitialized accounts -fn read_contract_with_default( - op: &StorageInstruction, - account: &Account, -) -> Result { - let mut storage_contract = read_contract(&account); - if let Ok(StorageContract::Default) = storage_contract { - match op { - StorageInstruction::SubmitMiningProof { .. } => { - storage_contract = Ok(StorageContract::ReplicatorStorage { - proofs: vec![], - reward_validations: vec![], - }) - } - StorageInstruction::AdvertiseStorageRecentBlockhash { .. } - | StorageInstruction::ProofValidation { .. } => { - storage_contract = Ok(StorageContract::ValidatorStorage { - entry_height: 0, - hash: Hash::default(), - lockout_validations: vec![], - reward_validations: vec![], - }) - } - StorageInstruction::ClaimStorageReward { .. } => Err(InvalidArgument)?, - } - } - storage_contract -} - -/// Store the result of a proof validation into the replicator account -fn store_validation_result( - account: &mut Account, - segment_index: usize, - status: ProofStatus, -) -> Result<(), InstructionError> { - let mut storage_contract = read_contract(account)?; - match &mut storage_contract { - StorageContract::ReplicatorStorage { - proofs, - reward_validations, - .. - } => { - if segment_index > reward_validations.len() || reward_validations.is_empty() { - reward_validations.resize(cmp::max(1, segment_index), vec![]); - } - let result = proofs[segment_index].clone(); - reward_validations[segment_index].push(CheckedProof { - proof: result, - status, - }); - } - _ => return Err(InstructionError::InvalidAccountData), - } - store_contract(account, &storage_contract)?; - Ok(()) -} pub fn process_instruction( _program_id: &Pubkey, @@ -112,210 +17,65 @@ pub fn process_instruction( ) -> Result<(), InstructionError> { solana_logger::setup(); - // accounts_keys[0] must be signed - if keyed_accounts[0].signer_key().is_none() { - info!("account[0] is unsigned"); - Err(InstructionError::GenericError)?; - } + let num_keyed_accounts = keyed_accounts.len(); + let (me, rest) = keyed_accounts.split_at_mut(1); - if let Ok(syscall) = bincode::deserialize(data) { - let mut storage_contract = - read_contract_with_default(&syscall, &keyed_accounts[0].account)?; - match syscall { - StorageInstruction::SubmitMiningProof { + // accounts_keys[0] must be signed + let storage_account_pubkey = me[0].signer_key(); + if storage_account_pubkey.is_none() { + info!("account[0] is unsigned"); + Err(InstructionError::MissingRequiredSignature)?; + } + let storage_account_pubkey = *storage_account_pubkey.unwrap(); + + let mut storage_account = StorageAccount::new(&mut me[0].account); + let mut rest: Vec<_> = rest + .iter_mut() + .map(|keyed_account| StorageAccount::new(&mut keyed_account.account)) + .collect(); + + match bincode::deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { + StorageInstruction::SubmitMiningProof { + sha_state, + entry_height, + signature, + } => { + if num_keyed_accounts != 1 { + Err(InstructionError::InvalidArgument)?; + } + storage_account.submit_mining_proof( + storage_account_pubkey, sha_state, entry_height, signature, - } => { - if let StorageContract::ReplicatorStorage { proofs, .. } = &mut storage_contract { - if keyed_accounts.len() != 1 { - // keyed_accounts[1] should be the main storage key - // to access its data - Err(InstructionError::InvalidArgument)?; - } - - let segment_index = get_segment_from_entry(entry_height); - if segment_index > proofs.len() || proofs.is_empty() { - proofs.resize(cmp::max(1, segment_index), Proof::default()); - } - - if segment_index > proofs.len() { - // only possible if usize max < u64 max - return Err(InstructionError::InvalidArgument); - } - - debug!( - "Mining proof submitted with contract {:?} entry_height: {}", - sha_state, entry_height - ); - - let proof_info = Proof { - id: *keyed_accounts[0].signer_key().unwrap(), - sha_state, - signature, - }; - proofs[segment_index] = proof_info; - } else { - Err(InstructionError::InvalidArgument)?; - } - } - StorageInstruction::AdvertiseStorageRecentBlockhash { hash, entry_height } => { - if let StorageContract::ValidatorStorage { - entry_height: state_entry_height, - hash: state_hash, - reward_validations, - lockout_validations, - } = &mut storage_contract - { - if keyed_accounts.len() != 1 { - // keyed_accounts[1] should be the main storage key - // to access its data - Err(InstructionError::InvalidArgument)?; - } - - let original_segments = *state_entry_height / ENTRIES_PER_SEGMENT; - let segments = entry_height / ENTRIES_PER_SEGMENT; - debug!( - "advertise new last id segments: {} orig: {}", - segments, original_segments - ); - if segments <= original_segments { - return Err(InstructionError::InvalidArgument); - } - - *state_entry_height = entry_height; - *state_hash = hash; - - // move lockout_validations to reward_validations - *reward_validations = lockout_validations.clone(); - lockout_validations.clear(); - lockout_validations.resize(segments as usize, Vec::new()); - } else { - return Err(InstructionError::InvalidArgument); - } - } - StorageInstruction::ClaimStorageReward { entry_height } => { - if keyed_accounts.len() != 1 { - // keyed_accounts[1] should be the main storage key - // to access its data - Err(InstructionError::InvalidArgument)?; - } - - if let StorageContract::ValidatorStorage { - reward_validations, .. - } = &mut storage_contract - { - let claims_index = get_segment_from_entry(entry_height); - let _num_validations = count_valid_proofs(&reward_validations[claims_index]); - // TODO can't just create lamports out of thin air - // keyed_accounts[0].account.lamports += TOTAL_VALIDATOR_REWARDS * num_validations; - reward_validations.clear(); - } else if let StorageContract::ReplicatorStorage { - reward_validations, .. - } = &mut storage_contract - { - // if current tick height is a full segment away? then allow reward collection - // storage needs to move to tick heights too, until then this makes little sense - let current_index = get_segment_from_entry(tick_height); - let claims_index = get_segment_from_entry(entry_height); - if current_index <= claims_index || claims_index >= reward_validations.len() { - debug!( - "current {:?}, claim {:?}, rewards {:?}", - current_index, - claims_index, - reward_validations.len() - ); - return Err(InstructionError::InvalidArgument); - } - let _num_validations = count_valid_proofs(&reward_validations[claims_index]); - // TODO can't just create lamports out of thin air - // keyed_accounts[0].account.lamports += num_validations - // * TOTAL_REPLICATOR_REWARDS - // * (num_validations / reward_validations[claims_index].len() as u64); - reward_validations.clear(); - } else { - return Err(InstructionError::InvalidArgument); - } - } - StorageInstruction::ProofValidation { - entry_height: proof_entry_height, - proofs, - } => { - if let StorageContract::ValidatorStorage { - entry_height: current_entry_height, - lockout_validations, - .. - } = &mut storage_contract - { - if keyed_accounts.len() == 1 { - // have to have at least 1 replicator to do any verification - Err(InstructionError::InvalidArgument)?; - } - - if proof_entry_height >= *current_entry_height { - return Err(InstructionError::InvalidArgument); - } - - let segment_index = get_segment_from_entry(proof_entry_height); - let mut previous_proofs = keyed_accounts[1..] - .iter_mut() - .filter_map(|account| { - read_contract(&account.account) - .ok() - .map(move |contract| match contract { - StorageContract::ReplicatorStorage { proofs, .. } => { - Some((&mut account.account, proofs[segment_index].clone())) - } - _ => None, - }) - }) - .flatten() - .collect::>(); - - if previous_proofs.len() != proofs.len() { - // don't have all the accounts to validate the proofs against - return Err(InstructionError::InvalidArgument); - } - - let mut valid_proofs: Vec<_> = proofs - .into_iter() - .enumerate() - .filter_map(|(i, entry)| { - if previous_proofs[i].1.signature != entry.proof.signature - || entry.status != ProofStatus::Valid - { - let _ = store_validation_result( - &mut previous_proofs[i].0, - segment_index, - entry.status, - ); - None - } else if store_validation_result( - &mut previous_proofs[i].0, - segment_index, - entry.status.clone(), - ) - .is_ok() - { - Some(entry) - } else { - None - } - }) - .collect(); - - // allow validators to store successful validations - lockout_validations[segment_index].append(&mut valid_proofs); - } else { - return Err(InstructionError::InvalidArgument); - } - } + ) + } + StorageInstruction::AdvertiseStorageRecentBlockhash { hash, entry_height } => { + if num_keyed_accounts != 1 { + // keyed_accounts[0] should be the main storage key + // to access its data + Err(InstructionError::InvalidArgument)?; + } + storage_account.advertise_storage_recent_blockhash(hash, entry_height) + } + StorageInstruction::ClaimStorageReward { entry_height } => { + if num_keyed_accounts != 1 { + // keyed_accounts[0] should be the main storage key + // to access its data + Err(InstructionError::InvalidArgument)?; + } + storage_account.claim_storage_reward(entry_height, tick_height) + } + StorageInstruction::ProofValidation { + entry_height, + proofs, + } => { + if num_keyed_accounts == 1 { + // have to have at least 1 replicator to do any verification + Err(InstructionError::InvalidArgument)?; + } + storage_account.proof_validation(entry_height, proofs, &mut rest) } - store_contract(&mut keyed_accounts[0].account, &storage_contract)?; - Ok(()) - } else { - info!("Invalid instruction data: {:?}", data); - Err(InstructionError::InvalidInstructionData) } } @@ -323,6 +83,7 @@ pub fn process_instruction( mod tests { use super::*; use crate::id; + use crate::storage_contract::{CheckedProof, Proof, ProofStatus, StorageContract}; use crate::storage_instruction; use crate::ENTRIES_PER_SEGMENT; use bincode::deserialize; @@ -423,42 +184,6 @@ mod tests { test_instruction(&ix, &mut accounts).unwrap(); } - #[test] - fn test_account_data() { - solana_logger::setup(); - let mut account = Account::default(); - account.data.resize(4 * 1024, 0); - let pubkey = &Pubkey::default(); - let mut keyed_account = KeyedAccount::new(&pubkey, false, &mut account); - // pretend it's a validator op code - let mut contract = read_contract(&keyed_account.account).unwrap(); - if let StorageContract::ValidatorStorage { .. } = contract { - assert!(true) - } - if let StorageContract::ReplicatorStorage { .. } = &mut contract { - panic!("this shouldn't work"); - } - - contract = StorageContract::ValidatorStorage { - entry_height: 0, - hash: Hash::default(), - lockout_validations: vec![], - reward_validations: vec![], - }; - store_contract(&mut keyed_account.account, &contract).unwrap(); - if let StorageContract::ReplicatorStorage { .. } = contract { - panic!("this shouldn't work"); - } - contract = StorageContract::ReplicatorStorage { - proofs: vec![], - reward_validations: vec![], - }; - store_contract(&mut keyed_account.account, &contract).unwrap(); - if let StorageContract::ValidatorStorage { .. } = contract { - panic!("this shouldn't work"); - } - } - #[test] fn test_validate_mining() { solana_logger::setup(); @@ -660,32 +385,4 @@ mod tests { storage_blockhash ); } - - /// check that uninitialized accounts are handled - #[test] - fn test_read_contract_with_default() { - let mut account = Account::default(); - // no space allocated - assert!(read_contract(&account).is_err()); - account.data.resize(4 * 1024, 0); - let instruction = StorageInstruction::AdvertiseStorageRecentBlockhash { - hash: Hash::default(), - entry_height: 0, - }; - read_contract_with_default(&instruction, &account).unwrap(); - let instruction = StorageInstruction::SubmitMiningProof { - sha_state: Hash::default(), - entry_height: 0, - signature: Signature::default(), - }; - read_contract_with_default(&instruction, &account).unwrap(); - let instruction = StorageInstruction::ProofValidation { - entry_height: 0, - proofs: vec![], - }; - read_contract_with_default(&instruction, &account).unwrap(); - // Can't claim rewards on an uninitialized account - let instruction = StorageInstruction::ClaimStorageReward { entry_height: 0 }; - assert!(read_contract_with_default(&instruction, &account).is_err()); - } } diff --git a/sdk/src/account.rs b/sdk/src/account.rs index ddf7f05478..627d8092fc 100644 --- a/sdk/src/account.rs +++ b/sdk/src/account.rs @@ -45,6 +45,14 @@ impl Account { executable: false, } } + + pub fn deserialize_data(&self) -> Result { + bincode::deserialize(&self.data) + } + + pub fn serialize_data(&mut self, state: &T) -> Result<(), bincode::Error> { + bincode::serialize_into(&mut self.data[..], state) + } } #[repr(C)] diff --git a/sdk/src/instruction_processor_utils.rs b/sdk/src/instruction_processor_utils.rs index 363e380874..cc57ac6901 100644 --- a/sdk/src/instruction_processor_utils.rs +++ b/sdk/src/instruction_processor_utils.rs @@ -1,6 +1,7 @@ -use crate::account::KeyedAccount; +use crate::account::{Account, KeyedAccount}; use crate::instruction::InstructionError; use crate::pubkey::Pubkey; +use bincode::ErrorKind; // All native programs export a symbol named process() pub const ENTRYPOINT: &str = "process"; @@ -29,3 +30,37 @@ macro_rules! solana_entrypoint( } ) ); + +/// Conveinence trait to covert bincode errors to instruction errors. +pub trait State { + fn state(&self) -> Result; + fn set_state(&mut self, state: &T) -> Result<(), InstructionError>; +} + +impl State for Account +where + T: serde::Serialize + serde::de::DeserializeOwned, +{ + fn state(&self) -> Result { + self.deserialize_data() + .map_err(|_| InstructionError::InvalidAccountData) + } + fn set_state(&mut self, state: &T) -> Result<(), InstructionError> { + self.serialize_data(state).map_err(|err| match *err { + ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall, + _ => InstructionError::GenericError, + }) + } +} + +impl<'a, T> State for KeyedAccount<'a> +where + T: serde::Serialize + serde::de::DeserializeOwned, +{ + fn state(&self) -> Result { + self.account.state() + } + fn set_state(&mut self, state: &T) -> Result<(), InstructionError> { + self.account.set_state(state) + } +}