Add storage point tracking and tie in storage rewards to economics (#4824)
* Add storage point tracking and tie in storage rewards to epochs and economics * Prevent validators from updating their validations for a segment * Fix test * Retain syscall scoping for readability * Update Credits to own epoch tracking
This commit is contained in:
parent
8a64e1ddc3
commit
df1c473341
|
@ -338,11 +338,8 @@ impl Replicator {
|
|||
let client = crate::gossip_service::get_client(&nodes);
|
||||
|
||||
if let Ok(Some(account)) = client.get_account(&self.storage_keypair.pubkey()) {
|
||||
if let Ok(StorageContract::ReplicatorStorage {
|
||||
reward_validations, ..
|
||||
}) = account.state()
|
||||
{
|
||||
if !reward_validations.is_empty() {
|
||||
if let Ok(StorageContract::ReplicatorStorage { validations, .. }) = account.state() {
|
||||
if !validations.is_empty() {
|
||||
let ix = storage_instruction::claim_reward(
|
||||
&self.keypair.pubkey(),
|
||||
&self.storage_keypair.pubkey(),
|
||||
|
|
|
@ -9,14 +9,33 @@ use solana_sdk::hash::Hash;
|
|||
use solana_sdk::instruction::InstructionError;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Signature;
|
||||
use solana_sdk::syscall;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub const VALIDATOR_REWARD: u64 = 200;
|
||||
pub const REPLICATOR_REWARD: u64 = 200;
|
||||
// Todo Tune this for actual use cases when replicators are feature complete
|
||||
// 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: u64,
|
||||
// currently pending credits
|
||||
pub current_epoch: u64,
|
||||
// credits ready to be claimed
|
||||
pub redeemable: u64,
|
||||
}
|
||||
|
||||
impl Credits {
|
||||
pub fn update_epoch(&mut self, current_epoch: u64) {
|
||||
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,
|
||||
|
@ -64,11 +83,12 @@ pub enum StorageContract {
|
|||
// Most recently advertised blockhash
|
||||
hash: Hash,
|
||||
// Lockouts and Rewards are per segment per replicator. It needs to remain this way until
|
||||
// the challenge stage is added. Once challenges are in rewards can just be a number
|
||||
// the challenge stage is added.
|
||||
lockout_validations: BTreeMap<usize, BTreeMap<Pubkey, Vec<ProofStatus>>>,
|
||||
// lamports that are ready to be claimed
|
||||
pending_lamports: u64,
|
||||
// Used to keep track of ongoing credits
|
||||
credits: Credits,
|
||||
},
|
||||
|
||||
ReplicatorStorage {
|
||||
owner: Pubkey,
|
||||
// TODO what to do about duplicate proofs across segments? - Check the blockhashes
|
||||
|
@ -76,7 +96,9 @@ pub enum StorageContract {
|
|||
proofs: BTreeMap<usize, 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
|
||||
reward_validations: BTreeMap<usize, BTreeMap<Pubkey, Vec<ProofStatus>>>,
|
||||
validations: BTreeMap<usize, BTreeMap<Pubkey, Vec<ProofStatus>>>,
|
||||
// Used to keep track of ongoing credits
|
||||
credits: Credits,
|
||||
},
|
||||
|
||||
RewardsPool,
|
||||
|
@ -92,7 +114,7 @@ pub fn create_validator_storage_account(owner: Pubkey, lamports: u64) -> Account
|
|||
slot: 0,
|
||||
hash: Hash::default(),
|
||||
lockout_validations: BTreeMap::new(),
|
||||
pending_lamports: 0,
|
||||
credits: Credits::default(),
|
||||
})
|
||||
.expect("set_state");
|
||||
|
||||
|
@ -115,7 +137,8 @@ impl<'a> StorageAccount<'a> {
|
|||
*storage_contract = StorageContract::ReplicatorStorage {
|
||||
owner,
|
||||
proofs: BTreeMap::new(),
|
||||
reward_validations: BTreeMap::new(),
|
||||
validations: BTreeMap::new(),
|
||||
credits: Credits::default(),
|
||||
};
|
||||
self.account.set_state(storage_contract)
|
||||
} else {
|
||||
|
@ -131,7 +154,7 @@ impl<'a> StorageAccount<'a> {
|
|||
slot: 0,
|
||||
hash: Hash::default(),
|
||||
lockout_validations: BTreeMap::new(),
|
||||
pending_lamports: 0,
|
||||
credits: Credits::default(),
|
||||
};
|
||||
self.account.set_state(storage_contract)
|
||||
} else {
|
||||
|
@ -145,16 +168,17 @@ impl<'a> StorageAccount<'a> {
|
|||
segment_index: usize,
|
||||
signature: Signature,
|
||||
blockhash: Hash,
|
||||
current_slot: u64,
|
||||
current: syscall::current::Current,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut storage_contract = &mut self.account.state()?;
|
||||
if let StorageContract::ReplicatorStorage {
|
||||
proofs,
|
||||
reward_validations,
|
||||
validations,
|
||||
credits,
|
||||
..
|
||||
} = &mut storage_contract
|
||||
{
|
||||
let current_segment = get_segment_from_slot(current_slot);
|
||||
let current_segment = get_segment_from_slot(current.slot);
|
||||
|
||||
// clean up the account
|
||||
// TODO check for time correctness - storage seems to run at a delay of about 3
|
||||
|
@ -163,7 +187,7 @@ impl<'a> StorageAccount<'a> {
|
|||
.filter(|(segment, _)| **segment >= current_segment.saturating_sub(5))
|
||||
.map(|(segment, proofs)| (*segment, proofs.clone()))
|
||||
.collect();
|
||||
*reward_validations = reward_validations
|
||||
*validations = validations
|
||||
.iter()
|
||||
.filter(|(segment, _)| **segment >= current_segment.saturating_sub(10))
|
||||
.map(|(segment, rewards)| (*segment, rewards.clone()))
|
||||
|
@ -207,6 +231,7 @@ impl<'a> StorageAccount<'a> {
|
|||
StorageError::ProofLimitReached as u32,
|
||||
));
|
||||
}
|
||||
credits.update_epoch(current.epoch);
|
||||
segment_proofs.push(proof);
|
||||
self.account.set_state(storage_contract)
|
||||
} else {
|
||||
|
@ -218,18 +243,18 @@ impl<'a> StorageAccount<'a> {
|
|||
&mut self,
|
||||
hash: Hash,
|
||||
slot: u64,
|
||||
current_slot: u64,
|
||||
current: syscall::current::Current,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut storage_contract = &mut self.account.state()?;
|
||||
if let StorageContract::ValidatorStorage {
|
||||
slot: state_slot,
|
||||
hash: state_hash,
|
||||
lockout_validations,
|
||||
pending_lamports,
|
||||
credits,
|
||||
..
|
||||
} = &mut storage_contract
|
||||
{
|
||||
let current_segment = get_segment_from_slot(current_slot);
|
||||
let current_segment = get_segment_from_slot(current.slot);
|
||||
let original_segment = get_segment_from_slot(*state_slot);
|
||||
let segment = get_segment_from_slot(slot);
|
||||
debug!(
|
||||
|
@ -245,20 +270,11 @@ impl<'a> StorageAccount<'a> {
|
|||
*state_slot = slot;
|
||||
*state_hash = hash;
|
||||
|
||||
// storage epoch updated, move the lockout_validations to pending_lamports
|
||||
let num_validations = count_valid_proofs(
|
||||
&lockout_validations
|
||||
.iter()
|
||||
.flat_map(|(_segment, proofs)| {
|
||||
proofs
|
||||
.iter()
|
||||
.flat_map(|(_, proof)| proof.clone())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
// storage epoch updated, move the lockout_validations to credits
|
||||
let (_num_valid, total_validations) = count_valid_proofs(&lockout_validations);
|
||||
lockout_validations.clear();
|
||||
*pending_lamports += VALIDATOR_REWARD * num_validations;
|
||||
credits.update_epoch(current.epoch);
|
||||
credits.current_epoch += total_validations;
|
||||
self.account.set_state(storage_contract)
|
||||
} else {
|
||||
Err(InstructionError::InvalidArgument)?
|
||||
|
@ -268,6 +284,7 @@ impl<'a> StorageAccount<'a> {
|
|||
pub fn proof_validation(
|
||||
&mut self,
|
||||
me: &Pubkey,
|
||||
current: syscall::current::Current,
|
||||
segment: u64,
|
||||
proofs_per_account: Vec<Vec<ProofStatus>>,
|
||||
replicator_accounts: &mut [StorageAccount],
|
||||
|
@ -331,7 +348,14 @@ impl<'a> StorageAccount<'a> {
|
|||
.into_iter()
|
||||
.zip(accounts.into_iter())
|
||||
.filter_map(|(checked_proofs, account)| {
|
||||
if store_validation_result(me, account, segment_index, &checked_proofs).is_ok()
|
||||
if store_validation_result(
|
||||
me,
|
||||
¤t,
|
||||
account,
|
||||
segment_index,
|
||||
&checked_proofs,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
Some((account.id, checked_proofs))
|
||||
} else {
|
||||
|
@ -359,13 +383,15 @@ impl<'a> StorageAccount<'a> {
|
|||
pub fn claim_storage_reward(
|
||||
&mut self,
|
||||
rewards_pool: &mut KeyedAccount,
|
||||
current: syscall::current::Current,
|
||||
rewards: syscall::rewards::Rewards,
|
||||
owner: &mut StorageAccount,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut storage_contract = &mut self.account.state()?;
|
||||
|
||||
if let StorageContract::ValidatorStorage {
|
||||
owner: account_owner,
|
||||
pending_lamports,
|
||||
credits,
|
||||
..
|
||||
} = &mut storage_contract
|
||||
{
|
||||
|
@ -375,21 +401,24 @@ impl<'a> StorageAccount<'a> {
|
|||
))?
|
||||
}
|
||||
|
||||
let pending = *pending_lamports;
|
||||
credits.update_epoch(current.epoch);
|
||||
let pending = (credits.redeemable as f64 * rewards.storage_point_value) as u64;
|
||||
if rewards_pool.account.lamports < pending {
|
||||
println!("reward pool has {}", rewards_pool.account.lamports);
|
||||
Err(InstructionError::CustomError(
|
||||
StorageError::RewardPoolDepleted as u32,
|
||||
))?
|
||||
}
|
||||
rewards_pool.account.lamports -= pending;
|
||||
owner.account.lamports += pending;
|
||||
//clear pending_lamports
|
||||
*pending_lamports = 0;
|
||||
if pending >= 1 {
|
||||
rewards_pool.account.lamports -= pending;
|
||||
owner.account.lamports += pending;
|
||||
//clear credits
|
||||
credits.redeemable = 0;
|
||||
}
|
||||
self.account.set_state(storage_contract)
|
||||
} else if let StorageContract::ReplicatorStorage {
|
||||
owner: account_owner,
|
||||
reward_validations,
|
||||
validations,
|
||||
credits,
|
||||
..
|
||||
} = &mut storage_contract
|
||||
{
|
||||
|
@ -398,22 +427,21 @@ impl<'a> StorageAccount<'a> {
|
|||
StorageError::InvalidOwner as u32,
|
||||
))?
|
||||
}
|
||||
|
||||
let checked_proofs = reward_validations
|
||||
.iter()
|
||||
.flat_map(|(_, proofs)| {
|
||||
proofs
|
||||
.iter()
|
||||
.flat_map(|(_, proofs)| proofs.clone())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
reward_validations.clear();
|
||||
let total_proofs = checked_proofs.len() as u64;
|
||||
let num_validations = count_valid_proofs(&checked_proofs);
|
||||
let reward = num_validations * REPLICATOR_REWARD * (num_validations / total_proofs);
|
||||
rewards_pool.account.lamports -= reward;
|
||||
owner.account.lamports += reward;
|
||||
credits.update_epoch(current.epoch);
|
||||
let (num_validations, _total_proofs) = count_valid_proofs(&validations);
|
||||
credits.current_epoch += num_validations;
|
||||
validations.clear();
|
||||
let reward = (credits.redeemable as f64 * rewards.storage_point_value) as u64;
|
||||
if rewards_pool.account.lamports < reward {
|
||||
Err(InstructionError::CustomError(
|
||||
StorageError::RewardPoolDepleted as u32,
|
||||
))?
|
||||
}
|
||||
if reward >= 1 {
|
||||
rewards_pool.account.lamports -= reward;
|
||||
owner.account.lamports += reward;
|
||||
credits.redeemable = 0;
|
||||
}
|
||||
self.account.set_state(storage_contract)
|
||||
} else {
|
||||
Err(InstructionError::InvalidArgument)?
|
||||
|
@ -428,6 +456,7 @@ pub fn create_rewards_pool() -> Account {
|
|||
/// Store the result of a proof validation into the replicator account
|
||||
fn store_validation_result(
|
||||
me: &Pubkey,
|
||||
current: &syscall::current::Current,
|
||||
storage_account: &mut StorageAccount,
|
||||
segment: usize,
|
||||
proof_mask: &[ProofStatus],
|
||||
|
@ -436,7 +465,8 @@ fn store_validation_result(
|
|||
match &mut storage_contract {
|
||||
StorageContract::ReplicatorStorage {
|
||||
proofs,
|
||||
reward_validations,
|
||||
validations,
|
||||
credits,
|
||||
..
|
||||
} => {
|
||||
if !proofs.contains_key(&segment) {
|
||||
|
@ -447,24 +477,39 @@ fn store_validation_result(
|
|||
return Err(InstructionError::InvalidAccountData);
|
||||
}
|
||||
|
||||
reward_validations
|
||||
.entry(segment)
|
||||
.or_default()
|
||||
.insert(*me, proof_mask.to_vec());
|
||||
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(current.epoch);
|
||||
credits.current_epoch += total_validations - recorded_validations;
|
||||
}
|
||||
_ => return Err(InstructionError::InvalidAccountData),
|
||||
}
|
||||
storage_account.account.set_state(&storage_contract)
|
||||
}
|
||||
|
||||
fn count_valid_proofs(proofs: &[ProofStatus]) -> u64 {
|
||||
fn count_valid_proofs(
|
||||
validations: &BTreeMap<usize, 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 {
|
||||
for proof in proofs.iter() {
|
||||
if let ProofStatus::Valid = proof {
|
||||
num += 1;
|
||||
}
|
||||
}
|
||||
num
|
||||
(num, proofs.len() as u64)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -493,7 +538,7 @@ mod tests {
|
|||
slot: 0,
|
||||
hash: Hash::default(),
|
||||
lockout_validations: BTreeMap::new(),
|
||||
pending_lamports: 0,
|
||||
credits: Credits::default(),
|
||||
};
|
||||
storage_account.account.set_state(&contract).unwrap();
|
||||
if let StorageContract::ReplicatorStorage { .. } = contract {
|
||||
|
@ -502,7 +547,8 @@ mod tests {
|
|||
contract = StorageContract::ReplicatorStorage {
|
||||
owner: Pubkey::default(),
|
||||
proofs: BTreeMap::new(),
|
||||
reward_validations: BTreeMap::new(),
|
||||
validations: BTreeMap::new(),
|
||||
credits: Credits::default(),
|
||||
};
|
||||
storage_account.account.set_state(&contract).unwrap();
|
||||
if let StorageContract::ValidatorStorage { .. } = contract {
|
||||
|
@ -530,6 +576,7 @@ mod tests {
|
|||
// account has no space
|
||||
store_validation_result(
|
||||
&Pubkey::default(),
|
||||
&syscall::current::Current::default(),
|
||||
&mut account,
|
||||
segment_index,
|
||||
&vec![ProofStatus::default(); 1],
|
||||
|
@ -547,7 +594,8 @@ mod tests {
|
|||
*storage_contract = StorageContract::ReplicatorStorage {
|
||||
owner: Pubkey::default(),
|
||||
proofs,
|
||||
reward_validations: BTreeMap::new(),
|
||||
validations: BTreeMap::new(),
|
||||
credits: Credits::default(),
|
||||
};
|
||||
};
|
||||
account.account.set_state(storage_contract).unwrap();
|
||||
|
@ -555,6 +603,7 @@ mod tests {
|
|||
// proof is valid
|
||||
store_validation_result(
|
||||
&Pubkey::default(),
|
||||
&syscall::current::Current::default(),
|
||||
&mut account,
|
||||
segment_index,
|
||||
&vec![ProofStatus::Valid],
|
||||
|
@ -564,6 +613,7 @@ mod tests {
|
|||
// proof failed verification but we should still be able to store it
|
||||
store_validation_result(
|
||||
&Pubkey::default(),
|
||||
&syscall::current::Current::default(),
|
||||
&mut account,
|
||||
segment_index,
|
||||
&vec![ProofStatus::NotValid],
|
||||
|
|
|
@ -5,7 +5,7 @@ use solana_sdk::hash::Hash;
|
|||
use solana_sdk::instruction::{AccountMeta, Instruction};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Signature;
|
||||
use solana_sdk::syscall::current;
|
||||
use solana_sdk::syscall::{current, rewards};
|
||||
use solana_sdk::system_instruction;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
@ -35,8 +35,10 @@ pub enum StorageInstruction {
|
|||
///
|
||||
/// Expects 1 Account:
|
||||
/// 0 - Storage account with credits to redeem
|
||||
/// 1 - MiningPool account to redeem credits from
|
||||
/// 1 - Current Syscall to figure out the current epoch
|
||||
/// 2 - Replicator 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
|
||||
|
@ -168,7 +170,10 @@ pub fn proof_validation(
|
|||
segment: u64,
|
||||
checked_proofs: Vec<(Pubkey, Vec<ProofStatus>)>,
|
||||
) -> Instruction {
|
||||
let mut account_metas = vec![AccountMeta::new(*storage_pubkey, true)];
|
||||
let mut account_metas = vec![
|
||||
AccountMeta::new(*storage_pubkey, true),
|
||||
AccountMeta::new(current::id(), false),
|
||||
];
|
||||
let mut proofs = vec![];
|
||||
checked_proofs.into_iter().for_each(|(id, p)| {
|
||||
proofs.push(p);
|
||||
|
@ -182,6 +187,8 @@ pub fn claim_reward(owner_pubkey: &Pubkey, storage_pubkey: &Pubkey) -> Instructi
|
|||
let storage_instruction = StorageInstruction::ClaimStorageReward;
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*storage_pubkey, false),
|
||||
AccountMeta::new(current::id(), false),
|
||||
AccountMeta::new(rewards::id(), false),
|
||||
AccountMeta::new(rewards_pools::random_id(), false),
|
||||
AccountMeta::new(*owner_pubkey, false),
|
||||
];
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::storage_instruction::StorageInstruction;
|
|||
use solana_sdk::account::KeyedAccount;
|
||||
use solana_sdk::instruction::InstructionError;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::syscall::current::Current;
|
||||
use solana_sdk::syscall;
|
||||
|
||||
pub fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
|
@ -42,13 +42,13 @@ pub fn process_instruction(
|
|||
// This instruction must be signed by `me`
|
||||
Err(InstructionError::InvalidArgument)?;
|
||||
}
|
||||
let current = Current::from(&rest[0].account).unwrap();
|
||||
let current = syscall::current::from_keyed_account(&rest[0])?;
|
||||
storage_account.submit_mining_proof(
|
||||
sha_state,
|
||||
segment_index,
|
||||
signature,
|
||||
blockhash,
|
||||
current.slot,
|
||||
current,
|
||||
)
|
||||
}
|
||||
StorageInstruction::AdvertiseStorageRecentBlockhash { hash, slot } => {
|
||||
|
@ -56,31 +56,47 @@ pub fn process_instruction(
|
|||
// This instruction must be signed by `me`
|
||||
Err(InstructionError::InvalidArgument)?;
|
||||
}
|
||||
let current = Current::from(&rest[0].account).unwrap();
|
||||
storage_account.advertise_storage_recent_blockhash(hash, slot, current.slot)
|
||||
let current = syscall::current::from_keyed_account(&rest[0])?;
|
||||
storage_account.advertise_storage_recent_blockhash(hash, slot, current)
|
||||
}
|
||||
StorageInstruction::ClaimStorageReward => {
|
||||
if rest.len() != 2 {
|
||||
if rest.len() != 4 {
|
||||
Err(InstructionError::InvalidArgument)?;
|
||||
}
|
||||
let (mining_pool, owner) = rest.split_at_mut(1);
|
||||
let (current, 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 = syscall::rewards::from_keyed_account(&rewards[0])?;
|
||||
let current = syscall::current::from_keyed_account(¤t[0])?;
|
||||
let mut owner = StorageAccount::new(*owner[0].unsigned_key(), &mut owner[0].account);
|
||||
|
||||
storage_account.claim_storage_reward(&mut mining_pool[0], &mut owner)
|
||||
storage_account.claim_storage_reward(
|
||||
&mut rewards_pools[0],
|
||||
current,
|
||||
rewards,
|
||||
&mut owner,
|
||||
)
|
||||
}
|
||||
StorageInstruction::ProofValidation { segment, proofs } => {
|
||||
if rest.is_empty() {
|
||||
Err(InstructionError::InvalidArgument)?;
|
||||
}
|
||||
|
||||
let (current, rest) = rest.split_at_mut(1);
|
||||
if me_unsigned || rest.is_empty() {
|
||||
// This instruction must be signed by `me` and `rest` cannot be empty
|
||||
Err(InstructionError::InvalidArgument)?;
|
||||
}
|
||||
let me_id = storage_account.id;
|
||||
let current = syscall::current::from_keyed_account(¤t[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, segment, proofs, &mut rest)
|
||||
storage_account.proof_validation(&me_id, current, segment, proofs, &mut rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,14 +12,13 @@ use solana_sdk::instruction::{Instruction, InstructionError};
|
|||
use solana_sdk::message::Message;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
|
||||
use solana_sdk::syscall::current;
|
||||
use solana_sdk::syscall::current::Current;
|
||||
use solana_sdk::syscall::rewards::Rewards;
|
||||
use solana_sdk::syscall::{current, rewards};
|
||||
use solana_sdk::system_instruction;
|
||||
use solana_sdk::timing::DEFAULT_TICKS_PER_SLOT;
|
||||
use solana_storage_api::storage_contract::StorageAccount;
|
||||
use solana_storage_api::storage_contract::{
|
||||
ProofStatus, StorageContract, REPLICATOR_REWARD, STORAGE_ACCOUNT_SPACE, VALIDATOR_REWARD,
|
||||
};
|
||||
use solana_storage_api::storage_contract::{ProofStatus, StorageContract, STORAGE_ACCOUNT_SPACE};
|
||||
use solana_storage_api::storage_instruction;
|
||||
use solana_storage_api::storage_processor::process_instruction;
|
||||
use solana_storage_api::SLOTS_PER_SEGMENT;
|
||||
|
@ -153,12 +152,12 @@ fn test_storage_tx() {
|
|||
#[test]
|
||||
fn test_serialize_overflow() {
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let tick_pubkey = Pubkey::new_rand();
|
||||
let current_id = current::id();
|
||||
let mut keyed_accounts = Vec::new();
|
||||
let mut user_account = Account::default();
|
||||
let mut current_account = current::create_account(1, 0, 0, 0);
|
||||
keyed_accounts.push(KeyedAccount::new(&pubkey, true, &mut user_account));
|
||||
keyed_accounts.push(KeyedAccount::new(&tick_pubkey, false, &mut current_account));
|
||||
keyed_accounts.push(KeyedAccount::new(¤t_id, false, &mut current_account));
|
||||
|
||||
let ix = storage_instruction::advertise_recent_blockhash(
|
||||
&pubkey,
|
||||
|
@ -267,7 +266,7 @@ fn test_validate_mining() {
|
|||
mut genesis_block,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_block(100_000);
|
||||
} = create_genesis_block(100_000_000_000);
|
||||
genesis_block
|
||||
.native_instruction_processors
|
||||
.push(solana_storage_program::solana_storage_program!());
|
||||
|
@ -402,6 +401,17 @@ fn test_validate_mining() {
|
|||
|
||||
assert_eq!(bank_client.get_balance(&validator_storage_id).unwrap(), 10);
|
||||
|
||||
let bank = Arc::new(Bank::new_from_parent(
|
||||
&bank,
|
||||
&Pubkey::default(),
|
||||
bank.slot() + bank.epoch_schedule().slots_per_epoch,
|
||||
));
|
||||
let bank_client = BankClient::new_shared(&bank);
|
||||
|
||||
let rewards = bank
|
||||
.get_account(&rewards::id())
|
||||
.map(|account| Rewards::from(&account).unwrap())
|
||||
.unwrap();
|
||||
let message = Message::new_with_payer(
|
||||
vec![storage_instruction::claim_reward(
|
||||
&owner_pubkey,
|
||||
|
@ -412,7 +422,7 @@ fn test_validate_mining() {
|
|||
assert_matches!(bank_client.send_message(&[&mint_keypair], message), Ok(_));
|
||||
assert_eq!(
|
||||
bank_client.get_balance(&owner_pubkey).unwrap(),
|
||||
1 + (VALIDATOR_REWARD * 10)
|
||||
1 + ((rewards.storage_point_value * 10_f64) as u64)
|
||||
);
|
||||
|
||||
// tick the bank into the next storage epoch so that rewards can be claimed
|
||||
|
@ -432,12 +442,11 @@ fn test_validate_mining() {
|
|||
)],
|
||||
Some(&mint_pubkey),
|
||||
);
|
||||
|
||||
assert_matches!(bank_client.send_message(&[&mint_keypair], message), Ok(_));
|
||||
|
||||
assert_eq!(
|
||||
bank_client.get_balance(&owner_pubkey).unwrap(),
|
||||
1 + (VALIDATOR_REWARD * 10) + (REPLICATOR_REWARD * 5)
|
||||
1 + ((rewards.storage_point_value * 10_f64) as u64)
|
||||
+ (rewards.storage_point_value * 5_f64) as u64
|
||||
);
|
||||
|
||||
let message = Message::new_with_payer(
|
||||
|
@ -448,10 +457,11 @@ fn test_validate_mining() {
|
|||
Some(&mint_pubkey),
|
||||
);
|
||||
assert_matches!(bank_client.send_message(&[&mint_keypair], message), Ok(_));
|
||||
|
||||
assert_eq!(
|
||||
bank_client.get_balance(&owner_pubkey).unwrap(),
|
||||
1 + (VALIDATOR_REWARD * 10) + (REPLICATOR_REWARD * 10)
|
||||
1 + (rewards.storage_point_value * 10_f64) as u64
|
||||
+ (rewards.storage_point_value * 5_f64) as u64
|
||||
+ (rewards.storage_point_value * 5_f64) as u64
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -269,8 +269,6 @@ impl Default for BlockhashQueue {
|
|||
}
|
||||
}
|
||||
|
||||
pub const DUMMY_REPLICATOR_POINTS: u64 = 100;
|
||||
|
||||
impl Bank {
|
||||
pub fn new(genesis_block: &GenesisBlock) -> Self {
|
||||
Self::new_with_paths(&genesis_block, None)
|
||||
|
@ -463,22 +461,21 @@ impl Bank {
|
|||
|
||||
let validator_points = self.stakes.write().unwrap().claim_points();
|
||||
|
||||
let replicator_rewards =
|
||||
self.inflation.replicator(year) * self.capitalization() as f64 * period;
|
||||
let storage_rewards = self.inflation.storage(year) * self.capitalization() as f64 * period;
|
||||
|
||||
let replicator_points = DUMMY_REPLICATOR_POINTS; // TODO: real value for points earned last epoch
|
||||
let storage_points = self.storage_accounts.write().unwrap().claim_points();
|
||||
|
||||
self.store_account(
|
||||
&rewards::id(),
|
||||
&rewards::create_account(
|
||||
1,
|
||||
validator_rewards / validator_points as f64,
|
||||
replicator_rewards / replicator_points as f64,
|
||||
storage_rewards / storage_points as f64,
|
||||
),
|
||||
);
|
||||
|
||||
self.capitalization.fetch_add(
|
||||
(validator_rewards + replicator_rewards) as usize,
|
||||
(validator_rewards + storage_rewards) as usize,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
}
|
||||
|
@ -1471,8 +1468,13 @@ mod tests {
|
|||
let ((vote_id, mut vote_account), stake) =
|
||||
crate::stakes::tests::create_staked_node_accounts(1_0000);
|
||||
|
||||
// set up stakes and vote accounts
|
||||
let ((validator_id, validator_account), (replicator_id, replicator_account)) =
|
||||
crate::storage_utils::tests::create_storage_accounts_with_credits(100);
|
||||
|
||||
// set up stakes,vote, and storage accounts
|
||||
bank.store_account(&stake.0, &stake.1);
|
||||
bank.store_account(&validator_id, &validator_account);
|
||||
bank.store_account(&replicator_id, &replicator_account);
|
||||
|
||||
// generate some rewards
|
||||
let mut vote_state = VoteState::from(&vote_account).unwrap();
|
||||
|
@ -1484,6 +1486,7 @@ mod tests {
|
|||
bank.store_account(&vote_id, &vote_account);
|
||||
|
||||
let validator_points = bank.stakes.read().unwrap().points();
|
||||
let storage_points = bank.storage_accounts.read().unwrap().points();
|
||||
|
||||
// put a child bank in epoch 1, which calls update_rewards()...
|
||||
let bank1 = Bank::new_from_parent(
|
||||
|
@ -1504,8 +1507,8 @@ mod tests {
|
|||
|
||||
assert!(
|
||||
((rewards.validator_point_value * validator_points as f64
|
||||
+ rewards.replicator_point_value * DUMMY_REPLICATOR_POINTS as f64) - // TODO: need replicator points per-epoch
|
||||
inflation as f64)
|
||||
+ rewards.storage_point_value * storage_points as f64)
|
||||
- inflation as f64)
|
||||
.abs()
|
||||
< 1.0 // rounding, truncating
|
||||
);
|
||||
|
|
|
@ -7,11 +7,15 @@ use std::collections::{HashMap, HashSet};
|
|||
|
||||
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize)]
|
||||
pub struct StorageAccounts {
|
||||
/// validator storage accounts
|
||||
/// validator storage accounts and their credits
|
||||
validator_accounts: HashSet<Pubkey>,
|
||||
|
||||
/// replicator storage accounts
|
||||
/// replicator storage accounts and their credits
|
||||
replicator_accounts: HashSet<Pubkey>,
|
||||
|
||||
/// unclaimed points.
|
||||
// 1 point == 1 storage account credit
|
||||
points: HashMap<Pubkey, u64>,
|
||||
}
|
||||
|
||||
pub fn is_storage(account: &Account) -> bool {
|
||||
|
@ -21,21 +25,35 @@ pub fn is_storage(account: &Account) -> bool {
|
|||
impl StorageAccounts {
|
||||
pub fn store(&mut self, pubkey: &Pubkey, account: &Account) {
|
||||
if let Ok(storage_state) = account.state() {
|
||||
if let StorageContract::ReplicatorStorage { .. } = storage_state {
|
||||
if let StorageContract::ReplicatorStorage { credits, .. } = storage_state {
|
||||
if account.lamports == 0 {
|
||||
self.replicator_accounts.remove(pubkey);
|
||||
} else {
|
||||
self.replicator_accounts.insert(*pubkey);
|
||||
self.points.insert(*pubkey, credits.current_epoch);
|
||||
}
|
||||
} else if let StorageContract::ValidatorStorage { .. } = storage_state {
|
||||
} else if let StorageContract::ValidatorStorage { credits, .. } = storage_state {
|
||||
if account.lamports == 0 {
|
||||
self.validator_accounts.remove(pubkey);
|
||||
} else {
|
||||
self.validator_accounts.insert(*pubkey);
|
||||
self.points.insert(*pubkey, credits.current_epoch);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// currently unclaimed points
|
||||
pub fn points(&self) -> u64 {
|
||||
self.points.values().sum()
|
||||
}
|
||||
|
||||
/// "claims" points, resets points to 0
|
||||
pub fn claim_points(&mut self) -> u64 {
|
||||
let points = self.points();
|
||||
self.points.clear();
|
||||
points
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validator_accounts(bank: &Bank) -> HashMap<Pubkey, Account> {
|
||||
|
@ -61,13 +79,14 @@ pub fn replicator_accounts(bank: &Bank) -> HashMap<Pubkey, Account> {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::bank_client::BankClient;
|
||||
use solana_sdk::client::SyncClient;
|
||||
use solana_sdk::genesis_block::create_genesis_block;
|
||||
use solana_sdk::message::Message;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_storage_api::storage_contract::{StorageAccount, STORAGE_ACCOUNT_SPACE};
|
||||
use solana_storage_api::{storage_instruction, storage_processor};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -113,4 +132,97 @@ mod tests {
|
|||
assert_eq!(validator_accounts(bank.as_ref()).len(), 1);
|
||||
assert_eq!(replicator_accounts(bank.as_ref()).len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_points() {
|
||||
// note: storage_points == storage_credits
|
||||
let credits = 42;
|
||||
let mut storage_accounts = StorageAccounts::default();
|
||||
assert_eq!(storage_accounts.points(), 0);
|
||||
assert_eq!(storage_accounts.claim_points(), 0);
|
||||
|
||||
// create random validator and replicator accounts with `credits`
|
||||
let ((validator_pubkey, validator_account), (replicator_pubkey, replicator_account)) =
|
||||
create_storage_accounts_with_credits(credits);
|
||||
|
||||
storage_accounts.store(&validator_pubkey, &validator_account);
|
||||
storage_accounts.store(&replicator_pubkey, &replicator_account);
|
||||
// check that 2x credits worth of points are available
|
||||
assert_eq!(storage_accounts.points(), credits * 2);
|
||||
|
||||
let ((validator_pubkey, validator_account), (replicator_pubkey, mut replicator_account)) =
|
||||
create_storage_accounts_with_credits(credits);
|
||||
|
||||
storage_accounts.store(&validator_pubkey, &validator_account);
|
||||
storage_accounts.store(&replicator_pubkey, &replicator_account);
|
||||
// check that 4x credits worth of points are available
|
||||
assert_eq!(storage_accounts.points(), credits * 2 * 2);
|
||||
|
||||
storage_accounts.store(&validator_pubkey, &validator_account);
|
||||
storage_accounts.store(&replicator_pubkey, &replicator_account);
|
||||
// check that storing again has no effect
|
||||
assert_eq!(storage_accounts.points(), credits * 2 * 2);
|
||||
|
||||
let storage_contract = &mut replicator_account.state().unwrap();
|
||||
if let StorageContract::ReplicatorStorage {
|
||||
credits: account_credits,
|
||||
..
|
||||
} = storage_contract
|
||||
{
|
||||
account_credits.current_epoch += 1;
|
||||
}
|
||||
replicator_account.set_state(storage_contract).unwrap();
|
||||
storage_accounts.store(&replicator_pubkey, &replicator_account);
|
||||
|
||||
// check that incremental store increases credits
|
||||
assert_eq!(storage_accounts.points(), credits * 2 * 2 + 1);
|
||||
|
||||
assert_eq!(storage_accounts.claim_points(), credits * 2 * 2 + 1);
|
||||
// check that once redeemed, the points are gone
|
||||
assert_eq!(storage_accounts.claim_points(), 0);
|
||||
}
|
||||
|
||||
pub fn create_storage_accounts_with_credits(
|
||||
credits: u64,
|
||||
) -> ((Pubkey, Account), (Pubkey, Account)) {
|
||||
let validator_pubkey = Pubkey::new_rand();
|
||||
let replicator_pubkey = Pubkey::new_rand();
|
||||
|
||||
let mut validator_account =
|
||||
Account::new(1, STORAGE_ACCOUNT_SPACE as usize, &solana_storage_api::id());
|
||||
let mut validator = StorageAccount::new(validator_pubkey, &mut validator_account);
|
||||
validator
|
||||
.initialize_validator_storage(validator_pubkey)
|
||||
.unwrap();
|
||||
let storage_contract = &mut validator_account.state().unwrap();
|
||||
if let StorageContract::ValidatorStorage {
|
||||
credits: account_credits,
|
||||
..
|
||||
} = storage_contract
|
||||
{
|
||||
account_credits.current_epoch = credits;
|
||||
}
|
||||
validator_account.set_state(storage_contract).unwrap();
|
||||
|
||||
let mut replicator_account =
|
||||
Account::new(1, STORAGE_ACCOUNT_SPACE as usize, &solana_storage_api::id());
|
||||
let mut replicator = StorageAccount::new(replicator_pubkey, &mut replicator_account);
|
||||
replicator
|
||||
.initialize_replicator_storage(replicator_pubkey)
|
||||
.unwrap();
|
||||
let storage_contract = &mut replicator_account.state().unwrap();
|
||||
if let StorageContract::ReplicatorStorage {
|
||||
credits: account_credits,
|
||||
..
|
||||
} = storage_contract
|
||||
{
|
||||
account_credits.current_epoch = credits;
|
||||
}
|
||||
replicator_account.set_state(storage_contract).unwrap();
|
||||
|
||||
(
|
||||
(validator_pubkey, validator_account),
|
||||
(replicator_pubkey, replicator_account),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ pub struct Inflation {
|
|||
/// Duration of grant pool inflation, in years
|
||||
pub grant_term: f64,
|
||||
|
||||
/// Percentage of total inflation allocated to replicator rewards
|
||||
pub replicator: f64,
|
||||
/// Percentage of total inflation allocated to storage rewards
|
||||
pub storage: f64,
|
||||
}
|
||||
|
||||
const DEFAULT_INITIAL: f64 = 0.15;
|
||||
|
@ -32,7 +32,7 @@ const DEFAULT_TAPER: f64 = 0.15;
|
|||
const DEFAULT_FOUNDATION: f64 = 0.05;
|
||||
const DEFAULT_GRANT: f64 = 0.05;
|
||||
const DEFAULT_FOUNDATION_GRANT_TERM: f64 = 7.0;
|
||||
const DEFAULT_REPLICATOR: f64 = 0.10;
|
||||
const DEFAULT_STORAGE: f64 = 0.10;
|
||||
|
||||
impl Default for Inflation {
|
||||
fn default() -> Self {
|
||||
|
@ -44,7 +44,7 @@ impl Default for Inflation {
|
|||
foundation_term: DEFAULT_FOUNDATION_GRANT_TERM,
|
||||
grant: DEFAULT_GRANT,
|
||||
grant_term: DEFAULT_FOUNDATION_GRANT_TERM,
|
||||
replicator: DEFAULT_REPLICATOR,
|
||||
storage: DEFAULT_STORAGE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,12 +63,12 @@ impl Inflation {
|
|||
|
||||
/// portion of total that goes to validators
|
||||
pub fn validator(&self, year: f64) -> f64 {
|
||||
self.total(year) - self.replicator(year) - self.grant(year) - self.foundation(year)
|
||||
self.total(year) - self.storage(year) - self.grant(year) - self.foundation(year)
|
||||
}
|
||||
|
||||
/// portion of total that goes to replicators
|
||||
pub fn replicator(&self, year: f64) -> f64 {
|
||||
self.total(year) * self.replicator
|
||||
/// portion of total that goes to storage mining
|
||||
pub fn storage(&self, year: f64) -> f64 {
|
||||
self.total(year) * self.storage
|
||||
}
|
||||
|
||||
/// portion of total that goes to grant pools
|
||||
|
@ -105,7 +105,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
total,
|
||||
inflation.validator(*year)
|
||||
+ inflation.replicator(*year)
|
||||
+ inflation.storage(*year)
|
||||
+ inflation.grant(*year)
|
||||
+ inflation.foundation(*year)
|
||||
);
|
||||
|
|
|
@ -16,7 +16,7 @@ crate::solana_name_id!(ID, "Sysca11Rewards11111111111111111111111111111");
|
|||
#[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
|
||||
pub struct Rewards {
|
||||
pub validator_point_value: f64,
|
||||
pub replicator_point_value: f64,
|
||||
pub storage_point_value: f64,
|
||||
}
|
||||
|
||||
impl Rewards {
|
||||
|
@ -34,13 +34,13 @@ impl Rewards {
|
|||
pub fn create_account(
|
||||
lamports: u64,
|
||||
validator_point_value: f64,
|
||||
replicator_point_value: f64,
|
||||
storage_point_value: f64,
|
||||
) -> Account {
|
||||
Account::new_data(
|
||||
lamports,
|
||||
&Rewards {
|
||||
validator_point_value,
|
||||
replicator_point_value,
|
||||
storage_point_value,
|
||||
},
|
||||
&syscall::id(),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue