atlas_vote_interface/state/
vote_state_v3.rs1#[cfg(feature = "bincode")]
2use super::VoteStateVersions;
3#[cfg(feature = "dev-context-only-utils")]
4use arbitrary::Arbitrary;
5#[cfg(feature = "serde")]
6use serde_derive::{Deserialize, Serialize};
7#[cfg(feature = "frozen-abi")]
8use atlas_frozen_abi_macro::{frozen_abi, AbiExample};
9use {
10 super::{
11 BlockTimestamp, CircBuf, LandedVote, Lockout, VoteInit, MAX_EPOCH_CREDITS_HISTORY,
12 MAX_LOCKOUT_HISTORY, VOTE_CREDITS_GRACE_SLOTS, VOTE_CREDITS_MAXIMUM_PER_SLOT,
13 },
14 crate::{
15 authorized_voters::AuthorizedVoters, error::VoteError, state::DEFAULT_PRIOR_VOTERS_OFFSET,
16 },
17 atlas_clock::{Clock, Epoch, Slot, UnixTimestamp},
18 atlas_instruction_error::InstructionError,
19 atlas_pubkey::Pubkey,
20 atlas_rent::Rent,
21 std::{collections::VecDeque, fmt::Debug},
22};
23
24#[cfg_attr(
25 feature = "frozen-abi",
26 frozen_abi(digest = "pZqasQc6duzMYzpzU7eriHH9cMXmubuUP4NmCrkWZjt"),
27 derive(AbiExample)
28)]
29#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
30#[derive(Debug, Default, PartialEq, Eq, Clone)]
31#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
32pub struct VoteStateV3 {
33 pub node_pubkey: Pubkey,
35
36 pub authorized_withdrawer: Pubkey,
38 pub commission: u8,
41
42 pub votes: VecDeque<LandedVote>,
43
44 pub root_slot: Option<Slot>,
47
48 pub authorized_voters: AuthorizedVoters,
50
51 pub prior_voters: CircBuf<(Pubkey, Epoch, Epoch)>,
55
56 pub epoch_credits: Vec<(Epoch, u64, u64)>,
59
60 pub last_timestamp: BlockTimestamp,
62}
63
64impl VoteStateV3 {
65 pub fn new(vote_init: &VoteInit, clock: &Clock) -> Self {
66 Self {
67 node_pubkey: vote_init.node_pubkey,
68 authorized_voters: AuthorizedVoters::new(clock.epoch, vote_init.authorized_voter),
69 authorized_withdrawer: vote_init.authorized_withdrawer,
70 commission: vote_init.commission,
71 ..VoteStateV3::default()
72 }
73 }
74
75 pub fn new_rand_for_tests(node_pubkey: Pubkey, root_slot: Slot) -> Self {
76 let votes = (1..32)
77 .map(|x| LandedVote {
78 latency: 0,
79 lockout: Lockout::new_with_confirmation_count(
80 u64::from(x).saturating_add(root_slot),
81 32_u32.saturating_sub(x),
82 ),
83 })
84 .collect();
85 Self {
86 node_pubkey,
87 root_slot: Some(root_slot),
88 votes,
89 ..VoteStateV3::default()
90 }
91 }
92
93 pub fn get_authorized_voter(&self, epoch: Epoch) -> Option<Pubkey> {
94 self.authorized_voters.get_authorized_voter(epoch)
95 }
96
97 pub fn authorized_voters(&self) -> &AuthorizedVoters {
98 &self.authorized_voters
99 }
100
101 pub fn prior_voters(&mut self) -> &CircBuf<(Pubkey, Epoch, Epoch)> {
102 &self.prior_voters
103 }
104
105 pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
106 rent.minimum_balance(VoteStateV3::size_of())
107 }
108
109 pub const fn size_of() -> usize {
112 3762 }
114
115 pub fn is_uninitialized(&self) -> bool {
116 self.authorized_voters.is_empty()
117 }
118
119 #[cfg(any(target_os = "atlas", feature = "bincode"))]
120 pub fn deserialize(input: &[u8]) -> Result<Self, InstructionError> {
121 let mut vote_state = Self::default();
122 Self::deserialize_into(input, &mut vote_state)?;
123 Ok(vote_state)
124 }
125
126 #[cfg(any(target_os = "atlas", feature = "bincode"))]
134 pub fn deserialize_into(
135 input: &[u8],
136 vote_state: &mut VoteStateV3,
137 ) -> Result<(), InstructionError> {
138 use super::vote_state_deserialize;
139 vote_state_deserialize::deserialize_into(input, vote_state, Self::deserialize_into_ptr)
140 }
141
142 #[cfg(any(target_os = "atlas", feature = "bincode"))]
154 pub fn deserialize_into_uninit(
155 input: &[u8],
156 vote_state: &mut std::mem::MaybeUninit<VoteStateV3>,
157 ) -> Result<(), InstructionError> {
158 VoteStateV3::deserialize_into_ptr(input, vote_state.as_mut_ptr())
159 }
160
161 #[cfg(any(target_os = "atlas", feature = "bincode"))]
162 fn deserialize_into_ptr(
163 input: &[u8],
164 vote_state: *mut VoteStateV3,
165 ) -> Result<(), InstructionError> {
166 use super::vote_state_deserialize::deserialize_vote_state_into_v3;
167
168 let mut cursor = std::io::Cursor::new(input);
169
170 let variant = atlas_serialize_utils::cursor::read_u32(&mut cursor)?;
171 match variant {
172 0 => {
175 #[cfg(not(target_os = "atlas"))]
176 {
177 unsafe {
183 vote_state.write(
184 bincode::deserialize::<VoteStateVersions>(input)
185 .map_err(|_| InstructionError::InvalidAccountData)
186 .and_then(|versioned| versioned.try_convert_to_v3())?,
187 );
188 }
189 Ok(())
190 }
191 #[cfg(target_os = "atlas")]
192 Err(InstructionError::InvalidAccountData)
193 }
194 1 => deserialize_vote_state_into_v3(&mut cursor, vote_state, false),
196 2 => deserialize_vote_state_into_v3(&mut cursor, vote_state, true),
198 _ => Err(InstructionError::InvalidAccountData),
199 }?;
200
201 Ok(())
202 }
203
204 #[cfg(feature = "bincode")]
205 pub fn serialize(
206 versioned: &VoteStateVersions,
207 output: &mut [u8],
208 ) -> Result<(), InstructionError> {
209 bincode::serialize_into(output, versioned).map_err(|err| match *err {
210 bincode::ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall,
211 _ => InstructionError::GenericError,
212 })
213 }
214
215 pub fn contains_slot(&self, candidate_slot: Slot) -> bool {
217 self.votes
218 .binary_search_by(|vote| vote.slot().cmp(&candidate_slot))
219 .is_ok()
220 }
221
222 #[cfg(test)]
223 pub(crate) fn get_max_sized_vote_state() -> VoteStateV3 {
224 use atlas_epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET;
225 let mut authorized_voters = AuthorizedVoters::default();
226 for i in 0..=MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
227 authorized_voters.insert(i, Pubkey::new_unique());
228 }
229
230 VoteStateV3 {
231 votes: VecDeque::from(vec![LandedVote::default(); MAX_LOCKOUT_HISTORY]),
232 root_slot: Some(u64::MAX),
233 epoch_credits: vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY],
234 authorized_voters,
235 ..Self::default()
236 }
237 }
238
239 pub fn process_next_vote_slot(
240 &mut self,
241 next_vote_slot: Slot,
242 epoch: Epoch,
243 current_slot: Slot,
244 ) {
245 if self
247 .last_voted_slot()
248 .is_some_and(|last_voted_slot| next_vote_slot <= last_voted_slot)
249 {
250 return;
251 }
252
253 self.pop_expired_votes(next_vote_slot);
254
255 let landed_vote = LandedVote {
256 latency: Self::compute_vote_latency(next_vote_slot, current_slot),
257 lockout: Lockout::new(next_vote_slot),
258 };
259
260 if self.votes.len() == MAX_LOCKOUT_HISTORY {
262 let credits = self.credits_for_vote_at_index(0);
263 let landed_vote = self.votes.pop_front().unwrap();
264 self.root_slot = Some(landed_vote.slot());
265
266 self.increment_credits(epoch, credits);
267 }
268 self.votes.push_back(landed_vote);
269 self.double_lockouts();
270 }
271
272 pub fn increment_credits(&mut self, epoch: Epoch, credits: u64) {
274 if self.epoch_credits.is_empty() {
278 self.epoch_credits.push((epoch, 0, 0));
279 } else if epoch != self.epoch_credits.last().unwrap().0 {
280 let (_, credits, prev_credits) = *self.epoch_credits.last().unwrap();
281
282 if credits != prev_credits {
283 self.epoch_credits.push((epoch, credits, credits));
286 } else {
287 self.epoch_credits.last_mut().unwrap().0 = epoch;
289 }
290
291 if self.epoch_credits.len() > MAX_EPOCH_CREDITS_HISTORY {
293 self.epoch_credits.remove(0);
294 }
295 }
296
297 self.epoch_credits.last_mut().unwrap().1 =
298 self.epoch_credits.last().unwrap().1.saturating_add(credits);
299 }
300
301 pub fn compute_vote_latency(voted_for_slot: Slot, current_slot: Slot) -> u8 {
303 std::cmp::min(current_slot.saturating_sub(voted_for_slot), u8::MAX as u64) as u8
304 }
305
306 pub fn credits_for_vote_at_index(&self, index: usize) -> u64 {
308 let latency = self
309 .votes
310 .get(index)
311 .map_or(0, |landed_vote| landed_vote.latency);
312
313 if latency == 0 {
316 1
317 } else {
318 match latency.checked_sub(VOTE_CREDITS_GRACE_SLOTS) {
319 None | Some(0) => {
320 VOTE_CREDITS_MAXIMUM_PER_SLOT as u64
322 }
323
324 Some(diff) => {
325 match VOTE_CREDITS_MAXIMUM_PER_SLOT.checked_sub(diff) {
328 None | Some(0) => 1,
330
331 Some(credits) => credits as u64,
332 }
333 }
334 }
335 }
336 }
337
338 pub fn nth_recent_lockout(&self, position: usize) -> Option<&Lockout> {
339 if position < self.votes.len() {
340 let pos = self
341 .votes
342 .len()
343 .checked_sub(position)
344 .and_then(|pos| pos.checked_sub(1))?;
345 self.votes.get(pos).map(|vote| &vote.lockout)
346 } else {
347 None
348 }
349 }
350
351 pub fn last_lockout(&self) -> Option<&Lockout> {
352 self.votes.back().map(|vote| &vote.lockout)
353 }
354
355 pub fn last_voted_slot(&self) -> Option<Slot> {
356 self.last_lockout().map(|v| v.slot())
357 }
358
359 pub fn tower(&self) -> Vec<Slot> {
362 self.votes.iter().map(|v| v.slot()).collect()
363 }
364
365 pub fn current_epoch(&self) -> Epoch {
366 if self.epoch_credits.is_empty() {
367 0
368 } else {
369 self.epoch_credits.last().unwrap().0
370 }
371 }
372
373 pub fn credits(&self) -> u64 {
376 if self.epoch_credits.is_empty() {
377 0
378 } else {
379 self.epoch_credits.last().unwrap().1
380 }
381 }
382
383 pub fn epoch_credits(&self) -> &Vec<(Epoch, u64, u64)> {
389 &self.epoch_credits
390 }
391
392 pub fn set_new_authorized_voter<F>(
393 &mut self,
394 authorized_pubkey: &Pubkey,
395 current_epoch: Epoch,
396 target_epoch: Epoch,
397 verify: F,
398 ) -> Result<(), InstructionError>
399 where
400 F: Fn(Pubkey) -> Result<(), InstructionError>,
401 {
402 let epoch_authorized_voter = self.get_and_update_authorized_voter(current_epoch)?;
403 verify(epoch_authorized_voter)?;
404
405 if self.authorized_voters.contains(target_epoch) {
411 return Err(VoteError::TooSoonToReauthorize.into());
412 }
413
414 let (latest_epoch, latest_authorized_pubkey) = self
416 .authorized_voters
417 .last()
418 .ok_or(InstructionError::InvalidAccountData)?;
419
420 if latest_authorized_pubkey != authorized_pubkey {
424 let epoch_of_last_authorized_switch =
426 self.prior_voters.last().map(|range| range.2).unwrap_or(0);
427
428 if target_epoch <= *latest_epoch {
435 return Err(InstructionError::InvalidAccountData);
436 }
437
438 self.prior_voters.append((
440 *latest_authorized_pubkey,
441 epoch_of_last_authorized_switch,
442 target_epoch,
443 ));
444 }
445
446 self.authorized_voters
447 .insert(target_epoch, *authorized_pubkey);
448
449 Ok(())
450 }
451
452 pub fn get_and_update_authorized_voter(
453 &mut self,
454 current_epoch: Epoch,
455 ) -> Result<Pubkey, InstructionError> {
456 let pubkey = self
457 .authorized_voters
458 .get_and_cache_authorized_voter_for_epoch(current_epoch)
459 .ok_or(InstructionError::InvalidAccountData)?;
460 self.authorized_voters
461 .purge_authorized_voters(current_epoch);
462 Ok(pubkey)
463 }
464
465 pub fn pop_expired_votes(&mut self, next_vote_slot: Slot) {
470 while let Some(vote) = self.last_lockout() {
471 if !vote.is_locked_out_at_slot(next_vote_slot) {
472 self.votes.pop_back();
473 } else {
474 break;
475 }
476 }
477 }
478
479 pub fn double_lockouts(&mut self) {
480 let stack_depth = self.votes.len();
481 for (i, v) in self.votes.iter_mut().enumerate() {
482 if stack_depth >
485 i.checked_add(v.confirmation_count() as usize)
486 .expect("`confirmation_count` and tower_size should be bounded by `MAX_LOCKOUT_HISTORY`")
487 {
488 v.lockout.increase_confirmation_count(1);
489 }
490 }
491 }
492
493 pub fn process_timestamp(
494 &mut self,
495 slot: Slot,
496 timestamp: UnixTimestamp,
497 ) -> Result<(), VoteError> {
498 if (slot < self.last_timestamp.slot || timestamp < self.last_timestamp.timestamp)
499 || (slot == self.last_timestamp.slot
500 && BlockTimestamp { slot, timestamp } != self.last_timestamp
501 && self.last_timestamp.slot != 0)
502 {
503 return Err(VoteError::TimestampTooOld);
504 }
505 self.last_timestamp = BlockTimestamp { slot, timestamp };
506 Ok(())
507 }
508
509 pub fn is_correct_size_and_initialized(data: &[u8]) -> bool {
510 const VERSION_OFFSET: usize = 4;
511 const DEFAULT_PRIOR_VOTERS_END: usize = VERSION_OFFSET + DEFAULT_PRIOR_VOTERS_OFFSET;
512 data.len() == VoteStateV3::size_of()
513 && data[VERSION_OFFSET..DEFAULT_PRIOR_VOTERS_END] != [0; DEFAULT_PRIOR_VOTERS_OFFSET]
514 }
515}