diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 2fc7b1f93d51fb..ab88fb418fcef9 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -421,6 +421,39 @@ impl Tower { self.last_vote_tx_blockhash } + pub fn refresh_last_vote_timestamp(&mut self, heaviest_slot_on_same_fork: Slot) { + let timestamp = if let Some(last_vote_timestamp) = self.last_vote.timestamp() { + // To avoid a refreshed vote tx getting caught in deduplication filters, + // we need to update timestamp. Increment by smallest amount to avoid skewing + // the Timestamp Oracle. + last_vote_timestamp.saturating_add(1) + } else { + // If the previous vote did not send a timestamp due to clock error, + // use the last good timestamp + 1 + self.last_timestamp.timestamp.saturating_add(1) + }; + + if let Some(last_voted_slot) = self.last_vote.last_voted_slot() { + if heaviest_slot_on_same_fork <= last_voted_slot { + warn!( + "Trying to refresh timestamp for vote on {last_voted_slot} + using smaller heaviest bank {heaviest_slot_on_same_fork}" + ); + return; + } + self.last_timestamp = BlockTimestamp { + slot: last_voted_slot, + timestamp, + }; + self.last_vote.set_timestamp(Some(timestamp)); + } else { + warn!( + "Trying to refresh timestamp for last vote on heaviest bank on same fork + {heaviest_slot_on_same_fork}, but there is no vote to refresh" + ); + } + } + pub fn refresh_last_vote_tx_blockhash(&mut self, new_vote_tx_blockhash: Hash) { self.last_vote_tx_blockhash = new_vote_tx_blockhash; } @@ -2575,6 +2608,44 @@ pub mod test { assert!(tower.maybe_timestamp(3).is_none()); // slot 3 gets no timestamp } + #[test] + fn test_refresh_last_vote_timestamp() { + let mut tower = Tower::default(); + + // Tower has no vote or timestamp + tower.last_vote.set_timestamp(None); + tower.refresh_last_vote_timestamp(5); + assert_eq!(tower.last_vote.timestamp(), None); + assert_eq!(tower.last_timestamp.slot, 0); + assert_eq!(tower.last_timestamp.timestamp, 0); + + // Tower has vote no timestamp, but is greater than heaviest_bank + tower.last_vote = + VoteTransaction::from(VoteStateUpdate::from(vec![(0, 3), (1, 2), (6, 1)])); + assert_eq!(tower.last_vote.timestamp(), None); + tower.refresh_last_vote_timestamp(5); + assert_eq!(tower.last_vote.timestamp(), None); + assert_eq!(tower.last_timestamp.slot, 0); + assert_eq!(tower.last_timestamp.timestamp, 0); + + // Tower has vote with no timestamp + tower.last_vote = + VoteTransaction::from(VoteStateUpdate::from(vec![(0, 3), (1, 2), (2, 1)])); + assert_eq!(tower.last_vote.timestamp(), None); + tower.refresh_last_vote_timestamp(5); + assert_eq!(tower.last_vote.timestamp(), Some(1)); + assert_eq!(tower.last_timestamp.slot, 2); + assert_eq!(tower.last_timestamp.timestamp, 1); + + // Vote has timestamp + tower.last_vote = + VoteTransaction::from(VoteStateUpdate::from(vec![(0, 3), (1, 2), (2, 1)])); + tower.refresh_last_vote_timestamp(5); + assert_eq!(tower.last_vote.timestamp(), Some(2)); + assert_eq!(tower.last_timestamp.slot, 2); + assert_eq!(tower.last_timestamp.timestamp, 2); + } + fn run_test_load_tower_snapshot( modify_original: F, modify_serialized: G, diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index daa59adc032ada..cbbb99bc1013b3 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -2184,8 +2184,9 @@ impl ReplayStage { return; } - // TODO: check the timestamp in this vote is correct, i.e. it shouldn't - // have changed from the original timestamp of the vote. + // Update timestamp for refreshed vote + tower.refresh_last_vote_timestamp(heaviest_bank_on_same_fork.slot()); + let vote_tx = Self::generate_vote_tx( identity_keypair, heaviest_bank_on_same_fork,