stake: Advance credits_observed
on activation epoch (#19309)
* stake: Advance `credits_observed` on activation epoch * Add test for merging stakes just after activation
This commit is contained in:
@ -2,7 +2,8 @@
|
||||
use {
|
||||
assert_matches::assert_matches,
|
||||
bincode::deserialize,
|
||||
solana_program_test::{processor, ProgramTest, ProgramTestError},
|
||||
solana_banks_client::BanksClient,
|
||||
solana_program_test::{processor, ProgramTest, ProgramTestContext, ProgramTestError},
|
||||
solana_sdk::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
clock::Clock,
|
||||
@ -34,6 +35,75 @@ use {
|
||||
// Use a big number to be sure that we get the right error
|
||||
const WRONG_SLOT_ERROR: u32 = 123456;
|
||||
|
||||
async fn setup_stake(
|
||||
context: &mut ProgramTestContext,
|
||||
user: &Keypair,
|
||||
vote_address: &Pubkey,
|
||||
stake_lamports: u64,
|
||||
) -> Pubkey {
|
||||
let stake_keypair = Keypair::new();
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&stake_instruction::create_account_and_delegate_stake(
|
||||
&context.payer.pubkey(),
|
||||
&stake_keypair.pubkey(),
|
||||
vote_address,
|
||||
&Authorized::auto(&user.pubkey()),
|
||||
&Lockup::default(),
|
||||
stake_lamports,
|
||||
),
|
||||
Some(&context.payer.pubkey()),
|
||||
&vec![&context.payer, &stake_keypair, user],
|
||||
context.last_blockhash,
|
||||
);
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
stake_keypair.pubkey()
|
||||
}
|
||||
|
||||
async fn setup_vote(context: &mut ProgramTestContext) -> Pubkey {
|
||||
// warp once to make sure stake config doesn't get rent-collected
|
||||
context.warp_to_slot(100).unwrap();
|
||||
let mut instructions = vec![];
|
||||
let validator_keypair = Keypair::new();
|
||||
instructions.push(system_instruction::create_account(
|
||||
&context.payer.pubkey(),
|
||||
&validator_keypair.pubkey(),
|
||||
42,
|
||||
0,
|
||||
&system_program::id(),
|
||||
));
|
||||
let vote_lamports = Rent::default().minimum_balance(VoteState::size_of());
|
||||
let vote_keypair = Keypair::new();
|
||||
let user_keypair = Keypair::new();
|
||||
instructions.append(&mut vote_instruction::create_account(
|
||||
&context.payer.pubkey(),
|
||||
&vote_keypair.pubkey(),
|
||||
&VoteInit {
|
||||
node_pubkey: validator_keypair.pubkey(),
|
||||
authorized_voter: user_keypair.pubkey(),
|
||||
..VoteInit::default()
|
||||
},
|
||||
vote_lamports,
|
||||
));
|
||||
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&instructions,
|
||||
Some(&context.payer.pubkey()),
|
||||
&vec![&context.payer, &validator_keypair, &vote_keypair],
|
||||
context.last_blockhash,
|
||||
);
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
vote_keypair.pubkey()
|
||||
}
|
||||
|
||||
fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
@ -166,63 +236,17 @@ async fn rent_collected_from_warp() {
|
||||
async fn stake_rewards_from_warp() {
|
||||
// Initialize and start the test network
|
||||
let program_test = ProgramTest::default();
|
||||
|
||||
let mut context = program_test.start_with_context().await;
|
||||
// warp once to make sure stake config doesn't get rent-collected
|
||||
context.warp_to_slot(100).unwrap();
|
||||
let mut instructions = vec![];
|
||||
let validator_keypair = Keypair::new();
|
||||
instructions.push(system_instruction::create_account(
|
||||
&context.payer.pubkey(),
|
||||
&validator_keypair.pubkey(),
|
||||
42,
|
||||
0,
|
||||
&system_program::id(),
|
||||
));
|
||||
let vote_lamports = Rent::default().minimum_balance(VoteState::size_of());
|
||||
let vote_keypair = Keypair::new();
|
||||
let user_keypair = Keypair::new();
|
||||
instructions.append(&mut vote_instruction::create_account(
|
||||
&context.payer.pubkey(),
|
||||
&vote_keypair.pubkey(),
|
||||
&VoteInit {
|
||||
node_pubkey: validator_keypair.pubkey(),
|
||||
authorized_voter: user_keypair.pubkey(),
|
||||
..VoteInit::default()
|
||||
},
|
||||
vote_lamports,
|
||||
));
|
||||
let vote_address = setup_vote(&mut context).await;
|
||||
|
||||
let stake_keypair = Keypair::new();
|
||||
let user_keypair = Keypair::new();
|
||||
let stake_lamports = 1_000_000_000_000;
|
||||
instructions.append(&mut stake_instruction::create_account_and_delegate_stake(
|
||||
&context.payer.pubkey(),
|
||||
&stake_keypair.pubkey(),
|
||||
&vote_keypair.pubkey(),
|
||||
&Authorized::auto(&user_keypair.pubkey()),
|
||||
&Lockup::default(),
|
||||
stake_lamports,
|
||||
));
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&instructions,
|
||||
Some(&context.payer.pubkey()),
|
||||
&vec![
|
||||
&context.payer,
|
||||
&validator_keypair,
|
||||
&vote_keypair,
|
||||
&stake_keypair,
|
||||
&user_keypair,
|
||||
],
|
||||
context.last_blockhash,
|
||||
);
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
let stake_address =
|
||||
setup_stake(&mut context, &user_keypair, &vote_address, stake_lamports).await;
|
||||
|
||||
let account = context
|
||||
.banks_client
|
||||
.get_account(stake_keypair.pubkey())
|
||||
.get_account(stake_address)
|
||||
.await
|
||||
.expect("account exists")
|
||||
.unwrap();
|
||||
@ -233,13 +257,13 @@ async fn stake_rewards_from_warp() {
|
||||
context.warp_to_slot(first_normal_slot).unwrap();
|
||||
let account = context
|
||||
.banks_client
|
||||
.get_account(stake_keypair.pubkey())
|
||||
.get_account(stake_address)
|
||||
.await
|
||||
.expect("account exists")
|
||||
.unwrap();
|
||||
assert_eq!(account.lamports, stake_lamports);
|
||||
|
||||
context.increment_vote_account_credits(&vote_keypair.pubkey(), 100);
|
||||
context.increment_vote_account_credits(&vote_address, 100);
|
||||
|
||||
// go forward and see that rewards have been distributed
|
||||
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
|
||||
@ -249,7 +273,7 @@ async fn stake_rewards_from_warp() {
|
||||
|
||||
let account = context
|
||||
.banks_client
|
||||
.get_account(stake_keypair.pubkey())
|
||||
.get_account(stake_address)
|
||||
.await
|
||||
.expect("account exists")
|
||||
.unwrap();
|
||||
@ -281,3 +305,112 @@ async fn stake_rewards_from_warp() {
|
||||
(_, 0, 0)
|
||||
);
|
||||
}
|
||||
|
||||
async fn check_credits_observed(
|
||||
banks_client: &mut BanksClient,
|
||||
stake_address: Pubkey,
|
||||
expected_credits: u64,
|
||||
) {
|
||||
let stake_account = banks_client
|
||||
.get_account(stake_address)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let stake_state: StakeState = deserialize(&stake_account.data).unwrap();
|
||||
assert_eq!(
|
||||
stake_state.stake().unwrap().credits_observed,
|
||||
expected_credits
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stake_merge_immediately_after_activation() {
|
||||
let program_test = ProgramTest::default();
|
||||
let mut context = program_test.start_with_context().await;
|
||||
let vote_address = setup_vote(&mut context).await;
|
||||
context.increment_vote_account_credits(&vote_address, 100);
|
||||
|
||||
let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot;
|
||||
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
|
||||
let mut current_slot = first_normal_slot + slots_per_epoch;
|
||||
context.warp_to_slot(current_slot).unwrap();
|
||||
|
||||
// this is annoying, but if no stake has earned rewards, the bank won't
|
||||
// iterate through the stakes at all, which means we can only test the
|
||||
// behavior of advancing credits observed if another stake is earning rewards
|
||||
|
||||
// make a base stake which receives rewards
|
||||
let user_keypair = Keypair::new();
|
||||
let stake_lamports = 1_000_000_000_000;
|
||||
let base_stake_address =
|
||||
setup_stake(&mut context, &user_keypair, &vote_address, stake_lamports).await;
|
||||
check_credits_observed(&mut context.banks_client, base_stake_address, 100).await;
|
||||
context.increment_vote_account_credits(&vote_address, 100);
|
||||
|
||||
current_slot += slots_per_epoch;
|
||||
context.warp_to_slot(current_slot).unwrap();
|
||||
|
||||
// make another stake which will just have its credits observed advanced
|
||||
let absorbed_stake_address =
|
||||
setup_stake(&mut context, &user_keypair, &vote_address, stake_lamports).await;
|
||||
// the new stake is at the right value
|
||||
check_credits_observed(&mut context.banks_client, absorbed_stake_address, 200).await;
|
||||
// the base stake hasn't been moved forward because no rewards were earned
|
||||
check_credits_observed(&mut context.banks_client, base_stake_address, 100).await;
|
||||
|
||||
context.increment_vote_account_credits(&vote_address, 100);
|
||||
current_slot += slots_per_epoch;
|
||||
context.warp_to_slot(current_slot).unwrap();
|
||||
|
||||
// check that base stake has earned rewards and credits moved forward
|
||||
let stake_account = context
|
||||
.banks_client
|
||||
.get_account(base_stake_address)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let stake_state: StakeState = deserialize(&stake_account.data).unwrap();
|
||||
assert_eq!(stake_state.stake().unwrap().credits_observed, 300);
|
||||
assert!(stake_account.lamports > stake_lamports);
|
||||
|
||||
// check that new stake hasn't earned rewards, but that credits_observed have been advanced
|
||||
let stake_account = context
|
||||
.banks_client
|
||||
.get_account(absorbed_stake_address)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let stake_state: StakeState = deserialize(&stake_account.data).unwrap();
|
||||
assert_eq!(stake_state.stake().unwrap().credits_observed, 300);
|
||||
assert_eq!(stake_account.lamports, stake_lamports);
|
||||
|
||||
// sanity-check that the activation epoch was actually last epoch
|
||||
let clock_account = context
|
||||
.banks_client
|
||||
.get_account(clock::id())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let clock: Clock = deserialize(&clock_account.data).unwrap();
|
||||
assert_eq!(
|
||||
clock.epoch,
|
||||
stake_state.delegation().unwrap().activation_epoch + 1
|
||||
);
|
||||
|
||||
// sanity-check that it's possible to merge the just-activated stake with the older stake!
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&stake_instruction::merge(
|
||||
&base_stake_address,
|
||||
&absorbed_stake_address,
|
||||
&user_keypair.pubkey(),
|
||||
),
|
||||
Some(&context.payer.pubkey()),
|
||||
&vec![&context.payer, &user_keypair],
|
||||
context.last_blockhash,
|
||||
);
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
Reference in New Issue
Block a user