Skip to main content

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			},
323			PruningMode::ArchiveAll | PruningMode::ArchiveCanonical => None,
324		};
325
326		Ok(StateDbSync { mode, non_canonical, pruning, pinned: Default::default(), ref_counting })
327	}
328
329	fn insert_block(
330		&mut self,
331		hash: &BlockHash,
332		number: u64,
333		parent_hash: &BlockHash,
334		mut changeset: ChangeSet<Key>,
335	) -> Result<CommitSet<Key>, Error<D::Error>> {
336		match self.mode {
337			PruningMode::ArchiveAll => {
338				changeset.deleted.clear();
339				// write changes immediately
340				Ok(CommitSet { data: changeset, meta: Default::default() })
341			},
342			PruningMode::Constrained(_) | PruningMode::ArchiveCanonical => self
343				.non_canonical
344				.insert(hash, number, parent_hash, changeset)
345				.map_err(Into::into),
346		}
347	}
348
349	fn canonicalize_block(&mut self, hash: &BlockHash) -> Result<CommitSet<Key>, Error<D::Error>> {
350		// NOTE: it is important that the change to `LAST_CANONICAL` (emit from
351		// `non_canonical.canonicalize`) and the insert of the new pruning journal (emit from
352		// `pruning.note_canonical`) are collected into the same `CommitSet` and are committed to
353		// the database atomically to keep their consistency when restarting the node
354		let mut commit = CommitSet::default();
355		if self.mode == PruningMode::ArchiveAll {
356			return Ok(commit);
357		}
358		let number = self.non_canonical.canonicalize(hash, &mut commit)?;
359		if self.mode == PruningMode::ArchiveCanonical {
360			commit.data.deleted.clear();
361		}
362		if let Some(ref mut pruning) = self.pruning {
363			pruning.note_canonical(hash, number, &mut commit)?;
364		}
365		self.prune(&mut commit)?;
366		Ok(commit)
367	}
368
369	/// Returns the block number of the last canonicalized block.
370	fn last_canonicalized(&self) -> LastCanonicalized {
371		if self.mode == PruningMode::ArchiveAll {
372			LastCanonicalized::NotCanonicalizing
373		} else {
374			self.non_canonical
375				.last_canonicalized_block_number()
376				.map(LastCanonicalized::Block)
377				.unwrap_or_else(|| LastCanonicalized::None)
378		}
379	}
380
381	fn is_pruned(&self, hash: &BlockHash, number: u64) -> IsPruned {
382		match self.mode {
383			PruningMode::ArchiveAll => IsPruned::NotPruned,
384			PruningMode::ArchiveCanonical | PruningMode::Constrained(_) => {
385				if self
386					.non_canonical
387					.last_canonicalized_block_number()
388					.map(|c| number > c)
389					.unwrap_or(true)
390				{
391					if self.non_canonical.have_block(hash) {
392						IsPruned::NotPruned
393					} else {
394						IsPruned::Pruned
395					}
396				} else {
397					match self.pruning.as_ref() {
398						// We don't know for sure.
399						None => IsPruned::MaybePruned,
400						Some(pruning) => match pruning.have_block(hash, number) {
401							HaveBlock::No => IsPruned::Pruned,
402							HaveBlock::Yes => IsPruned::NotPruned,
403							HaveBlock::Maybe => IsPruned::MaybePruned,
404						},
405					}
406				}
407			},
408		}
409	}
410
411	fn prune(&mut self, commit: &mut CommitSet<Key>) -> Result<(), Error<D::Error>> {
412		if let (&mut Some(ref mut pruning), PruningMode::Constrained(constraints)) =
413			(&mut self.pruning, &self.mode)
414		{
415			loop {
416				if pruning.window_size() <= constraints.max_blocks.unwrap_or(0) as u64 {
417					break;
418				}
419
420				let pinned = &self.pinned;
421				match pruning.next_hash() {
422					// the block record is temporary unavailable, break and try next time
423					Err(Error::StateDb(StateDbError::BlockUnavailable)) => break,
424					res => {
425						if res?.map_or(false, |h| pinned.contains_key(&h)) {
426							break;
427						}
428					},
429				}
430				match pruning.prune_one(commit) {
431					// this branch should not reach as previous `next_hash` don't return error
432					// keeping it for robustness
433					Err(Error::StateDb(StateDbError::BlockUnavailable)) => break,
434					res => res?,
435				}
436			}
437		}
438		Ok(())
439	}
440
441	/// Revert all non-canonical blocks with the best block number.
442	/// Returns a database commit or `None` if not possible.
443	/// For archive an empty commit set is returned.
444	fn revert_one(&mut self) -> Option<CommitSet<Key>> {
445		match self.mode {
446			PruningMode::ArchiveAll => Some(CommitSet::default()),
447			PruningMode::ArchiveCanonical | PruningMode::Constrained(_) => {
448				self.non_canonical.revert_one()
449			},
450		}
451	}
452
453	fn remove(&mut self, hash: &BlockHash) -> Option<CommitSet<Key>> {
454		match self.mode {
455			PruningMode::ArchiveAll => Some(CommitSet::default()),
456			PruningMode::ArchiveCanonical | PruningMode::Constrained(_) => {
457				self.non_canonical.remove(hash)
458			},
459		}
460	}
461
462	fn pin<F>(&mut self, hash: &BlockHash, number: u64, hint: F) -> Result<(), PinError>
463	where
464		F: Fn() -> bool,
465	{
466		match self.mode {
467			PruningMode::ArchiveAll => Ok(()),
468			PruningMode::ArchiveCanonical | PruningMode::Constrained(_) => {
469				let have_block = self.non_canonical.have_block(hash) ||
470					self.pruning.as_ref().map_or_else(
471						|| hint(),
472						|pruning| match pruning.have_block(hash, number) {
473							HaveBlock::No => false,
474							HaveBlock::Yes => true,
475							HaveBlock::Maybe => hint(),
476						},
477					);
478				if have_block {
479					let refs = self.pinned.entry(hash.clone()).or_default();
480					if *refs == 0 {
481						trace!(target: LOG_TARGET_PIN, "Pinned block: {:?}", hash);
482						self.non_canonical.pin(hash);
483					}
484					*refs += 1;
485					Ok(())
486				} else {
487					Err(PinError::InvalidBlock)
488				}
489			},
490		}
491	}
492
493	fn unpin(&mut self, hash: &BlockHash) {
494		match self.pinned.entry(hash.clone()) {
495			Entry::Occupied(mut entry) => {
496				*entry.get_mut() -= 1;
497				if *entry.get() == 0 {
498					trace!(target: LOG_TARGET_PIN, "Unpinned block: {:?}", hash);
499					entry.remove();
500					self.non_canonical.unpin(hash);
501				} else {
502					trace!(target: LOG_TARGET_PIN, "Releasing reference for {:?}", hash);
503				}
504			},
505			Entry::Vacant(_) => {},
506		}
507	}
508
509	fn sync(&mut self) {
510		self.non_canonical.sync();
511	}
512
513	pub fn get<DB: NodeDb, Q: ?Sized>(
514		&self,
515		key: &Q,
516		db: &DB,
517	) -> Result<Option<DBValue>, Error<DB::Error>>
518	where
519		Q: AsRef<DB::Key>,
520		Key: std::borrow::Borrow<Q>,
521		Q: std::hash::Hash + Eq,
522	{
523		if let Some(value) = self.non_canonical.get(key) {
524			return Ok(Some(value));
525		}
526		db.get(key.as_ref()).map_err(Error::Db)
527	}
528}
529
530/// State DB maintenance. See module description.
531/// Can be shared across threads.
532pub struct StateDb<BlockHash: Hash, Key: Hash, D: MetaDb> {
533	db: RwLock<StateDbSync<BlockHash, Key, D>>,
534}
535
536impl<BlockHash: Hash, Key: Hash, D: MetaDb> StateDb<BlockHash, Key, D> {
537	/// Create an instance of [`StateDb`].
538	pub fn open(
539		db: D,
540		requested_mode: Option<PruningMode>,
541		ref_counting: bool,
542		should_init: bool,
543	) -> Result<(CommitSet<Key>, StateDb<BlockHash, Key, D>), Error<D::Error>> {
544		let stored_mode = fetch_stored_pruning_mode(&db)?;
545
546		let selected_mode = match (should_init, stored_mode, requested_mode) {
547			(true, stored_mode, requested_mode) => {
548				assert!(stored_mode.is_none(), "The storage has just been initialized. No meta-data is expected to be found in it.");
549				requested_mode.unwrap_or_default()
550			},
551
552			(false, None, _) => {
553				return Err(StateDbError::Metadata(
554					"An existing StateDb does not have PRUNING_MODE stored in its meta-data".into(),
555				)
556				.into())
557			},
558
559			(false, Some(stored), None) => stored,
560
561			(false, Some(stored), Some(requested)) => choose_pruning_mode(stored, requested)?,
562		};
563
564		let db_init_commit_set = if should_init {
565			let mut cs: CommitSet<Key> = Default::default();
566
567			let key = to_meta_key(PRUNING_MODE, &());
568			let value = selected_mode.id().to_owned();
569
570			cs.meta.inserted.push((key, value));
571
572			cs
573		} else {
574			Default::default()
575		};
576
577		let state_db =
578			StateDb { db: RwLock::new(StateDbSync::new(selected_mode, ref_counting, db)?) };
579
580		Ok((db_init_commit_set, state_db))
581	}
582
583	pub fn pruning_mode(&self) -> PruningMode {
584		self.db.read().mode.clone()
585	}
586
587	/// Add a new non-canonical block.
588	pub fn insert_block(
589		&self,
590		hash: &BlockHash,
591		number: u64,
592		parent_hash: &BlockHash,
593		changeset: ChangeSet<Key>,
594	) -> Result<CommitSet<Key>, Error<D::Error>> {
595		self.db.write().insert_block(hash, number, parent_hash, changeset)
596	}
597
598	/// Finalize a previously inserted block.
599	pub fn canonicalize_block(&self, hash: &BlockHash) -> Result<CommitSet<Key>, Error<D::Error>> {
600		self.db.write().canonicalize_block(hash)
601	}
602
603	/// Prevents pruning of specified block and its descendants.
604	/// `hint` used for further checking if the given block exists
605	pub fn pin<F>(&self, hash: &BlockHash, number: u64, hint: F) -> Result<(), PinError>
606	where
607		F: Fn() -> bool,
608	{
609		self.db.write().pin(hash, number, hint)
610	}
611
612	/// Allows pruning of specified block.
613	pub fn unpin(&self, hash: &BlockHash) {
614		self.db.write().unpin(hash)
615	}
616
617	/// Confirm that all changes made to commit sets are on disk. Allows for temporarily pinned
618	/// blocks to be released.
619	pub fn sync(&self) {
620		self.db.write().sync()
621	}
622
623	/// Get a value from non-canonical/pruning overlay or the backing DB.
624	pub fn get<DB: NodeDb, Q: ?Sized>(
625		&self,
626		key: &Q,
627		db: &DB,
628	) -> Result<Option<DBValue>, Error<DB::Error>>
629	where
630		Q: AsRef<DB::Key>,
631		Key: std::borrow::Borrow<Q>,
632		Q: std::hash::Hash + Eq,
633	{
634		self.db.read().get(key, db)
635	}
636
637	/// Revert all non-canonical blocks with the best block number.
638	/// Returns a database commit or `None` if not possible.
639	/// For archive an empty commit set is returned.
640	pub fn revert_one(&self) -> Option<CommitSet<Key>> {
641		self.db.write().revert_one()
642	}
643
644	/// Remove specified non-canonical block.
645	/// Returns a database commit or `None` if not possible.
646	pub fn remove(&self, hash: &BlockHash) -> Option<CommitSet<Key>> {
647		self.db.write().remove(hash)
648	}
649
650	/// Returns last canonicalized block.
651	pub fn last_canonicalized(&self) -> LastCanonicalized {
652		self.db.read().last_canonicalized()
653	}
654
655	/// Check if block is pruned away.
656	pub fn is_pruned(&self, hash: &BlockHash, number: u64) -> IsPruned {
657		self.db.read().is_pruned(hash, number)
658	}
659
660	/// Reset in-memory changes to the last disk-backed state.
661	pub fn reset(&self, db: D) -> Result<(), Error<D::Error>> {
662		let mut state_db = self.db.write();
663		*state_db = StateDbSync::new(state_db.mode.clone(), state_db.ref_counting, db)?;
664		Ok(())
665	}
666}
667
668/// The result return by `StateDb::is_pruned`
669#[derive(Debug, PartialEq, Eq)]
670pub enum IsPruned {
671	/// Definitely pruned
672	Pruned,
673	/// Definitely not pruned
674	NotPruned,
675	/// May or may not pruned, need further checking
676	MaybePruned,
677}
678
679fn fetch_stored_pruning_mode<D: MetaDb>(db: &D) -> Result<Option<PruningMode>, Error<D::Error>> {
680	let meta_key_mode = to_meta_key(PRUNING_MODE, &());
681	if let Some(stored_mode) = db.get_meta(&meta_key_mode).map_err(Error::Db)? {
682		if let Some(mode) = PruningMode::from_id(&stored_mode) {
683			Ok(Some(mode))
684		} else {
685			Err(StateDbError::Metadata(format!(
686				"Invalid value stored for PRUNING_MODE: {:02x?}",
687				stored_mode
688			))
689			.into())
690		}
691	} else {
692		Ok(None)
693	}
694}
695
696fn choose_pruning_mode(
697	stored: PruningMode,
698	requested: PruningMode,
699) -> Result<PruningMode, StateDbError> {
700	match (stored, requested) {
701		(PruningMode::ArchiveAll, PruningMode::ArchiveAll) => Ok(PruningMode::ArchiveAll),
702		(PruningMode::ArchiveCanonical, PruningMode::ArchiveCanonical) => {
703			Ok(PruningMode::ArchiveCanonical)
704		},
705		(PruningMode::Constrained(_), PruningMode::Constrained(requested)) => {
706			Ok(PruningMode::Constrained(requested))
707		},
708		(stored, requested) => Err(StateDbError::IncompatiblePruningModes { requested, stored }),
709	}
710}
711
712#[cfg(test)]
713mod tests {
714	use crate::{
715		test::{make_changeset, make_db, TestDb},
716		Constraints, Error, IsPruned, PruningMode, StateDb, StateDbError,
717	};
718	use sp_core::H256;
719
720	fn make_test_db(settings: PruningMode) -> (TestDb, StateDb<H256, H256, TestDb>) {
721		let mut db = make_db(&[91, 921, 922, 93, 94]);
722		let (state_db_init, state_db) =
723			StateDb::open(db.clone(), Some(settings), false, true).unwrap();
724		db.commit(&state_db_init);
725
726		db.commit(
727			&state_db
728				.insert_block(
729					&H256::from_low_u64_be(1),
730					1,
731					&H256::from_low_u64_be(0),
732					make_changeset(&[1], &[91]),
733				)
734				.unwrap(),
735		);
736		db.commit(
737			&state_db
738				.insert_block(
739					&H256::from_low_u64_be(21),
740					2,
741					&H256::from_low_u64_be(1),
742					make_changeset(&[21], &[921, 1]),
743				)
744				.unwrap(),
745		);
746		db.commit(
747			&state_db
748				.insert_block(
749					&H256::from_low_u64_be(22),
750					2,
751					&H256::from_low_u64_be(1),
752					make_changeset(&[22], &[922]),
753				)
754				.unwrap(),
755		);
756		db.commit(
757			&state_db
758				.insert_block(
759					&H256::from_low_u64_be(3),
760					3,
761					&H256::from_low_u64_be(21),
762					make_changeset(&[3], &[93]),
763				)
764				.unwrap(),
765		);
766		db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(1)).unwrap());
767		db.commit(
768			&state_db
769				.insert_block(
770					&H256::from_low_u64_be(4),
771					4,
772					&H256::from_low_u64_be(3),
773					make_changeset(&[4], &[94]),
774				)
775				.unwrap(),
776		);
777		db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(21)).unwrap());
778		db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(3)).unwrap());
779
780		(db, state_db)
781	}
782
783	#[test]
784	fn full_archive_keeps_everything() {
785		let (db, sdb) = make_test_db(PruningMode::ArchiveAll);
786		assert!(db.data_eq(&make_db(&[1, 21, 22, 3, 4, 91, 921, 922, 93, 94])));
787		assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::NotPruned);
788	}
789
790	#[test]
791	fn canonical_archive_keeps_canonical() {
792		let (db, _) = make_test_db(PruningMode::ArchiveCanonical);
793		assert!(db.data_eq(&make_db(&[1, 21, 3, 91, 921, 922, 93, 94])));
794	}
795
796	#[test]
797	fn block_record_unavailable() {
798		let (mut db, state_db) =
799			make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(1) }));
800		// import 2 blocks
801		for i in &[5, 6] {
802			db.commit(
803				&state_db
804					.insert_block(
805						&H256::from_low_u64_be(*i),
806						*i,
807						&H256::from_low_u64_be(*i - 1),
808						make_changeset(&[], &[]),
809					)
810					.unwrap(),
811			);
812		}
813		// canonicalize block 4 but not commit it to db
814		let c1 = state_db.canonicalize_block(&H256::from_low_u64_be(4)).unwrap();
815		assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(3), 3), IsPruned::Pruned);
816
817		// canonicalize block 5 but not commit it to db, block 4 is not pruned due to it is not
818		// commit to db yet (unavailable), return `MaybePruned` here because `apply_pending` is not
819		// called and block 3 is still in cache
820		let c2 = state_db.canonicalize_block(&H256::from_low_u64_be(5)).unwrap();
821		assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(4), 4), IsPruned::MaybePruned);
822
823		// commit block 4 and 5 to db, and import a new block will prune both block 4 and 5
824		db.commit(&c1);
825		db.commit(&c2);
826		db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(6)).unwrap());
827		assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(4), 4), IsPruned::Pruned);
828		assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(5), 5), IsPruned::Pruned);
829	}
830
831	#[test]
832	fn prune_window_0() {
833		let (db, _) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(0) }));
834		assert!(db.data_eq(&make_db(&[21, 3, 922, 94])));
835	}
836
837	#[test]
838	fn prune_window_1() {
839		let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(1) }));
840		assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::Pruned);
841		assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(1), 1), IsPruned::Pruned);
842		assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(21), 2), IsPruned::Pruned);
843		assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(22), 2), IsPruned::Pruned);
844		assert!(db.data_eq(&make_db(&[21, 3, 922, 93, 94])));
845	}
846
847	#[test]
848	fn prune_window_2() {
849		let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(2) }));
850		assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::Pruned);
851		assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(1), 1), IsPruned::Pruned);
852		assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(21), 2), IsPruned::NotPruned);
853		assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(22), 2), IsPruned::Pruned);
854		assert!(db.data_eq(&make_db(&[1, 21, 3, 921, 922, 93, 94])));
855	}
856
857	#[test]
858	fn detects_incompatible_mode() {
859		let mut db = make_db(&[]);
860		let (state_db_init, state_db) =
861			StateDb::open(db.clone(), Some(PruningMode::ArchiveAll), false, true).unwrap();
862		db.commit(&state_db_init);
863		db.commit(
864			&state_db
865				.insert_block(
866					&H256::from_low_u64_be(0),
867					0,
868					&H256::from_low_u64_be(0),
869					make_changeset(&[], &[]),
870				)
871				.unwrap(),
872		);
873		let new_mode = PruningMode::Constrained(Constraints { max_blocks: Some(2) });
874		let state_db_open_result: Result<(_, StateDb<H256, H256, TestDb>), _> =
875			StateDb::open(db.clone(), Some(new_mode), false, false);
876		assert!(state_db_open_result.is_err());
877	}
878
879	fn check_stored_and_requested_mode_compatibility(
880		mode_when_created: Option<PruningMode>,
881		mode_when_reopened: Option<PruningMode>,
882		expected_effective_mode_when_reopened: Result<PruningMode, ()>,
883	) {
884		let mut db = make_db(&[]);
885		let (state_db_init, state_db) =
886			StateDb::<H256, H256, TestDb>::open(db.clone(), mode_when_created, false, true)
887				.unwrap();
888		db.commit(&state_db_init);
889		std::mem::drop(state_db);
890
891		let state_db_reopen_result =
892			StateDb::<H256, H256, TestDb>::open(db.clone(), mode_when_reopened, false, false);
893		if let Ok(expected_mode) = expected_effective_mode_when_reopened {
894			let (state_db_init, state_db_reopened) = state_db_reopen_result.unwrap();
895			db.commit(&state_db_init);
896			assert_eq!(state_db_reopened.pruning_mode(), expected_mode,)
897		} else {
898			assert!(matches!(
899				state_db_reopen_result,
900				Err(Error::StateDb(StateDbError::IncompatiblePruningModes { .. }))
901			));
902		}
903	}
904
905	#[test]
906	fn pruning_mode_compatibility() {
907		for (created, reopened, expected) in [
908			(None, None, Ok(PruningMode::blocks_pruning(256))),
909			(None, Some(PruningMode::blocks_pruning(256)), Ok(PruningMode::blocks_pruning(256))),
910			(None, Some(PruningMode::blocks_pruning(128)), Ok(PruningMode::blocks_pruning(128))),
911			(None, Some(PruningMode::blocks_pruning(512)), Ok(PruningMode::blocks_pruning(512))),
912			(None, Some(PruningMode::ArchiveAll), Err(())),
913			(None, Some(PruningMode::ArchiveCanonical), Err(())),
914			(Some(PruningMode::blocks_pruning(256)), None, Ok(PruningMode::blocks_pruning(256))),
915			(
916				Some(PruningMode::blocks_pruning(256)),
917				Some(PruningMode::blocks_pruning(256)),
918				Ok(PruningMode::blocks_pruning(256)),
919			),
920			(
921				Some(PruningMode::blocks_pruning(256)),
922				Some(PruningMode::blocks_pruning(128)),
923				Ok(PruningMode::blocks_pruning(128)),
924			),
925			(
926				Some(PruningMode::blocks_pruning(256)),
927				Some(PruningMode::blocks_pruning(512)),
928				Ok(PruningMode::blocks_pruning(512)),
929			),
930			(Some(PruningMode::blocks_pruning(256)), Some(PruningMode::ArchiveAll), Err(())),
931			(Some(PruningMode::blocks_pruning(256)), Some(PruningMode::ArchiveCanonical), Err(())),
932			(Some(PruningMode::ArchiveAll), None, Ok(PruningMode::ArchiveAll)),
933			(Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(256)), Err(())),
934			(Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(128)), Err(())),
935			(Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(512)), Err(())),
936			(
937				Some(PruningMode::ArchiveAll),
938				Some(PruningMode::ArchiveAll),
939				Ok(PruningMode::ArchiveAll),
940			),
941			(Some(PruningMode::ArchiveAll), Some(PruningMode::ArchiveCanonical), Err(())),
942			(Some(PruningMode::ArchiveCanonical), None, Ok(PruningMode::ArchiveCanonical)),
943			(Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(256)), Err(())),
944			(Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(128)), Err(())),
945			(Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(512)), Err(())),
946			(Some(PruningMode::ArchiveCanonical), Some(PruningMode::ArchiveAll), Err(())),
947			(
948				Some(PruningMode::ArchiveCanonical),
949				Some(PruningMode::ArchiveCanonical),
950				Ok(PruningMode::ArchiveCanonical),
951			),
952		] {
953			check_stored_and_requested_mode_compatibility(created, reopened, expected);
954		}
955	}
956}