genesis validators (#7235)
* genesis validators * slp1 nodes get 500SOL * no commission
This commit is contained in:
parent
e1b7f40c2b
commit
369f37a0a4
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
stakes::{create_and_add_stakes, StakerInfo},
|
||||
unlocks::UnlockInfo,
|
||||
validators::{create_and_add_validator, ValidatorInfo},
|
||||
};
|
||||
use solana_sdk::{genesis_config::GenesisConfig, native_token::sol_to_lamports};
|
||||
|
||||
|
@ -614,6 +615,21 @@ fn add_stakes(
|
|||
.sum::<u64>()
|
||||
}
|
||||
|
||||
pub const VALIDATOR_INFOS: &[ValidatorInfo] = &[ValidatorInfo {
|
||||
name: "aurel@ethereum.ro",
|
||||
node: "GeZ5PrJi9muVCJiJAaFBNGoCEdxGEqTp7L2BmT2WTTy1",
|
||||
vote: "7ZdRx2EBYoRuPfyeoNbuHodMUXcAQRcC37MUw3kP6akn",
|
||||
node_sol: 500.0,
|
||||
commission: 0,
|
||||
}];
|
||||
|
||||
fn add_validators(genesis_config: &mut GenesisConfig, validator_infos: &[ValidatorInfo]) -> u64 {
|
||||
validator_infos
|
||||
.iter()
|
||||
.map(|validator_info| create_and_add_validator(genesis_config, validator_info))
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
pub fn add_genesis_accounts(genesis_config: &mut GenesisConfig) -> u64 {
|
||||
add_stakes(
|
||||
genesis_config,
|
||||
|
@ -630,7 +646,7 @@ pub fn add_genesis_accounts(genesis_config: &mut GenesisConfig) -> u64 {
|
|||
&BATCH_THREE_STAKER_INFOS,
|
||||
&BATCH_THREE_UNLOCK_INFO,
|
||||
sol_to_lamports(1_000_000.0),
|
||||
)
|
||||
) + add_validators(genesis_config, &VALIDATOR_INFOS)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -2,6 +2,7 @@ pub mod address_generator;
|
|||
pub mod genesis_accounts;
|
||||
pub mod stakes;
|
||||
pub mod unlocks;
|
||||
pub mod validators;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ mod address_generator;
|
|||
mod genesis_accounts;
|
||||
mod stakes;
|
||||
mod unlocks;
|
||||
mod validators;
|
||||
|
||||
use crate::genesis_accounts::add_genesis_accounts;
|
||||
use clap::{crate_description, crate_name, value_t, value_t_or_exit, App, Arg, ArgMatches};
|
||||
|
|
|
@ -8,7 +8,7 @@ use solana_sdk::{
|
|||
pubkey::Pubkey, system_program, timing::years_as_slots,
|
||||
};
|
||||
use solana_stake_program::stake_state::{
|
||||
create_lockup_stake_account, get_stake_rent_exempt_reserve, Authorized, Lockup,
|
||||
create_lockup_stake_account, Authorized, Lockup, StakeState,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -52,7 +52,8 @@ pub fn create_and_add_stakes(
|
|||
|
||||
let total_lamports = sol_to_lamports(staker_info.sol);
|
||||
|
||||
let staker_rent_reserve = get_stake_rent_exempt_reserve(&genesis_config.rent).max(1);
|
||||
// staker is a system account
|
||||
let staker_rent_reserve = genesis_config.rent.minimum_balance(0).max(1);
|
||||
let staker_fees = calculate_staker_fees(genesis_config, 1.0);
|
||||
|
||||
let mut stakes_lamports = total_lamports - staker_fees;
|
||||
|
@ -61,6 +62,7 @@ pub fn create_and_add_stakes(
|
|||
// the staker account needs to be rent exempt *and* carry enough
|
||||
// lamports to cover TX fees (delegation) for one year,
|
||||
// and we support one delegation per epoch
|
||||
// a single staker may administer any number of accounts
|
||||
genesis_config
|
||||
.accounts
|
||||
.entry(authorized.staker)
|
||||
|
@ -85,7 +87,7 @@ pub fn create_and_add_stakes(
|
|||
|
||||
let mut address_generator = AddressGenerator::new(&authorized.staker, staker_info.name);
|
||||
|
||||
let stake_rent_exempt_reserve = get_stake_rent_exempt_reserve(&genesis_config.rent);
|
||||
let stake_rent_reserve = StakeState::get_rent_exempt_reserve(&genesis_config.rent);
|
||||
|
||||
for unlock in unlocks {
|
||||
let lamports = unlock.amount(stakes_lamports);
|
||||
|
@ -111,7 +113,7 @@ pub fn create_and_add_stakes(
|
|||
),
|
||||
);
|
||||
}
|
||||
if remainder <= stake_rent_exempt_reserve {
|
||||
if remainder <= stake_rent_reserve {
|
||||
genesis_config.add_account(
|
||||
address_generator.next(),
|
||||
create_lockup_stake_account(
|
||||
|
@ -171,7 +173,7 @@ mod tests {
|
|||
.iter()
|
||||
.all(|(_pubkey, account)| account.lamports <= granularity
|
||||
|| account.lamports - granularity
|
||||
<= get_stake_rent_exempt_reserve(&genesis_config.rent)));
|
||||
<= StakeState::get_rent_exempt_reserve(&genesis_config.rent)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -184,11 +186,12 @@ mod tests {
|
|||
..Rent::default()
|
||||
};
|
||||
|
||||
let reserve = get_stake_rent_exempt_reserve(&rent);
|
||||
let reserve = StakeState::get_rent_exempt_reserve(&rent);
|
||||
let staker_reserve = rent.minimum_balance(0);
|
||||
|
||||
// verify that a small remainder ends up in the last stake
|
||||
let granularity = reserve;
|
||||
let total_lamports = reserve + reserve * 2 + 1;
|
||||
let total_lamports = staker_reserve + reserve * 2 + 1;
|
||||
create_and_check_stakes(
|
||||
&mut GenesisConfig {
|
||||
rent,
|
||||
|
@ -214,7 +217,7 @@ mod tests {
|
|||
|
||||
// huge granularity doesn't blow up
|
||||
let granularity = std::u64::MAX;
|
||||
let total_lamports = reserve + reserve * 2 + 1;
|
||||
let total_lamports = staker_reserve + reserve * 2 + 1;
|
||||
create_and_check_stakes(
|
||||
&mut GenesisConfig {
|
||||
rent,
|
||||
|
@ -240,7 +243,7 @@ mod tests {
|
|||
|
||||
// exactly reserve as a remainder, reserve gets folded in
|
||||
let granularity = reserve * 3;
|
||||
let total_lamports = reserve + (granularity + reserve) * 2;
|
||||
let total_lamports = staker_reserve + (granularity + reserve) * 2;
|
||||
create_and_check_stakes(
|
||||
&mut GenesisConfig {
|
||||
rent,
|
||||
|
@ -265,7 +268,7 @@ mod tests {
|
|||
);
|
||||
// exactly reserve + 1 as a remainder, reserve + 1 gets its own stake
|
||||
let granularity = reserve * 3;
|
||||
let total_lamports = reserve + (granularity + reserve + 1) * 2;
|
||||
let total_lamports = staker_reserve + (granularity + reserve + 1) * 2;
|
||||
create_and_check_stakes(
|
||||
&mut GenesisConfig {
|
||||
rent,
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
//! validators generator
|
||||
use solana_sdk::{
|
||||
account::Account, genesis_config::GenesisConfig, native_token::sol_to_lamports, pubkey::Pubkey,
|
||||
system_program, timing::years_as_slots,
|
||||
};
|
||||
use solana_vote_program::vote_state::{self, VoteState};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ValidatorInfo {
|
||||
pub name: &'static str,
|
||||
pub node: &'static str,
|
||||
pub node_sol: f64,
|
||||
pub vote: &'static str,
|
||||
pub commission: u8,
|
||||
}
|
||||
|
||||
// the node's account needs carry enough
|
||||
// lamports to cover TX fees for voting for one year,
|
||||
// validators can vote once per slot
|
||||
fn calculate_voting_fees(genesis_config: &GenesisConfig, years: f64) -> u64 {
|
||||
genesis_config.fee_calculator.max_lamports_per_signature
|
||||
* years_as_slots(
|
||||
years,
|
||||
&genesis_config.poh_config.target_tick_duration,
|
||||
genesis_config.ticks_per_slot,
|
||||
) as u64
|
||||
}
|
||||
|
||||
/// create and add vote and node id accounts for a validator
|
||||
pub fn create_and_add_validator(
|
||||
genesis_config: &mut GenesisConfig,
|
||||
// information about this validator
|
||||
validator_info: &ValidatorInfo,
|
||||
) -> u64 {
|
||||
let node: Pubkey = validator_info.node.parse().expect("invalide node");
|
||||
let vote: Pubkey = validator_info.vote.parse().expect("invalide vote");
|
||||
let node_lamports = sol_to_lamports(validator_info.node_sol);
|
||||
|
||||
// node is the system account from which votes will be issued
|
||||
let node_rent_reserve = genesis_config.rent.minimum_balance(0).max(1);
|
||||
let node_voting_fees = calculate_voting_fees(genesis_config, 1.0);
|
||||
|
||||
let vote_rent_reserve = VoteState::get_rent_exempt_reserve(&genesis_config.rent).max(1);
|
||||
|
||||
let mut total_lamports = node_voting_fees + vote_rent_reserve + node_lamports;
|
||||
|
||||
genesis_config
|
||||
.accounts
|
||||
.entry(node)
|
||||
.or_insert_with(|| {
|
||||
total_lamports += node_rent_reserve;
|
||||
Account::new(node_rent_reserve, 0, &system_program::id())
|
||||
})
|
||||
.lamports += node_voting_fees + node_lamports;
|
||||
|
||||
assert!(
|
||||
genesis_config.accounts.get(&vote).is_none(),
|
||||
"{} is already in genesis",
|
||||
vote
|
||||
);
|
||||
|
||||
genesis_config.add_account(
|
||||
vote,
|
||||
vote_state::create_account(&vote, &node, validator_info.commission, vote_rent_reserve),
|
||||
);
|
||||
|
||||
total_lamports
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_sdk::rent::Rent;
|
||||
|
||||
fn create_and_check_validators(
|
||||
genesis_config: &mut GenesisConfig,
|
||||
validator_infos: &[ValidatorInfo],
|
||||
total_lamports: u64,
|
||||
len: usize,
|
||||
) {
|
||||
assert_eq!(
|
||||
validator_infos
|
||||
.iter()
|
||||
.map(|validator_info| create_and_add_validator(genesis_config, validator_info))
|
||||
.sum::<u64>(),
|
||||
total_lamports
|
||||
);
|
||||
assert_eq!(genesis_config.accounts.len(), len);
|
||||
assert_eq!(
|
||||
genesis_config
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|(_pubkey, account)| account.lamports)
|
||||
.sum::<u64>(),
|
||||
total_lamports,
|
||||
);
|
||||
assert!(genesis_config
|
||||
.accounts
|
||||
.iter()
|
||||
.all(|(_pubkey, account)| account.lamports
|
||||
>= genesis_config.rent.minimum_balance(0).max(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_one_validator() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 1,
|
||||
exemption_threshold: 1.0,
|
||||
..Rent::default()
|
||||
};
|
||||
let mut genesis_config = GenesisConfig {
|
||||
rent,
|
||||
..GenesisConfig::default()
|
||||
};
|
||||
|
||||
let total_lamports = VoteState::get_rent_exempt_reserve(&rent)
|
||||
+ calculate_voting_fees(&genesis_config, 1.0)
|
||||
+ rent.minimum_balance(0);
|
||||
|
||||
create_and_check_validators(
|
||||
&mut genesis_config,
|
||||
&[ValidatorInfo {
|
||||
name: "fun",
|
||||
node: "AiTDdNHW2vNtHt7PqWMHx3B8cMPRDNgc7kMiLPJM25QC", // random pubkeys
|
||||
node_sol: 0.0,
|
||||
vote: "77TQYZTHodhnxJcSuVjUvx8GYRCkykPyHtmFTFLjj1Rc",
|
||||
commission: 50,
|
||||
}],
|
||||
total_lamports,
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_one_validator_two_votes() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 1,
|
||||
exemption_threshold: 1.0,
|
||||
..Rent::default()
|
||||
};
|
||||
let mut genesis_config = GenesisConfig {
|
||||
rent,
|
||||
..GenesisConfig::default()
|
||||
};
|
||||
let total_lamports = VoteState::get_rent_exempt_reserve(&rent) * 2
|
||||
+ calculate_voting_fees(&genesis_config, 1.0) * 2 // two vote accounts
|
||||
+ rent.minimum_balance(0) // one node account
|
||||
+ sol_to_lamports(1.0); // 2nd vote account ask has SOL
|
||||
|
||||
// weird case, just wanted to verify that the duplicated node account gets double fees
|
||||
create_and_check_validators(
|
||||
&mut genesis_config,
|
||||
&[
|
||||
ValidatorInfo {
|
||||
name: "fun",
|
||||
node: "3VTm54dw8w6jTTsPH4BfoV5vo6mF985JAMtNDRYcaGFc", // random pubkeys
|
||||
node_sol: 0.0,
|
||||
vote: "GTKWbUoLw3Bv7Ld92crhyXcEk9zUu3VEKfzeuWJZdnfW",
|
||||
commission: 50,
|
||||
},
|
||||
ValidatorInfo {
|
||||
name: "unfun",
|
||||
node: "3VTm54dw8w6jTTsPH4BfoV5vo6mF985JAMtNDRYcaGFc", // random pubkeys, same node
|
||||
node_sol: 1.0,
|
||||
vote: "8XrFPRULg98kSm535kFaLV4GMnK5JQSuAymyrCHXsUcy",
|
||||
commission: 50,
|
||||
},
|
||||
],
|
||||
total_lamports,
|
||||
3,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_vote_collision() {
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 1,
|
||||
exemption_threshold: 1.0,
|
||||
..Rent::default()
|
||||
};
|
||||
let mut genesis_config = GenesisConfig {
|
||||
rent,
|
||||
..GenesisConfig::default()
|
||||
};
|
||||
|
||||
create_and_check_validators(
|
||||
&mut genesis_config,
|
||||
&[
|
||||
ValidatorInfo {
|
||||
name: "fun",
|
||||
node: "3VTm54dw8w6jTTsPH4BfoV5vo6mF985JAMtNDRYcaGFc", // random pubkeys
|
||||
node_sol: 0.0,
|
||||
vote: "GTKWbUoLw3Bv7Ld92crhyXcEk9zUu3VEKfzeuWJZdnfW",
|
||||
commission: 50,
|
||||
},
|
||||
ValidatorInfo {
|
||||
name: "unfun",
|
||||
node: "3VTm54dw8w6jTTsPH4BfoV5vo6mF985JAMtNDRYcaGFc", // random pubkeys, same node
|
||||
node_sol: 0.0,
|
||||
vote: "GTKWbUoLw3Bv7Ld92crhyXcEk9zUu3VEKfzeuWJZdnfW", // duplicate vote, bad juju
|
||||
commission: 50,
|
||||
},
|
||||
],
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -36,6 +36,10 @@ impl Default for StakeState {
|
|||
}
|
||||
|
||||
impl StakeState {
|
||||
pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
|
||||
rent.minimum_balance(std::mem::size_of::<StakeState>())
|
||||
}
|
||||
|
||||
// utility function, used by Stakes, tests
|
||||
pub fn from(account: &Account) -> Option<StakeState> {
|
||||
account.state().ok()
|
||||
|
@ -801,10 +805,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_stake_rent_exempt_reserve(rent: &Rent) -> u64 {
|
||||
rent.minimum_balance(std::mem::size_of::<StakeState>())
|
||||
}
|
||||
|
||||
// genesis investor accounts
|
||||
pub fn create_lockup_stake_account(
|
||||
authorized: &Authorized,
|
||||
|
|
|
@ -12,6 +12,7 @@ use solana_sdk::{
|
|||
hash::Hash,
|
||||
instruction::InstructionError,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
slot_hashes::SlotHash,
|
||||
sysvar::clock::Clock,
|
||||
};
|
||||
|
@ -121,6 +122,10 @@ impl VoteState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
|
||||
rent.minimum_balance(VoteState::size_of())
|
||||
}
|
||||
|
||||
pub fn size_of() -> usize {
|
||||
// Upper limit on the size of the Vote State. Equal to
|
||||
// size_of(VoteState) when votes.len() is MAX_LOCKOUT_HISTORY
|
||||
|
|
Loading…
Reference in New Issue