From 1f0f947ed2a965549e63ee33b8613bfcfd0447ef Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Thu, 20 Jun 2019 12:22:29 -0700 Subject: [PATCH] add validator rewards pools (#4742) * add validator rewards pools * populate rewards syscall --- Cargo.lock | 1 + programs/stake_api/Cargo.toml | 1 + programs/stake_api/src/lib.rs | 1 + programs/stake_api/src/rewards_pools.rs | 67 +++++++++++++++++++ programs/stake_api/src/stake_state.rs | 1 + runtime/src/bank.rs | 88 +++++++++++++------------ runtime/src/genesis_utils.rs | 21 +++--- runtime/src/stakes.rs | 20 +++--- 8 files changed, 138 insertions(+), 62 deletions(-) create mode 100644 programs/stake_api/src/rewards_pools.rs diff --git a/Cargo.lock b/Cargo.lock index 12ebe9ef7..cab10a8c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2711,6 +2711,7 @@ version = "0.16.0" dependencies = [ "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)", + "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_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "solana-logger 0.16.0", diff --git a/programs/stake_api/Cargo.toml b/programs/stake_api/Cargo.toml index 647181ea1..a7c2cfdd2 100644 --- a/programs/stake_api/Cargo.toml +++ b/programs/stake_api/Cargo.toml @@ -11,6 +11,7 @@ edition = "2018" [dependencies] bincode = "1.1.4" log = "0.4.2" +rand = "0.6.5" serde = "1.0.92" serde_derive = "1.0.92" solana-logger = { path = "../../logger", version = "0.16.0" } diff --git a/programs/stake_api/src/lib.rs b/programs/stake_api/src/lib.rs index f89610649..30af545ca 100644 --- a/programs/stake_api/src/lib.rs +++ b/programs/stake_api/src/lib.rs @@ -1,3 +1,4 @@ +pub mod rewards_pools; pub mod stake_instruction; pub mod stake_state; diff --git a/programs/stake_api/src/rewards_pools.rs b/programs/stake_api/src/rewards_pools.rs new file mode 100644 index 000000000..01b027530 --- /dev/null +++ b/programs/stake_api/src/rewards_pools.rs @@ -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()); + } + } +} diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index 23c8df127..2409ca753 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -23,6 +23,7 @@ pub enum StakeState { /// the number of lamports each point is worth point_value: f64, }, + RewardsPool, } impl Default for StakeState { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 1d899ddc9..8ab2b08bd 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -27,15 +27,16 @@ use solana_metrics::{ use solana_sdk::account::Account; use solana_sdk::fee_calculator::FeeCalculator; 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::native_loader; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, Signature}; -use solana_sdk::syscall::current; -use solana_sdk::syscall::fees; -use solana_sdk::syscall::slot_hashes::{self, SlotHashes}; -use solana_sdk::syscall::tick_height; +use solana_sdk::syscall::{ + current, fees, rewards, + slot_hashes::{self, SlotHashes}, + tick_height, +}; use solana_sdk::system_transaction; use solana_sdk::timing::{duration_as_ms, duration_as_ns, duration_as_us, MAX_RECENT_BLOCKHASHES}; use solana_sdk::transaction::{Result, Transaction, TransactionError}; @@ -264,6 +265,8 @@ impl Default for BlockhashQueue { } } +pub const DUMMY_REPLICATOR_POINTS: u64 = 100; + impl Bank { pub fn new(genesis_block: &GenesisBlock) -> Self { Self::new_with_paths(&genesis_block, None) @@ -357,7 +360,7 @@ impl Bank { 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_fees(); self @@ -435,7 +438,7 @@ impl Bank { } // 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() { return; } @@ -450,33 +453,29 @@ impl Bank { // years_elapsed = slots_elapsed / slots/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) as u64; + let validator_rewards = + self.inflation.validator(year) * self.capitalization() as f64 * period; - // claim points and create a pool - let mining_pool = self - .stakes - .write() - .unwrap() - .create_mining_pool(epoch, validator_rewards); + let validator_points = self.stakes.write().unwrap().claim_points(); - self.store_account( - &Pubkey::new( - hashv(&[ - blockhash.as_ref(), - "StakeMiningPool".as_ref(), - &serialize(&epoch).unwrap(), - ]) - .as_ref(), - ), - &mining_pool, - ); + let replicator_rewards = + self.inflation.replicator(year) * self.capitalization() as f64 * period; - self.capitalization - .fetch_add(validator_rewards as usize, Ordering::Relaxed); - } + let replicator_points = DUMMY_REPLICATOR_POINTS; // TODO: real value for points earned last epoch + + 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 { @@ -1390,8 +1389,7 @@ mod tests { use solana_sdk::instruction::InstructionError; use solana_sdk::poh_config::PohConfig; use solana_sdk::signature::{Keypair, KeypairUtil}; - use solana_sdk::syscall::fees::Fees; - use solana_sdk::syscall::tick_height::TickHeight; + use solana_sdk::syscall::{fees::Fees, rewards::Rewards, tick_height::TickHeight}; use solana_sdk::system_instruction; use solana_sdk::system_transaction; use solana_sdk::timing::DEFAULT_TICKS_PER_SLOT; @@ -1474,6 +1472,8 @@ mod tests { } 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()... let bank1 = Bank::new_from_parent( &bank, @@ -1482,18 +1482,22 @@ mod tests { ); // verify that there's inflation 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 validator_rewards: u64 = bank1 - .stakes - .read() - .unwrap() - .mining_pools() - .map(|(_key, account)| account.lamports) - .sum(); + let rewards = bank1 + .get_account(&rewards::id()) + .map(|account| Rewards::from(&account).unwrap()) + .unwrap(); - 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] diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index ad233fcad..1270428a8 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -1,9 +1,9 @@ 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::signature::{Keypair, KeypairUtil}; use solana_sdk::system_program; -use solana_stake_api::stake_state; +use solana_stake_api; use solana_vote_api::vote_state; // The default stake placed with the bootstrap leader @@ -38,8 +38,8 @@ pub fn create_genesis_block_with_leader( bootstrap_leader_stake_lamports, ); - let genesis_block = GenesisBlock::new( - &[ + let mut builder = Builder::new() + .accounts(&[ // the mint ( mint_keypair.pubkey(), @@ -56,22 +56,23 @@ pub fn create_genesis_block_with_leader( // passive bootstrap leader stake, duplicates above temporarily ( staking_keypair.pubkey(), - stake_state::create_stake_account( + solana_stake_api::stake_state::create_stake_account( &voting_keypair.pubkey(), &vote_state, bootstrap_leader_stake_lamports, ), ), - ], - &[ + ]) + .native_instruction_processors(&[ solana_bpf_loader_program!(), solana_vote_program!(), solana_stake_program!(), - ], - ); + ]); + + builder = solana_stake_api::rewards_pools::genesis(builder); GenesisBlockInfo { - genesis_block, + genesis_block: builder.build(), mint_keypair, voting_keypair, } diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index 546817616..62d95139d 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -2,7 +2,7 @@ //! node stakes use solana_sdk::account::Account; 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 std::collections::HashMap; @@ -136,6 +136,14 @@ impl Stakes { _ => false, }) } + pub fn rewards_pools(&self) -> impl Iterator { + self.stake_accounts + .iter() + .filter(|(_key, account)| match StakeState::from(account) { + Some(StakeState::RewardsPool) => true, + _ => false, + }) + } pub fn highest_staked_node(&self) -> Option { self.vote_accounts @@ -146,7 +154,7 @@ impl Stakes { } /// currently unclaimed points - pub fn points(&mut self) -> u64 { + pub fn points(&self) -> u64 { self.points } @@ -156,14 +164,6 @@ impl Stakes { self.points = 0; 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)]