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:
Sagar Dhawan 2019-06-26 10:40:03 -07:00 committed by GitHub
parent 8a64e1ddc3
commit df1c473341
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 318 additions and 123 deletions

View File

@ -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(),

View File

@ -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,
&current,
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],

View File

@ -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),
];

View File

@ -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(&current[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(&current[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)
}
}
}

View File

@ -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(&current_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
);
}

View File

@ -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
);

View File

@ -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),
)
}
}

View File

@ -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)
);

View File

@ -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(),
)