Add redeem_vote_credits to runtime (#7910)
* Move redeem_vote_credits into runtime * fixup * test * move stake manipulation to stake program * chugga for less indentation
This commit is contained in:
@ -365,13 +365,28 @@ impl Stake {
|
|||||||
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
|
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
|
||||||
self.delegation.stake(epoch, history)
|
self.delegation.stake(epoch, history)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn redeem_rewards(
|
||||||
|
&mut self,
|
||||||
|
point_value: f64,
|
||||||
|
vote_state: &VoteState,
|
||||||
|
stake_history: Option<&StakeHistory>,
|
||||||
|
) -> Option<(u64, u64)> {
|
||||||
|
self.calculate_rewards(point_value, vote_state, stake_history)
|
||||||
|
.map(|(voters_reward, stakers_reward, credits_observed)| {
|
||||||
|
self.credits_observed = credits_observed;
|
||||||
|
self.delegation.stake += stakers_reward;
|
||||||
|
(voters_reward, stakers_reward)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// for a given stake and vote_state, calculate what distributions and what updates should be made
|
/// for a given stake and vote_state, calculate what distributions and what updates should be made
|
||||||
/// returns a tuple in the case of a payout of:
|
/// returns a tuple in the case of a payout of:
|
||||||
/// * voter_rewards to be distributed
|
/// * voter_rewards to be distributed
|
||||||
/// * staker_rewards to be distributed
|
/// * staker_rewards to be distributed
|
||||||
/// * new value for credits_observed in the stake
|
/// * new value for credits_observed in the stake
|
||||||
// returns None if there's no payout or if any deserved payout is < 1 lamport
|
// returns None if there's no payout or if any deserved payout is < 1 lamport
|
||||||
fn calculate_rewards(
|
pub fn calculate_rewards(
|
||||||
&self,
|
&self,
|
||||||
point_value: f64,
|
point_value: f64,
|
||||||
vote_state: &VoteState,
|
vote_state: &VoteState,
|
||||||
@ -805,6 +820,33 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// utility function, used by runtime
|
||||||
|
pub fn redeem_rewards(
|
||||||
|
stake_account: &mut Account,
|
||||||
|
vote_account: &mut Account,
|
||||||
|
point_value: f64,
|
||||||
|
stake_history: Option<&StakeHistory>,
|
||||||
|
) -> Result<u64, InstructionError> {
|
||||||
|
if let StakeState::Stake(meta, mut stake) = stake_account.state()? {
|
||||||
|
let vote_state = vote_account.state()?;
|
||||||
|
|
||||||
|
if let Some((voters_reward, stakers_reward)) =
|
||||||
|
stake.redeem_rewards(point_value, &vote_state, stake_history)
|
||||||
|
{
|
||||||
|
stake_account.lamports += stakers_reward;
|
||||||
|
vote_account.lamports += voters_reward;
|
||||||
|
|
||||||
|
stake_account.set_state(&StakeState::Stake(meta, stake))?;
|
||||||
|
|
||||||
|
Ok(stakers_reward + voters_reward)
|
||||||
|
} else {
|
||||||
|
Err(StakeError::NoCreditsToRedeem.into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(InstructionError::InvalidAccountData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// utility function, used by runtime::Stakes, tests
|
// utility function, used by runtime::Stakes, tests
|
||||||
pub fn new_stake_history_entry<'a, I>(
|
pub fn new_stake_history_entry<'a, I>(
|
||||||
epoch: Epoch,
|
epoch: Epoch,
|
||||||
@ -1924,6 +1966,43 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stake_state_redeem_rewards() {
|
||||||
|
let mut vote_state = VoteState::default();
|
||||||
|
// assume stake.stake() is right
|
||||||
|
// bootstrap means fully-vested stake at epoch 0
|
||||||
|
let stake_lamports = 1;
|
||||||
|
let mut stake = Stake::new(
|
||||||
|
stake_lamports,
|
||||||
|
&Pubkey::default(),
|
||||||
|
&vote_state,
|
||||||
|
std::u64::MAX,
|
||||||
|
&Config::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// this one can't collect now, credits_observed == vote_state.credits()
|
||||||
|
assert_eq!(
|
||||||
|
None,
|
||||||
|
stake.redeem_rewards(1_000_000_000.0, &vote_state, None)
|
||||||
|
);
|
||||||
|
|
||||||
|
// put 2 credits in at epoch 0
|
||||||
|
vote_state.increment_credits(0);
|
||||||
|
vote_state.increment_credits(0);
|
||||||
|
|
||||||
|
// this one should be able to collect exactly 2
|
||||||
|
assert_eq!(
|
||||||
|
Some((0, stake_lamports * 2)),
|
||||||
|
stake.redeem_rewards(1.0, &vote_state, None)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
stake.delegation.stake,
|
||||||
|
stake_lamports + (stake_lamports * 2)
|
||||||
|
);
|
||||||
|
assert_eq!(stake.credits_observed, 2);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_state_calculate_rewards() {
|
fn test_stake_state_calculate_rewards() {
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
|
@ -48,7 +48,7 @@ use solana_sdk::{
|
|||||||
timing::years_as_slots,
|
timing::years_as_slots,
|
||||||
transaction::{Result, Transaction, TransactionError},
|
transaction::{Result, Transaction, TransactionError},
|
||||||
};
|
};
|
||||||
use solana_stake_program::stake_state::Delegation;
|
use solana_stake_program::stake_state::{self, Delegation};
|
||||||
use solana_vote_program::vote_state::VoteState;
|
use solana_vote_program::vote_state::VoteState;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
@ -630,12 +630,50 @@ impl Bank {
|
|||||||
&sysvar::rewards::create_account(1, validator_point_value, storage_point_value),
|
&sysvar::rewards::create_account(1, validator_point_value, storage_point_value),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let validator_rewards = self.pay_validator_rewards(validator_point_value);
|
||||||
|
|
||||||
self.capitalization.fetch_add(
|
self.capitalization.fetch_add(
|
||||||
(validator_rewards + storage_rewards) as u64,
|
validator_rewards + storage_rewards as u64,
|
||||||
Ordering::Relaxed,
|
Ordering::Relaxed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// iterate over all stakes, redeem vote credits for each stake we can
|
||||||
|
/// successfully load and parse, return total payout
|
||||||
|
fn pay_validator_rewards(&self, point_value: f64) -> u64 {
|
||||||
|
let stake_history = self.stakes.read().unwrap().history().clone();
|
||||||
|
self.stake_delegations()
|
||||||
|
.iter()
|
||||||
|
.map(|(stake_pubkey, delegation)| {
|
||||||
|
match (
|
||||||
|
self.get_account(&stake_pubkey),
|
||||||
|
self.get_account(&delegation.voter_pubkey),
|
||||||
|
) {
|
||||||
|
(Some(mut stake_account), Some(mut vote_account)) => {
|
||||||
|
let rewards = stake_state::redeem_rewards(
|
||||||
|
&mut stake_account,
|
||||||
|
&mut vote_account,
|
||||||
|
point_value,
|
||||||
|
Some(&stake_history),
|
||||||
|
);
|
||||||
|
if let Ok(rewards) = rewards {
|
||||||
|
self.store_account(&stake_pubkey, &stake_account);
|
||||||
|
self.store_account(&delegation.voter_pubkey, &vote_account);
|
||||||
|
rewards
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"stake_state::redeem_rewards() failed for {}: {:?}",
|
||||||
|
stake_pubkey, rewards
|
||||||
|
);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, _) => 0,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_recent_blockhashes(&self) {
|
pub fn update_recent_blockhashes(&self) {
|
||||||
let blockhash_queue = self.blockhash_queue.read().unwrap();
|
let blockhash_queue = self.blockhash_queue.read().unwrap();
|
||||||
let recent_blockhash_iter = blockhash_queue.get_recent_blockhashes();
|
let recent_blockhash_iter = blockhash_queue.get_recent_blockhashes();
|
||||||
@ -2920,14 +2958,14 @@ mod tests {
|
|||||||
}));
|
}));
|
||||||
assert_eq!(bank.capitalization(), 42 * 1_000_000_000);
|
assert_eq!(bank.capitalization(), 42 * 1_000_000_000);
|
||||||
|
|
||||||
let ((vote_id, mut vote_account), stake) =
|
let ((vote_id, mut vote_account), (stake_id, stake_account)) =
|
||||||
crate::stakes::tests::create_staked_node_accounts(1_0000);
|
crate::stakes::tests::create_staked_node_accounts(1_0000);
|
||||||
|
|
||||||
let ((validator_id, validator_account), (archiver_id, archiver_account)) =
|
let ((validator_id, validator_account), (archiver_id, archiver_account)) =
|
||||||
crate::storage_utils::tests::create_storage_accounts_with_credits(100);
|
crate::storage_utils::tests::create_storage_accounts_with_credits(100);
|
||||||
|
|
||||||
// set up stakes, vote, and storage accounts
|
// set up stakes, vote, and storage accounts
|
||||||
bank.store_account(&stake.0, &stake.1);
|
bank.store_account(&stake_id, &stake_account);
|
||||||
bank.store_account(&validator_id, &validator_account.borrow());
|
bank.store_account(&validator_id, &validator_account.borrow());
|
||||||
bank.store_account(&archiver_id, &archiver_account.borrow());
|
bank.store_account(&archiver_id, &archiver_account.borrow());
|
||||||
|
|
||||||
@ -2960,6 +2998,16 @@ mod tests {
|
|||||||
.map(|account| Rewards::from_account(&account).unwrap())
|
.map(|account| Rewards::from_account(&account).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// verify the stake and vote accounts are the right size
|
||||||
|
assert!(
|
||||||
|
((bank1.get_balance(&stake_id) - stake_account.lamports + bank1.get_balance(&vote_id)
|
||||||
|
- vote_account.lamports) as f64
|
||||||
|
- rewards.validator_point_value * validator_points as f64)
|
||||||
|
.abs()
|
||||||
|
< 1.0
|
||||||
|
);
|
||||||
|
|
||||||
|
// verify the rewards are the right size
|
||||||
assert!(
|
assert!(
|
||||||
((rewards.validator_point_value * validator_points as f64
|
((rewards.validator_point_value * validator_points as f64
|
||||||
+ rewards.storage_point_value * storage_points as f64)
|
+ rewards.storage_point_value * storage_points as f64)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use assert_matches::assert_matches;
|
|
||||||
use solana_runtime::{
|
use solana_runtime::{
|
||||||
bank::Bank,
|
bank::Bank,
|
||||||
bank_client::BankClient,
|
bank_client::BankClient,
|
||||||
@ -11,7 +10,7 @@ use solana_sdk::{
|
|||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::{Keypair, KeypairUtil},
|
signature::{Keypair, KeypairUtil},
|
||||||
system_instruction::create_address_with_seed,
|
system_instruction::create_address_with_seed,
|
||||||
sysvar::{self, rewards::Rewards, stake_history::StakeHistory, Sysvar},
|
sysvar::{self, stake_history::StakeHistory, Sysvar},
|
||||||
};
|
};
|
||||||
use solana_stake_program::{
|
use solana_stake_program::{
|
||||||
stake_instruction::{self},
|
stake_instruction::{self},
|
||||||
@ -241,6 +240,14 @@ fn test_stake_account_lifetime() {
|
|||||||
assert!(false, "wrong account type found")
|
assert!(false, "wrong account type found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if warmed_up(&bank, &stake_pubkey) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Cycle thru banks until we're fully warmed up
|
||||||
|
bank = next_epoch(&bank);
|
||||||
|
}
|
||||||
|
|
||||||
// Reward redemption
|
// Reward redemption
|
||||||
// Submit enough votes to generate rewards
|
// Submit enough votes to generate rewards
|
||||||
bank = fill_epoch_with_votes(&bank, &vote_keypair, &mint_keypair);
|
bank = fill_epoch_with_votes(&bank, &vote_keypair, &mint_keypair);
|
||||||
@ -251,35 +258,14 @@ fn test_stake_account_lifetime() {
|
|||||||
|
|
||||||
// 1 less vote, as the first vote should have cleared the lockout
|
// 1 less vote, as the first vote should have cleared the lockout
|
||||||
assert_eq!(vote_state.votes.len(), 31);
|
assert_eq!(vote_state.votes.len(), 31);
|
||||||
assert_eq!(vote_state.credits(), 1);
|
// one vote per slot, might be more slots than 32 in the epoch
|
||||||
|
assert!(vote_state.credits() >= 1);
|
||||||
bank = fill_epoch_with_votes(&bank, &vote_keypair, &mint_keypair);
|
bank = fill_epoch_with_votes(&bank, &vote_keypair, &mint_keypair);
|
||||||
|
|
||||||
loop {
|
|
||||||
if warmed_up(&bank, &stake_pubkey) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Cycle thru banks until we're fully warmed up
|
|
||||||
bank = next_epoch(&bank);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that rewards are there
|
|
||||||
let rewards_account = bank
|
|
||||||
.get_account(&sysvar::rewards::id())
|
|
||||||
.expect("account not found");
|
|
||||||
assert_matches!(Rewards::from_account(&rewards_account), Some(_));
|
|
||||||
|
|
||||||
let pre_staked = get_staked(&bank, &stake_pubkey);
|
let pre_staked = get_staked(&bank, &stake_pubkey);
|
||||||
|
|
||||||
// Redeem the credit
|
// next epoch bank should pay rewards
|
||||||
let bank_client = BankClient::new_shared(&bank);
|
bank = next_epoch(&bank);
|
||||||
let message = Message::new_with_payer(
|
|
||||||
vec![stake_instruction::redeem_vote_credits(
|
|
||||||
&stake_pubkey,
|
|
||||||
&vote_pubkey,
|
|
||||||
)],
|
|
||||||
Some(&mint_pubkey),
|
|
||||||
);
|
|
||||||
assert_matches!(bank_client.send_message(&[&mint_keypair], message), Ok(_));
|
|
||||||
|
|
||||||
// Test that balance increased, and that the balance got staked
|
// Test that balance increased, and that the balance got staked
|
||||||
let staked = get_staked(&bank, &stake_pubkey);
|
let staked = get_staked(&bank, &stake_pubkey);
|
||||||
@ -290,6 +276,8 @@ fn test_stake_account_lifetime() {
|
|||||||
// split the stake
|
// split the stake
|
||||||
let split_stake_keypair = Keypair::new();
|
let split_stake_keypair = Keypair::new();
|
||||||
let split_stake_pubkey = split_stake_keypair.pubkey();
|
let split_stake_pubkey = split_stake_keypair.pubkey();
|
||||||
|
|
||||||
|
let bank_client = BankClient::new_shared(&bank);
|
||||||
// Test split
|
// Test split
|
||||||
let message = Message::new_with_payer(
|
let message = Message::new_with_payer(
|
||||||
stake_instruction::split(
|
stake_instruction::split(
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
//! useful extras for Account state
|
//! useful extras for Account state
|
||||||
use crate::account::{Account, KeyedAccount};
|
use crate::{
|
||||||
use crate::instruction::InstructionError;
|
account::{Account, KeyedAccount},
|
||||||
|
instruction::InstructionError,
|
||||||
|
};
|
||||||
use bincode::ErrorKind;
|
use bincode::ErrorKind;
|
||||||
|
|
||||||
/// Convenience trait to covert bincode errors to instruction errors.
|
/// Convenience trait to covert bincode errors to instruction errors.
|
||||||
|
Reference in New Issue
Block a user