Merge api/program into single units (#7061)

This commit is contained in:
Jack May
2019-11-20 16:32:19 -08:00
committed by GitHub
parent 186bf7ae32
commit 3415db9739
105 changed files with 224 additions and 4095 deletions

View File

@ -0,0 +1,24 @@
[package]
name = "solana-storage-program"
version = "0.21.0"
description = "Solana Storage program"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
bincode = "1.2.0"
log = "0.4.8"
rand = "0.6.5"
num-derive = "0.3"
num-traits = "0.2"
serde = "1.0.102"
serde_derive = "1.0.102"
solana-logger = { path = "../../logger", version = "0.21.0" }
solana-sdk = { path = "../../sdk", version = "0.21.0" }
[lib]
crate-type = ["lib", "cdylib"]
name = "solana_storage_program"

View File

@ -0,0 +1,18 @@
pub mod rewards_pools;
pub mod storage_contract;
pub mod storage_instruction;
pub mod storage_processor;
use crate::storage_processor::process_instruction;
const STORAGE_PROGRAM_ID: [u8; 32] = [
6, 162, 25, 123, 127, 68, 233, 59, 131, 151, 21, 152, 162, 120, 90, 37, 154, 88, 86, 5, 156,
221, 182, 201, 142, 103, 151, 112, 0, 0, 0, 0,
];
solana_sdk::declare_program!(
STORAGE_PROGRAM_ID,
"Storage111111111111111111111111111111111111",
solana_storage_program,
process_instruction
);

View File

@ -0,0 +1,60 @@
//! rewards_pools
//! * initialize genesis with rewards pools
//! * keep track of rewards
//! * own mining pools
use crate::storage_contract::create_rewards_pool;
use rand::{thread_rng, Rng};
use solana_sdk::genesis_config::GenesisConfig;
use solana_sdk::hash::{hash, Hash};
use solana_sdk::pubkey::Pubkey;
// base rewards pool ID
const ID: [u8; 32] = [
6, 162, 25, 123, 127, 71, 141, 232, 129, 171, 58, 183, 79, 88, 181, 17, 163, 11, 51, 111, 22,
123, 67, 115, 5, 131, 109, 161, 16, 0, 0, 0,
];
solana_sdk::solana_name_id!(ID, "StorageMiningPoo111111111111111111111111111");
// to cut down on collisions for redemptions, we make multiple accounts
pub const NUM_REWARDS_POOLS: usize = 32;
pub fn add_genesis_accounts(genesis_config: &mut GenesisConfig) {
let mut pubkey = id();
for _i in 0..NUM_REWARDS_POOLS {
genesis_config.add_rewards_pool(pubkey, create_rewards_pool());
pubkey = Pubkey::new(hash(pubkey.as_ref()).as_ref());
}
}
pub fn random_id() -> Pubkey {
let mut id = Hash::new(&ID);
for _i in 0..thread_rng().gen_range(0, NUM_REWARDS_POOLS) {
id = hash(id.as_ref());
}
Pubkey::new(id.as_ref())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let mut genesis_config = GenesisConfig::default();
add_genesis_accounts(&mut genesis_config);
for _i in 0..NUM_REWARDS_POOLS {
let id = random_id();
assert!(genesis_config
.rewards_pools
.iter()
.position(|x| x.0 == id)
.is_some());
}
}
}

View File

@ -0,0 +1,646 @@
use crate::storage_instruction::StorageAccountType;
use log::*;
use num_derive::FromPrimitive;
use serde_derive::{Deserialize, Serialize};
use solana_sdk::{
account::{Account, KeyedAccount},
account_utils::State,
clock::Epoch,
hash::Hash,
instruction::InstructionError,
pubkey::Pubkey,
signature::Signature,
sysvar,
};
use std::collections::BTreeMap;
// Todo Tune this for actual use cases when PoRep is feature complete
pub const STORAGE_ACCOUNT_SPACE: u64 = 1024 * 8;
pub const MAX_PROOFS_PER_SEGMENT: usize = 80;
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Credits {
// current epoch
epoch: Epoch,
// currently pending credits
pub current_epoch: Epoch,
// credits ready to be claimed
pub redeemable: u64,
}
impl Credits {
pub fn update_epoch(&mut self, current_epoch: Epoch) {
if self.epoch != current_epoch {
self.epoch = current_epoch;
self.redeemable += self.current_epoch;
self.current_epoch = 0;
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive)]
pub enum StorageError {
InvalidSegment,
InvalidBlockhash,
InvalidProofMask,
DuplicateProof,
RewardPoolDepleted,
InvalidOwner,
ProofLimitReached,
}
#[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, PartialEq)]
pub struct Proof {
/// The encryption key the archiver used (also used to generate offsets)
pub signature: Signature,
/// A "recent" blockhash used to generate the seed
pub blockhash: Hash,
/// The resulting sampled state
pub sha_state: Hash,
/// The segment this proof is for
pub segment_index: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum StorageContract {
Uninitialized, // Must be first (aka, 0)
ValidatorStorage {
owner: Pubkey,
// Most recently advertised segment
segment: u64,
// Most recently advertised blockhash
hash: Hash,
// Lockouts and Rewards are per segment per archiver. It needs to remain this way until
// the challenge stage is added.
lockout_validations: BTreeMap<u64, BTreeMap<Pubkey, Vec<ProofStatus>>>,
// Used to keep track of ongoing credits
credits: Credits,
},
ArchiverStorage {
owner: Pubkey,
// TODO what to do about duplicate proofs across segments? - Check the blockhashes
// Map of Proofs per segment, in a Vec
proofs: BTreeMap<u64, Vec<Proof>>,
// Map of Rewards per segment, in a BTreeMap based on the validator account that verified
// the proof. This can be used for challenge stage when its added
validations: BTreeMap<u64, BTreeMap<Pubkey, Vec<ProofStatus>>>,
// Used to keep track of ongoing credits
credits: Credits,
},
RewardsPool,
}
// utility function, used by Bank, tests, genesis
pub fn create_validator_storage_account(owner: Pubkey, lamports: u64) -> Account {
let mut storage_account = Account::new(lamports, STORAGE_ACCOUNT_SPACE as usize, &crate::id());
storage_account
.set_state(&StorageContract::ValidatorStorage {
owner,
segment: 0,
hash: Hash::default(),
lockout_validations: BTreeMap::new(),
credits: Credits::default(),
})
.expect("set_state");
storage_account
}
pub struct StorageAccount<'a> {
pub(crate) id: Pubkey,
account: &'a mut Account,
}
impl<'a> StorageAccount<'a> {
pub fn new(id: Pubkey, account: &'a mut Account) -> Self {
Self { id, account }
}
pub fn initialize_storage(
&mut self,
owner: Pubkey,
account_type: StorageAccountType,
) -> Result<(), InstructionError> {
let storage_contract = &mut self.account.state()?;
if let StorageContract::Uninitialized = storage_contract {
*storage_contract = match account_type {
StorageAccountType::Archiver => StorageContract::ArchiverStorage {
owner,
proofs: BTreeMap::new(),
validations: BTreeMap::new(),
credits: Credits::default(),
},
StorageAccountType::Validator => StorageContract::ValidatorStorage {
owner,
segment: 0,
hash: Hash::default(),
lockout_validations: BTreeMap::new(),
credits: Credits::default(),
},
};
self.account.set_state(storage_contract)
} else {
Err(InstructionError::AccountAlreadyInitialized)
}
}
pub fn submit_mining_proof(
&mut self,
sha_state: Hash,
segment_index: u64,
signature: Signature,
blockhash: Hash,
clock: sysvar::clock::Clock,
) -> Result<(), InstructionError> {
let mut storage_contract = &mut self.account.state()?;
if let StorageContract::ArchiverStorage {
proofs,
validations,
credits,
..
} = &mut storage_contract
{
let current_segment = clock.segment;
// clean up the account
// TODO check for time correctness - storage seems to run at a delay of about 3
*proofs = proofs
.iter()
.filter(|(segment, _)| **segment >= current_segment.saturating_sub(5))
.map(|(segment, proofs)| (*segment, proofs.clone()))
.collect();
*validations = validations
.iter()
.filter(|(segment, _)| **segment >= current_segment.saturating_sub(10))
.map(|(segment, rewards)| (*segment, rewards.clone()))
.collect();
if segment_index >= current_segment {
// attempt to submit proof for unconfirmed segment
return Err(InstructionError::CustomError(
StorageError::InvalidSegment as u32,
));
}
debug!(
"Mining proof submitted with contract {:?} segment_index: {}",
sha_state, segment_index
);
// TODO check that this blockhash is valid and recent
// if !is_valid(&blockhash) {
// // proof isn't using a recent blockhash
// return Err(InstructionError::CustomError(InvalidBlockhash as u32));
// }
let proof = Proof {
sha_state,
signature,
blockhash,
segment_index,
};
// store the proofs in the "current" segment's entry in the hash map.
let segment_proofs = proofs.entry(current_segment).or_default();
if segment_proofs.contains(&proof) {
// do not accept duplicate proofs
return Err(InstructionError::CustomError(
StorageError::DuplicateProof as u32,
));
}
if segment_proofs.len() >= MAX_PROOFS_PER_SEGMENT {
// do not accept more than MAX_PROOFS_PER_SEGMENT
return Err(InstructionError::CustomError(
StorageError::ProofLimitReached as u32,
));
}
credits.update_epoch(clock.epoch);
segment_proofs.push(proof);
self.account.set_state(storage_contract)
} else {
Err(InstructionError::InvalidArgument)
}
}
pub fn advertise_storage_recent_blockhash(
&mut self,
hash: Hash,
segment: u64,
clock: sysvar::clock::Clock,
) -> Result<(), InstructionError> {
let mut storage_contract = &mut self.account.state()?;
if let StorageContract::ValidatorStorage {
segment: state_segment,
hash: state_hash,
lockout_validations,
credits,
..
} = &mut storage_contract
{
debug!("advertise new segment: {} orig: {}", segment, clock.segment);
if segment < *state_segment || segment > clock.segment {
return Err(InstructionError::CustomError(
StorageError::InvalidSegment as u32,
));
}
*state_segment = segment;
*state_hash = hash;
// storage epoch updated, move the lockout_validations to credits
let (_num_valid, total_validations) = count_valid_proofs(&lockout_validations);
lockout_validations.clear();
credits.update_epoch(clock.epoch);
credits.current_epoch += total_validations;
self.account.set_state(storage_contract)
} else {
Err(InstructionError::InvalidArgument)
}
}
pub fn proof_validation(
&mut self,
me: &Pubkey,
clock: sysvar::clock::Clock,
segment_index: u64,
proofs_per_account: Vec<Vec<ProofStatus>>,
archiver_accounts: &mut [StorageAccount],
) -> Result<(), InstructionError> {
let mut storage_contract = &mut self.account.state()?;
if let StorageContract::ValidatorStorage {
segment: state_segment,
lockout_validations,
..
} = &mut storage_contract
{
if segment_index > *state_segment {
return Err(InstructionError::CustomError(
StorageError::InvalidSegment as u32,
));
}
let accounts = archiver_accounts
.iter_mut()
.enumerate()
.filter_map(|(i, account)| {
account.account.state().ok().map(|contract| match contract {
StorageContract::ArchiverStorage {
proofs: account_proofs,
..
} => {
//TODO do this better
if let Some(segment_proofs) =
account_proofs.get(&segment_index).cloned()
{
if proofs_per_account
.get(i)
.filter(|proofs| proofs.len() == segment_proofs.len())
.is_some()
{
Some(account)
} else {
None
}
} else {
None
}
}
_ => None,
})
})
.flatten()
.collect::<Vec<_>>();
if accounts.len() != proofs_per_account.len() {
// don't have all the accounts to validate the proofs_per_account against
return Err(InstructionError::CustomError(
StorageError::InvalidProofMask as u32,
));
}
let stored_proofs: Vec<_> = proofs_per_account
.into_iter()
.zip(accounts.into_iter())
.filter_map(|(checked_proofs, account)| {
if store_validation_result(me, &clock, account, segment_index, &checked_proofs)
.is_ok()
{
Some((account.id, checked_proofs))
} else {
None
}
})
.collect();
// allow validators to store successful validations
stored_proofs
.into_iter()
.for_each(|(archiver_account_id, proof_mask)| {
lockout_validations
.entry(segment_index)
.or_default()
.insert(archiver_account_id, proof_mask);
});
self.account.set_state(storage_contract)
} else {
Err(InstructionError::InvalidArgument)
}
}
pub fn claim_storage_reward(
&mut self,
rewards_pool: &mut KeyedAccount,
clock: sysvar::clock::Clock,
rewards: sysvar::rewards::Rewards,
owner: &mut StorageAccount,
) -> Result<(), InstructionError> {
let mut storage_contract = &mut self.account.state()?;
if let StorageContract::ValidatorStorage {
owner: account_owner,
credits,
..
} = &mut storage_contract
{
if owner.id != *account_owner {
return Err(InstructionError::CustomError(
StorageError::InvalidOwner as u32,
));
}
credits.update_epoch(clock.epoch);
check_redeemable(credits, rewards.storage_point_value, rewards_pool, owner)?;
self.account.set_state(storage_contract)
} else if let StorageContract::ArchiverStorage {
owner: account_owner,
validations,
credits,
..
} = &mut storage_contract
{
if owner.id != *account_owner {
return Err(InstructionError::CustomError(
StorageError::InvalidOwner as u32,
));
}
credits.update_epoch(clock.epoch);
let (num_validations, _total_proofs) = count_valid_proofs(&validations);
credits.current_epoch += num_validations;
validations.clear();
check_redeemable(credits, rewards.storage_point_value, rewards_pool, owner)?;
self.account.set_state(storage_contract)
} else {
Err(InstructionError::InvalidArgument)
}
}
}
fn check_redeemable(
credits: &mut Credits,
storage_point_value: f64,
rewards_pool: &mut KeyedAccount,
owner: &mut StorageAccount,
) -> Result<(), InstructionError> {
let rewards = (credits.redeemable as f64 * storage_point_value) as u64;
if rewards_pool.account.lamports < rewards {
Err(InstructionError::CustomError(
StorageError::RewardPoolDepleted as u32,
))
} else {
if rewards >= 1 {
rewards_pool.account.lamports -= rewards;
owner.account.lamports += rewards;
//clear credits
credits.redeemable = 0;
}
Ok(())
}
}
pub fn create_rewards_pool() -> Account {
Account::new_data(std::u64::MAX, &StorageContract::RewardsPool, &crate::id()).unwrap()
}
/// Store the result of a proof validation into the archiver account
fn store_validation_result(
me: &Pubkey,
clock: &sysvar::clock::Clock,
storage_account: &mut StorageAccount,
segment: u64,
proof_mask: &[ProofStatus],
) -> Result<(), InstructionError> {
let mut storage_contract = storage_account.account.state()?;
match &mut storage_contract {
StorageContract::ArchiverStorage {
proofs,
validations,
credits,
..
} => {
if !proofs.contains_key(&segment) {
return Err(InstructionError::InvalidAccountData);
}
if proofs.get(&segment).unwrap().len() != proof_mask.len() {
return Err(InstructionError::InvalidAccountData);
}
let (recorded_validations, _) = count_valid_proofs(&validations);
let entry = validations.entry(segment).or_default();
if !entry.contains_key(me) {
entry.insert(*me, proof_mask.to_vec());
}
let (total_validations, _) = count_valid_proofs(&validations);
credits.update_epoch(clock.epoch);
credits.current_epoch += total_validations - recorded_validations;
}
_ => return Err(InstructionError::InvalidAccountData),
}
storage_account.account.set_state(&storage_contract)
}
fn count_valid_proofs(
validations: &BTreeMap<u64, BTreeMap<Pubkey, Vec<ProofStatus>>>,
) -> (u64, u64) {
let proofs = validations
.iter()
.flat_map(|(_, proofs)| {
proofs
.iter()
.flat_map(|(_, proofs)| proofs)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let mut num = 0;
for proof in proofs.iter() {
if let ProofStatus::Valid = proof {
num += 1;
}
}
(num, proofs.len() as u64)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{id, rewards_pools};
use std::collections::BTreeMap;
#[test]
fn test_account_data() {
solana_logger::setup();
let mut account = Account::default();
account.data.resize(STORAGE_ACCOUNT_SPACE as usize, 0);
let storage_account = StorageAccount::new(Pubkey::default(), &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::ArchiverStorage { .. } = &mut contract {
panic!("Contract should not decode into two types");
}
contract = StorageContract::ValidatorStorage {
owner: Pubkey::default(),
segment: 0,
hash: Hash::default(),
lockout_validations: BTreeMap::new(),
credits: Credits::default(),
};
storage_account.account.set_state(&contract).unwrap();
if let StorageContract::ArchiverStorage { .. } = contract {
panic!("Wrong contract type");
}
contract = StorageContract::ArchiverStorage {
owner: Pubkey::default(),
proofs: BTreeMap::new(),
validations: BTreeMap::new(),
credits: Credits::default(),
};
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 {
id: Pubkey::default(),
account: &mut Account {
owner: id(),
..Account::default()
},
};
let segment_index = 0;
let proof = Proof {
segment_index,
..Proof::default()
};
// account has no space
store_validation_result(
&Pubkey::default(),
&sysvar::clock::Clock::default(),
&mut account,
segment_index,
&vec![ProofStatus::default(); 1],
)
.unwrap_err();
account
.account
.data
.resize(STORAGE_ACCOUNT_SPACE as usize, 0);
let storage_contract = &mut account.account.state().unwrap();
if let StorageContract::Uninitialized = storage_contract {
let mut proofs = BTreeMap::new();
proofs.insert(0, vec![proof.clone()]);
*storage_contract = StorageContract::ArchiverStorage {
owner: Pubkey::default(),
proofs,
validations: BTreeMap::new(),
credits: Credits::default(),
};
};
account.account.set_state(storage_contract).unwrap();
// proof is valid
store_validation_result(
&Pubkey::default(),
&sysvar::clock::Clock::default(),
&mut account,
segment_index,
&vec![ProofStatus::Valid],
)
.unwrap();
// proof failed verification but we should still be able to store it
store_validation_result(
&Pubkey::default(),
&sysvar::clock::Clock::default(),
&mut account,
segment_index,
&vec![ProofStatus::NotValid],
)
.unwrap();
}
#[test]
fn test_redeemable() {
let mut credits = Credits {
epoch: 0,
current_epoch: 0,
redeemable: 100,
};
let mut owner_account = Account {
lamports: 1,
..Account::default()
};
let mut rewards_pool = create_rewards_pool();
let pool_id = rewards_pools::id();
let mut keyed_pool_account = KeyedAccount::new(&pool_id, false, &mut rewards_pool);
let mut owner = StorageAccount {
id: Pubkey::default(),
account: &mut owner_account,
};
// check that redeeming from depleted pools fails
keyed_pool_account.account.lamports = 0;
assert_eq!(
check_redeemable(&mut credits, 1.0, &mut keyed_pool_account, &mut owner),
Err(InstructionError::CustomError(
StorageError::RewardPoolDepleted as u32,
))
);
assert_eq!(owner.account.lamports, 1);
keyed_pool_account.account.lamports = 200;
assert_eq!(
check_redeemable(&mut credits, 1.0, &mut keyed_pool_account, &mut owner),
Ok(())
);
// check that the owner's balance increases
assert_eq!(owner.account.lamports, 101);
}
}

View File

@ -0,0 +1,189 @@
use crate::storage_contract::{ProofStatus, STORAGE_ACCOUNT_SPACE};
use crate::{id, rewards_pools};
use serde_derive::{Deserialize, Serialize};
use solana_sdk::hash::Hash;
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Signature;
use solana_sdk::system_instruction;
use solana_sdk::sysvar::{clock, rewards};
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub enum StorageAccountType {
Archiver,
Validator,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum StorageInstruction {
/// Initialize the account as a validator or archiver
///
/// Expects 1 Account:
/// 0 - Account to be initialized
InitializeStorage {
owner: Pubkey,
account_type: StorageAccountType,
},
SubmitMiningProof {
sha_state: Hash,
segment_index: u64,
signature: Signature,
blockhash: Hash,
},
AdvertiseStorageRecentBlockhash {
hash: Hash,
segment: u64,
},
/// Redeem storage reward credits
///
/// Expects 1 Account:
/// 0 - Storage account with credits to redeem
/// 1 - Clock Syscall to figure out the clock epoch
/// 2 - Archiver account to credit - this account *must* be the owner
/// 3 - MiningPool account to redeem credits from
/// 4 - Rewards Syscall to figure out point values
ClaimStorageReward,
ProofValidation {
/// The segment during which this proof was generated
segment: u64,
/// A Vec of proof masks per keyed archiver account loaded by the instruction
proofs: Vec<Vec<ProofStatus>>,
},
}
fn get_ratios() -> (u64, u64) {
// max number bytes available for account metas and proofs
// The maximum transaction size is == `PACKET_DATA_SIZE` (1232 bytes)
// There are approx. 900 bytes left over after the storage instruction is wrapped into
// a signed transaction.
static MAX_BYTES: u64 = 900;
let account_meta_size: u64 =
bincode::serialized_size(&AccountMeta::new(Pubkey::new_rand(), false)).unwrap_or(0);
let proof_size: u64 = bincode::serialized_size(&ProofStatus::default()).unwrap_or(0);
// the ratio between account meta size and a single proof status
let ratio = (account_meta_size + proof_size - 1) / proof_size;
let bytes = (MAX_BYTES + ratio - 1) / ratio;
(ratio, bytes)
}
/// Returns how many accounts and their proofs will fit in a single proof validation tx
///
/// # Arguments
///
/// * `proof_mask_max` - The largest proof mask across all accounts intended for submission
///
pub fn validation_account_limit(proof_mask_max: usize) -> u64 {
let (ratio, bytes) = get_ratios();
// account_meta_count * (ratio + proof_mask_max) = bytes
bytes / (ratio + proof_mask_max as u64)
}
pub fn proof_mask_limit() -> u64 {
let (ratio, bytes) = get_ratios();
bytes - ratio
}
pub fn create_storage_account(
from_pubkey: &Pubkey,
storage_owner: &Pubkey,
storage_pubkey: &Pubkey,
lamports: u64,
account_type: StorageAccountType,
) -> Vec<Instruction> {
vec![
system_instruction::create_account(
from_pubkey,
storage_pubkey,
lamports,
STORAGE_ACCOUNT_SPACE,
&id(),
),
Instruction::new(
id(),
&StorageInstruction::InitializeStorage {
owner: *storage_owner,
account_type,
},
vec![AccountMeta::new(*storage_pubkey, false)],
),
]
}
pub fn mining_proof(
storage_pubkey: &Pubkey,
sha_state: Hash,
segment_index: u64,
signature: Signature,
blockhash: Hash,
) -> Instruction {
let storage_instruction = StorageInstruction::SubmitMiningProof {
sha_state,
segment_index,
signature,
blockhash,
};
let account_metas = vec![
AccountMeta::new(*storage_pubkey, true),
AccountMeta::new(clock::id(), false),
];
Instruction::new(id(), &storage_instruction, account_metas)
}
pub fn advertise_recent_blockhash(
storage_pubkey: &Pubkey,
storage_hash: Hash,
segment: u64,
) -> Instruction {
let storage_instruction = StorageInstruction::AdvertiseStorageRecentBlockhash {
hash: storage_hash,
segment,
};
let account_metas = vec![
AccountMeta::new(*storage_pubkey, true),
AccountMeta::new(clock::id(), false),
];
Instruction::new(id(), &storage_instruction, account_metas)
}
pub fn proof_validation(
storage_pubkey: &Pubkey,
segment: u64,
checked_proofs: Vec<(Pubkey, Vec<ProofStatus>)>,
) -> Instruction {
let mut account_metas = vec![
AccountMeta::new(*storage_pubkey, true),
AccountMeta::new(clock::id(), false),
];
let mut proofs = vec![];
checked_proofs.into_iter().for_each(|(id, p)| {
proofs.push(p);
account_metas.push(AccountMeta::new(id, false))
});
let storage_instruction = StorageInstruction::ProofValidation { segment, proofs };
Instruction::new(id(), &storage_instruction, account_metas)
}
pub fn claim_reward(owner_pubkey: &Pubkey, storage_pubkey: &Pubkey) -> Instruction {
let storage_instruction = StorageInstruction::ClaimStorageReward;
let account_metas = vec![
AccountMeta::new(*storage_pubkey, false),
AccountMeta::new(clock::id(), false),
AccountMeta::new(rewards::id(), false),
AccountMeta::new(rewards_pools::random_id(), false),
AccountMeta::new(*owner_pubkey, false),
];
Instruction::new(id(), &storage_instruction, account_metas)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_size() {
// check that if there's 50 proof per account, only 1 account can fit in a single tx
assert_eq!(validation_account_limit(50), 1);
}
}

View File

@ -0,0 +1,96 @@
//! storage program
//! Receive mining proofs from miners, validate the answers
//! and give reward for good proofs.
use crate::{storage_contract::StorageAccount, storage_instruction::StorageInstruction};
use solana_sdk::{
account::KeyedAccount,
instruction::InstructionError,
instruction_processor_utils::limited_deserialize,
pubkey::Pubkey,
sysvar::{clock::Clock, rewards::Rewards, Sysvar},
};
pub fn process_instruction(
_program_id: &Pubkey,
keyed_accounts: &mut [KeyedAccount],
data: &[u8],
) -> Result<(), InstructionError> {
solana_logger::setup();
let (me, rest) = keyed_accounts.split_at_mut(1);
let me_unsigned = me[0].signer_key().is_none();
let mut storage_account = StorageAccount::new(*me[0].unsigned_key(), &mut me[0].account);
match limited_deserialize(data)? {
StorageInstruction::InitializeStorage {
owner,
account_type,
} => {
if !rest.is_empty() {
return Err(InstructionError::InvalidArgument);
}
storage_account.initialize_storage(owner, account_type)
}
StorageInstruction::SubmitMiningProof {
sha_state,
segment_index,
signature,
blockhash,
} => {
if me_unsigned || rest.len() != 1 {
// This instruction must be signed by `me`
return Err(InstructionError::InvalidArgument);
}
let clock = Clock::from_keyed_account(&rest[0])?;
storage_account.submit_mining_proof(
sha_state,
segment_index,
signature,
blockhash,
clock,
)
}
StorageInstruction::AdvertiseStorageRecentBlockhash { hash, segment } => {
if me_unsigned || rest.len() != 1 {
// This instruction must be signed by `me`
return Err(InstructionError::InvalidArgument);
}
let clock = Clock::from_keyed_account(&rest[0])?;
storage_account.advertise_storage_recent_blockhash(hash, segment, clock)
}
StorageInstruction::ClaimStorageReward => {
if rest.len() != 4 {
return Err(InstructionError::InvalidArgument);
}
let (clock, rest) = rest.split_at_mut(1);
let (rewards, rest) = rest.split_at_mut(1);
let (rewards_pools, owner) = rest.split_at_mut(1);
let rewards = Rewards::from_keyed_account(&rewards[0])?;
let clock = Clock::from_keyed_account(&clock[0])?;
let mut owner = StorageAccount::new(*owner[0].unsigned_key(), &mut owner[0].account);
storage_account.claim_storage_reward(&mut rewards_pools[0], clock, rewards, &mut owner)
}
StorageInstruction::ProofValidation { segment, proofs } => {
if rest.is_empty() {
return Err(InstructionError::InvalidArgument);
}
let (clock, rest) = rest.split_at_mut(1);
if me_unsigned || rest.is_empty() {
// This instruction must be signed by `me` and `rest` cannot be empty
return Err(InstructionError::InvalidArgument);
}
let me_id = storage_account.id;
let clock = Clock::from_keyed_account(&clock[0])?;
let mut rest: Vec<_> = rest
.iter_mut()
.map(|keyed_account| {
StorageAccount::new(*keyed_account.unsigned_key(), &mut keyed_account.account)
})
.collect();
storage_account.proof_validation(&me_id, clock, segment, proofs, &mut rest)
}
}
}