Files
solana/programs/storage_api/src/storage_contract.rs
Sagar Dhawan 0b23af324b Refactor Storage Program (#3622)
* Refactor Storage Program

* Replace KeyedAccount trait with StorageAccount struct

* Implement State for Account, not StorageAccount

* Make State trait more generic

* Move validation check into function
2019-04-04 12:01:09 -07:00

413 lines
14 KiB
Rust

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 {
Skipped,
Valid,
NotValid,
}
impl Default for ProofStatus {
fn default() -> Self {
ProofStatus::Skipped
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct Proof {
pub id: Pubkey,
pub signature: Signature,
pub sha_state: Hash,
}
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct CheckedProof {
pub proof: Proof,
pub status: ProofStatus,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum StorageContract {
//don't move this
Default,
ValidatorStorage {
entry_height: u64,
hash: Hash,
lockout_validations: Vec<Vec<CheckedProof>>,
reward_validations: Vec<Vec<CheckedProof>>,
},
ReplicatorStorage {
proofs: Vec<Proof>,
reward_validations: Vec<Vec<CheckedProof>>,
},
}
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<CheckedProof>,
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::<Vec<_>>();
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();
}
}