1use crate::{
2 CycleInfo, CycleInfoDeserializer, CycleInfoSerializer, DeferredCreditDeserializer,
3 DeferredCreditSerializer, DeferredCredits, PoSChanges, PoSConfig, PosError, PosResult,
4 ProductionStats, SelectorController,
5};
6use bitvec::vec::BitVec;
7use massa_db_exports::{
8 DBBatch, MassaDirection, MassaIteratorMode, ShareableMassaDBController,
9 CYCLE_HISTORY_DESER_ERROR, CYCLE_HISTORY_PREFIX, CYCLE_HISTORY_SER_ERROR,
10 DEFERRED_CREDITS_DESER_ERROR, DEFERRED_CREDITS_PREFIX, DEFERRED_CREDITS_SER_ERROR, STATE_CF,
11};
12use massa_hash::{Hash, HashXof, HASH_XOF_SIZE_BYTES};
13use massa_ledger_exports::LedgerController;
14use massa_models::{
15 address::Address, amount::Amount, config::LEDGER_ENTRY_BASE_COST, prehash::PreHashMap,
16 slot::Slot,
17};
18use massa_serialization::{
19 buf_to_array_ctr, DeserializeError, Deserializer, Serializer, U64VarIntSerializer,
20};
21use nom::AsBytes;
22use std::{
23 collections::{BTreeMap, VecDeque},
24 ops::{
25 Bound::{Excluded, Included, Unbounded},
26 RangeBounds,
27 },
28 path::PathBuf,
29};
30use tracing::{debug, warn};
31
32const COMPLETE_IDENT: u8 = 0u8;
34const RNG_SEED_IDENT: u8 = 1u8;
35const FINAL_STATE_HASH_SNAPSHOT_IDENT: u8 = 2u8;
36const ROLL_COUNT_IDENT: u8 = 3u8;
37const PROD_STATS_IDENT: u8 = 4u8;
38
39const PROD_STATS_FAIL_IDENT: u8 = 0u8;
41const PROD_STATS_SUCCESS_IDENT: u8 = 1u8;
42
43#[macro_export]
45macro_rules! complete_key {
46 ($cycle_prefix:expr) => {
47 [&$cycle_prefix[..], &[COMPLETE_IDENT]].concat()
48 };
49}
50
51#[macro_export]
53macro_rules! rng_seed_key {
54 ($cycle_prefix:expr) => {
55 [&$cycle_prefix[..], &[RNG_SEED_IDENT]].concat()
56 };
57}
58
59#[macro_export]
61macro_rules! final_state_hash_snapshot_key {
62 ($cycle_prefix:expr) => {
63 [&$cycle_prefix[..], &[FINAL_STATE_HASH_SNAPSHOT_IDENT]].concat()
64 };
65}
66
67#[macro_export]
69macro_rules! roll_count_prefix {
70 ($cycle_prefix:expr) => {
71 [&$cycle_prefix[..], &[ROLL_COUNT_IDENT]].concat()
72 };
73}
74
75#[macro_export]
77macro_rules! roll_count_key {
78 ($cycle_prefix:expr, $addr:expr) => {
79 [
80 &$cycle_prefix[..],
81 &[ROLL_COUNT_IDENT],
82 &$addr.to_prefixed_bytes()[..],
83 ]
84 .concat()
85 };
86}
87
88#[macro_export]
90macro_rules! prod_stats_prefix {
91 ($cycle_prefix:expr) => {
92 [&$cycle_prefix[..], &[PROD_STATS_IDENT]].concat()
93 };
94}
95
96#[macro_export]
98macro_rules! prod_stats_fail_key {
99 ($cycle_prefix:expr, $addr:expr) => {
100 [
101 &$cycle_prefix[..],
102 &[PROD_STATS_IDENT],
103 &$addr.to_prefixed_bytes()[..],
104 &[PROD_STATS_FAIL_IDENT],
105 ]
106 .concat()
107 };
108}
109
110#[macro_export]
112macro_rules! prod_stats_success_key {
113 ($cycle_prefix:expr, $addr:expr) => {
114 [
115 &$cycle_prefix[..],
116 &[PROD_STATS_IDENT],
117 &$addr.to_prefixed_bytes()[..],
118 &[PROD_STATS_SUCCESS_IDENT],
119 ]
120 .concat()
121 };
122}
123
124#[macro_export]
126macro_rules! deferred_credits_key {
127 ($id:expr) => {
128 [&DEFERRED_CREDITS_PREFIX.as_bytes(), &$id[..]].concat()
129 };
130}
131
132#[derive(Clone)]
133pub struct PoSFinalState {
135 pub config: PoSConfig,
137 pub db: ShareableMassaDBController,
139 pub cycle_history_cache: VecDeque<(u64, bool)>,
141 pub rng_seed_cache: Option<(u64, BitVec<u8>)>,
143 pub selector: Box<dyn SelectorController>,
145 pub initial_rolls: BTreeMap<Address, u64>,
147 pub initial_seeds: Vec<Hash>,
149 pub credits_serializer: DeferredCreditSerializer,
151 pub credits_deserializer: DeferredCreditDeserializer,
153 pub cycle_info_serializer: CycleInfoSerializer,
155 pub cycle_info_deserializer: CycleInfoDeserializer,
157}
158
159impl PoSFinalState {
160 fn slots_per_cycle(&self) -> usize {
162 self.config
163 .periods_per_cycle
164 .saturating_mul(self.config.thread_count as u64)
165 .try_into()
166 .expect("slots_per_cycle overflow")
167 }
168
169 pub fn new(
171 config: PoSConfig,
172 initial_seed_string: &str,
173 initial_rolls_path: &PathBuf,
174 selector: Box<dyn SelectorController>,
175 db: ShareableMassaDBController,
176 ) -> Result<Self, PosError> {
177 let initial_rolls = serde_json::from_str::<BTreeMap<Address, u64>>(
179 &std::fs::read_to_string(initial_rolls_path).map_err(|err| {
180 PosError::RollsFileLoadingError(format!("error while deserializing: {}", err))
181 })?,
182 )
183 .map_err(|err| PosError::RollsFileLoadingError(format!("error opening file: {}", err)))?;
184
185 let init_seed = Hash::compute_from(initial_seed_string.as_bytes());
187 let initial_seeds = vec![Hash::compute_from(init_seed.to_bytes()), init_seed];
188
189 let cycle_info_deserializer =
190 CycleInfoDeserializer::new(config.max_rolls_length, config.max_production_stats_length);
191
192 let pos_state = Self {
193 config: config.clone(),
194 db,
195 cycle_history_cache: Default::default(),
196 rng_seed_cache: None,
197 selector,
198 initial_rolls,
199 initial_seeds,
200 credits_serializer: DeferredCreditSerializer::new(),
201 credits_deserializer: DeferredCreditDeserializer::new(config.max_credit_length),
202 cycle_info_serializer: CycleInfoSerializer::new(),
203 cycle_info_deserializer,
204 };
205
206 Ok(pos_state)
207 }
208
209 pub fn load_initial_deferred_credits(
216 &mut self,
217 batch: &mut DBBatch,
218 ledger: &dyn LedgerController,
219 ) -> Result<(), PosError> {
220 let Some(initial_deferred_credits_path) = &self.config.initial_deferred_credits_path else {
221 return Ok(());
222 };
223
224 use serde::Deserialize;
225 #[derive(Deserialize)]
226 struct AddressInitialDeferredCredits {
227 slot: Slot,
228 amount: Amount,
229 }
230
231 let initial_deferred_credits =
232 serde_json::from_str::<PreHashMap<Address, Vec<AddressInitialDeferredCredits>>>(
233 &std::fs::read_to_string(initial_deferred_credits_path).map_err(|err| {
234 PosError::DeferredCreditsFileLoadingError(format!(
235 "error while deserializing initial deferred credits file {}: {}",
236 initial_deferred_credits_path.display(),
237 err
238 ))
239 })?,
240 )
241 .map_err(|err| {
242 PosError::DeferredCreditsFileLoadingError(format!(
243 "error loading initial deferred credits file {}: {}",
244 initial_deferred_credits_path.display(),
245 err
246 ))
247 })?;
248
249 for (address, deferred_credits) in initial_deferred_credits {
250 let address_is_sc = matches!(address, Address::SC(..));
251 let address_exists = ledger.entry_exists(&address);
252 for AddressInitialDeferredCredits { slot, amount } in deferred_credits {
253 if address_is_sc {
254 warn!(
255 "deferred credits for address {} with amount {} will not execute because it is a SC address",
256 address, amount
257 );
258 } else if !address_exists && amount < LEDGER_ENTRY_BASE_COST {
259 warn!(
260 "deferred credits for address {} with amount {} will likely not execute because the address does not exist in the ledger and the amount is below the ledger entry base cost",
261 address, amount
262 );
263 }
264
265 self.put_deferred_credits_entry(&slot, &address, &amount, batch);
268 }
269 }
270
271 Ok(())
272 }
273
274 pub fn recompute_pos_state_caches(&mut self) {
276 self.cycle_history_cache = self.get_cycle_history_cycles().into();
277
278 if let Some((cycle, _)) = self.cycle_history_cache.back() {
279 self.rng_seed_cache = self
280 .get_cycle_history_rng_seed(*cycle)
281 .map(|rng_seed| (*cycle, rng_seed));
282 } else {
283 self.rng_seed_cache = None;
284 }
285 }
286
287 pub fn validate_selector_history(&self) -> PosResult<()> {
293 for (cycle, complete) in self.get_cycle_history_cycles() {
294 if !complete {
295 continue;
296 }
297 if self.get_cycle_history_rng_seed(cycle).is_none() {
298 return Err(PosError::ContainerInconsistency(format!(
299 "complete cycle {} is missing RNG seed required for selector feeding",
300 cycle
301 )));
302 }
303 if self
304 .get_cycle_history_final_state_hash_snapshot(cycle)
305 .is_none()
306 {
307 return Err(PosError::ContainerInconsistency(format!(
308 "complete cycle {} is missing final state hash snapshot required for selector feeding",
309 cycle
310 )));
311 }
312 }
313 Ok(())
314 }
315
316 pub fn reset(&mut self) {
320 let mut db = self.db.write();
321 db.delete_prefix(CYCLE_HISTORY_PREFIX, STATE_CF, None);
322 db.delete_prefix(DEFERRED_CREDITS_PREFIX, STATE_CF, None);
323 self.cycle_history_cache = Default::default();
324 self.rng_seed_cache = None;
325 }
326
327 pub fn create_initial_cycle(&mut self, batch: &mut DBBatch) {
331 let mut rng_seed = BitVec::with_capacity(self.slots_per_cycle());
332 rng_seed.extend(vec![false; self.config.thread_count as usize]);
333
334 self.put_new_cycle_info(
335 &CycleInfo::new(
336 0,
337 false,
338 self.initial_rolls.clone(),
339 rng_seed,
340 PreHashMap::default(),
341 ),
342 batch,
343 );
344 }
345
346 pub fn create_new_cycle_from_last(
360 &mut self,
361 last_cycle_info: &CycleInfo,
362 first_slot: Slot,
363 last_slot: Slot,
364 batch: &mut DBBatch,
365 ) -> Result<(), PosError> {
366 let cycle = last_slot.get_cycle(self.config.periods_per_cycle);
367
368 let continuing_same_cycle = last_cycle_info.cycle == cycle
372 && !first_slot.is_first_of_cycle(self.config.periods_per_cycle);
373
374 let (mut rng_seed, production_stats) = if continuing_same_cycle {
375 (
376 last_cycle_info.rng_seed.clone(),
377 last_cycle_info.production_stats.clone(),
378 )
379 } else {
380 (
381 BitVec::with_capacity(self.slots_per_cycle()),
382 PreHashMap::default(),
383 )
384 };
385
386 let num_slots = match last_slot.slots_since(&first_slot, self.config.thread_count) {
387 Ok(slots_since) => slots_since.saturating_add(1),
388 Err(_) => return Ok(()),
390 };
391
392 let slots_per_cycle = self.slots_per_cycle();
393 if rng_seed.len() > slots_per_cycle {
396 return Err(PosError::ContainerInconsistency(format!(
397 "RNG seed for cycle {} exceeds slots_per_cycle: {} > {}",
398 cycle,
399 rng_seed.len(),
400 slots_per_cycle
401 )));
402 }
403 let to_add = (num_slots as usize).min(slots_per_cycle.saturating_sub(rng_seed.len()));
405 rng_seed.extend(vec![false; to_add]);
406
407 let complete =
408 last_slot.is_last_of_cycle(self.config.periods_per_cycle, self.config.thread_count);
409 if rng_seed.len() > slots_per_cycle || (complete && rng_seed.len() != slots_per_cycle) {
410 return Err(PosError::ContainerInconsistency(format!(
411 "invalid RNG seed length {} for cycle {} (slots_per_cycle {}, complete={})",
412 rng_seed.len(),
413 cycle,
414 slots_per_cycle,
415 complete
416 )));
417 }
418
419 self.put_new_cycle_info(
420 &CycleInfo::new(
421 cycle,
422 complete,
423 last_cycle_info.roll_counts.clone(),
424 rng_seed,
425 production_stats,
426 ),
427 batch,
428 );
429
430 Ok(())
431 }
432
433 pub fn delete_cycle_info(&mut self, cycle: u64, batch: &mut DBBatch) {
435 let db = self.db.read();
436
437 let prefix = self.cycle_history_cycle_prefix(cycle);
438
439 for (serialized_key, _) in db.prefix_iterator_cf(STATE_CF, &prefix) {
440 if !serialized_key.starts_with(prefix.as_bytes()) {
441 break;
442 }
443 db.delete_key(batch, serialized_key.to_vec());
444 }
445 }
446
447 pub fn compute_initial_draws(&mut self) -> PosResult<()> {
450 self.validate_selector_history()?;
452
453 let history_starts_late = self
455 .cycle_history_cache
456 .front()
457 .map(|c_info| c_info.0 > 0)
458 .unwrap_or(false);
459
460 let mut max_cycle = None;
461
462 if !history_starts_late {
464 for draw_cycle in 0u64..=1 {
465 self.feed_selector(draw_cycle)?;
466 max_cycle = Some(draw_cycle);
467 }
468 }
469
470 for (idx, hist_item) in self.cycle_history_cache.iter().enumerate() {
472 if !hist_item.1 {
473 break;
474 }
475 if history_starts_late && idx == 0 {
476 continue;
479 }
480 let draw_cycle = hist_item.0.checked_add(2).ok_or_else(|| {
481 PosError::OverflowError("cycle overflow in give_selector_controller".into())
482 })?;
483 self.feed_selector(draw_cycle)?;
484 max_cycle = Some(draw_cycle);
485 }
486
487 if let Some(wait_cycle) = max_cycle {
489 self.selector.as_mut().wait_for_draws(wait_cycle)?;
490 }
491 Ok(())
492 }
493
494 pub fn apply_changes_to_batch(
514 &mut self,
515 changes: PoSChanges,
516 slot: Slot,
517 feed_selector: bool,
518 batch: &mut DBBatch,
519 ) -> PosResult<()> {
520 let slots_per_cycle = self.slots_per_cycle();
521
522 let cycle = slot.get_cycle(self.config.periods_per_cycle);
524
525 if let Some(info) = self.cycle_history_cache.back() {
530 if cycle == info.0 && !info.1 {
531 } else if info.0.checked_add(1) == Some(cycle) && info.1 {
533 if self
538 .get_cycle_history_final_state_hash_snapshot(info.0)
539 .is_none()
540 {
541 return Err(PosError::ContainerInconsistency(format!(
542 "complete cycle {} is missing final state hash snapshot before starting cycle {}",
543 info.0, cycle
544 )));
545 }
546
547 let roll_counts = self.get_all_roll_counts(info.0);
548 self.put_new_cycle_info(
549 &CycleInfo::new(
550 cycle,
551 false,
552 roll_counts,
553 BitVec::with_capacity(slots_per_cycle),
554 PreHashMap::default(),
555 ),
556 batch,
557 );
558 while self.cycle_history_cache.len() > self.config.cycle_history_length {
559 if let Some((old_cycle, _)) = self.cycle_history_cache.pop_front() {
560 self.delete_cycle_info(old_cycle, batch);
561 }
562 }
563 } else {
564 return Err(PosError::OverflowError(
565 "invalid cycle sequence in PoS final state".into(),
566 ));
567 }
568 } else {
569 return Err(PosError::ContainerInconsistency(
570 "PoS history should never be empty here".into(),
571 ));
572 }
573
574 let complete: bool =
575 slot.is_last_of_cycle(self.config.periods_per_cycle, self.config.thread_count);
576
577 let mut rng_seed = self.get_cycle_history_rng_seed(cycle).ok_or_else(|| {
581 PosError::ContainerInconsistency(format!("missing RNG seed for cycle {}", cycle))
582 })?;
583 if rng_seed.len() > slots_per_cycle {
585 return Err(PosError::ContainerInconsistency(format!(
586 "RNG seed for cycle {} exceeds slots_per_cycle: {} > {}",
587 cycle,
588 rng_seed.len(),
589 slots_per_cycle
590 )));
591 }
592 rng_seed.extend(changes.seed_bits);
593 if rng_seed.len() > slots_per_cycle || (complete && rng_seed.len() != slots_per_cycle) {
594 return Err(PosError::ContainerInconsistency(format!(
595 "cycle {} RNG seed length {} invalid (slots_per_cycle {}, complete={})",
596 cycle,
597 rng_seed.len(),
598 slots_per_cycle,
599 complete
600 )));
601 }
602
603 self.put_cycle_history_complete(cycle, complete, batch);
604 self.put_cycle_history_rng_seed(cycle, rng_seed, batch);
605
606 for (addr, roll_count) in changes.roll_changes {
608 self.put_cycle_history_address_entry(cycle, &addr, Some(&roll_count), None, batch);
609 }
610
611 for (addr, stats) in changes.production_stats {
613 if let Some(prev_production_stats) = self.get_production_stats_for_address(cycle, &addr)
614 {
615 let mut new_production_stats = prev_production_stats;
616 new_production_stats.extend(&stats);
617 self.put_cycle_history_address_entry(
618 cycle,
619 &addr,
620 None,
621 Some(&new_production_stats),
622 batch,
623 );
624 } else {
625 self.put_cycle_history_address_entry(cycle, &addr, None, Some(&stats), batch);
626 }
627 }
628
629 for (slot, credits) in changes.deferred_credits.credits.iter() {
631 for (address, amount) in credits.iter() {
632 self.put_deferred_credits_entry(slot, address, amount, batch);
633 }
634 }
635
636 debug!(
640 "After slot {} PoS cycle list is {:?}",
641 slot, self.cycle_history_cache
642 );
643 if complete && feed_selector {
644 self.feed_selector(cycle.checked_add(2).ok_or_else(|| {
645 PosError::OverflowError("cycle overflow when feeding selector".into())
646 })?)
647 } else {
648 Ok(())
649 }
650 }
651
652 pub fn feed_selector(&self, draw_cycle: u64) -> PosResult<()> {
654 let (lookback_rolls, lookback_state_hash) = match draw_cycle.checked_sub(3) {
657 Some(c) => {
659 let index = self
660 .get_cycle_index(c)
661 .ok_or(PosError::CycleUnavailable(c))?;
662 let cycle_info = &self.cycle_history_cache[index];
663 if !cycle_info.1 {
664 return Err(PosError::CycleUnfinished(c));
665 }
666 let state_hash = self
670 .get_cycle_history_final_state_hash_snapshot(cycle_info.0)
671 .ok_or_else(|| {
672 PosError::ContainerInconsistency(format!(
673 "complete cycle {} is missing final state hash snapshot required for selector feeding",
674 c
675 ))
676 })?;
677 (self.get_all_roll_counts(cycle_info.0), Some(state_hash))
678 }
679 None => (self.initial_rolls.clone(), None),
681 };
682
683 let lookback_seed = match draw_cycle.checked_sub(2) {
685 Some(c) => {
687 let index = self
688 .get_cycle_index(c)
689 .ok_or(PosError::CycleUnavailable(c))?;
690 let cycle_info = &self.cycle_history_cache[index];
691 if !cycle_info.1 {
692 return Err(PosError::CycleUnfinished(c));
693 }
694 let u64_ser = U64VarIntSerializer::new();
695 let mut seed = Vec::new();
696 u64_ser.serialize(&c, &mut seed).unwrap();
697 let rng_seed = self
698 .get_cycle_history_rng_seed(cycle_info.0)
699 .ok_or_else(|| {
700 PosError::ContainerInconsistency(format!(
701 "complete cycle {} is missing RNG seed required for selector feeding",
702 c
703 ))
704 })?;
705 seed.extend(rng_seed.into_vec());
706 if let Some(lookback_state_hash) = lookback_state_hash {
707 seed.extend(lookback_state_hash.to_bytes());
708 }
709 Hash::compute_from(&seed)
710 }
711 None => self.initial_seeds[draw_cycle as usize],
713 };
714
715 self.selector
717 .as_ref()
718 .feed_cycle(draw_cycle, lookback_rolls, lookback_seed)
719 }
720
721 pub fn feed_cycle_state_hash(
723 &self,
724 cycle: u64,
725 final_state_hash: HashXof<HASH_XOF_SIZE_BYTES>,
726 ) {
727 if self.get_cycle_index(cycle).is_some() {
728 let mut batch = DBBatch::new();
729 self.put_cycle_history_final_state_hash_snapshot(
730 cycle,
731 Some(final_state_hash),
732 &mut batch,
733 );
734
735 self.db.write().write_batch(batch, Default::default(), None);
736 } else {
737 panic!("cycle {} should be contained here", cycle);
738 }
739 }
740}
741
742impl PoSFinalState {
744 pub fn get_rolls_for(&self, addr: &Address) -> u64 {
746 self.cycle_history_cache
747 .back()
748 .and_then(|info| {
749 let cycle = info.0;
750 let db = self.db.read();
751
752 let key = roll_count_key!(self.cycle_history_cycle_prefix(cycle), addr);
753
754 if let Some(serialized_value) =
755 db.get_cf(STATE_CF, key).expect(CYCLE_HISTORY_DESER_ERROR)
756 {
757 let (_, amount) = self
758 .cycle_info_deserializer
759 .rolls_deser
760 .u64_deserializer
761 .deserialize::<DeserializeError>(&serialized_value)
762 .expect(CYCLE_HISTORY_DESER_ERROR);
763
764 Some(amount)
765 } else {
766 None
767 }
768 })
769 .unwrap_or_default()
770 }
771
772 pub fn get_address_active_rolls(&self, addr: &Address, cycle: u64) -> Option<u64> {
775 match cycle.checked_sub(3) {
776 Some(lookback_cycle) => {
777 let key = roll_count_key!(self.cycle_history_cycle_prefix(lookback_cycle), addr);
778 let db = self.db.read();
779 if let Some(serialized_value) =
780 db.get_cf(STATE_CF, key).expect(CYCLE_HISTORY_DESER_ERROR)
781 {
782 let (_, amount) = self
783 .cycle_info_deserializer
784 .rolls_deser
785 .u64_deserializer
786 .deserialize::<DeserializeError>(&serialized_value)
787 .expect(CYCLE_HISTORY_DESER_ERROR);
788
789 Some(amount)
790 } else {
791 None
792 }
793 }
794 None => self.initial_rolls.get(addr).cloned(),
795 }
796 }
797
798 pub fn get_all_active_rolls(&self, cycle: u64) -> BTreeMap<Address, u64> {
800 match cycle.checked_sub(3) {
801 Some(lookback_cycle) => {
802 self.get_all_roll_counts(lookback_cycle)
804 }
805 None => self.initial_rolls.clone(),
806 }
807 }
808
809 pub fn get_deferred_credits_range<R>(
813 &self,
814 range: R,
815 addr_filter: Option<&Address>,
816 ) -> DeferredCredits
817 where
818 R: RangeBounds<Slot>,
819 {
820 let db = self.db.read();
821
822 let mut deferred_credits = DeferredCredits::new();
823
824 let mut start_key_buffer = Vec::new();
825 start_key_buffer.extend_from_slice(DEFERRED_CREDITS_PREFIX.as_bytes());
826
827 match range.start_bound() {
828 Included(slot) => {
829 start_key_buffer.extend_from_slice(&slot.to_bytes_key());
830 }
831 Excluded(slot) => {
832 start_key_buffer.extend_from_slice(
833 &slot
834 .get_next_slot(self.config.thread_count)
835 .expect(DEFERRED_CREDITS_SER_ERROR)
836 .to_bytes_key(),
837 );
838 }
839 Unbounded => {}
840 };
841
842 let iterator_cf = db
843 .iterator_cf(
844 STATE_CF,
845 MassaIteratorMode::From(&start_key_buffer, MassaDirection::Forward),
846 )
847 .take_while(|(k, _)| k.starts_with(DEFERRED_CREDITS_PREFIX.as_bytes()));
848
849 for (serialized_key, serialized_value) in iterator_cf {
850 let rest_key = &serialized_key[DEFERRED_CREDITS_PREFIX.len()..];
852 let (rest_key, slot) = buf_to_array_ctr(rest_key, Slot::from_bytes_key)
853 .expect(DEFERRED_CREDITS_DESER_ERROR);
854
855 if !range.contains(&slot) {
856 break;
857 }
858
859 let (_, address) = self
860 .credits_deserializer
861 .address_deserializer
862 .deserialize::<DeserializeError>(rest_key)
863 .expect(DEFERRED_CREDITS_DESER_ERROR);
864
865 if let Some(addr_filter_value) = &addr_filter {
866 if &&address != addr_filter_value {
867 continue;
868 }
869 }
870
871 let (_, amount) = self
872 .credits_deserializer
873 .amount_deserializer
874 .deserialize::<DeserializeError>(&serialized_value)
875 .expect(DEFERRED_CREDITS_DESER_ERROR);
876
877 deferred_credits.insert(slot, address, amount);
878 }
879
880 deferred_credits
881 }
882
883 pub fn get_cycle_index(&self, cycle: u64) -> Option<usize> {
885 let first_cycle = match self.cycle_history_cache.front() {
886 Some(c) => c.0,
887 None => return None, };
889 if cycle < first_cycle {
890 return None; }
892 let index: usize = match (cycle - first_cycle).try_into() {
893 Ok(v) => v,
894 Err(_) => return None, };
896 if index >= self.cycle_history_cache.len() {
897 return None; }
899 Some(index)
900 }
901
902 pub fn get_all_roll_counts(&self, cycle: u64) -> BTreeMap<Address, u64> {
904 let db = self.db.read();
905
906 if self.get_cycle_index(cycle).is_none() {
907 panic!("Cycle {} not in history", cycle)
908 }
909
910 let mut roll_counts: BTreeMap<Address, u64> = BTreeMap::new();
911 let prefix = roll_count_prefix!(self.cycle_history_cycle_prefix(cycle));
912 for (serialized_key, serialized_value) in db.prefix_iterator_cf(STATE_CF, &prefix) {
913 if !serialized_key.starts_with(prefix.as_bytes()) {
914 break;
915 }
916
917 let rest_key = &serialized_key[CYCLE_HISTORY_PREFIX.len()..];
919 let (rest_key, _cycle) = buf_to_array_ctr(rest_key, |v| u64::from_be_bytes(*v))
920 .expect(CYCLE_HISTORY_DESER_ERROR);
921
922 let (_, address) = self
923 .cycle_info_deserializer
924 .rolls_deser
925 .address_deserializer
926 .deserialize::<DeserializeError>(&rest_key[1..])
927 .expect(CYCLE_HISTORY_DESER_ERROR);
928
929 let (_, amount) = self
930 .cycle_info_deserializer
931 .rolls_deser
932 .u64_deserializer
933 .deserialize::<DeserializeError>(&serialized_value)
934 .expect(CYCLE_HISTORY_DESER_ERROR);
935
936 roll_counts.insert(address, amount);
937 }
938
939 roll_counts
940 }
941
942 pub fn get_all_production_stats(
944 &self,
945 cycle: u64,
946 ) -> Option<PreHashMap<Address, ProductionStats>> {
947 self.get_cycle_index(cycle)
948 .map(|idx| self.get_all_production_stats_private(self.cycle_history_cache[idx].0))
949 }
950
951 fn get_all_production_stats_private(&self, cycle: u64) -> PreHashMap<Address, ProductionStats> {
953 let db = self.db.read();
954
955 let mut production_stats: PreHashMap<Address, ProductionStats> = PreHashMap::default();
956 let mut cur_production_stat = ProductionStats::default();
957 let mut cur_address = None;
958
959 let prefix = prod_stats_prefix!(self.cycle_history_cycle_prefix(cycle));
960 for (serialized_key, serialized_value) in db.prefix_iterator_cf(STATE_CF, &prefix) {
961 if !serialized_key.starts_with(prefix.as_bytes()) {
962 break;
963 }
964
965 let rest_key = &serialized_key[CYCLE_HISTORY_PREFIX.len()..];
967 let (rest_key, _cycle) = buf_to_array_ctr(rest_key, |v| u64::from_be_bytes(*v))
968 .expect(CYCLE_HISTORY_DESER_ERROR);
969
970 let (rest_key, address) = self
971 .cycle_info_deserializer
972 .production_stats_deser
973 .address_deserializer
974 .deserialize::<DeserializeError>(&rest_key[1..])
975 .expect(CYCLE_HISTORY_DESER_ERROR);
976
977 if cur_address != Some(address) {
978 cur_address = Some(address);
979 cur_production_stat = ProductionStats::default();
980 }
981
982 let (_, value) = self
983 .cycle_info_deserializer
984 .production_stats_deser
985 .u64_deserializer
986 .deserialize::<DeserializeError>(&serialized_value)
987 .expect(CYCLE_HISTORY_DESER_ERROR);
988
989 if rest_key.len() == 1 && rest_key[0] == PROD_STATS_FAIL_IDENT {
990 cur_production_stat.block_failure_count = value;
991 } else if rest_key.len() == 1 && rest_key[0] == PROD_STATS_SUCCESS_IDENT {
992 cur_production_stat.block_success_count = value;
993 } else {
994 panic!("{}", CYCLE_HISTORY_DESER_ERROR);
995 }
996
997 production_stats.insert(address, cur_production_stat);
998 }
999
1000 production_stats
1001 }
1002
1003 fn get_cycle_history_rng_seed(&self, cycle: u64) -> Option<BitVec<u8>> {
1005 if let Some((cached_cycle, rng_seed)) = &self.rng_seed_cache {
1006 if *cached_cycle == cycle {
1007 return Some(rng_seed.clone());
1008 }
1009 }
1010
1011 let serialized_rng_seed = self
1012 .db
1013 .read()
1014 .get_cf(
1015 STATE_CF,
1016 rng_seed_key!(self.cycle_history_cycle_prefix(cycle)),
1017 )
1018 .expect(CYCLE_HISTORY_DESER_ERROR);
1019 let serialized_rng_seed = serialized_rng_seed?;
1020
1021 let (_, rng_seed) = self
1022 .cycle_info_deserializer
1023 .bitvec_deser
1024 .deserialize::<DeserializeError>(&serialized_rng_seed)
1025 .expect(CYCLE_HISTORY_DESER_ERROR);
1026
1027 Some(rng_seed)
1028 }
1029
1030 fn get_cycle_history_final_state_hash_snapshot(
1034 &self,
1035 cycle: u64,
1036 ) -> Option<HashXof<HASH_XOF_SIZE_BYTES>> {
1037 let db = self.db.read();
1038
1039 let serialized_state_hash = db
1040 .get_cf(
1041 STATE_CF,
1042 final_state_hash_snapshot_key!(self.cycle_history_cycle_prefix(cycle)),
1043 )
1044 .expect(CYCLE_HISTORY_DESER_ERROR)?;
1045 let (_, state_hash) = self
1046 .cycle_info_deserializer
1047 .opt_hash_deser
1048 .deserialize::<DeserializeError>(&serialized_state_hash)
1049 .expect(CYCLE_HISTORY_DESER_ERROR);
1050 state_hash
1051 }
1052
1053 fn get_cycle_history_cycles(&self) -> Vec<(u64, bool)> {
1056 let mut found_cycles: Vec<u64> = Vec::new();
1057
1058 {
1059 let db = self.db.read();
1060
1061 while let Some((serialized_key, _)) = match found_cycles.last() {
1062 Some(prev_cycle) => db
1063 .iterator_cf(
1064 STATE_CF,
1065 MassaIteratorMode::From(
1066 &self.cycle_history_cycle_prefix(prev_cycle.saturating_add(1)),
1067 MassaDirection::Forward,
1068 ),
1069 )
1070 .next(),
1071 None => db
1072 .iterator_cf(
1073 STATE_CF,
1074 MassaIteratorMode::From(
1075 CYCLE_HISTORY_PREFIX.as_bytes(),
1076 MassaDirection::Forward,
1077 ),
1078 )
1079 .next(),
1080 } {
1081 if !serialized_key.starts_with(CYCLE_HISTORY_PREFIX.as_bytes()) {
1082 break;
1083 }
1084
1085 let rest_key = &serialized_key[CYCLE_HISTORY_PREFIX.len()..];
1087 let (_rest_key, cycle) = buf_to_array_ctr(rest_key, |v| u64::from_be_bytes(*v))
1088 .expect(CYCLE_HISTORY_DESER_ERROR);
1089
1090 found_cycles.push(cycle);
1091 }
1092 }
1093
1094 found_cycles
1095 .into_iter()
1096 .map(|cycle| (cycle, self.is_cycle_complete(cycle).unwrap_or(false)))
1097 .collect()
1098 }
1099
1100 pub fn get_cycle_info(&self, cycle: u64) -> Option<CycleInfo> {
1102 let complete = self.is_cycle_complete(cycle)?;
1105 let rng_seed = self.get_cycle_history_rng_seed(cycle)?;
1106 let final_state_hash_snapshot = self.get_cycle_history_final_state_hash_snapshot(cycle);
1107
1108 let roll_counts = self.get_all_roll_counts(cycle);
1109
1110 let production_stats = self.get_all_production_stats(cycle).unwrap_or_default();
1111
1112 let mut cycle_info =
1113 CycleInfo::new(cycle, complete, roll_counts, rng_seed, production_stats);
1114 cycle_info.final_state_hash_snapshot = final_state_hash_snapshot;
1115 Some(cycle_info)
1116 }
1117
1118 pub fn get_address_credits_for_slot(&self, addr: &Address, slot: &Slot) -> Option<Amount> {
1120 let db = self.db.read();
1121
1122 let mut serialized_key = Vec::new();
1123 serialized_key.extend_from_slice(&slot.to_bytes_key());
1124 self.credits_serializer
1125 .address_ser
1126 .serialize(addr, &mut serialized_key)
1127 .expect(DEFERRED_CREDITS_SER_ERROR);
1128
1129 match db.get_cf(STATE_CF, deferred_credits_key!(serialized_key)) {
1130 Ok(Some(serialized_amount)) => {
1131 let (_, amount) = self
1132 .credits_deserializer
1133 .amount_deserializer
1134 .deserialize::<DeserializeError>(&serialized_amount)
1135 .expect(DEFERRED_CREDITS_DESER_ERROR);
1136 Some(amount)
1137 }
1138 _ => None,
1139 }
1140 }
1141
1142 pub fn get_production_stats_for_address(
1144 &self,
1145 cycle: u64,
1146 address: &Address,
1147 ) -> Option<ProductionStats> {
1148 let db = self.db.read();
1149
1150 let prefix = self.cycle_history_cycle_prefix(cycle);
1151
1152 let query = vec![
1153 (STATE_CF, prod_stats_fail_key!(prefix, *address)),
1154 (STATE_CF, prod_stats_success_key!(prefix, *address)),
1155 ];
1156
1157 let results = db.multi_get_cf(query);
1158
1159 match (results.first(), results.get(1)) {
1160 (Some(Ok(Some(serialized_fail))), Some(Ok(Some(serialized_success)))) => {
1161 let (_, fail) = self
1162 .cycle_info_deserializer
1163 .production_stats_deser
1164 .u64_deserializer
1165 .deserialize::<DeserializeError>(serialized_fail)
1166 .expect(CYCLE_HISTORY_DESER_ERROR);
1167 let (_, success) = self
1168 .cycle_info_deserializer
1169 .production_stats_deser
1170 .u64_deserializer
1171 .deserialize::<DeserializeError>(serialized_success)
1172 .expect(CYCLE_HISTORY_DESER_ERROR);
1173
1174 Some(ProductionStats {
1175 block_success_count: success,
1176 block_failure_count: fail,
1177 })
1178 }
1179 _ => None,
1180 }
1181 }
1182
1183 pub fn is_cycle_complete(&self, cycle: u64) -> Option<bool> {
1185 let key = complete_key!(self.cycle_history_cycle_prefix(cycle));
1186 let res = self.db.read().get_cf(STATE_CF, key);
1187 match res {
1188 Ok(Some(complete_value)) => Some(complete_value.first() == Some(&1)),
1189 Ok(None) => None,
1190 Err(err) => {
1191 panic!(
1192 "Error while checking if cycle {} is complete: {}",
1193 cycle, err
1194 );
1195 }
1196 }
1197 }
1198}
1199
1200impl PoSFinalState {
1202 pub fn put_new_cycle_info(&mut self, cycle_info: &CycleInfo, batch: &mut DBBatch) {
1204 self.put_cycle_history_complete(cycle_info.cycle, cycle_info.complete, batch);
1205 self.put_cycle_history_rng_seed(cycle_info.cycle, cycle_info.rng_seed.clone(), batch);
1206 self.put_cycle_history_final_state_hash_snapshot(
1207 cycle_info.cycle,
1208 cycle_info.final_state_hash_snapshot,
1209 batch,
1210 );
1211 for (address, roll) in cycle_info.roll_counts.iter() {
1212 self.put_cycle_history_address_entry(
1213 cycle_info.cycle,
1214 address,
1215 Some(roll),
1216 None,
1217 batch,
1218 );
1219 }
1220 for (address, prod_stats) in cycle_info.production_stats.iter() {
1221 self.put_cycle_history_address_entry(
1222 cycle_info.cycle,
1223 address,
1224 None,
1225 Some(prod_stats),
1226 batch,
1227 );
1228 }
1229 self.cycle_history_cache
1230 .push_back((cycle_info.cycle, cycle_info.complete));
1231 }
1232
1233 fn put_cycle_history_complete(&mut self, cycle: u64, value: bool, batch: &mut DBBatch) {
1235 let db = self.db.read();
1236
1237 let prefix = self.cycle_history_cycle_prefix(cycle);
1238
1239 let serialized_value = if value { &[1] } else { &[0] };
1240
1241 db.put_or_update_entry_value(batch, complete_key!(prefix), serialized_value);
1242
1243 if let Some(index) = self.get_cycle_index(cycle) {
1244 self.cycle_history_cache[index].1 = value;
1245 }
1246 }
1247
1248 fn put_cycle_history_final_state_hash_snapshot(
1250 &self,
1251 cycle: u64,
1252 value: Option<HashXof<HASH_XOF_SIZE_BYTES>>,
1253 batch: &mut DBBatch,
1254 ) {
1255 let db = self.db.read();
1256
1257 let prefix = self.cycle_history_cycle_prefix(cycle);
1258
1259 let mut serialized_value = Vec::new();
1260 self.cycle_info_serializer
1261 .opt_hash_ser
1262 .serialize(&value, &mut serialized_value)
1263 .expect(CYCLE_HISTORY_SER_ERROR);
1264
1265 db.put_or_update_entry_value(
1266 batch,
1267 final_state_hash_snapshot_key!(prefix),
1268 &serialized_value,
1269 );
1270 }
1271
1272 fn put_cycle_history_rng_seed(&mut self, cycle: u64, value: BitVec<u8>, batch: &mut DBBatch) {
1274 let db = self.db.read();
1275
1276 let prefix = self.cycle_history_cycle_prefix(cycle);
1277
1278 let mut serialized_value = Vec::new();
1279 self.cycle_info_serializer
1280 .bitvec_ser
1281 .serialize(&value, &mut serialized_value)
1282 .expect(CYCLE_HISTORY_SER_ERROR);
1283
1284 self.rng_seed_cache = Some((cycle, value.clone()));
1285
1286 db.put_or_update_entry_value(batch, rng_seed_key!(prefix), &serialized_value);
1287 }
1288
1289 fn put_cycle_history_address_entry(
1291 &self,
1292 cycle: u64,
1293 address: &Address,
1294 roll_count: Option<&u64>,
1295 production_stats: Option<&ProductionStats>,
1296 batch: &mut DBBatch,
1297 ) {
1298 let db = self.db.read();
1299
1300 let prefix = self.cycle_history_cycle_prefix(cycle);
1301
1302 if let Some(0) = roll_count {
1304 db.delete_key(batch, roll_count_key!(prefix, address));
1305 } else if let Some(roll_count) = roll_count {
1306 let mut serialized_roll_count = Vec::new();
1307 self.cycle_info_serializer
1308 .u64_ser
1309 .serialize(roll_count, &mut serialized_roll_count)
1310 .expect(CYCLE_HISTORY_SER_ERROR);
1311 db.put_or_update_entry_value(
1312 batch,
1313 roll_count_key!(prefix, address),
1314 &serialized_roll_count,
1315 );
1316 }
1317
1318 if let Some(production_stats) = production_stats {
1320 let mut serialized_prod_stats_fail = Vec::new();
1321 self.cycle_info_serializer
1322 .u64_ser
1323 .serialize(
1324 &production_stats.block_failure_count,
1325 &mut serialized_prod_stats_fail,
1326 )
1327 .expect(CYCLE_HISTORY_SER_ERROR);
1328 db.put_or_update_entry_value(
1329 batch,
1330 prod_stats_fail_key!(prefix, address),
1331 &serialized_prod_stats_fail,
1332 );
1333
1334 let mut serialized_prod_stats_success = Vec::new();
1336 self.cycle_info_serializer
1337 .u64_ser
1338 .serialize(
1339 &production_stats.block_success_count,
1340 &mut serialized_prod_stats_success,
1341 )
1342 .expect(CYCLE_HISTORY_SER_ERROR);
1343 db.put_or_update_entry_value(
1344 batch,
1345 prod_stats_success_key!(prefix, address),
1346 &serialized_prod_stats_success,
1347 );
1348 }
1349 }
1350
1351 pub fn put_deferred_credits_entry(
1353 &self,
1354 slot: &Slot,
1355 address: &Address,
1356 amount: &Amount,
1357 batch: &mut DBBatch,
1358 ) {
1359 let db = self.db.read();
1360
1361 let mut serialized_key = Vec::new();
1362 serialized_key.extend_from_slice(&slot.to_bytes_key());
1363 self.credits_serializer
1364 .address_ser
1365 .serialize(address, &mut serialized_key)
1366 .expect(DEFERRED_CREDITS_SER_ERROR);
1367
1368 if amount.is_zero() {
1369 db.delete_key(batch, deferred_credits_key!(serialized_key));
1370 } else {
1371 let mut serialized_amount = Vec::new();
1372 self.credits_serializer
1373 .amount_ser
1374 .serialize(amount, &mut serialized_amount)
1375 .expect(DEFERRED_CREDITS_SER_ERROR);
1376
1377 db.put_or_update_entry_value(
1378 batch,
1379 deferred_credits_key!(serialized_key),
1380 &serialized_amount,
1381 );
1382 }
1383 }
1384}
1385
1386impl PoSFinalState {
1388 fn cycle_history_cycle_prefix(&self, cycle: u64) -> Vec<u8> {
1390 let mut serialized_key = Vec::new();
1391 serialized_key.extend_from_slice(CYCLE_HISTORY_PREFIX.as_bytes());
1392 serialized_key.extend_from_slice(&cycle.to_be_bytes());
1393 serialized_key
1394 }
1395
1396 pub fn is_cycle_history_key_value_valid(
1398 &self,
1399 serialized_key: &[u8],
1400 serialized_value: &[u8],
1401 ) -> bool {
1402 if !serialized_key.starts_with(CYCLE_HISTORY_PREFIX.as_bytes()) {
1403 return false;
1404 }
1405
1406 let rest_key = &serialized_key[CYCLE_HISTORY_PREFIX.len()..];
1408 let Some((rest_key, cycle)) = buf_to_array_ctr(rest_key, |v| u64::from_be_bytes(*v)) else {
1409 return false;
1410 };
1411
1412 if rest_key.is_empty() {
1413 return false;
1414 }
1415
1416 let slots_per_cycle = self.slots_per_cycle();
1417
1418 match rest_key[0] {
1419 COMPLETE_IDENT => {
1420 if rest_key.len() != 1 {
1421 return false;
1422 }
1423 if serialized_value.len() != 1 {
1424 return false;
1425 }
1426 if serialized_value[0] > 1 {
1427 return false;
1428 }
1429 if serialized_value[0] == 1 {
1431 let prefix = self.cycle_history_cycle_prefix(cycle);
1432 let Ok(Some(serialized_rng_seed)) =
1433 self.db.read().get_cf(STATE_CF, rng_seed_key!(prefix))
1434 else {
1435 return false;
1436 };
1437 let Ok((rest, rng_seed)) =
1438 self.cycle_info_deserializer
1439 .bitvec_deser
1440 .deserialize::<DeserializeError>(&serialized_rng_seed)
1441 else {
1442 return false;
1443 };
1444 if !rest.is_empty() || rng_seed.len() != slots_per_cycle {
1445 return false;
1446 }
1447 }
1448 }
1449 RNG_SEED_IDENT => {
1450 if rest_key.len() != 1 {
1451 return false;
1452 }
1453 let Ok((rest_key, rng_seed)) = self
1454 .cycle_info_deserializer
1455 .bitvec_deser
1456 .deserialize::<DeserializeError>(serialized_value)
1457 else {
1458 return false;
1459 };
1460 if !rest_key.is_empty() {
1461 return false;
1462 }
1463 if rng_seed.len() > slots_per_cycle {
1466 return false;
1467 }
1468 }
1469 FINAL_STATE_HASH_SNAPSHOT_IDENT => {
1470 if rest_key.len() != 1 {
1471 return false;
1472 }
1473 let Ok((rest_key, _final_state_hash)) = self
1474 .cycle_info_deserializer
1475 .opt_hash_deser
1476 .deserialize::<DeserializeError>(serialized_value)
1477 else {
1478 return false;
1479 };
1480 if !rest_key.is_empty() {
1481 return false;
1482 }
1483 }
1484 ROLL_COUNT_IDENT => {
1485 let Ok((rest_key, _addr)): std::result::Result<
1486 (&[u8], Address),
1487 nom::Err<massa_serialization::DeserializeError<'_>>,
1488 > = self
1489 .cycle_info_deserializer
1490 .rolls_deser
1491 .address_deserializer
1492 .deserialize::<DeserializeError>(&rest_key[1..])
1493 else {
1494 return false;
1495 };
1496 if !rest_key.is_empty() {
1497 return false;
1498 }
1499 let Ok((rest_key, _addr)) = self
1500 .cycle_info_deserializer
1501 .rolls_deser
1502 .u64_deserializer
1503 .deserialize::<DeserializeError>(serialized_value)
1504 else {
1505 return false;
1506 };
1507 if !rest_key.is_empty() {
1508 return false;
1509 }
1510 }
1511 PROD_STATS_IDENT => {
1512 let Ok((rest_key, _addr)): std::result::Result<
1513 (&[u8], Address),
1514 nom::Err<massa_serialization::DeserializeError<'_>>,
1515 > = self
1516 .cycle_info_deserializer
1517 .rolls_deser
1518 .address_deserializer
1519 .deserialize::<DeserializeError>(&rest_key[1..])
1520 else {
1521 return false;
1522 };
1523 if rest_key.len() != 1 {
1524 return false;
1525 }
1526
1527 match rest_key[0] {
1528 PROD_STATS_FAIL_IDENT => {
1529 let Ok((rest_key, _fail)) = self
1530 .cycle_info_deserializer
1531 .production_stats_deser
1532 .u64_deserializer
1533 .deserialize::<DeserializeError>(serialized_value)
1534 else {
1535 return false;
1536 };
1537 if !rest_key.is_empty() {
1538 return false;
1539 }
1540 }
1541 PROD_STATS_SUCCESS_IDENT => {
1542 let Ok((rest_key, _success)) = self
1543 .cycle_info_deserializer
1544 .production_stats_deser
1545 .u64_deserializer
1546 .deserialize::<DeserializeError>(serialized_value)
1547 else {
1548 return false;
1549 };
1550 if !rest_key.is_empty() {
1551 return false;
1552 }
1553 }
1554 _ => {
1555 return false;
1556 }
1557 }
1558 }
1559 _ => {
1560 return false;
1561 }
1562 }
1563
1564 true
1565 }
1566
1567 pub fn is_deferred_credits_key_value_valid(
1569 &self,
1570 serialized_key: &[u8],
1571 serialized_value: &[u8],
1572 ) -> bool {
1573 if !serialized_key.starts_with(DEFERRED_CREDITS_PREFIX.as_bytes()) {
1574 return false;
1575 }
1576
1577 let rest_key = &serialized_key[DEFERRED_CREDITS_PREFIX.len()..];
1579 let Some((rest_key, _slot)) = buf_to_array_ctr(rest_key, Slot::from_bytes_key) else {
1580 return false;
1581 };
1582
1583 let Ok((rest_key, _addr)): std::result::Result<
1584 (&[u8], Address),
1585 nom::Err<massa_serialization::DeserializeError<'_>>,
1586 > = self
1587 .credits_deserializer
1588 .address_deserializer
1589 .deserialize::<DeserializeError>(rest_key)
1590 else {
1591 return false;
1592 };
1593 if !rest_key.is_empty() {
1594 return false;
1595 }
1596
1597 let Ok((rest_key, _amount)) = self
1598 .credits_deserializer
1599 .amount_deserializer
1600 .deserialize::<DeserializeError>(serialized_value)
1601 else {
1602 return false;
1603 };
1604 if !rest_key.is_empty() {
1605 return false;
1606 }
1607
1608 true
1609 }
1610}
1611
1612#[cfg(feature = "test-exports")]
1614impl PoSFinalState {
1615 pub fn get_deferred_credits(&self) -> DeferredCredits {
1617 let db = self.db.read();
1618
1619 let mut deferred_credits = DeferredCredits::new();
1620
1621 for (serialized_key, serialized_value) in
1622 db.prefix_iterator_cf(STATE_CF, DEFERRED_CREDITS_PREFIX.as_bytes())
1623 {
1624 if !serialized_key.starts_with(DEFERRED_CREDITS_PREFIX.as_bytes()) {
1625 break;
1626 }
1627
1628 let rest_key = &serialized_key[DEFERRED_CREDITS_PREFIX.len()..];
1630 let (rest_key, slot) = buf_to_array_ctr(rest_key, Slot::from_bytes_key)
1631 .expect(DEFERRED_CREDITS_DESER_ERROR);
1632
1633 let (_, address) = self
1634 .credits_deserializer
1635 .address_deserializer
1636 .deserialize::<DeserializeError>(rest_key)
1637 .expect(DEFERRED_CREDITS_DESER_ERROR);
1638
1639 let (_, amount) = self
1640 .credits_deserializer
1641 .amount_deserializer
1642 .deserialize::<DeserializeError>(&serialized_value)
1643 .expect(DEFERRED_CREDITS_DESER_ERROR);
1644
1645 deferred_credits.insert(slot, address, amount);
1646 }
1647 deferred_credits
1648 }
1649}
1650
1651#[cfg(test)]
1652mod tests {
1653 use super::*;
1654
1655 use std::collections::HashMap;
1656 use std::str::FromStr;
1657 use std::sync::Arc;
1658
1659 use assert_matches::assert_matches;
1660 use bitvec::prelude::*;
1661 use parking_lot::RwLock;
1662 use tempfile::TempDir;
1663
1664 use crate::MockSelectorController;
1665
1666 use massa_db_exports::{MassaDBConfig, MassaDBController};
1667 use massa_db_worker::MassaDB;
1668 use massa_models::config::constants::{
1669 MAX_DEFERRED_CREDITS_LENGTH, MAX_PRODUCTION_STATS_LENGTH, MAX_ROLLS_COUNT_LENGTH,
1670 POS_SAVED_CYCLES,
1671 };
1672 use massa_signature::KeyPair;
1673
1674 #[test]
1676 fn test_initial_deferred_credits_loading() {
1677 let initial_deferred_credits_file = tempfile::NamedTempFile::new()
1678 .expect("could not create temporary initial deferred credits file");
1679 let deferred_credits_file_contents = "{
1681 \"AU12pAcVUzsgUBJHaYSAtDKVTYnUT9NorBDjoDovMfAFTLFa16MNa\": [
1682 {
1683 \"slot\": {\"period\": 3, \"thread\": 0},
1684 \"amount\": \"5.01\"
1685 },
1686 {
1687 \"slot\": {\"period\": 4, \"thread\": 1},
1688 \"amount\": \"6.0\"
1689 },
1690 {
1691 \"slot\": {\"period\": 255, \"thread\": 0},
1692 \"amount\": \"5.01\"
1693 },
1694 {
1695 \"slot\": {\"period\": 256, \"thread\": 0},
1696 \"amount\": \"6.0\"
1697 }
1698 ],
1699 \"AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53\": [
1700 {
1701 \"slot\": {\"period\": 3, \"thread\": 0},
1702 \"amount\": \"2.01\"
1703 }
1704 ]
1705 }";
1706 std::fs::write(
1707 initial_deferred_credits_file.path(),
1708 deferred_credits_file_contents.as_bytes(),
1709 )
1710 .expect("failed writing initial deferred credits file");
1711 let pos_config = PoSConfig {
1712 periods_per_cycle: 2,
1713 thread_count: 2,
1714 cycle_history_length: POS_SAVED_CYCLES,
1715 max_rolls_length: MAX_ROLLS_COUNT_LENGTH,
1716 max_production_stats_length: MAX_PRODUCTION_STATS_LENGTH,
1717 max_credit_length: MAX_DEFERRED_CREDITS_LENGTH,
1718 initial_deferred_credits_path: Some(initial_deferred_credits_file.path().to_path_buf()),
1719 };
1720 let tempdir = tempfile::TempDir::new().expect("cannot create temp directory");
1722 let db_config = MassaDBConfig {
1723 path: tempdir.path().to_path_buf(),
1724 max_history_length: 10,
1725 max_final_state_elements_size: 100_000,
1726 max_versioning_elements_size: 100_000,
1727 thread_count: 2,
1728 max_ledger_backups: 10,
1729 enable_metrics: false,
1730 };
1731 let db = Arc::new(RwLock::new(
1732 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
1733 ));
1734 let selector_controller = Box::new(MockSelectorController::new());
1735 let init_seed = Hash::compute_from(b"");
1736 let initial_seeds = vec![Hash::compute_from(init_seed.to_bytes()), init_seed];
1737 let cycle_info_deserializer = CycleInfoDeserializer::new(
1738 pos_config.max_rolls_length,
1739 pos_config.max_production_stats_length,
1740 );
1741 let mut pos_state = PoSFinalState {
1742 config: pos_config.clone(),
1743 db: db.clone(),
1744 cycle_history_cache: Default::default(),
1745 rng_seed_cache: None,
1746 selector: selector_controller,
1747 initial_rolls: Default::default(),
1748 initial_seeds,
1749 credits_serializer: DeferredCreditSerializer::new(),
1750 credits_deserializer: DeferredCreditDeserializer::new(pos_config.max_credit_length),
1751 cycle_info_serializer: CycleInfoSerializer::new(),
1752 cycle_info_deserializer,
1753 };
1754 let mut batch = DBBatch::new();
1755 let mut ledger = massa_ledger_exports::MockLedgerController::new();
1757 ledger.expect_entry_exists().returning(|_| false);
1758 pos_state
1759 .load_initial_deferred_credits(&mut batch, &ledger)
1760 .expect("error while loading initial deferred credits");
1761 db.write().write_batch(batch, DBBatch::new(), None);
1762 let deferred_credits = pos_state.get_deferred_credits().credits;
1763 let addr1 =
1764 Address::from_str("AU12pAcVUzsgUBJHaYSAtDKVTYnUT9NorBDjoDovMfAFTLFa16MNa").unwrap();
1765 let a_a1_s3 = Amount::from_str("5.01").unwrap();
1766 let addr2 =
1767 Address::from_str("AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53").unwrap();
1768 let a_a2_s3 = Amount::from_str("2.01").unwrap();
1769 let expected_credits = vec![
1770 (
1771 Slot::new(3, 0),
1772 vec![(addr1, a_a1_s3), (addr2, a_a2_s3)]
1773 .into_iter()
1774 .collect(),
1775 ),
1776 (
1777 Slot::new(4, 1),
1778 vec![(addr1, Amount::from_str("6.0").unwrap())]
1779 .into_iter()
1780 .collect(),
1781 ),
1782 (
1783 Slot::new(255, 0),
1784 vec![(addr1, Amount::from_str("5.01").unwrap())]
1785 .into_iter()
1786 .collect(),
1787 ),
1788 (
1789 Slot::new(256, 0),
1790 vec![(addr1, Amount::from_str("6.0").unwrap())]
1791 .into_iter()
1792 .collect(),
1793 ),
1794 ]
1795 .into_iter()
1796 .collect();
1797 assert_eq!(
1798 deferred_credits, expected_credits,
1799 "deferred credits not loaded correctly"
1800 );
1801 let credits_range_1 =
1802 pos_state.get_deferred_credits_range(Slot::new(4, 0)..Slot::new(4, 1), None);
1803 assert!(credits_range_1.is_empty());
1804 let credits_range_2 =
1805 pos_state.get_deferred_credits_range(Slot::new(2, 0)..Slot::new(3, 1), None);
1806 let expected_credits_range_2 = vec![(
1807 Slot::new(3, 0),
1808 vec![(addr1, a_a1_s3), (addr2, a_a2_s3)]
1809 .into_iter()
1810 .collect(),
1811 )]
1812 .into_iter()
1813 .collect();
1814 assert_eq!(credits_range_2.credits, expected_credits_range_2);
1815 let credits_range_3 =
1816 pos_state.get_deferred_credits_range(Slot::new(7, 0)..Slot::new(9, 5), None);
1817 assert!(credits_range_3.is_empty());
1818 let credits_range_4 =
1819 pos_state.get_deferred_credits_range(Slot::new(7, 0)..Slot::new(255, 1), None);
1820
1821 let a_a1_s255 = Amount::from_str("5.01").unwrap();
1822 let expected_credits_range_4 = vec![(
1823 Slot::new(255, 0),
1824 vec![(addr1, a_a1_s255)].into_iter().collect(),
1825 )]
1826 .into_iter()
1827 .collect();
1828 assert_eq!(credits_range_4.credits, expected_credits_range_4);
1829 }
1830
1831 #[test]
1833 fn test_initial_rolls_loading() {
1834 let initial_deferred_credits_file =
1835 tempfile::NamedTempFile::new().expect("could not create temporary initial rolls file");
1836 let initial_rolls_file_0 =
1837 tempfile::NamedTempFile::new().expect("could not create temporary initial rolls file");
1838
1839 let rolls_file_contents_0 = "{}";
1841 std::fs::write(
1842 initial_rolls_file_0.path(),
1843 rolls_file_contents_0.as_bytes(),
1844 )
1845 .expect("failed writing initial rolls file 0");
1846 let deferred_credits_file_contents = "{}";
1848 std::fs::write(
1849 initial_deferred_credits_file.path(),
1850 deferred_credits_file_contents.as_bytes(),
1851 )
1852 .expect("failed writing initial deferred credits file");
1853
1854 let tempdir = tempfile::TempDir::new().expect("cannot create temp directory");
1856 let db_config = MassaDBConfig {
1857 path: tempdir.path().to_path_buf(),
1858 max_history_length: 10,
1859 max_final_state_elements_size: 100,
1860 max_versioning_elements_size: 100,
1861 thread_count: 2,
1862 max_ledger_backups: 10,
1863 enable_metrics: false,
1864 };
1865 let db = Arc::new(RwLock::new(
1866 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
1867 ));
1868 let selector_controller = Box::new(MockSelectorController::new());
1869
1870 let pos_config = PoSConfig {
1871 periods_per_cycle: 2,
1872 thread_count: 2,
1873 cycle_history_length: POS_SAVED_CYCLES,
1874 max_rolls_length: MAX_ROLLS_COUNT_LENGTH,
1875 max_production_stats_length: MAX_PRODUCTION_STATS_LENGTH,
1876 max_credit_length: MAX_DEFERRED_CREDITS_LENGTH,
1877 initial_deferred_credits_path: Some(initial_deferred_credits_file.path().to_path_buf()),
1878 };
1879
1880 let init_seed = "";
1881 let pos_state_0 = PoSFinalState::new(
1882 pos_config.clone(),
1883 init_seed,
1884 &initial_rolls_file_0.path().to_path_buf(),
1885 selector_controller,
1886 db.clone(),
1887 );
1888
1889 assert!(pos_state_0.is_ok());
1891
1892 let initial_rolls_file_1 =
1894 tempfile::NamedTempFile::new().expect("could not create temporary initial rolls file");
1895 let data_1 = HashMap::from([("foo", "bar")]);
1896 let rolls_file_contents_1 = serde_json::to_string(&data_1).unwrap();
1897 std::fs::write(
1899 initial_rolls_file_1.path(),
1900 rolls_file_contents_1.as_bytes(),
1901 )
1902 .expect("failed writing initial rolls file 1");
1903 let selector_controller = Box::new(MockSelectorController::new());
1904
1905 let pos_state_1 = PoSFinalState::new(
1906 pos_config.clone(),
1907 init_seed,
1908 &initial_rolls_file_1.path().to_path_buf(),
1909 selector_controller,
1910 db.clone(),
1911 );
1912
1913 assert!(pos_state_1.is_err());
1915
1916 let addr1_ = "AU12pAcVUzsgUBJHaYSAtDKVTYnUT9NorBDjoDovMfAFTLFa16MNa";
1919 let addr1 = Address::from_str(addr1_).unwrap();
1920 let roll1: u64 = 5;
1921 let addr2_ = "AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53";
1922 let addr2 = Address::from_str(addr2_).unwrap();
1923 let roll2: u64 = 65529;
1924
1925 let initial_rolls_file_2 =
1926 tempfile::NamedTempFile::new().expect("could not create temporary initial rolls file");
1927 let data_2 = HashMap::from([(addr1_, roll1), (addr2_, roll2)]);
1928 let rolls_file_contents_2 = serde_json::to_string(&data_2).unwrap();
1929 std::fs::write(
1930 initial_rolls_file_2.path(),
1931 rolls_file_contents_2.as_bytes(),
1932 )
1933 .expect("failed writing initial rolls file 2");
1934 let selector_controller = Box::new(MockSelectorController::new());
1935
1936 let mut pos_state_2 = PoSFinalState::new(
1937 pos_config,
1938 init_seed,
1939 &initial_rolls_file_2.path().to_path_buf(),
1940 selector_controller,
1941 db.clone(),
1942 )
1943 .unwrap();
1944
1945 assert_eq!(pos_state_2.initial_rolls.get(&addr1), Some(&roll1));
1946 assert_eq!(pos_state_2.initial_rolls.get(&addr2), Some(&roll2));
1947 assert_eq!(pos_state_2.get_address_active_rolls(&addr1, 0), Some(roll1));
1949 assert_eq!(
1951 pos_state_2.get_all_active_rolls(0).get(&addr1),
1952 Some(&roll1)
1953 );
1954 assert_eq!(
1955 pos_state_2.get_all_active_rolls(0).get(&addr2),
1956 Some(&roll2)
1957 );
1958
1959 let roll_counts_c0 = BTreeMap::from([(addr1, roll1), (addr2, roll2)]);
1961 let roll_a1_c1 = roll1.checked_sub(1).unwrap();
1962 let roll_counts_c1 =
1963 BTreeMap::from([(addr1, roll_a1_c1), (addr2, roll2.checked_add(1).unwrap())]);
1964 let roll_counts_c2 = BTreeMap::from([
1965 (addr1, roll1.checked_sub(2).unwrap()),
1966 (addr2, roll2.checked_add(2).unwrap()),
1967 ]);
1968 let roll_a1_c3 = roll1.checked_sub(3).unwrap();
1969 let roll_a2_c3 = roll2.checked_add(3).unwrap();
1970 let roll_counts_c3 = BTreeMap::from([(addr1, roll_a1_c3), (addr2, roll_a2_c3)]);
1971 let roll_a1_c4 = roll1.checked_sub(4).unwrap();
1972 let roll_a2_c4 = roll2.checked_add(4).unwrap();
1973 let roll_counts_c4 = BTreeMap::from([(addr1, roll_a1_c4), (addr2, roll_a2_c4)]);
1974 let cycle_info_0 = CycleInfo::new(
1975 0,
1976 Default::default(),
1977 roll_counts_c0,
1978 Default::default(),
1979 Default::default(),
1980 );
1981 let cycle_info_1 = CycleInfo {
1982 cycle: 1,
1983 roll_counts: roll_counts_c1.clone(),
1984 ..cycle_info_0.clone()
1985 };
1986 let cycle_info_2 = CycleInfo {
1987 cycle: 2,
1988 roll_counts: roll_counts_c2,
1989 ..cycle_info_0.clone()
1990 };
1991 let cycle_info_3 = CycleInfo {
1992 cycle: 3,
1993 roll_counts: roll_counts_c3,
1994 ..cycle_info_0.clone()
1995 };
1996 let cycle_info_4 = CycleInfo {
1997 cycle: 4,
1998 roll_counts: roll_counts_c4,
1999 ..cycle_info_0.clone()
2000 };
2001
2002 let mut batch = DBBatch::new();
2003 pos_state_2.put_new_cycle_info(&cycle_info_0, &mut batch);
2004 pos_state_2.put_new_cycle_info(&cycle_info_1, &mut batch);
2005 pos_state_2.put_new_cycle_info(&cycle_info_2, &mut batch);
2006 pos_state_2.put_new_cycle_info(&cycle_info_3, &mut batch);
2007 pos_state_2.put_new_cycle_info(&cycle_info_4, &mut batch);
2008 pos_state_2
2009 .db
2010 .write()
2011 .write_batch(batch, DBBatch::new(), None);
2012
2013 assert_eq!(
2014 pos_state_2.get_address_active_rolls(&addr1, 4),
2015 Some(roll_a1_c1)
2016 );
2017 assert_eq!(
2018 pos_state_2.get_address_active_rolls(&addr1, 4 + 3 + 1),
2019 None
2020 );
2021 assert_eq!(pos_state_2.get_address_active_rolls(&addr1, 19), None);
2022
2023 let rolls_1 = pos_state_2.get_rolls_for(&addr1);
2024 assert_eq!(rolls_1, roll_a1_c4);
2025 let rolls_2 = pos_state_2.get_rolls_for(&addr2);
2026 assert_eq!(rolls_2, roll_a2_c4);
2027
2028 let active_rolls = pos_state_2.get_all_active_rolls(4);
2030 assert_eq!(active_rolls, roll_counts_c1);
2031 }
2032
2033 #[test]
2038 fn test_pos_cache_recomputation() {
2039 let pos_config = PoSConfig {
2040 periods_per_cycle: 2,
2041 thread_count: 2,
2042 cycle_history_length: POS_SAVED_CYCLES,
2043 max_rolls_length: MAX_ROLLS_COUNT_LENGTH,
2044 max_production_stats_length: MAX_PRODUCTION_STATS_LENGTH,
2045 max_credit_length: MAX_DEFERRED_CREDITS_LENGTH,
2046 initial_deferred_credits_path: None,
2047 };
2048
2049 let tempdir = TempDir::new().expect("cannot create temp directory");
2051 let db_config = MassaDBConfig {
2052 path: tempdir.path().to_path_buf(),
2053 max_history_length: 10,
2054 max_final_state_elements_size: 100_000,
2055 max_versioning_elements_size: 100_000,
2056 thread_count: 2,
2057 max_ledger_backups: 10,
2058 enable_metrics: false,
2059 };
2060 let db = Arc::new(RwLock::new(
2061 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2062 ));
2063 let selector_controller = Box::new(MockSelectorController::new());
2064 let init_seed = Hash::compute_from(b"");
2065 let initial_seeds = vec![Hash::compute_from(init_seed.to_bytes()), init_seed];
2066
2067 let cycle_info_deserializer = CycleInfoDeserializer::new(
2068 pos_config.max_rolls_length,
2069 pos_config.max_production_stats_length,
2070 );
2071
2072 let mut pos_state = PoSFinalState {
2073 config: pos_config.clone(),
2074 db: db.clone(),
2075 cycle_history_cache: Default::default(),
2076 rng_seed_cache: None,
2077 selector: selector_controller,
2078 initial_rolls: Default::default(),
2079 initial_seeds,
2080 credits_serializer: DeferredCreditSerializer::new(),
2081 credits_deserializer: DeferredCreditDeserializer::new(pos_config.max_credit_length),
2082 cycle_info_serializer: CycleInfoSerializer::new(),
2083 cycle_info_deserializer,
2084 };
2085
2086 let mut cycle_infos = Vec::new();
2088 for cycle in 509..516 {
2089 cycle_infos.push(CycleInfo::new(
2090 cycle,
2091 Default::default(),
2092 Default::default(),
2093 Default::default(),
2094 Default::default(),
2095 ));
2096 }
2097
2098 let mut batch = DBBatch::new();
2099 pos_state.put_new_cycle_info(&cycle_infos[0], &mut batch);
2100 pos_state.put_new_cycle_info(&cycle_infos[1], &mut batch);
2101 pos_state.put_new_cycle_info(&cycle_infos[2], &mut batch);
2102 pos_state.put_new_cycle_info(&cycle_infos[3], &mut batch);
2103 pos_state.put_new_cycle_info(&cycle_infos[4], &mut batch);
2104 pos_state.put_new_cycle_info(&cycle_infos[5], &mut batch);
2105 pos_state.put_new_cycle_info(&cycle_infos[6], &mut batch);
2106
2107 pos_state
2108 .db
2109 .write()
2110 .write_batch(batch, DBBatch::new(), None);
2111
2112 pos_state.cycle_history_cache.clear();
2115 pos_state.recompute_pos_state_caches();
2116
2117 assert_eq!(
2119 pos_state.cycle_history_cache.len(),
2120 cycle_infos.len(),
2121 "Cycle history len does not match"
2122 );
2123
2124 for ((cycle_from_history, _), cycle) in
2125 pos_state.cycle_history_cache.iter().zip(cycle_infos.iter())
2126 {
2127 assert_eq!(
2128 *cycle_from_history, cycle.cycle,
2129 "Cycle number does not match"
2130 );
2131 }
2132 }
2133
2134 #[test]
2136 fn test_pos_final_state_hash_computation() {
2137 let pos_config = PoSConfig {
2138 periods_per_cycle: 4,
2140 thread_count: 2,
2141 cycle_history_length: POS_SAVED_CYCLES,
2142 max_rolls_length: MAX_ROLLS_COUNT_LENGTH,
2143 max_production_stats_length: MAX_PRODUCTION_STATS_LENGTH,
2144 max_credit_length: MAX_DEFERRED_CREDITS_LENGTH,
2145 initial_deferred_credits_path: None,
2146 };
2147
2148 let tempdir = TempDir::new().expect("cannot create temp directory");
2150 let db_config = MassaDBConfig {
2151 path: tempdir.path().to_path_buf(),
2152 max_history_length: 10,
2153 max_final_state_elements_size: 100_000,
2154 max_versioning_elements_size: 100_000,
2155 thread_count: 2,
2156 max_ledger_backups: 10,
2157 enable_metrics: false,
2158 };
2159 let db = Arc::new(RwLock::new(
2160 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2161 ));
2162 let selector_controller = Box::new(MockSelectorController::new());
2163 let init_seed = Hash::compute_from(b"");
2164 let initial_seeds = vec![Hash::compute_from(init_seed.to_bytes()), init_seed];
2165
2166 let cycle_info_deserializer = CycleInfoDeserializer::new(
2167 pos_config.max_rolls_length,
2168 pos_config.max_production_stats_length,
2169 );
2170
2171 let mut pos_state = PoSFinalState {
2172 config: pos_config.clone(),
2173 db: db.clone(),
2174 cycle_history_cache: Default::default(),
2175 rng_seed_cache: None,
2176 selector: selector_controller,
2177 initial_rolls: Default::default(),
2178 initial_seeds,
2179 credits_serializer: DeferredCreditSerializer::new(),
2180 credits_deserializer: DeferredCreditDeserializer::new(pos_config.max_credit_length),
2181 cycle_info_serializer: CycleInfoSerializer::new(),
2182 cycle_info_deserializer,
2183 };
2184
2185 pos_state.recompute_pos_state_caches();
2186
2187 let mut batch = DBBatch::new();
2188 pos_state.create_initial_cycle(&mut batch);
2189 db.write()
2190 .write_batch(batch, Default::default(), Some(Slot::new(0, 0)));
2191
2192 let addr = Address::from_public_key(&KeyPair::generate(0).unwrap().get_public_key());
2194 let mut roll_changes = PreHashMap::default();
2195 roll_changes.insert(addr, 10);
2196 let mut production_stats = PreHashMap::default();
2197 production_stats.insert(
2198 addr,
2199 ProductionStats {
2200 block_success_count: 4,
2201 block_failure_count: 0,
2202 },
2203 );
2204 let changes = PoSChanges {
2205 seed_bits: bitvec![u8, Lsb0; 0, 1],
2206 roll_changes: roll_changes.clone(),
2207 production_stats: production_stats.clone(),
2208 deferred_credits: DeferredCredits::new(),
2209 };
2210
2211 let mut batch = DBBatch::new();
2212 pos_state
2213 .apply_changes_to_batch(changes, Slot::new(0, 0), false, &mut batch)
2214 .unwrap();
2215 db.write()
2216 .write_batch(batch, Default::default(), Some(Slot::new(0, 0)));
2217
2218 roll_changes.clear();
2220 roll_changes.insert(addr, 20);
2221 production_stats.clear();
2222 production_stats.insert(
2223 addr,
2224 ProductionStats {
2225 block_success_count: 4,
2226 block_failure_count: 6,
2227 },
2228 );
2229 let changes = PoSChanges {
2230 seed_bits: bitvec![u8, Lsb0; 1, 0],
2231 roll_changes: roll_changes.clone(),
2232 production_stats: production_stats.clone(),
2233 deferred_credits: DeferredCredits::new(),
2234 };
2235
2236 let mut batch = DBBatch::new();
2237 pos_state
2238 .apply_changes_to_batch(changes, Slot::new(0, 1), false, &mut batch)
2239 .unwrap();
2240 db.write()
2241 .write_batch(batch, Default::default(), Some(Slot::new(0, 1)));
2242
2243 roll_changes.clear();
2245 roll_changes.insert(addr, 0);
2246 production_stats.clear();
2247 production_stats.insert(
2248 addr,
2249 ProductionStats {
2250 block_success_count: 4,
2251 block_failure_count: 12,
2252 },
2253 );
2254
2255 let changes = PoSChanges {
2256 seed_bits: bitvec![u8, Lsb0; 0, 1],
2257 roll_changes,
2258 production_stats,
2259 deferred_credits: DeferredCredits::new(),
2260 };
2261
2262 let mut batch = DBBatch::new();
2263 pos_state
2264 .apply_changes_to_batch(changes, Slot::new(1, 0), false, &mut batch)
2265 .unwrap();
2266 db.write()
2267 .write_batch(batch, Default::default(), Some(Slot::new(1, 0)));
2268
2269 let cycles = pos_state.get_cycle_history_cycles();
2270 assert_eq!(cycles.len(), 1, "wrong number of cycles");
2271 assert_eq!(cycles[0].0, 0, "cycle should be the 1st one");
2272 assert!(!cycles[0].1, "cycle should not be complete yet");
2273
2274 let cycle_info_a = pos_state.get_cycle_info(0).unwrap();
2275
2276 let mut prod_stats = HashMap::default();
2277 prod_stats.insert(
2278 addr,
2279 ProductionStats {
2280 block_success_count: 12,
2281 block_failure_count: 18,
2282 },
2283 );
2284
2285 let cycle_info_b = CycleInfo::new(
2286 0,
2287 false,
2288 BTreeMap::default(),
2289 bitvec![u8, Lsb0; 0, 0, 0, 1, 1, 0, 0, 1],
2290 prod_stats,
2291 );
2292
2293 assert_eq!(cycle_info_a, cycle_info_b, "cycle_info mismatch");
2294 }
2295
2296 #[test]
2297 fn test_feed_selector() {
2298 let initial_deferred_credits_file =
2299 tempfile::NamedTempFile::new().expect("could not create temporary initial rolls file");
2300
2301 let initial_rolls_file_0 =
2302 tempfile::NamedTempFile::new().expect("could not create temporary initial rolls file");
2303
2304 let rolls_file_contents_0 = "{}";
2306 std::fs::write(
2307 initial_rolls_file_0.path(),
2308 rolls_file_contents_0.as_bytes(),
2309 )
2310 .expect("failed writing initial rolls file 0");
2311
2312 let deferred_credits_file_contents = "{}";
2314 std::fs::write(
2315 initial_deferred_credits_file.path(),
2316 deferred_credits_file_contents.as_bytes(),
2317 )
2318 .expect("failed writing initial deferred credits file");
2319
2320 let tempdir = tempfile::TempDir::new().expect("cannot create temp directory");
2322 let db_config = MassaDBConfig {
2323 path: tempdir.path().to_path_buf(),
2324 max_history_length: 10,
2325 max_final_state_elements_size: 100,
2326 max_versioning_elements_size: 100,
2327 thread_count: 2,
2328 max_ledger_backups: 10,
2329 enable_metrics: false,
2330 };
2331 let db = Arc::new(RwLock::new(
2332 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2333 ));
2334 let selector_controller = Box::new(MockSelectorController::new());
2335
2336 let pos_config = PoSConfig {
2337 periods_per_cycle: 2,
2338 thread_count: 2,
2339 cycle_history_length: POS_SAVED_CYCLES,
2340 max_rolls_length: MAX_ROLLS_COUNT_LENGTH,
2341 max_production_stats_length: MAX_PRODUCTION_STATS_LENGTH,
2342 max_credit_length: MAX_DEFERRED_CREDITS_LENGTH,
2343 initial_deferred_credits_path: Some(initial_deferred_credits_file.path().to_path_buf()),
2344 };
2345
2346 let init_seed = "";
2347 let mut pos_state_0 = PoSFinalState::new(
2348 pos_config,
2349 init_seed,
2350 &initial_rolls_file_0.path().to_path_buf(),
2351 selector_controller,
2352 db.clone(),
2353 )
2354 .unwrap();
2355
2356 let cycle_info_0 = CycleInfo::new(
2357 0,
2358 true,
2359 Default::default(),
2360 Default::default(),
2361 Default::default(),
2362 );
2363 let cycle_info_1 = CycleInfo {
2364 cycle: 1,
2365 ..cycle_info_0.clone()
2366 };
2367 let cycle_info_2 = CycleInfo {
2368 cycle: 2,
2369 ..cycle_info_0.clone()
2370 };
2371 let cycle_info_3 = CycleInfo {
2372 cycle: 3,
2373 ..cycle_info_0.clone()
2374 };
2375 let cycle_info_4 = CycleInfo {
2376 cycle: 4,
2377 complete: false,
2378 ..cycle_info_0.clone()
2379 };
2380
2381 let mut batch = DBBatch::new();
2382 pos_state_0.put_new_cycle_info(&cycle_info_0, &mut batch);
2383 pos_state_0.put_new_cycle_info(&cycle_info_1, &mut batch);
2384 pos_state_0.put_new_cycle_info(&cycle_info_2, &mut batch);
2385 pos_state_0.put_new_cycle_info(&cycle_info_3, &mut batch);
2386 pos_state_0.put_new_cycle_info(&cycle_info_4, &mut batch);
2387 pos_state_0
2388 .db
2389 .write()
2390 .write_batch(batch, DBBatch::new(), None);
2391
2392 assert_matches!(
2394 pos_state_0.feed_selector(4 + 3),
2395 Err(PosError::CycleUnfinished(4))
2396 );
2397
2398 assert_matches!(
2400 pos_state_0.feed_selector(4),
2401 Err(PosError::ContainerInconsistency(_))
2402 );
2403 assert_matches!(
2404 pos_state_0.validate_selector_history(),
2405 Err(PosError::ContainerInconsistency(_))
2406 );
2407 }
2408
2409 #[test]
2410 fn test_feed_selector_2() {
2411 let initial_deferred_credits_file =
2412 tempfile::NamedTempFile::new().expect("could not create temporary initial rolls file");
2413
2414 let initial_rolls_file_0 =
2415 tempfile::NamedTempFile::new().expect("could not create temporary initial rolls file");
2416
2417 let rolls_file_contents_0 = "{}";
2419 std::fs::write(
2420 initial_rolls_file_0.path(),
2421 rolls_file_contents_0.as_bytes(),
2422 )
2423 .expect("failed writing initial rolls file 0");
2424
2425 let deferred_credits_file_contents = "{}";
2427 std::fs::write(
2428 initial_deferred_credits_file.path(),
2429 deferred_credits_file_contents.as_bytes(),
2430 )
2431 .expect("failed writing initial deferred credits file");
2432
2433 let tempdir = tempfile::TempDir::new().expect("cannot create temp directory");
2435 let db_config = MassaDBConfig {
2436 path: tempdir.path().to_path_buf(),
2437 max_history_length: 10,
2438 thread_count: 2,
2439 max_final_state_elements_size: 100,
2440 max_versioning_elements_size: 100,
2441 max_ledger_backups: 10,
2442 enable_metrics: false,
2443 };
2444 let db = Arc::new(RwLock::new(
2445 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2446 ));
2447 let mut selector_controller = Box::new(MockSelectorController::new());
2448 selector_controller
2449 .expect_feed_cycle()
2450 .times(1)
2451 .returning(|_, _, _| Ok(()));
2452
2453 let pos_config = PoSConfig {
2454 periods_per_cycle: 2,
2455 thread_count: 2,
2456 cycle_history_length: POS_SAVED_CYCLES,
2457 max_rolls_length: MAX_ROLLS_COUNT_LENGTH,
2458 max_production_stats_length: MAX_PRODUCTION_STATS_LENGTH,
2459 max_credit_length: MAX_DEFERRED_CREDITS_LENGTH,
2460 initial_deferred_credits_path: Some(initial_deferred_credits_file.path().to_path_buf()),
2461 };
2462
2463 let init_seed = "";
2464 let mut pos_state_0 = PoSFinalState::new(
2465 pos_config,
2466 init_seed,
2467 &initial_rolls_file_0.path().to_path_buf(),
2468 selector_controller,
2469 db.clone(),
2470 )
2471 .unwrap();
2472
2473 let cycle_info_0 = CycleInfo::new(
2474 0,
2475 true,
2476 Default::default(),
2477 Default::default(),
2478 Default::default(),
2479 );
2480 let cycle_info_1 = CycleInfo {
2481 cycle: 1,
2482 ..cycle_info_0.clone()
2483 };
2484 let cycle_info_2 = CycleInfo {
2485 cycle: 2,
2486 ..cycle_info_0.clone()
2487 };
2488 let cycle_info_3 = CycleInfo {
2489 cycle: 3,
2490 ..cycle_info_0.clone()
2491 };
2492 let cycle_info_4 = CycleInfo {
2493 cycle: 4,
2494 complete: false,
2495 ..cycle_info_0.clone()
2496 };
2497
2498 let mut batch = DBBatch::new();
2499 pos_state_0.put_new_cycle_info(&cycle_info_0, &mut batch);
2500 pos_state_0.put_new_cycle_info(&cycle_info_1, &mut batch);
2501 pos_state_0.put_new_cycle_info(&cycle_info_2, &mut batch);
2502 pos_state_0.put_new_cycle_info(&cycle_info_3, &mut batch);
2503 pos_state_0.put_new_cycle_info(&cycle_info_4, &mut batch);
2504 pos_state_0
2505 .db
2506 .write()
2507 .write_batch(batch, DBBatch::new(), None);
2508
2509 let _ = pos_state_0.feed_selector(2);
2511 }
2512
2513 #[test]
2515 fn test_rng_seed_validation_rejects_oversized_seed() {
2516 let pos_config = PoSConfig {
2517 periods_per_cycle: 2,
2518 thread_count: 2, cycle_history_length: POS_SAVED_CYCLES,
2520 max_rolls_length: MAX_ROLLS_COUNT_LENGTH,
2521 max_production_stats_length: MAX_PRODUCTION_STATS_LENGTH,
2522 max_credit_length: MAX_DEFERRED_CREDITS_LENGTH,
2523 initial_deferred_credits_path: None,
2524 };
2525
2526 let tempdir = TempDir::new().expect("cannot create temp directory");
2527 let db_config = MassaDBConfig {
2528 path: tempdir.path().to_path_buf(),
2529 max_history_length: 10,
2530 max_final_state_elements_size: 100_000,
2531 max_versioning_elements_size: 100_000,
2532 thread_count: 2,
2533 max_ledger_backups: 10,
2534 enable_metrics: false,
2535 };
2536 let db = Arc::new(RwLock::new(
2537 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2538 ));
2539 let selector_controller = Box::new(MockSelectorController::new());
2540 let init_seed = Hash::compute_from(b"");
2541 let initial_seeds = vec![Hash::compute_from(init_seed.to_bytes()), init_seed];
2542
2543 let cycle_info_deserializer = CycleInfoDeserializer::new(
2544 pos_config.max_rolls_length,
2545 pos_config.max_production_stats_length,
2546 );
2547
2548 let mut pos_state = PoSFinalState {
2549 config: pos_config.clone(),
2550 db: db.clone(),
2551 cycle_history_cache: Default::default(),
2552 rng_seed_cache: None,
2553 selector: selector_controller,
2554 initial_rolls: Default::default(),
2555 initial_seeds,
2556 credits_serializer: DeferredCreditSerializer::new(),
2557 credits_deserializer: DeferredCreditDeserializer::new(pos_config.max_credit_length),
2558 cycle_info_serializer: CycleInfoSerializer::new(),
2559 cycle_info_deserializer,
2560 };
2561
2562 let oversized = bitvec![u8, Lsb0; 0, 1, 0, 1, 0]; let cycle_info = CycleInfo::new(
2564 0,
2565 false,
2566 BTreeMap::default(),
2567 oversized.clone(),
2568 PreHashMap::default(),
2569 );
2570 let mut batch = DBBatch::new();
2571 pos_state.put_new_cycle_info(&cycle_info, &mut batch);
2572 db.write().write_batch(batch, DBBatch::new(), None);
2573
2574 let prefix = pos_state.cycle_history_cycle_prefix(0);
2575 let key = rng_seed_key!(prefix);
2576 let mut serialized_value = Vec::new();
2577 pos_state
2578 .cycle_info_serializer
2579 .bitvec_ser
2580 .serialize(&oversized, &mut serialized_value)
2581 .unwrap();
2582
2583 assert!(
2584 !pos_state.is_cycle_history_key_value_valid(&key, &serialized_value),
2585 "oversized RNG seed must be rejected"
2586 );
2587
2588 let short_seed = bitvec![u8, Lsb0; 0, 1];
2590 let mut batch = DBBatch::new();
2591 pos_state.put_cycle_history_rng_seed(0, short_seed, &mut batch);
2592 pos_state.put_cycle_history_complete(0, true, &mut batch);
2593 db.write().write_batch(batch, DBBatch::new(), None);
2594
2595 let complete_key = complete_key!(prefix);
2596 assert!(
2597 !pos_state.is_cycle_history_key_value_valid(&complete_key, &[1]),
2598 "complete cycle with wrong RNG seed length must be rejected"
2599 );
2600 }
2601
2602 #[test]
2604 fn test_apply_changes_returns_error_on_seed_length_mismatch() {
2605 let pos_config = PoSConfig {
2606 periods_per_cycle: 2,
2607 thread_count: 2, cycle_history_length: POS_SAVED_CYCLES,
2609 max_rolls_length: MAX_ROLLS_COUNT_LENGTH,
2610 max_production_stats_length: MAX_PRODUCTION_STATS_LENGTH,
2611 max_credit_length: MAX_DEFERRED_CREDITS_LENGTH,
2612 initial_deferred_credits_path: None,
2613 };
2614
2615 let tempdir = TempDir::new().expect("cannot create temp directory");
2616 let db_config = MassaDBConfig {
2617 path: tempdir.path().to_path_buf(),
2618 max_history_length: 10,
2619 max_final_state_elements_size: 100_000,
2620 max_versioning_elements_size: 100_000,
2621 thread_count: 2,
2622 max_ledger_backups: 10,
2623 enable_metrics: false,
2624 };
2625 let db = Arc::new(RwLock::new(
2626 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2627 ));
2628 let selector_controller = Box::new(MockSelectorController::new());
2629 let init_seed = Hash::compute_from(b"");
2630 let initial_seeds = vec![Hash::compute_from(init_seed.to_bytes()), init_seed];
2631
2632 let cycle_info_deserializer = CycleInfoDeserializer::new(
2633 pos_config.max_rolls_length,
2634 pos_config.max_production_stats_length,
2635 );
2636
2637 let mut pos_state = PoSFinalState {
2638 config: pos_config.clone(),
2639 db: db.clone(),
2640 cycle_history_cache: Default::default(),
2641 rng_seed_cache: None,
2642 selector: selector_controller,
2643 initial_rolls: Default::default(),
2644 initial_seeds,
2645 credits_serializer: DeferredCreditSerializer::new(),
2646 credits_deserializer: DeferredCreditDeserializer::new(pos_config.max_credit_length),
2647 cycle_info_serializer: CycleInfoSerializer::new(),
2648 cycle_info_deserializer,
2649 };
2650
2651 let mut batch = DBBatch::new();
2653 pos_state.put_new_cycle_info(
2654 &CycleInfo::new(
2655 0,
2656 false,
2657 BTreeMap::default(),
2658 bitvec![u8, Lsb0; 0, 1],
2659 PreHashMap::default(),
2660 ),
2661 &mut batch,
2662 );
2663 db.write()
2664 .write_batch(batch, Default::default(), Some(Slot::new(1, 0)));
2665 pos_state.recompute_pos_state_caches();
2666
2667 let changes = PoSChanges {
2669 seed_bits: bitvec![u8, Lsb0; 1],
2670 roll_changes: PreHashMap::default(),
2671 production_stats: PreHashMap::default(),
2672 deferred_credits: DeferredCredits::new(),
2673 };
2674 let mut batch = DBBatch::new();
2675 let err = pos_state
2676 .apply_changes_to_batch(changes, Slot::new(1, 1), false, &mut batch)
2677 .expect_err("seed length mismatch on cycle completion must be an error");
2678 assert_matches!(err, PosError::ContainerInconsistency(_));
2679 }
2680
2681 #[test]
2684 fn test_create_new_cycle_from_last_caps_seed_on_complete() {
2685 let pos_config = PoSConfig {
2686 periods_per_cycle: 2,
2687 thread_count: 2, cycle_history_length: POS_SAVED_CYCLES,
2689 max_rolls_length: MAX_ROLLS_COUNT_LENGTH,
2690 max_production_stats_length: MAX_PRODUCTION_STATS_LENGTH,
2691 max_credit_length: MAX_DEFERRED_CREDITS_LENGTH,
2692 initial_deferred_credits_path: None,
2693 };
2694
2695 let tempdir = TempDir::new().expect("cannot create temp directory");
2696 let db_config = MassaDBConfig {
2697 path: tempdir.path().to_path_buf(),
2698 max_history_length: 10,
2699 max_final_state_elements_size: 100_000,
2700 max_versioning_elements_size: 100_000,
2701 thread_count: 2,
2702 max_ledger_backups: 10,
2703 enable_metrics: false,
2704 };
2705 let db = Arc::new(RwLock::new(
2706 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2707 ));
2708 let selector_controller = Box::new(MockSelectorController::new());
2709 let init_seed = Hash::compute_from(b"");
2710 let initial_seeds = vec![Hash::compute_from(init_seed.to_bytes()), init_seed];
2711
2712 let cycle_info_deserializer = CycleInfoDeserializer::new(
2713 pos_config.max_rolls_length,
2714 pos_config.max_production_stats_length,
2715 );
2716
2717 let mut pos_state = PoSFinalState {
2718 config: pos_config.clone(),
2719 db: db.clone(),
2720 cycle_history_cache: Default::default(),
2721 rng_seed_cache: None,
2722 selector: selector_controller,
2723 initial_rolls: Default::default(),
2724 initial_seeds,
2725 credits_serializer: DeferredCreditSerializer::new(),
2726 credits_deserializer: DeferredCreditDeserializer::new(pos_config.max_credit_length),
2727 cycle_info_serializer: CycleInfoSerializer::new(),
2728 cycle_info_deserializer,
2729 };
2730
2731 let last_cycle = CycleInfo::new(
2733 0,
2734 false,
2735 BTreeMap::default(),
2736 bitvec![u8, Lsb0; 0, 0],
2737 PreHashMap::default(),
2738 );
2739 let mut batch = DBBatch::new();
2740 pos_state
2741 .create_new_cycle_from_last(
2742 &last_cycle,
2743 Slot::new(0, 1),
2744 Slot::new(1, 1), &mut batch,
2746 )
2747 .expect("interpolation should cap seed length instead of overflowing");
2748 db.write()
2749 .write_batch(batch, Default::default(), Some(Slot::new(1, 1)));
2750
2751 let cycle_info = pos_state.get_cycle_info(0).expect("cycle 0 should exist");
2752 assert!(cycle_info.complete);
2753 assert_eq!(cycle_info.rng_seed.len(), 4);
2754 }
2755
2756 #[test]
2757 fn test_create_new_cycle_from_last_production_stats() {
2758 let pos_config = PoSConfig {
2759 periods_per_cycle: 2,
2760 thread_count: 2, cycle_history_length: POS_SAVED_CYCLES,
2762 max_rolls_length: MAX_ROLLS_COUNT_LENGTH,
2763 max_production_stats_length: MAX_PRODUCTION_STATS_LENGTH,
2764 max_credit_length: MAX_DEFERRED_CREDITS_LENGTH,
2765 initial_deferred_credits_path: None,
2766 };
2767
2768 let tempdir = TempDir::new().expect("cannot create temp directory");
2769 let db_config = MassaDBConfig {
2770 path: tempdir.path().to_path_buf(),
2771 max_history_length: 10,
2772 max_final_state_elements_size: 100_000,
2773 max_versioning_elements_size: 100_000,
2774 thread_count: 2,
2775 max_ledger_backups: 10,
2776 enable_metrics: false,
2777 };
2778 let db = Arc::new(RwLock::new(
2779 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2780 ));
2781 let selector_controller = Box::new(MockSelectorController::new());
2782 let init_seed = Hash::compute_from(b"");
2783 let initial_seeds = vec![Hash::compute_from(init_seed.to_bytes()), init_seed];
2784
2785 let cycle_info_deserializer = CycleInfoDeserializer::new(
2786 pos_config.max_rolls_length,
2787 pos_config.max_production_stats_length,
2788 );
2789
2790 let mut pos_state = PoSFinalState {
2791 config: pos_config.clone(),
2792 db: db.clone(),
2793 cycle_history_cache: Default::default(),
2794 rng_seed_cache: None,
2795 selector: selector_controller,
2796 initial_rolls: Default::default(),
2797 initial_seeds,
2798 credits_serializer: DeferredCreditSerializer::new(),
2799 credits_deserializer: DeferredCreditDeserializer::new(pos_config.max_credit_length),
2800 cycle_info_serializer: CycleInfoSerializer::new(),
2801 cycle_info_deserializer,
2802 };
2803
2804 let stats_addr =
2806 Address::from_str("AU12pAcVUzsgUBJHaYSAtDKVTYnUT9NorBDjoDovMfAFTLFa16MNa").unwrap();
2807 let mut snapshot_stats = PreHashMap::default();
2808 snapshot_stats.insert(
2809 stats_addr,
2810 ProductionStats {
2811 block_success_count: 3,
2812 block_failure_count: 1,
2813 },
2814 );
2815 let snapshot = CycleInfo::new(
2816 0,
2817 false,
2818 BTreeMap::default(),
2819 bitvec![u8, Lsb0; 0, 0],
2820 snapshot_stats,
2821 );
2822
2823 let mut batch = DBBatch::new();
2825 pos_state
2826 .create_new_cycle_from_last(
2827 &snapshot,
2828 Slot::new(0, 1), Slot::new(0, 1),
2830 &mut batch,
2831 )
2832 .expect("same-cycle interpolation should succeed");
2833 db.write()
2834 .write_batch(batch, Default::default(), Some(Slot::new(0, 1)));
2835
2836 let cycle_0 = pos_state.get_cycle_info(0).expect("cycle 0 should exist");
2837 let kept = cycle_0
2838 .production_stats
2839 .get(&stats_addr)
2840 .expect("continuing the same incomplete cycle must keep production stats");
2841 assert_eq!(kept.block_success_count, 3);
2842 assert_eq!(kept.block_failure_count, 1);
2843
2844 let mut batch = DBBatch::new();
2846 pos_state
2847 .create_new_cycle_from_last(
2848 &snapshot,
2849 Slot::new(2, 0), Slot::new(3, 1), &mut batch,
2852 )
2853 .expect("new cycle interpolation should succeed");
2854 db.write()
2855 .write_batch(batch, Default::default(), Some(Slot::new(3, 1)));
2856
2857 let cycle_1 = pos_state.get_cycle_info(1).expect("cycle 1 should exist");
2858 assert!(
2859 cycle_1.production_stats.is_empty(),
2860 "interpolated new cycle must have empty production stats, got {:?}",
2861 cycle_1.production_stats
2862 );
2863 }
2864}