From 64962ec524c1a36b0eb08df8745da4acfe4f0c0a Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 20 Jan 2020 09:51:13 -0700 Subject: [PATCH 1/4] Split timestamp calculation into separate fn for math unit testing --- ledger/src/blockstore.rs | 41 ++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index 56ba4f7f390f06..976316f871d45b 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -1294,22 +1294,7 @@ impl Blockstore { .flat_map(|query_slot| self.get_block_timestamps(query_slot).unwrap_or_default()) .collect(); - let (stake_weighted_timestamps_sum, total_stake) = unique_timestamps - .into_iter() - .filter_map(|(vote_pubkey, (timestamp_slot, timestamp))| { - let offset = (slot - timestamp_slot) as u32 * slot_duration; - stakes - .get(&vote_pubkey) - .map(|(stake, _account)| ((timestamp as u64 + offset.as_secs()) * stake, stake)) - }) - .fold((0, 0), |(timestamps, stakes), (timestamp, stake)| { - (timestamps + timestamp, stakes + stake) - }); - if total_stake > 0 { - Some((stake_weighted_timestamps_sum / total_stake) as i64) - } else { - None - } + calculate_stake_weighted_timestamp(unique_timestamps, stakes, slot, slot_duration) } fn get_timestamp_slots( @@ -2181,6 +2166,30 @@ fn slot_has_updates(slot_meta: &SlotMeta, slot_meta_backup: &Option) - (slot_meta_backup.is_some() && slot_meta_backup.as_ref().unwrap().consumed != slot_meta.consumed)) } +fn calculate_stake_weighted_timestamp( + unique_timestamps: HashMap, + stakes: &HashMap, + slot: Slot, + slot_duration: Duration, +) -> Option { + let (stake_weighted_timestamps_sum, total_stake) = unique_timestamps + .into_iter() + .filter_map(|(vote_pubkey, (timestamp_slot, timestamp))| { + let offset = (slot - timestamp_slot) as u32 * slot_duration; + stakes + .get(&vote_pubkey) + .map(|(stake, _account)| ((timestamp as u64 + offset.as_secs()) * stake, stake)) + }) + .fold((0, 0), |(timestamps, stakes), (timestamp, stake)| { + (timestamps + timestamp, stakes + stake) + }); + if total_stake > 0 { + Some((stake_weighted_timestamps_sum / total_stake) as i64) + } else { + None + } +} + // Creates a new ledger with slot 0 full of ticks (and only ticks). // // Returns the blockhash that can be used to append entries with. From be17c0d1d3a4715d3696d308535937c3d3f20316 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 20 Jan 2020 09:51:30 -0700 Subject: [PATCH 2/4] Add failing test --- ledger/src/blockstore.rs | 53 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index 976316f871d45b..7a1e5787e102e1 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -2437,6 +2437,7 @@ pub mod tests { use solana_sdk::{ hash::{self, hash, Hash}, instruction::CompiledInstruction, + native_token::sol_to_lamports, packet::PACKET_DATA_SIZE, pubkey::Pubkey, signature::Signature, @@ -4890,6 +4891,58 @@ pub mod tests { ); } + #[test] + fn test_calculate_stake_weighted_timestamp() { + let recent_timestamp: UnixTimestamp = 1578909061; + let slot = 5; + let slot_duration = Duration::from_millis(400); + let expected_offset = (slot * slot_duration).as_secs(); + let pubkey0 = Pubkey::new_rand(); + let pubkey1 = Pubkey::new_rand(); + let pubkey2 = Pubkey::new_rand(); + let pubkey3 = Pubkey::new_rand(); + let unique_timestamps: HashMap = [ + (pubkey0, (0, recent_timestamp)), + (pubkey1, (0, recent_timestamp)), + (pubkey2, (0, recent_timestamp)), + (pubkey3, (0, recent_timestamp)), + ] + .iter() + .cloned() + .collect(); + + let stakes: HashMap = [ + ( + pubkey0, + (sol_to_lamports(3.0), Account::new(1, 0, &Pubkey::default())), + ), + ( + pubkey1, + (sol_to_lamports(3.0), Account::new(1, 0, &Pubkey::default())), + ), + ( + pubkey2, + (sol_to_lamports(3.0), Account::new(1, 0, &Pubkey::default())), + ), + ( + pubkey3, + (sol_to_lamports(3.0), Account::new(1, 0, &Pubkey::default())), + ), + ] + .iter() + .cloned() + .collect(); + assert_eq!( + calculate_stake_weighted_timestamp( + unique_timestamps, + &stakes, + slot as Slot, + slot_duration + ), + Some(recent_timestamp + expected_offset as i64) + ); // Panics at 'attempt to add with overflow' + } + #[test] fn test_persist_transaction_status() { let blockstore_path = get_tmp_ledger_path!(); From 3fdbc8ae30c3da2b1cbb43f82db0ac33cabd9341 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 20 Jan 2020 10:05:06 -0700 Subject: [PATCH 3/4] Fix failing test; also bump stakes to near expected cluster max supply --- ledger/src/blockstore.rs | 76 +++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index 7a1e5787e102e1..406433fe57f937 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -2176,12 +2176,15 @@ fn calculate_stake_weighted_timestamp( .into_iter() .filter_map(|(vote_pubkey, (timestamp_slot, timestamp))| { let offset = (slot - timestamp_slot) as u32 * slot_duration; - stakes - .get(&vote_pubkey) - .map(|(stake, _account)| ((timestamp as u64 + offset.as_secs()) * stake, stake)) + stakes.get(&vote_pubkey).map(|(stake, _account)| { + ( + (timestamp as u128 + offset.as_secs() as u128) * *stake as u128, + stake, + ) + }) }) .fold((0, 0), |(timestamps, stakes), (timestamp, stake)| { - (timestamps + timestamp, stakes + stake) + (timestamps + timestamp, stakes + *stake as u128) }); if total_stake > 0 { Some((stake_weighted_timestamps_sum / total_stake) as i64) @@ -4914,19 +4917,74 @@ pub mod tests { let stakes: HashMap = [ ( pubkey0, - (sol_to_lamports(3.0), Account::new(1, 0, &Pubkey::default())), + ( + sol_to_lamports(4_500_000_000.0), + Account::new(1, 0, &Pubkey::default()), + ), + ), + ( + pubkey1, + ( + sol_to_lamports(4_500_000_000.0), + Account::new(1, 0, &Pubkey::default()), + ), + ), + ( + pubkey2, + ( + sol_to_lamports(4_500_000_000.0), + Account::new(1, 0, &Pubkey::default()), + ), + ), + ( + pubkey3, + ( + sol_to_lamports(4_500_000_000.0), + Account::new(1, 0, &Pubkey::default()), + ), + ), + ] + .iter() + .cloned() + .collect(); + assert_eq!( + calculate_stake_weighted_timestamp( + unique_timestamps.clone(), + &stakes, + slot as Slot, + slot_duration + ), + Some(recent_timestamp + expected_offset as i64) + ); + + let stakes: HashMap = [ + ( + pubkey0, + ( + sol_to_lamports(15_000_000_000.0), + Account::new(1, 0, &Pubkey::default()), + ), ), ( pubkey1, - (sol_to_lamports(3.0), Account::new(1, 0, &Pubkey::default())), + ( + sol_to_lamports(1_000_000_000.0), + Account::new(1, 0, &Pubkey::default()), + ), ), ( pubkey2, - (sol_to_lamports(3.0), Account::new(1, 0, &Pubkey::default())), + ( + sol_to_lamports(1_000_000_000.0), + Account::new(1, 0, &Pubkey::default()), + ), ), ( pubkey3, - (sol_to_lamports(3.0), Account::new(1, 0, &Pubkey::default())), + ( + sol_to_lamports(1_000_000_000.0), + Account::new(1, 0, &Pubkey::default()), + ), ), ] .iter() @@ -4940,7 +4998,7 @@ pub mod tests { slot_duration ), Some(recent_timestamp + expected_offset as i64) - ); // Panics at 'attempt to add with overflow' + ); } #[test] From c494aacd194019a5748532c1e375ca9632a3205c Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 20 Jan 2020 12:27:52 -0700 Subject: [PATCH 4/4] Don't error on timestamp of slot 0 --- programs/vote/src/vote_state.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/programs/vote/src/vote_state.rs b/programs/vote/src/vote_state.rs index a91e5c4b905585..c1bb0f2fea1d4f 100644 --- a/programs/vote/src/vote_state.rs +++ b/programs/vote/src/vote_state.rs @@ -404,7 +404,8 @@ impl VoteState { ) -> Result<(), VoteError> { if (slot < self.last_timestamp.slot || timestamp < self.last_timestamp.timestamp) || ((slot == self.last_timestamp.slot || timestamp == self.last_timestamp.timestamp) - && BlockTimestamp { slot, timestamp } != self.last_timestamp) + && BlockTimestamp { slot, timestamp } != self.last_timestamp + && self.last_timestamp.slot != 0) { return Err(VoteError::TimestampTooOld); } @@ -1403,5 +1404,9 @@ mod tests { timestamp: timestamp + 1 } ); + + // Test initial vote + vote_state.last_timestamp = BlockTimestamp::default(); + assert_eq!(vote_state.process_timestamp(0, timestamp), Ok(())); } }