rewrite vote credits redemption to eat from rewards_pools on an epoch-sensitive basis (#4775)
* move redemption to rewards pools * rewrite redemption, touch a few other things * re-establish test coverage
This commit is contained in:
parent
f39e74f0d7
commit
a49f5378e2
|
@ -145,7 +145,6 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
let leader_stake = Stake {
|
let leader_stake = Stake {
|
||||||
stake: BOOTSTRAP_LEADER_LAMPORTS,
|
stake: BOOTSTRAP_LEADER_LAMPORTS,
|
||||||
epoch: 0,
|
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,8 @@
|
||||||
//! * keep track of rewards
|
//! * keep track of rewards
|
||||||
//! * own mining pools
|
//! * own mining pools
|
||||||
|
|
||||||
use crate::stake_state::StakeState;
|
use crate::stake_state::create_rewards_pool;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use solana_sdk::account::Account;
|
|
||||||
use solana_sdk::genesis_block::Builder;
|
use solana_sdk::genesis_block::Builder;
|
||||||
use solana_sdk::hash::{hash, Hash};
|
use solana_sdk::hash::{hash, Hash};
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
@ -25,10 +24,7 @@ pub fn genesis(mut builder: Builder) -> Builder {
|
||||||
let mut pubkey = id();
|
let mut pubkey = id();
|
||||||
|
|
||||||
for _i in 0..NUM_REWARDS_POOLS {
|
for _i in 0..NUM_REWARDS_POOLS {
|
||||||
builder = builder.rewards_pool(
|
builder = builder.rewards_pool(pubkey, create_rewards_pool());
|
||||||
pubkey,
|
|
||||||
Account::new_data(std::u64::MAX, &StakeState::RewardsPool, &crate::id()).unwrap(),
|
|
||||||
);
|
|
||||||
pubkey = Pubkey::new(hash(pubkey.as_ref()).as_ref());
|
pubkey = Pubkey::new(hash(pubkey.as_ref()).as_ref());
|
||||||
}
|
}
|
||||||
builder
|
builder
|
||||||
|
|
|
@ -11,30 +11,25 @@ use solana_sdk::system_instruction;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum StakeInstruction {
|
pub enum StakeInstruction {
|
||||||
// Initialize the stake account as a MiningPool account
|
|
||||||
///
|
|
||||||
/// Expects 1 Accounts:
|
|
||||||
/// 0 - MiningPool StakeAccount to be initialized
|
|
||||||
InitializeMiningPool,
|
|
||||||
|
|
||||||
/// `Delegate` a stake to a particular node
|
/// `Delegate` a stake to a particular node
|
||||||
///
|
///
|
||||||
/// Expects 2 Accounts:
|
/// Expects 3 Accounts:
|
||||||
/// 0 - Uninitialized StakeAccount to be delegated <= must have this signature
|
/// 0 - Uninitialized StakeAccount to be delegated <= must have this signature
|
||||||
/// 1 - VoteAccount to which this Stake will be delegated
|
/// 1 - VoteAccount to which this Stake will be delegated
|
||||||
|
/// 2 - Current syscall Account that carries current bank epoch
|
||||||
///
|
///
|
||||||
/// The u64 is the portion of the Stake account balance to be activated,
|
/// The u64 is the portion of the Stake account balance to be activated,
|
||||||
/// must be less than StakeAccount.lamports
|
/// must be less than StakeAccount.lamports
|
||||||
///
|
///
|
||||||
/// This instruction resets rewards, so issue
|
|
||||||
DelegateStake(u64),
|
DelegateStake(u64),
|
||||||
|
|
||||||
/// Redeem credits in the stake account
|
/// Redeem credits in the stake account
|
||||||
///
|
///
|
||||||
/// Expects 3 Accounts:
|
/// Expects 4 Accounts:
|
||||||
/// 0 - MiningPool Stake Account to redeem credits from
|
/// 0 - Delegate StakeAccount to be updated with rewards
|
||||||
/// 1 - Delegate StakeAccount to be updated
|
/// 1 - VoteAccount to which the Stake is delegated,
|
||||||
/// 2 - VoteAccount to which the Stake is delegated,
|
/// 2 - RewardsPool Stake Account from which to redeem credits
|
||||||
|
/// 3 - Rewards syscall Account that carries points values
|
||||||
RedeemVoteCredits,
|
RedeemVoteCredits,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,36 +58,12 @@ pub fn create_stake_account_and_delegate_stake(
|
||||||
instructions
|
instructions
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_mining_pool_account(
|
pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction {
|
||||||
from_pubkey: &Pubkey,
|
|
||||||
staker_pubkey: &Pubkey,
|
|
||||||
lamports: u64,
|
|
||||||
) -> Vec<Instruction> {
|
|
||||||
vec![
|
|
||||||
system_instruction::create_account(
|
|
||||||
from_pubkey,
|
|
||||||
staker_pubkey,
|
|
||||||
lamports,
|
|
||||||
std::mem::size_of::<StakeState>() as u64,
|
|
||||||
&id(),
|
|
||||||
),
|
|
||||||
Instruction::new(
|
|
||||||
id(),
|
|
||||||
&StakeInstruction::InitializeMiningPool,
|
|
||||||
vec![AccountMeta::new(*staker_pubkey, false)],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redeem_vote_credits(
|
|
||||||
mining_pool_pubkey: &Pubkey,
|
|
||||||
stake_pubkey: &Pubkey,
|
|
||||||
vote_pubkey: &Pubkey,
|
|
||||||
) -> Instruction {
|
|
||||||
let account_metas = vec![
|
let account_metas = vec![
|
||||||
AccountMeta::new(*mining_pool_pubkey, false),
|
|
||||||
AccountMeta::new(*stake_pubkey, false),
|
AccountMeta::new(*stake_pubkey, false),
|
||||||
AccountMeta::new(*vote_pubkey, false),
|
AccountMeta::new(*vote_pubkey, false),
|
||||||
|
AccountMeta::new(crate::rewards_pools::random_id(), false),
|
||||||
|
AccountMeta::new(syscall::rewards::id(), false),
|
||||||
];
|
];
|
||||||
Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas)
|
Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas)
|
||||||
}
|
}
|
||||||
|
@ -106,11 +77,6 @@ pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey, stake: u64) -
|
||||||
Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas)
|
Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current(current_account: &KeyedAccount) -> Result<syscall::current::Current, InstructionError> {
|
|
||||||
syscall::current::Current::from(current_account.account)
|
|
||||||
.ok_or(InstructionError::InvalidArgument)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_instruction(
|
pub fn process_instruction(
|
||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
keyed_accounts: &mut [KeyedAccount],
|
keyed_accounts: &mut [KeyedAccount],
|
||||||
|
@ -130,29 +96,32 @@ pub fn process_instruction(
|
||||||
|
|
||||||
// TODO: data-driven unpack and dispatch of KeyedAccounts
|
// TODO: data-driven unpack and dispatch of KeyedAccounts
|
||||||
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
|
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
|
||||||
StakeInstruction::InitializeMiningPool => {
|
|
||||||
if !rest.is_empty() {
|
|
||||||
Err(InstructionError::InvalidInstructionData)?;
|
|
||||||
}
|
|
||||||
me.initialize_mining_pool()
|
|
||||||
}
|
|
||||||
StakeInstruction::DelegateStake(stake) => {
|
StakeInstruction::DelegateStake(stake) => {
|
||||||
if rest.len() != 2 {
|
if rest.len() != 2 {
|
||||||
Err(InstructionError::InvalidInstructionData)?;
|
Err(InstructionError::InvalidInstructionData)?;
|
||||||
}
|
}
|
||||||
let vote = &rest[0];
|
let vote = &rest[0];
|
||||||
|
|
||||||
me.delegate_stake(vote, stake, ¤t(&rest[1])?)
|
me.delegate_stake(
|
||||||
|
vote,
|
||||||
|
stake,
|
||||||
|
&syscall::current::from_keyed_account(&rest[1])?,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
StakeInstruction::RedeemVoteCredits => {
|
StakeInstruction::RedeemVoteCredits => {
|
||||||
if rest.len() != 2 {
|
if rest.len() != 3 {
|
||||||
Err(InstructionError::InvalidInstructionData)?;
|
Err(InstructionError::InvalidInstructionData)?;
|
||||||
}
|
}
|
||||||
let (stake, vote) = rest.split_at_mut(1);
|
let (vote, rest) = rest.split_at_mut(1);
|
||||||
let stake = &mut stake[0];
|
|
||||||
let vote = &mut vote[0];
|
let vote = &mut vote[0];
|
||||||
|
let (rewards_pool, rest) = rest.split_at_mut(1);
|
||||||
|
let rewards_pool = &mut rewards_pool[0];
|
||||||
|
|
||||||
me.redeem_vote_credits(stake, vote)
|
me.redeem_vote_credits(
|
||||||
|
vote,
|
||||||
|
rewards_pool,
|
||||||
|
&syscall::rewards::from_keyed_account(&rest[0])?,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,6 +139,8 @@ mod tests {
|
||||||
.map(|meta| {
|
.map(|meta| {
|
||||||
if syscall::current::check_id(&meta.pubkey) {
|
if syscall::current::check_id(&meta.pubkey) {
|
||||||
syscall::current::create_account(1, 0, 0, 0)
|
syscall::current::create_account(1, 0, 0, 0)
|
||||||
|
} else if syscall::rewards::check_id(&meta.pubkey) {
|
||||||
|
syscall::rewards::create_account(1, 0.0, 0.0)
|
||||||
} else {
|
} else {
|
||||||
Account::default()
|
Account::default()
|
||||||
}
|
}
|
||||||
|
@ -190,11 +161,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_process_instruction() {
|
fn test_stake_process_instruction() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
process_instruction(&redeem_vote_credits(
|
process_instruction(&redeem_vote_credits(&Pubkey::default(), &Pubkey::default(),)),
|
||||||
&Pubkey::default(),
|
|
||||||
&Pubkey::default(),
|
|
||||||
&Pubkey::default()
|
|
||||||
)),
|
|
||||||
Err(InstructionError::InvalidAccountData),
|
Err(InstructionError::InvalidAccountData),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -265,7 +232,7 @@ mod tests {
|
||||||
Err(InstructionError::InvalidAccountData),
|
Err(InstructionError::InvalidAccountData),
|
||||||
);
|
);
|
||||||
|
|
||||||
// gets the check in redeem_vote_credits
|
// gets the deserialization checks in redeem_vote_credits
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
super::process_instruction(
|
super::process_instruction(
|
||||||
&Pubkey::default(),
|
&Pubkey::default(),
|
||||||
|
@ -273,6 +240,11 @@ mod tests {
|
||||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
||||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
||||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&syscall::rewards::id(),
|
||||||
|
false,
|
||||||
|
&mut syscall::rewards::create_account(1, 0.0, 0.0)
|
||||||
|
),
|
||||||
],
|
],
|
||||||
&serialize(&StakeInstruction::RedeemVoteCredits).unwrap(),
|
&serialize(&StakeInstruction::RedeemVoteCredits).unwrap(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -10,6 +10,7 @@ use solana_sdk::account_utils::State;
|
||||||
use solana_sdk::instruction::InstructionError;
|
use solana_sdk::instruction::InstructionError;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::syscall;
|
use solana_sdk::syscall;
|
||||||
|
use solana_sdk::timing::Epoch;
|
||||||
use solana_vote_api::vote_state::VoteState;
|
use solana_vote_api::vote_state::VoteState;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
|
@ -31,18 +32,6 @@ impl Default for StakeState {
|
||||||
StakeState::Uninitialized
|
StakeState::Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: trusted values of network parameters come from where?
|
|
||||||
const TICKS_PER_SECOND: f64 = 10f64;
|
|
||||||
const TICKS_PER_SLOT: f64 = 8f64;
|
|
||||||
|
|
||||||
// credits/yr or slots/yr is seconds/year * ticks/second * slots/tick
|
|
||||||
const CREDITS_PER_YEAR: f64 = (365f64 * 24f64 * 3600f64) * TICKS_PER_SECOND / TICKS_PER_SLOT;
|
|
||||||
|
|
||||||
// TODO: 20% is a niiice rate... TODO: make this a member of MiningPool?
|
|
||||||
const STAKE_REWARD_TARGET_RATE: f64 = 0.20;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
const STAKE_GETS_PAID_EVERY_VOTE: u64 = 200_000_000; // if numbers above (TICKS_YEAR) move, fix this
|
|
||||||
|
|
||||||
impl StakeState {
|
impl StakeState {
|
||||||
// utility function, used by Stakes, tests
|
// utility function, used by Stakes, tests
|
||||||
|
@ -60,20 +49,96 @@ impl StakeState {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
|
pub struct Stake {
|
||||||
|
pub voter_pubkey: Pubkey,
|
||||||
|
pub credits_observed: u64,
|
||||||
|
pub stake: u64, // stake amount activated
|
||||||
|
pub activated: Epoch, // epoch the stake was activated
|
||||||
|
pub deactivated: Epoch, // epoch the stake was deactivated, std::Epoch::MAX if not deactivated
|
||||||
|
}
|
||||||
|
pub const STAKE_WARMUP_EPOCHS: u64 = 3;
|
||||||
|
|
||||||
|
impl Default for Stake {
|
||||||
|
fn default() -> Self {
|
||||||
|
Stake {
|
||||||
|
voter_pubkey: Pubkey::default(),
|
||||||
|
credits_observed: 0,
|
||||||
|
stake: 0,
|
||||||
|
activated: 0,
|
||||||
|
deactivated: std::u64::MAX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stake {
|
||||||
|
pub fn stake(&self, epoch: u64) -> u64 {
|
||||||
|
// before "activated" or after deactivated?
|
||||||
|
if epoch < self.activated || epoch >= self.deactivated {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// curr slot | 0 | 1 | 2 ... | 100 | 101 | 102 | 103
|
||||||
|
// action | activate | de-activate | |
|
||||||
|
// | | | | | | | | |
|
||||||
|
// | v | | | v | | |
|
||||||
|
// stake | 1/3 | 2/3 | 3/3 ... | 3/3 | 2/3 | 1/3 | 0/3
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// activated | 0 ...
|
||||||
|
// deactivated | std::u64::MAX ... 103 ...
|
||||||
|
|
||||||
|
// activate/deactivate can't possibly overlap
|
||||||
|
// (see delegate_stake() and deactivate())
|
||||||
|
if epoch - self.activated < STAKE_WARMUP_EPOCHS {
|
||||||
|
// warmup
|
||||||
|
(self.stake / STAKE_WARMUP_EPOCHS) * (epoch - self.activated + 1)
|
||||||
|
} else if self.deactivated - epoch < STAKE_WARMUP_EPOCHS {
|
||||||
|
// cooldown
|
||||||
|
(self.stake / STAKE_WARMUP_EPOCHS) * (self.deactivated - epoch)
|
||||||
|
} else {
|
||||||
|
self.stake
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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:
|
||||||
|
/// * voter_rewards to be distributed
|
||||||
|
/// * staker_rewards to be distributed
|
||||||
|
/// * new value for credits_observed in the stake
|
||||||
|
// returns None if there's no payout or if any deserved payout is < 1 lamport
|
||||||
pub fn calculate_rewards(
|
pub fn calculate_rewards(
|
||||||
credits_observed: u64,
|
&self,
|
||||||
stake: u64,
|
point_value: f64,
|
||||||
vote_state: &VoteState,
|
vote_state: &VoteState,
|
||||||
) -> Option<(u64, u64)> {
|
) -> Option<(u64, u64, u64)> {
|
||||||
if credits_observed >= vote_state.credits() {
|
if self.credits_observed >= vote_state.credits() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let total_rewards = stake as f64
|
let mut credits_observed = self.credits_observed;
|
||||||
* STAKE_REWARD_TARGET_RATE
|
let mut total_rewards = 0f64;
|
||||||
* (vote_state.credits() - credits_observed) as f64
|
for (epoch, credits, prev_credits) in vote_state.epoch_credits() {
|
||||||
/ CREDITS_PER_YEAR;
|
// figure out how much this stake has seen that
|
||||||
|
// for which the vote account has a record
|
||||||
|
let epoch_credits = if self.credits_observed < *prev_credits {
|
||||||
|
// the staker observed the entire epoch
|
||||||
|
credits - prev_credits
|
||||||
|
} else if self.credits_observed < *credits {
|
||||||
|
// the staker registered sometime during the epoch, partial credit
|
||||||
|
credits - credits_observed
|
||||||
|
} else {
|
||||||
|
// the staker has already observed/redeemed this epoch, or activated
|
||||||
|
// after this epoch
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
total_rewards += (self.stake(*epoch) * epoch_credits) as f64 * point_value;
|
||||||
|
|
||||||
|
// don't want to assume anything about order of the iterator...
|
||||||
|
credits_observed = std::cmp::max(credits_observed, *credits);
|
||||||
|
}
|
||||||
|
|
||||||
// don't bother trying to collect fractional lamports
|
// don't bother trying to collect fractional lamports
|
||||||
if total_rewards < 1f64 {
|
if total_rewards < 1f64 {
|
||||||
|
@ -87,70 +152,34 @@ impl StakeState {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((voter_rewards as u64, staker_rewards as u64))
|
Some((
|
||||||
}
|
voter_rewards as u64,
|
||||||
|
staker_rewards as u64,
|
||||||
|
credits_observed,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)]
|
fn delegate(&mut self, stake: u64, voter_pubkey: &Pubkey, vote_state: &VoteState, epoch: u64) {
|
||||||
pub struct Stake {
|
assert!(std::u64::MAX - epoch >= (STAKE_WARMUP_EPOCHS * 2));
|
||||||
pub voter_pubkey: Pubkey,
|
|
||||||
pub credits_observed: u64,
|
|
||||||
pub stake: u64, // activated stake
|
|
||||||
pub epoch: u64, // epoch the stake was activated
|
|
||||||
pub prev_stake: u64, // for warmup, cooldown
|
|
||||||
}
|
|
||||||
pub const STAKE_WARMUP_EPOCHS: u64 = 3;
|
|
||||||
|
|
||||||
impl Stake {
|
|
||||||
pub fn stake(&self, epoch: u64) -> u64 {
|
|
||||||
// prev_stake for stuff in the past
|
|
||||||
if epoch < self.epoch {
|
|
||||||
return self.prev_stake;
|
|
||||||
}
|
|
||||||
if epoch - self.epoch >= STAKE_WARMUP_EPOCHS {
|
|
||||||
return self.stake;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.stake != 0 {
|
|
||||||
// warmup
|
|
||||||
// 1/3rd, then 2/3rds...
|
|
||||||
(self.stake / STAKE_WARMUP_EPOCHS) * (epoch - self.epoch + 1)
|
|
||||||
} else if self.prev_stake != 0 {
|
|
||||||
// cool down
|
|
||||||
// 3/3rds, then 2/3rds...
|
|
||||||
self.prev_stake - ((self.prev_stake / STAKE_WARMUP_EPOCHS) * (epoch - self.epoch))
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delegate(
|
|
||||||
&mut self,
|
|
||||||
stake: u64,
|
|
||||||
voter_pubkey: &Pubkey,
|
|
||||||
vote_state: &VoteState,
|
|
||||||
epoch: u64, // current: &syscall::current::Current
|
|
||||||
) {
|
|
||||||
// resets the current stake's credits
|
// resets the current stake's credits
|
||||||
self.voter_pubkey = *voter_pubkey;
|
self.voter_pubkey = *voter_pubkey;
|
||||||
self.credits_observed = vote_state.credits();
|
self.credits_observed = vote_state.credits();
|
||||||
|
|
||||||
// when this stake was activated
|
// when this stake was activated
|
||||||
self.epoch = epoch;
|
self.activated = epoch;
|
||||||
self.stake = stake;
|
self.stake = stake;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivate(&mut self, epoch: u64) {
|
fn deactivate(&mut self, epoch: u64) {
|
||||||
self.voter_pubkey = Pubkey::default();
|
self.deactivated = std::cmp::max(
|
||||||
self.credits_observed = std::u64::MAX;
|
epoch + STAKE_WARMUP_EPOCHS,
|
||||||
self.prev_stake = self.stake(epoch);
|
self.activated + 2 * STAKE_WARMUP_EPOCHS - 1,
|
||||||
self.stake = 0;
|
);
|
||||||
self.epoch = epoch;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait StakeAccount {
|
pub trait StakeAccount {
|
||||||
fn initialize_mining_pool(&mut self) -> Result<(), InstructionError>;
|
|
||||||
fn delegate_stake(
|
fn delegate_stake(
|
||||||
&mut self,
|
&mut self,
|
||||||
vote_account: &KeyedAccount,
|
vote_account: &KeyedAccount,
|
||||||
|
@ -163,40 +192,13 @@ pub trait StakeAccount {
|
||||||
) -> Result<(), InstructionError>;
|
) -> Result<(), InstructionError>;
|
||||||
fn redeem_vote_credits(
|
fn redeem_vote_credits(
|
||||||
&mut self,
|
&mut self,
|
||||||
stake_account: &mut KeyedAccount,
|
|
||||||
vote_account: &mut KeyedAccount,
|
vote_account: &mut KeyedAccount,
|
||||||
|
rewards_account: &mut KeyedAccount,
|
||||||
|
rewards: &syscall::rewards::Rewards,
|
||||||
) -> Result<(), InstructionError>;
|
) -> Result<(), InstructionError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StakeAccount for KeyedAccount<'a> {
|
impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||||
fn initialize_mining_pool(&mut self) -> Result<(), InstructionError> {
|
|
||||||
if let StakeState::Uninitialized = self.state()? {
|
|
||||||
self.set_state(&StakeState::MiningPool {
|
|
||||||
epoch: 0,
|
|
||||||
point_value: 0.0,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(InstructionError::InvalidAccountData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deactivate_stake(
|
|
||||||
&mut self,
|
|
||||||
current: &syscall::current::Current,
|
|
||||||
) -> Result<(), InstructionError> {
|
|
||||||
if self.signer_key().is_none() {
|
|
||||||
return Err(InstructionError::MissingRequiredSignature);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let StakeState::Stake(mut stake) = self.state()? {
|
|
||||||
stake.deactivate(current.epoch);
|
|
||||||
|
|
||||||
self.set_state(&StakeState::Stake(stake))
|
|
||||||
} else {
|
|
||||||
Err(InstructionError::InvalidAccountData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delegate_stake(
|
fn delegate_stake(
|
||||||
&mut self,
|
&mut self,
|
||||||
vote_account: &KeyedAccount,
|
vote_account: &KeyedAccount,
|
||||||
|
@ -226,14 +228,30 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||||
Err(InstructionError::InvalidAccountData)
|
Err(InstructionError::InvalidAccountData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn deactivate_stake(
|
||||||
|
&mut self,
|
||||||
|
current: &syscall::current::Current,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
if self.signer_key().is_none() {
|
||||||
|
return Err(InstructionError::MissingRequiredSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let StakeState::Stake(mut stake) = self.state()? {
|
||||||
|
stake.deactivate(current.epoch);
|
||||||
|
|
||||||
|
self.set_state(&StakeState::Stake(stake))
|
||||||
|
} else {
|
||||||
|
Err(InstructionError::InvalidAccountData)
|
||||||
|
}
|
||||||
|
}
|
||||||
fn redeem_vote_credits(
|
fn redeem_vote_credits(
|
||||||
&mut self,
|
&mut self,
|
||||||
stake_account: &mut KeyedAccount,
|
|
||||||
vote_account: &mut KeyedAccount,
|
vote_account: &mut KeyedAccount,
|
||||||
|
rewards_account: &mut KeyedAccount,
|
||||||
|
rewards: &syscall::rewards::Rewards,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
if let (StakeState::MiningPool { .. }, StakeState::Stake(mut stake)) =
|
if let (StakeState::Stake(mut stake), StakeState::RewardsPool) =
|
||||||
(self.state()?, stake_account.state()?)
|
(self.state()?, rewards_account.state()?)
|
||||||
{
|
{
|
||||||
let vote_state: VoteState = vote_account.state()?;
|
let vote_state: VoteState = vote_account.state()?;
|
||||||
|
|
||||||
|
@ -241,25 +259,20 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||||
return Err(InstructionError::InvalidArgument);
|
return Err(InstructionError::InvalidArgument);
|
||||||
}
|
}
|
||||||
|
|
||||||
if stake.credits_observed > vote_state.credits() {
|
if let Some((stakers_reward, voters_reward, credits_observed)) =
|
||||||
return Err(InstructionError::InvalidAccountData);
|
stake.calculate_rewards(rewards.validator_point_value, &vote_state)
|
||||||
}
|
{
|
||||||
|
if rewards_account.account.lamports < (stakers_reward + voters_reward) {
|
||||||
if let Some((stakers_reward, voters_reward)) = StakeState::calculate_rewards(
|
|
||||||
stake.credits_observed,
|
|
||||||
stake_account.account.lamports,
|
|
||||||
&vote_state,
|
|
||||||
) {
|
|
||||||
if self.account.lamports < (stakers_reward + voters_reward) {
|
|
||||||
return Err(InstructionError::UnbalancedInstruction);
|
return Err(InstructionError::UnbalancedInstruction);
|
||||||
}
|
}
|
||||||
self.account.lamports -= stakers_reward + voters_reward;
|
rewards_account.account.lamports -= stakers_reward + voters_reward;
|
||||||
stake_account.account.lamports += stakers_reward;
|
|
||||||
|
self.account.lamports += stakers_reward;
|
||||||
vote_account.account.lamports += voters_reward;
|
vote_account.account.lamports += voters_reward;
|
||||||
|
|
||||||
stake.credits_observed = vote_state.credits();
|
stake.credits_observed = credits_observed;
|
||||||
|
|
||||||
stake_account.set_state(&StakeState::Stake(stake))
|
self.set_state(&StakeState::Stake(stake))
|
||||||
} else {
|
} else {
|
||||||
// not worth collecting
|
// not worth collecting
|
||||||
Err(InstructionError::CustomError(1))
|
Err(InstructionError::CustomError(1))
|
||||||
|
@ -283,8 +296,8 @@ pub fn create_stake_account(
|
||||||
voter_pubkey: *voter_pubkey,
|
voter_pubkey: *voter_pubkey,
|
||||||
credits_observed: vote_state.credits(),
|
credits_observed: vote_state.credits(),
|
||||||
stake: lamports,
|
stake: lamports,
|
||||||
epoch: 0,
|
activated: 0,
|
||||||
prev_stake: 0,
|
deactivated: std::u64::MAX,
|
||||||
}))
|
}))
|
||||||
.expect("set_state");
|
.expect("set_state");
|
||||||
|
|
||||||
|
@ -292,14 +305,8 @@ pub fn create_stake_account(
|
||||||
}
|
}
|
||||||
|
|
||||||
// utility function, used by Bank, tests, genesis
|
// utility function, used by Bank, tests, genesis
|
||||||
pub fn create_mining_pool(lamports: u64, epoch: u64, point_value: f64) -> Account {
|
pub fn create_rewards_pool() -> Account {
|
||||||
let mut mining_pool_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id());
|
Account::new_data(std::u64::MAX, &StakeState::RewardsPool, &crate::id()).unwrap()
|
||||||
|
|
||||||
mining_pool_account
|
|
||||||
.set_state(&StakeState::MiningPool { epoch, point_value })
|
|
||||||
.expect("set_state");
|
|
||||||
|
|
||||||
mining_pool_account
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -359,8 +366,8 @@ mod tests {
|
||||||
voter_pubkey: vote_keypair.pubkey(),
|
voter_pubkey: vote_keypair.pubkey(),
|
||||||
credits_observed: vote_state.credits(),
|
credits_observed: vote_state.credits(),
|
||||||
stake: stake_lamports,
|
stake: stake_lamports,
|
||||||
epoch: 0,
|
activated: 0,
|
||||||
prev_stake: 0
|
deactivated: std::u64::MAX,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// verify that delegate_stake can't be called twice StakeState::default()
|
// verify that delegate_stake can't be called twice StakeState::default()
|
||||||
|
@ -414,190 +421,180 @@ mod tests {
|
||||||
#[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();
|
||||||
let mut vote_i = 0;
|
let mut stake = Stake::default();
|
||||||
|
|
||||||
// put a credit in the vote_state
|
// warmup makes this look like zero until WARMUP_EPOCHS
|
||||||
while vote_state.credits() == 0 {
|
stake.stake = 1;
|
||||||
vote_state.process_slot_vote_unchecked(vote_i);
|
|
||||||
vote_i += 1;
|
// this one can't collect now, credits_observed == vote_state.credits()
|
||||||
}
|
assert_eq!(None, stake.calculate_rewards(1_000_000_000.0, &vote_state));
|
||||||
// this guy can't collect now, not enough stake to get paid on 1 credit
|
|
||||||
assert_eq!(None, StakeState::calculate_rewards(0, 100, &vote_state));
|
// put 2 credits in at epoch 0
|
||||||
// this guy can
|
vote_state.increment_credits(0);
|
||||||
|
vote_state.increment_credits(0);
|
||||||
|
|
||||||
|
// this one can't collect now, no epoch credits have been saved off
|
||||||
|
assert_eq!(None, stake.calculate_rewards(1_000_000_000.0, &vote_state));
|
||||||
|
|
||||||
|
// put 1 credit in epoch 1, pushes the 2 above into a redeemable state
|
||||||
|
vote_state.increment_credits(1);
|
||||||
|
|
||||||
|
// still can't collect yet, warmup puts the kibosh on it
|
||||||
|
assert_eq!(None, stake.calculate_rewards(1.0, &vote_state));
|
||||||
|
|
||||||
|
stake.stake = STAKE_WARMUP_EPOCHS;
|
||||||
|
// this one should be able to collect exactly 2
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some((0, 1)),
|
Some((0, 1 * 2, 2)),
|
||||||
StakeState::calculate_rewards(0, STAKE_GETS_PAID_EVERY_VOTE, &vote_state)
|
stake.calculate_rewards(1.0, &vote_state)
|
||||||
);
|
|
||||||
// but, there's not enough to split
|
|
||||||
vote_state.commission = std::u32::MAX / 2;
|
|
||||||
assert_eq!(
|
|
||||||
None,
|
|
||||||
StakeState::calculate_rewards(0, STAKE_GETS_PAID_EVERY_VOTE, &vote_state)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// put more credit in the vote_state
|
stake.stake = STAKE_WARMUP_EPOCHS;
|
||||||
while vote_state.credits() < 10 {
|
stake.credits_observed = 1;
|
||||||
vote_state.process_slot_vote_unchecked(vote_i);
|
// this one should be able to collect exactly 1 (only observed one)
|
||||||
vote_i += 1;
|
|
||||||
}
|
|
||||||
vote_state.commission = 0;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some((0, 10)),
|
Some((0, 1 * 1, 2)),
|
||||||
StakeState::calculate_rewards(0, STAKE_GETS_PAID_EVERY_VOTE, &vote_state)
|
stake.calculate_rewards(1.0, &vote_state)
|
||||||
);
|
);
|
||||||
vote_state.commission = std::u32::MAX;
|
|
||||||
|
stake.stake = STAKE_WARMUP_EPOCHS;
|
||||||
|
stake.credits_observed = 2;
|
||||||
|
// this one should be able to collect none because credits_observed >= credits in a
|
||||||
|
// redeemable state (the 2 credits in epoch 0)
|
||||||
|
assert_eq!(None, stake.calculate_rewards(1.0, &vote_state));
|
||||||
|
|
||||||
|
// put 1 credit in epoch 2, pushes the 1 for epoch 1 to redeemable
|
||||||
|
vote_state.increment_credits(2);
|
||||||
|
// this one should be able to collect two now, one credit by a stake of 2
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some((10, 0)),
|
Some((0, 2 * 1, 3)),
|
||||||
StakeState::calculate_rewards(0, STAKE_GETS_PAID_EVERY_VOTE, &vote_state)
|
stake.calculate_rewards(1.0, &vote_state)
|
||||||
);
|
);
|
||||||
vote_state.commission = std::u32::MAX / 2;
|
|
||||||
|
stake.credits_observed = 0;
|
||||||
|
// this one should be able to collect everything from t=0 a warmed up stake of 2
|
||||||
|
// (2 credits at stake of 1) + (1 credit at a stake of 2)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some((5, 5)),
|
Some((0, 2 * 1 + 1 * 2, 3)),
|
||||||
StakeState::calculate_rewards(0, STAKE_GETS_PAID_EVERY_VOTE, &vote_state)
|
stake.calculate_rewards(1.0, &vote_state)
|
||||||
|
);
|
||||||
|
|
||||||
|
// same as above, but is a really small commission out of 32 bits,
|
||||||
|
// verify that None comes back on small redemptions where no one gets paid
|
||||||
|
vote_state.commission = 1;
|
||||||
|
assert_eq!(
|
||||||
|
None, // would be Some((0, 2 * 1 + 1 * 2, 3)),
|
||||||
|
stake.calculate_rewards(1.0, &vote_state)
|
||||||
|
);
|
||||||
|
vote_state.commission = std::u32::MAX - 1;
|
||||||
|
assert_eq!(
|
||||||
|
None, // would be pSome((0, 2 * 1 + 1 * 2, 3)),
|
||||||
|
stake.calculate_rewards(1.0, &vote_state)
|
||||||
);
|
);
|
||||||
// not even enough stake to get paid on 10 credits...
|
|
||||||
assert_eq!(None, StakeState::calculate_rewards(0, 100, &vote_state));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_redeem_vote_credits() {
|
fn test_stake_redeem_vote_credits() {
|
||||||
let current = syscall::current::Current::default();
|
let current = syscall::current::Current::default();
|
||||||
|
let mut rewards = syscall::rewards::Rewards::default();
|
||||||
|
rewards.validator_point_value = 100.0;
|
||||||
|
|
||||||
let vote_keypair = Keypair::new();
|
let rewards_pool_pubkey = Pubkey::new_rand();
|
||||||
let mut vote_state = VoteState::default();
|
let mut rewards_pool_account = create_rewards_pool();
|
||||||
for i in 0..1000 {
|
let mut rewards_pool_keyed_account =
|
||||||
vote_state.process_slot_vote_unchecked(i);
|
KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account);
|
||||||
}
|
|
||||||
|
|
||||||
let vote_pubkey = vote_keypair.pubkey();
|
|
||||||
let mut vote_account =
|
|
||||||
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
|
||||||
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
|
||||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
|
||||||
|
|
||||||
let pubkey = Pubkey::default();
|
let pubkey = Pubkey::default();
|
||||||
let mut stake_account = Account::new(
|
let stake_lamports = 100;
|
||||||
STAKE_GETS_PAID_EVERY_VOTE,
|
|
||||||
std::mem::size_of::<StakeState>(),
|
|
||||||
&id(),
|
|
||||||
);
|
|
||||||
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account);
|
|
||||||
|
|
||||||
// delegate the stake
|
|
||||||
assert!(stake_keyed_account
|
|
||||||
.delegate_stake(&vote_keyed_account, STAKE_GETS_PAID_EVERY_VOTE, ¤t)
|
|
||||||
.is_ok());
|
|
||||||
|
|
||||||
let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
|
|
||||||
let mut mining_pool_keyed_account =
|
|
||||||
KeyedAccount::new(&pubkey, true, &mut mining_pool_account);
|
|
||||||
|
|
||||||
// not a mining pool yet...
|
|
||||||
assert_eq!(
|
|
||||||
mining_pool_keyed_account
|
|
||||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote_keyed_account),
|
|
||||||
Err(InstructionError::InvalidAccountData)
|
|
||||||
);
|
|
||||||
|
|
||||||
mining_pool_keyed_account
|
|
||||||
.set_state(&StakeState::MiningPool {
|
|
||||||
epoch: 0,
|
|
||||||
point_value: 0.0,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// no movement in vote account, so no redemption needed
|
|
||||||
assert_eq!(
|
|
||||||
mining_pool_keyed_account
|
|
||||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote_keyed_account),
|
|
||||||
Err(InstructionError::CustomError(1))
|
|
||||||
);
|
|
||||||
|
|
||||||
// move the vote account forward
|
|
||||||
vote_state.process_slot_vote_unchecked(1000);
|
|
||||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
|
||||||
|
|
||||||
// now, no lamports in the pool!
|
|
||||||
assert_eq!(
|
|
||||||
mining_pool_keyed_account
|
|
||||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote_keyed_account),
|
|
||||||
Err(InstructionError::UnbalancedInstruction)
|
|
||||||
);
|
|
||||||
|
|
||||||
// add a lamport to pool
|
|
||||||
mining_pool_keyed_account.account.lamports = 2;
|
|
||||||
assert!(mining_pool_keyed_account
|
|
||||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote_keyed_account)
|
|
||||||
.is_ok()); // yay
|
|
||||||
|
|
||||||
// lamports only shifted around, none made or lost
|
|
||||||
assert_eq!(
|
|
||||||
2 + 100 + STAKE_GETS_PAID_EVERY_VOTE,
|
|
||||||
mining_pool_account.lamports + vote_account.lamports + stake_account.lamports
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_stake_redeem_vote_credits_vote_errors() {
|
|
||||||
let current = syscall::current::Current::default();
|
|
||||||
|
|
||||||
let vote_keypair = Keypair::new();
|
|
||||||
let mut vote_state = VoteState::default();
|
|
||||||
for i in 0..1000 {
|
|
||||||
vote_state.process_slot_vote_unchecked(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
let vote_pubkey = vote_keypair.pubkey();
|
|
||||||
let mut vote_account =
|
|
||||||
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
|
||||||
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
|
||||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
|
||||||
|
|
||||||
let pubkey = Pubkey::default();
|
|
||||||
let stake_lamports = 0;
|
|
||||||
let mut stake_account =
|
let mut stake_account =
|
||||||
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
|
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
|
||||||
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account);
|
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account);
|
||||||
|
|
||||||
|
let vote_pubkey = Pubkey::new_rand();
|
||||||
|
let mut vote_account =
|
||||||
|
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
||||||
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
||||||
|
|
||||||
|
// not delegated yet, deserialization fails
|
||||||
|
assert_eq!(
|
||||||
|
stake_keyed_account.redeem_vote_credits(
|
||||||
|
&mut vote_keyed_account,
|
||||||
|
&mut rewards_pool_keyed_account,
|
||||||
|
&rewards
|
||||||
|
),
|
||||||
|
Err(InstructionError::InvalidAccountData)
|
||||||
|
);
|
||||||
|
|
||||||
// delegate the stake
|
// delegate the stake
|
||||||
assert!(stake_keyed_account
|
assert!(stake_keyed_account
|
||||||
.delegate_stake(&vote_keyed_account, stake_lamports, ¤t)
|
.delegate_stake(&vote_keyed_account, stake_lamports, ¤t)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
// no credits to claim
|
||||||
let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
|
|
||||||
let mut mining_pool_keyed_account =
|
|
||||||
KeyedAccount::new(&pubkey, true, &mut mining_pool_account);
|
|
||||||
mining_pool_keyed_account
|
|
||||||
.set_state(&StakeState::MiningPool {
|
|
||||||
epoch: 0,
|
|
||||||
point_value: 0.0,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut vote_state = VoteState::default();
|
|
||||||
for i in 0..100 {
|
|
||||||
// go back in time, previous state had 1000 votes
|
|
||||||
vote_state.process_slot_vote_unchecked(i);
|
|
||||||
}
|
|
||||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
|
||||||
// voter credits lower than stake_delegate credits... TODO: is this an error?
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mining_pool_keyed_account
|
stake_keyed_account.redeem_vote_credits(
|
||||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote_keyed_account),
|
&mut vote_keyed_account,
|
||||||
|
&mut rewards_pool_keyed_account,
|
||||||
|
&rewards
|
||||||
|
),
|
||||||
|
Err(InstructionError::CustomError(1))
|
||||||
|
);
|
||||||
|
|
||||||
|
// swapped rewards and vote, deserialization of rewards_pool fails
|
||||||
|
assert_eq!(
|
||||||
|
stake_keyed_account.redeem_vote_credits(
|
||||||
|
&mut rewards_pool_keyed_account,
|
||||||
|
&mut vote_keyed_account,
|
||||||
|
&rewards
|
||||||
|
),
|
||||||
Err(InstructionError::InvalidAccountData)
|
Err(InstructionError::InvalidAccountData)
|
||||||
);
|
);
|
||||||
|
|
||||||
let vote1_keypair = Keypair::new();
|
let mut vote_account =
|
||||||
let vote1_pubkey = vote1_keypair.pubkey();
|
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
||||||
let mut vote1_account =
|
|
||||||
vote_state::create_account(&vote1_pubkey, &Pubkey::new_rand(), 0, 100);
|
let mut vote_state = VoteState::from(&vote_account).unwrap();
|
||||||
let mut vote1_keyed_account = KeyedAccount::new(&vote1_pubkey, false, &mut vote1_account);
|
// put in some credits in epoch 0 for which we should have a non-zero stake
|
||||||
vote1_keyed_account.set_state(&vote_state).unwrap();
|
for _i in 0..100 {
|
||||||
|
vote_state.increment_credits(0);
|
||||||
|
}
|
||||||
|
vote_state.increment_credits(1);
|
||||||
|
|
||||||
|
vote_state.to(&mut vote_account).unwrap();
|
||||||
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
||||||
|
|
||||||
|
// some credits to claim, but rewards pool empty (shouldn't ever happen)
|
||||||
|
rewards_pool_keyed_account.account.lamports = 1;
|
||||||
|
assert_eq!(
|
||||||
|
stake_keyed_account.redeem_vote_credits(
|
||||||
|
&mut vote_keyed_account,
|
||||||
|
&mut rewards_pool_keyed_account,
|
||||||
|
&rewards
|
||||||
|
),
|
||||||
|
Err(InstructionError::UnbalancedInstruction)
|
||||||
|
);
|
||||||
|
rewards_pool_keyed_account.account.lamports = std::u64::MAX;
|
||||||
|
|
||||||
|
// finally! some credits to claim
|
||||||
|
assert_eq!(
|
||||||
|
stake_keyed_account.redeem_vote_credits(
|
||||||
|
&mut vote_keyed_account,
|
||||||
|
&mut rewards_pool_keyed_account,
|
||||||
|
&rewards
|
||||||
|
),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
|
||||||
|
let wrong_vote_pubkey = Pubkey::new_rand();
|
||||||
|
let mut wrong_vote_keyed_account =
|
||||||
|
KeyedAccount::new(&wrong_vote_pubkey, false, &mut vote_account);
|
||||||
|
|
||||||
// wrong voter_pubkey...
|
// wrong voter_pubkey...
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mining_pool_keyed_account
|
stake_keyed_account.redeem_vote_credits(
|
||||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote1_keyed_account),
|
&mut wrong_vote_keyed_account,
|
||||||
|
&mut rewards_pool_keyed_account,
|
||||||
|
&rewards
|
||||||
|
),
|
||||||
Err(InstructionError::InvalidArgument)
|
Err(InstructionError::InvalidArgument)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use solana_metrics::datapoint_warn;
|
||||||
use solana_sdk::account::KeyedAccount;
|
use solana_sdk::account::KeyedAccount;
|
||||||
use solana_sdk::instruction::{AccountMeta, Instruction, InstructionError};
|
use solana_sdk::instruction::{AccountMeta, Instruction, InstructionError};
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::syscall::slot_hashes;
|
use solana_sdk::syscall;
|
||||||
use solana_sdk::system_instruction;
|
use solana_sdk::system_instruction;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
@ -52,15 +52,22 @@ pub fn create_account(
|
||||||
fn metas_for_authorized_signer(
|
fn metas_for_authorized_signer(
|
||||||
vote_pubkey: &Pubkey,
|
vote_pubkey: &Pubkey,
|
||||||
authorized_voter_pubkey: &Pubkey, // currently authorized
|
authorized_voter_pubkey: &Pubkey, // currently authorized
|
||||||
|
other_params: &[AccountMeta],
|
||||||
) -> Vec<AccountMeta> {
|
) -> Vec<AccountMeta> {
|
||||||
let is_own_signer = authorized_voter_pubkey == vote_pubkey;
|
let is_own_signer = authorized_voter_pubkey == vote_pubkey;
|
||||||
|
|
||||||
// vote account
|
// vote account
|
||||||
let mut account_metas = vec![AccountMeta::new(*vote_pubkey, is_own_signer)];
|
let mut account_metas = vec![AccountMeta::new(*vote_pubkey, is_own_signer)];
|
||||||
|
|
||||||
|
for meta in other_params {
|
||||||
|
account_metas.push(meta.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// append signer at the end
|
||||||
if !is_own_signer {
|
if !is_own_signer {
|
||||||
account_metas.push(AccountMeta::new(*authorized_voter_pubkey, true)) // signer
|
account_metas.push(AccountMeta::new(*authorized_voter_pubkey, true)) // signer
|
||||||
}
|
}
|
||||||
|
|
||||||
account_metas
|
account_metas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +76,7 @@ pub fn authorize_voter(
|
||||||
authorized_voter_pubkey: &Pubkey, // currently authorized
|
authorized_voter_pubkey: &Pubkey, // currently authorized
|
||||||
new_authorized_voter_pubkey: &Pubkey,
|
new_authorized_voter_pubkey: &Pubkey,
|
||||||
) -> Instruction {
|
) -> Instruction {
|
||||||
let account_metas = metas_for_authorized_signer(vote_pubkey, authorized_voter_pubkey);
|
let account_metas = metas_for_authorized_signer(vote_pubkey, authorized_voter_pubkey, &[]);
|
||||||
|
|
||||||
Instruction::new(
|
Instruction::new(
|
||||||
id(),
|
id(),
|
||||||
|
@ -83,10 +90,16 @@ pub fn vote(
|
||||||
authorized_voter_pubkey: &Pubkey,
|
authorized_voter_pubkey: &Pubkey,
|
||||||
recent_votes: Vec<Vote>,
|
recent_votes: Vec<Vote>,
|
||||||
) -> Instruction {
|
) -> Instruction {
|
||||||
let mut account_metas = metas_for_authorized_signer(vote_pubkey, authorized_voter_pubkey);
|
let account_metas = metas_for_authorized_signer(
|
||||||
|
vote_pubkey,
|
||||||
|
authorized_voter_pubkey,
|
||||||
|
&[
|
||||||
// request slot_hashes syscall account after vote_pubkey
|
// request slot_hashes syscall account after vote_pubkey
|
||||||
account_metas.insert(1, AccountMeta::new(slot_hashes::id(), false));
|
AccountMeta::new(syscall::slot_hashes::id(), false),
|
||||||
|
// request current syscall account after that
|
||||||
|
AccountMeta::new(syscall::current::id(), false),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
Instruction::new(id(), &VoteInstruction::Vote(recent_votes), account_metas)
|
Instruction::new(id(), &VoteInstruction::Vote(recent_votes), account_metas)
|
||||||
}
|
}
|
||||||
|
@ -119,9 +132,18 @@ pub fn process_instruction(
|
||||||
}
|
}
|
||||||
VoteInstruction::Vote(votes) => {
|
VoteInstruction::Vote(votes) => {
|
||||||
datapoint_warn!("vote-native", ("count", 1, i64));
|
datapoint_warn!("vote-native", ("count", 1, i64));
|
||||||
let (slot_hashes, other_signers) = rest.split_at_mut(1);
|
if rest.len() < 2 {
|
||||||
let slot_hashes = &mut slot_hashes[0];
|
Err(InstructionError::InvalidInstructionData)?;
|
||||||
vote_state::process_votes(me, slot_hashes, other_signers, &votes)
|
}
|
||||||
|
let (slot_hashes_and_current, other_signers) = rest.split_at_mut(2);
|
||||||
|
|
||||||
|
vote_state::process_votes(
|
||||||
|
me,
|
||||||
|
&syscall::slot_hashes::from_keyed_account(&slot_hashes_and_current[0])?,
|
||||||
|
&syscall::current::from_keyed_account(&slot_hashes_and_current[1])?,
|
||||||
|
other_signers,
|
||||||
|
&votes,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +163,20 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> {
|
fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> {
|
||||||
let mut accounts = vec![];
|
let mut accounts: Vec<_> = instruction
|
||||||
|
.accounts
|
||||||
|
.iter()
|
||||||
|
.map(|meta| {
|
||||||
|
if syscall::current::check_id(&meta.pubkey) {
|
||||||
|
syscall::current::create_account(1, 0, 0, 0)
|
||||||
|
} else if syscall::slot_hashes::check_id(&meta.pubkey) {
|
||||||
|
syscall::slot_hashes::create_account(1, &[])
|
||||||
|
} else {
|
||||||
|
Account::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
for _ in 0..instruction.accounts.len() {
|
for _ in 0..instruction.accounts.len() {
|
||||||
accounts.push(Account::default());
|
accounts.push(Account::default());
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,30 +9,35 @@ use solana_sdk::account_utils::State;
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::hash::Hash;
|
||||||
use solana_sdk::instruction::InstructionError;
|
use solana_sdk::instruction::InstructionError;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::syscall::slot_hashes;
|
use solana_sdk::syscall::current::Current;
|
||||||
|
pub use solana_sdk::timing::{Epoch, Slot};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
// Maximum number of votes to keep around
|
// Maximum number of votes to keep around
|
||||||
pub const MAX_LOCKOUT_HISTORY: usize = 31;
|
pub const MAX_LOCKOUT_HISTORY: usize = 31;
|
||||||
pub const INITIAL_LOCKOUT: usize = 2;
|
pub const INITIAL_LOCKOUT: usize = 2;
|
||||||
|
|
||||||
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
|
// Maximum number of credits history to keep around
|
||||||
|
// smaller numbers makes
|
||||||
|
pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
|
||||||
|
|
||||||
|
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
pub struct Vote {
|
pub struct Vote {
|
||||||
/// A vote for height slot
|
/// A vote for height slot
|
||||||
pub slot: u64,
|
pub slot: Slot,
|
||||||
// signature of the bank's state at given slot
|
// signature of the bank's state at given slot
|
||||||
pub hash: Hash,
|
pub hash: Hash,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vote {
|
impl Vote {
|
||||||
pub fn new(slot: u64, hash: Hash) -> Self {
|
pub fn new(slot: Slot, hash: Hash) -> Self {
|
||||||
Self { slot, hash }
|
Self { slot, hash }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Lockout {
|
pub struct Lockout {
|
||||||
pub slot: u64,
|
pub slot: Slot,
|
||||||
pub confirmation_count: u32,
|
pub confirmation_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,10 +56,10 @@ impl Lockout {
|
||||||
|
|
||||||
// The slot height at which this vote expires (cannot vote for any slot
|
// The slot height at which this vote expires (cannot vote for any slot
|
||||||
// less than this)
|
// less than this)
|
||||||
pub fn expiration_slot(&self) -> u64 {
|
pub fn expiration_slot(&self) -> Slot {
|
||||||
self.slot + self.lockout()
|
self.slot + self.lockout()
|
||||||
}
|
}
|
||||||
pub fn is_expired(&self, slot: u64) -> bool {
|
pub fn is_expired(&self, slot: Slot) -> bool {
|
||||||
self.expiration_slot() < slot
|
self.expiration_slot() < slot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,21 +73,27 @@ pub struct VoteState {
|
||||||
/// payout should be given to this VoteAccount
|
/// payout should be given to this VoteAccount
|
||||||
pub commission: u32,
|
pub commission: u32,
|
||||||
pub root_slot: Option<u64>,
|
pub root_slot: Option<u64>,
|
||||||
|
|
||||||
|
/// current epoch
|
||||||
|
epoch: Epoch,
|
||||||
|
/// current credits earned, monotonically increasing
|
||||||
credits: u64,
|
credits: u64,
|
||||||
|
|
||||||
|
/// credits as of previous epoch
|
||||||
|
last_epoch_credits: u64,
|
||||||
|
|
||||||
|
/// history of how many credits earned by the end of each epoch
|
||||||
|
/// each tuple is (Epoch, credits, prev_credits)
|
||||||
|
epoch_credits: Vec<(Epoch, u64, u64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VoteState {
|
impl VoteState {
|
||||||
pub fn new(vote_pubkey: &Pubkey, node_pubkey: &Pubkey, commission: u32) -> Self {
|
pub fn new(vote_pubkey: &Pubkey, node_pubkey: &Pubkey, commission: u32) -> Self {
|
||||||
let votes = VecDeque::new();
|
|
||||||
let credits = 0;
|
|
||||||
let root_slot = None;
|
|
||||||
Self {
|
Self {
|
||||||
votes,
|
|
||||||
node_pubkey: *node_pubkey,
|
node_pubkey: *node_pubkey,
|
||||||
authorized_voter_pubkey: *vote_pubkey,
|
authorized_voter_pubkey: *vote_pubkey,
|
||||||
credits,
|
|
||||||
commission,
|
commission,
|
||||||
root_slot,
|
..VoteState::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +103,7 @@ impl VoteState {
|
||||||
let mut vote_state = Self::default();
|
let mut vote_state = Self::default();
|
||||||
vote_state.votes = VecDeque::from(vec![Lockout::default(); MAX_LOCKOUT_HISTORY]);
|
vote_state.votes = VecDeque::from(vec![Lockout::default(); MAX_LOCKOUT_HISTORY]);
|
||||||
vote_state.root_slot = Some(std::u64::MAX);
|
vote_state.root_slot = Some(std::u64::MAX);
|
||||||
|
vote_state.epoch_credits = vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY];
|
||||||
serialized_size(&vote_state).unwrap() as usize
|
serialized_size(&vote_state).unwrap() as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,11 +148,13 @@ impl VoteState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_votes(&mut self, votes: &[Vote], slot_hashes: &[(u64, Hash)]) {
|
pub fn process_votes(&mut self, votes: &[Vote], slot_hashes: &[(Slot, Hash)], epoch: Epoch) {
|
||||||
votes.iter().for_each(|v| self.process_vote(v, slot_hashes));
|
votes
|
||||||
|
.iter()
|
||||||
|
.for_each(|v| self.process_vote(v, slot_hashes, epoch));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_vote(&mut self, vote: &Vote, slot_hashes: &[(u64, Hash)]) {
|
pub fn process_vote(&mut self, vote: &Vote, slot_hashes: &[(Slot, Hash)], epoch: Epoch) {
|
||||||
// Ignore votes for slots earlier than we already have votes for
|
// Ignore votes for slots earlier than we already have votes for
|
||||||
if self
|
if self
|
||||||
.votes
|
.votes
|
||||||
|
@ -176,24 +190,45 @@ impl VoteState {
|
||||||
|
|
||||||
let vote = Lockout::new(&vote);
|
let vote = Lockout::new(&vote);
|
||||||
|
|
||||||
// TODO: Integrity checks
|
|
||||||
// Verify the vote's bank hash matches what is expected
|
|
||||||
|
|
||||||
self.pop_expired_votes(vote.slot);
|
self.pop_expired_votes(vote.slot);
|
||||||
|
|
||||||
// Once the stack is full, pop the oldest vote and distribute rewards
|
// Once the stack is full, pop the oldest vote and distribute rewards
|
||||||
if self.votes.len() == MAX_LOCKOUT_HISTORY {
|
if self.votes.len() == MAX_LOCKOUT_HISTORY {
|
||||||
let vote = self.votes.pop_front().unwrap();
|
let vote = self.votes.pop_front().unwrap();
|
||||||
self.root_slot = Some(vote.slot);
|
self.root_slot = Some(vote.slot);
|
||||||
self.credits += 1;
|
|
||||||
|
self.increment_credits(epoch);
|
||||||
}
|
}
|
||||||
self.votes.push_back(vote);
|
self.votes.push_back(vote);
|
||||||
self.double_lockouts();
|
self.double_lockouts();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_vote_unchecked(&mut self, vote: &Vote) {
|
/// increment credits, record credits for last epoch if new epoch
|
||||||
self.process_vote(vote, &[(vote.slot, vote.hash)]);
|
pub fn increment_credits(&mut self, epoch: Epoch) {
|
||||||
|
// record credits by epoch
|
||||||
|
|
||||||
|
if epoch != self.epoch {
|
||||||
|
// encode the delta, but be able to return partial for stakers who
|
||||||
|
// attach halfway through an epoch
|
||||||
|
self.epoch_credits
|
||||||
|
.push((self.epoch, self.credits, self.last_epoch_credits));
|
||||||
|
// if stakers do not claim before the epoch goes away they lose the
|
||||||
|
// credits...
|
||||||
|
if self.epoch_credits.len() > MAX_EPOCH_CREDITS_HISTORY {
|
||||||
|
self.epoch_credits.remove(0);
|
||||||
}
|
}
|
||||||
pub fn process_slot_vote_unchecked(&mut self, slot: u64) {
|
self.epoch = epoch;
|
||||||
|
self.last_epoch_credits = self.credits;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.credits += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "uncheckeds" used by tests, locktower
|
||||||
|
pub fn process_vote_unchecked(&mut self, vote: &Vote) {
|
||||||
|
self.process_vote(vote, &[(vote.slot, vote.hash)], self.epoch);
|
||||||
|
}
|
||||||
|
pub fn process_slot_vote_unchecked(&mut self, slot: Slot) {
|
||||||
self.process_vote_unchecked(&Vote::new(slot, Hash::default()));
|
self.process_vote_unchecked(&Vote::new(slot, Hash::default()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +247,13 @@ impl VoteState {
|
||||||
self.credits
|
self.credits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Number of "credits" owed to this account from the mining pool on a per-epoch basis,
|
||||||
|
/// starting from credits observed.
|
||||||
|
/// Each tuple of (Epoch, u64) is the credits() delta as of the end of the Epoch
|
||||||
|
pub fn epoch_credits(&self) -> impl Iterator<Item = &(Epoch, u64, u64)> {
|
||||||
|
self.epoch_credits.iter()
|
||||||
|
}
|
||||||
|
|
||||||
fn pop_expired_votes(&mut self, slot: u64) {
|
fn pop_expired_votes(&mut self, slot: u64) {
|
||||||
loop {
|
loop {
|
||||||
if self.votes.back().map_or(false, |v| v.is_expired(slot)) {
|
if self.votes.back().map_or(false, |v| v.is_expired(slot)) {
|
||||||
|
@ -280,7 +322,8 @@ pub fn initialize_account(
|
||||||
|
|
||||||
pub fn process_votes(
|
pub fn process_votes(
|
||||||
vote_account: &mut KeyedAccount,
|
vote_account: &mut KeyedAccount,
|
||||||
slot_hashes_account: &mut KeyedAccount,
|
slot_hashes: &[(Slot, Hash)],
|
||||||
|
current: &Current,
|
||||||
other_signers: &[KeyedAccount],
|
other_signers: &[KeyedAccount],
|
||||||
votes: &[Vote],
|
votes: &[Vote],
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
|
@ -290,12 +333,6 @@ pub fn process_votes(
|
||||||
return Err(InstructionError::UninitializedAccount);
|
return Err(InstructionError::UninitializedAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !slot_hashes::check_id(slot_hashes_account.unsigned_key()) {
|
|
||||||
return Err(InstructionError::InvalidArgument);
|
|
||||||
}
|
|
||||||
|
|
||||||
let slot_hashes: Vec<(u64, Hash)> = slot_hashes_account.state()?;
|
|
||||||
|
|
||||||
let authorized = Some(&vote_state.authorized_voter_pubkey);
|
let authorized = Some(&vote_state.authorized_voter_pubkey);
|
||||||
// find a signer that matches the authorized_voter_pubkey
|
// find a signer that matches the authorized_voter_pubkey
|
||||||
if vote_account.signer_key() != authorized
|
if vote_account.signer_key() != authorized
|
||||||
|
@ -306,7 +343,7 @@ pub fn process_votes(
|
||||||
return Err(InstructionError::MissingRequiredSignature);
|
return Err(InstructionError::MissingRequiredSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
vote_state.process_votes(&votes, &slot_hashes);
|
vote_state.process_votes(&votes, slot_hashes, current.epoch);
|
||||||
vote_account.set_state(&vote_state)
|
vote_account.set_state(&vote_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,12 +356,10 @@ pub fn create_account(
|
||||||
) -> Account {
|
) -> Account {
|
||||||
let mut vote_account = Account::new(lamports, VoteState::size_of(), &id());
|
let mut vote_account = Account::new(lamports, VoteState::size_of(), &id());
|
||||||
|
|
||||||
initialize_account(
|
VoteState::new(vote_pubkey, node_pubkey, commission)
|
||||||
&mut KeyedAccount::new(vote_pubkey, false, &mut vote_account),
|
.to(&mut vote_account)
|
||||||
node_pubkey,
|
|
||||||
commission,
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
vote_account
|
vote_account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,12 +386,9 @@ pub fn create_bootstrap_leader_account(
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::vote_state;
|
use crate::vote_state;
|
||||||
use bincode::serialized_size;
|
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
use solana_sdk::account_utils::State;
|
use solana_sdk::account_utils::State;
|
||||||
use solana_sdk::hash::hash;
|
use solana_sdk::hash::hash;
|
||||||
use solana_sdk::syscall;
|
|
||||||
use solana_sdk::syscall::slot_hashes;
|
|
||||||
|
|
||||||
const MAX_RECENT_VOTES: usize = 16;
|
const MAX_RECENT_VOTES: usize = 16;
|
||||||
|
|
||||||
|
@ -385,30 +417,20 @@ mod tests {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_test_slot_hashes_account(slot_hashes: &[(u64, Hash)]) -> (Pubkey, Account) {
|
|
||||||
let mut slot_hashes_account = Account::new(
|
|
||||||
0,
|
|
||||||
serialized_size(&slot_hashes).unwrap() as usize,
|
|
||||||
&syscall::id(),
|
|
||||||
);
|
|
||||||
slot_hashes_account
|
|
||||||
.set_state(&slot_hashes.to_vec())
|
|
||||||
.unwrap();
|
|
||||||
(slot_hashes::id(), slot_hashes_account)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn simulate_process_vote(
|
fn simulate_process_vote(
|
||||||
vote_pubkey: &Pubkey,
|
vote_pubkey: &Pubkey,
|
||||||
vote_account: &mut Account,
|
vote_account: &mut Account,
|
||||||
vote: &Vote,
|
vote: &Vote,
|
||||||
slot_hashes: &[(u64, Hash)],
|
slot_hashes: &[(u64, Hash)],
|
||||||
|
epoch: u64,
|
||||||
) -> Result<VoteState, InstructionError> {
|
) -> Result<VoteState, InstructionError> {
|
||||||
let (slot_hashes_id, mut slot_hashes_account) =
|
|
||||||
create_test_slot_hashes_account(slot_hashes);
|
|
||||||
|
|
||||||
process_votes(
|
process_votes(
|
||||||
&mut KeyedAccount::new(vote_pubkey, true, vote_account),
|
&mut KeyedAccount::new(vote_pubkey, true, vote_account),
|
||||||
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
|
slot_hashes,
|
||||||
|
&Current {
|
||||||
|
epoch,
|
||||||
|
..Current::default()
|
||||||
|
},
|
||||||
&[],
|
&[],
|
||||||
&[vote.clone()],
|
&[vote.clone()],
|
||||||
)?;
|
)?;
|
||||||
|
@ -421,7 +443,13 @@ mod tests {
|
||||||
vote_account: &mut Account,
|
vote_account: &mut Account,
|
||||||
vote: &Vote,
|
vote: &Vote,
|
||||||
) -> Result<VoteState, InstructionError> {
|
) -> Result<VoteState, InstructionError> {
|
||||||
simulate_process_vote(vote_pubkey, vote_account, vote, &[(vote.slot, vote.hash)])
|
simulate_process_vote(
|
||||||
|
vote_pubkey,
|
||||||
|
vote_account,
|
||||||
|
vote,
|
||||||
|
&[(vote.slot, vote.hash)],
|
||||||
|
0,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -479,58 +507,45 @@ mod tests {
|
||||||
&mut vote_account,
|
&mut vote_account,
|
||||||
&vote,
|
&vote,
|
||||||
&[(0, Hash::default())],
|
&[(0, Hash::default())],
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(vote_state.votes.len(), 0);
|
assert_eq!(vote_state.votes.len(), 0);
|
||||||
|
|
||||||
// wrong slot
|
// wrong slot
|
||||||
let vote_state =
|
let vote_state =
|
||||||
simulate_process_vote(&vote_pubkey, &mut vote_account, &vote, &[(1, hash)]).unwrap();
|
simulate_process_vote(&vote_pubkey, &mut vote_account, &vote, &[(1, hash)], 0).unwrap();
|
||||||
assert_eq!(vote_state.votes.len(), 0);
|
assert_eq!(vote_state.votes.len(), 0);
|
||||||
|
|
||||||
// empty slot_hashes
|
// empty slot_hashes
|
||||||
let vote_state =
|
let vote_state =
|
||||||
simulate_process_vote(&vote_pubkey, &mut vote_account, &vote, &[]).unwrap();
|
simulate_process_vote(&vote_pubkey, &mut vote_account, &vote, &[], 0).unwrap();
|
||||||
assert_eq!(vote_state.votes.len(), 0);
|
assert_eq!(vote_state.votes.len(), 0);
|
||||||
|
|
||||||
// this one would work, but the wrong account is passed for slot_hashes_id
|
|
||||||
let (_slot_hashes_id, mut slot_hashes_account) =
|
|
||||||
create_test_slot_hashes_account(&[(vote.slot, vote.hash)]);
|
|
||||||
assert_eq!(
|
|
||||||
process_votes(
|
|
||||||
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
|
||||||
&mut KeyedAccount::new(&Pubkey::default(), false, &mut slot_hashes_account),
|
|
||||||
&[],
|
|
||||||
&[vote.clone()],
|
|
||||||
),
|
|
||||||
Err(InstructionError::InvalidArgument)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vote_signature() {
|
fn test_vote_signature() {
|
||||||
let (vote_pubkey, mut vote_account) = create_test_account();
|
let (vote_pubkey, mut vote_account) = create_test_account();
|
||||||
|
|
||||||
let vote = vec![Vote::new(1, Hash::default())];
|
let vote = Vote::new(1, Hash::default());
|
||||||
|
|
||||||
let (slot_hashes_id, mut slot_hashes_account) =
|
|
||||||
create_test_slot_hashes_account(&[(1, Hash::default())]);
|
|
||||||
|
|
||||||
// unsigned
|
// unsigned
|
||||||
let res = process_votes(
|
let res = process_votes(
|
||||||
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
||||||
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
|
&[(vote.slot, vote.hash)],
|
||||||
|
&Current::default(),
|
||||||
&[],
|
&[],
|
||||||
&vote,
|
&[vote],
|
||||||
);
|
);
|
||||||
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
||||||
|
|
||||||
// unsigned
|
// unsigned
|
||||||
let res = process_votes(
|
let res = process_votes(
|
||||||
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
||||||
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
|
&[(vote.slot, vote.hash)],
|
||||||
|
&Current::default(),
|
||||||
&[],
|
&[],
|
||||||
&vote,
|
&[vote],
|
||||||
);
|
);
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Ok(()));
|
||||||
|
|
||||||
|
@ -562,28 +577,28 @@ mod tests {
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Ok(()));
|
||||||
|
|
||||||
// not signed by authorized voter
|
// not signed by authorized voter
|
||||||
let vote = vec![Vote::new(2, Hash::default())];
|
let vote = Vote::new(2, Hash::default());
|
||||||
let (slot_hashes_id, mut slot_hashes_account) =
|
|
||||||
create_test_slot_hashes_account(&[(2, Hash::default())]);
|
|
||||||
let res = process_votes(
|
let res = process_votes(
|
||||||
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
||||||
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
|
&[(vote.slot, vote.hash)],
|
||||||
|
&Current::default(),
|
||||||
&[],
|
&[],
|
||||||
&vote,
|
&[vote],
|
||||||
);
|
);
|
||||||
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
||||||
|
|
||||||
// signed by authorized voter
|
// signed by authorized voter
|
||||||
let vote = vec![Vote::new(2, Hash::default())];
|
let vote = Vote::new(2, Hash::default());
|
||||||
let res = process_votes(
|
let res = process_votes(
|
||||||
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
||||||
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
|
&[(vote.slot, vote.hash)],
|
||||||
|
&Current::default(),
|
||||||
&[KeyedAccount::new(
|
&[KeyedAccount::new(
|
||||||
&authorized_voter_pubkey,
|
&authorized_voter_pubkey,
|
||||||
true,
|
true,
|
||||||
&mut Account::default(),
|
&mut Account::default(),
|
||||||
)],
|
)],
|
||||||
&vote,
|
&[vote],
|
||||||
);
|
);
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Ok(()));
|
||||||
}
|
}
|
||||||
|
@ -773,8 +788,8 @@ mod tests {
|
||||||
.collect();
|
.collect();
|
||||||
let slot_hashes: Vec<_> = votes.iter().map(|vote| (vote.slot, vote.hash)).collect();
|
let slot_hashes: Vec<_> = votes.iter().map(|vote| (vote.slot, vote.hash)).collect();
|
||||||
|
|
||||||
vote_state_a.process_votes(&votes, &slot_hashes);
|
vote_state_a.process_votes(&votes, &slot_hashes, 0);
|
||||||
vote_state_b.process_votes(&votes, &slot_hashes);
|
vote_state_b.process_votes(&votes, &slot_hashes, 0);
|
||||||
assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
|
assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -796,4 +811,42 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vote_state_epoch_credits() {
|
||||||
|
let mut vote_state = VoteState::default();
|
||||||
|
|
||||||
|
assert_eq!(vote_state.credits(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
vote_state
|
||||||
|
.epoch_credits()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<(Epoch, u64, u64)>>(),
|
||||||
|
vec![]
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut expected = vec![];
|
||||||
|
let mut credits = 0;
|
||||||
|
let epochs = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
|
||||||
|
for epoch in 0..epochs {
|
||||||
|
for _j in 0..epoch {
|
||||||
|
vote_state.increment_credits(epoch);
|
||||||
|
credits += 1;
|
||||||
|
}
|
||||||
|
expected.push((epoch, credits, credits - epoch));
|
||||||
|
}
|
||||||
|
expected.pop(); // last one doesn't count, doesn't get saved off
|
||||||
|
while expected.len() > MAX_EPOCH_CREDITS_HISTORY {
|
||||||
|
expected.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(vote_state.credits(), credits);
|
||||||
|
assert_eq!(
|
||||||
|
vote_state
|
||||||
|
.epoch_credits()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<(Epoch, u64, u64)>>(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -417,7 +417,7 @@ impl Bank {
|
||||||
fn update_slot_hashes(&self) {
|
fn update_slot_hashes(&self) {
|
||||||
let mut account = self
|
let mut account = self
|
||||||
.get_account(&slot_hashes::id())
|
.get_account(&slot_hashes::id())
|
||||||
.unwrap_or_else(|| slot_hashes::create_account(1));
|
.unwrap_or_else(|| slot_hashes::create_account(1, &[]));
|
||||||
|
|
||||||
let mut slot_hashes = SlotHashes::from(&account).unwrap();
|
let mut slot_hashes = SlotHashes::from(&account).unwrap();
|
||||||
slot_hashes.add(self.slot(), self.hash());
|
slot_hashes.add(self.slot(), self.hash());
|
||||||
|
@ -546,6 +546,10 @@ impl Bank {
|
||||||
self.capitalization
|
self.capitalization
|
||||||
.fetch_add(account.lamports as usize, Ordering::Relaxed);
|
.fetch_add(account.lamports as usize, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
for (pubkey, account) in genesis_block.rewards_pools.iter() {
|
||||||
|
self.store_account(pubkey, account);
|
||||||
|
}
|
||||||
|
|
||||||
// highest staked node is the first collector
|
// highest staked node is the first collector
|
||||||
self.collector_id = self
|
self.collector_id = self
|
||||||
.stakes
|
.stakes
|
||||||
|
|
|
@ -4,6 +4,8 @@ use crate::account::Account;
|
||||||
use crate::syscall;
|
use crate::syscall;
|
||||||
use bincode::serialized_size;
|
use bincode::serialized_size;
|
||||||
|
|
||||||
|
pub use crate::timing::{Epoch, Slot};
|
||||||
|
|
||||||
crate::solana_name_id!(ID, "Sysca11Current11111111111111111111111111111");
|
crate::solana_name_id!(ID, "Sysca11Current11111111111111111111111111111");
|
||||||
|
|
||||||
const ID: [u8; 32] = [
|
const ID: [u8; 32] = [
|
||||||
|
@ -14,9 +16,9 @@ const ID: [u8; 32] = [
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
|
||||||
pub struct Current {
|
pub struct Current {
|
||||||
pub slot: u64,
|
pub slot: Slot,
|
||||||
pub epoch: u64,
|
pub epoch: Epoch,
|
||||||
pub stakers_epoch: u64,
|
pub stakers_epoch: Epoch,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Current {
|
impl Current {
|
||||||
|
@ -32,7 +34,7 @@ impl Current {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_account(lamports: u64, slot: u64, epoch: u64, stakers_epoch: u64) -> Account {
|
pub fn create_account(lamports: u64, slot: Slot, epoch: Epoch, stakers_epoch: Epoch) -> Account {
|
||||||
Account::new_data(
|
Account::new_data(
|
||||||
lamports,
|
lamports,
|
||||||
&Current {
|
&Current {
|
||||||
|
@ -45,6 +47,15 @@ pub fn create_account(lamports: u64, slot: u64, epoch: u64, stakers_epoch: u64)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::account::KeyedAccount;
|
||||||
|
use crate::instruction::InstructionError;
|
||||||
|
pub fn from_keyed_account(account: &KeyedAccount) -> Result<Current, InstructionError> {
|
||||||
|
if !check_id(account.unsigned_key()) {
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
Current::from(account.account).ok_or(InstructionError::InvalidArgument)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -47,6 +47,16 @@ pub fn create_account(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::account::KeyedAccount;
|
||||||
|
use crate::instruction::InstructionError;
|
||||||
|
pub fn from_keyed_account(account: &KeyedAccount) -> Result<Rewards, InstructionError> {
|
||||||
|
if !check_id(account.unsigned_key()) {
|
||||||
|
dbg!(account.unsigned_key());
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
Rewards::from(account.account).ok_or(InstructionError::InvalidAccountData)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -9,6 +9,8 @@ use crate::syscall;
|
||||||
use bincode::serialized_size;
|
use bincode::serialized_size;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
pub use crate::timing::Slot;
|
||||||
|
|
||||||
/// "Sysca11SlotHashes11111111111111111111111111"
|
/// "Sysca11SlotHashes11111111111111111111111111"
|
||||||
/// slot hashes account pubkey
|
/// slot hashes account pubkey
|
||||||
const ID: [u8; 32] = [
|
const ID: [u8; 32] = [
|
||||||
|
@ -29,7 +31,7 @@ pub const MAX_SLOT_HASHES: usize = 512; // 512 slots to get your vote in
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
pub struct SlotHashes {
|
pub struct SlotHashes {
|
||||||
// non-pub to keep control of size
|
// non-pub to keep control of size
|
||||||
inner: Vec<(u64, Hash)>,
|
inner: Vec<(Slot, Hash)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SlotHashes {
|
impl SlotHashes {
|
||||||
|
@ -46,10 +48,15 @@ impl SlotHashes {
|
||||||
})
|
})
|
||||||
.unwrap() as usize
|
.unwrap() as usize
|
||||||
}
|
}
|
||||||
pub fn add(&mut self, slot: u64, hash: Hash) {
|
pub fn add(&mut self, slot: Slot, hash: Hash) {
|
||||||
self.inner.insert(0, (slot, hash));
|
self.inner.insert(0, (slot, hash));
|
||||||
self.inner.truncate(MAX_SLOT_HASHES);
|
self.inner.truncate(MAX_SLOT_HASHES);
|
||||||
}
|
}
|
||||||
|
pub fn new(slot_hashes: &[(Slot, Hash)]) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: slot_hashes.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for SlotHashes {
|
impl Deref for SlotHashes {
|
||||||
|
@ -59,8 +66,19 @@ impl Deref for SlotHashes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_account(lamports: u64) -> Account {
|
pub fn create_account(lamports: u64, slot_hashes: &[(Slot, Hash)]) -> Account {
|
||||||
Account::new(lamports, SlotHashes::size_of(), &syscall::id())
|
let mut account = Account::new(lamports, SlotHashes::size_of(), &syscall::id());
|
||||||
|
SlotHashes::new(slot_hashes).to(&mut account).unwrap();
|
||||||
|
account
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::account::KeyedAccount;
|
||||||
|
use crate::instruction::InstructionError;
|
||||||
|
pub fn from_keyed_account(account: &KeyedAccount) -> Result<SlotHashes, InstructionError> {
|
||||||
|
if !check_id(account.unsigned_key()) {
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
SlotHashes::from(account.account).ok_or(InstructionError::InvalidArgument)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -71,7 +89,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_account() {
|
fn test_create_account() {
|
||||||
let lamports = 42;
|
let lamports = 42;
|
||||||
let account = create_account(lamports);
|
let account = create_account(lamports, &[]);
|
||||||
|
assert_eq!(account.data.len(), SlotHashes::size_of());
|
||||||
let slot_hashes = SlotHashes::from(&account);
|
let slot_hashes = SlotHashes::from(&account);
|
||||||
assert_eq!(slot_hashes, Some(SlotHashes { inner: vec![] }));
|
assert_eq!(slot_hashes, Some(SlotHashes { inner: vec![] }));
|
||||||
let mut slot_hashes = slot_hashes.unwrap();
|
let mut slot_hashes = slot_hashes.unwrap();
|
||||||
|
|
|
@ -57,3 +57,11 @@ pub fn timestamp() -> u64 {
|
||||||
.expect("create timestamp in timing");
|
.expect("create timestamp in timing");
|
||||||
duration_as_ms(&now)
|
duration_as_ms(&now)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Slot is a unit of time given to a leader for encoding,
|
||||||
|
/// is some some number of Ticks long. Use a u64 to count them.
|
||||||
|
pub type Slot = u64;
|
||||||
|
|
||||||
|
/// Epoch is a unit of time a given leader schedule is honored,
|
||||||
|
/// some number of Slots. Use a u64 to count them.
|
||||||
|
pub type Epoch = u64;
|
||||||
|
|
|
@ -51,9 +51,8 @@ pub enum WalletCommand {
|
||||||
CreateVoteAccount(Pubkey, Pubkey, u32, u64),
|
CreateVoteAccount(Pubkey, Pubkey, u32, u64),
|
||||||
ShowVoteAccount(Pubkey),
|
ShowVoteAccount(Pubkey),
|
||||||
CreateStakeAccount(Pubkey, u64),
|
CreateStakeAccount(Pubkey, u64),
|
||||||
CreateMiningPoolAccount(Pubkey, u64),
|
|
||||||
DelegateStake(Keypair, Pubkey, u64),
|
DelegateStake(Keypair, Pubkey, u64),
|
||||||
RedeemVoteCredits(Pubkey, Pubkey, Pubkey),
|
RedeemVoteCredits(Pubkey, Pubkey),
|
||||||
ShowStakeAccount(Pubkey),
|
ShowStakeAccount(Pubkey),
|
||||||
CreateStorageMiningPoolAccount(Pubkey, u64),
|
CreateStorageMiningPoolAccount(Pubkey, u64),
|
||||||
CreateReplicatorStorageAccount(Pubkey, Pubkey),
|
CreateReplicatorStorageAccount(Pubkey, Pubkey),
|
||||||
|
@ -238,15 +237,6 @@ pub fn parse_command(
|
||||||
lamports,
|
lamports,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
("create-mining-pool-account", Some(matches)) => {
|
|
||||||
let mining_pool_account_pubkey =
|
|
||||||
value_of(matches, "mining_pool_account_pubkey").unwrap();
|
|
||||||
let lamports = matches.value_of("lamports").unwrap().parse()?;
|
|
||||||
Ok(WalletCommand::CreateMiningPoolAccount(
|
|
||||||
mining_pool_account_pubkey,
|
|
||||||
lamports,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
("delegate-stake", Some(matches)) => {
|
("delegate-stake", Some(matches)) => {
|
||||||
let staking_account_keypair =
|
let staking_account_keypair =
|
||||||
keypair_of(matches, "staking_account_keypair_file").unwrap();
|
keypair_of(matches, "staking_account_keypair_file").unwrap();
|
||||||
|
@ -259,12 +249,9 @@ pub fn parse_command(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
("redeem-vote-credits", Some(matches)) => {
|
("redeem-vote-credits", Some(matches)) => {
|
||||||
let mining_pool_account_pubkey =
|
|
||||||
value_of(matches, "mining_pool_account_pubkey").unwrap();
|
|
||||||
let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap();
|
let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap();
|
||||||
let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap();
|
let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap();
|
||||||
Ok(WalletCommand::RedeemVoteCredits(
|
Ok(WalletCommand::RedeemVoteCredits(
|
||||||
mining_pool_account_pubkey,
|
|
||||||
staking_account_pubkey,
|
staking_account_pubkey,
|
||||||
voting_account_pubkey,
|
voting_account_pubkey,
|
||||||
))
|
))
|
||||||
|
@ -273,15 +260,6 @@ pub fn parse_command(
|
||||||
let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap();
|
let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap();
|
||||||
Ok(WalletCommand::ShowStakeAccount(staking_account_pubkey))
|
Ok(WalletCommand::ShowStakeAccount(staking_account_pubkey))
|
||||||
}
|
}
|
||||||
("create-storage-mining-pool-account", Some(matches)) => {
|
|
||||||
let storage_mining_pool_account_pubkey =
|
|
||||||
value_of(matches, "storage_mining_pool_account_pubkey").unwrap();
|
|
||||||
let lamports = matches.value_of("lamports").unwrap().parse()?;
|
|
||||||
Ok(WalletCommand::CreateStorageMiningPoolAccount(
|
|
||||||
storage_mining_pool_account_pubkey,
|
|
||||||
lamports,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
("create-replicator-storage-account", Some(matches)) => {
|
("create-replicator-storage-account", Some(matches)) => {
|
||||||
let account_owner = value_of(matches, "storage_account_owner").unwrap();
|
let account_owner = value_of(matches, "storage_account_owner").unwrap();
|
||||||
let storage_account_pubkey = value_of(matches, "storage_account_pubkey").unwrap();
|
let storage_account_pubkey = value_of(matches, "storage_account_pubkey").unwrap();
|
||||||
|
@ -580,28 +558,6 @@ fn process_create_stake_account(
|
||||||
Ok(signature_str.to_string())
|
Ok(signature_str.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_create_mining_pool_account(
|
|
||||||
rpc_client: &RpcClient,
|
|
||||||
config: &WalletConfig,
|
|
||||||
mining_pool_account_pubkey: &Pubkey,
|
|
||||||
lamports: u64,
|
|
||||||
) -> ProcessResult {
|
|
||||||
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
|
|
||||||
let ixs = stake_instruction::create_mining_pool_account(
|
|
||||||
&config.keypair.pubkey(),
|
|
||||||
mining_pool_account_pubkey,
|
|
||||||
lamports,
|
|
||||||
);
|
|
||||||
let mut tx = Transaction::new_signed_with_payer(
|
|
||||||
ixs,
|
|
||||||
Some(&config.keypair.pubkey()),
|
|
||||||
&[&config.keypair],
|
|
||||||
recent_blockhash,
|
|
||||||
);
|
|
||||||
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?;
|
|
||||||
Ok(signature_str.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_delegate_stake(
|
fn process_delegate_stake(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
|
@ -631,13 +587,11 @@ fn process_delegate_stake(
|
||||||
fn process_redeem_vote_credits(
|
fn process_redeem_vote_credits(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
mining_pool_account_pubkey: &Pubkey,
|
|
||||||
staking_account_pubkey: &Pubkey,
|
staking_account_pubkey: &Pubkey,
|
||||||
voting_account_pubkey: &Pubkey,
|
voting_account_pubkey: &Pubkey,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
|
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||||
let ixs = vec![stake_instruction::redeem_vote_credits(
|
let ixs = vec![stake_instruction::redeem_vote_credits(
|
||||||
mining_pool_account_pubkey,
|
|
||||||
staking_account_pubkey,
|
staking_account_pubkey,
|
||||||
voting_account_pubkey,
|
voting_account_pubkey,
|
||||||
)];
|
)];
|
||||||
|
@ -663,15 +617,7 @@ fn process_show_stake_account(
|
||||||
println!("account lamports: {}", stake_account.lamports);
|
println!("account lamports: {}", stake_account.lamports);
|
||||||
println!("voter pubkey: {}", stake.voter_pubkey);
|
println!("voter pubkey: {}", stake.voter_pubkey);
|
||||||
println!("credits observed: {}", stake.credits_observed);
|
println!("credits observed: {}", stake.credits_observed);
|
||||||
println!("epoch: {}", stake.epoch);
|
println!("stake: {}", stake.stake);
|
||||||
println!("activated stake: {}", stake.stake);
|
|
||||||
println!("previous stake: {}", stake.prev_stake);
|
|
||||||
Ok("".to_string())
|
|
||||||
}
|
|
||||||
Ok(StakeState::MiningPool { epoch, point_value }) => {
|
|
||||||
println!("account lamports: {}", stake_account.lamports);
|
|
||||||
println!("epoch: {}", epoch);
|
|
||||||
println!("point_value: {}", point_value);
|
|
||||||
Ok("".to_string())
|
Ok("".to_string())
|
||||||
}
|
}
|
||||||
_ => Err(WalletError::RpcRequestError(
|
_ => Err(WalletError::RpcRequestError(
|
||||||
|
@ -1066,15 +1012,6 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
|
||||||
process_create_stake_account(&rpc_client, config, &staking_account_pubkey, *lamports)
|
process_create_stake_account(&rpc_client, config, &staking_account_pubkey, *lamports)
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletCommand::CreateMiningPoolAccount(mining_pool_account_pubkey, lamports) => {
|
|
||||||
process_create_mining_pool_account(
|
|
||||||
&rpc_client,
|
|
||||||
config,
|
|
||||||
&mining_pool_account_pubkey,
|
|
||||||
*lamports,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
WalletCommand::DelegateStake(staking_account_keypair, voting_account_pubkey, lamports) => {
|
WalletCommand::DelegateStake(staking_account_keypair, voting_account_pubkey, lamports) => {
|
||||||
process_delegate_stake(
|
process_delegate_stake(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
|
@ -1085,17 +1022,14 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletCommand::RedeemVoteCredits(
|
WalletCommand::RedeemVoteCredits(staking_account_pubkey, voting_account_pubkey) => {
|
||||||
mining_pool_account_pubkey,
|
process_redeem_vote_credits(
|
||||||
staking_account_pubkey,
|
|
||||||
voting_account_pubkey,
|
|
||||||
) => process_redeem_vote_credits(
|
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
config,
|
config,
|
||||||
&mining_pool_account_pubkey,
|
|
||||||
&staking_account_pubkey,
|
&staking_account_pubkey,
|
||||||
&voting_account_pubkey,
|
&voting_account_pubkey,
|
||||||
),
|
)
|
||||||
|
}
|
||||||
|
|
||||||
WalletCommand::ShowStakeAccount(staking_account_pubkey) => {
|
WalletCommand::ShowStakeAccount(staking_account_pubkey) => {
|
||||||
process_show_stake_account(&rpc_client, config, &staking_account_pubkey)
|
process_show_stake_account(&rpc_client, config, &staking_account_pubkey)
|
||||||
|
@ -1417,27 +1351,6 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||||
.help("Vote account pubkey"),
|
.help("Vote account pubkey"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("create-mining-pool-account")
|
|
||||||
.about("Create staking mining pool account")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("mining_pool_account_pubkey")
|
|
||||||
.index(1)
|
|
||||||
.value_name("PUBKEY")
|
|
||||||
.takes_value(true)
|
|
||||||
.required(true)
|
|
||||||
.validator(is_pubkey)
|
|
||||||
.help("Staking mining pool account address to fund"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("lamports")
|
|
||||||
.index(2)
|
|
||||||
.value_name("NUM")
|
|
||||||
.takes_value(true)
|
|
||||||
.required(true)
|
|
||||||
.help("The number of lamports to assign to the mining pool account"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("create-stake-account")
|
SubCommand::with_name("create-stake-account")
|
||||||
.about("Create staking account")
|
.about("Create staking account")
|
||||||
|
|
Loading…
Reference in New Issue