16
programs/bpf/Cargo.lock
generated
16
programs/bpf/Cargo.lock
generated
@ -1657,7 +1657,6 @@ dependencies = [
|
||||
"solana-rayon-threadlimit 1.2.0",
|
||||
"solana-sdk 1.2.0",
|
||||
"solana-stake-program 1.2.0",
|
||||
"solana-storage-program 1.2.0",
|
||||
"solana-vote-program 1.2.0",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -1727,21 +1726,6 @@ dependencies = [
|
||||
"thiserror 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-storage-program"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"solana-logger 1.2.0",
|
||||
"solana-sdk 1.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-vote-program"
|
||||
version = "1.2.0"
|
||||
|
@ -3,7 +3,7 @@
|
||||
extern crate solana_sdk;
|
||||
use solana_sdk::{
|
||||
account_info::AccountInfo,
|
||||
clock::{get_segment_from_slot, DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT},
|
||||
clock::DEFAULT_SLOTS_PER_EPOCH,
|
||||
entrypoint,
|
||||
entrypoint::ProgramResult,
|
||||
info,
|
||||
@ -27,10 +27,6 @@ fn process_instruction(
|
||||
sysvar::clock::id().log();
|
||||
let clock = Clock::from_account_info(&accounts[2]).expect("clock");
|
||||
assert_eq!(clock.slot, DEFAULT_SLOTS_PER_EPOCH + 1);
|
||||
assert_eq!(
|
||||
clock.segment,
|
||||
get_segment_from_slot(clock.slot, DEFAULT_SLOTS_PER_SEGMENT)
|
||||
);
|
||||
|
||||
// Fees
|
||||
info!("Fees identifier:");
|
||||
|
@ -439,7 +439,7 @@ mod tests {
|
||||
RefCell::new(if sysvar::clock::check_id(&meta.pubkey) {
|
||||
sysvar::clock::Clock::default().create_account(1)
|
||||
} else if sysvar::rewards::check_id(&meta.pubkey) {
|
||||
sysvar::rewards::create_account(1, 0.0, 0.0)
|
||||
sysvar::rewards::create_account(1, 0.0)
|
||||
} else if sysvar::stake_history::check_id(&meta.pubkey) {
|
||||
sysvar::stake_history::create_account(1, &StakeHistory::default())
|
||||
} else if config::check_id(&meta.pubkey) {
|
||||
@ -680,7 +680,7 @@ mod tests {
|
||||
KeyedAccount::new(
|
||||
&sysvar::rewards::id(),
|
||||
false,
|
||||
&RefCell::new(sysvar::rewards::create_account(1, 0.0, 0.0))
|
||||
&RefCell::new(sysvar::rewards::create_account(1, 0.0))
|
||||
),
|
||||
KeyedAccount::new(
|
||||
&sysvar::stake_history::id(),
|
||||
@ -719,7 +719,7 @@ mod tests {
|
||||
KeyedAccount::new(
|
||||
&sysvar::rewards::id(),
|
||||
false,
|
||||
&RefCell::new(sysvar::rewards::create_account(1, 0.0, 0.0))
|
||||
&RefCell::new(sysvar::rewards::create_account(1, 0.0))
|
||||
),
|
||||
],
|
||||
&serialize(&StakeInstruction::Deactivate).unwrap(),
|
||||
|
@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "solana-storage-program"
|
||||
version = "1.2.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.1"
|
||||
log = "0.4.8"
|
||||
rand = "0.7.0"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
serde = "1.0.110"
|
||||
serde_derive = "1.0.103"
|
||||
solana-logger = { path = "../../logger", version = "1.2.0" }
|
||||
solana-sdk = { path = "../../sdk", version = "1.2.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.3.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib", "cdylib"]
|
||||
name = "solana_storage_program"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
@ -1,12 +0,0 @@
|
||||
pub mod rewards_pools;
|
||||
pub mod storage_contract;
|
||||
pub mod storage_instruction;
|
||||
pub mod storage_processor;
|
||||
|
||||
use crate::storage_processor::process_instruction;
|
||||
|
||||
solana_sdk::declare_program!(
|
||||
"Storage111111111111111111111111111111111111",
|
||||
solana_storage_program,
|
||||
process_instruction
|
||||
);
|
@ -1,51 +0,0 @@
|
||||
//! 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
|
||||
solana_sdk::declare_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) -> u64 {
|
||||
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());
|
||||
}
|
||||
0 // didn't consume any lamports
|
||||
}
|
||||
|
||||
pub fn random_id() -> Pubkey {
|
||||
let mut id = Hash::new(id().as_ref());
|
||||
|
||||
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 {
|
||||
assert!(genesis_config.rewards_pools.get(&random_id()).is_some())
|
||||
}
|
||||
}
|
||||
}
|
@ -1,642 +0,0 @@
|
||||
use crate::storage_instruction::StorageAccountType;
|
||||
use log::*;
|
||||
use num_derive::FromPrimitive;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use solana_sdk::{
|
||||
account::{Account, KeyedAccount},
|
||||
account_utils::StateMut,
|
||||
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(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::Custom(
|
||||
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::Custom(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::Custom(
|
||||
StorageError::DuplicateProof as u32,
|
||||
));
|
||||
}
|
||||
if segment_proofs.len() >= MAX_PROOFS_PER_SEGMENT {
|
||||
// do not accept more than MAX_PROOFS_PER_SEGMENT
|
||||
return Err(InstructionError::Custom(
|
||||
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::Custom(
|
||||
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::Custom(
|
||||
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::Custom(
|
||||
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: &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::Custom(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::Custom(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: &KeyedAccount,
|
||||
owner: &mut StorageAccount,
|
||||
) -> Result<(), InstructionError> {
|
||||
let rewards = (credits.redeemable as f64 * storage_point_value) as u64;
|
||||
if rewards_pool.lamports()? < rewards {
|
||||
Err(InstructionError::Custom(
|
||||
StorageError::RewardPoolDepleted as u32,
|
||||
))
|
||||
} else {
|
||||
if rewards >= 1 {
|
||||
rewards_pool.try_account_ref_mut()?.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::{cell::RefCell, 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 = RefCell::new(create_rewards_pool());
|
||||
let pool_id = rewards_pools::id();
|
||||
let 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.borrow_mut().lamports = 0;
|
||||
assert_eq!(
|
||||
check_redeemable(&mut credits, 1.0, &keyed_pool_account, &mut owner),
|
||||
Err(InstructionError::Custom(
|
||||
StorageError::RewardPoolDepleted as u32,
|
||||
))
|
||||
);
|
||||
assert_eq!(owner.account.lamports, 1);
|
||||
|
||||
keyed_pool_account.account.borrow_mut().lamports = 200;
|
||||
assert_eq!(
|
||||
check_redeemable(&mut credits, 1.0, &keyed_pool_account, &mut owner),
|
||||
Ok(())
|
||||
);
|
||||
// check that the owner's balance increases
|
||||
assert_eq!(owner.account.lamports, 101);
|
||||
}
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,296 +0,0 @@
|
||||
//! 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,
|
||||
program_utils::limited_deserialize,
|
||||
pubkey::Pubkey,
|
||||
sysvar::{clock::Clock, rewards::Rewards, Sysvar},
|
||||
};
|
||||
|
||||
pub fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
keyed_accounts: &[KeyedAccount],
|
||||
data: &[u8],
|
||||
) -> Result<(), InstructionError> {
|
||||
solana_logger::setup();
|
||||
|
||||
let (me, rest) = keyed_accounts.split_at(1);
|
||||
let me_unsigned = me[0].signer_key().is_none();
|
||||
let mut me_account = me[0].try_account_ref_mut()?;
|
||||
let mut storage_account = StorageAccount::new(*me[0].unsigned_key(), &mut me_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(1);
|
||||
let (rewards, rest) = rest.split_at(1);
|
||||
let (rewards_pools, owner) = rest.split_at(1);
|
||||
|
||||
let rewards = Rewards::from_keyed_account(&rewards[0])?;
|
||||
let clock = Clock::from_keyed_account(&clock[0])?;
|
||||
let mut owner_account = owner[0].try_account_ref_mut()?;
|
||||
let mut owner = StorageAccount::new(*owner[0].unsigned_key(), &mut owner_account);
|
||||
|
||||
storage_account.claim_storage_reward(&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(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 = rest
|
||||
.iter()
|
||||
.map(|keyed_account| Ok((keyed_account, keyed_account.try_account_ref_mut()?)))
|
||||
.collect::<Result<Vec<_>, InstructionError>>()?;
|
||||
let mut rest = rest
|
||||
.iter_mut()
|
||||
.map(|(keyed_account, account_ref)| {
|
||||
StorageAccount::new(*keyed_account.unsigned_key(), account_ref)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
storage_account.proof_validation(&me_id, clock, segment, proofs, &mut rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
id,
|
||||
storage_contract::STORAGE_ACCOUNT_SPACE,
|
||||
storage_instruction::{self, StorageAccountType},
|
||||
};
|
||||
use log::*;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use solana_sdk::{
|
||||
account::{create_keyed_accounts, Account, KeyedAccount},
|
||||
clock::DEFAULT_SLOTS_PER_SEGMENT,
|
||||
hash::Hash,
|
||||
instruction::{Instruction, InstructionError},
|
||||
signature::Signature,
|
||||
sysvar::{
|
||||
clock::{self, Clock},
|
||||
Sysvar,
|
||||
},
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
fn test_instruction(
|
||||
ix: &Instruction,
|
||||
program_accounts: &[Account],
|
||||
) -> Result<(), InstructionError> {
|
||||
let program_accounts: Vec<_> = program_accounts
|
||||
.iter()
|
||||
.map(|account| RefCell::new(account.clone()))
|
||||
.collect();
|
||||
let keyed_accounts: Vec<_> = ix
|
||||
.accounts
|
||||
.iter()
|
||||
.zip(program_accounts.iter())
|
||||
.map(|(account_meta, account)| {
|
||||
KeyedAccount::new(&account_meta.pubkey, account_meta.is_signer, account)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let ret = process_instruction(&id(), &keyed_accounts, &ix.data);
|
||||
info!("ret: {:?}", ret);
|
||||
ret
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proof_bounds() {
|
||||
let account_owner = Pubkey::new_rand();
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let mut account = Account {
|
||||
data: vec![0; STORAGE_ACCOUNT_SPACE as usize],
|
||||
..Account::default()
|
||||
};
|
||||
{
|
||||
let mut storage_account = StorageAccount::new(pubkey, &mut account);
|
||||
storage_account
|
||||
.initialize_storage(account_owner, StorageAccountType::Archiver)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let ix = storage_instruction::mining_proof(
|
||||
&pubkey,
|
||||
Hash::default(),
|
||||
0,
|
||||
Signature::default(),
|
||||
Hash::default(),
|
||||
);
|
||||
// the proof is for segment 0, need to move the slot into segment 2
|
||||
let mut clock_account = Clock::default().create_account(1);
|
||||
Clock::to_account(
|
||||
&Clock {
|
||||
slot: DEFAULT_SLOTS_PER_SEGMENT * 2,
|
||||
segment: 2,
|
||||
..Clock::default()
|
||||
},
|
||||
&mut clock_account,
|
||||
);
|
||||
|
||||
assert_eq!(test_instruction(&ix, &[account, clock_account]), Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_storage_tx() {
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let accounts = [(&pubkey, &RefCell::new(Account::default()))];
|
||||
let keyed_accounts = create_keyed_accounts(&accounts);
|
||||
assert!(process_instruction(&id(), &keyed_accounts, &[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_overflow() {
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let clock_id = clock::id();
|
||||
let mut keyed_accounts = Vec::new();
|
||||
let user_account = RefCell::new(Account::default());
|
||||
let clock_account = RefCell::new(Clock::default().create_account(1));
|
||||
keyed_accounts.push(KeyedAccount::new(&pubkey, true, &user_account));
|
||||
keyed_accounts.push(KeyedAccount::new(&clock_id, false, &clock_account));
|
||||
|
||||
let ix = storage_instruction::advertise_recent_blockhash(&pubkey, Hash::default(), 1);
|
||||
|
||||
assert_eq!(
|
||||
process_instruction(&id(), &keyed_accounts, &ix.data),
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_accounts_len() {
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let accounts = [Account::default()];
|
||||
|
||||
let ix = storage_instruction::mining_proof(
|
||||
&pubkey,
|
||||
Hash::default(),
|
||||
0,
|
||||
Signature::default(),
|
||||
Hash::default(),
|
||||
);
|
||||
// move tick height into segment 1
|
||||
let mut clock_account = Clock::default().create_account(1);
|
||||
Clock::to_account(
|
||||
&Clock {
|
||||
slot: 16,
|
||||
segment: 1,
|
||||
..Clock::default()
|
||||
},
|
||||
&mut clock_account,
|
||||
);
|
||||
|
||||
assert!(test_instruction(&ix, &accounts).is_err());
|
||||
|
||||
let accounts = [Account::default(), clock_account, Account::default()];
|
||||
|
||||
assert!(test_instruction(&ix, &accounts).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_submit_mining_invalid_slot() {
|
||||
solana_logger::setup();
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let mut accounts = [Account::default(), Account::default()];
|
||||
accounts[0].data.resize(STORAGE_ACCOUNT_SPACE as usize, 0);
|
||||
accounts[1].data.resize(STORAGE_ACCOUNT_SPACE as usize, 0);
|
||||
|
||||
let ix = storage_instruction::mining_proof(
|
||||
&pubkey,
|
||||
Hash::default(),
|
||||
0,
|
||||
Signature::default(),
|
||||
Hash::default(),
|
||||
);
|
||||
|
||||
// submitting a proof for a slot in the past, so this should fail
|
||||
assert!(test_instruction(&ix, &accounts).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_submit_mining_ok() {
|
||||
solana_logger::setup();
|
||||
let account_owner = Pubkey::new_rand();
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let mut account = Account::default();
|
||||
account.data.resize(STORAGE_ACCOUNT_SPACE as usize, 0);
|
||||
{
|
||||
let mut storage_account = StorageAccount::new(pubkey, &mut account);
|
||||
storage_account
|
||||
.initialize_storage(account_owner, StorageAccountType::Archiver)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let ix = storage_instruction::mining_proof(
|
||||
&pubkey,
|
||||
Hash::default(),
|
||||
0,
|
||||
Signature::default(),
|
||||
Hash::default(),
|
||||
);
|
||||
// move slot into segment 1
|
||||
let mut clock_account = Clock::default().create_account(1);
|
||||
Clock::to_account(
|
||||
&Clock {
|
||||
slot: DEFAULT_SLOTS_PER_SEGMENT,
|
||||
segment: 1,
|
||||
..Clock::default()
|
||||
},
|
||||
&mut clock_account,
|
||||
);
|
||||
|
||||
assert_matches!(test_instruction(&ix, &[account, clock_account]), Ok(_));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user