switch over to passive stakes (#4295)
* add failing test * switch over to passive stakes * test multiple stakers
This commit is contained in:
parent
a0ffbf50a5
commit
87414de3e2
|
@ -2,6 +2,7 @@ pub mod bench;
|
||||||
mod cli;
|
mod cli;
|
||||||
pub mod order_book;
|
pub mod order_book;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate solana_exchange_program;
|
extern crate solana_exchange_program;
|
||||||
|
|
||||||
|
@ -46,7 +47,6 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Funding keypair: {}", identity.pubkey());
|
info!("Funding keypair: {}", identity.pubkey());
|
||||||
debug!("Exchange program name: {}", solana_exchange_program!().0);
|
|
||||||
|
|
||||||
let accounts_in_groups = batch_size * account_groups;
|
let accounts_in_groups = batch_size * account_groups;
|
||||||
const NUM_SIGNERS: u64 = 2;
|
const NUM_SIGNERS: u64 = 2;
|
||||||
|
|
|
@ -13,6 +13,7 @@ declare prints=(
|
||||||
'println!'
|
'println!'
|
||||||
'eprint!'
|
'eprint!'
|
||||||
'eprintln!'
|
'eprintln!'
|
||||||
|
'dbg!'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Parts of the tree that are expected to be print free
|
# Parts of the tree that are expected to be print free
|
||||||
|
@ -23,9 +24,13 @@ declare print_free_tree=(
|
||||||
'netutil/src'
|
'netutil/src'
|
||||||
'runtime/src'
|
'runtime/src'
|
||||||
'sdk/src'
|
'sdk/src'
|
||||||
|
'programs/vote_api/src'
|
||||||
|
'programs/vote_program/src'
|
||||||
|
'programs/stake_api/src'
|
||||||
|
'programs/stake_program/src'
|
||||||
)
|
)
|
||||||
|
|
||||||
if _ git grep --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
|
if _ git grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -34,7 +39,7 @@ fi
|
||||||
# Default::default()
|
# Default::default()
|
||||||
#
|
#
|
||||||
# Ref: https://github.com/solana-labs/solana/issues/2630
|
# Ref: https://github.com/solana-labs/solana/issues/2630
|
||||||
if _ git grep 'Default::default()' -- '*.rs'; then
|
if _ git grep -n 'Default::default()' -- '*.rs'; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -168,10 +168,9 @@ mod tests {
|
||||||
use crate::blocktree::tests::make_slot_entries;
|
use crate::blocktree::tests::make_slot_entries;
|
||||||
use crate::genesis_utils::create_genesis_block;
|
use crate::genesis_utils::create_genesis_block;
|
||||||
use crate::genesis_utils::{create_genesis_block_with_leader, BOOTSTRAP_LEADER_LAMPORTS};
|
use crate::genesis_utils::{create_genesis_block_with_leader, BOOTSTRAP_LEADER_LAMPORTS};
|
||||||
use crate::voting_keypair::tests::new_vote_account;
|
use crate::staking_utils::tests::setup_vote_and_stake_accounts;
|
||||||
use solana_runtime::bank::Bank;
|
use solana_runtime::bank::Bank;
|
||||||
use solana_runtime::epoch_schedule::{EpochSchedule, MINIMUM_SLOT_LENGTH};
|
use solana_runtime::epoch_schedule::{EpochSchedule, MINIMUM_SLOT_LENGTH};
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread::Builder;
|
use std::thread::Builder;
|
||||||
|
@ -373,25 +372,20 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_next_leader_slot_next_epoch() {
|
fn test_next_leader_slot_next_epoch() {
|
||||||
let pubkey = Pubkey::new_rand();
|
let (mut genesis_block, mint_keypair) = create_genesis_block(10_000);
|
||||||
let (mut genesis_block, mint_keypair, _voting_keypair) = create_genesis_block_with_leader(
|
|
||||||
2 * BOOTSTRAP_LEADER_LAMPORTS,
|
|
||||||
&pubkey,
|
|
||||||
BOOTSTRAP_LEADER_LAMPORTS,
|
|
||||||
);
|
|
||||||
genesis_block.epoch_warmup = false;
|
genesis_block.epoch_warmup = false;
|
||||||
|
|
||||||
let bank = Bank::new(&genesis_block);
|
let bank = Bank::new(&genesis_block);
|
||||||
let cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank));
|
let cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank));
|
||||||
let delegate_id = Pubkey::new_rand();
|
|
||||||
|
|
||||||
// Create new vote account
|
// Create new vote account
|
||||||
let new_voting_keypair = Keypair::new();
|
let node_id = Pubkey::new_rand();
|
||||||
new_vote_account(
|
let vote_id = Pubkey::new_rand();
|
||||||
&mint_keypair,
|
setup_vote_and_stake_accounts(
|
||||||
&new_voting_keypair,
|
|
||||||
&delegate_id,
|
|
||||||
&bank,
|
&bank,
|
||||||
|
&mint_keypair,
|
||||||
|
&vote_id,
|
||||||
|
&node_id,
|
||||||
BOOTSTRAP_LEADER_LAMPORTS,
|
BOOTSTRAP_LEADER_LAMPORTS,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -412,14 +406,14 @@ mod tests {
|
||||||
|
|
||||||
let schedule = cache.compute_epoch_schedule(epoch, &bank).unwrap();
|
let schedule = cache.compute_epoch_schedule(epoch, &bank).unwrap();
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
while schedule[index] != delegate_id {
|
while schedule[index] != node_id {
|
||||||
index += 1
|
index += 1;
|
||||||
|
assert_ne!(index, genesis_block.slots_per_epoch);
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_slot += index;
|
expected_slot += index;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.next_leader_slot(&delegate_id, 0, &bank, None),
|
cache.next_leader_slot(&node_id, 0, &bank, None),
|
||||||
Some(expected_slot),
|
Some(expected_slot),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,6 @@ pub mod streamer;
|
||||||
pub mod test_tx;
|
pub mod test_tx;
|
||||||
pub mod tpu;
|
pub mod tpu;
|
||||||
pub mod tvu;
|
pub mod tvu;
|
||||||
pub mod voting_keypair;
|
|
||||||
pub mod window_service;
|
pub mod window_service;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -83,6 +82,8 @@ extern crate log;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
|
|
|
@ -397,7 +397,6 @@ impl LocalCluster {
|
||||||
),
|
),
|
||||||
client.get_recent_blockhash().unwrap().0,
|
client.get_recent_blockhash().unwrap().0,
|
||||||
);
|
);
|
||||||
dbg!(vote_account_pubkey);
|
|
||||||
client
|
client
|
||||||
.retry_transfer(&from_account, &mut transaction, 5)
|
.retry_transfer(&from_account, &mut transaction, 5)
|
||||||
.expect("fund vote");
|
.expect("fund vote");
|
||||||
|
@ -407,7 +406,6 @@ impl LocalCluster {
|
||||||
|
|
||||||
let stake_account_keypair = Keypair::new();
|
let stake_account_keypair = Keypair::new();
|
||||||
let stake_account_pubkey = stake_account_keypair.pubkey();
|
let stake_account_pubkey = stake_account_keypair.pubkey();
|
||||||
dbg!(stake_account_pubkey);
|
|
||||||
let mut transaction = Transaction::new_signed_instructions(
|
let mut transaction = Transaction::new_signed_instructions(
|
||||||
&[from_account.as_ref()],
|
&[from_account.as_ref()],
|
||||||
vec![stake_instruction::create_account(
|
vec![stake_instruction::create_account(
|
||||||
|
@ -424,7 +422,6 @@ impl LocalCluster {
|
||||||
client
|
client
|
||||||
.wait_for_balance(&stake_account_pubkey, Some(amount))
|
.wait_for_balance(&stake_account_pubkey, Some(amount))
|
||||||
.expect("get balance");
|
.expect("get balance");
|
||||||
dbg!(amount);
|
|
||||||
|
|
||||||
let mut transaction = Transaction::new_signed_instructions(
|
let mut transaction = Transaction::new_signed_instructions(
|
||||||
&[from_account.as_ref(), &stake_account_keypair],
|
&[from_account.as_ref(), &stake_account_keypair],
|
||||||
|
@ -443,7 +440,6 @@ impl LocalCluster {
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
.expect("delegate stake");
|
.expect("delegate stake");
|
||||||
dbg!("delegated");
|
|
||||||
}
|
}
|
||||||
info!("Checking for vote account registration");
|
info!("Checking for vote account registration");
|
||||||
let vote_account_user_data = client.get_account_data(&vote_account_pubkey);
|
let vote_account_user_data = client.get_account_data(&vote_account_pubkey);
|
||||||
|
|
|
@ -110,15 +110,18 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::genesis_utils::{
|
use crate::genesis_utils::{
|
||||||
create_genesis_block, create_genesis_block_with_leader, BOOTSTRAP_LEADER_LAMPORTS,
|
create_genesis_block, create_genesis_block_with_leader, BOOTSTRAP_LEADER_LAMPORTS,
|
||||||
};
|
};
|
||||||
use crate::voting_keypair::tests as voting_keypair_tests;
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
|
use solana_sdk::instruction::Instruction;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
|
use solana_sdk::transaction::Transaction;
|
||||||
|
use solana_stake_api::stake_instruction;
|
||||||
|
use solana_vote_api::vote_instruction;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -148,14 +151,65 @@ pub mod tests {
|
||||||
assert_eq!(vote_account_stakes_at_epoch(&bank, 1), expected);
|
assert_eq!(vote_account_stakes_at_epoch(&bank, 1), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn setup_vote_and_stake_accounts(
|
||||||
|
bank: &Bank,
|
||||||
|
from_account: &Keypair,
|
||||||
|
vote_id: &Pubkey,
|
||||||
|
node_id: &Pubkey,
|
||||||
|
amount: u64,
|
||||||
|
) {
|
||||||
|
fn process_instructions<T: KeypairUtil>(
|
||||||
|
bank: &Bank,
|
||||||
|
keypairs: &[&T],
|
||||||
|
ixs: Vec<Instruction>,
|
||||||
|
) {
|
||||||
|
bank.process_transaction(&Transaction::new_signed_instructions(
|
||||||
|
keypairs,
|
||||||
|
ixs,
|
||||||
|
bank.last_blockhash(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
process_instructions(
|
||||||
|
bank,
|
||||||
|
&[from_account],
|
||||||
|
vote_instruction::create_account(&from_account.pubkey(), vote_id, node_id, 0, amount),
|
||||||
|
);
|
||||||
|
|
||||||
|
let stake_account_keypair = Keypair::new();
|
||||||
|
let stake_account_pubkey = stake_account_keypair.pubkey();
|
||||||
|
|
||||||
|
process_instructions(
|
||||||
|
bank,
|
||||||
|
&[from_account],
|
||||||
|
vec![stake_instruction::create_account(
|
||||||
|
&from_account.pubkey(),
|
||||||
|
&stake_account_pubkey,
|
||||||
|
amount,
|
||||||
|
)],
|
||||||
|
);
|
||||||
|
|
||||||
|
process_instructions(
|
||||||
|
bank,
|
||||||
|
&[from_account, &stake_account_keypair],
|
||||||
|
vec![stake_instruction::delegate_stake(
|
||||||
|
&from_account.pubkey(),
|
||||||
|
&stake_account_pubkey,
|
||||||
|
vote_id,
|
||||||
|
)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_epoch_stakes_and_lockouts() {
|
fn test_epoch_stakes_and_lockouts() {
|
||||||
|
let stake = 42;
|
||||||
let validator = Keypair::new();
|
let validator = Keypair::new();
|
||||||
|
|
||||||
let (genesis_block, mint_keypair) = create_genesis_block(500);
|
let (genesis_block, mint_keypair) = create_genesis_block(10_000);
|
||||||
|
|
||||||
let bank = Bank::new(&genesis_block);
|
let bank = Bank::new(&genesis_block);
|
||||||
let bank_voter = Keypair::new();
|
let vote_id = Pubkey::new_rand();
|
||||||
|
|
||||||
// Give the validator some stake but don't setup a staking account
|
// Give the validator some stake but don't setup a staking account
|
||||||
// Validator has no lamports staked, so they get filtered out. Only the bootstrap leader
|
// Validator has no lamports staked, so they get filtered out. Only the bootstrap leader
|
||||||
|
@ -165,12 +219,12 @@ pub mod tests {
|
||||||
|
|
||||||
// Make a mint vote account. Because the mint has nonzero stake, this
|
// Make a mint vote account. Because the mint has nonzero stake, this
|
||||||
// should show up in the active set
|
// should show up in the active set
|
||||||
voting_keypair_tests::new_vote_account(
|
setup_vote_and_stake_accounts(
|
||||||
&mint_keypair,
|
|
||||||
&bank_voter,
|
|
||||||
&mint_keypair.pubkey(),
|
|
||||||
&bank,
|
&bank,
|
||||||
499,
|
&mint_keypair,
|
||||||
|
&vote_id,
|
||||||
|
&mint_keypair.pubkey(),
|
||||||
|
stake,
|
||||||
);
|
);
|
||||||
|
|
||||||
// soonest slot that could be a new epoch is 1
|
// soonest slot that could be a new epoch is 1
|
||||||
|
@ -190,7 +244,7 @@ pub mod tests {
|
||||||
|
|
||||||
let result: HashSet<_> = HashSet::from_iter(epoch_stakes_and_lockouts(&bank, epoch));
|
let result: HashSet<_> = HashSet::from_iter(epoch_stakes_and_lockouts(&bank, epoch));
|
||||||
let expected: HashSet<_> =
|
let expected: HashSet<_> =
|
||||||
HashSet::from_iter(vec![(BOOTSTRAP_LEADER_LAMPORTS, None), (499, None)]);
|
HashSet::from_iter(vec![(BOOTSTRAP_LEADER_LAMPORTS, None), (stake, None)]);
|
||||||
assert_eq!(result, expected);
|
assert_eq!(result, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,167 +0,0 @@
|
||||||
//! The `vote_signer_proxy` votes on the `blockhash` of the bank at a regular cadence
|
|
||||||
|
|
||||||
use jsonrpc_core;
|
|
||||||
use solana_client::rpc_client::RpcClient;
|
|
||||||
use solana_client::rpc_request::RpcRequest;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
|
|
||||||
use solana_vote_signer::rpc::LocalVoteSigner;
|
|
||||||
use solana_vote_signer::rpc::VoteSigner;
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub struct RemoteVoteSigner {
|
|
||||||
rpc_client: RpcClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RemoteVoteSigner {
|
|
||||||
pub fn new(signer: SocketAddr) -> Self {
|
|
||||||
let rpc_client = RpcClient::new_socket(signer);
|
|
||||||
Self { rpc_client }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VoteSigner for RemoteVoteSigner {
|
|
||||||
fn register(
|
|
||||||
&self,
|
|
||||||
pubkey: &Pubkey,
|
|
||||||
sig: &Signature,
|
|
||||||
msg: &[u8],
|
|
||||||
) -> jsonrpc_core::Result<Pubkey> {
|
|
||||||
let params = json!([pubkey, sig, msg]);
|
|
||||||
let resp = self
|
|
||||||
.rpc_client
|
|
||||||
.retry_make_rpc_request(&RpcRequest::RegisterNode, Some(params), 5)
|
|
||||||
.unwrap();
|
|
||||||
let vote_account: Pubkey = serde_json::from_value(resp).unwrap();
|
|
||||||
Ok(vote_account)
|
|
||||||
}
|
|
||||||
fn sign(
|
|
||||||
&self,
|
|
||||||
pubkey: &Pubkey,
|
|
||||||
sig: &Signature,
|
|
||||||
msg: &[u8],
|
|
||||||
) -> jsonrpc_core::Result<Signature> {
|
|
||||||
let params = json!([pubkey, sig, msg]);
|
|
||||||
let resp = self
|
|
||||||
.rpc_client
|
|
||||||
.retry_make_rpc_request(&RpcRequest::SignVote, Some(params), 0)
|
|
||||||
.unwrap();
|
|
||||||
let vote_signature: Signature = serde_json::from_value(resp).unwrap();
|
|
||||||
Ok(vote_signature)
|
|
||||||
}
|
|
||||||
fn deregister(&self, pubkey: &Pubkey, sig: &Signature, msg: &[u8]) -> jsonrpc_core::Result<()> {
|
|
||||||
let params = json!([pubkey, sig, msg]);
|
|
||||||
let _resp = self
|
|
||||||
.rpc_client
|
|
||||||
.retry_make_rpc_request(&RpcRequest::DeregisterNode, Some(params), 5)
|
|
||||||
.unwrap();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeypairUtil for VotingKeypair {
|
|
||||||
/// Return a local VotingKeypair with a new keypair. Used for unit-tests.
|
|
||||||
fn new() -> Self {
|
|
||||||
Self::new_with_signer(
|
|
||||||
&Arc::new(Keypair::new()),
|
|
||||||
Box::new(LocalVoteSigner::default()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the public key of the keypair used to sign votes
|
|
||||||
fn pubkey(&self) -> Pubkey {
|
|
||||||
self.vote_account
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sign_message(&self, msg: &[u8]) -> Signature {
|
|
||||||
let sig = self.keypair.sign_message(msg);
|
|
||||||
self.signer
|
|
||||||
.sign(&self.keypair.pubkey(), &sig, &msg)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VotingKeypair {
|
|
||||||
keypair: Arc<Keypair>,
|
|
||||||
signer: Box<VoteSigner + Send + Sync>,
|
|
||||||
vote_account: Pubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VotingKeypair {
|
|
||||||
pub fn new_with_signer(keypair: &Arc<Keypair>, signer: Box<VoteSigner + Send + Sync>) -> Self {
|
|
||||||
let msg = "Registering a new node";
|
|
||||||
let sig = keypair.sign_message(msg.as_bytes());
|
|
||||||
let vote_account = signer
|
|
||||||
.register(&keypair.pubkey(), &sig, msg.as_bytes())
|
|
||||||
.unwrap();
|
|
||||||
Self {
|
|
||||||
keypair: keypair.clone(),
|
|
||||||
signer,
|
|
||||||
vote_account,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub mod tests {
|
|
||||||
use solana_runtime::bank::Bank;
|
|
||||||
use solana_sdk::instruction::Instruction;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
|
||||||
use solana_sdk::transaction::Transaction;
|
|
||||||
use solana_vote_api::vote_instruction;
|
|
||||||
use solana_vote_api::vote_state::Vote;
|
|
||||||
|
|
||||||
fn process_instructions<T: KeypairUtil>(bank: &Bank, keypairs: &[&T], ixs: Vec<Instruction>) {
|
|
||||||
let blockhash = bank.last_blockhash();
|
|
||||||
let tx = Transaction::new_signed_instructions(keypairs, ixs, blockhash);
|
|
||||||
bank.process_transaction(&tx).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_vote_account(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
voting_keypair: &Keypair,
|
|
||||||
node_id: &Pubkey,
|
|
||||||
bank: &Bank,
|
|
||||||
lamports: u64,
|
|
||||||
) {
|
|
||||||
let voting_pubkey = voting_keypair.pubkey();
|
|
||||||
let ixs = vote_instruction::create_account(
|
|
||||||
&from_keypair.pubkey(),
|
|
||||||
&voting_pubkey,
|
|
||||||
node_id,
|
|
||||||
0,
|
|
||||||
lamports,
|
|
||||||
);
|
|
||||||
process_instructions(bank, &[from_keypair], ixs);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_vote<T: KeypairUtil>(voting_keypair: &T, bank: &Bank, slot: u64) {
|
|
||||||
let ix = vote_instruction::vote(&voting_keypair.pubkey(), vec![Vote::new(slot)]);
|
|
||||||
process_instructions(bank, &[voting_keypair], vec![ix]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_vote_account_with_vote<T: KeypairUtil>(
|
|
||||||
from_keypair: &T,
|
|
||||||
voting_keypair: &T,
|
|
||||||
node_id: &Pubkey,
|
|
||||||
bank: &Bank,
|
|
||||||
lamports: u64,
|
|
||||||
slot: u64,
|
|
||||||
) {
|
|
||||||
let voting_pubkey = voting_keypair.pubkey();
|
|
||||||
let mut ixs = vote_instruction::create_account(
|
|
||||||
&from_keypair.pubkey(),
|
|
||||||
&voting_pubkey,
|
|
||||||
node_id,
|
|
||||||
0,
|
|
||||||
lamports,
|
|
||||||
);
|
|
||||||
ixs.push(vote_instruction::vote(
|
|
||||||
&voting_pubkey,
|
|
||||||
vec![Vote::new(slot)],
|
|
||||||
));
|
|
||||||
process_instructions(bank, &[from_keypair, voting_keypair], ixs);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -42,6 +42,23 @@ const STAKE_REWARD_TARGET_RATE: f64 = 0.20;
|
||||||
const STAKE_GETS_PAID_EVERY_VOTE: u64 = 200_000_000; // if numbers above (TICKS_YEAR) move, fix this
|
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
|
||||||
|
pub fn from(account: &Account) -> Option<StakeState> {
|
||||||
|
account.state().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility function, used by Stakes, tests
|
||||||
|
pub fn voter_id_from(account: &Account) -> Option<Pubkey> {
|
||||||
|
Self::from(account).and_then(|state: Self| state.voter_id())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn voter_id(&self) -> Option<Pubkey> {
|
||||||
|
match self {
|
||||||
|
StakeState::Delegate { voter_id, .. } => Some(*voter_id),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn calculate_rewards(
|
pub fn calculate_rewards(
|
||||||
credits_observed: u64,
|
credits_observed: u64,
|
||||||
stake: u64,
|
stake: u64,
|
||||||
|
@ -176,8 +193,6 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_delegate_stake() {
|
fn test_stake_delegate_stake() {
|
||||||
dbg!(std::env::var("CARGO_FOO").unwrap_or("not set".to_string()));
|
|
||||||
|
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..1000 {
|
for i in 0..1000 {
|
||||||
|
|
|
@ -9,6 +9,7 @@ use crate::blockhash_queue::BlockhashQueue;
|
||||||
use crate::epoch_schedule::EpochSchedule;
|
use crate::epoch_schedule::EpochSchedule;
|
||||||
use crate::locked_accounts_results::LockedAccountsResults;
|
use crate::locked_accounts_results::LockedAccountsResults;
|
||||||
use crate::message_processor::{MessageProcessor, ProcessInstruction};
|
use crate::message_processor::{MessageProcessor, ProcessInstruction};
|
||||||
|
use crate::stakes::Stakes;
|
||||||
use crate::status_cache::StatusCache;
|
use crate::status_cache::StatusCache;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
@ -30,39 +31,6 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
/// cache of staking information
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
pub struct Stakes {
|
|
||||||
/// vote accounts
|
|
||||||
vote_accounts: HashMap<Pubkey, (u64, Account)>,
|
|
||||||
|
|
||||||
/// stake_accounts
|
|
||||||
stake_accounts: HashMap<Pubkey, Account>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stakes {
|
|
||||||
pub fn is_stake(account: &Account) -> bool {
|
|
||||||
solana_vote_api::check_id(&account.owner) || solana_stake_api::check_id(&account.owner)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn store(&mut self, pubkey: &Pubkey, account: &Account) {
|
|
||||||
if solana_vote_api::check_id(&account.owner) {
|
|
||||||
if account.lamports != 0 {
|
|
||||||
self.vote_accounts
|
|
||||||
.insert(*pubkey, (account.lamports, account.clone()));
|
|
||||||
} else {
|
|
||||||
self.vote_accounts.remove(pubkey);
|
|
||||||
}
|
|
||||||
} else if solana_stake_api::check_id(&account.owner) {
|
|
||||||
if account.lamports != 0 {
|
|
||||||
self.stake_accounts.insert(*pubkey, account.clone());
|
|
||||||
} else {
|
|
||||||
self.stake_accounts.remove(pubkey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type BankStatusCache = StatusCache<Result<()>>;
|
type BankStatusCache = StatusCache<Result<()>>;
|
||||||
|
|
||||||
/// Manager for the state of all accounts and programs after processing its entries.
|
/// Manager for the state of all accounts and programs after processing its entries.
|
||||||
|
@ -959,15 +927,13 @@ impl Bank {
|
||||||
/// current vote accounts for this bank along with the stake
|
/// current vote accounts for this bank along with the stake
|
||||||
/// attributed to each account
|
/// attributed to each account
|
||||||
pub fn vote_accounts(&self) -> HashMap<Pubkey, (u64, Account)> {
|
pub fn vote_accounts(&self) -> HashMap<Pubkey, (u64, Account)> {
|
||||||
self.stakes.read().unwrap().vote_accounts.clone()
|
self.stakes.read().unwrap().vote_accounts().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// vote accounts for the specific epoch along with the stake
|
/// vote accounts for the specific epoch along with the stake
|
||||||
/// attributed to each account
|
/// attributed to each account
|
||||||
pub fn epoch_vote_accounts(&self, epoch: u64) -> Option<&HashMap<Pubkey, (u64, Account)>> {
|
pub fn epoch_vote_accounts(&self, epoch: u64) -> Option<&HashMap<Pubkey, (u64, Account)>> {
|
||||||
self.epoch_stakes
|
self.epoch_stakes.get(&epoch).map(Stakes::vote_accounts)
|
||||||
.get(&epoch)
|
|
||||||
.map(|stakes| &stakes.vote_accounts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// given a slot, return the epoch and offset into the epoch this slot falls
|
/// given a slot, return the epoch and offset into the epoch this slot falls
|
||||||
|
@ -1922,4 +1888,5 @@ mod tests {
|
||||||
|
|
||||||
assert!(bank.is_delta.load(Ordering::Relaxed));
|
assert!(bank.is_delta.load(Ordering::Relaxed));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub mod loader_utils;
|
||||||
pub mod locked_accounts_results;
|
pub mod locked_accounts_results;
|
||||||
pub mod message_processor;
|
pub mod message_processor;
|
||||||
mod native_loader;
|
mod native_loader;
|
||||||
|
pub mod stakes;
|
||||||
mod status_cache;
|
mod status_cache;
|
||||||
mod system_instruction_processor;
|
mod system_instruction_processor;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
//! Stakes serve as a cache of stake and vote accounts to derive
|
||||||
|
//! node stakes
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use solana_sdk::account::Account;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_stake_api::stake_state::StakeState;
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct Stakes {
|
||||||
|
/// vote accounts
|
||||||
|
vote_accounts: HashMap<Pubkey, (u64, Account)>,
|
||||||
|
|
||||||
|
/// stake_accounts
|
||||||
|
stake_accounts: HashMap<Pubkey, Account>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stakes {
|
||||||
|
// sum the stakes that point to the given voter_id
|
||||||
|
fn calculate_stake(&self, voter_id: &Pubkey) -> u64 {
|
||||||
|
self.stake_accounts
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, stake_account)| {
|
||||||
|
Some(*voter_id) == StakeState::voter_id_from(stake_account)
|
||||||
|
})
|
||||||
|
.map(|(_, stake_account)| stake_account.lamports)
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stake(account: &Account) -> bool {
|
||||||
|
solana_vote_api::check_id(&account.owner) || solana_stake_api::check_id(&account.owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store(&mut self, pubkey: &Pubkey, account: &Account) {
|
||||||
|
if solana_vote_api::check_id(&account.owner) {
|
||||||
|
if account.lamports == 0 {
|
||||||
|
self.vote_accounts.remove(pubkey);
|
||||||
|
} else {
|
||||||
|
// update the stake of this entry
|
||||||
|
let stake = self
|
||||||
|
.vote_accounts
|
||||||
|
.get(pubkey)
|
||||||
|
.map_or_else(|| self.calculate_stake(pubkey), |v| v.0);
|
||||||
|
|
||||||
|
self.vote_accounts.insert(*pubkey, (stake, account.clone()));
|
||||||
|
}
|
||||||
|
} else if solana_stake_api::check_id(&account.owner) {
|
||||||
|
// old_stake is stake lamports and voter_id from the pre-store() version
|
||||||
|
let old_stake = self.stake_accounts.get(pubkey).and_then(|old_account| {
|
||||||
|
StakeState::voter_id_from(old_account)
|
||||||
|
.map(|old_voter_id| (old_account.lamports, old_voter_id))
|
||||||
|
});
|
||||||
|
|
||||||
|
let stake =
|
||||||
|
StakeState::voter_id_from(account).map(|voter_id| (account.lamports, voter_id));
|
||||||
|
|
||||||
|
// if adjustments need to be made...
|
||||||
|
if stake != old_stake {
|
||||||
|
if let Some((old_stake, old_voter_id)) = old_stake {
|
||||||
|
self.vote_accounts
|
||||||
|
.entry(old_voter_id)
|
||||||
|
.and_modify(|e| e.0 -= old_stake);
|
||||||
|
}
|
||||||
|
if let Some((stake, voter_id)) = stake {
|
||||||
|
self.vote_accounts
|
||||||
|
.entry(voter_id)
|
||||||
|
.and_modify(|e| e.0 += stake);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.lamports == 0 {
|
||||||
|
self.stake_accounts.remove(pubkey);
|
||||||
|
} else {
|
||||||
|
self.stake_accounts.insert(*pubkey, account.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn vote_accounts(&self) -> &HashMap<Pubkey, (u64, Account)> {
|
||||||
|
&self.vote_accounts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_stake_api::stake_state;
|
||||||
|
use solana_vote_api::vote_state::{self, VoteState};
|
||||||
|
|
||||||
|
// set up some dummies for a staked node (( vote ) ( stake ))
|
||||||
|
fn create_staked_node_accounts(stake: u64) -> ((Pubkey, Account), (Pubkey, Account)) {
|
||||||
|
let vote_id = Pubkey::new_rand();
|
||||||
|
let vote_account = vote_state::create_account(&vote_id, &Pubkey::new_rand(), 0, 1);
|
||||||
|
(
|
||||||
|
(vote_id, vote_account),
|
||||||
|
create_stake_account(stake, &vote_id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add stake to a vote_id ( stake )
|
||||||
|
fn create_stake_account(stake: u64, vote_id: &Pubkey) -> (Pubkey, Account) {
|
||||||
|
(
|
||||||
|
Pubkey::new_rand(),
|
||||||
|
stake_state::create_delegate_stake_account(&vote_id, &VoteState::default(), stake),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stakes_basic() {
|
||||||
|
let mut stakes = Stakes::default();
|
||||||
|
|
||||||
|
let ((vote_id, vote_account), (stake_id, mut stake_account)) =
|
||||||
|
create_staked_node_accounts(10);
|
||||||
|
|
||||||
|
stakes.store(&vote_id, &vote_account);
|
||||||
|
stakes.store(&stake_id, &stake_account);
|
||||||
|
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
stake_account.lamports = 42;
|
||||||
|
stakes.store(&stake_id, &stake_account);
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
stake_account.lamports = 0;
|
||||||
|
stakes.store(&stake_id, &stake_account);
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stakes_vote_account_disappear_reappear() {
|
||||||
|
let mut stakes = Stakes::default();
|
||||||
|
|
||||||
|
let ((vote_id, mut vote_account), (stake_id, stake_account)) =
|
||||||
|
create_staked_node_accounts(10);
|
||||||
|
|
||||||
|
stakes.store(&vote_id, &vote_account);
|
||||||
|
stakes.store(&stake_id, &stake_account);
|
||||||
|
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
vote_account.lamports = 0;
|
||||||
|
stakes.store(&vote_id, &vote_account);
|
||||||
|
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_none());
|
||||||
|
}
|
||||||
|
vote_account.lamports = 1;
|
||||||
|
stakes.store(&vote_id, &vote_account);
|
||||||
|
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stakes_change_delegate() {
|
||||||
|
let mut stakes = Stakes::default();
|
||||||
|
|
||||||
|
let ((vote_id, vote_account), (stake_id, stake_account)) = create_staked_node_accounts(10);
|
||||||
|
|
||||||
|
let ((vote_id2, vote_account2), (_stake_id2, stake_account2)) =
|
||||||
|
create_staked_node_accounts(10);
|
||||||
|
|
||||||
|
stakes.store(&vote_id, &vote_account);
|
||||||
|
stakes.store(&vote_id2, &vote_account2);
|
||||||
|
|
||||||
|
// delegates to vote_id
|
||||||
|
stakes.store(&stake_id, &stake_account);
|
||||||
|
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 10);
|
||||||
|
assert!(vote_accounts.get(&vote_id2).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id2).unwrap().0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delegates to vote_id2
|
||||||
|
stakes.store(&stake_id, &stake_account2);
|
||||||
|
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 0);
|
||||||
|
assert!(vote_accounts.get(&vote_id2).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id2).unwrap().0, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_stakes_multiple_stakers() {
|
||||||
|
let mut stakes = Stakes::default();
|
||||||
|
|
||||||
|
let ((vote_id, vote_account), (stake_id, stake_account)) = create_staked_node_accounts(10);
|
||||||
|
|
||||||
|
let (stake_id2, stake_account2) = create_stake_account(10, &vote_id);
|
||||||
|
|
||||||
|
stakes.store(&vote_id, &vote_account);
|
||||||
|
|
||||||
|
// delegates to vote_id
|
||||||
|
stakes.store(&stake_id, &stake_account);
|
||||||
|
stakes.store(&stake_id2, &stake_account2);
|
||||||
|
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stakes_not_delegate() {
|
||||||
|
let mut stakes = Stakes::default();
|
||||||
|
|
||||||
|
let ((vote_id, vote_account), (stake_id, stake_account)) = create_staked_node_accounts(10);
|
||||||
|
|
||||||
|
stakes.store(&vote_id, &vote_account);
|
||||||
|
stakes.store(&stake_id, &stake_account);
|
||||||
|
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// not a stake account, and whacks above entry
|
||||||
|
stakes.store(&stake_id, &Account::new(1, 0, &solana_stake_api::id()));
|
||||||
|
{
|
||||||
|
let vote_accounts = stakes.vote_accounts();
|
||||||
|
assert!(vote_accounts.get(&vote_id).is_some());
|
||||||
|
assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue