1mod noncanonical;
45mod pruning;
46#[cfg(test)]
47mod test;
48
49use codec::Codec;
50use log::trace;
51use noncanonical::NonCanonicalOverlay;
52use parking_lot::RwLock;
53use pruning::{HaveBlock, RefWindow};
54use std::{
55 collections::{hash_map::Entry, HashMap},
56 fmt,
57};
58
59const LOG_TARGET: &str = "state-db";
60const LOG_TARGET_PIN: &str = "state-db::pin";
61const PRUNING_MODE: &[u8] = b"mode";
62const PRUNING_MODE_ARCHIVE: &[u8] = b"archive";
63const PRUNING_MODE_ARCHIVE_CANON: &[u8] = b"archive_canonical";
64const PRUNING_MODE_CONSTRAINED: &[u8] = b"constrained";
65pub(crate) const DEFAULT_MAX_BLOCK_CONSTRAINT: u32 = 256;
66
67pub type DBValue = Vec<u8>;
69
70pub trait Hash:
72 Send
73 + Sync
74 + Sized
75 + Eq
76 + PartialEq
77 + Clone
78 + Default
79 + fmt::Debug
80 + Codec
81 + std::hash::Hash
82 + 'static
83{
84}
85impl<
86 T: Send
87 + Sync
88 + Sized
89 + Eq
90 + PartialEq
91 + Clone
92 + Default
93 + fmt::Debug
94 + Codec
95 + std::hash::Hash
96 + 'static,
97 > Hash for T
98{
99}
100
101pub trait MetaDb {
103 type Error: fmt::Debug;
104
105 fn get_meta(&self, key: &[u8]) -> Result<Option<DBValue>, Self::Error>;
107}
108
109pub trait NodeDb {
111 type Key: ?Sized;
112 type Error: fmt::Debug;
113
114 fn get(&self, key: &Self::Key) -> Result<Option<DBValue>, Self::Error>;
116}
117
118#[derive(Eq, PartialEq)]
120pub enum Error<E> {
121 Db(E),
123 StateDb(StateDbError),
124}
125
126#[derive(Eq, PartialEq)]
127pub enum StateDbError {
128 Decoding(codec::Error),
130 InvalidBlock,
132 InvalidBlockNumber,
134 InvalidParent,
136 IncompatiblePruningModes { stored: PruningMode, requested: PruningMode },
138 TooManySiblingBlocks { number: u64 },
140 BlockAlreadyExists,
142 Metadata(String),
144 BlockUnavailable,
146 BlockMissing,
148}
149
150impl<E> From<StateDbError> for Error<E> {
151 fn from(inner: StateDbError) -> Self {
152 Self::StateDb(inner)
153 }
154}
155
156#[derive(Debug)]
158pub enum PinError {
159 InvalidBlock,
161}
162
163impl<E: fmt::Debug> From<codec::Error> for Error<E> {
164 fn from(x: codec::Error) -> Self {
165 StateDbError::Decoding(x).into()
166 }
167}
168
169impl<E: fmt::Debug> fmt::Debug for Error<E> {
170 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171 match self {
172 Self::Db(e) => e.fmt(f),
173 Self::StateDb(e) => e.fmt(f),
174 }
175 }
176}
177
178impl fmt::Debug for StateDbError {
179 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180 match self {
181 Self::Decoding(e) => write!(f, "Error decoding sliceable value: {}", e),
182 Self::InvalidBlock => write!(f, "Trying to canonicalize invalid block"),
183 Self::InvalidBlockNumber => write!(f, "Trying to insert block with invalid number"),
184 Self::InvalidParent => write!(f, "Trying to insert block with unknown parent"),
185 Self::IncompatiblePruningModes { stored, requested } => write!(
186 f,
187 "Incompatible pruning modes [stored: {:?}; requested: {:?}]",
188 stored, requested
189 ),
190 Self::TooManySiblingBlocks { number } => {
191 write!(f, "Too many sibling blocks at #{number} inserted")
192 },
193 Self::BlockAlreadyExists => write!(f, "Block already exists"),
194 Self::Metadata(message) => write!(f, "Invalid metadata: {}", message),
195 Self::BlockUnavailable => {
196 write!(f, "Trying to get a block record from db while it is not commit to db yet")
197 },
198 Self::BlockMissing => write!(f, "Block record is missing from the pruning window"),
199 }
200 }
201}
202
203#[derive(Default, Debug, Clone)]
205pub struct ChangeSet<H: Hash> {
206 pub inserted: Vec<(H, DBValue)>,
208 pub deleted: Vec<H>,
210}
211
212#[derive(Default, Debug, Clone)]
214pub struct CommitSet<H: Hash> {
215 pub data: ChangeSet<H>,
217 pub meta: ChangeSet<Vec<u8>>,
219}
220
221#[derive(Debug, Clone, Eq, PartialEq)]
223pub struct Constraints {
224 pub max_blocks: Option<u32>,
227}
228
229#[derive(Debug, Clone, Eq, PartialEq)]
231pub enum PruningMode {
232 Constrained(Constraints),
234 ArchiveAll,
236 ArchiveCanonical,
238}
239
240impl PruningMode {
241 pub fn blocks_pruning(n: u32) -> PruningMode {
243 PruningMode::Constrained(Constraints { max_blocks: Some(n) })
244 }
245
246 pub fn is_archive(&self) -> bool {
248 match *self {
249 PruningMode::ArchiveAll | PruningMode::ArchiveCanonical => true,
250 PruningMode::Constrained(_) => false,
251 }
252 }
253
254 pub fn id(&self) -> &[u8] {
256 match self {
257 PruningMode::ArchiveAll => PRUNING_MODE_ARCHIVE,
258 PruningMode::ArchiveCanonical => PRUNING_MODE_ARCHIVE_CANON,
259 PruningMode::Constrained(_) => PRUNING_MODE_CONSTRAINED,
260 }
261 }
262
263 pub fn from_id(id: &[u8]) -> Option<Self> {
264 match id {
265 PRUNING_MODE_ARCHIVE => Some(Self::ArchiveAll),
266 PRUNING_MODE_ARCHIVE_CANON => Some(Self::ArchiveCanonical),
267 PRUNING_MODE_CONSTRAINED => Some(Self::Constrained(Default::default())),
268 _ => None,
269 }
270 }
271}
272
273impl Default for PruningMode {
274 fn default() -> Self {
275 PruningMode::Constrained(Default::default())
276 }
277}
278
279impl Default for Constraints {
280 fn default() -> Self {
281 Self { max_blocks: Some(DEFAULT_MAX_BLOCK_CONSTRAINT) }
282 }
283}
284
285fn to_meta_key<S: Codec>(suffix: &[u8], data: &S) -> Vec<u8> {
286 let mut buffer = data.encode();
287 buffer.extend(suffix);
288 buffer
289}
290
291#[derive(Clone, Debug, PartialEq, Eq)]
293pub enum LastCanonicalized {
294 None,
296 Block(u64),
298 NotCanonicalizing,
300}
301
302pub struct StateDbSync<BlockHash: Hash, Key: Hash, D: MetaDb> {
303 mode: PruningMode,
304 non_canonical: NonCanonicalOverlay<BlockHash, Key>,
305 pruning: Option<RefWindow<BlockHash, Key, D>>,
306 pinned: HashMap<BlockHash, u32>,
307 ref_counting: bool,
308}
309
310impl<BlockHash: Hash, Key: Hash, D: MetaDb> StateDbSync<BlockHash, Key, D> {
311 fn new(
312 mode: PruningMode,
313 ref_counting: bool,
314 db: D,
315 ) -> Result<StateDbSync<BlockHash, Key, D>, Error<D::Error>> {
316 trace!(target: LOG_TARGET, "StateDb settings: {:?}. Ref-counting: {}", mode, ref_counting);
317
318 let non_canonical: NonCanonicalOverlay<BlockHash, Key> = NonCanonicalOverlay::new(&db)?;
319 let pruning: Option<RefWindow<BlockHash, Key, D>> = match mode {
320 PruningMode::Constrained(Constraints { max_blocks }) =>
321 Some(RefWindow::new(db, max_blocks.unwrap_or(0), ref_counting)?),
322 PruningMode::ArchiveAll | PruningMode::ArchiveCanonical => None,
323 };
324
325 Ok(StateDbSync { mode, non_canonical, pruning, pinned: Default::default(), ref_counting })
326 }
327
328 fn insert_block(
329 &mut self,
330 hash: &BlockHash,
331 number: u64,
332 parent_hash: &BlockHash,
333 mut changeset: ChangeSet<Key>,
334 ) -> Result<CommitSet<Key>, Error<D::Error>> {
335 match self.mode {
336 PruningMode::ArchiveAll => {
337 changeset.deleted.clear();
338 Ok(CommitSet { data: changeset, meta: Default::default() })
340 },
341 PruningMode::Constrained(_) | PruningMode::ArchiveCanonical => self
342 .non_canonical
343 .insert(hash, number, parent_hash, changeset)
344 .map_err(Into::into),
345 }
346 }
347
348 fn canonicalize_block(&mut self, hash: &BlockHash) -> Result<CommitSet<Key>, Error<D::Error>> {
349 let mut commit = CommitSet::default();
354 if self.mode == PruningMode::ArchiveAll {
355 return Ok(commit)
356 }
357 let number = self.non_canonical.canonicalize(hash, &mut commit)?;
358 if self.mode == PruningMode::ArchiveCanonical {
359 commit.data.deleted.clear();
360 }
361 if let Some(ref mut pruning) = self.pruning {
362 pruning.note_canonical(hash, number, &mut commit)?;
363 }
364 self.prune(&mut commit)?;
365 Ok(commit)
366 }
367
368 fn last_canonicalized(&self) -> LastCanonicalized {
370 if self.mode == PruningMode::ArchiveAll {
371 LastCanonicalized::NotCanonicalizing
372 } else {
373 self.non_canonical
374 .last_canonicalized_block_number()
375 .map(LastCanonicalized::Block)
376 .unwrap_or_else(|| LastCanonicalized::None)
377 }
378 }
379
380 fn is_pruned(&self, hash: &BlockHash, number: u64) -> IsPruned {
381 match self.mode {
382 PruningMode::ArchiveAll => IsPruned::NotPruned,
383 PruningMode::ArchiveCanonical | PruningMode::Constrained(_) => {
384 if self
385 .non_canonical
386 .last_canonicalized_block_number()
387 .map(|c| number > c)
388 .unwrap_or(true)
389 {
390 if self.non_canonical.have_block(hash) {
391 IsPruned::NotPruned
392 } else {
393 IsPruned::Pruned
394 }
395 } else {
396 match self.pruning.as_ref() {
397 None => IsPruned::MaybePruned,
399 Some(pruning) => match pruning.have_block(hash, number) {
400 HaveBlock::No => IsPruned::Pruned,
401 HaveBlock::Yes => IsPruned::NotPruned,
402 HaveBlock::Maybe => IsPruned::MaybePruned,
403 },
404 }
405 }
406 },
407 }
408 }
409
410 fn prune(&mut self, commit: &mut CommitSet<Key>) -> Result<(), Error<D::Error>> {
411 if let (&mut Some(ref mut pruning), PruningMode::Constrained(constraints)) =
412 (&mut self.pruning, &self.mode)
413 {
414 loop {
415 if pruning.window_size() <= constraints.max_blocks.unwrap_or(0) as u64 {
416 break
417 }
418
419 let pinned = &self.pinned;
420 match pruning.next_hash() {
421 Err(Error::StateDb(StateDbError::BlockUnavailable)) => break,
423 res =>
424 if res?.map_or(false, |h| pinned.contains_key(&h)) {
425 break
426 },
427 }
428 match pruning.prune_one(commit) {
429 Err(Error::StateDb(StateDbError::BlockUnavailable)) => break,
432 res => res?,
433 }
434 }
435 }
436 Ok(())
437 }
438
439 fn revert_one(&mut self) -> Option<CommitSet<Key>> {
443 match self.mode {
444 PruningMode::ArchiveAll => Some(CommitSet::default()),
445 PruningMode::ArchiveCanonical | PruningMode::Constrained(_) =>
446 self.non_canonical.revert_one(),
447 }
448 }
449
450 fn remove(&mut self, hash: &BlockHash) -> Option<CommitSet<Key>> {
451 match self.mode {
452 PruningMode::ArchiveAll => Some(CommitSet::default()),
453 PruningMode::ArchiveCanonical | PruningMode::Constrained(_) =>
454 self.non_canonical.remove(hash),
455 }
456 }
457
458 fn pin<F>(&mut self, hash: &BlockHash, number: u64, hint: F) -> Result<(), PinError>
459 where
460 F: Fn() -> bool,
461 {
462 match self.mode {
463 PruningMode::ArchiveAll => Ok(()),
464 PruningMode::ArchiveCanonical | PruningMode::Constrained(_) => {
465 let have_block = self.non_canonical.have_block(hash) ||
466 self.pruning.as_ref().map_or_else(
467 || hint(),
468 |pruning| match pruning.have_block(hash, number) {
469 HaveBlock::No => false,
470 HaveBlock::Yes => true,
471 HaveBlock::Maybe => hint(),
472 },
473 );
474 if have_block {
475 let refs = self.pinned.entry(hash.clone()).or_default();
476 if *refs == 0 {
477 trace!(target: LOG_TARGET_PIN, "Pinned block: {:?}", hash);
478 self.non_canonical.pin(hash);
479 }
480 *refs += 1;
481 Ok(())
482 } else {
483 Err(PinError::InvalidBlock)
484 }
485 },
486 }
487 }
488
489 fn unpin(&mut self, hash: &BlockHash) {
490 match self.pinned.entry(hash.clone()) {
491 Entry::Occupied(mut entry) => {
492 *entry.get_mut() -= 1;
493 if *entry.get() == 0 {
494 trace!(target: LOG_TARGET_PIN, "Unpinned block: {:?}", hash);
495 entry.remove();
496 self.non_canonical.unpin(hash);
497 } else {
498 trace!(target: LOG_TARGET_PIN, "Releasing reference for {:?}", hash);
499 }
500 },
501 Entry::Vacant(_) => {},
502 }
503 }
504
505 fn sync(&mut self) {
506 self.non_canonical.sync();
507 }
508
509 pub fn get<DB: NodeDb, Q: ?Sized>(
510 &self,
511 key: &Q,
512 db: &DB,
513 ) -> Result<Option<DBValue>, Error<DB::Error>>
514 where
515 Q: AsRef<DB::Key>,
516 Key: std::borrow::Borrow<Q>,
517 Q: std::hash::Hash + Eq,
518 {
519 if let Some(value) = self.non_canonical.get(key) {
520 return Ok(Some(value))
521 }
522 db.get(key.as_ref()).map_err(Error::Db)
523 }
524}
525
526pub struct StateDb<BlockHash: Hash, Key: Hash, D: MetaDb> {
529 db: RwLock<StateDbSync<BlockHash, Key, D>>,
530}
531
532impl<BlockHash: Hash, Key: Hash, D: MetaDb> StateDb<BlockHash, Key, D> {
533 pub fn open(
535 db: D,
536 requested_mode: Option<PruningMode>,
537 ref_counting: bool,
538 should_init: bool,
539 ) -> Result<(CommitSet<Key>, StateDb<BlockHash, Key, D>), Error<D::Error>> {
540 let stored_mode = fetch_stored_pruning_mode(&db)?;
541
542 let selected_mode = match (should_init, stored_mode, requested_mode) {
543 (true, stored_mode, requested_mode) => {
544 assert!(stored_mode.is_none(), "The storage has just been initialized. No meta-data is expected to be found in it.");
545 requested_mode.unwrap_or_default()
546 },
547
548 (false, None, _) =>
549 return Err(StateDbError::Metadata(
550 "An existing StateDb does not have PRUNING_MODE stored in its meta-data".into(),
551 )
552 .into()),
553
554 (false, Some(stored), None) => stored,
555
556 (false, Some(stored), Some(requested)) => choose_pruning_mode(stored, requested)?,
557 };
558
559 let db_init_commit_set = if should_init {
560 let mut cs: CommitSet<Key> = Default::default();
561
562 let key = to_meta_key(PRUNING_MODE, &());
563 let value = selected_mode.id().to_owned();
564
565 cs.meta.inserted.push((key, value));
566
567 cs
568 } else {
569 Default::default()
570 };
571
572 let state_db =
573 StateDb { db: RwLock::new(StateDbSync::new(selected_mode, ref_counting, db)?) };
574
575 Ok((db_init_commit_set, state_db))
576 }
577
578 pub fn pruning_mode(&self) -> PruningMode {
579 self.db.read().mode.clone()
580 }
581
582 pub fn insert_block(
584 &self,
585 hash: &BlockHash,
586 number: u64,
587 parent_hash: &BlockHash,
588 changeset: ChangeSet<Key>,
589 ) -> Result<CommitSet<Key>, Error<D::Error>> {
590 self.db.write().insert_block(hash, number, parent_hash, changeset)
591 }
592
593 pub fn canonicalize_block(&self, hash: &BlockHash) -> Result<CommitSet<Key>, Error<D::Error>> {
595 self.db.write().canonicalize_block(hash)
596 }
597
598 pub fn pin<F>(&self, hash: &BlockHash, number: u64, hint: F) -> Result<(), PinError>
601 where
602 F: Fn() -> bool,
603 {
604 self.db.write().pin(hash, number, hint)
605 }
606
607 pub fn unpin(&self, hash: &BlockHash) {
609 self.db.write().unpin(hash)
610 }
611
612 pub fn sync(&self) {
615 self.db.write().sync()
616 }
617
618 pub fn get<DB: NodeDb, Q: ?Sized>(
620 &self,
621 key: &Q,
622 db: &DB,
623 ) -> Result<Option<DBValue>, Error<DB::Error>>
624 where
625 Q: AsRef<DB::Key>,
626 Key: std::borrow::Borrow<Q>,
627 Q: std::hash::Hash + Eq,
628 {
629 self.db.read().get(key, db)
630 }
631
632 pub fn revert_one(&self) -> Option<CommitSet<Key>> {
636 self.db.write().revert_one()
637 }
638
639 pub fn remove(&self, hash: &BlockHash) -> Option<CommitSet<Key>> {
642 self.db.write().remove(hash)
643 }
644
645 pub fn last_canonicalized(&self) -> LastCanonicalized {
647 self.db.read().last_canonicalized()
648 }
649
650 pub fn is_pruned(&self, hash: &BlockHash, number: u64) -> IsPruned {
652 self.db.read().is_pruned(hash, number)
653 }
654
655 pub fn reset(&self, db: D) -> Result<(), Error<D::Error>> {
657 let mut state_db = self.db.write();
658 *state_db = StateDbSync::new(state_db.mode.clone(), state_db.ref_counting, db)?;
659 Ok(())
660 }
661}
662
663#[derive(Debug, PartialEq, Eq)]
665pub enum IsPruned {
666 Pruned,
668 NotPruned,
670 MaybePruned,
672}
673
674fn fetch_stored_pruning_mode<D: MetaDb>(db: &D) -> Result<Option<PruningMode>, Error<D::Error>> {
675 let meta_key_mode = to_meta_key(PRUNING_MODE, &());
676 if let Some(stored_mode) = db.get_meta(&meta_key_mode).map_err(Error::Db)? {
677 if let Some(mode) = PruningMode::from_id(&stored_mode) {
678 Ok(Some(mode))
679 } else {
680 Err(StateDbError::Metadata(format!(
681 "Invalid value stored for PRUNING_MODE: {:02x?}",
682 stored_mode
683 ))
684 .into())
685 }
686 } else {
687 Ok(None)
688 }
689}
690
691fn choose_pruning_mode(
692 stored: PruningMode,
693 requested: PruningMode,
694) -> Result<PruningMode, StateDbError> {
695 match (stored, requested) {
696 (PruningMode::ArchiveAll, PruningMode::ArchiveAll) => Ok(PruningMode::ArchiveAll),
697 (PruningMode::ArchiveCanonical, PruningMode::ArchiveCanonical) =>
698 Ok(PruningMode::ArchiveCanonical),
699 (PruningMode::Constrained(_), PruningMode::Constrained(requested)) =>
700 Ok(PruningMode::Constrained(requested)),
701 (stored, requested) => Err(StateDbError::IncompatiblePruningModes { requested, stored }),
702 }
703}
704
705#[cfg(test)]
706mod tests {
707 use crate::{
708 test::{make_changeset, make_db, TestDb},
709 Constraints, Error, IsPruned, PruningMode, StateDb, StateDbError,
710 };
711 use sp_core::H256;
712
713 fn make_test_db(settings: PruningMode) -> (TestDb, StateDb<H256, H256, TestDb>) {
714 let mut db = make_db(&[91, 921, 922, 93, 94]);
715 let (state_db_init, state_db) =
716 StateDb::open(db.clone(), Some(settings), false, true).unwrap();
717 db.commit(&state_db_init);
718
719 db.commit(
720 &state_db
721 .insert_block(
722 &H256::from_low_u64_be(1),
723 1,
724 &H256::from_low_u64_be(0),
725 make_changeset(&[1], &[91]),
726 )
727 .unwrap(),
728 );
729 db.commit(
730 &state_db
731 .insert_block(
732 &H256::from_low_u64_be(21),
733 2,
734 &H256::from_low_u64_be(1),
735 make_changeset(&[21], &[921, 1]),
736 )
737 .unwrap(),
738 );
739 db.commit(
740 &state_db
741 .insert_block(
742 &H256::from_low_u64_be(22),
743 2,
744 &H256::from_low_u64_be(1),
745 make_changeset(&[22], &[922]),
746 )
747 .unwrap(),
748 );
749 db.commit(
750 &state_db
751 .insert_block(
752 &H256::from_low_u64_be(3),
753 3,
754 &H256::from_low_u64_be(21),
755 make_changeset(&[3], &[93]),
756 )
757 .unwrap(),
758 );
759 db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(1)).unwrap());
760 db.commit(
761 &state_db
762 .insert_block(
763 &H256::from_low_u64_be(4),
764 4,
765 &H256::from_low_u64_be(3),
766 make_changeset(&[4], &[94]),
767 )
768 .unwrap(),
769 );
770 db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(21)).unwrap());
771 db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(3)).unwrap());
772
773 (db, state_db)
774 }
775
776 #[test]
777 fn full_archive_keeps_everything() {
778 let (db, sdb) = make_test_db(PruningMode::ArchiveAll);
779 assert!(db.data_eq(&make_db(&[1, 21, 22, 3, 4, 91, 921, 922, 93, 94])));
780 assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::NotPruned);
781 }
782
783 #[test]
784 fn canonical_archive_keeps_canonical() {
785 let (db, _) = make_test_db(PruningMode::ArchiveCanonical);
786 assert!(db.data_eq(&make_db(&[1, 21, 3, 91, 921, 922, 93, 94])));
787 }
788
789 #[test]
790 fn block_record_unavailable() {
791 let (mut db, state_db) =
792 make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(1) }));
793 for i in &[5, 6] {
795 db.commit(
796 &state_db
797 .insert_block(
798 &H256::from_low_u64_be(*i),
799 *i,
800 &H256::from_low_u64_be(*i - 1),
801 make_changeset(&[], &[]),
802 )
803 .unwrap(),
804 );
805 }
806 let c1 = state_db.canonicalize_block(&H256::from_low_u64_be(4)).unwrap();
808 assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(3), 3), IsPruned::Pruned);
809
810 let c2 = state_db.canonicalize_block(&H256::from_low_u64_be(5)).unwrap();
814 assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(4), 4), IsPruned::MaybePruned);
815
816 db.commit(&c1);
818 db.commit(&c2);
819 db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(6)).unwrap());
820 assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(4), 4), IsPruned::Pruned);
821 assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(5), 5), IsPruned::Pruned);
822 }
823
824 #[test]
825 fn prune_window_0() {
826 let (db, _) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(0) }));
827 assert!(db.data_eq(&make_db(&[21, 3, 922, 94])));
828 }
829
830 #[test]
831 fn prune_window_1() {
832 let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(1) }));
833 assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::Pruned);
834 assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(1), 1), IsPruned::Pruned);
835 assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(21), 2), IsPruned::Pruned);
836 assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(22), 2), IsPruned::Pruned);
837 assert!(db.data_eq(&make_db(&[21, 3, 922, 93, 94])));
838 }
839
840 #[test]
841 fn prune_window_2() {
842 let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(2) }));
843 assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::Pruned);
844 assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(1), 1), IsPruned::Pruned);
845 assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(21), 2), IsPruned::NotPruned);
846 assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(22), 2), IsPruned::Pruned);
847 assert!(db.data_eq(&make_db(&[1, 21, 3, 921, 922, 93, 94])));
848 }
849
850 #[test]
851 fn detects_incompatible_mode() {
852 let mut db = make_db(&[]);
853 let (state_db_init, state_db) =
854 StateDb::open(db.clone(), Some(PruningMode::ArchiveAll), false, true).unwrap();
855 db.commit(&state_db_init);
856 db.commit(
857 &state_db
858 .insert_block(
859 &H256::from_low_u64_be(0),
860 0,
861 &H256::from_low_u64_be(0),
862 make_changeset(&[], &[]),
863 )
864 .unwrap(),
865 );
866 let new_mode = PruningMode::Constrained(Constraints { max_blocks: Some(2) });
867 let state_db_open_result: Result<(_, StateDb<H256, H256, TestDb>), _> =
868 StateDb::open(db.clone(), Some(new_mode), false, false);
869 assert!(state_db_open_result.is_err());
870 }
871
872 fn check_stored_and_requested_mode_compatibility(
873 mode_when_created: Option<PruningMode>,
874 mode_when_reopened: Option<PruningMode>,
875 expected_effective_mode_when_reopened: Result<PruningMode, ()>,
876 ) {
877 let mut db = make_db(&[]);
878 let (state_db_init, state_db) =
879 StateDb::<H256, H256, TestDb>::open(db.clone(), mode_when_created, false, true)
880 .unwrap();
881 db.commit(&state_db_init);
882 std::mem::drop(state_db);
883
884 let state_db_reopen_result =
885 StateDb::<H256, H256, TestDb>::open(db.clone(), mode_when_reopened, false, false);
886 if let Ok(expected_mode) = expected_effective_mode_when_reopened {
887 let (state_db_init, state_db_reopened) = state_db_reopen_result.unwrap();
888 db.commit(&state_db_init);
889 assert_eq!(state_db_reopened.pruning_mode(), expected_mode,)
890 } else {
891 assert!(matches!(
892 state_db_reopen_result,
893 Err(Error::StateDb(StateDbError::IncompatiblePruningModes { .. }))
894 ));
895 }
896 }
897
898 #[test]
899 fn pruning_mode_compatibility() {
900 for (created, reopened, expected) in [
901 (None, None, Ok(PruningMode::blocks_pruning(256))),
902 (None, Some(PruningMode::blocks_pruning(256)), Ok(PruningMode::blocks_pruning(256))),
903 (None, Some(PruningMode::blocks_pruning(128)), Ok(PruningMode::blocks_pruning(128))),
904 (None, Some(PruningMode::blocks_pruning(512)), Ok(PruningMode::blocks_pruning(512))),
905 (None, Some(PruningMode::ArchiveAll), Err(())),
906 (None, Some(PruningMode::ArchiveCanonical), Err(())),
907 (Some(PruningMode::blocks_pruning(256)), None, Ok(PruningMode::blocks_pruning(256))),
908 (
909 Some(PruningMode::blocks_pruning(256)),
910 Some(PruningMode::blocks_pruning(256)),
911 Ok(PruningMode::blocks_pruning(256)),
912 ),
913 (
914 Some(PruningMode::blocks_pruning(256)),
915 Some(PruningMode::blocks_pruning(128)),
916 Ok(PruningMode::blocks_pruning(128)),
917 ),
918 (
919 Some(PruningMode::blocks_pruning(256)),
920 Some(PruningMode::blocks_pruning(512)),
921 Ok(PruningMode::blocks_pruning(512)),
922 ),
923 (Some(PruningMode::blocks_pruning(256)), Some(PruningMode::ArchiveAll), Err(())),
924 (Some(PruningMode::blocks_pruning(256)), Some(PruningMode::ArchiveCanonical), Err(())),
925 (Some(PruningMode::ArchiveAll), None, Ok(PruningMode::ArchiveAll)),
926 (Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(256)), Err(())),
927 (Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(128)), Err(())),
928 (Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(512)), Err(())),
929 (
930 Some(PruningMode::ArchiveAll),
931 Some(PruningMode::ArchiveAll),
932 Ok(PruningMode::ArchiveAll),
933 ),
934 (Some(PruningMode::ArchiveAll), Some(PruningMode::ArchiveCanonical), Err(())),
935 (Some(PruningMode::ArchiveCanonical), None, Ok(PruningMode::ArchiveCanonical)),
936 (Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(256)), Err(())),
937 (Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(128)), Err(())),
938 (Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(512)), Err(())),
939 (Some(PruningMode::ArchiveCanonical), Some(PruningMode::ArchiveAll), Err(())),
940 (
941 Some(PruningMode::ArchiveCanonical),
942 Some(PruningMode::ArchiveCanonical),
943 Ok(PruningMode::ArchiveCanonical),
944 ),
945 ] {
946 check_stored_and_requested_mode_compatibility(created, reopened, expected);
947 }
948 }
949}