referrerpolicy=no-referrer-when-downgrade

sc_state_db/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! State database maintenance. Handles canonicalization and pruning in the database.
20//!
21//! # Canonicalization.
22//! Canonicalization window tracks a tree of blocks identified by header hash. The in-memory
23//! overlay allows to get any trie node that was inserted in any of the blocks within the window.
24//! The overlay is journaled to the backing database and rebuilt on startup.
25//! There's a limit of 32 blocks that may have the same block number in the canonicalization window.
26//!
27//! Canonicalization function selects one root from the top of the tree and discards all other roots
28//! and their subtrees. Upon canonicalization all trie nodes that were inserted in the block are
29//! added to the backing DB and block tracking is moved to the pruning window, where no forks are
30//! allowed.
31//!
32//! # Canonicalization vs Finality
33//! Database engine uses a notion of canonicality, rather then finality. A canonical block may not
34//! be yet finalized from the perspective of the consensus engine, but it still can't be reverted in
35//! the database. Most of the time during normal operation last canonical block is the same as last
36//! finalized. However if finality stall for a long duration for some reason, there's only a certain
37//! number of blocks that can fit in the non-canonical overlay, so canonicalization of an
38//! unfinalized block may be forced.
39//!
40//! # Pruning.
41//! See `RefWindow` for pruning algorithm details. `StateDb` prunes on each canonicalization until
42//! pruning constraints are satisfied.
43
44mod 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
67/// Database value type.
68pub type DBValue = Vec<u8>;
69
70/// Basic set of requirements for the Block hash and node key types.
71pub 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
101/// Backend database trait. Read-only.
102pub trait MetaDb {
103	type Error: fmt::Debug;
104
105	/// Get meta value, such as the journal.
106	fn get_meta(&self, key: &[u8]) -> Result<Option<DBValue>, Self::Error>;
107}
108
109/// Backend database trait. Read-only.
110pub trait NodeDb {
111	type Key: ?Sized;
112	type Error: fmt::Debug;
113
114	/// Get state trie node.
115	fn get(&self, key: &Self::Key) -> Result<Option<DBValue>, Self::Error>;
116}
117
118/// Error type.
119#[derive(Eq, PartialEq)]
120pub enum Error<E> {
121	/// Database backend error.
122	Db(E),
123	StateDb(StateDbError),
124}
125
126#[derive(Eq, PartialEq)]
127pub enum StateDbError {
128	/// `Codec` decoding error.
129	Decoding(codec::Error),
130	/// Trying to canonicalize invalid block.
131	InvalidBlock,
132	/// Trying to insert block with invalid number.
133	InvalidBlockNumber,
134	/// Trying to insert block with unknown parent.
135	InvalidParent,
136	/// Invalid pruning mode specified. Contains expected mode.
137	IncompatiblePruningModes { stored: PruningMode, requested: PruningMode },
138	/// Too many unfinalized sibling blocks inserted.
139	TooManySiblingBlocks { number: u64 },
140	/// Trying to insert existing block.
141	BlockAlreadyExists,
142	/// Invalid metadata
143	Metadata(String),
144	/// Trying to get a block record from db while it is not commit to db yet
145	BlockUnavailable,
146	/// Block record is missing from the pruning window
147	BlockMissing,
148}
149
150impl<E> From<StateDbError> for Error<E> {
151	fn from(inner: StateDbError) -> Self {
152		Self::StateDb(inner)
153	}
154}
155
156/// Pinning error type.
157#[derive(Debug)]
158pub enum PinError {
159	/// Trying to pin invalid block.
160	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/// A set of state node changes.
204#[derive(Default, Debug, Clone)]
205pub struct ChangeSet<H: Hash> {
206	/// Inserted nodes.
207	pub inserted: Vec<(H, DBValue)>,
208	/// Deleted nodes.
209	pub deleted: Vec<H>,
210}
211
212/// A set of changes to the backing database.
213#[derive(Default, Debug, Clone)]
214pub struct CommitSet<H: Hash> {
215	/// State node changes.
216	pub data: ChangeSet<H>,
217	/// Metadata changes.
218	pub meta: ChangeSet<Vec<u8>>,
219}
220
221/// Pruning constraints. If none are specified pruning is
222#[derive(Debug, Clone, Eq, PartialEq)]
223pub struct Constraints {
224	/// Maximum blocks. Defaults to 0 when unspecified, effectively keeping only non-canonical
225	/// states.
226	pub max_blocks: Option<u32>,
227}
228
229/// Pruning mode.
230#[derive(Debug, Clone, Eq, PartialEq)]
231pub enum PruningMode {
232	/// Maintain a pruning window.
233	Constrained(Constraints),
234	/// No pruning. Canonicalization is a no-op.
235	ArchiveAll,
236	/// Canonicalization discards non-canonical nodes. All the canonical nodes are kept in the DB.
237	ArchiveCanonical,
238}
239
240impl PruningMode {
241	/// Create a mode that keeps given number of blocks.
242	pub fn blocks_pruning(n: u32) -> PruningMode {
243		PruningMode::Constrained(Constraints { max_blocks: Some(n) })
244	}
245
246	/// Is this an archive (either ArchiveAll or ArchiveCanonical) pruning mode?
247	pub fn is_archive(&self) -> bool {
248		match *self {
249			PruningMode::ArchiveAll | PruningMode::ArchiveCanonical => true,
250			PruningMode::Constrained(_) => false,
251		}
252	}
253
254	/// Returns the pruning mode
255	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/// Status information about the last canonicalized block.
292#[derive(Clone, Debug, PartialEq, Eq)]
293pub enum LastCanonicalized {
294	/// Not yet have canonicalized any block.
295	None,
296	/// The given block number is the last canonicalized block.
297	Block(u64),
298	/// No canonicalization is happening (pruning mode is archive all).
299	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				// write changes immediately
339				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		// NOTE: it is important that the change to `LAST_CANONICAL` (emit from
350		// `non_canonical.canonicalize`) and the insert of the new pruning journal (emit from
351		// `pruning.note_canonical`) are collected into the same `CommitSet` and are committed to
352		// the database atomically to keep their consistency when restarting the node
353		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	/// Returns the block number of the last canonicalized block.
369	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						// We don't know for sure.
398						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					// the block record is temporary unavailable, break and try next time
422					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					// this branch should not reach as previous `next_hash` don't return error
430					// keeping it for robustness
431					Err(Error::StateDb(StateDbError::BlockUnavailable)) => break,
432					res => res?,
433				}
434			}
435		}
436		Ok(())
437	}
438
439	/// Revert all non-canonical blocks with the best block number.
440	/// Returns a database commit or `None` if not possible.
441	/// For archive an empty commit set is returned.
442	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
526/// State DB maintenance. See module description.
527/// Can be shared across threads.
528pub 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	/// Create an instance of [`StateDb`].
534	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	/// Add a new non-canonical block.
583	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	/// Finalize a previously inserted block.
594	pub fn canonicalize_block(&self, hash: &BlockHash) -> Result<CommitSet<Key>, Error<D::Error>> {
595		self.db.write().canonicalize_block(hash)
596	}
597
598	/// Prevents pruning of specified block and its descendants.
599	/// `hint` used for further checking if the given block exists
600	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	/// Allows pruning of specified block.
608	pub fn unpin(&self, hash: &BlockHash) {
609		self.db.write().unpin(hash)
610	}
611
612	/// Confirm that all changes made to commit sets are on disk. Allows for temporarily pinned
613	/// blocks to be released.
614	pub fn sync(&self) {
615		self.db.write().sync()
616	}
617
618	/// Get a value from non-canonical/pruning overlay or the backing DB.
619	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	/// Revert all non-canonical blocks with the best block number.
633	/// Returns a database commit or `None` if not possible.
634	/// For archive an empty commit set is returned.
635	pub fn revert_one(&self) -> Option<CommitSet<Key>> {
636		self.db.write().revert_one()
637	}
638
639	/// Remove specified non-canonical block.
640	/// Returns a database commit or `None` if not possible.
641	pub fn remove(&self, hash: &BlockHash) -> Option<CommitSet<Key>> {
642		self.db.write().remove(hash)
643	}
644
645	/// Returns last canonicalized block.
646	pub fn last_canonicalized(&self) -> LastCanonicalized {
647		self.db.read().last_canonicalized()
648	}
649
650	/// Check if block is pruned away.
651	pub fn is_pruned(&self, hash: &BlockHash, number: u64) -> IsPruned {
652		self.db.read().is_pruned(hash, number)
653	}
654
655	/// Reset in-memory changes to the last disk-backed state.
656	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/// The result return by `StateDb::is_pruned`
664#[derive(Debug, PartialEq, Eq)]
665pub enum IsPruned {
666	/// Definitely pruned
667	Pruned,
668	/// Definitely not pruned
669	NotPruned,
670	/// May or may not pruned, need further checking
671	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		// import 2 blocks
794		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		// canonicalize block 4 but not commit it to db
807		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		// canonicalize block 5 but not commit it to db, block 4 is not pruned due to it is not
811		// commit to db yet (unavailable), return `MaybePruned` here because `apply_pending` is not
812		// called and block 3 is still in cache
813		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		// commit block 4 and 5 to db, and import a new block will prune both block 4 and 5
817		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}