add validator rewards pools (#4742)

* add validator rewards pools

* populate rewards syscall
This commit is contained in:
Rob Walker 2019-06-20 12:22:29 -07:00 committed by GitHub
parent 6854c64a09
commit 1f0f947ed2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 138 additions and 62 deletions

1
Cargo.lock generated
View File

@ -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",

View File

@ -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" }

View File

@ -1,3 +1,4 @@
pub mod rewards_pools;
pub mod stake_instruction; pub mod stake_instruction;
pub mod stake_state; pub mod stake_state;

View File

@ -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());
}
}
}

View File

@ -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 {

View File

@ -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 =
{ self.inflation.validator(year) * self.capitalization() as f64 * period;
let validator_rewards =
(self.inflation.validator(year) * self.capitalization() as f64 * period) as u64;
// claim points and create a pool let validator_points = self.stakes.write().unwrap().claim_points();
let mining_pool = self
.stakes
.write()
.unwrap()
.create_mining_pool(epoch, validator_rewards);
self.store_account( let replicator_rewards =
&Pubkey::new( self.inflation.replicator(year) * self.capitalization() as f64 * period;
hashv(&[
blockhash.as_ref(),
"StakeMiningPool".as_ref(),
&serialize(&epoch).unwrap(),
])
.as_ref(),
),
&mining_pool,
);
self.capitalization let replicator_points = DUMMY_REPLICATOR_POINTS; // TODO: real value for points earned last epoch
.fetch_add(validator_rewards as usize, Ordering::Relaxed);
} self.store_account(
&rewards::id(),
&rewards::create_account(
1,
validator_rewards / validator_points as f64,
replicator_rewards / replicator_points as f64,
),
);
self.capitalization.fetch_add(
(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]

View File

@ -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,
} }

View File

@ -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)]