add validator rewards pools (#4742)
* add validator rewards pools * populate rewards syscall
This commit is contained in:
parent
6854c64a09
commit
1f0f947ed2
|
@ -2711,6 +2711,7 @@ version = "0.16.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"solana-logger 0.16.0",
|
"solana-logger 0.16.0",
|
||||||
|
|
|
@ -11,6 +11,7 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.1.4"
|
bincode = "1.1.4"
|
||||||
log = "0.4.2"
|
log = "0.4.2"
|
||||||
|
rand = "0.6.5"
|
||||||
serde = "1.0.92"
|
serde = "1.0.92"
|
||||||
serde_derive = "1.0.92"
|
serde_derive = "1.0.92"
|
||||||
solana-logger = { path = "../../logger", version = "0.16.0" }
|
solana-logger = { path = "../../logger", version = "0.16.0" }
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod rewards_pools;
|
||||||
pub mod stake_instruction;
|
pub mod stake_instruction;
|
||||||
pub mod stake_state;
|
pub mod stake_state;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
//! rewards_pools
|
||||||
|
//! * initialize genesis with rewards pools
|
||||||
|
//! * keep track of rewards
|
||||||
|
//! * own mining pools
|
||||||
|
|
||||||
|
use crate::stake_state::StakeState;
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use solana_sdk::account::Account;
|
||||||
|
use solana_sdk::genesis_block::Builder;
|
||||||
|
use solana_sdk::hash::{hash, Hash};
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
|
// base rewards pool ID
|
||||||
|
const ID: [u8; 32] = [
|
||||||
|
6, 161, 216, 23, 186, 139, 91, 88, 83, 34, 32, 112, 237, 188, 184, 153, 69, 67, 238, 112, 93,
|
||||||
|
54, 133, 142, 145, 182, 214, 15, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
solana_sdk::solana_name_id!(ID, "StakeRewards1111111111111111111111111111111");
|
||||||
|
|
||||||
|
// to cut down on collisions for redemptions, we make multiple accounts
|
||||||
|
pub const NUM_REWARDS_POOLS: usize = 256;
|
||||||
|
|
||||||
|
pub fn genesis(mut builder: Builder) -> Builder {
|
||||||
|
let mut pubkey = id();
|
||||||
|
|
||||||
|
for _i in 0..NUM_REWARDS_POOLS {
|
||||||
|
builder = builder.rewards_pool(
|
||||||
|
pubkey,
|
||||||
|
Account::new_data(std::u64::MAX, &StakeState::RewardsPool, &crate::id()).unwrap(),
|
||||||
|
);
|
||||||
|
pubkey = Pubkey::new(hash(pubkey.as_ref()).as_ref());
|
||||||
|
}
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_id() -> Pubkey {
|
||||||
|
let mut id = Hash::new(&ID);
|
||||||
|
|
||||||
|
for _i in 0..thread_rng().gen_range(0, NUM_REWARDS_POOLS) {
|
||||||
|
id = hash(id.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
Pubkey::new(id.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use solana_sdk::genesis_block::Builder;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let builder = Builder::new();
|
||||||
|
|
||||||
|
let genesis_block = genesis(builder).build();
|
||||||
|
|
||||||
|
for _i in 0..NUM_REWARDS_POOLS {
|
||||||
|
let id = random_id();
|
||||||
|
assert!(genesis_block
|
||||||
|
.rewards_pools
|
||||||
|
.iter()
|
||||||
|
.position(|x| x.0 == id)
|
||||||
|
.is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ pub enum StakeState {
|
||||||
/// the number of lamports each point is worth
|
/// the number of lamports each point is worth
|
||||||
point_value: f64,
|
point_value: f64,
|
||||||
},
|
},
|
||||||
|
RewardsPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StakeState {
|
impl Default for StakeState {
|
||||||
|
|
|
@ -27,15 +27,16 @@ use solana_metrics::{
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
use solana_sdk::fee_calculator::FeeCalculator;
|
use solana_sdk::fee_calculator::FeeCalculator;
|
||||||
use solana_sdk::genesis_block::GenesisBlock;
|
use solana_sdk::genesis_block::GenesisBlock;
|
||||||
use solana_sdk::hash::{extend_and_hash, hashv, Hash};
|
use solana_sdk::hash::{extend_and_hash, Hash};
|
||||||
use solana_sdk::inflation::Inflation;
|
use solana_sdk::inflation::Inflation;
|
||||||
use solana_sdk::native_loader;
|
use solana_sdk::native_loader;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::{Keypair, Signature};
|
use solana_sdk::signature::{Keypair, Signature};
|
||||||
use solana_sdk::syscall::current;
|
use solana_sdk::syscall::{
|
||||||
use solana_sdk::syscall::fees;
|
current, fees, rewards,
|
||||||
use solana_sdk::syscall::slot_hashes::{self, SlotHashes};
|
slot_hashes::{self, SlotHashes},
|
||||||
use solana_sdk::syscall::tick_height;
|
tick_height,
|
||||||
|
};
|
||||||
use solana_sdk::system_transaction;
|
use solana_sdk::system_transaction;
|
||||||
use solana_sdk::timing::{duration_as_ms, duration_as_ns, duration_as_us, MAX_RECENT_BLOCKHASHES};
|
use solana_sdk::timing::{duration_as_ms, duration_as_ns, duration_as_us, MAX_RECENT_BLOCKHASHES};
|
||||||
use solana_sdk::transaction::{Result, Transaction, TransactionError};
|
use solana_sdk::transaction::{Result, Transaction, TransactionError};
|
||||||
|
@ -264,6 +265,8 @@ impl Default for BlockhashQueue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const DUMMY_REPLICATOR_POINTS: u64 = 100;
|
||||||
|
|
||||||
impl Bank {
|
impl Bank {
|
||||||
pub fn new(genesis_block: &GenesisBlock) -> Self {
|
pub fn new(genesis_block: &GenesisBlock) -> Self {
|
||||||
Self::new_with_paths(&genesis_block, None)
|
Self::new_with_paths(&genesis_block, None)
|
||||||
|
@ -357,7 +360,7 @@ impl Bank {
|
||||||
self.ancestors.insert(p.slot(), i + 1);
|
self.ancestors.insert(p.slot(), i + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.update_rewards(parent.epoch(), parent.last_blockhash());
|
self.update_rewards(parent.epoch());
|
||||||
self.update_current();
|
self.update_current();
|
||||||
self.update_fees();
|
self.update_fees();
|
||||||
self
|
self
|
||||||
|
@ -435,7 +438,7 @@ impl Bank {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update reward for previous epoch
|
// update reward for previous epoch
|
||||||
fn update_rewards(&mut self, epoch: u64, blockhash: Hash) {
|
fn update_rewards(&mut self, epoch: u64) {
|
||||||
if epoch == self.epoch() {
|
if epoch == self.epoch() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -450,33 +453,29 @@ impl Bank {
|
||||||
// years_elapsed = slots_elapsed / slots/year
|
// years_elapsed = slots_elapsed / slots/year
|
||||||
let period = self.epoch_schedule.get_slots_in_epoch(epoch) as f64 / self.slots_per_year;
|
let period = self.epoch_schedule.get_slots_in_epoch(epoch) as f64 / self.slots_per_year;
|
||||||
|
|
||||||
// validators
|
|
||||||
{
|
|
||||||
let validator_rewards =
|
let validator_rewards =
|
||||||
(self.inflation.validator(year) * self.capitalization() as f64 * period) as u64;
|
self.inflation.validator(year) * self.capitalization() as f64 * period;
|
||||||
|
|
||||||
// claim points and create a pool
|
let validator_points = self.stakes.write().unwrap().claim_points();
|
||||||
let mining_pool = self
|
|
||||||
.stakes
|
let replicator_rewards =
|
||||||
.write()
|
self.inflation.replicator(year) * self.capitalization() as f64 * period;
|
||||||
.unwrap()
|
|
||||||
.create_mining_pool(epoch, validator_rewards);
|
let replicator_points = DUMMY_REPLICATOR_POINTS; // TODO: real value for points earned last epoch
|
||||||
|
|
||||||
self.store_account(
|
self.store_account(
|
||||||
&Pubkey::new(
|
&rewards::id(),
|
||||||
hashv(&[
|
&rewards::create_account(
|
||||||
blockhash.as_ref(),
|
1,
|
||||||
"StakeMiningPool".as_ref(),
|
validator_rewards / validator_points as f64,
|
||||||
&serialize(&epoch).unwrap(),
|
replicator_rewards / replicator_points as f64,
|
||||||
])
|
|
||||||
.as_ref(),
|
|
||||||
),
|
),
|
||||||
&mining_pool,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
self.capitalization
|
self.capitalization.fetch_add(
|
||||||
.fetch_add(validator_rewards as usize, Ordering::Relaxed);
|
(validator_rewards + replicator_rewards) as usize,
|
||||||
}
|
Ordering::Relaxed,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_hash(&self) -> bool {
|
fn set_hash(&self) -> bool {
|
||||||
|
@ -1390,8 +1389,7 @@ mod tests {
|
||||||
use solana_sdk::instruction::InstructionError;
|
use solana_sdk::instruction::InstructionError;
|
||||||
use solana_sdk::poh_config::PohConfig;
|
use solana_sdk::poh_config::PohConfig;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
use solana_sdk::syscall::fees::Fees;
|
use solana_sdk::syscall::{fees::Fees, rewards::Rewards, tick_height::TickHeight};
|
||||||
use solana_sdk::syscall::tick_height::TickHeight;
|
|
||||||
use solana_sdk::system_instruction;
|
use solana_sdk::system_instruction;
|
||||||
use solana_sdk::system_transaction;
|
use solana_sdk::system_transaction;
|
||||||
use solana_sdk::timing::DEFAULT_TICKS_PER_SLOT;
|
use solana_sdk::timing::DEFAULT_TICKS_PER_SLOT;
|
||||||
|
@ -1474,6 +1472,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
bank.store_account(&vote_id, &vote_account);
|
bank.store_account(&vote_id, &vote_account);
|
||||||
|
|
||||||
|
let validator_points = bank.stakes.read().unwrap().points();
|
||||||
|
|
||||||
// put a child bank in epoch 1, which calls update_rewards()...
|
// put a child bank in epoch 1, which calls update_rewards()...
|
||||||
let bank1 = Bank::new_from_parent(
|
let bank1 = Bank::new_from_parent(
|
||||||
&bank,
|
&bank,
|
||||||
|
@ -1482,18 +1482,22 @@ mod tests {
|
||||||
);
|
);
|
||||||
// verify that there's inflation
|
// verify that there's inflation
|
||||||
assert_ne!(bank1.capitalization(), bank.capitalization());
|
assert_ne!(bank1.capitalization(), bank.capitalization());
|
||||||
// verify the inflation is in rewards pools
|
|
||||||
|
// verify the inflation is represented in validator_points *
|
||||||
let inflation = bank1.capitalization() - bank.capitalization();
|
let inflation = bank1.capitalization() - bank.capitalization();
|
||||||
|
|
||||||
let validator_rewards: u64 = bank1
|
let rewards = bank1
|
||||||
.stakes
|
.get_account(&rewards::id())
|
||||||
.read()
|
.map(|account| Rewards::from(&account).unwrap())
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.mining_pools()
|
|
||||||
.map(|(_key, account)| account.lamports)
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
assert_eq!(validator_rewards, inflation);
|
assert!(
|
||||||
|
((rewards.validator_point_value * validator_points as f64
|
||||||
|
+ rewards.replicator_point_value * DUMMY_REPLICATOR_POINTS as f64) - // TODO: need replicator points per-epoch
|
||||||
|
inflation as f64)
|
||||||
|
.abs()
|
||||||
|
< 1.0 // rounding, truncating
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
use solana_sdk::genesis_block::GenesisBlock;
|
use solana_sdk::genesis_block::{Builder, GenesisBlock};
|
||||||
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::system_program;
|
use solana_sdk::system_program;
|
||||||
use solana_stake_api::stake_state;
|
use solana_stake_api;
|
||||||
use solana_vote_api::vote_state;
|
use solana_vote_api::vote_state;
|
||||||
|
|
||||||
// The default stake placed with the bootstrap leader
|
// The default stake placed with the bootstrap leader
|
||||||
|
@ -38,8 +38,8 @@ pub fn create_genesis_block_with_leader(
|
||||||
bootstrap_leader_stake_lamports,
|
bootstrap_leader_stake_lamports,
|
||||||
);
|
);
|
||||||
|
|
||||||
let genesis_block = GenesisBlock::new(
|
let mut builder = Builder::new()
|
||||||
&[
|
.accounts(&[
|
||||||
// the mint
|
// the mint
|
||||||
(
|
(
|
||||||
mint_keypair.pubkey(),
|
mint_keypair.pubkey(),
|
||||||
|
@ -56,22 +56,23 @@ pub fn create_genesis_block_with_leader(
|
||||||
// passive bootstrap leader stake, duplicates above temporarily
|
// passive bootstrap leader stake, duplicates above temporarily
|
||||||
(
|
(
|
||||||
staking_keypair.pubkey(),
|
staking_keypair.pubkey(),
|
||||||
stake_state::create_stake_account(
|
solana_stake_api::stake_state::create_stake_account(
|
||||||
&voting_keypair.pubkey(),
|
&voting_keypair.pubkey(),
|
||||||
&vote_state,
|
&vote_state,
|
||||||
bootstrap_leader_stake_lamports,
|
bootstrap_leader_stake_lamports,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
])
|
||||||
&[
|
.native_instruction_processors(&[
|
||||||
solana_bpf_loader_program!(),
|
solana_bpf_loader_program!(),
|
||||||
solana_vote_program!(),
|
solana_vote_program!(),
|
||||||
solana_stake_program!(),
|
solana_stake_program!(),
|
||||||
],
|
]);
|
||||||
);
|
|
||||||
|
builder = solana_stake_api::rewards_pools::genesis(builder);
|
||||||
|
|
||||||
GenesisBlockInfo {
|
GenesisBlockInfo {
|
||||||
genesis_block,
|
genesis_block: builder.build(),
|
||||||
mint_keypair,
|
mint_keypair,
|
||||||
voting_keypair,
|
voting_keypair,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//! node stakes
|
//! node stakes
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_stake_api::stake_state::{create_mining_pool, StakeState};
|
use solana_stake_api::stake_state::StakeState;
|
||||||
use solana_vote_api::vote_state::VoteState;
|
use solana_vote_api::vote_state::VoteState;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -136,6 +136,14 @@ impl Stakes {
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub fn rewards_pools(&self) -> impl Iterator<Item = (&Pubkey, &Account)> {
|
||||||
|
self.stake_accounts
|
||||||
|
.iter()
|
||||||
|
.filter(|(_key, account)| match StakeState::from(account) {
|
||||||
|
Some(StakeState::RewardsPool) => true,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn highest_staked_node(&self) -> Option<Pubkey> {
|
pub fn highest_staked_node(&self) -> Option<Pubkey> {
|
||||||
self.vote_accounts
|
self.vote_accounts
|
||||||
|
@ -146,7 +154,7 @@ impl Stakes {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// currently unclaimed points
|
/// currently unclaimed points
|
||||||
pub fn points(&mut self) -> u64 {
|
pub fn points(&self) -> u64 {
|
||||||
self.points
|
self.points
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,14 +164,6 @@ impl Stakes {
|
||||||
self.points = 0;
|
self.points = 0;
|
||||||
points
|
points
|
||||||
}
|
}
|
||||||
|
|
||||||
/// claims points
|
|
||||||
/// makes a pool with the lamports and points spread over those points and
|
|
||||||
pub fn create_mining_pool(&mut self, epoch: u64, lamports: u64) -> Account {
|
|
||||||
let points = self.claim_points();
|
|
||||||
|
|
||||||
create_mining_pool(lamports, epoch, lamports as f64 / points as f64)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in New Issue