getBlockTime: Fix RootedSlotIterator lowest root (#7681)
* Determine lowest_nonzero_root for purged blocktrees, and clean up slot offset math * Filter duplicate timestamp votes * Refactor deduping code
This commit is contained in:
		| @@ -1247,30 +1247,25 @@ impl Blocktree { | |||||||
|         slot_duration: Duration, |         slot_duration: Duration, | ||||||
|         stakes: &HashMap<Pubkey, (u64, Account)>, |         stakes: &HashMap<Pubkey, (u64, Account)>, | ||||||
|     ) -> Option<UnixTimestamp> { |     ) -> Option<UnixTimestamp> { | ||||||
|         let mut total_stake = 0; |         let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = self | ||||||
|         let stake_weighted_timestamps_sum: u64 = self |  | ||||||
|             .get_timestamp_slots(slot, TIMESTAMP_SLOT_INTERVAL, TIMESTAMP_SLOT_RANGE) |             .get_timestamp_slots(slot, TIMESTAMP_SLOT_INTERVAL, TIMESTAMP_SLOT_RANGE) | ||||||
|             .iter() |             .into_iter() | ||||||
|             .flat_map(|timestamp_slot| { |             .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; |                 let offset = (slot - timestamp_slot) as u32 * slot_duration; | ||||||
|                 if let Ok(timestamps) = self.get_block_timestamps(*timestamp_slot) { |                 stakes | ||||||
|                     timestamps |                     .get(&vote_pubkey) | ||||||
|                         .iter() |                     .map(|(stake, _account)| ((timestamp as u64 + offset.as_secs()) * stake, stake)) | ||||||
|                         .filter_map(|(vote_pubkey, timestamp)| { |  | ||||||
|                             stakes.get(vote_pubkey).map(|(stake, _account)| { |  | ||||||
|                                 total_stake += stake; |  | ||||||
|                                 (*timestamp as u64 + offset.as_secs()) * stake |  | ||||||
|                             }) |  | ||||||
|                         }) |  | ||||||
|                         .collect() |  | ||||||
|                 } else { |  | ||||||
|                     vec![] |  | ||||||
|                 } |  | ||||||
|             }) |             }) | ||||||
|             .sum(); |             .fold((0, 0), |(timestamps, stakes), (timestamp, stake)| { | ||||||
|  |                 (timestamps + timestamp, stakes + stake) | ||||||
|  |             }); | ||||||
|         if total_stake > 0 { |         if total_stake > 0 { | ||||||
|             let mean_timestamp: u64 = stake_weighted_timestamps_sum / total_stake; |             Some((stake_weighted_timestamps_sum / total_stake) as i64) | ||||||
|             Some(mean_timestamp as i64) |  | ||||||
|         } else { |         } else { | ||||||
|             None |             None | ||||||
|         } |         } | ||||||
| @@ -1282,10 +1277,12 @@ impl Blocktree { | |||||||
|         timestamp_interval: u64, |         timestamp_interval: u64, | ||||||
|         timestamp_sample_range: usize, |         timestamp_sample_range: usize, | ||||||
|     ) -> Vec<Slot> { |     ) -> Vec<Slot> { | ||||||
|         let rooted_slots = RootedSlotIterator::new(0, &self); |         let root_iterator = self.db.iter::<cf::Root>(IteratorMode::Start); | ||||||
|         if !self.is_root(slot) || rooted_slots.is_err() { |         if !self.is_root(slot) || root_iterator.is_err() { | ||||||
|             return vec![]; |             return vec![]; | ||||||
|         } |         } | ||||||
|  |         let lowest_nonzero_root = root_iterator.unwrap().map(|(slot, _)| slot).nth(1).unwrap(); | ||||||
|  |         let rooted_slots = RootedSlotIterator::new(lowest_nonzero_root, &self); | ||||||
|         let slots: Vec<Slot> = rooted_slots |         let slots: Vec<Slot> = rooted_slots | ||||||
|             .unwrap() |             .unwrap() | ||||||
|             .map(|(iter_slot, _)| iter_slot) |             .map(|(iter_slot, _)| iter_slot) | ||||||
| @@ -1377,14 +1374,14 @@ impl Blocktree { | |||||||
|         self.transaction_status_cf.put(index, status) |         self.transaction_status_cf.put(index, status) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn get_block_timestamps(&self, slot: Slot) -> Result<Vec<(Pubkey, UnixTimestamp)>> { |     fn get_block_timestamps(&self, slot: Slot) -> Result<Vec<(Pubkey, (Slot, UnixTimestamp))>> { | ||||||
|         let slot_entries = self.get_slot_entries(slot, 0, None)?; |         let slot_entries = self.get_slot_entries(slot, 0, None)?; | ||||||
|         Ok(slot_entries |         Ok(slot_entries | ||||||
|             .iter() |             .iter() | ||||||
|             .cloned() |             .cloned() | ||||||
|             .flat_map(|entry| entry.transactions) |             .flat_map(|entry| entry.transactions) | ||||||
|             .flat_map(|transaction| { |             .flat_map(|transaction| { | ||||||
|                 let mut timestamps: Vec<(Pubkey, UnixTimestamp)> = Vec::new(); |                 let mut timestamps: Vec<(Pubkey, (Slot, UnixTimestamp))> = Vec::new(); | ||||||
|                 for instruction in transaction.message.instructions { |                 for instruction in transaction.message.instructions { | ||||||
|                     let program_id = instruction.program_id(&transaction.message.account_keys); |                     let program_id = instruction.program_id(&transaction.message.account_keys); | ||||||
|                     if program_id == &solana_vote_program::id() { |                     if program_id == &solana_vote_program::id() { | ||||||
| @@ -1392,9 +1389,12 @@ impl Blocktree { | |||||||
|                             limited_deserialize(&instruction.data) |                             limited_deserialize(&instruction.data) | ||||||
|                         { |                         { | ||||||
|                             if let Some(timestamp) = vote.timestamp { |                             if let Some(timestamp) = vote.timestamp { | ||||||
|                                 let vote_pubkey = transaction.message.account_keys |                                 let timestamp_slot = vote.slots.iter().max(); | ||||||
|                                     [instruction.accounts[0] as usize]; |                                 if let Some(timestamp_slot) = timestamp_slot { | ||||||
|                                 timestamps.push((vote_pubkey, timestamp)); |                                     let vote_pubkey = transaction.message.account_keys | ||||||
|  |                                         [instruction.accounts[0] as usize]; | ||||||
|  |                                     timestamps.push((vote_pubkey, (*timestamp_slot, timestamp))); | ||||||
|  |                                 } | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| @@ -4495,11 +4495,11 @@ pub mod tests { | |||||||
|  |  | ||||||
|         assert_eq!( |         assert_eq!( | ||||||
|             blocktree.get_timestamp_slots(2, timestamp_interval, timestamp_sample_range), |             blocktree.get_timestamp_slots(2, timestamp_interval, timestamp_sample_range), | ||||||
|             vec![0, 1, 2] |             vec![1, 2] | ||||||
|         ); |         ); | ||||||
|         assert_eq!( |         assert_eq!( | ||||||
|             blocktree.get_timestamp_slots(3, timestamp_interval, timestamp_sample_range), |             blocktree.get_timestamp_slots(3, timestamp_interval, timestamp_sample_range), | ||||||
|             vec![0, 1, 2, 3] |             vec![1, 2, 3] | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         drop(blocktree); |         drop(blocktree); | ||||||
| @@ -4535,11 +4535,11 @@ pub mod tests { | |||||||
|  |  | ||||||
|         assert_eq!( |         assert_eq!( | ||||||
|             blocktree.get_timestamp_slots(2, timestamp_interval, timestamp_sample_range), |             blocktree.get_timestamp_slots(2, timestamp_interval, timestamp_sample_range), | ||||||
|             vec![0, 1, 2] |             vec![1, 2] | ||||||
|         ); |         ); | ||||||
|         assert_eq!( |         assert_eq!( | ||||||
|             blocktree.get_timestamp_slots(8, timestamp_interval, timestamp_sample_range), |             blocktree.get_timestamp_slots(8, timestamp_interval, timestamp_sample_range), | ||||||
|             vec![0, 1, 2, 3, 4] |             vec![1, 2, 3, 4, 5] | ||||||
|         ); |         ); | ||||||
|         assert_eq!( |         assert_eq!( | ||||||
|             blocktree.get_timestamp_slots(13, timestamp_interval, timestamp_sample_range), |             blocktree.get_timestamp_slots(13, timestamp_interval, timestamp_sample_range), | ||||||
| @@ -4659,14 +4659,14 @@ pub mod tests { | |||||||
|     fn test_get_block_timestamps() { |     fn test_get_block_timestamps() { | ||||||
|         let vote_keypairs: Vec<Keypair> = (0..6).map(|_| Keypair::new()).collect(); |         let vote_keypairs: Vec<Keypair> = (0..6).map(|_| Keypair::new()).collect(); | ||||||
|         let base_timestamp = 1576183541; |         let base_timestamp = 1576183541; | ||||||
|         let mut expected_timestamps: Vec<(Pubkey, UnixTimestamp)> = Vec::new(); |         let mut expected_timestamps: Vec<(Pubkey, (Slot, UnixTimestamp))> = Vec::new(); | ||||||
|  |  | ||||||
|         // Populate slot 1 with vote transactions, some of which have timestamps |         // Populate slot 1 with vote transactions, some of which have timestamps | ||||||
|         let mut vote_entries: Vec<Entry> = Vec::new(); |         let mut vote_entries: Vec<Entry> = Vec::new(); | ||||||
|         for (i, keypair) in vote_keypairs.iter().enumerate() { |         for (i, keypair) in vote_keypairs.iter().enumerate() { | ||||||
|             let timestamp = if i % 2 == 0 { |             let timestamp = if i % 2 == 0 { | ||||||
|                 let unique_timestamp = base_timestamp + i as i64; |                 let unique_timestamp = base_timestamp + i as i64; | ||||||
|                 expected_timestamps.push((keypair.pubkey(), unique_timestamp)); |                 expected_timestamps.push((keypair.pubkey(), (1, unique_timestamp))); | ||||||
|                 Some(unique_timestamp) |                 Some(unique_timestamp) | ||||||
|             } else { |             } else { | ||||||
|                 None |                 None | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user