referrerpolicy=no-referrer-when-downgrade

sc_client_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//! Client backend that is backed by a database.
20//!
21//! # Canonicality vs. Finality
22//!
23//! Finality indicates that a block will not be reverted, according to the consensus algorithm,
24//! while canonicality indicates that the block may be reverted, but we will be unable to do so,
25//! having discarded heavy state that will allow a chain reorganization.
26//!
27//! Finality implies canonicality but not vice-versa.
28
29#![warn(missing_docs)]
30
31pub mod offchain;
32
33pub mod bench;
34
35mod children;
36mod parity_db;
37mod pinned_blocks_cache;
38mod record_stats_state;
39mod stats;
40#[cfg(any(feature = "rocksdb", test))]
41mod upgrade;
42mod utils;
43
44use linked_hash_map::LinkedHashMap;
45use log::{debug, trace, warn};
46use parking_lot::{Mutex, RwLock};
47use prometheus_endpoint::Registry;
48use std::{
49	collections::{HashMap, HashSet},
50	io,
51	path::{Path, PathBuf},
52	sync::Arc,
53};
54
55use crate::{
56	pinned_blocks_cache::PinnedBlocksCache,
57	record_stats_state::RecordStatsState,
58	stats::StateUsageStats,
59	utils::{meta_keys, read_db, read_meta, remove_from_db, DatabaseType, Meta},
60};
61use codec::{Decode, Encode};
62use hash_db::Prefix;
63use sc_client_api::{
64	backend::NewBlockState,
65	blockchain::{BlockGap, BlockGapType},
66	leaves::{FinalizationOutcome, LeafSet},
67	utils::is_descendent_of,
68	IoInfo, MemoryInfo, MemorySize, TrieCacheContext, UsageInfo,
69};
70use sc_state_db::{IsPruned, LastCanonicalized, StateDb};
71use sp_arithmetic::traits::Saturating;
72use sp_blockchain::{
73	Backend as _, CachedHeaderMetadata, DisplacedLeavesAfterFinalization, Error as ClientError,
74	HeaderBackend, HeaderMetadata, HeaderMetadataCache, Result as ClientResult,
75};
76use sp_core::{
77	offchain::OffchainOverlayedChange,
78	storage::{well_known_keys, ChildInfo},
79};
80use sp_database::Transaction;
81use sp_runtime::{
82	generic::BlockId,
83	traits::{
84		Block as BlockT, Hash, HashingFor, Header as HeaderT, NumberFor, One, SaturatedConversion,
85		Zero,
86	},
87	Justification, Justifications, StateVersion, Storage,
88};
89use sp_state_machine::{
90	backend::{AsTrieBackend, Backend as StateBackend},
91	BackendTransaction, ChildStorageCollection, DBValue, IndexOperation, IterArgs,
92	OffchainChangesCollection, StateMachineStats, StorageCollection, StorageIterator, StorageKey,
93	StorageValue, UsageInfo as StateUsageInfo,
94};
95use sp_trie::{cache::SharedTrieCache, prefixed_key, MemoryDB, MerkleValue, PrefixedMemoryDB};
96use utils::BLOCK_GAP_CURRENT_VERSION;
97
98// Re-export the Database trait so that one can pass an implementation of it.
99pub use sc_state_db::PruningMode;
100pub use sp_database::Database;
101
102pub use bench::BenchmarkingState;
103
104/// Filter to determine if a block should be excluded from pruning.
105///
106/// Note: This filter only affects **block body** (and future header) pruning.
107/// It does **not** affect state pruning, which is configured separately.
108pub trait PruningFilter: Send + Sync {
109	/// Check if a block with the given justifications should be preserved.
110	///
111	/// Returns `true` to preserve the block, `false` to allow pruning.
112	fn should_retain(&self, justifications: &Justifications) -> bool;
113}
114
115impl<F> PruningFilter for F
116where
117	F: Fn(&Justifications) -> bool + Send + Sync,
118{
119	fn should_retain(&self, justifications: &Justifications) -> bool {
120		(self)(justifications)
121	}
122}
123
124const CACHE_HEADERS: usize = 8;
125
126/// DB-backed patricia trie state, transaction type is an overlay of changes to commit.
127pub type DbState<H> = sp_state_machine::TrieBackend<Arc<dyn sp_state_machine::Storage<H>>, H>;
128
129/// Builder for [`DbState`].
130pub type DbStateBuilder<Hasher> =
131	sp_state_machine::TrieBackendBuilder<Arc<dyn sp_state_machine::Storage<Hasher>>, Hasher>;
132
133/// Length of a [`DbHash`].
134const DB_HASH_LEN: usize = 32;
135
136/// Hash type that this backend uses for the database.
137pub type DbHash = sp_core::H256;
138
139/// An extrinsic entry in the database.
140#[derive(Debug, Encode, Decode)]
141enum DbExtrinsic<B: BlockT> {
142	/// Extrinsic that contains indexed data.
143	Indexed {
144		/// Hash of the indexed part.
145		hash: DbHash,
146		/// Extrinsic header.
147		header: Vec<u8>,
148	},
149	/// Complete extrinsic data.
150	Full(B::Extrinsic),
151	/// Extrinsic that renews multiple indexed data items within a single call.
152	///
153	/// `hashes` is in submission order: the proof-of-storage inherent provider
154	/// walks `block_indexed_body` linearly and the runtime indexes a parallel
155	/// `Vec<TransactionInfo>` by the same position, so reordering here would
156	/// desync proof construction from verification.
157	MultiRenew {
158		/// Submission order; see variant docs.
159		hashes: Vec<DbHash>,
160		extrinsic: Vec<u8>,
161	},
162}
163
164/// A reference tracking state.
165///
166/// It makes sure that the hash we are using stays pinned in storage
167/// until this structure is dropped.
168pub struct RefTrackingState<Block: BlockT> {
169	state: DbState<HashingFor<Block>>,
170	storage: Arc<StorageDb<Block>>,
171	parent_hash: Option<Block::Hash>,
172}
173
174impl<B: BlockT> RefTrackingState<B> {
175	fn new(
176		state: DbState<HashingFor<B>>,
177		storage: Arc<StorageDb<B>>,
178		parent_hash: Option<B::Hash>,
179	) -> Self {
180		RefTrackingState { state, parent_hash, storage }
181	}
182}
183
184impl<B: BlockT> Drop for RefTrackingState<B> {
185	fn drop(&mut self) {
186		if let Some(hash) = &self.parent_hash {
187			self.storage.state_db.unpin(hash);
188		}
189	}
190}
191
192impl<Block: BlockT> std::fmt::Debug for RefTrackingState<Block> {
193	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194		write!(f, "Block {:?}", self.parent_hash)
195	}
196}
197
198/// A raw iterator over the `RefTrackingState`.
199pub struct RawIter<B: BlockT> {
200	inner: <DbState<HashingFor<B>> as StateBackend<HashingFor<B>>>::RawIter,
201}
202
203impl<B: BlockT> StorageIterator<HashingFor<B>> for RawIter<B> {
204	type Backend = RefTrackingState<B>;
205	type Error = <DbState<HashingFor<B>> as StateBackend<HashingFor<B>>>::Error;
206
207	fn next_key(&mut self, backend: &Self::Backend) -> Option<Result<StorageKey, Self::Error>> {
208		self.inner.next_key(&backend.state)
209	}
210
211	fn next_pair(
212		&mut self,
213		backend: &Self::Backend,
214	) -> Option<Result<(StorageKey, StorageValue), Self::Error>> {
215		self.inner.next_pair(&backend.state)
216	}
217
218	fn was_complete(&self) -> bool {
219		self.inner.was_complete()
220	}
221}
222
223impl<B: BlockT> StateBackend<HashingFor<B>> for RefTrackingState<B> {
224	type Error = <DbState<HashingFor<B>> as StateBackend<HashingFor<B>>>::Error;
225	type TrieBackendStorage =
226		<DbState<HashingFor<B>> as StateBackend<HashingFor<B>>>::TrieBackendStorage;
227	type RawIter = RawIter<B>;
228
229	fn storage(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
230		self.state.storage(key)
231	}
232
233	fn storage_hash(&self, key: &[u8]) -> Result<Option<B::Hash>, Self::Error> {
234		self.state.storage_hash(key)
235	}
236
237	fn child_storage(
238		&self,
239		child_info: &ChildInfo,
240		key: &[u8],
241	) -> Result<Option<Vec<u8>>, Self::Error> {
242		self.state.child_storage(child_info, key)
243	}
244
245	fn child_storage_hash(
246		&self,
247		child_info: &ChildInfo,
248		key: &[u8],
249	) -> Result<Option<B::Hash>, Self::Error> {
250		self.state.child_storage_hash(child_info, key)
251	}
252
253	fn closest_merkle_value(
254		&self,
255		key: &[u8],
256	) -> Result<Option<MerkleValue<B::Hash>>, Self::Error> {
257		self.state.closest_merkle_value(key)
258	}
259
260	fn child_closest_merkle_value(
261		&self,
262		child_info: &ChildInfo,
263		key: &[u8],
264	) -> Result<Option<MerkleValue<B::Hash>>, Self::Error> {
265		self.state.child_closest_merkle_value(child_info, key)
266	}
267
268	fn exists_storage(&self, key: &[u8]) -> Result<bool, Self::Error> {
269		self.state.exists_storage(key)
270	}
271
272	fn exists_child_storage(
273		&self,
274		child_info: &ChildInfo,
275		key: &[u8],
276	) -> Result<bool, Self::Error> {
277		self.state.exists_child_storage(child_info, key)
278	}
279
280	fn next_storage_key(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
281		self.state.next_storage_key(key)
282	}
283
284	fn next_child_storage_key(
285		&self,
286		child_info: &ChildInfo,
287		key: &[u8],
288	) -> Result<Option<Vec<u8>>, Self::Error> {
289		self.state.next_child_storage_key(child_info, key)
290	}
291
292	fn storage_root<'a>(
293		&self,
294		delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
295		state_version: StateVersion,
296	) -> (B::Hash, BackendTransaction<HashingFor<B>>) {
297		self.state.storage_root(delta, state_version)
298	}
299
300	fn child_storage_root<'a>(
301		&self,
302		child_info: &ChildInfo,
303		delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
304		state_version: StateVersion,
305	) -> (B::Hash, bool, BackendTransaction<HashingFor<B>>) {
306		self.state.child_storage_root(child_info, delta, state_version)
307	}
308
309	fn raw_iter(&self, args: IterArgs) -> Result<Self::RawIter, Self::Error> {
310		self.state.raw_iter(args).map(|inner| RawIter { inner })
311	}
312
313	fn register_overlay_stats(&self, stats: &StateMachineStats) {
314		self.state.register_overlay_stats(stats);
315	}
316
317	fn usage_info(&self) -> StateUsageInfo {
318		self.state.usage_info()
319	}
320}
321
322impl<B: BlockT> AsTrieBackend<HashingFor<B>> for RefTrackingState<B> {
323	type TrieBackendStorage =
324		<DbState<HashingFor<B>> as StateBackend<HashingFor<B>>>::TrieBackendStorage;
325
326	fn as_trie_backend(
327		&self,
328	) -> &sp_state_machine::TrieBackend<Self::TrieBackendStorage, HashingFor<B>> {
329		&self.state.as_trie_backend()
330	}
331}
332
333/// Database settings.
334pub struct DatabaseSettings {
335	/// The maximum trie cache size in bytes.
336	///
337	/// If `None` is given, the cache is disabled.
338	pub trie_cache_maximum_size: Option<usize>,
339	/// Requested state pruning mode.
340	pub state_pruning: Option<PruningMode>,
341	/// Where to find the database.
342	pub source: DatabaseSource,
343	/// Block pruning mode.
344	///
345	/// NOTE: only finalized blocks are subject for removal!
346	pub blocks_pruning: BlocksPruning,
347	/// Filters to exclude blocks from pruning.
348	///
349	/// If any filter returns `true` for a block's justifications, the block body
350	/// (and in the future, the header) will be preserved even when it falls
351	/// outside the pruning window. Does not affect state pruning.
352	pub pruning_filters: Vec<Arc<dyn PruningFilter>>,
353	/// Prometheus metrics registry.
354	pub metrics_registry: Option<Registry>,
355}
356
357/// Block pruning settings.
358#[derive(Debug, Clone, Copy, PartialEq)]
359pub enum BlocksPruning {
360	/// Keep full block history, of every block that was ever imported.
361	KeepAll,
362	/// Keep full finalized block history.
363	KeepFinalized,
364	/// Keep N recent finalized blocks.
365	Some(u32),
366}
367
368impl BlocksPruning {
369	/// True if this is an archive pruning mode (either KeepAll or KeepFinalized).
370	pub fn is_archive(&self) -> bool {
371		match *self {
372			BlocksPruning::KeepAll | BlocksPruning::KeepFinalized => true,
373			BlocksPruning::Some(_) => false,
374		}
375	}
376}
377
378/// Where to find the database..
379#[derive(Debug, Clone)]
380pub enum DatabaseSource {
381	/// Check given path, and see if there is an existing database there. If it's either `RocksDb`
382	/// or `ParityDb`, use it. If there is none, create a new instance of `ParityDb`.
383	Auto {
384		/// Path to the paritydb database.
385		paritydb_path: PathBuf,
386		/// Path to the rocksdb database.
387		rocksdb_path: PathBuf,
388		/// Cache size in MiB. Used only by `RocksDb` variant of `DatabaseSource`.
389		cache_size: usize,
390	},
391	/// Load a RocksDB database from a given path. Recommended for most uses.
392	#[cfg(feature = "rocksdb")]
393	RocksDb {
394		/// Path to the database.
395		path: PathBuf,
396		/// Cache size in MiB.
397		cache_size: usize,
398	},
399
400	/// Load a ParityDb database from a given path.
401	ParityDb {
402		/// Path to the database.
403		path: PathBuf,
404	},
405
406	/// Use a custom already-open database.
407	Custom {
408		/// the handle to the custom storage
409		db: Arc<dyn Database<DbHash>>,
410
411		/// if set, the `create` flag will be required to open such datasource
412		require_create_flag: bool,
413	},
414}
415
416impl DatabaseSource {
417	/// Return path for databases that are stored on disk.
418	pub fn path(&self) -> Option<&Path> {
419		match self {
420			// as per https://github.com/paritytech/substrate/pull/9500#discussion_r684312550
421			//
422			// IIUC this is needed for polkadot to create its own dbs, so until it can use parity db
423			// I would think rocksdb, but later parity-db.
424			DatabaseSource::Auto { paritydb_path, .. } => Some(paritydb_path),
425			#[cfg(feature = "rocksdb")]
426			DatabaseSource::RocksDb { path, .. } => Some(path),
427			DatabaseSource::ParityDb { path } => Some(path),
428			DatabaseSource::Custom { .. } => None,
429		}
430	}
431
432	/// Set path for databases that are stored on disk.
433	pub fn set_path(&mut self, p: &Path) -> bool {
434		match self {
435			DatabaseSource::Auto { ref mut paritydb_path, .. } => {
436				*paritydb_path = p.into();
437				true
438			},
439			#[cfg(feature = "rocksdb")]
440			DatabaseSource::RocksDb { ref mut path, .. } => {
441				*path = p.into();
442				true
443			},
444			DatabaseSource::ParityDb { ref mut path } => {
445				*path = p.into();
446				true
447			},
448			DatabaseSource::Custom { .. } => false,
449		}
450	}
451}
452
453impl std::fmt::Display for DatabaseSource {
454	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
455		let name = match self {
456			DatabaseSource::Auto { .. } => "Auto",
457			#[cfg(feature = "rocksdb")]
458			DatabaseSource::RocksDb { .. } => "RocksDb",
459			DatabaseSource::ParityDb { .. } => "ParityDb",
460			DatabaseSource::Custom { .. } => "Custom",
461		};
462		write!(f, "{}", name)
463	}
464}
465
466pub(crate) mod columns {
467	pub const META: u32 = crate::utils::COLUMN_META;
468	pub const STATE: u32 = 1;
469	pub const STATE_META: u32 = 2;
470	/// maps hashes to lookup keys and numbers to canon hashes.
471	pub const KEY_LOOKUP: u32 = 3;
472	pub const HEADER: u32 = 4;
473	pub const BODY: u32 = 5;
474	pub const JUSTIFICATIONS: u32 = 6;
475	pub const AUX: u32 = 8;
476	/// Offchain workers local storage
477	pub const OFFCHAIN: u32 = 9;
478	/// Transactions
479	pub const TRANSACTION: u32 = 11;
480	pub const BODY_INDEX: u32 = 12;
481}
482
483struct PendingBlock<Block: BlockT> {
484	header: Block::Header,
485	justifications: Option<Justifications>,
486	body: Option<Vec<Block::Extrinsic>>,
487	indexed_body: Option<Vec<Vec<u8>>>,
488	leaf_state: NewBlockState,
489	register_as_leaf: bool,
490}
491
492// wrapper that implements trait required for state_db
493#[derive(Clone)]
494struct StateMetaDb(Arc<dyn Database<DbHash>>);
495
496impl sc_state_db::MetaDb for StateMetaDb {
497	type Error = sp_database::error::DatabaseError;
498
499	fn get_meta(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
500		Ok(self.0.get(columns::STATE_META, key))
501	}
502}
503
504struct MetaUpdate<Block: BlockT> {
505	pub hash: Block::Hash,
506	pub number: NumberFor<Block>,
507	pub is_best: bool,
508	pub is_finalized: bool,
509	pub with_state: bool,
510}
511
512fn cache_header<Hash: std::cmp::Eq + std::hash::Hash, Header>(
513	cache: &mut LinkedHashMap<Hash, Option<Header>>,
514	hash: Hash,
515	header: Option<Header>,
516) {
517	cache.insert(hash, header);
518	while cache.len() > CACHE_HEADERS {
519		cache.pop_front();
520	}
521}
522
523/// Block database
524pub struct BlockchainDb<Block: BlockT> {
525	db: Arc<dyn Database<DbHash>>,
526	meta: Arc<RwLock<Meta<NumberFor<Block>, Block::Hash>>>,
527	leaves: RwLock<LeafSet<Block::Hash, NumberFor<Block>>>,
528	header_metadata_cache: Arc<HeaderMetadataCache<Block>>,
529	header_cache: Mutex<LinkedHashMap<Block::Hash, Option<Block::Header>>>,
530	pinned_blocks_cache: Arc<RwLock<PinnedBlocksCache<Block>>>,
531}
532
533impl<Block: BlockT> BlockchainDb<Block> {
534	fn new(db: Arc<dyn Database<DbHash>>) -> ClientResult<Self> {
535		let meta = read_meta::<Block>(&*db, columns::HEADER)?;
536		let leaves = LeafSet::read_from_db(&*db, columns::META, meta_keys::LEAF_PREFIX)?;
537		Ok(BlockchainDb {
538			db,
539			leaves: RwLock::new(leaves),
540			meta: Arc::new(RwLock::new(meta)),
541			header_metadata_cache: Arc::new(HeaderMetadataCache::default()),
542			header_cache: Default::default(),
543			pinned_blocks_cache: Arc::new(RwLock::new(PinnedBlocksCache::new())),
544		})
545	}
546
547	fn update_meta(&self, update: MetaUpdate<Block>) {
548		let MetaUpdate { hash, number, is_best, is_finalized, with_state } = update;
549		let mut meta = self.meta.write();
550		if number.is_zero() {
551			meta.genesis_hash = hash;
552		}
553
554		if is_best {
555			meta.best_number = number;
556			meta.best_hash = hash;
557		}
558
559		if is_finalized {
560			if with_state {
561				meta.finalized_state = Some((hash, number));
562			}
563			meta.finalized_number = number;
564			meta.finalized_hash = hash;
565		}
566	}
567
568	fn update_block_gap(&self, gap: Option<BlockGap<NumberFor<Block>>>) {
569		let mut meta = self.meta.write();
570		meta.block_gap = gap;
571	}
572
573	/// Empty the cache of pinned items.
574	fn clear_pinning_cache(&self) {
575		self.pinned_blocks_cache.write().clear();
576	}
577
578	/// Load a justification into the cache of pinned items.
579	/// Reference count of the item will not be increased. Use this
580	/// to load values for items into the cache which have already been pinned.
581	fn insert_justifications_if_pinned(&self, hash: Block::Hash, justification: Justification) {
582		let mut cache = self.pinned_blocks_cache.write();
583		if !cache.contains(hash) {
584			return;
585		}
586
587		let justifications = Justifications::from(justification);
588		cache.insert_justifications(hash, Some(justifications));
589	}
590
591	/// Load a justification from the db into the cache of pinned items.
592	/// Reference count of the item will not be increased. Use this
593	/// to load values for items into the cache which have already been pinned.
594	fn insert_persisted_justifications_if_pinned(&self, hash: Block::Hash) -> ClientResult<()> {
595		let mut cache = self.pinned_blocks_cache.write();
596		if !cache.contains(hash) {
597			return Ok(());
598		}
599
600		let justifications = self.justifications_uncached(hash)?;
601		cache.insert_justifications(hash, justifications);
602		Ok(())
603	}
604
605	/// Load a block body from the db into the cache of pinned items.
606	/// Reference count of the item will not be increased. Use this
607	/// to load values for items items into the cache which have already been pinned.
608	fn insert_persisted_body_if_pinned(&self, hash: Block::Hash) -> ClientResult<()> {
609		let mut cache = self.pinned_blocks_cache.write();
610		if !cache.contains(hash) {
611			return Ok(());
612		}
613
614		let body = self.body_uncached(hash)?;
615		cache.insert_body(hash, body);
616		Ok(())
617	}
618
619	/// Bump reference count for pinned item.
620	fn bump_ref(&self, hash: Block::Hash) {
621		self.pinned_blocks_cache.write().pin(hash);
622	}
623
624	/// Decrease reference count for pinned item and remove if reference count is 0.
625	fn unpin(&self, hash: Block::Hash) {
626		self.pinned_blocks_cache.write().unpin(hash);
627	}
628
629	fn justifications_uncached(&self, hash: Block::Hash) -> ClientResult<Option<Justifications>> {
630		match read_db(
631			&*self.db,
632			columns::KEY_LOOKUP,
633			columns::JUSTIFICATIONS,
634			BlockId::<Block>::Hash(hash),
635		)? {
636			Some(justifications) => match Decode::decode(&mut &justifications[..]) {
637				Ok(justifications) => Ok(Some(justifications)),
638				Err(err) => {
639					return Err(sp_blockchain::Error::Backend(format!(
640						"Error decoding justifications: {err}"
641					)))
642				},
643			},
644			None => Ok(None),
645		}
646	}
647
648	fn body_uncached(&self, hash: Block::Hash) -> ClientResult<Option<Vec<Block::Extrinsic>>> {
649		if let Some(body) =
650			read_db(&*self.db, columns::KEY_LOOKUP, columns::BODY, BlockId::Hash::<Block>(hash))?
651		{
652			// Plain body
653			match Decode::decode(&mut &body[..]) {
654				Ok(body) => return Ok(Some(body)),
655				Err(err) => {
656					return Err(sp_blockchain::Error::Backend(format!(
657						"Error decoding body: {err}"
658					)))
659				},
660			}
661		}
662
663		if let Some(index) = read_db(
664			&*self.db,
665			columns::KEY_LOOKUP,
666			columns::BODY_INDEX,
667			BlockId::Hash::<Block>(hash),
668		)? {
669			match Vec::<DbExtrinsic<Block>>::decode(&mut &index[..]) {
670				Ok(index) => {
671					let mut body = Vec::new();
672					for ex in index {
673						match ex {
674							DbExtrinsic::Indexed { hash, header } => {
675								match self.db.get(columns::TRANSACTION, hash.as_ref()) {
676									Some(t) => {
677										let mut input =
678											utils::join_input(header.as_ref(), t.as_ref());
679										let ex = Block::Extrinsic::decode(&mut input).map_err(
680											|err| {
681												sp_blockchain::Error::Backend(format!(
682													"Error decoding indexed extrinsic: {err}"
683												))
684											},
685										)?;
686										body.push(ex);
687									},
688									None => {
689										return Err(sp_blockchain::Error::Backend(format!(
690											"Missing indexed transaction {hash:?}"
691										)))
692									},
693								};
694							},
695							DbExtrinsic::Full(ex) => {
696								body.push(ex);
697							},
698							DbExtrinsic::MultiRenew { extrinsic, .. } => {
699								// Multi-renewal extrinsic: header contains the full
700								// encoded extrinsic (no indexed data to join).
701								let ex = Block::Extrinsic::decode(&mut &extrinsic[..]).map_err(
702									|err| {
703										sp_blockchain::Error::Backend(format!(
704											"Error decoding multi-renew extrinsic: {err}"
705										))
706									},
707								)?;
708								body.push(ex);
709							},
710						}
711					}
712					return Ok(Some(body));
713				},
714				Err(err) => {
715					return Err(sp_blockchain::Error::Backend(format!(
716						"Error decoding body list: {err}",
717					)))
718				},
719			}
720		}
721		Ok(None)
722	}
723
724	fn block_indexed_hashes_iter(
725		&self,
726		hash: Block::Hash,
727	) -> ClientResult<Option<impl Iterator<Item = DbHash>>> {
728		let Some(body) = read_db(
729			&*self.db,
730			columns::KEY_LOOKUP,
731			columns::BODY_INDEX,
732			BlockId::<Block>::Hash(hash),
733		)?
734		else {
735			return Ok(None);
736		};
737		match Vec::<DbExtrinsic<Block>>::decode(&mut &body[..]) {
738			Ok(index) => Ok(Some(index.into_iter().flat_map(|ex| match ex {
739				DbExtrinsic::Indexed { hash, .. } => vec![hash],
740				DbExtrinsic::MultiRenew { hashes, .. } => hashes.into_iter().collect(),
741				_ => vec![],
742			}))),
743			Err(err) => {
744				Err(sp_blockchain::Error::Backend(format!("Error decoding body list: {err}")))
745			},
746		}
747	}
748}
749
750impl<Block: BlockT> sc_client_api::blockchain::HeaderBackend<Block> for BlockchainDb<Block> {
751	fn header(&self, hash: Block::Hash) -> ClientResult<Option<Block::Header>> {
752		let mut cache = self.header_cache.lock();
753		if let Some(result) = cache.get_refresh(&hash) {
754			return Ok(result.clone());
755		}
756		let header = utils::read_header(
757			&*self.db,
758			columns::KEY_LOOKUP,
759			columns::HEADER,
760			BlockId::<Block>::Hash(hash),
761		)?;
762		cache_header(&mut cache, hash, header.clone());
763		Ok(header)
764	}
765
766	fn info(&self) -> sc_client_api::blockchain::Info<Block> {
767		let meta = self.meta.read();
768		sc_client_api::blockchain::Info {
769			best_hash: meta.best_hash,
770			best_number: meta.best_number,
771			genesis_hash: meta.genesis_hash,
772			finalized_hash: meta.finalized_hash,
773			finalized_number: meta.finalized_number,
774			finalized_state: meta.finalized_state,
775			number_leaves: self.leaves.read().count(),
776			block_gap: meta.block_gap,
777		}
778	}
779
780	fn status(&self, hash: Block::Hash) -> ClientResult<sc_client_api::blockchain::BlockStatus> {
781		match self.header(hash)?.is_some() {
782			true => Ok(sc_client_api::blockchain::BlockStatus::InChain),
783			false => Ok(sc_client_api::blockchain::BlockStatus::Unknown),
784		}
785	}
786
787	fn number(&self, hash: Block::Hash) -> ClientResult<Option<NumberFor<Block>>> {
788		Ok(self.header_metadata(hash).ok().map(|header_metadata| header_metadata.number))
789	}
790
791	fn hash(&self, number: NumberFor<Block>) -> ClientResult<Option<Block::Hash>> {
792		Ok(utils::read_header::<Block>(
793			&*self.db,
794			columns::KEY_LOOKUP,
795			columns::HEADER,
796			BlockId::Number(number),
797		)?
798		.map(|header| header.hash()))
799	}
800}
801
802impl<Block: BlockT> sc_client_api::blockchain::Backend<Block> for BlockchainDb<Block> {
803	fn body(&self, hash: Block::Hash) -> ClientResult<Option<Vec<Block::Extrinsic>>> {
804		let cache = self.pinned_blocks_cache.read();
805		if let Some(result) = cache.body(&hash) {
806			return Ok(result.clone());
807		}
808
809		self.body_uncached(hash)
810	}
811
812	fn justifications(&self, hash: Block::Hash) -> ClientResult<Option<Justifications>> {
813		let cache = self.pinned_blocks_cache.read();
814		if let Some(result) = cache.justifications(&hash) {
815			return Ok(result.clone());
816		}
817
818		self.justifications_uncached(hash)
819	}
820
821	fn last_finalized(&self) -> ClientResult<Block::Hash> {
822		Ok(self.meta.read().finalized_hash)
823	}
824
825	fn leaves(&self) -> ClientResult<Vec<Block::Hash>> {
826		Ok(self.leaves.read().hashes())
827	}
828
829	fn children(&self, parent_hash: Block::Hash) -> ClientResult<Vec<Block::Hash>> {
830		children::read_children(&*self.db, columns::META, meta_keys::CHILDREN_PREFIX, parent_hash)
831	}
832
833	fn indexed_transaction(&self, hash: DbHash) -> ClientResult<Option<Vec<u8>>> {
834		Ok(self.db.get(columns::TRANSACTION, hash.as_ref()))
835	}
836
837	fn has_indexed_transaction(&self, hash: DbHash) -> ClientResult<bool> {
838		Ok(self.db.contains(columns::TRANSACTION, hash.as_ref()))
839	}
840
841	fn block_indexed_hashes(&self, hash: Block::Hash) -> ClientResult<Option<Vec<DbHash>>> {
842		self.block_indexed_hashes_iter(hash).map(|hashes| hashes.map(Iterator::collect))
843	}
844
845	fn block_indexed_body(&self, hash: Block::Hash) -> ClientResult<Option<Vec<Vec<u8>>>> {
846		match self.block_indexed_hashes_iter(hash) {
847			Ok(Some(hashes)) => Ok(Some(
848				hashes
849					.map(|hash| match self.db.get(columns::TRANSACTION, hash.as_ref()) {
850						Some(t) => Ok(t),
851						None => Err(sp_blockchain::Error::Backend(format!(
852							"Missing indexed transaction {hash:?}",
853						))),
854					})
855					.collect::<Result<_, _>>()?,
856			)),
857			Ok(None) => Ok(None),
858			Err(err) => Err(err),
859		}
860	}
861}
862
863impl<Block: BlockT> HeaderMetadata<Block> for BlockchainDb<Block> {
864	type Error = sp_blockchain::Error;
865
866	fn header_metadata(
867		&self,
868		hash: Block::Hash,
869	) -> Result<CachedHeaderMetadata<Block>, Self::Error> {
870		self.header_metadata_cache.header_metadata(hash).map_or_else(
871			|| {
872				self.header(hash)?
873					.map(|header| {
874						let header_metadata = CachedHeaderMetadata::from(&header);
875						self.header_metadata_cache
876							.insert_header_metadata(header_metadata.hash, header_metadata.clone());
877						header_metadata
878					})
879					.ok_or_else(|| {
880						ClientError::UnknownBlock(format!(
881							"Header was not found in the database: {hash:?}",
882						))
883					})
884			},
885			Ok,
886		)
887	}
888
889	fn insert_header_metadata(&self, hash: Block::Hash, metadata: CachedHeaderMetadata<Block>) {
890		self.header_metadata_cache.insert_header_metadata(hash, metadata)
891	}
892
893	fn remove_header_metadata(&self, hash: Block::Hash) {
894		self.header_cache.lock().remove(&hash);
895		self.header_metadata_cache.remove_header_metadata(hash);
896	}
897}
898
899/// Database transaction
900pub struct BlockImportOperation<Block: BlockT> {
901	old_state: RecordStatsState<RefTrackingState<Block>, Block>,
902	db_updates: PrefixedMemoryDB<HashingFor<Block>>,
903	storage_updates: StorageCollection,
904	child_storage_updates: ChildStorageCollection,
905	offchain_storage_updates: OffchainChangesCollection,
906	pending_block: Option<PendingBlock<Block>>,
907	aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>,
908	finalized_blocks: Vec<(Block::Hash, Option<Justification>)>,
909	set_head: Option<Block::Hash>,
910	commit_state: bool,
911	create_gap: bool,
912	reset_storage: bool,
913	index_ops: Vec<IndexOperation>,
914	prefetched_indexed_transactions: HashMap<DbHash, Vec<u8>>,
915}
916
917impl<Block: BlockT> BlockImportOperation<Block> {
918	fn apply_offchain(&mut self, transaction: &mut Transaction<DbHash>) {
919		let mut count = 0;
920		for ((prefix, key), value_operation) in self.offchain_storage_updates.drain(..) {
921			count += 1;
922			let key = crate::offchain::concatenate_prefix_and_key(&prefix, &key);
923			match value_operation {
924				OffchainOverlayedChange::SetValue(val) => {
925					transaction.set_from_vec(columns::OFFCHAIN, &key, val)
926				},
927				OffchainOverlayedChange::Remove => transaction.remove(columns::OFFCHAIN, &key),
928			}
929		}
930
931		if count > 0 {
932			log::debug!(target: "sc_offchain", "Applied {count} offchain indexing changes.");
933		}
934	}
935
936	fn apply_aux(&mut self, transaction: &mut Transaction<DbHash>) {
937		for (key, maybe_val) in self.aux_ops.drain(..) {
938			match maybe_val {
939				Some(val) => transaction.set_from_vec(columns::AUX, &key, val),
940				None => transaction.remove(columns::AUX, &key),
941			}
942		}
943	}
944
945	fn apply_new_state(
946		&mut self,
947		storage: Storage,
948		state_version: StateVersion,
949	) -> ClientResult<Block::Hash> {
950		if storage.top.keys().any(|k| well_known_keys::is_child_storage_key(k)) {
951			return Err(sp_blockchain::Error::InvalidState);
952		}
953
954		let child_delta = storage.children_default.values().map(|child_content| {
955			(
956				&child_content.child_info,
957				child_content.data.iter().map(|(k, v)| (&k[..], Some(&v[..]))),
958			)
959		});
960
961		let (root, transaction) = self.old_state.full_storage_root(
962			storage.top.iter().map(|(k, v)| (&k[..], Some(&v[..]))),
963			child_delta,
964			state_version,
965		);
966
967		self.db_updates = transaction;
968		Ok(root)
969	}
970}
971
972impl<Block: BlockT> sc_client_api::backend::BlockImportOperation<Block>
973	for BlockImportOperation<Block>
974{
975	type State = RecordStatsState<RefTrackingState<Block>, Block>;
976
977	fn state(&self) -> ClientResult<Option<&Self::State>> {
978		Ok(Some(&self.old_state))
979	}
980
981	fn set_block_data(
982		&mut self,
983		header: Block::Header,
984		body: Option<Vec<Block::Extrinsic>>,
985		indexed_body: Option<Vec<Vec<u8>>>,
986		justifications: Option<Justifications>,
987		leaf_state: NewBlockState,
988		register_as_leaf: bool,
989	) -> ClientResult<()> {
990		assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
991		self.pending_block = Some(PendingBlock {
992			header,
993			body,
994			indexed_body,
995			justifications,
996			leaf_state,
997			register_as_leaf,
998		});
999		Ok(())
1000	}
1001
1002	fn update_db_storage(
1003		&mut self,
1004		update: PrefixedMemoryDB<HashingFor<Block>>,
1005	) -> ClientResult<()> {
1006		self.db_updates = update;
1007		Ok(())
1008	}
1009
1010	fn reset_storage(
1011		&mut self,
1012		storage: Storage,
1013		state_version: StateVersion,
1014	) -> ClientResult<Block::Hash> {
1015		let root = self.apply_new_state(storage, state_version)?;
1016		self.commit_state = true;
1017		self.reset_storage = true;
1018		Ok(root)
1019	}
1020
1021	fn set_genesis_state(
1022		&mut self,
1023		storage: Storage,
1024		commit: bool,
1025		state_version: StateVersion,
1026	) -> ClientResult<Block::Hash> {
1027		let root = self.apply_new_state(storage, state_version)?;
1028		self.commit_state = commit;
1029		Ok(root)
1030	}
1031
1032	fn insert_aux<I>(&mut self, ops: I) -> ClientResult<()>
1033	where
1034		I: IntoIterator<Item = (Vec<u8>, Option<Vec<u8>>)>,
1035	{
1036		self.aux_ops.append(&mut ops.into_iter().collect());
1037		Ok(())
1038	}
1039
1040	fn update_storage(
1041		&mut self,
1042		update: StorageCollection,
1043		child_update: ChildStorageCollection,
1044	) -> ClientResult<()> {
1045		self.storage_updates = update;
1046		self.child_storage_updates = child_update;
1047		Ok(())
1048	}
1049
1050	fn update_offchain_storage(
1051		&mut self,
1052		offchain_update: OffchainChangesCollection,
1053	) -> ClientResult<()> {
1054		self.offchain_storage_updates = offchain_update;
1055		Ok(())
1056	}
1057
1058	fn mark_finalized(
1059		&mut self,
1060		block: Block::Hash,
1061		justification: Option<Justification>,
1062	) -> ClientResult<()> {
1063		self.finalized_blocks.push((block, justification));
1064		Ok(())
1065	}
1066
1067	fn mark_head(&mut self, hash: Block::Hash) -> ClientResult<()> {
1068		assert!(self.set_head.is_none(), "Only one set head per operation is allowed");
1069		self.set_head = Some(hash);
1070		Ok(())
1071	}
1072
1073	fn update_transaction_index(&mut self, index_ops: Vec<IndexOperation>) -> ClientResult<()> {
1074		self.index_ops = index_ops;
1075		Ok(())
1076	}
1077
1078	fn set_renew_payloads(&mut self, payloads: HashMap<DbHash, Vec<u8>>) -> ClientResult<()> {
1079		self.prefetched_indexed_transactions = payloads;
1080		Ok(())
1081	}
1082
1083	fn set_create_gap(&mut self, create_gap: bool) {
1084		self.create_gap = create_gap;
1085	}
1086}
1087
1088struct StorageDb<Block: BlockT> {
1089	pub db: Arc<dyn Database<DbHash>>,
1090	pub state_db: StateDb<Block::Hash, Vec<u8>, StateMetaDb>,
1091	prefix_keys: bool,
1092}
1093
1094impl<Block: BlockT> sp_state_machine::Storage<HashingFor<Block>> for StorageDb<Block> {
1095	fn get(&self, key: &Block::Hash, prefix: Prefix) -> Result<Option<DBValue>, String> {
1096		if self.prefix_keys {
1097			let key = prefixed_key::<HashingFor<Block>>(key, prefix);
1098			self.state_db.get(&key, self)
1099		} else {
1100			self.state_db.get(key.as_ref(), self)
1101		}
1102		.map_err(|e| format!("Database backend error: {e:?}"))
1103	}
1104}
1105
1106impl<Block: BlockT> sc_state_db::NodeDb for StorageDb<Block> {
1107	type Error = io::Error;
1108	type Key = [u8];
1109
1110	fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
1111		Ok(self.db.get(columns::STATE, key))
1112	}
1113}
1114
1115struct DbGenesisStorage<Block: BlockT> {
1116	root: Block::Hash,
1117	storage: PrefixedMemoryDB<HashingFor<Block>>,
1118}
1119
1120impl<Block: BlockT> DbGenesisStorage<Block> {
1121	pub fn new(root: Block::Hash, storage: PrefixedMemoryDB<HashingFor<Block>>) -> Self {
1122		DbGenesisStorage { root, storage }
1123	}
1124}
1125
1126impl<Block: BlockT> sp_state_machine::Storage<HashingFor<Block>> for DbGenesisStorage<Block> {
1127	fn get(&self, key: &Block::Hash, prefix: Prefix) -> Result<Option<DBValue>, String> {
1128		use hash_db::HashDB;
1129		Ok(self.storage.get(key, prefix))
1130	}
1131}
1132
1133struct EmptyStorage<Block: BlockT>(pub Block::Hash);
1134
1135impl<Block: BlockT> EmptyStorage<Block> {
1136	pub fn new() -> Self {
1137		let mut root = Block::Hash::default();
1138		let mut mdb = MemoryDB::<HashingFor<Block>>::default();
1139		// both triedbmut are the same on empty storage.
1140		sp_trie::trie_types::TrieDBMutBuilderV1::<HashingFor<Block>>::new(&mut mdb, &mut root)
1141			.build();
1142		EmptyStorage(root)
1143	}
1144}
1145
1146impl<Block: BlockT> sp_state_machine::Storage<HashingFor<Block>> for EmptyStorage<Block> {
1147	fn get(&self, _key: &Block::Hash, _prefix: Prefix) -> Result<Option<DBValue>, String> {
1148		Ok(None)
1149	}
1150}
1151
1152/// Frozen `value` at time `at`.
1153///
1154/// Used as inner structure under lock in `FrozenForDuration`.
1155struct Frozen<T: Clone> {
1156	at: std::time::Instant,
1157	value: Option<T>,
1158}
1159
1160/// Some value frozen for period of time.
1161///
1162/// If time `duration` not passed since the value was instantiated,
1163/// current frozen value is returned. Otherwise, you have to provide
1164/// a new value which will be again frozen for `duration`.
1165pub(crate) struct FrozenForDuration<T: Clone> {
1166	duration: std::time::Duration,
1167	value: parking_lot::Mutex<Frozen<T>>,
1168}
1169
1170impl<T: Clone> FrozenForDuration<T> {
1171	fn new(duration: std::time::Duration) -> Self {
1172		Self { duration, value: Frozen { at: std::time::Instant::now(), value: None }.into() }
1173	}
1174
1175	fn take_or_else<F>(&self, f: F) -> T
1176	where
1177		F: FnOnce() -> T,
1178	{
1179		let mut lock = self.value.lock();
1180		let now = std::time::Instant::now();
1181		match lock.value.as_ref() {
1182			Some(value) if now.saturating_duration_since(lock.at) <= self.duration => value.clone(),
1183			_ => {
1184				let new_value = f();
1185				lock.at = now;
1186				lock.value = Some(new_value.clone());
1187				new_value
1188			},
1189		}
1190	}
1191}
1192
1193/// Disk backend.
1194///
1195/// Disk backend keeps data in a key-value store. In archive mode, trie nodes are kept from all
1196/// blocks. Otherwise, trie nodes are kept only from some recent blocks.
1197pub struct Backend<Block: BlockT> {
1198	storage: Arc<StorageDb<Block>>,
1199	offchain_storage: offchain::LocalStorage,
1200	blockchain: BlockchainDb<Block>,
1201	canonicalization_delay: u64,
1202	import_lock: Arc<RwLock<()>>,
1203	is_archive: bool,
1204	blocks_pruning: BlocksPruning,
1205	io_stats: FrozenForDuration<(kvdb::IoStats, StateUsageInfo)>,
1206	state_usage: Arc<StateUsageStats>,
1207	genesis_state: RwLock<Option<Arc<DbGenesisStorage<Block>>>>,
1208	shared_trie_cache: Option<sp_trie::cache::SharedTrieCache<HashingFor<Block>>>,
1209	pruning_filters: Vec<Arc<dyn PruningFilter>>,
1210}
1211
1212impl<Block: BlockT> Backend<Block> {
1213	/// Create a new instance of database backend.
1214	///
1215	/// The pruning window is how old a block must be before the state is pruned.
1216	pub fn new(db_config: DatabaseSettings, canonicalization_delay: u64) -> ClientResult<Self> {
1217		use utils::OpenDbError;
1218
1219		let db_source = &db_config.source;
1220
1221		let (needs_init, db) =
1222			match crate::utils::open_database::<Block>(db_source, DatabaseType::Full, false) {
1223				Ok(db) => (false, db),
1224				Err(OpenDbError::DoesNotExist) => {
1225					let db =
1226						crate::utils::open_database::<Block>(db_source, DatabaseType::Full, true)?;
1227					(true, db)
1228				},
1229				Err(as_is) => return Err(as_is.into()),
1230			};
1231
1232		Self::from_database(db as Arc<_>, canonicalization_delay, &db_config, needs_init)
1233	}
1234
1235	/// Reset the shared trie cache.
1236	pub fn reset_trie_cache(&self) {
1237		if let Some(cache) = &self.shared_trie_cache {
1238			cache.reset();
1239		}
1240	}
1241
1242	/// Create new memory-backed client backend for tests.
1243	#[cfg(any(test, feature = "test-helpers"))]
1244	pub fn new_test(blocks_pruning: u32, canonicalization_delay: u64) -> Self {
1245		Self::new_test_with_tx_storage(BlocksPruning::Some(blocks_pruning), canonicalization_delay)
1246	}
1247
1248	/// Create new memory-backed client backend for tests with custom pruning filters.
1249	#[cfg(any(test, feature = "test-helpers"))]
1250	pub fn new_test_with_pruning_filters(
1251		blocks_pruning: u32,
1252		canonicalization_delay: u64,
1253		pruning_filters: Vec<Arc<dyn PruningFilter>>,
1254	) -> Self {
1255		Self::new_test_with_tx_storage_and_filters(
1256			BlocksPruning::Some(blocks_pruning),
1257			canonicalization_delay,
1258			pruning_filters,
1259		)
1260	}
1261
1262	/// Create new memory-backed client backend for tests.
1263	#[cfg(any(test, feature = "test-helpers"))]
1264	pub fn new_test_with_tx_storage(
1265		blocks_pruning: BlocksPruning,
1266		canonicalization_delay: u64,
1267	) -> Self {
1268		Self::new_test_with_tx_storage_and_filters(
1269			blocks_pruning,
1270			canonicalization_delay,
1271			Default::default(),
1272		)
1273	}
1274
1275	/// Create new memory-backed client backend for tests with custom pruning filters.
1276	#[cfg(any(test, feature = "test-helpers"))]
1277	pub fn new_test_with_tx_storage_and_filters(
1278		blocks_pruning: BlocksPruning,
1279		canonicalization_delay: u64,
1280		pruning_filters: Vec<Arc<dyn PruningFilter>>,
1281	) -> Self {
1282		let db = kvdb_memorydb::create(crate::utils::NUM_COLUMNS);
1283		let db = sp_database::as_database(db);
1284		Self::new_test_with_tx_storage_source(
1285			blocks_pruning,
1286			canonicalization_delay,
1287			DatabaseSource::Custom { db, require_create_flag: true },
1288			pruning_filters,
1289		)
1290	}
1291
1292	/// Test backend with caller-chosen `DatabaseSource` (memdb / rocksdb / parity-db).
1293	#[cfg(any(test, feature = "test-helpers"))]
1294	pub fn new_test_with_tx_storage_source(
1295		blocks_pruning: BlocksPruning,
1296		canonicalization_delay: u64,
1297		source: DatabaseSource,
1298		pruning_filters: Vec<Arc<dyn PruningFilter>>,
1299	) -> Self {
1300		let state_pruning = match blocks_pruning {
1301			BlocksPruning::KeepAll => PruningMode::ArchiveAll,
1302			BlocksPruning::KeepFinalized => PruningMode::ArchiveCanonical,
1303			BlocksPruning::Some(n) => PruningMode::blocks_pruning(n),
1304		};
1305		let db_setting = DatabaseSettings {
1306			trie_cache_maximum_size: Some(16 * 1024 * 1024),
1307			state_pruning: Some(state_pruning),
1308			source,
1309			blocks_pruning,
1310			pruning_filters,
1311			metrics_registry: None,
1312		};
1313
1314		Self::new(db_setting, canonicalization_delay).expect("failed to create test-db")
1315	}
1316
1317	/// Expose the Database that is used by this backend.
1318	/// The second argument is the Column that stores the State.
1319	///
1320	/// Should only be needed for benchmarking.
1321	#[cfg(feature = "runtime-benchmarks")]
1322	pub fn expose_db(&self) -> (Arc<dyn sp_database::Database<DbHash>>, sp_database::ColumnId) {
1323		(self.storage.db.clone(), columns::STATE)
1324	}
1325
1326	/// Expose the Storage that is used by this backend.
1327	///
1328	/// Should only be needed for benchmarking.
1329	#[cfg(feature = "runtime-benchmarks")]
1330	pub fn expose_storage(&self) -> Arc<dyn sp_state_machine::Storage<HashingFor<Block>>> {
1331		self.storage.clone()
1332	}
1333
1334	/// Expose the shared trie cache that is used by this backend.
1335	///
1336	/// Should only be needed for benchmarking.
1337	#[cfg(feature = "runtime-benchmarks")]
1338	pub fn expose_shared_trie_cache(
1339		&self,
1340	) -> Option<sp_trie::cache::SharedTrieCache<HashingFor<Block>>> {
1341		self.shared_trie_cache.clone()
1342	}
1343
1344	fn from_database(
1345		db: Arc<dyn Database<DbHash>>,
1346		canonicalization_delay: u64,
1347		config: &DatabaseSettings,
1348		should_init: bool,
1349	) -> ClientResult<Self> {
1350		let mut db_init_transaction = Transaction::new();
1351
1352		let requested_state_pruning = config.state_pruning.clone();
1353		let state_meta_db = StateMetaDb(db.clone());
1354		let map_e = sp_blockchain::Error::from_state_db;
1355
1356		let (state_db_init_commit_set, state_db) = StateDb::open(
1357			state_meta_db,
1358			requested_state_pruning,
1359			!db.supports_ref_counting(),
1360			should_init,
1361		)
1362		.map_err(map_e)?;
1363
1364		apply_state_commit(&mut db_init_transaction, state_db_init_commit_set);
1365
1366		let state_pruning_used = state_db.pruning_mode();
1367		let is_archive_pruning = state_pruning_used.is_archive();
1368		let blockchain = BlockchainDb::new(db.clone())?;
1369
1370		let storage_db =
1371			StorageDb { db: db.clone(), state_db, prefix_keys: !db.supports_ref_counting() };
1372
1373		let offchain_storage = offchain::LocalStorage::new(db.clone());
1374
1375		let shared_trie_cache = config.trie_cache_maximum_size.map(|maximum_size| {
1376			let system_memory = sysinfo::System::new_all();
1377			let used_memory = system_memory.used_memory();
1378			let total_memory = system_memory.total_memory();
1379
1380			debug!("Initializing shared trie cache with size {} bytes, {}% of total memory", maximum_size, (maximum_size as f64 / total_memory as f64 * 100.0));
1381			if maximum_size as u64 > total_memory - used_memory {
1382				warn!(
1383					"Not enough memory to initialize shared trie cache. Cache size: {} bytes. System memory: used {} bytes, total {} bytes",
1384					maximum_size, used_memory, total_memory,
1385				);
1386			}
1387
1388			SharedTrieCache::new(sp_trie::cache::CacheSize::new(maximum_size), config.metrics_registry.as_ref())
1389		});
1390
1391		let backend = Backend {
1392			storage: Arc::new(storage_db),
1393			offchain_storage,
1394			blockchain,
1395			canonicalization_delay,
1396			import_lock: Default::default(),
1397			is_archive: is_archive_pruning,
1398			io_stats: FrozenForDuration::new(std::time::Duration::from_secs(1)),
1399			state_usage: Arc::new(StateUsageStats::new()),
1400			blocks_pruning: config.blocks_pruning,
1401			genesis_state: RwLock::new(None),
1402			shared_trie_cache,
1403			pruning_filters: config.pruning_filters.clone(),
1404		};
1405
1406		// Older DB versions have no last state key. Check if the state is available and set it.
1407		let info = backend.blockchain.info();
1408		if info.finalized_state.is_none() &&
1409			info.finalized_hash != Default::default() &&
1410			sc_client_api::Backend::have_state_at(
1411				&backend,
1412				info.finalized_hash,
1413				info.finalized_number,
1414			) {
1415			backend.blockchain.update_meta(MetaUpdate {
1416				hash: info.finalized_hash,
1417				number: info.finalized_number,
1418				is_best: info.finalized_hash == info.best_hash,
1419				is_finalized: true,
1420				with_state: true,
1421			});
1422		}
1423
1424		// Non archive nodes cannot fill the missing block gap with bodies.
1425		// If the gap is present, it means that every restart will try to fill the gap:
1426		// - a block request is made for each and every block in the gap
1427		// - the request is fulfilled putting pressure on the network and other nodes
1428		// - upon receiving the block, the block cannot be executed since the state
1429		//  of the parent block might have been discarded
1430		// - then the sync engine closes the gap in memory, but never in DB.
1431		//
1432		// This leads to inefficient syncing and high CPU usage on every restart. To mitigate this,
1433		// remove the gap from the DB if we detect it and the current node is not an archive.
1434		match (backend.is_archive, info.block_gap) {
1435			(false, Some(gap)) if matches!(gap.gap_type, BlockGapType::MissingBody) => {
1436				warn!(
1437					"Detected a missing body gap for non-archive nodes. Removing the gap={:?}",
1438					gap
1439				);
1440
1441				db_init_transaction.remove(columns::META, meta_keys::BLOCK_GAP);
1442				db_init_transaction.remove(columns::META, meta_keys::BLOCK_GAP_VERSION);
1443				backend.blockchain.update_block_gap(None);
1444			},
1445			_ => {},
1446		}
1447
1448		db.commit(db_init_transaction)?;
1449
1450		Ok(backend)
1451	}
1452
1453	/// Handle setting head within a transaction. `route_to` should be the last
1454	/// block that existed in the database. `best_to` should be the best block
1455	/// to be set.
1456	///
1457	/// In the case where the new best block is a block to be imported, `route_to`
1458	/// should be the parent of `best_to`. In the case where we set an existing block
1459	/// to be best, `route_to` should equal to `best_to`.
1460	fn set_head_with_transaction(
1461		&self,
1462		transaction: &mut Transaction<DbHash>,
1463		route_to: Block::Hash,
1464		best_to: (NumberFor<Block>, Block::Hash),
1465	) -> ClientResult<(Vec<Block::Hash>, Vec<Block::Hash>)> {
1466		let mut enacted = Vec::default();
1467		let mut retracted = Vec::default();
1468
1469		let (best_number, best_hash) = best_to;
1470
1471		let meta = self.blockchain.meta.read();
1472
1473		if meta.best_number.saturating_sub(best_number).saturated_into::<u64>() >
1474			self.canonicalization_delay
1475		{
1476			return Err(sp_blockchain::Error::SetHeadTooOld);
1477		}
1478
1479		let parent_exists =
1480			self.blockchain.status(route_to)? == sp_blockchain::BlockStatus::InChain;
1481
1482		// Cannot find tree route with empty DB or when imported a detached block.
1483		if meta.best_hash != Default::default() && parent_exists {
1484			let tree_route = sp_blockchain::tree_route(&self.blockchain, meta.best_hash, route_to)?;
1485
1486			// uncanonicalize: check safety violations and ensure the numbers no longer
1487			// point to these block hashes in the key mapping.
1488			for r in tree_route.retracted() {
1489				if r.hash == meta.finalized_hash {
1490					warn!(
1491						"Potential safety failure: reverting finalized block {:?}",
1492						(&r.number, &r.hash)
1493					);
1494
1495					return Err(sp_blockchain::Error::NotInFinalizedChain);
1496				}
1497
1498				retracted.push(r.hash);
1499				utils::remove_number_to_key_mapping(transaction, columns::KEY_LOOKUP, r.number)?;
1500			}
1501
1502			// canonicalize: set the number lookup to map to this block's hash.
1503			for e in tree_route.enacted() {
1504				enacted.push(e.hash);
1505				utils::insert_number_to_key_mapping(
1506					transaction,
1507					columns::KEY_LOOKUP,
1508					e.number,
1509					e.hash,
1510				)?;
1511			}
1512		}
1513
1514		let lookup_key = utils::number_and_hash_to_lookup_key(best_number, &best_hash)?;
1515		transaction.set_from_vec(columns::META, meta_keys::BEST_BLOCK, lookup_key);
1516		utils::insert_number_to_key_mapping(
1517			transaction,
1518			columns::KEY_LOOKUP,
1519			best_number,
1520			best_hash,
1521		)?;
1522
1523		Ok((enacted, retracted))
1524	}
1525
1526	fn ensure_sequential_finalization(
1527		&self,
1528		header: &Block::Header,
1529		last_finalized: Option<Block::Hash>,
1530	) -> ClientResult<()> {
1531		let last_finalized =
1532			last_finalized.unwrap_or_else(|| self.blockchain.meta.read().finalized_hash);
1533		if last_finalized != self.blockchain.meta.read().genesis_hash &&
1534			*header.parent_hash() != last_finalized
1535		{
1536			return Err(sp_blockchain::Error::NonSequentialFinalization(format!(
1537				"Last finalized {last_finalized:?} not parent of {:?}",
1538				header.hash()
1539			)));
1540		}
1541		Ok(())
1542	}
1543
1544	/// `remove_displaced` can be set to `false` if this is not the last of many subsequent calls
1545	/// for performance reasons.
1546	fn finalize_block_with_transaction(
1547		&self,
1548		transaction: &mut Transaction<DbHash>,
1549		hash: Block::Hash,
1550		header: &Block::Header,
1551		last_finalized: Option<Block::Hash>,
1552		justification: Option<Justification>,
1553		current_transaction_justifications: &mut HashMap<Block::Hash, Justification>,
1554		remove_displaced: bool,
1555	) -> ClientResult<MetaUpdate<Block>> {
1556		// TODO: ensure best chain contains this block.
1557		let number = *header.number();
1558		self.ensure_sequential_finalization(header, last_finalized)?;
1559		let with_state = sc_client_api::Backend::have_state_at(self, hash, number);
1560
1561		self.note_finalized(
1562			transaction,
1563			header,
1564			hash,
1565			with_state,
1566			current_transaction_justifications,
1567			remove_displaced,
1568		)?;
1569
1570		if let Some(justification) = justification {
1571			transaction.set_from_vec(
1572				columns::JUSTIFICATIONS,
1573				&utils::number_and_hash_to_lookup_key(number, hash)?,
1574				Justifications::from(justification.clone()).encode(),
1575			);
1576			current_transaction_justifications.insert(hash, justification);
1577		}
1578		Ok(MetaUpdate { hash, number, is_best: false, is_finalized: true, with_state })
1579	}
1580
1581	// performs forced canonicalization with a delay after importing a non-finalized block.
1582	fn force_delayed_canonicalize(
1583		&self,
1584		transaction: &mut Transaction<DbHash>,
1585	) -> ClientResult<()> {
1586		let best_canonical = match self.storage.state_db.last_canonicalized() {
1587			LastCanonicalized::None => 0,
1588			LastCanonicalized::Block(b) => b,
1589			// Nothing needs to be done when canonicalization is not happening.
1590			LastCanonicalized::NotCanonicalizing => return Ok(()),
1591		};
1592
1593		let info = self.blockchain.info();
1594		let best_number: u64 = self.blockchain.info().best_number.saturated_into();
1595
1596		for to_canonicalize in
1597			best_canonical + 1..=best_number.saturating_sub(self.canonicalization_delay)
1598		{
1599			let hash_to_canonicalize = sc_client_api::blockchain::HeaderBackend::hash(
1600				&self.blockchain,
1601				to_canonicalize.saturated_into(),
1602			)?
1603			.ok_or_else(|| {
1604				let best_hash = info.best_hash;
1605
1606				sp_blockchain::Error::Backend(format!(
1607					"Can't canonicalize missing block number #{to_canonicalize} when for best block {best_hash:?} (#{best_number})",
1608				))
1609			})?;
1610
1611			if !sc_client_api::Backend::have_state_at(
1612				self,
1613				hash_to_canonicalize,
1614				to_canonicalize.saturated_into(),
1615			) {
1616				return Ok(());
1617			}
1618
1619			trace!(target: "db", "Canonicalize block #{to_canonicalize} ({hash_to_canonicalize:?})");
1620			let commit = self.storage.state_db.canonicalize_block(&hash_to_canonicalize).map_err(
1621				sp_blockchain::Error::from_state_db::<
1622					sc_state_db::Error<sp_database::error::DatabaseError>,
1623				>,
1624			)?;
1625			apply_state_commit(transaction, commit);
1626		}
1627
1628		Ok(())
1629	}
1630
1631	fn try_commit_operation(&self, mut operation: BlockImportOperation<Block>) -> ClientResult<()> {
1632		let mut transaction = Transaction::new();
1633
1634		operation.apply_aux(&mut transaction);
1635		operation.apply_offchain(&mut transaction);
1636
1637		let mut meta_updates = Vec::with_capacity(operation.finalized_blocks.len());
1638		let (best_num, mut last_finalized_hash, mut last_finalized_num, mut block_gap) = {
1639			let meta = self.blockchain.meta.read();
1640			(meta.best_number, meta.finalized_hash, meta.finalized_number, meta.block_gap)
1641		};
1642
1643		let mut block_gap_updated = false;
1644
1645		let mut current_transaction_justifications: HashMap<Block::Hash, Justification> =
1646			HashMap::new();
1647		let mut finalized_blocks = operation.finalized_blocks.into_iter().peekable();
1648		while let Some((block_hash, justification)) = finalized_blocks.next() {
1649			let block_header = self.blockchain.expect_header(block_hash)?;
1650			meta_updates.push(self.finalize_block_with_transaction(
1651				&mut transaction,
1652				block_hash,
1653				&block_header,
1654				Some(last_finalized_hash),
1655				justification,
1656				&mut current_transaction_justifications,
1657				finalized_blocks.peek().is_none(),
1658			)?);
1659			last_finalized_hash = block_hash;
1660			last_finalized_num = *block_header.number();
1661		}
1662
1663		let imported = if let Some(pending_block) = operation.pending_block {
1664			let hash = pending_block.header.hash();
1665
1666			let parent_hash = *pending_block.header.parent_hash();
1667			let number = *pending_block.header.number();
1668			let highest_leaf = self
1669				.blockchain
1670				.leaves
1671				.read()
1672				.highest_leaf()
1673				.map(|(n, _)| n)
1674				.unwrap_or(Zero::zero());
1675			let header_exists_in_db =
1676				number <= highest_leaf && self.blockchain.header(hash)?.is_some();
1677			// Body in DB (not incoming block) - needed to update gap when adding body to existing
1678			// header.
1679			let body_exists_in_db = self.blockchain.body(hash)?.is_some();
1680			// Incoming block has body - used for fast sync gap handling.
1681			let incoming_has_body = pending_block.body.is_some();
1682
1683			// blocks are keyed by number + hash.
1684			let lookup_key = utils::number_and_hash_to_lookup_key(number, hash)?;
1685
1686			if pending_block.leaf_state.is_best() {
1687				self.set_head_with_transaction(&mut transaction, parent_hash, (number, hash))?;
1688			};
1689
1690			utils::insert_hash_to_key_mapping(&mut transaction, columns::KEY_LOOKUP, number, hash)?;
1691
1692			transaction.set_from_vec(columns::HEADER, &lookup_key, pending_block.header.encode());
1693			if let Some(body) = pending_block.body {
1694				// If we have index ops, store body in indexed format; otherwise store as a
1695				// plain blob.
1696				if operation.index_ops.is_empty() {
1697					transaction.set_from_vec(columns::BODY, &lookup_key, body.encode());
1698				} else {
1699					let body = apply_index_ops::<Block>(
1700						&mut transaction,
1701						body,
1702						operation.index_ops,
1703						operation.prefetched_indexed_transactions,
1704					);
1705					transaction.set_from_vec(columns::BODY_INDEX, &lookup_key, body);
1706				}
1707			}
1708			if let Some(body) = pending_block.indexed_body {
1709				apply_indexed_body::<Block>(&mut transaction, body);
1710			}
1711			if let Some(justifications) = pending_block.justifications {
1712				transaction.set_from_vec(
1713					columns::JUSTIFICATIONS,
1714					&lookup_key,
1715					justifications.encode(),
1716				);
1717			}
1718
1719			if number.is_zero() {
1720				transaction.set(columns::META, meta_keys::GENESIS_HASH, hash.as_ref());
1721
1722				if operation.commit_state {
1723					transaction.set_from_vec(columns::META, meta_keys::FINALIZED_STATE, lookup_key);
1724				} else {
1725					// When we don't want to commit the genesis state, we still preserve it in
1726					// memory to bootstrap consensus. It is queried for an initial list of
1727					// authorities, etc.
1728					*self.genesis_state.write() = Some(Arc::new(DbGenesisStorage::new(
1729						*pending_block.header.state_root(),
1730						operation.db_updates.clone(),
1731					)));
1732				}
1733			}
1734
1735			let finalized = if operation.commit_state {
1736				let mut changeset: sc_state_db::ChangeSet<Vec<u8>> =
1737					sc_state_db::ChangeSet::default();
1738				let mut ops: u64 = 0;
1739				let mut bytes: u64 = 0;
1740				let mut removal: u64 = 0;
1741				let mut bytes_removal: u64 = 0;
1742				for (mut key, (val, rc)) in operation.db_updates.drain() {
1743					self.storage.db.sanitize_key(&mut key);
1744					if rc > 0 {
1745						ops += 1;
1746						bytes += key.len() as u64 + val.len() as u64;
1747						if rc == 1 {
1748							changeset.inserted.push((key, val.to_vec()));
1749						} else {
1750							changeset.inserted.push((key.clone(), val.to_vec()));
1751							for _ in 0..rc - 1 {
1752								changeset.inserted.push((key.clone(), Default::default()));
1753							}
1754						}
1755					} else if rc < 0 {
1756						removal += 1;
1757						bytes_removal += key.len() as u64;
1758						if rc == -1 {
1759							changeset.deleted.push(key);
1760						} else {
1761							for _ in 0..-rc {
1762								changeset.deleted.push(key.clone());
1763							}
1764						}
1765					}
1766				}
1767				self.state_usage.tally_writes_nodes(ops, bytes);
1768				self.state_usage.tally_removed_nodes(removal, bytes_removal);
1769
1770				let mut ops: u64 = 0;
1771				let mut bytes: u64 = 0;
1772				for (key, value) in operation
1773					.storage_updates
1774					.iter()
1775					.chain(operation.child_storage_updates.iter().flat_map(|(_, s)| s.iter()))
1776				{
1777					ops += 1;
1778					bytes += key.len() as u64;
1779					if let Some(v) = value.as_ref() {
1780						bytes += v.len() as u64;
1781					}
1782				}
1783				self.state_usage.tally_writes(ops, bytes);
1784				let number_u64 = number.saturated_into::<u64>();
1785				let commit = self
1786					.storage
1787					.state_db
1788					.insert_block(&hash, number_u64, pending_block.header.parent_hash(), changeset)
1789					.map_err(|e: sc_state_db::Error<sp_database::error::DatabaseError>| {
1790						sp_blockchain::Error::from_state_db(e)
1791					})?;
1792				apply_state_commit(&mut transaction, commit);
1793				if number <= last_finalized_num {
1794					// Canonicalize in the db when re-importing existing blocks with state.
1795					let commit = self.storage.state_db.canonicalize_block(&hash).map_err(
1796						sp_blockchain::Error::from_state_db::<
1797							sc_state_db::Error<sp_database::error::DatabaseError>,
1798						>,
1799					)?;
1800					apply_state_commit(&mut transaction, commit);
1801					meta_updates.push(MetaUpdate {
1802						hash,
1803						number,
1804						is_best: false,
1805						is_finalized: true,
1806						with_state: true,
1807					});
1808				}
1809
1810				// Check if need to finalize. Genesis is always finalized instantly.
1811				let finalized = number_u64 == 0 || pending_block.leaf_state.is_final();
1812				finalized
1813			} else {
1814				(number.is_zero() && last_finalized_num.is_zero()) ||
1815					pending_block.leaf_state.is_final()
1816			};
1817
1818			let header = &pending_block.header;
1819			let is_best = pending_block.leaf_state.is_best();
1820			trace!(
1821				target: "db",
1822				"DB Commit {hash:?} ({number}), best={is_best}, state={}, header_in_db={header_exists_in_db} body_in_db={body_exists_in_db} incoming_body={incoming_has_body}, finalized={finalized}",
1823				operation.commit_state,
1824			);
1825
1826			self.state_usage.merge_sm(operation.old_state.usage_info());
1827
1828			// release state reference so that it can be finalized
1829			// VERY IMPORTANT
1830			drop(operation.old_state);
1831
1832			if finalized {
1833				// TODO: ensure best chain contains this block.
1834				self.ensure_sequential_finalization(header, Some(last_finalized_hash))?;
1835				let mut current_transaction_justifications = HashMap::new();
1836				self.note_finalized(
1837					&mut transaction,
1838					header,
1839					hash,
1840					operation.commit_state,
1841					&mut current_transaction_justifications,
1842					true,
1843				)?;
1844			} else {
1845				// canonicalize blocks which are old enough, regardless of finality.
1846				self.force_delayed_canonicalize(&mut transaction)?
1847			}
1848
1849			if !header_exists_in_db {
1850				// Add a new leaf if the block has the potential to be finalized.
1851				if pending_block.register_as_leaf &&
1852					(number > last_finalized_num || last_finalized_num.is_zero())
1853				{
1854					let mut leaves = self.blockchain.leaves.write();
1855					leaves.import(hash, number, parent_hash);
1856					leaves.prepare_transaction(
1857						&mut transaction,
1858						columns::META,
1859						meta_keys::LEAF_PREFIX,
1860					);
1861				}
1862
1863				let mut children = children::read_children(
1864					&*self.storage.db,
1865					columns::META,
1866					meta_keys::CHILDREN_PREFIX,
1867					parent_hash,
1868				)?;
1869				if !children.contains(&hash) {
1870					children.push(hash);
1871					children::write_children(
1872						&mut transaction,
1873						columns::META,
1874						meta_keys::CHILDREN_PREFIX,
1875						parent_hash,
1876						children,
1877					);
1878				}
1879			}
1880
1881			let should_check_block_gap = !header_exists_in_db || !body_exists_in_db;
1882			debug!(
1883				target: "db",
1884				"should_check_block_gap = {should_check_block_gap}",
1885			);
1886
1887			if should_check_block_gap {
1888				let update_gap =
1889					|transaction: &mut Transaction<DbHash>,
1890					 new_gap: BlockGap<NumberFor<Block>>,
1891					 block_gap: &mut Option<BlockGap<NumberFor<Block>>>| {
1892						transaction.set(columns::META, meta_keys::BLOCK_GAP, &new_gap.encode());
1893						transaction.set(
1894							columns::META,
1895							meta_keys::BLOCK_GAP_VERSION,
1896							&BLOCK_GAP_CURRENT_VERSION.encode(),
1897						);
1898						block_gap.replace(new_gap);
1899						debug!(target: "db", "Update block gap. {block_gap:?}");
1900					};
1901
1902				let remove_gap =
1903					|transaction: &mut Transaction<DbHash>,
1904					 block_gap: &mut Option<BlockGap<NumberFor<Block>>>| {
1905						transaction.remove(columns::META, meta_keys::BLOCK_GAP);
1906						transaction.remove(columns::META, meta_keys::BLOCK_GAP_VERSION);
1907						*block_gap = None;
1908						debug!(target: "db", "Removed block gap.");
1909					};
1910
1911				if let Some(mut gap) = block_gap {
1912					match gap.gap_type {
1913						BlockGapType::MissingHeaderAndBody => {
1914							// Handle blocks at gap start or immediately following (possibly
1915							// indicating blocks already imported during warp sync where
1916							// start was not updated).
1917							if number == gap.start {
1918								gap.start = number + One::one();
1919								utils::insert_number_to_key_mapping(
1920									&mut transaction,
1921									columns::KEY_LOOKUP,
1922									number,
1923									hash,
1924								)?;
1925								if gap.start > gap.end {
1926									remove_gap(&mut transaction, &mut block_gap);
1927								} else {
1928									update_gap(&mut transaction, gap, &mut block_gap);
1929								}
1930								block_gap_updated = true;
1931							}
1932						},
1933						BlockGapType::MissingBody => {
1934							// Gap increased when syncing the header chain during fast sync.
1935							if number == gap.end + One::one() && !incoming_has_body {
1936								gap.end += One::one();
1937								utils::insert_number_to_key_mapping(
1938									&mut transaction,
1939									columns::KEY_LOOKUP,
1940									number,
1941									hash,
1942								)?;
1943								update_gap(&mut transaction, gap, &mut block_gap);
1944								block_gap_updated = true;
1945							// Gap decreased when downloading the full blocks.
1946							} else if number == gap.start && incoming_has_body {
1947								gap.start += One::one();
1948								if gap.start > gap.end {
1949									remove_gap(&mut transaction, &mut block_gap);
1950								} else {
1951									update_gap(&mut transaction, gap, &mut block_gap);
1952								}
1953								block_gap_updated = true;
1954							}
1955						},
1956					}
1957				} else if operation.create_gap {
1958					if number > best_num + One::one() &&
1959						self.blockchain.header(parent_hash)?.is_none()
1960					{
1961						let gap = BlockGap {
1962							start: best_num + One::one(),
1963							end: number - One::one(),
1964							gap_type: BlockGapType::MissingHeaderAndBody,
1965						};
1966						update_gap(&mut transaction, gap, &mut block_gap);
1967						block_gap_updated = true;
1968						debug!(target: "db", "Detected block gap (warp sync) {block_gap:?}");
1969					} else if number == best_num + One::one() &&
1970						self.blockchain.header(parent_hash)?.is_some() &&
1971						!incoming_has_body
1972					{
1973						let gap = BlockGap {
1974							start: number,
1975							end: number,
1976							gap_type: BlockGapType::MissingBody,
1977						};
1978						update_gap(&mut transaction, gap, &mut block_gap);
1979						block_gap_updated = true;
1980						debug!(target: "db", "Detected block gap (fast sync) {block_gap:?}");
1981					}
1982				}
1983			}
1984
1985			meta_updates.push(MetaUpdate {
1986				hash,
1987				number,
1988				is_best: pending_block.leaf_state.is_best(),
1989				is_finalized: finalized,
1990				with_state: operation.commit_state,
1991			});
1992			Some((pending_block.header, hash))
1993		} else {
1994			None
1995		};
1996
1997		if let Some(set_head) = operation.set_head {
1998			if let Some(header) =
1999				sc_client_api::blockchain::HeaderBackend::header(&self.blockchain, set_head)?
2000			{
2001				let number = header.number();
2002				let hash = header.hash();
2003
2004				self.set_head_with_transaction(&mut transaction, hash, (*number, hash))?;
2005
2006				meta_updates.push(MetaUpdate {
2007					hash,
2008					number: *number,
2009					is_best: true,
2010					is_finalized: false,
2011					with_state: false,
2012				});
2013			} else {
2014				return Err(sp_blockchain::Error::UnknownBlock(format!(
2015					"Cannot set head {set_head:?}",
2016				)));
2017			}
2018		}
2019
2020		self.storage.db.commit(transaction)?;
2021
2022		// `reset_storage == true` means the entire state got replaced.
2023		// In this case we optimize the `STATE` column to improve read performance.
2024		if operation.reset_storage {
2025			if let Err(e) = self.storage.db.optimize_db_col(columns::STATE) {
2026				warn!(target: "db", "Failed to optimize database after state import: {e:?}");
2027			}
2028		}
2029
2030		// Apply all in-memory state changes.
2031		// Code beyond this point can't fail.
2032
2033		if let Some((header, hash)) = imported {
2034			trace!(target: "db", "DB Commit done {hash:?}");
2035			let header_metadata = CachedHeaderMetadata::from(&header);
2036			self.blockchain.insert_header_metadata(header_metadata.hash, header_metadata);
2037			cache_header(&mut self.blockchain.header_cache.lock(), hash, Some(header));
2038		}
2039
2040		for m in meta_updates {
2041			self.blockchain.update_meta(m);
2042		}
2043		if block_gap_updated {
2044			self.blockchain.update_block_gap(block_gap);
2045		}
2046
2047		Ok(())
2048	}
2049
2050	// Write stuff to a transaction after a new block is finalized. This canonicalizes finalized
2051	// blocks. Fails if called with a block which was not a child of the last finalized block.
2052	/// `remove_displaced` can be set to `false` if this is not the last of many subsequent calls
2053	/// for performance reasons.
2054	fn note_finalized(
2055		&self,
2056		transaction: &mut Transaction<DbHash>,
2057		f_header: &Block::Header,
2058		f_hash: Block::Hash,
2059		with_state: bool,
2060		current_transaction_justifications: &mut HashMap<Block::Hash, Justification>,
2061		remove_displaced: bool,
2062	) -> ClientResult<()> {
2063		let f_num = *f_header.number();
2064
2065		let lookup_key = utils::number_and_hash_to_lookup_key(f_num, f_hash)?;
2066		if with_state {
2067			transaction.set_from_vec(columns::META, meta_keys::FINALIZED_STATE, lookup_key.clone());
2068		}
2069		transaction.set_from_vec(columns::META, meta_keys::FINALIZED_BLOCK, lookup_key);
2070
2071		let requires_canonicalization = match self.storage.state_db.last_canonicalized() {
2072			LastCanonicalized::None => true,
2073			LastCanonicalized::Block(b) => f_num.saturated_into::<u64>() > b,
2074			LastCanonicalized::NotCanonicalizing => false,
2075		};
2076
2077		if requires_canonicalization && sc_client_api::Backend::have_state_at(self, f_hash, f_num) {
2078			let commit = self.storage.state_db.canonicalize_block(&f_hash).map_err(
2079				sp_blockchain::Error::from_state_db::<
2080					sc_state_db::Error<sp_database::error::DatabaseError>,
2081				>,
2082			)?;
2083			apply_state_commit(transaction, commit);
2084		}
2085
2086		if remove_displaced {
2087			let new_displaced = self.blockchain.displaced_leaves_after_finalizing(
2088				f_hash,
2089				f_num,
2090				*f_header.parent_hash(),
2091			)?;
2092
2093			self.blockchain.leaves.write().remove_displaced_leaves(FinalizationOutcome::new(
2094				new_displaced.displaced_leaves.iter().copied(),
2095			));
2096
2097			if !matches!(self.blocks_pruning, BlocksPruning::KeepAll) {
2098				self.prune_displaced_branches(transaction, &new_displaced)?;
2099			}
2100		}
2101
2102		self.prune_blocks(transaction, f_num, current_transaction_justifications)?;
2103
2104		Ok(())
2105	}
2106
2107	fn prune_blocks(
2108		&self,
2109		transaction: &mut Transaction<DbHash>,
2110		finalized_number: NumberFor<Block>,
2111		current_transaction_justifications: &mut HashMap<Block::Hash, Justification>,
2112	) -> ClientResult<()> {
2113		if let BlocksPruning::Some(blocks_pruning) = self.blocks_pruning {
2114			// Always keep the last finalized block
2115			let keep = std::cmp::max(blocks_pruning, 1);
2116			if finalized_number >= keep.into() {
2117				let number = finalized_number.saturating_sub(keep.into());
2118
2119				// Before we prune a block, check if it is pinned
2120				if let Some(hash) = self.blockchain.hash(number)? {
2121					// Check if any pruning filter wants to preserve this block.
2122					// We need to check both the current transaction justifications (not yet in DB)
2123					// and the DB itself (for justifications from previous transactions).
2124					if !self.pruning_filters.is_empty() {
2125						let justifications = match current_transaction_justifications.get(&hash) {
2126							Some(j) => Some(Justifications::from(j.clone())),
2127							None => self.blockchain.justifications(hash)?,
2128						};
2129
2130						let should_retain = justifications
2131							.map(|j| self.pruning_filters.iter().any(|f| f.should_retain(&j)))
2132							.unwrap_or(false);
2133
2134						// We can just return here, pinning can be ignored since the block will
2135						// remain in the DB.
2136						if should_retain {
2137							debug!(
2138								target: "db",
2139								"Preserving block #{number} ({hash}) due to keep predicate match"
2140							);
2141							return Ok(());
2142						}
2143					}
2144
2145					self.blockchain.insert_persisted_body_if_pinned(hash)?;
2146
2147					// If the block was finalized in this transaction, it will not be in the db
2148					// yet.
2149					if let Some(justification) = current_transaction_justifications.remove(&hash) {
2150						self.blockchain.insert_justifications_if_pinned(hash, justification);
2151					} else {
2152						self.blockchain.insert_persisted_justifications_if_pinned(hash)?;
2153					}
2154				};
2155
2156				self.prune_block(transaction, BlockId::<Block>::number(number))?;
2157			}
2158		}
2159		Ok(())
2160	}
2161
2162	fn prune_displaced_branches(
2163		&self,
2164		transaction: &mut Transaction<DbHash>,
2165		displaced: &DisplacedLeavesAfterFinalization<Block>,
2166	) -> ClientResult<()> {
2167		// Discard all blocks from displaced branches
2168		for &hash in displaced.displaced_blocks.iter() {
2169			self.blockchain.insert_persisted_body_if_pinned(hash)?;
2170			self.prune_block(transaction, BlockId::<Block>::hash(hash))?;
2171		}
2172		Ok(())
2173	}
2174
2175	fn prune_block(
2176		&self,
2177		transaction: &mut Transaction<DbHash>,
2178		id: BlockId<Block>,
2179	) -> ClientResult<()> {
2180		debug!(target: "db", "Removing block #{id}");
2181		utils::remove_from_db(
2182			transaction,
2183			&*self.storage.db,
2184			columns::KEY_LOOKUP,
2185			columns::BODY,
2186			id,
2187		)?;
2188		utils::remove_from_db(
2189			transaction,
2190			&*self.storage.db,
2191			columns::KEY_LOOKUP,
2192			columns::JUSTIFICATIONS,
2193			id,
2194		)?;
2195		if let Some(index) =
2196			read_db(&*self.storage.db, columns::KEY_LOOKUP, columns::BODY_INDEX, id)?
2197		{
2198			utils::remove_from_db(
2199				transaction,
2200				&*self.storage.db,
2201				columns::KEY_LOOKUP,
2202				columns::BODY_INDEX,
2203				id,
2204			)?;
2205			match Vec::<DbExtrinsic<Block>>::decode(&mut &index[..]) {
2206				Ok(index) => {
2207					for ex in index {
2208						match ex {
2209							DbExtrinsic::Indexed { hash, .. } => {
2210								transaction.release(columns::TRANSACTION, hash);
2211							},
2212							DbExtrinsic::MultiRenew { hashes, .. } => {
2213								for hash in hashes {
2214									transaction.release(columns::TRANSACTION, hash);
2215								}
2216							},
2217							DbExtrinsic::Full(_) => {},
2218						}
2219					}
2220				},
2221				Err(err) => {
2222					return Err(sp_blockchain::Error::Backend(format!(
2223						"Error decoding body list: {err}",
2224					)))
2225				},
2226			}
2227		}
2228		Ok(())
2229	}
2230
2231	fn empty_state(&self) -> RecordStatsState<RefTrackingState<Block>, Block> {
2232		let root = EmptyStorage::<Block>::new().0; // Empty trie
2233		let db_state = DbStateBuilder::<HashingFor<Block>>::new(self.storage.clone(), root)
2234			.with_optional_cache(self.shared_trie_cache.as_ref().map(|c| c.local_cache_untrusted()))
2235			.build();
2236		let state = RefTrackingState::new(db_state, self.storage.clone(), None);
2237		RecordStatsState::new(state, None, self.state_usage.clone())
2238	}
2239}
2240
2241fn apply_state_commit(
2242	transaction: &mut Transaction<DbHash>,
2243	commit: sc_state_db::CommitSet<Vec<u8>>,
2244) {
2245	for (key, val) in commit.data.inserted.into_iter() {
2246		transaction.set_from_vec(columns::STATE, &key[..], val);
2247	}
2248	for key in commit.data.deleted.into_iter() {
2249		transaction.remove(columns::STATE, &key[..]);
2250	}
2251	for (key, val) in commit.meta.inserted.into_iter() {
2252		transaction.set_from_vec(columns::STATE_META, &key[..], val);
2253	}
2254	for key in commit.meta.deleted.into_iter() {
2255		transaction.remove(columns::STATE_META, &key[..]);
2256	}
2257}
2258
2259fn apply_index_ops<Block: BlockT>(
2260	transaction: &mut Transaction<DbHash>,
2261	body: Vec<Block::Extrinsic>,
2262	ops: Vec<IndexOperation>,
2263	mut prefetched: HashMap<DbHash, Vec<u8>>,
2264) -> Vec<u8> {
2265	let mut extrinsic_index: Vec<DbExtrinsic<Block>> = Vec::with_capacity(body.len());
2266	let mut index_map = HashMap::new();
2267	// Submission order matters; see `DbExtrinsic::MultiRenew`. Duplicates are kept so
2268	// per-occurrence refcount inc/dec stays symmetric with prune-time release.
2269	let mut renewed_map: HashMap<u32, Vec<DbHash>> = HashMap::new();
2270	for op in ops {
2271		match op {
2272			IndexOperation::Insert { extrinsic, hash, size } => {
2273				index_map.insert(extrinsic, (hash, size));
2274			},
2275			IndexOperation::Renew { extrinsic, hash } => {
2276				renewed_map
2277					.entry(extrinsic)
2278					.or_default()
2279					.push(DbHash::from_slice(hash.as_ref()));
2280			},
2281		}
2282	}
2283	let mut store_or_reference = |tx: &mut Transaction<DbHash>, hash: DbHash| {
2284		if let Some(bytes) = prefetched.remove(&hash) {
2285			tx.store(columns::TRANSACTION, hash, bytes);
2286		} else {
2287			tx.reference(columns::TRANSACTION, hash);
2288		}
2289	};
2290	let mut n_inserted = 0usize;
2291	let mut n_renew_slots = 0usize;
2292	let mut n_renew_hashes = 0usize;
2293	let mut n_full = 0usize;
2294	for (index, extrinsic) in body.into_iter().enumerate() {
2295		let db_extrinsic = if let Some(hashes) = renewed_map.remove(&(index as u32)) {
2296			n_renew_slots += 1;
2297			n_renew_hashes += hashes.len();
2298			let encoded = extrinsic.encode();
2299			if hashes.len() == 1 {
2300				// Single renewal: backwards-compatible Indexed variant
2301				let hash = hashes[0];
2302				store_or_reference(transaction, hash);
2303				DbExtrinsic::Indexed { hash, header: encoded }
2304			} else {
2305				// Multi-renewal: bump ref counter for each hash
2306				for hash in &hashes {
2307					store_or_reference(transaction, *hash);
2308				}
2309				DbExtrinsic::MultiRenew { hashes, extrinsic: encoded }
2310			}
2311		} else {
2312			match index_map.get(&(index as u32)) {
2313				Some((hash, size)) => {
2314					let encoded = extrinsic.encode();
2315					if *size as usize <= encoded.len() {
2316						n_inserted += 1;
2317						let offset = encoded.len() - *size as usize;
2318						transaction.store(
2319							columns::TRANSACTION,
2320							DbHash::from_slice(hash.as_ref()),
2321							encoded[offset..].to_vec(),
2322						);
2323						DbExtrinsic::Indexed {
2324							hash: DbHash::from_slice(hash.as_ref()),
2325							header: encoded[..offset].to_vec(),
2326						}
2327					} else {
2328						// Invalid indexed slice. Just store full data and don't index anything.
2329						n_full += 1;
2330						DbExtrinsic::Full(extrinsic)
2331					}
2332				},
2333				_ => {
2334					n_full += 1;
2335					DbExtrinsic::Full(extrinsic)
2336				},
2337			}
2338		};
2339		extrinsic_index.push(db_extrinsic);
2340	}
2341	debug!(
2342		target: "db",
2343		"DB transaction index: {} inserted, {} slots renewed ({} hashes), {} full",
2344		n_inserted,
2345		n_renew_slots,
2346		n_renew_hashes,
2347		n_full,
2348	);
2349	extrinsic_index.encode()
2350}
2351
2352fn apply_indexed_body<Block: BlockT>(transaction: &mut Transaction<DbHash>, body: Vec<Vec<u8>>) {
2353	for extrinsic in body {
2354		let hash = sp_runtime::traits::BlakeTwo256::hash(&extrinsic);
2355		transaction.store(columns::TRANSACTION, DbHash::from_slice(hash.as_ref()), extrinsic);
2356	}
2357}
2358
2359impl<Block> sc_client_api::backend::AuxStore for Backend<Block>
2360where
2361	Block: BlockT,
2362{
2363	fn insert_aux<
2364		'a,
2365		'b: 'a,
2366		'c: 'a,
2367		I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
2368		D: IntoIterator<Item = &'a &'b [u8]>,
2369	>(
2370		&self,
2371		insert: I,
2372		delete: D,
2373	) -> ClientResult<()> {
2374		let mut transaction = Transaction::new();
2375		for (k, v) in insert {
2376			transaction.set(columns::AUX, k, v);
2377		}
2378		for k in delete {
2379			transaction.remove(columns::AUX, k);
2380		}
2381		self.storage.db.commit(transaction)?;
2382		Ok(())
2383	}
2384
2385	fn get_aux(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
2386		Ok(self.storage.db.get(columns::AUX, key))
2387	}
2388}
2389
2390impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
2391	type BlockImportOperation = BlockImportOperation<Block>;
2392	type Blockchain = BlockchainDb<Block>;
2393	type State = RecordStatsState<RefTrackingState<Block>, Block>;
2394	type OffchainStorage = offchain::LocalStorage;
2395
2396	fn begin_operation(&self) -> ClientResult<Self::BlockImportOperation> {
2397		Ok(BlockImportOperation {
2398			pending_block: None,
2399			old_state: self.empty_state(),
2400			db_updates: PrefixedMemoryDB::default(),
2401			storage_updates: Default::default(),
2402			child_storage_updates: Default::default(),
2403			offchain_storage_updates: Default::default(),
2404			aux_ops: Vec::new(),
2405			finalized_blocks: Vec::new(),
2406			set_head: None,
2407			commit_state: false,
2408			create_gap: true,
2409			reset_storage: false,
2410			index_ops: Default::default(),
2411			prefetched_indexed_transactions: Default::default(),
2412		})
2413	}
2414
2415	fn begin_state_operation(
2416		&self,
2417		operation: &mut Self::BlockImportOperation,
2418		block: Block::Hash,
2419	) -> ClientResult<()> {
2420		if block == Default::default() {
2421			operation.old_state = self.empty_state();
2422		} else {
2423			operation.old_state = self.state_at(block, TrieCacheContext::Untrusted)?;
2424		}
2425
2426		operation.commit_state = true;
2427		Ok(())
2428	}
2429
2430	fn commit_operation(&self, operation: Self::BlockImportOperation) -> ClientResult<()> {
2431		let usage = operation.old_state.usage_info();
2432		self.state_usage.merge_sm(usage);
2433
2434		if let Err(e) = self.try_commit_operation(operation) {
2435			let state_meta_db = StateMetaDb(self.storage.db.clone());
2436			self.storage
2437				.state_db
2438				.reset(state_meta_db)
2439				.map_err(sp_blockchain::Error::from_state_db)?;
2440			self.blockchain.clear_pinning_cache();
2441			Err(e)
2442		} else {
2443			self.storage.state_db.sync();
2444			Ok(())
2445		}
2446	}
2447
2448	fn finalize_block(
2449		&self,
2450		hash: Block::Hash,
2451		justification: Option<Justification>,
2452	) -> ClientResult<()> {
2453		let mut transaction = Transaction::new();
2454		let header = self.blockchain.expect_header(hash)?;
2455
2456		let mut current_transaction_justifications = HashMap::new();
2457		let m = self.finalize_block_with_transaction(
2458			&mut transaction,
2459			hash,
2460			&header,
2461			None,
2462			justification,
2463			&mut current_transaction_justifications,
2464			true,
2465		)?;
2466
2467		self.storage.db.commit(transaction)?;
2468		self.blockchain.update_meta(m);
2469		Ok(())
2470	}
2471
2472	fn append_justification(
2473		&self,
2474		hash: Block::Hash,
2475		justification: Justification,
2476	) -> ClientResult<()> {
2477		let mut transaction: Transaction<DbHash> = Transaction::new();
2478		let header = self.blockchain.expect_header(hash)?;
2479		let number = *header.number();
2480
2481		// Check if the block is finalized first.
2482		let is_descendent_of = is_descendent_of(&self.blockchain, None);
2483		let last_finalized = self.blockchain.last_finalized()?;
2484
2485		// We can do a quick check first, before doing a proper but more expensive check
2486		if number > self.blockchain.info().finalized_number ||
2487			(hash != last_finalized && !is_descendent_of(&hash, &last_finalized)?)
2488		{
2489			return Err(ClientError::NotInFinalizedChain);
2490		}
2491
2492		let justifications = if let Some(mut stored_justifications) =
2493			self.blockchain.justifications(hash)?
2494		{
2495			if !stored_justifications.append(justification) {
2496				return Err(ClientError::BadJustification("Duplicate consensus engine ID".into()));
2497			}
2498			stored_justifications
2499		} else {
2500			Justifications::from(justification)
2501		};
2502
2503		transaction.set_from_vec(
2504			columns::JUSTIFICATIONS,
2505			&utils::number_and_hash_to_lookup_key(number, hash)?,
2506			justifications.encode(),
2507		);
2508
2509		self.storage.db.commit(transaction)?;
2510
2511		Ok(())
2512	}
2513
2514	fn offchain_storage(&self) -> Option<Self::OffchainStorage> {
2515		Some(self.offchain_storage.clone())
2516	}
2517
2518	fn usage_info(&self) -> Option<UsageInfo> {
2519		let (io_stats, state_stats) = self.io_stats.take_or_else(|| {
2520			(
2521				// TODO: implement DB stats and cache size retrieval
2522				kvdb::IoStats::empty(),
2523				self.state_usage.take(),
2524			)
2525		});
2526		let database_cache = MemorySize::from_bytes(0);
2527		let state_cache = MemorySize::from_bytes(
2528			self.shared_trie_cache.as_ref().map_or(0, |c| c.used_memory_size()),
2529		);
2530
2531		Some(UsageInfo {
2532			memory: MemoryInfo { state_cache, database_cache },
2533			io: IoInfo {
2534				transactions: io_stats.transactions,
2535				bytes_read: io_stats.bytes_read,
2536				bytes_written: io_stats.bytes_written,
2537				writes: io_stats.writes,
2538				reads: io_stats.reads,
2539				average_transaction_size: io_stats.avg_transaction_size() as u64,
2540				state_reads: state_stats.reads.ops,
2541				state_writes: state_stats.writes.ops,
2542				state_writes_cache: state_stats.overlay_writes.ops,
2543				state_reads_cache: state_stats.cache_reads.ops,
2544				state_writes_nodes: state_stats.nodes_writes.ops,
2545			},
2546		})
2547	}
2548
2549	fn revert(
2550		&self,
2551		n: NumberFor<Block>,
2552		revert_finalized: bool,
2553	) -> ClientResult<(NumberFor<Block>, HashSet<Block::Hash>)> {
2554		let mut reverted_finalized = HashSet::new();
2555
2556		let info = self.blockchain.info();
2557
2558		let highest_leaf = self
2559			.blockchain
2560			.leaves
2561			.read()
2562			.highest_leaf()
2563			.and_then(|(n, h)| h.last().map(|h| (n, *h)));
2564
2565		let best_number = info.best_number;
2566		let best_hash = info.best_hash;
2567
2568		let finalized = info.finalized_number;
2569
2570		let revertible = best_number - finalized;
2571		let n = if !revert_finalized && revertible < n { revertible } else { n };
2572
2573		let (n, mut number_to_revert, mut hash_to_revert) = match highest_leaf {
2574			Some((l_n, l_h)) => (n + (l_n - best_number), l_n, l_h),
2575			None => (n, best_number, best_hash),
2576		};
2577
2578		let mut revert_blocks = || -> ClientResult<NumberFor<Block>> {
2579			for c in 0..n.saturated_into::<u64>() {
2580				if number_to_revert.is_zero() {
2581					return Ok(c.saturated_into::<NumberFor<Block>>());
2582				}
2583				let mut transaction = Transaction::new();
2584				let removed = self.blockchain.header(hash_to_revert)?.ok_or_else(|| {
2585					sp_blockchain::Error::UnknownBlock(format!(
2586						"Error reverting to {hash_to_revert}. Block header not found.",
2587					))
2588				})?;
2589				let removed_hash = hash_to_revert;
2590
2591				let prev_number = number_to_revert.saturating_sub(One::one());
2592				let prev_hash =
2593					if prev_number == best_number { best_hash } else { *removed.parent_hash() };
2594
2595				if !self.have_state_at(prev_hash, prev_number) {
2596					return Ok(c.saturated_into::<NumberFor<Block>>());
2597				}
2598
2599				match self.storage.state_db.revert_one() {
2600					Some(commit) => {
2601						apply_state_commit(&mut transaction, commit);
2602
2603						number_to_revert = prev_number;
2604						hash_to_revert = prev_hash;
2605
2606						let update_finalized = number_to_revert < finalized;
2607
2608						let key = utils::number_and_hash_to_lookup_key(
2609							number_to_revert,
2610							&hash_to_revert,
2611						)?;
2612						if update_finalized {
2613							transaction.set_from_vec(
2614								columns::META,
2615								meta_keys::FINALIZED_BLOCK,
2616								key.clone(),
2617							);
2618
2619							reverted_finalized.insert(removed_hash);
2620							if let Some((hash, _)) = self.blockchain.info().finalized_state {
2621								if hash == hash_to_revert {
2622									if !number_to_revert.is_zero() &&
2623										self.have_state_at(prev_hash, prev_number)
2624									{
2625										let lookup_key = utils::number_and_hash_to_lookup_key(
2626											prev_number,
2627											prev_hash,
2628										)?;
2629										transaction.set_from_vec(
2630											columns::META,
2631											meta_keys::FINALIZED_STATE,
2632											lookup_key,
2633										);
2634									} else {
2635										transaction
2636											.remove(columns::META, meta_keys::FINALIZED_STATE);
2637									}
2638								}
2639							}
2640						}
2641
2642						transaction.set_from_vec(columns::META, meta_keys::BEST_BLOCK, key);
2643						transaction.remove(columns::KEY_LOOKUP, removed_hash.as_ref());
2644						children::remove_children(
2645							&mut transaction,
2646							columns::META,
2647							meta_keys::CHILDREN_PREFIX,
2648							hash_to_revert,
2649						);
2650						self.prune_block(&mut transaction, BlockId::Hash(removed_hash))?;
2651						remove_from_db::<Block>(
2652							&mut transaction,
2653							&*self.storage.db,
2654							columns::KEY_LOOKUP,
2655							columns::HEADER,
2656							BlockId::Hash(removed_hash),
2657						)?;
2658
2659						self.storage.db.commit(transaction)?;
2660
2661						// Clean the cache
2662						self.blockchain.remove_header_metadata(removed_hash);
2663
2664						let is_best = number_to_revert < best_number;
2665
2666						self.blockchain.update_meta(MetaUpdate {
2667							hash: hash_to_revert,
2668							number: number_to_revert,
2669							is_best,
2670							is_finalized: update_finalized,
2671							with_state: false,
2672						});
2673					},
2674					None => return Ok(c.saturated_into::<NumberFor<Block>>()),
2675				}
2676			}
2677
2678			Ok(n)
2679		};
2680
2681		let reverted = revert_blocks()?;
2682
2683		let revert_leaves = || -> ClientResult<()> {
2684			let mut transaction = Transaction::new();
2685			let mut leaves = self.blockchain.leaves.write();
2686
2687			leaves.revert(hash_to_revert, number_to_revert).into_iter().try_for_each(
2688				|(h, _)| {
2689					self.blockchain.remove_header_metadata(h);
2690					transaction.remove(columns::KEY_LOOKUP, h.as_ref());
2691
2692					self.prune_block(&mut transaction, BlockId::Hash(h))?;
2693					remove_from_db::<Block>(
2694						&mut transaction,
2695						&*self.storage.db,
2696						columns::KEY_LOOKUP,
2697						columns::HEADER,
2698						BlockId::Hash(h),
2699					)?;
2700
2701					Ok::<_, ClientError>(())
2702				},
2703			)?;
2704			leaves.prepare_transaction(&mut transaction, columns::META, meta_keys::LEAF_PREFIX);
2705			self.storage.db.commit(transaction)?;
2706
2707			Ok(())
2708		};
2709
2710		revert_leaves()?;
2711
2712		Ok((reverted, reverted_finalized))
2713	}
2714
2715	fn remove_leaf_block(&self, hash: Block::Hash) -> ClientResult<()> {
2716		let best_hash = self.blockchain.info().best_hash;
2717
2718		if best_hash == hash {
2719			return Err(sp_blockchain::Error::Backend(format!("Can't remove best block {hash:?}")));
2720		}
2721
2722		let hdr = self.blockchain.header_metadata(hash)?;
2723		if !self.have_state_at(hash, hdr.number) {
2724			return Err(sp_blockchain::Error::UnknownBlock(format!(
2725				"State already discarded for {hash:?}",
2726			)));
2727		}
2728
2729		let mut leaves = self.blockchain.leaves.write();
2730		if !leaves.contains(hdr.number, hash) {
2731			return Err(sp_blockchain::Error::Backend(format!(
2732				"Can't remove non-leaf block {hash:?}",
2733			)));
2734		}
2735
2736		let mut transaction = Transaction::new();
2737		if let Some(commit) = self.storage.state_db.remove(&hash) {
2738			apply_state_commit(&mut transaction, commit);
2739		}
2740		transaction.remove(columns::KEY_LOOKUP, hash.as_ref());
2741
2742		let children: Vec<_> = self
2743			.blockchain()
2744			.children(hdr.parent)?
2745			.into_iter()
2746			.filter(|child_hash| *child_hash != hash)
2747			.collect();
2748		let parent_leaf = if children.is_empty() {
2749			children::remove_children(
2750				&mut transaction,
2751				columns::META,
2752				meta_keys::CHILDREN_PREFIX,
2753				hdr.parent,
2754			);
2755			Some(hdr.parent)
2756		} else {
2757			children::write_children(
2758				&mut transaction,
2759				columns::META,
2760				meta_keys::CHILDREN_PREFIX,
2761				hdr.parent,
2762				children,
2763			);
2764			None
2765		};
2766
2767		let remove_outcome = leaves.remove(hash, hdr.number, parent_leaf);
2768		leaves.prepare_transaction(&mut transaction, columns::META, meta_keys::LEAF_PREFIX);
2769		if let Err(e) = self.storage.db.commit(transaction) {
2770			if let Some(outcome) = remove_outcome {
2771				leaves.undo().undo_remove(outcome);
2772			}
2773			return Err(e.into());
2774		}
2775		self.blockchain().remove_header_metadata(hash);
2776		Ok(())
2777	}
2778
2779	fn blockchain(&self) -> &BlockchainDb<Block> {
2780		&self.blockchain
2781	}
2782
2783	fn state_at(
2784		&self,
2785		hash: Block::Hash,
2786		trie_cache_context: TrieCacheContext,
2787	) -> ClientResult<Self::State> {
2788		if hash == self.blockchain.meta.read().genesis_hash {
2789			if let Some(genesis_state) = &*self.genesis_state.read() {
2790				let root = genesis_state.root;
2791				let db_state =
2792					DbStateBuilder::<HashingFor<Block>>::new(genesis_state.clone(), root)
2793						.with_optional_cache(self.shared_trie_cache.as_ref().map(|c| {
2794							if matches!(trie_cache_context, TrieCacheContext::Trusted) {
2795								c.local_cache_trusted()
2796							} else {
2797								c.local_cache_untrusted()
2798							}
2799						}))
2800						.build();
2801
2802				let state = RefTrackingState::new(db_state, self.storage.clone(), None);
2803				return Ok(RecordStatsState::new(state, None, self.state_usage.clone()));
2804			}
2805		}
2806
2807		match self.blockchain.header_metadata(hash) {
2808			Ok(ref hdr) => {
2809				let hint = || {
2810					sc_state_db::NodeDb::get(self.storage.as_ref(), hdr.state_root.as_ref())
2811						.unwrap_or(None)
2812						.is_some()
2813				};
2814
2815				if let Ok(()) =
2816					self.storage.state_db.pin(&hash, hdr.number.saturated_into::<u64>(), hint)
2817				{
2818					let root = hdr.state_root;
2819					let db_state =
2820						DbStateBuilder::<HashingFor<Block>>::new(self.storage.clone(), root)
2821							.with_optional_cache(self.shared_trie_cache.as_ref().map(|c| {
2822								if matches!(trie_cache_context, TrieCacheContext::Trusted) {
2823									c.local_cache_trusted()
2824								} else {
2825									c.local_cache_untrusted()
2826								}
2827							}))
2828							.build();
2829					let state = RefTrackingState::new(db_state, self.storage.clone(), Some(hash));
2830					Ok(RecordStatsState::new(state, Some(hash), self.state_usage.clone()))
2831				} else {
2832					Err(sp_blockchain::Error::UnknownBlock(format!(
2833						"State already discarded for {hash:?}",
2834					)))
2835				}
2836			},
2837			Err(e) => Err(e),
2838		}
2839	}
2840
2841	fn have_state_at(&self, hash: Block::Hash, number: NumberFor<Block>) -> bool {
2842		if self.is_archive {
2843			match self.blockchain.header_metadata(hash) {
2844				Ok(header) => sp_state_machine::Storage::get(
2845					self.storage.as_ref(),
2846					&header.state_root,
2847					(&[], None),
2848				)
2849				.unwrap_or(None)
2850				.is_some(),
2851				_ => false,
2852			}
2853		} else {
2854			match self.storage.state_db.is_pruned(&hash, number.saturated_into::<u64>()) {
2855				IsPruned::Pruned => false,
2856				IsPruned::NotPruned => true,
2857				IsPruned::MaybePruned => match self.blockchain.header_metadata(hash) {
2858					Ok(header) => sp_state_machine::Storage::get(
2859						self.storage.as_ref(),
2860						&header.state_root,
2861						(&[], None),
2862					)
2863					.unwrap_or(None)
2864					.is_some(),
2865					_ => false,
2866				},
2867			}
2868		}
2869	}
2870
2871	fn get_import_lock(&self) -> &RwLock<()> {
2872		&self.import_lock
2873	}
2874
2875	fn requires_full_sync(&self) -> bool {
2876		matches!(
2877			self.storage.state_db.pruning_mode(),
2878			PruningMode::ArchiveAll | PruningMode::ArchiveCanonical
2879		)
2880	}
2881
2882	fn pin_block(&self, hash: <Block as BlockT>::Hash) -> sp_blockchain::Result<()> {
2883		let hint = || {
2884			let header_metadata = self.blockchain.header_metadata(hash);
2885			header_metadata
2886				.map(|hdr| {
2887					sc_state_db::NodeDb::get(self.storage.as_ref(), hdr.state_root.as_ref())
2888						.unwrap_or(None)
2889						.is_some()
2890				})
2891				.unwrap_or(false)
2892		};
2893
2894		if let Some(number) = self.blockchain.number(hash)? {
2895			self.storage.state_db.pin(&hash, number.saturated_into::<u64>(), hint).map_err(
2896				|_| {
2897					sp_blockchain::Error::UnknownBlock(format!(
2898						"Unable to pin: state already discarded for `{hash:?}`",
2899					))
2900				},
2901			)?;
2902		} else {
2903			return Err(ClientError::UnknownBlock(format!(
2904				"Can not pin block with hash `{hash:?}`. Block not found.",
2905			)));
2906		}
2907
2908		if self.blocks_pruning != BlocksPruning::KeepAll {
2909			// Only increase reference count for this hash. Value is loaded once we prune.
2910			self.blockchain.bump_ref(hash);
2911		}
2912		Ok(())
2913	}
2914
2915	fn unpin_block(&self, hash: <Block as BlockT>::Hash) {
2916		self.storage.state_db.unpin(&hash);
2917
2918		if self.blocks_pruning != BlocksPruning::KeepAll {
2919			self.blockchain.unpin(hash);
2920		}
2921	}
2922}
2923
2924impl<Block: BlockT> sc_client_api::backend::LocalBackend<Block> for Backend<Block> {}
2925
2926#[cfg(test)]
2927pub(crate) mod tests {
2928	use super::*;
2929	use crate::{columns, utils::number_and_hash_to_lookup_key};
2930	use hash_db::{HashDB, EMPTY_PREFIX};
2931	use sc_client_api::{
2932		backend::{Backend as BTrait, BlockImportOperation as Op},
2933		blockchain::Backend as BLBTrait,
2934	};
2935	use sp_blockchain::{lowest_common_ancestor, tree_route};
2936	use sp_core::H256;
2937	use sp_runtime::{
2938		testing::{Block as RawBlock, Header, MockCallU64, TestXt},
2939		traits::{BlakeTwo256, Hash},
2940		ConsensusEngineId, StateVersion,
2941	};
2942
2943	const CONS0_ENGINE_ID: ConsensusEngineId = *b"CON0";
2944	const CONS1_ENGINE_ID: ConsensusEngineId = *b"CON1";
2945
2946	type UncheckedXt = TestXt<MockCallU64, ()>;
2947	pub(crate) type Block = RawBlock<UncheckedXt>;
2948
2949	pub fn insert_header(
2950		backend: &Backend<Block>,
2951		number: u64,
2952		parent_hash: H256,
2953		changes: Option<Vec<(Vec<u8>, Vec<u8>)>>,
2954		extrinsics_root: H256,
2955	) -> H256 {
2956		insert_block(backend, number, parent_hash, changes, extrinsics_root, Vec::new(), None)
2957			.unwrap()
2958	}
2959
2960	pub fn insert_block(
2961		backend: &Backend<Block>,
2962		number: u64,
2963		parent_hash: H256,
2964		_changes: Option<Vec<(Vec<u8>, Vec<u8>)>>,
2965		extrinsics_root: H256,
2966		body: Vec<UncheckedXt>,
2967		transaction_index: Option<Vec<IndexOperation>>,
2968	) -> Result<H256, sp_blockchain::Error> {
2969		insert_block_with_prefetched(
2970			backend,
2971			number,
2972			parent_hash,
2973			extrinsics_root,
2974			body,
2975			transaction_index,
2976			HashMap::new(),
2977		)
2978	}
2979
2980	pub fn insert_block_with_prefetched(
2981		backend: &Backend<Block>,
2982		number: u64,
2983		parent_hash: H256,
2984		extrinsics_root: H256,
2985		body: Vec<UncheckedXt>,
2986		transaction_index: Option<Vec<IndexOperation>>,
2987		prefetched: HashMap<H256, Vec<u8>>,
2988	) -> Result<H256, sp_blockchain::Error> {
2989		use sp_runtime::testing::Digest;
2990
2991		let digest = Digest::default();
2992		let mut header =
2993			Header { number, parent_hash, state_root: Default::default(), digest, extrinsics_root };
2994
2995		let block_hash = if number == 0 { Default::default() } else { parent_hash };
2996		let mut op = backend.begin_operation().unwrap();
2997		backend.begin_state_operation(&mut op, block_hash).unwrap();
2998		if !prefetched.is_empty() {
2999			op.set_renew_payloads(prefetched).unwrap();
3000		}
3001		if let Some(index) = transaction_index {
3002			op.update_transaction_index(index).unwrap();
3003		}
3004
3005		let (root, overlay) = op.old_state.storage_root(
3006			vec![(block_hash.as_ref(), Some(block_hash.as_ref()))].into_iter(),
3007			StateVersion::V1,
3008		);
3009		op.update_db_storage(overlay).unwrap();
3010		header.state_root = root.into();
3011
3012		op.set_block_data(header.clone(), Some(body), None, None, NewBlockState::Best, true)
3013			.unwrap();
3014
3015		backend.commit_operation(op)?;
3016
3017		Ok(header.hash())
3018	}
3019
3020	/// Mirrors `apply_block` so runtime ops override wrapper-supplied ones when both are present.
3021	pub fn insert_block_with_synthetic_ops(
3022		backend: &Backend<Block>,
3023		number: u64,
3024		parent_hash: H256,
3025		extrinsics_root: H256,
3026		body: Vec<UncheckedXt>,
3027		runtime_index_ops: Vec<IndexOperation>,
3028		synthetic_index_ops: Vec<IndexOperation>,
3029		renew_payloads: HashMap<H256, Vec<u8>>,
3030	) -> Result<H256, sp_blockchain::Error> {
3031		use sp_runtime::testing::Digest;
3032
3033		let digest = Digest::default();
3034		let mut header =
3035			Header { number, parent_hash, state_root: Default::default(), digest, extrinsics_root };
3036
3037		let block_hash = if number == 0 { Default::default() } else { parent_hash };
3038		let mut op = backend.begin_operation().unwrap();
3039		backend.begin_state_operation(&mut op, block_hash).unwrap();
3040		op.set_renew_payloads(renew_payloads).unwrap();
3041		op.update_transaction_index(synthetic_index_ops).unwrap();
3042		if !runtime_index_ops.is_empty() {
3043			op.update_transaction_index(runtime_index_ops).unwrap();
3044		}
3045
3046		let (root, overlay) = op.old_state.storage_root(
3047			vec![(block_hash.as_ref(), Some(block_hash.as_ref()))].into_iter(),
3048			StateVersion::V1,
3049		);
3050		op.update_db_storage(overlay).unwrap();
3051		header.state_root = root.into();
3052
3053		op.set_block_data(header.clone(), Some(body), None, None, NewBlockState::Best, true)
3054			.unwrap();
3055
3056		backend.commit_operation(op)?;
3057
3058		Ok(header.hash())
3059	}
3060
3061	pub fn insert_disconnected_header(
3062		backend: &Backend<Block>,
3063		number: u64,
3064		parent_hash: H256,
3065		extrinsics_root: H256,
3066		best: bool,
3067	) -> H256 {
3068		use sp_runtime::testing::Digest;
3069
3070		let digest = Digest::default();
3071		let header =
3072			Header { number, parent_hash, state_root: Default::default(), digest, extrinsics_root };
3073
3074		let mut op = backend.begin_operation().unwrap();
3075
3076		op.set_block_data(
3077			header.clone(),
3078			Some(vec![]),
3079			None,
3080			None,
3081			if best { NewBlockState::Best } else { NewBlockState::Normal },
3082			true,
3083		)
3084		.unwrap();
3085
3086		backend.commit_operation(op).unwrap();
3087
3088		header.hash()
3089	}
3090
3091	pub fn insert_header_no_head(
3092		backend: &Backend<Block>,
3093		number: u64,
3094		parent_hash: H256,
3095		extrinsics_root: H256,
3096	) -> H256 {
3097		use sp_runtime::testing::Digest;
3098
3099		let digest = Digest::default();
3100		let mut header =
3101			Header { number, parent_hash, state_root: Default::default(), digest, extrinsics_root };
3102		let mut op = backend.begin_operation().unwrap();
3103
3104		let root = backend
3105			.state_at(parent_hash, TrieCacheContext::Untrusted)
3106			.unwrap_or_else(|_| {
3107				if parent_hash == Default::default() {
3108					backend.empty_state()
3109				} else {
3110					panic!("Unknown block: {parent_hash:?}")
3111				}
3112			})
3113			.storage_root(
3114				vec![(parent_hash.as_ref(), Some(parent_hash.as_ref()))].into_iter(),
3115				StateVersion::V1,
3116			)
3117			.0;
3118		header.state_root = root.into();
3119
3120		op.set_block_data(header.clone(), None, None, None, NewBlockState::Normal, true)
3121			.unwrap();
3122		backend.commit_operation(op).unwrap();
3123
3124		header.hash()
3125	}
3126
3127	#[test]
3128	fn block_hash_inserted_correctly() {
3129		let backing = {
3130			let db = Backend::<Block>::new_test(1, 0);
3131			for i in 0..10 {
3132				assert!(db.blockchain().hash(i).unwrap().is_none());
3133
3134				{
3135					let hash = if i == 0 {
3136						Default::default()
3137					} else {
3138						db.blockchain.hash(i - 1).unwrap().unwrap()
3139					};
3140
3141					let mut op = db.begin_operation().unwrap();
3142					db.begin_state_operation(&mut op, hash).unwrap();
3143					let header = Header {
3144						number: i,
3145						parent_hash: hash,
3146						state_root: Default::default(),
3147						digest: Default::default(),
3148						extrinsics_root: Default::default(),
3149					};
3150
3151					op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best, true)
3152						.unwrap();
3153					db.commit_operation(op).unwrap();
3154				}
3155
3156				assert!(db.blockchain().hash(i).unwrap().is_some())
3157			}
3158			db.storage.db.clone()
3159		};
3160
3161		let backend = Backend::<Block>::new(
3162			DatabaseSettings {
3163				trie_cache_maximum_size: Some(16 * 1024 * 1024),
3164				state_pruning: Some(PruningMode::blocks_pruning(1)),
3165				source: DatabaseSource::Custom { db: backing, require_create_flag: false },
3166				blocks_pruning: BlocksPruning::KeepFinalized,
3167				pruning_filters: Default::default(),
3168				metrics_registry: None,
3169			},
3170			0,
3171		)
3172		.unwrap();
3173		assert_eq!(backend.blockchain().info().best_number, 9);
3174		for i in 0..10 {
3175			assert!(backend.blockchain().hash(i).unwrap().is_some())
3176		}
3177	}
3178
3179	#[test]
3180	fn set_state_data() {
3181		set_state_data_inner(StateVersion::V0);
3182		set_state_data_inner(StateVersion::V1);
3183	}
3184	fn set_state_data_inner(state_version: StateVersion) {
3185		let db = Backend::<Block>::new_test(2, 0);
3186		let hash = {
3187			let mut op = db.begin_operation().unwrap();
3188			let mut header = Header {
3189				number: 0,
3190				parent_hash: Default::default(),
3191				state_root: Default::default(),
3192				digest: Default::default(),
3193				extrinsics_root: Default::default(),
3194			};
3195
3196			let storage = vec![(vec![1, 3, 5], vec![2, 4, 6]), (vec![1, 2, 3], vec![9, 9, 9])];
3197
3198			header.state_root = op
3199				.old_state
3200				.storage_root(storage.iter().map(|(x, y)| (&x[..], Some(&y[..]))), state_version)
3201				.0
3202				.into();
3203			let hash = header.hash();
3204
3205			op.reset_storage(
3206				Storage {
3207					top: storage.into_iter().collect(),
3208					children_default: Default::default(),
3209				},
3210				state_version,
3211			)
3212			.unwrap();
3213			op.set_block_data(header.clone(), Some(vec![]), None, None, NewBlockState::Best, true)
3214				.unwrap();
3215
3216			db.commit_operation(op).unwrap();
3217
3218			let state = db.state_at(hash, TrieCacheContext::Untrusted).unwrap();
3219
3220			assert_eq!(state.storage(&[1, 3, 5]).unwrap(), Some(vec![2, 4, 6]));
3221			assert_eq!(state.storage(&[1, 2, 3]).unwrap(), Some(vec![9, 9, 9]));
3222			assert_eq!(state.storage(&[5, 5, 5]).unwrap(), None);
3223
3224			hash
3225		};
3226
3227		{
3228			let mut op = db.begin_operation().unwrap();
3229			db.begin_state_operation(&mut op, hash).unwrap();
3230			let mut header = Header {
3231				number: 1,
3232				parent_hash: hash,
3233				state_root: Default::default(),
3234				digest: Default::default(),
3235				extrinsics_root: Default::default(),
3236			};
3237
3238			let storage = vec![(vec![1, 3, 5], None), (vec![5, 5, 5], Some(vec![4, 5, 6]))];
3239
3240			let (root, overlay) = op.old_state.storage_root(
3241				storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))),
3242				state_version,
3243			);
3244			op.update_db_storage(overlay).unwrap();
3245			header.state_root = root.into();
3246
3247			op.update_storage(storage, Vec::new()).unwrap();
3248			op.set_block_data(header.clone(), Some(vec![]), None, None, NewBlockState::Best, true)
3249				.unwrap();
3250
3251			db.commit_operation(op).unwrap();
3252
3253			let state = db.state_at(header.hash(), TrieCacheContext::Untrusted).unwrap();
3254
3255			assert_eq!(state.storage(&[1, 3, 5]).unwrap(), None);
3256			assert_eq!(state.storage(&[1, 2, 3]).unwrap(), Some(vec![9, 9, 9]));
3257			assert_eq!(state.storage(&[5, 5, 5]).unwrap(), Some(vec![4, 5, 6]));
3258		}
3259	}
3260
3261	#[test]
3262	fn delete_only_when_negative_rc() {
3263		sp_tracing::try_init_simple();
3264		let state_version = StateVersion::default();
3265		let key;
3266		let backend = Backend::<Block>::new_test(1, 0);
3267
3268		let hash = {
3269			let mut op = backend.begin_operation().unwrap();
3270			backend.begin_state_operation(&mut op, Default::default()).unwrap();
3271			let mut header = Header {
3272				number: 0,
3273				parent_hash: Default::default(),
3274				state_root: Default::default(),
3275				digest: Default::default(),
3276				extrinsics_root: Default::default(),
3277			};
3278
3279			header.state_root =
3280				op.old_state.storage_root(std::iter::empty(), state_version).0.into();
3281			let hash = header.hash();
3282
3283			op.reset_storage(
3284				Storage { top: Default::default(), children_default: Default::default() },
3285				state_version,
3286			)
3287			.unwrap();
3288
3289			key = op.db_updates.insert(EMPTY_PREFIX, b"hello");
3290			op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best, true)
3291				.unwrap();
3292
3293			backend.commit_operation(op).unwrap();
3294			assert_eq!(
3295				backend
3296					.storage
3297					.db
3298					.get(columns::STATE, &sp_trie::prefixed_key::<BlakeTwo256>(&key, EMPTY_PREFIX))
3299					.unwrap(),
3300				&b"hello"[..]
3301			);
3302			hash
3303		};
3304
3305		let hashof1 = {
3306			let mut op = backend.begin_operation().unwrap();
3307			backend.begin_state_operation(&mut op, hash).unwrap();
3308			let mut header = Header {
3309				number: 1,
3310				parent_hash: hash,
3311				state_root: Default::default(),
3312				digest: Default::default(),
3313				extrinsics_root: Default::default(),
3314			};
3315
3316			let storage: Vec<(_, _)> = vec![];
3317
3318			header.state_root = op
3319				.old_state
3320				.storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y))), state_version)
3321				.0
3322				.into();
3323			let hash = header.hash();
3324
3325			op.db_updates.insert(EMPTY_PREFIX, b"hello");
3326			op.db_updates.remove(&key, EMPTY_PREFIX);
3327			op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best, true)
3328				.unwrap();
3329
3330			backend.commit_operation(op).unwrap();
3331			assert_eq!(
3332				backend
3333					.storage
3334					.db
3335					.get(columns::STATE, &sp_trie::prefixed_key::<BlakeTwo256>(&key, EMPTY_PREFIX))
3336					.unwrap(),
3337				&b"hello"[..]
3338			);
3339			hash
3340		};
3341
3342		let hashof2 = {
3343			let mut op = backend.begin_operation().unwrap();
3344			backend.begin_state_operation(&mut op, hashof1).unwrap();
3345			let mut header = Header {
3346				number: 2,
3347				parent_hash: hashof1,
3348				state_root: Default::default(),
3349				digest: Default::default(),
3350				extrinsics_root: Default::default(),
3351			};
3352
3353			let storage: Vec<(_, _)> = vec![];
3354
3355			header.state_root = op
3356				.old_state
3357				.storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y))), state_version)
3358				.0
3359				.into();
3360			let hash = header.hash();
3361
3362			op.db_updates.remove(&key, EMPTY_PREFIX);
3363			op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best, true)
3364				.unwrap();
3365
3366			backend.commit_operation(op).unwrap();
3367
3368			assert!(backend
3369				.storage
3370				.db
3371				.get(columns::STATE, &sp_trie::prefixed_key::<BlakeTwo256>(&key, EMPTY_PREFIX))
3372				.is_some());
3373			hash
3374		};
3375
3376		let hashof3 = {
3377			let mut op = backend.begin_operation().unwrap();
3378			backend.begin_state_operation(&mut op, hashof2).unwrap();
3379			let mut header = Header {
3380				number: 3,
3381				parent_hash: hashof2,
3382				state_root: Default::default(),
3383				digest: Default::default(),
3384				extrinsics_root: Default::default(),
3385			};
3386
3387			let storage: Vec<(_, _)> = vec![];
3388
3389			header.state_root = op
3390				.old_state
3391				.storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y))), state_version)
3392				.0
3393				.into();
3394			let hash = header.hash();
3395
3396			op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best, true)
3397				.unwrap();
3398
3399			backend.commit_operation(op).unwrap();
3400			hash
3401		};
3402
3403		let hashof4 = {
3404			let mut op = backend.begin_operation().unwrap();
3405			backend.begin_state_operation(&mut op, hashof3).unwrap();
3406			let mut header = Header {
3407				number: 4,
3408				parent_hash: hashof3,
3409				state_root: Default::default(),
3410				digest: Default::default(),
3411				extrinsics_root: Default::default(),
3412			};
3413
3414			let storage: Vec<(_, _)> = vec![];
3415
3416			header.state_root = op
3417				.old_state
3418				.storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y))), state_version)
3419				.0
3420				.into();
3421			let hash = header.hash();
3422
3423			op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best, true)
3424				.unwrap();
3425
3426			backend.commit_operation(op).unwrap();
3427			assert!(backend
3428				.storage
3429				.db
3430				.get(columns::STATE, &sp_trie::prefixed_key::<BlakeTwo256>(&key, EMPTY_PREFIX))
3431				.is_none());
3432			hash
3433		};
3434
3435		backend.finalize_block(hashof1, None).unwrap();
3436		backend.finalize_block(hashof2, None).unwrap();
3437		backend.finalize_block(hashof3, None).unwrap();
3438		backend.finalize_block(hashof4, None).unwrap();
3439		assert!(backend
3440			.storage
3441			.db
3442			.get(columns::STATE, &sp_trie::prefixed_key::<BlakeTwo256>(&key, EMPTY_PREFIX))
3443			.is_none());
3444	}
3445
3446	#[test]
3447	fn tree_route_works() {
3448		let backend = Backend::<Block>::new_test(1000, 100);
3449		let blockchain = backend.blockchain();
3450		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
3451
3452		// fork from genesis: 3 prong.
3453		let a1 = insert_header(&backend, 1, block0, None, Default::default());
3454		let a2 = insert_header(&backend, 2, a1, None, Default::default());
3455		let a3 = insert_header(&backend, 3, a2, None, Default::default());
3456
3457		// fork from genesis: 2 prong.
3458		let b1 = insert_header(&backend, 1, block0, None, H256::from([1; 32]));
3459		let b2 = insert_header(&backend, 2, b1, None, Default::default());
3460
3461		{
3462			let tree_route = tree_route(blockchain, a1, a1).unwrap();
3463
3464			assert_eq!(tree_route.common_block().hash, a1);
3465			assert!(tree_route.retracted().is_empty());
3466			assert!(tree_route.enacted().is_empty());
3467		}
3468
3469		{
3470			let tree_route = tree_route(blockchain, a3, b2).unwrap();
3471
3472			assert_eq!(tree_route.common_block().hash, block0);
3473			assert_eq!(
3474				tree_route.retracted().iter().map(|r| r.hash).collect::<Vec<_>>(),
3475				vec![a3, a2, a1]
3476			);
3477			assert_eq!(
3478				tree_route.enacted().iter().map(|r| r.hash).collect::<Vec<_>>(),
3479				vec![b1, b2]
3480			);
3481		}
3482
3483		{
3484			let tree_route = tree_route(blockchain, a1, a3).unwrap();
3485
3486			assert_eq!(tree_route.common_block().hash, a1);
3487			assert!(tree_route.retracted().is_empty());
3488			assert_eq!(
3489				tree_route.enacted().iter().map(|r| r.hash).collect::<Vec<_>>(),
3490				vec![a2, a3]
3491			);
3492		}
3493
3494		{
3495			let tree_route = tree_route(blockchain, a3, a1).unwrap();
3496
3497			assert_eq!(tree_route.common_block().hash, a1);
3498			assert_eq!(
3499				tree_route.retracted().iter().map(|r| r.hash).collect::<Vec<_>>(),
3500				vec![a3, a2]
3501			);
3502			assert!(tree_route.enacted().is_empty());
3503		}
3504
3505		{
3506			let tree_route = tree_route(blockchain, a2, a2).unwrap();
3507
3508			assert_eq!(tree_route.common_block().hash, a2);
3509			assert!(tree_route.retracted().is_empty());
3510			assert!(tree_route.enacted().is_empty());
3511		}
3512	}
3513
3514	#[test]
3515	fn tree_route_child() {
3516		let backend = Backend::<Block>::new_test(1000, 100);
3517		let blockchain = backend.blockchain();
3518
3519		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
3520		let block1 = insert_header(&backend, 1, block0, None, Default::default());
3521
3522		{
3523			let tree_route = tree_route(blockchain, block0, block1).unwrap();
3524
3525			assert_eq!(tree_route.common_block().hash, block0);
3526			assert!(tree_route.retracted().is_empty());
3527			assert_eq!(
3528				tree_route.enacted().iter().map(|r| r.hash).collect::<Vec<_>>(),
3529				vec![block1]
3530			);
3531		}
3532	}
3533
3534	#[test]
3535	fn lowest_common_ancestor_works() {
3536		let backend = Backend::<Block>::new_test(1000, 100);
3537		let blockchain = backend.blockchain();
3538		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
3539
3540		// fork from genesis: 3 prong.
3541		let a1 = insert_header(&backend, 1, block0, None, Default::default());
3542		let a2 = insert_header(&backend, 2, a1, None, Default::default());
3543		let a3 = insert_header(&backend, 3, a2, None, Default::default());
3544
3545		// fork from genesis: 2 prong.
3546		let b1 = insert_header(&backend, 1, block0, None, H256::from([1; 32]));
3547		let b2 = insert_header(&backend, 2, b1, None, Default::default());
3548
3549		{
3550			let lca = lowest_common_ancestor(blockchain, a3, b2).unwrap();
3551
3552			assert_eq!(lca.hash, block0);
3553			assert_eq!(lca.number, 0);
3554		}
3555
3556		{
3557			let lca = lowest_common_ancestor(blockchain, a1, a3).unwrap();
3558
3559			assert_eq!(lca.hash, a1);
3560			assert_eq!(lca.number, 1);
3561		}
3562
3563		{
3564			let lca = lowest_common_ancestor(blockchain, a3, a1).unwrap();
3565
3566			assert_eq!(lca.hash, a1);
3567			assert_eq!(lca.number, 1);
3568		}
3569
3570		{
3571			let lca = lowest_common_ancestor(blockchain, a2, a3).unwrap();
3572
3573			assert_eq!(lca.hash, a2);
3574			assert_eq!(lca.number, 2);
3575		}
3576
3577		{
3578			let lca = lowest_common_ancestor(blockchain, a2, a1).unwrap();
3579
3580			assert_eq!(lca.hash, a1);
3581			assert_eq!(lca.number, 1);
3582		}
3583
3584		{
3585			let lca = lowest_common_ancestor(blockchain, a2, a2).unwrap();
3586
3587			assert_eq!(lca.hash, a2);
3588			assert_eq!(lca.number, 2);
3589		}
3590	}
3591
3592	#[test]
3593	fn displaced_leaves_after_finalizing_works_with_disconnect() {
3594		// In this test we will create a situation that can typically happen after warp sync.
3595		// The situation looks like this:
3596		// g -> <unimported> -> a3 -> a4
3597		// Basically there is a gap of unimported blocks at some point in the chain.
3598		let backend = Backend::<Block>::new_test(1000, 100);
3599		let blockchain = backend.blockchain();
3600		let genesis_number = 0;
3601		let genesis_hash =
3602			insert_header(&backend, genesis_number, Default::default(), None, Default::default());
3603
3604		let a3_number = 3;
3605		let a3_hash = insert_disconnected_header(
3606			&backend,
3607			a3_number,
3608			H256::from([200; 32]),
3609			H256::from([1; 32]),
3610			true,
3611		);
3612
3613		let a4_number = 4;
3614		let a4_hash =
3615			insert_disconnected_header(&backend, a4_number, a3_hash, H256::from([2; 32]), true);
3616		{
3617			let displaced = blockchain
3618				.displaced_leaves_after_finalizing(a3_hash, a3_number, H256::from([200; 32]))
3619				.unwrap();
3620			assert_eq!(blockchain.leaves().unwrap(), vec![a4_hash, genesis_hash]);
3621			assert_eq!(displaced.displaced_leaves, vec![(genesis_number, genesis_hash)]);
3622			assert_eq!(displaced.displaced_blocks, vec![]);
3623		}
3624
3625		{
3626			let displaced = blockchain
3627				.displaced_leaves_after_finalizing(a4_hash, a4_number, a3_hash)
3628				.unwrap();
3629			assert_eq!(blockchain.leaves().unwrap(), vec![a4_hash, genesis_hash]);
3630			assert_eq!(displaced.displaced_leaves, vec![(genesis_number, genesis_hash)]);
3631			assert_eq!(displaced.displaced_blocks, vec![]);
3632		}
3633
3634		// Import block a1 which has the genesis block as parent.
3635		// g -> a1 -> <unimported> -> a3(f) -> a4
3636		let a1_number = 1;
3637		let a1_hash = insert_disconnected_header(
3638			&backend,
3639			a1_number,
3640			genesis_hash,
3641			H256::from([123; 32]),
3642			false,
3643		);
3644		{
3645			let displaced = blockchain
3646				.displaced_leaves_after_finalizing(a3_hash, a3_number, H256::from([2; 32]))
3647				.unwrap();
3648			assert_eq!(blockchain.leaves().unwrap(), vec![a4_hash, a1_hash]);
3649			assert_eq!(displaced.displaced_leaves, vec![]);
3650			assert_eq!(displaced.displaced_blocks, vec![]);
3651		}
3652
3653		// Import block b1 which has the genesis block as parent.
3654		// g -> a1 -> <unimported> -> a3(f) -> a4
3655		//  \-> b1
3656		let b1_number = 1;
3657		let b1_hash = insert_disconnected_header(
3658			&backend,
3659			b1_number,
3660			genesis_hash,
3661			H256::from([124; 32]),
3662			false,
3663		);
3664		{
3665			let displaced = blockchain
3666				.displaced_leaves_after_finalizing(a3_hash, a3_number, H256::from([2; 32]))
3667				.unwrap();
3668			assert_eq!(blockchain.leaves().unwrap(), vec![a4_hash, a1_hash, b1_hash]);
3669			assert_eq!(displaced.displaced_leaves, vec![]);
3670			assert_eq!(displaced.displaced_blocks, vec![]);
3671		}
3672
3673		// If branch of b blocks is higher in number than a branch, we
3674		// should still not prune disconnected leafs.
3675		// g -> a1 -> <unimported> -> a3(f) -> a4
3676		//  \-> b1 -> b2 ----------> b3 ----> b4 -> b5
3677		let b2_number = 2;
3678		let b2_hash =
3679			insert_disconnected_header(&backend, b2_number, b1_hash, H256::from([40; 32]), false);
3680		let b3_number = 3;
3681		let b3_hash =
3682			insert_disconnected_header(&backend, b3_number, b2_hash, H256::from([41; 32]), false);
3683		let b4_number = 4;
3684		let b4_hash =
3685			insert_disconnected_header(&backend, b4_number, b3_hash, H256::from([42; 32]), false);
3686		let b5_number = 5;
3687		let b5_hash =
3688			insert_disconnected_header(&backend, b5_number, b4_hash, H256::from([43; 32]), false);
3689		{
3690			let displaced = blockchain
3691				.displaced_leaves_after_finalizing(a3_hash, a3_number, H256::from([2; 32]))
3692				.unwrap();
3693			assert_eq!(blockchain.leaves().unwrap(), vec![b5_hash, a4_hash, a1_hash]);
3694			assert_eq!(displaced.displaced_leaves, vec![]);
3695			assert_eq!(displaced.displaced_blocks, vec![]);
3696		}
3697
3698		// Even though there is a disconnect, diplace should still detect
3699		// branches above the block gap.
3700		//                              /-> c4
3701		// g -> a1 -> <unimported> -> a3 -> a4(f)
3702		//  \-> b1 -> b2 ----------> b3 -> b4 -> b5
3703		let c4_number = 4;
3704		let c4_hash =
3705			insert_disconnected_header(&backend, c4_number, a3_hash, H256::from([44; 32]), false);
3706		{
3707			let displaced = blockchain
3708				.displaced_leaves_after_finalizing(a4_hash, a4_number, a3_hash)
3709				.unwrap();
3710			assert_eq!(blockchain.leaves().unwrap(), vec![b5_hash, a4_hash, c4_hash, a1_hash]);
3711			assert_eq!(displaced.displaced_leaves, vec![(c4_number, c4_hash)]);
3712			assert_eq!(displaced.displaced_blocks, vec![c4_hash]);
3713		}
3714	}
3715
3716	#[test]
3717	fn disconnected_blocks_do_not_become_leaves_and_warp_sync_scenario() {
3718		// Simulate a realistic case:
3719		//
3720		// 1. Import genesis (block #0) normally — becomes a leaf.
3721		// 2. Import warp sync proof blocks at #5, #10, #15 without leaf registration. Their parents
3722		//    are NOT in the DB. They must NOT appear as leaves.
3723		// 3. Import block #20 as Final. Its parent (#19) is not in the DB. Being Final, it updates
3724		//    finalized number to 20.
3725		// 4. Import blocks #1..#19 with Normal state (gap sync). Since last_finalized_num is now 20
3726		//    and each block number < 20, the leaf condition (number > last_finalized_num ||
3727		//    last_finalized_num.is_zero()) is FALSE — they must NOT become leaves.
3728		// 5. Assert throughout and verify displaced_leaves_after_finalizing works cleanly with no
3729		//    disconnected proof blocks in the displaced list.
3730
3731		let backend = Backend::<Block>::new_test(1000, 100);
3732		let blockchain = backend.blockchain();
3733
3734		let insert_block_raw = |number: u64,
3735		                        parent_hash: H256,
3736		                        ext_root: H256,
3737		                        state: NewBlockState,
3738		                        register_as_leaf: bool|
3739		 -> H256 {
3740			use sp_runtime::testing::Digest;
3741			let digest = Digest::default();
3742			let header = Header {
3743				number,
3744				parent_hash,
3745				state_root: Default::default(),
3746				digest,
3747				extrinsics_root: ext_root,
3748			};
3749			let mut op = backend.begin_operation().unwrap();
3750			op.set_block_data(header.clone(), Some(vec![]), None, None, state, register_as_leaf)
3751				.unwrap();
3752			backend.commit_operation(op).unwrap();
3753			header.hash()
3754		};
3755
3756		// --- Step 1: import genesis ---
3757		let genesis_hash = insert_header(&backend, 0, Default::default(), None, Default::default());
3758		assert_eq!(blockchain.leaves().unwrap(), vec![genesis_hash]);
3759
3760		// --- Step 2: import warp sync proof blocks without leaf registration ---
3761		// These simulate authority-set-change blocks from the warp sync proof.
3762		// Their parents are NOT in the DB.
3763		let _proof5_hash = insert_block_raw(
3764			5,
3765			H256::from([5; 32]),
3766			H256::from([50; 32]),
3767			NewBlockState::Normal,
3768			false,
3769		);
3770		let _proof10_hash = insert_block_raw(
3771			10,
3772			H256::from([10; 32]),
3773			H256::from([100; 32]),
3774			NewBlockState::Normal,
3775			false,
3776		);
3777		let _proof15_hash = insert_block_raw(
3778			15,
3779			H256::from([15; 32]),
3780			H256::from([150; 32]),
3781			NewBlockState::Normal,
3782			false,
3783		);
3784
3785		// Leaves must still only contain genesis.
3786		assert_eq!(blockchain.leaves().unwrap(), vec![genesis_hash]);
3787
3788		// The disconnected blocks should still be retrievable from the DB.
3789		assert!(blockchain.header(_proof5_hash).unwrap().is_some());
3790		assert!(blockchain.header(_proof10_hash).unwrap().is_some());
3791		assert!(blockchain.header(_proof15_hash).unwrap().is_some());
3792
3793		// --- Step 3: import warp sync target block #20 as Final ---
3794		// Parent (#19) is not in the DB. Use the same low-level approach but with
3795		// NewBlockState::Final. Being Final, it will be set as best + finalized.
3796		let block20_hash = insert_block_raw(
3797			20,
3798			H256::from([19; 32]),
3799			H256::from([200; 32]),
3800			NewBlockState::Final,
3801			true,
3802		);
3803
3804		// Block #20 should now be a leaf (it's best and finalized).
3805		let leaves = blockchain.leaves().unwrap();
3806		assert!(leaves.contains(&block20_hash));
3807		// Verify finalized number was updated to 20.
3808		assert_eq!(blockchain.info().finalized_number, 20);
3809		assert_eq!(blockchain.info().finalized_hash, block20_hash);
3810		// Disconnected proof blocks must still not be leaves.
3811		assert!(!leaves.contains(&_proof5_hash));
3812		assert!(!leaves.contains(&_proof10_hash));
3813		assert!(!leaves.contains(&_proof15_hash));
3814
3815		// --- Step 4: import gap sync blocks #1..#19 with Normal state ---
3816		// Since last_finalized_num is 20, each block with number < 20 should NOT
3817		// become a leaf (the condition `number > last_finalized_num` is false).
3818		// Build the chain: genesis -> #1 -> #2 -> ... -> #19.
3819		let mut prev_hash = genesis_hash;
3820		let mut gap_hashes = Vec::new();
3821		for n in 1..=19 {
3822			let h = insert_disconnected_header(&backend, n, prev_hash, Default::default(), false);
3823			gap_hashes.push(h);
3824			prev_hash = h;
3825		}
3826
3827		// Verify gap sync blocks did NOT create new leaves.
3828		let leaves = blockchain.leaves().unwrap();
3829		for (i, gap_hash) in gap_hashes.iter().enumerate() {
3830			assert!(
3831				!leaves.contains(gap_hash),
3832				"Gap sync block #{} should not be a leaf, but it is",
3833				i + 1,
3834			);
3835		}
3836		// Block #20 should still be a leaf.
3837		assert!(leaves.contains(&block20_hash));
3838		// Disconnected proof blocks must still not be leaves.
3839		assert!(!leaves.contains(&_proof5_hash));
3840		assert!(!leaves.contains(&_proof10_hash));
3841		assert!(!leaves.contains(&_proof15_hash));
3842
3843		// --- Step 5: verify displaced_leaves_after_finalizing works cleanly ---
3844		// Call it for block #20 to verify no disconnected proof blocks appear
3845		// in the displaced list and it completes without errors.
3846		{
3847			let displaced = blockchain
3848				.displaced_leaves_after_finalizing(
3849					block20_hash,
3850					20,
3851					H256::from([19; 32]), // parent hash of block #20
3852				)
3853				.unwrap();
3854			// Disconnected proof blocks were never leaves, so they must not
3855			// appear in displaced_leaves.
3856			assert!(!displaced.displaced_leaves.iter().any(|(_, h)| *h == _proof5_hash),);
3857			assert!(!displaced.displaced_leaves.iter().any(|(_, h)| *h == _proof10_hash),);
3858			assert!(!displaced.displaced_leaves.iter().any(|(_, h)| *h == _proof15_hash),);
3859			// None of the gap sync blocks should be displaced leaves either
3860			// (they were never added as leaves).
3861			for gap_hash in &gap_hashes {
3862				assert!(!displaced.displaced_leaves.iter().any(|(_, h)| h == gap_hash),);
3863			}
3864		}
3865	}
3866
3867	#[test]
3868	fn displaced_leaves_after_finalizing_works() {
3869		let backend = Backend::<Block>::new_test(1000, 100);
3870		let blockchain = backend.blockchain();
3871		let genesis_number = 0;
3872		let genesis_hash =
3873			insert_header(&backend, genesis_number, Default::default(), None, Default::default());
3874
3875		// fork from genesis: 3 prong.
3876		// block 0 -> a1 -> a2 -> a3
3877		//        \
3878		//         -> b1 -> b2 -> c1 -> c2
3879		//              \
3880		//               -> d1 -> d2
3881		let a1_number = 1;
3882		let a1_hash = insert_header(&backend, a1_number, genesis_hash, None, Default::default());
3883		let a2_number = 2;
3884		let a2_hash = insert_header(&backend, a2_number, a1_hash, None, Default::default());
3885		let a3_number = 3;
3886		let a3_hash = insert_header(&backend, a3_number, a2_hash, None, Default::default());
3887
3888		{
3889			let displaced = blockchain
3890				.displaced_leaves_after_finalizing(genesis_hash, genesis_number, Default::default())
3891				.unwrap();
3892			assert_eq!(displaced.displaced_leaves, vec![]);
3893			assert_eq!(displaced.displaced_blocks, vec![]);
3894		}
3895		{
3896			let displaced_a1 = blockchain
3897				.displaced_leaves_after_finalizing(a1_hash, a1_number, genesis_hash)
3898				.unwrap();
3899			assert_eq!(displaced_a1.displaced_leaves, vec![]);
3900			assert_eq!(displaced_a1.displaced_blocks, vec![]);
3901
3902			let displaced_a2 = blockchain
3903				.displaced_leaves_after_finalizing(a2_hash, a2_number, a1_hash)
3904				.unwrap();
3905			assert_eq!(displaced_a2.displaced_leaves, vec![]);
3906			assert_eq!(displaced_a2.displaced_blocks, vec![]);
3907
3908			let displaced_a3 = blockchain
3909				.displaced_leaves_after_finalizing(a3_hash, a3_number, a2_hash)
3910				.unwrap();
3911			assert_eq!(displaced_a3.displaced_leaves, vec![]);
3912			assert_eq!(displaced_a3.displaced_blocks, vec![]);
3913		}
3914		{
3915			// Finalized block is above leaves and not imported yet.
3916			// We will not be able to make a connection,
3917			// nothing can be marked as displaced.
3918			let displaced = blockchain
3919				.displaced_leaves_after_finalizing(H256::from([57; 32]), 10, H256::from([56; 32]))
3920				.unwrap();
3921			assert_eq!(displaced.displaced_leaves, vec![]);
3922			assert_eq!(displaced.displaced_blocks, vec![]);
3923		}
3924
3925		// fork from genesis: 2 prong.
3926		let b1_number = 1;
3927		let b1_hash = insert_header(&backend, b1_number, genesis_hash, None, H256::from([1; 32]));
3928		let b2_number = 2;
3929		let b2_hash = insert_header(&backend, b2_number, b1_hash, None, Default::default());
3930
3931		// fork from b2.
3932		let c1_number = 3;
3933		let c1_hash = insert_header(&backend, c1_number, b2_hash, None, H256::from([2; 32]));
3934		let c2_number = 4;
3935		let c2_hash = insert_header(&backend, c2_number, c1_hash, None, Default::default());
3936
3937		// fork from b1.
3938		let d1_number = 2;
3939		let d1_hash = insert_header(&backend, d1_number, b1_hash, None, H256::from([3; 32]));
3940		let d2_number = 3;
3941		let d2_hash = insert_header(&backend, d2_number, d1_hash, None, Default::default());
3942
3943		{
3944			let displaced_a1 = blockchain
3945				.displaced_leaves_after_finalizing(a1_hash, a1_number, genesis_hash)
3946				.unwrap();
3947			assert_eq!(
3948				displaced_a1.displaced_leaves,
3949				vec![(c2_number, c2_hash), (d2_number, d2_hash)]
3950			);
3951			let mut displaced_blocks = vec![b1_hash, b2_hash, c1_hash, c2_hash, d1_hash, d2_hash];
3952			displaced_blocks.sort();
3953			assert_eq!(displaced_a1.displaced_blocks, displaced_blocks);
3954
3955			let displaced_a2 = blockchain
3956				.displaced_leaves_after_finalizing(a2_hash, a2_number, a1_hash)
3957				.unwrap();
3958			assert_eq!(displaced_a1.displaced_leaves, displaced_a2.displaced_leaves);
3959			assert_eq!(displaced_a1.displaced_blocks, displaced_a2.displaced_blocks);
3960
3961			let displaced_a3 = blockchain
3962				.displaced_leaves_after_finalizing(a3_hash, a3_number, a2_hash)
3963				.unwrap();
3964			assert_eq!(displaced_a1.displaced_leaves, displaced_a3.displaced_leaves);
3965			assert_eq!(displaced_a1.displaced_blocks, displaced_a3.displaced_blocks);
3966		}
3967		{
3968			let displaced = blockchain
3969				.displaced_leaves_after_finalizing(b1_hash, b1_number, genesis_hash)
3970				.unwrap();
3971			assert_eq!(displaced.displaced_leaves, vec![(a3_number, a3_hash)]);
3972			let mut displaced_blocks = vec![a1_hash, a2_hash, a3_hash];
3973			displaced_blocks.sort();
3974			assert_eq!(displaced.displaced_blocks, displaced_blocks);
3975		}
3976		{
3977			let displaced = blockchain
3978				.displaced_leaves_after_finalizing(b2_hash, b2_number, b1_hash)
3979				.unwrap();
3980			assert_eq!(
3981				displaced.displaced_leaves,
3982				vec![(a3_number, a3_hash), (d2_number, d2_hash)]
3983			);
3984			let mut displaced_blocks = vec![a1_hash, a2_hash, a3_hash, d1_hash, d2_hash];
3985			displaced_blocks.sort();
3986			assert_eq!(displaced.displaced_blocks, displaced_blocks);
3987		}
3988		{
3989			let displaced = blockchain
3990				.displaced_leaves_after_finalizing(c2_hash, c2_number, c1_hash)
3991				.unwrap();
3992			assert_eq!(
3993				displaced.displaced_leaves,
3994				vec![(a3_number, a3_hash), (d2_number, d2_hash)]
3995			);
3996			let mut displaced_blocks = vec![a1_hash, a2_hash, a3_hash, d1_hash, d2_hash];
3997			displaced_blocks.sort();
3998			assert_eq!(displaced.displaced_blocks, displaced_blocks);
3999		}
4000	}
4001
4002	#[test]
4003	fn test_tree_route_regression() {
4004		// NOTE: this is a test for a regression introduced in #3665, the result
4005		// of tree_route would be erroneously computed, since it was taking into
4006		// account the `ancestor` in `CachedHeaderMetadata` for the comparison.
4007		// in this test we simulate the same behavior with the side-effect
4008		// triggering the issue being eviction of a previously fetched record
4009		// from the cache, therefore this test is dependent on the LRU cache
4010		// size for header metadata, which is currently set to 5000 elements.
4011		let backend = Backend::<Block>::new_test(10000, 10000);
4012		let blockchain = backend.blockchain();
4013
4014		let genesis = insert_header(&backend, 0, Default::default(), None, Default::default());
4015
4016		let block100 = (1..=100).fold(genesis, |parent, n| {
4017			insert_header(&backend, n, parent, None, Default::default())
4018		});
4019
4020		let block7000 = (101..=7000).fold(block100, |parent, n| {
4021			insert_header(&backend, n, parent, None, Default::default())
4022		});
4023
4024		// This will cause the ancestor of `block100` to be set to `genesis` as a side-effect.
4025		lowest_common_ancestor(blockchain, genesis, block100).unwrap();
4026
4027		// While traversing the tree we will have to do 6900 calls to
4028		// `header_metadata`, which will make sure we will exhaust our cache
4029		// which only takes 5000 elements. In particular, the `CachedHeaderMetadata` struct for
4030		// block #100 will be evicted and will get a new value (with ancestor set to its parent).
4031		let tree_route = tree_route(blockchain, block100, block7000).unwrap();
4032
4033		assert!(tree_route.retracted().is_empty());
4034	}
4035
4036	#[test]
4037	fn test_leaves_with_complex_block_tree() {
4038		let backend: Arc<Backend<substrate_test_runtime_client::runtime::Block>> =
4039			Arc::new(Backend::new_test(20, 20));
4040		substrate_test_runtime_client::trait_tests::test_leaves_for_backend(backend);
4041	}
4042
4043	#[test]
4044	fn test_children_with_complex_block_tree() {
4045		let backend: Arc<Backend<substrate_test_runtime_client::runtime::Block>> =
4046			Arc::new(Backend::new_test(20, 20));
4047		substrate_test_runtime_client::trait_tests::test_children_for_backend(backend);
4048	}
4049
4050	#[test]
4051	fn test_blockchain_query_by_number_gets_canonical() {
4052		let backend: Arc<Backend<substrate_test_runtime_client::runtime::Block>> =
4053			Arc::new(Backend::new_test(20, 20));
4054		substrate_test_runtime_client::trait_tests::test_blockchain_query_by_number_gets_canonical(
4055			backend,
4056		);
4057	}
4058
4059	#[test]
4060	fn test_leaves_pruned_on_finality() {
4061		//   / 1b - 2b - 3b
4062		// 0 - 1a - 2a
4063		//   \ 1c
4064		let backend: Backend<Block> = Backend::new_test(10, 10);
4065		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
4066
4067		let block1_a = insert_header(&backend, 1, block0, None, Default::default());
4068		let block1_b = insert_header(&backend, 1, block0, None, [1; 32].into());
4069		let block1_c = insert_header(&backend, 1, block0, None, [2; 32].into());
4070
4071		assert_eq!(backend.blockchain().leaves().unwrap(), vec![block1_a, block1_b, block1_c]);
4072
4073		let block2_a = insert_header(&backend, 2, block1_a, None, Default::default());
4074		let block2_b = insert_header(&backend, 2, block1_b, None, Default::default());
4075
4076		let block3_b = insert_header(&backend, 3, block2_b, None, [3; 32].into());
4077
4078		assert_eq!(backend.blockchain().leaves().unwrap(), vec![block3_b, block2_a, block1_c]);
4079
4080		backend.finalize_block(block1_a, None).unwrap();
4081		backend.finalize_block(block2_a, None).unwrap();
4082
4083		// All leaves are pruned that are known to not belong to canonical branch
4084		assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2_a]);
4085	}
4086
4087	#[test]
4088	fn test_aux() {
4089		let backend: Backend<substrate_test_runtime_client::runtime::Block> =
4090			Backend::new_test(0, 0);
4091		assert!(backend.get_aux(b"test").unwrap().is_none());
4092		backend.insert_aux(&[(&b"test"[..], &b"hello"[..])], &[]).unwrap();
4093		assert_eq!(b"hello", &backend.get_aux(b"test").unwrap().unwrap()[..]);
4094		backend.insert_aux(&[], &[&b"test"[..]]).unwrap();
4095		assert!(backend.get_aux(b"test").unwrap().is_none());
4096	}
4097
4098	#[test]
4099	fn test_finalize_block_with_justification() {
4100		use sc_client_api::blockchain::Backend as BlockChainBackend;
4101
4102		let backend = Backend::<Block>::new_test(10, 10);
4103
4104		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
4105		let block1 = insert_header(&backend, 1, block0, None, Default::default());
4106
4107		let justification = Some((CONS0_ENGINE_ID, vec![1, 2, 3]));
4108		backend.finalize_block(block1, justification.clone()).unwrap();
4109
4110		assert_eq!(
4111			backend.blockchain().justifications(block1).unwrap(),
4112			justification.map(Justifications::from),
4113		);
4114	}
4115
4116	#[test]
4117	fn test_append_justification_to_finalized_block() {
4118		use sc_client_api::blockchain::Backend as BlockChainBackend;
4119
4120		let backend = Backend::<Block>::new_test(10, 10);
4121
4122		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
4123		let block1 = insert_header(&backend, 1, block0, None, Default::default());
4124
4125		let just0 = (CONS0_ENGINE_ID, vec![1, 2, 3]);
4126		backend.finalize_block(block1, Some(just0.clone().into())).unwrap();
4127
4128		let just1 = (CONS1_ENGINE_ID, vec![4, 5]);
4129		backend.append_justification(block1, just1.clone()).unwrap();
4130
4131		let just2 = (CONS1_ENGINE_ID, vec![6, 7]);
4132		assert!(matches!(
4133			backend.append_justification(block1, just2),
4134			Err(ClientError::BadJustification(_))
4135		));
4136
4137		let justifications = {
4138			let mut just = Justifications::from(just0);
4139			just.append(just1);
4140			just
4141		};
4142		assert_eq!(backend.blockchain().justifications(block1).unwrap(), Some(justifications),);
4143	}
4144
4145	#[test]
4146	fn test_finalize_multiple_blocks_in_single_op() {
4147		let backend = Backend::<Block>::new_test(10, 10);
4148
4149		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
4150		let block1 = insert_header(&backend, 1, block0, None, Default::default());
4151		let block2 = insert_header(&backend, 2, block1, None, Default::default());
4152		let block3 = insert_header(&backend, 3, block2, None, Default::default());
4153		let block4 = insert_header(&backend, 4, block3, None, Default::default());
4154		{
4155			let mut op = backend.begin_operation().unwrap();
4156			backend.begin_state_operation(&mut op, block0).unwrap();
4157			op.mark_finalized(block1, None).unwrap();
4158			op.mark_finalized(block2, None).unwrap();
4159			backend.commit_operation(op).unwrap();
4160		}
4161		{
4162			let mut op = backend.begin_operation().unwrap();
4163			backend.begin_state_operation(&mut op, block2).unwrap();
4164			op.mark_finalized(block3, None).unwrap();
4165			op.mark_finalized(block4, None).unwrap();
4166			backend.commit_operation(op).unwrap();
4167		}
4168	}
4169
4170	#[test]
4171	fn storage_hash_is_cached_correctly() {
4172		let state_version = StateVersion::default();
4173		let backend = Backend::<Block>::new_test(10, 10);
4174
4175		let hash0 = {
4176			let mut op = backend.begin_operation().unwrap();
4177			backend.begin_state_operation(&mut op, Default::default()).unwrap();
4178			let mut header = Header {
4179				number: 0,
4180				parent_hash: Default::default(),
4181				state_root: Default::default(),
4182				digest: Default::default(),
4183				extrinsics_root: Default::default(),
4184			};
4185
4186			let storage = vec![(b"test".to_vec(), b"test".to_vec())];
4187
4188			header.state_root = op
4189				.old_state
4190				.storage_root(storage.iter().map(|(x, y)| (&x[..], Some(&y[..]))), state_version)
4191				.0
4192				.into();
4193			let hash = header.hash();
4194
4195			op.reset_storage(
4196				Storage {
4197					top: storage.into_iter().collect(),
4198					children_default: Default::default(),
4199				},
4200				state_version,
4201			)
4202			.unwrap();
4203			op.set_block_data(header.clone(), Some(vec![]), None, None, NewBlockState::Best, true)
4204				.unwrap();
4205
4206			backend.commit_operation(op).unwrap();
4207
4208			hash
4209		};
4210
4211		let block0_hash = backend
4212			.state_at(hash0, TrieCacheContext::Untrusted)
4213			.unwrap()
4214			.storage_hash(&b"test"[..])
4215			.unwrap();
4216
4217		let hash1 = {
4218			let mut op = backend.begin_operation().unwrap();
4219			backend.begin_state_operation(&mut op, hash0).unwrap();
4220			let mut header = Header {
4221				number: 1,
4222				parent_hash: hash0,
4223				state_root: Default::default(),
4224				digest: Default::default(),
4225				extrinsics_root: Default::default(),
4226			};
4227
4228			let storage = vec![(b"test".to_vec(), Some(b"test2".to_vec()))];
4229
4230			let (root, overlay) = op.old_state.storage_root(
4231				storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))),
4232				state_version,
4233			);
4234			op.update_db_storage(overlay).unwrap();
4235			header.state_root = root.into();
4236			let hash = header.hash();
4237
4238			op.update_storage(storage, Vec::new()).unwrap();
4239			op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Normal, true)
4240				.unwrap();
4241
4242			backend.commit_operation(op).unwrap();
4243
4244			hash
4245		};
4246
4247		{
4248			let header = backend.blockchain().header(hash1).unwrap().unwrap();
4249			let mut op = backend.begin_operation().unwrap();
4250			op.set_block_data(header, None, None, None, NewBlockState::Best, true).unwrap();
4251			backend.commit_operation(op).unwrap();
4252		}
4253
4254		let block1_hash = backend
4255			.state_at(hash1, TrieCacheContext::Untrusted)
4256			.unwrap()
4257			.storage_hash(&b"test"[..])
4258			.unwrap();
4259
4260		assert_ne!(block0_hash, block1_hash);
4261	}
4262
4263	#[test]
4264	fn test_finalize_non_sequential() {
4265		let backend = Backend::<Block>::new_test(10, 10);
4266
4267		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
4268		let block1 = insert_header(&backend, 1, block0, None, Default::default());
4269		let block2 = insert_header(&backend, 2, block1, None, Default::default());
4270		{
4271			let mut op = backend.begin_operation().unwrap();
4272			backend.begin_state_operation(&mut op, block0).unwrap();
4273			op.mark_finalized(block2, None).unwrap();
4274			backend.commit_operation(op).unwrap_err();
4275		}
4276	}
4277
4278	#[test]
4279	fn prune_blocks_on_finalize() {
4280		let pruning_modes =
4281			vec![BlocksPruning::Some(2), BlocksPruning::KeepFinalized, BlocksPruning::KeepAll];
4282
4283		for pruning_mode in pruning_modes {
4284			let backend = Backend::<Block>::new_test_with_tx_storage(pruning_mode, 0);
4285			let mut blocks = Vec::new();
4286			let mut prev_hash = Default::default();
4287			for i in 0..5 {
4288				let hash = insert_block(
4289					&backend,
4290					i,
4291					prev_hash,
4292					None,
4293					Default::default(),
4294					vec![UncheckedXt::new_transaction(i.into(), ())],
4295					None,
4296				)
4297				.unwrap();
4298				blocks.push(hash);
4299				prev_hash = hash;
4300			}
4301
4302			{
4303				let mut op = backend.begin_operation().unwrap();
4304				backend.begin_state_operation(&mut op, blocks[4]).unwrap();
4305				for i in 1..5 {
4306					op.mark_finalized(blocks[i], None).unwrap();
4307				}
4308				backend.commit_operation(op).unwrap();
4309			}
4310			let bc = backend.blockchain();
4311
4312			if matches!(pruning_mode, BlocksPruning::Some(_)) {
4313				assert_eq!(None, bc.body(blocks[0]).unwrap());
4314				assert_eq!(None, bc.body(blocks[1]).unwrap());
4315				assert_eq!(None, bc.body(blocks[2]).unwrap());
4316				assert_eq!(
4317					Some(vec![UncheckedXt::new_transaction(3.into(), ())]),
4318					bc.body(blocks[3]).unwrap()
4319				);
4320				assert_eq!(
4321					Some(vec![UncheckedXt::new_transaction(4.into(), ())]),
4322					bc.body(blocks[4]).unwrap()
4323				);
4324			} else {
4325				for i in 0..5 {
4326					assert_eq!(
4327						Some(vec![UncheckedXt::new_transaction((i as u64).into(), ())]),
4328						bc.body(blocks[i]).unwrap()
4329					);
4330				}
4331			}
4332		}
4333	}
4334
4335	#[test]
4336	fn prune_blocks_on_finalize_with_fork() {
4337		sp_tracing::try_init_simple();
4338
4339		let pruning_modes =
4340			vec![BlocksPruning::Some(2), BlocksPruning::KeepFinalized, BlocksPruning::KeepAll];
4341
4342		for pruning in pruning_modes {
4343			let backend = Backend::<Block>::new_test_with_tx_storage(pruning, 10);
4344			let mut blocks = Vec::new();
4345			let mut prev_hash = Default::default();
4346			for i in 0..5 {
4347				let hash = insert_block(
4348					&backend,
4349					i,
4350					prev_hash,
4351					None,
4352					Default::default(),
4353					vec![UncheckedXt::new_transaction(i.into(), ())],
4354					None,
4355				)
4356				.unwrap();
4357				blocks.push(hash);
4358				prev_hash = hash;
4359			}
4360
4361			// insert a fork at block 2
4362			let fork_hash_root = insert_block(
4363				&backend,
4364				2,
4365				blocks[1],
4366				None,
4367				H256::random(),
4368				vec![UncheckedXt::new_transaction(2.into(), ())],
4369				None,
4370			)
4371			.unwrap();
4372			insert_block(
4373				&backend,
4374				3,
4375				fork_hash_root,
4376				None,
4377				H256::random(),
4378				vec![
4379					UncheckedXt::new_transaction(3.into(), ()),
4380					UncheckedXt::new_transaction(11.into(), ()),
4381				],
4382				None,
4383			)
4384			.unwrap();
4385			let mut op = backend.begin_operation().unwrap();
4386			backend.begin_state_operation(&mut op, blocks[4]).unwrap();
4387			op.mark_head(blocks[4]).unwrap();
4388			backend.commit_operation(op).unwrap();
4389
4390			let bc = backend.blockchain();
4391			assert_eq!(
4392				Some(vec![UncheckedXt::new_transaction(2.into(), ())]),
4393				bc.body(fork_hash_root).unwrap()
4394			);
4395
4396			for i in 1..5 {
4397				let mut op = backend.begin_operation().unwrap();
4398				backend.begin_state_operation(&mut op, blocks[4]).unwrap();
4399				op.mark_finalized(blocks[i], None).unwrap();
4400				backend.commit_operation(op).unwrap();
4401			}
4402
4403			if matches!(pruning, BlocksPruning::Some(_)) {
4404				assert_eq!(None, bc.body(blocks[0]).unwrap());
4405				assert_eq!(None, bc.body(blocks[1]).unwrap());
4406				assert_eq!(None, bc.body(blocks[2]).unwrap());
4407
4408				assert_eq!(
4409					Some(vec![UncheckedXt::new_transaction(3.into(), ())]),
4410					bc.body(blocks[3]).unwrap()
4411				);
4412				assert_eq!(
4413					Some(vec![UncheckedXt::new_transaction(4.into(), ())]),
4414					bc.body(blocks[4]).unwrap()
4415				);
4416			} else {
4417				for i in 0..5 {
4418					assert_eq!(
4419						Some(vec![UncheckedXt::new_transaction((i as u64).into(), ())]),
4420						bc.body(blocks[i]).unwrap()
4421					);
4422				}
4423			}
4424
4425			if matches!(pruning, BlocksPruning::KeepAll) {
4426				assert_eq!(
4427					Some(vec![UncheckedXt::new_transaction(2.into(), ())]),
4428					bc.body(fork_hash_root).unwrap()
4429				);
4430			} else {
4431				assert_eq!(None, bc.body(fork_hash_root).unwrap());
4432			}
4433
4434			assert_eq!(bc.info().best_number, 4);
4435			for i in 0..5 {
4436				assert!(bc.hash(i).unwrap().is_some());
4437			}
4438		}
4439	}
4440
4441	#[test]
4442	fn prune_blocks_on_finalize_and_reorg() {
4443		// 	0 - 1b
4444		// 	\ - 1a - 2a - 3a
4445		// 	     \ - 2b
4446
4447		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(10), 10);
4448
4449		let make_block = |index, parent, val: u64| {
4450			insert_block(
4451				&backend,
4452				index,
4453				parent,
4454				None,
4455				H256::random(),
4456				vec![UncheckedXt::new_transaction(val.into(), ())],
4457				None,
4458			)
4459			.unwrap()
4460		};
4461
4462		let block_0 = make_block(0, Default::default(), 0x00);
4463		let block_1a = make_block(1, block_0, 0x1a);
4464		let block_1b = make_block(1, block_0, 0x1b);
4465		let block_2a = make_block(2, block_1a, 0x2a);
4466		let block_2b = make_block(2, block_1a, 0x2b);
4467		let block_3a = make_block(3, block_2a, 0x3a);
4468
4469		// Make sure 1b is head
4470		let mut op = backend.begin_operation().unwrap();
4471		backend.begin_state_operation(&mut op, block_0).unwrap();
4472		op.mark_head(block_1b).unwrap();
4473		backend.commit_operation(op).unwrap();
4474
4475		// Finalize 3a
4476		let mut op = backend.begin_operation().unwrap();
4477		backend.begin_state_operation(&mut op, block_0).unwrap();
4478		op.mark_head(block_3a).unwrap();
4479		op.mark_finalized(block_1a, None).unwrap();
4480		op.mark_finalized(block_2a, None).unwrap();
4481		op.mark_finalized(block_3a, None).unwrap();
4482		backend.commit_operation(op).unwrap();
4483
4484		let bc = backend.blockchain();
4485		assert_eq!(None, bc.body(block_1b).unwrap());
4486		assert_eq!(None, bc.body(block_2b).unwrap());
4487		assert_eq!(
4488			Some(vec![UncheckedXt::new_transaction(0x00.into(), ())]),
4489			bc.body(block_0).unwrap()
4490		);
4491		assert_eq!(
4492			Some(vec![UncheckedXt::new_transaction(0x1a.into(), ())]),
4493			bc.body(block_1a).unwrap()
4494		);
4495		assert_eq!(
4496			Some(vec![UncheckedXt::new_transaction(0x2a.into(), ())]),
4497			bc.body(block_2a).unwrap()
4498		);
4499		assert_eq!(
4500			Some(vec![UncheckedXt::new_transaction(0x3a.into(), ())]),
4501			bc.body(block_3a).unwrap()
4502		);
4503	}
4504
4505	#[test]
4506	fn indexed_data_block_body() {
4507		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(1), 10);
4508
4509		let x0 = UncheckedXt::new_transaction(0.into(), ()).encode();
4510		let x1 = UncheckedXt::new_transaction(1.into(), ()).encode();
4511		let x0_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x0[1..]);
4512		let x1_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x1[1..]);
4513		let index = vec![
4514			IndexOperation::Insert {
4515				extrinsic: 0,
4516				hash: x0_hash.as_ref().to_vec(),
4517				size: (x0.len() - 1) as u32,
4518			},
4519			IndexOperation::Insert {
4520				extrinsic: 1,
4521				hash: x1_hash.as_ref().to_vec(),
4522				size: (x1.len() - 1) as u32,
4523			},
4524		];
4525		let hash = insert_block(
4526			&backend,
4527			0,
4528			Default::default(),
4529			None,
4530			Default::default(),
4531			vec![
4532				UncheckedXt::new_transaction(0.into(), ()),
4533				UncheckedXt::new_transaction(1.into(), ()),
4534			],
4535			Some(index),
4536		)
4537		.unwrap();
4538		let bc = backend.blockchain();
4539		assert_eq!(bc.indexed_transaction(x0_hash).unwrap().unwrap(), &x0[1..]);
4540		assert_eq!(bc.indexed_transaction(x1_hash).unwrap().unwrap(), &x1[1..]);
4541
4542		let hashof0 = bc.info().genesis_hash;
4543		// Push one more blocks and make sure block is pruned and transaction index is cleared.
4544		let block1 =
4545			insert_block(&backend, 1, hash, None, Default::default(), vec![], None).unwrap();
4546		backend.finalize_block(block1, None).unwrap();
4547		assert_eq!(bc.body(hashof0).unwrap(), None);
4548		assert_eq!(bc.indexed_transaction(x0_hash).unwrap(), None);
4549		assert_eq!(bc.indexed_transaction(x1_hash).unwrap(), None);
4550	}
4551
4552	#[test]
4553	fn index_invalid_size() {
4554		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(1), 10);
4555
4556		let x0 = UncheckedXt::new_transaction(0.into(), ()).encode();
4557		let x1 = UncheckedXt::new_transaction(1.into(), ()).encode();
4558
4559		let x0_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x0[..]);
4560		let x1_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x1[..]);
4561		let index = vec![
4562			IndexOperation::Insert {
4563				extrinsic: 0,
4564				hash: x0_hash.as_ref().to_vec(),
4565				size: (x0.len()) as u32,
4566			},
4567			IndexOperation::Insert {
4568				extrinsic: 1,
4569				hash: x1_hash.as_ref().to_vec(),
4570				size: (x1.len() + 1) as u32,
4571			},
4572		];
4573		insert_block(
4574			&backend,
4575			0,
4576			Default::default(),
4577			None,
4578			Default::default(),
4579			vec![
4580				UncheckedXt::new_transaction(0.into(), ()),
4581				UncheckedXt::new_transaction(1.into(), ()),
4582			],
4583			Some(index),
4584		)
4585		.unwrap();
4586		let bc = backend.blockchain();
4587		assert_eq!(bc.indexed_transaction(x0_hash).unwrap().unwrap(), &x0[..]);
4588		assert_eq!(bc.indexed_transaction(x1_hash).unwrap(), None);
4589	}
4590
4591	#[test]
4592	fn renew_transaction_storage() {
4593		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(2), 10);
4594		let mut blocks = Vec::new();
4595		let mut prev_hash = Default::default();
4596		let x1 = UncheckedXt::new_transaction(0.into(), ()).encode();
4597		let x1_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x1[1..]);
4598		for i in 0..10 {
4599			let mut index = Vec::new();
4600			if i == 0 {
4601				index.push(IndexOperation::Insert {
4602					extrinsic: 0,
4603					hash: x1_hash.as_ref().to_vec(),
4604					size: (x1.len() - 1) as u32,
4605				});
4606			} else if i < 5 {
4607				// keep renewing 1st
4608				index.push(IndexOperation::Renew { extrinsic: 0, hash: x1_hash.as_ref().to_vec() });
4609			} // else stop renewing
4610			let hash = insert_block(
4611				&backend,
4612				i,
4613				prev_hash,
4614				None,
4615				Default::default(),
4616				vec![UncheckedXt::new_transaction(i.into(), ())],
4617				Some(index),
4618			)
4619			.unwrap();
4620			blocks.push(hash);
4621			prev_hash = hash;
4622		}
4623
4624		for i in 1..10 {
4625			let mut op = backend.begin_operation().unwrap();
4626			backend.begin_state_operation(&mut op, blocks[4]).unwrap();
4627			op.mark_finalized(blocks[i], None).unwrap();
4628			backend.commit_operation(op).unwrap();
4629			let bc = backend.blockchain();
4630			if i < 6 {
4631				assert!(bc.indexed_transaction(x1_hash).unwrap().is_some());
4632			} else {
4633				assert!(bc.indexed_transaction(x1_hash).unwrap().is_none());
4634			}
4635		}
4636	}
4637
4638	#[test]
4639	fn multi_renew_transaction_storage() {
4640		// Test that multiple renewals within a single extrinsic work correctly
4641		// and that data survives across the renewal window.
4642		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(2), 10);
4643		let mut blocks = Vec::new();
4644		let mut prev_hash = Default::default();
4645
4646		// Two distinct data items
4647		let x1 = UncheckedXt::new_transaction(0.into(), ()).encode();
4648		let x2 = UncheckedXt::new_transaction(1.into(), ()).encode();
4649		let x1_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x1[1..]);
4650		let x2_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x2[1..]);
4651
4652		for i in 0..10 {
4653			let mut index = Vec::new();
4654			if i == 0 {
4655				// Block 0: Insert both items as separate extrinsics
4656				index.push(IndexOperation::Insert {
4657					extrinsic: 0,
4658					hash: x1_hash.as_ref().to_vec(),
4659					size: (x1.len() - 1) as u32,
4660				});
4661				index.push(IndexOperation::Insert {
4662					extrinsic: 1,
4663					hash: x2_hash.as_ref().to_vec(),
4664					size: (x2.len() - 1) as u32,
4665				});
4666			} else if i < 5 {
4667				// Blocks 1-4: Renew BOTH items in a single extrinsic (multi-renew)
4668				index.push(IndexOperation::Renew { extrinsic: 0, hash: x1_hash.as_ref().to_vec() });
4669				index.push(IndexOperation::Renew { extrinsic: 0, hash: x2_hash.as_ref().to_vec() });
4670			}
4671			// Blocks 5+: stop renewing
4672
4673			let body = if i == 0 {
4674				vec![
4675					UncheckedXt::new_transaction(0.into(), ()),
4676					UncheckedXt::new_transaction(1.into(), ()),
4677				]
4678			} else {
4679				vec![UncheckedXt::new_transaction(i.into(), ())]
4680			};
4681			let hash =
4682				insert_block(&backend, i, prev_hash, None, Default::default(), body, Some(index))
4683					.unwrap();
4684			blocks.push(hash);
4685			prev_hash = hash;
4686		}
4687
4688		// Finalize progressively and check that both items survive while renewed
4689		for i in 1..10 {
4690			let mut op = backend.begin_operation().unwrap();
4691			backend.begin_state_operation(&mut op, blocks[4]).unwrap();
4692			op.mark_finalized(blocks[i], None).unwrap();
4693			backend.commit_operation(op).unwrap();
4694			let bc = backend.blockchain();
4695			if i < 6 {
4696				assert!(
4697					bc.indexed_transaction(x1_hash).unwrap().is_some(),
4698					"x1 should exist at finalization step {i}"
4699				);
4700				assert!(
4701					bc.indexed_transaction(x2_hash).unwrap().is_some(),
4702					"x2 should exist at finalization step {i}"
4703				);
4704			} else {
4705				assert!(
4706					bc.indexed_transaction(x1_hash).unwrap().is_none(),
4707					"x1 should be pruned at finalization step {i}"
4708				);
4709				assert!(
4710					bc.indexed_transaction(x2_hash).unwrap().is_none(),
4711					"x2 should be pruned at finalization step {i}"
4712				);
4713			}
4714		}
4715	}
4716
4717	#[test]
4718	fn multi_renew_block_indexed_body() {
4719		// Test that block_indexed_body returns data for all hashes in a MultiRenew extrinsic.
4720		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(10), 10);
4721
4722		let x1 = UncheckedXt::new_transaction(0.into(), ()).encode();
4723		let x2 = UncheckedXt::new_transaction(1.into(), ()).encode();
4724		let x1_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x1[1..]);
4725		let x2_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x2[1..]);
4726
4727		// Block 0: Insert both items
4728		let block0 = insert_block(
4729			&backend,
4730			0,
4731			Default::default(),
4732			None,
4733			Default::default(),
4734			vec![
4735				UncheckedXt::new_transaction(0.into(), ()),
4736				UncheckedXt::new_transaction(1.into(), ()),
4737			],
4738			Some(vec![
4739				IndexOperation::Insert {
4740					extrinsic: 0,
4741					hash: x1_hash.as_ref().to_vec(),
4742					size: (x1.len() - 1) as u32,
4743				},
4744				IndexOperation::Insert {
4745					extrinsic: 1,
4746					hash: x2_hash.as_ref().to_vec(),
4747					size: (x2.len() - 1) as u32,
4748				},
4749			]),
4750		)
4751		.unwrap();
4752
4753		// Block 1: Multi-renew both in a single extrinsic
4754		let block1 = insert_block(
4755			&backend,
4756			1,
4757			block0,
4758			None,
4759			Default::default(),
4760			vec![UncheckedXt::new_transaction(10.into(), ())],
4761			Some(vec![
4762				IndexOperation::Renew { extrinsic: 0, hash: x1_hash.as_ref().to_vec() },
4763				IndexOperation::Renew { extrinsic: 0, hash: x2_hash.as_ref().to_vec() },
4764			]),
4765		)
4766		.unwrap();
4767
4768		let bc = backend.blockchain();
4769		let indexed_body = bc.block_indexed_body(block1).unwrap().unwrap();
4770		assert_eq!(indexed_body.len(), 2, "Should have 2 indexed data blobs");
4771		assert_eq!(&indexed_body[0][..], &x1[1..]);
4772		assert_eq!(&indexed_body[1][..], &x2[1..]);
4773	}
4774
4775	#[test]
4776	fn multi_renew_prune_releases_all() {
4777		// Test that pruning a block with MultiRenew correctly releases all ref counts.
4778		// Use BlocksPruning::Some(2) and build enough blocks so both the insert block
4779		// and the multi-renew block get pruned.
4780		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(2), 10);
4781		let mut blocks = Vec::new();
4782		let mut prev_hash = Default::default();
4783
4784		let x1 = UncheckedXt::new_transaction(0.into(), ()).encode();
4785		let x2 = UncheckedXt::new_transaction(1.into(), ()).encode();
4786		let x1_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x1[1..]);
4787		let x2_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x2[1..]);
4788
4789		for i in 0..6 {
4790			let mut index = Vec::new();
4791			let body = if i == 0 {
4792				// Block 0: Insert both items
4793				index.push(IndexOperation::Insert {
4794					extrinsic: 0,
4795					hash: x1_hash.as_ref().to_vec(),
4796					size: (x1.len() - 1) as u32,
4797				});
4798				index.push(IndexOperation::Insert {
4799					extrinsic: 1,
4800					hash: x2_hash.as_ref().to_vec(),
4801					size: (x2.len() - 1) as u32,
4802				});
4803				vec![
4804					UncheckedXt::new_transaction(0.into(), ()),
4805					UncheckedXt::new_transaction(1.into(), ()),
4806				]
4807			} else if i == 1 {
4808				// Block 1: Multi-renew both in one extrinsic
4809				index.push(IndexOperation::Renew { extrinsic: 0, hash: x1_hash.as_ref().to_vec() });
4810				index.push(IndexOperation::Renew { extrinsic: 0, hash: x2_hash.as_ref().to_vec() });
4811				vec![UncheckedXt::new_transaction(10.into(), ())]
4812			} else {
4813				// Blocks 2+: empty, just advancing
4814				vec![UncheckedXt::new_transaction(i.into(), ())]
4815			};
4816			let hash =
4817				insert_block(&backend, i, prev_hash, None, Default::default(), body, Some(index))
4818					.unwrap();
4819			blocks.push(hash);
4820			prev_hash = hash;
4821		}
4822
4823		let bc = backend.blockchain();
4824		// Before finalization, data exists
4825		assert!(bc.indexed_transaction(x1_hash).unwrap().is_some());
4826		assert!(bc.indexed_transaction(x2_hash).unwrap().is_some());
4827
4828		// Finalize progressively
4829		for i in 1..6 {
4830			let mut op = backend.begin_operation().unwrap();
4831			backend.begin_state_operation(&mut op, blocks[4]).unwrap();
4832			op.mark_finalized(blocks[i], None).unwrap();
4833			backend.commit_operation(op).unwrap();
4834		}
4835
4836		// After finalizing block 5 with pruning=2, blocks 0-3 are pruned.
4837		// Both insert (block 0) and multi-renew (block 1) refs are released.
4838		assert!(
4839			bc.indexed_transaction(x1_hash).unwrap().is_none(),
4840			"x1 should be gone after all referring blocks are pruned"
4841		);
4842		assert!(
4843			bc.indexed_transaction(x2_hash).unwrap().is_none(),
4844			"x2 should be gone after all referring blocks are pruned"
4845		);
4846	}
4847
4848	#[test]
4849	fn multi_renew_body_reconstruction() {
4850		// Test that body_uncached can reconstruct extrinsics from MultiRenew blocks.
4851		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(10), 10);
4852
4853		let x1 = UncheckedXt::new_transaction(0.into(), ()).encode();
4854		let x2 = UncheckedXt::new_transaction(1.into(), ()).encode();
4855		let x1_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x1[1..]);
4856		let x2_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x2[1..]);
4857
4858		// Block 0: Insert both
4859		let block0 = insert_block(
4860			&backend,
4861			0,
4862			Default::default(),
4863			None,
4864			Default::default(),
4865			vec![
4866				UncheckedXt::new_transaction(0.into(), ()),
4867				UncheckedXt::new_transaction(1.into(), ()),
4868			],
4869			Some(vec![
4870				IndexOperation::Insert {
4871					extrinsic: 0,
4872					hash: x1_hash.as_ref().to_vec(),
4873					size: (x1.len() - 1) as u32,
4874				},
4875				IndexOperation::Insert {
4876					extrinsic: 1,
4877					hash: x2_hash.as_ref().to_vec(),
4878					size: (x2.len() - 1) as u32,
4879				},
4880			]),
4881		)
4882		.unwrap();
4883
4884		// Block 1: Multi-renew both in one extrinsic
4885		let renew_xt = UncheckedXt::new_transaction(10.into(), ());
4886		let block1 = insert_block(
4887			&backend,
4888			1,
4889			block0,
4890			None,
4891			Default::default(),
4892			vec![renew_xt.clone()],
4893			Some(vec![
4894				IndexOperation::Renew { extrinsic: 0, hash: x1_hash.as_ref().to_vec() },
4895				IndexOperation::Renew { extrinsic: 0, hash: x2_hash.as_ref().to_vec() },
4896			]),
4897		)
4898		.unwrap();
4899
4900		// Reconstruct body from block 1
4901		let bc = backend.blockchain();
4902		let body = bc.body(block1).unwrap().unwrap();
4903		assert_eq!(body.len(), 1, "Block 1 has one extrinsic");
4904		assert_eq!(body[0], renew_xt, "Extrinsic should be reconstructed correctly");
4905	}
4906
4907	#[test]
4908	fn single_renew_backwards_compatible() {
4909		// Verify that a single renewal per extrinsic still uses DbExtrinsic::Indexed,
4910		// preserving backwards compatibility.
4911		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(2), 10);
4912		let mut prev_hash = Default::default();
4913
4914		let x1 = UncheckedXt::new_transaction(0.into(), ()).encode();
4915		let x1_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x1[1..]);
4916
4917		// Block 0: Insert
4918		let block0 = insert_block(
4919			&backend,
4920			0,
4921			prev_hash,
4922			None,
4923			Default::default(),
4924			vec![UncheckedXt::new_transaction(0.into(), ())],
4925			Some(vec![IndexOperation::Insert {
4926				extrinsic: 0,
4927				hash: x1_hash.as_ref().to_vec(),
4928				size: (x1.len() - 1) as u32,
4929			}]),
4930		)
4931		.unwrap();
4932		prev_hash = block0;
4933
4934		// Block 1: Single renew (should produce Indexed, not MultiRenew)
4935		let block1 = insert_block(
4936			&backend,
4937			1,
4938			prev_hash,
4939			None,
4940			Default::default(),
4941			vec![UncheckedXt::new_transaction(1.into(), ())],
4942			Some(vec![IndexOperation::Renew { extrinsic: 0, hash: x1_hash.as_ref().to_vec() }]),
4943		)
4944		.unwrap();
4945
4946		// Verify data is accessible
4947		let bc = backend.blockchain();
4948		assert!(bc.indexed_transaction(x1_hash).unwrap().is_some());
4949
4950		// Verify body can be reconstructed (confirms Indexed variant works)
4951		let body = bc.body(block1).unwrap().unwrap();
4952		assert_eq!(body.len(), 1);
4953		assert_eq!(body[0], UncheckedXt::new_transaction(1.into(), ()));
4954
4955		// Verify block_indexed_body returns the data
4956		let indexed = bc.block_indexed_body(block1).unwrap().unwrap();
4957		assert_eq!(indexed.len(), 1);
4958		assert_eq!(&indexed[0][..], &x1[1..]);
4959	}
4960
4961	#[test]
4962	fn multi_renew_duplicate_hash_balanced_lifecycle() {
4963		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(2), 10);
4964		let mut blocks = Vec::new();
4965		let mut prev_hash = Default::default();
4966
4967		let x1 = UncheckedXt::new_transaction(0.into(), ()).encode();
4968		let x1_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x1[1..]);
4969
4970		for i in 0..6 {
4971			let mut index = Vec::new();
4972			let body = if i == 0 {
4973				index.push(IndexOperation::Insert {
4974					extrinsic: 0,
4975					hash: x1_hash.as_ref().to_vec(),
4976					size: (x1.len() - 1) as u32,
4977				});
4978				vec![UncheckedXt::new_transaction(0.into(), ())]
4979			} else if i == 1 {
4980				index.push(IndexOperation::Renew { extrinsic: 0, hash: x1_hash.as_ref().to_vec() });
4981				index.push(IndexOperation::Renew { extrinsic: 0, hash: x1_hash.as_ref().to_vec() });
4982				vec![UncheckedXt::new_transaction(10.into(), ())]
4983			} else {
4984				vec![UncheckedXt::new_transaction(i.into(), ())]
4985			};
4986			let hash =
4987				insert_block(&backend, i, prev_hash, None, Default::default(), body, Some(index))
4988					.unwrap();
4989			blocks.push(hash);
4990			prev_hash = hash;
4991		}
4992
4993		let bc = backend.blockchain();
4994		assert!(bc.indexed_transaction(x1_hash).unwrap().is_some());
4995
4996		for i in 1..6 {
4997			let mut op = backend.begin_operation().unwrap();
4998			backend.begin_state_operation(&mut op, blocks[4]).unwrap();
4999			op.mark_finalized(blocks[i], None).unwrap();
5000			backend.commit_operation(op).unwrap();
5001		}
5002
5003		assert!(bc.indexed_transaction(x1_hash).unwrap().is_none());
5004	}
5005
5006	#[test]
5007	fn multi_renew_mixed_duplicates_and_uniques() {
5008		// Ops [W, X, Y, W, Z]: insertion-order preserved, duplicate W kept.
5009		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(2), 10);
5010		let mut blocks = Vec::new();
5011		let mut prev_hash = Default::default();
5012
5013		let w = UncheckedXt::new_transaction(0.into(), ()).encode();
5014		let x = UncheckedXt::new_transaction(1.into(), ()).encode();
5015		let y = UncheckedXt::new_transaction(2.into(), ()).encode();
5016		let z = UncheckedXt::new_transaction(3.into(), ()).encode();
5017		let w_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&w[1..]);
5018		let x_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x[1..]);
5019		let y_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&y[1..]);
5020		let z_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&z[1..]);
5021
5022		for i in 0..6 {
5023			let mut index = Vec::new();
5024			let body = if i == 0 {
5025				index.push(IndexOperation::Insert {
5026					extrinsic: 0,
5027					hash: w_hash.as_ref().to_vec(),
5028					size: (w.len() - 1) as u32,
5029				});
5030				index.push(IndexOperation::Insert {
5031					extrinsic: 1,
5032					hash: x_hash.as_ref().to_vec(),
5033					size: (x.len() - 1) as u32,
5034				});
5035				index.push(IndexOperation::Insert {
5036					extrinsic: 2,
5037					hash: y_hash.as_ref().to_vec(),
5038					size: (y.len() - 1) as u32,
5039				});
5040				index.push(IndexOperation::Insert {
5041					extrinsic: 3,
5042					hash: z_hash.as_ref().to_vec(),
5043					size: (z.len() - 1) as u32,
5044				});
5045				vec![
5046					UncheckedXt::new_transaction(0.into(), ()),
5047					UncheckedXt::new_transaction(1.into(), ()),
5048					UncheckedXt::new_transaction(2.into(), ()),
5049					UncheckedXt::new_transaction(3.into(), ()),
5050				]
5051			} else if i == 1 {
5052				// 5 ops: W appears twice (positions 0 and 3), X/Y/Z once each.
5053				index.push(IndexOperation::Renew { extrinsic: 0, hash: w_hash.as_ref().to_vec() });
5054				index.push(IndexOperation::Renew { extrinsic: 0, hash: x_hash.as_ref().to_vec() });
5055				index.push(IndexOperation::Renew { extrinsic: 0, hash: y_hash.as_ref().to_vec() });
5056				index.push(IndexOperation::Renew { extrinsic: 0, hash: w_hash.as_ref().to_vec() });
5057				index.push(IndexOperation::Renew { extrinsic: 0, hash: z_hash.as_ref().to_vec() });
5058				vec![UncheckedXt::new_transaction(10.into(), ())]
5059			} else {
5060				vec![UncheckedXt::new_transaction(i.into(), ())]
5061			};
5062			let hash =
5063				insert_block(&backend, i, prev_hash, None, Default::default(), body, Some(index))
5064					.unwrap();
5065			blocks.push(hash);
5066			prev_hash = hash;
5067		}
5068
5069		let bc = backend.blockchain();
5070
5071		let indexed_body = bc.block_indexed_body(blocks[1]).unwrap().unwrap();
5072		assert_eq!(indexed_body.len(), 5);
5073		assert_eq!(&indexed_body[0][..], &w[1..]);
5074		assert_eq!(&indexed_body[1][..], &x[1..]);
5075		assert_eq!(&indexed_body[2][..], &y[1..]);
5076		assert_eq!(&indexed_body[3][..], &w[1..]);
5077		assert_eq!(&indexed_body[4][..], &z[1..]);
5078
5079		for i in 1..6 {
5080			let mut op = backend.begin_operation().unwrap();
5081			backend.begin_state_operation(&mut op, blocks[4]).unwrap();
5082			op.mark_finalized(blocks[i], None).unwrap();
5083			backend.commit_operation(op).unwrap();
5084		}
5085
5086		assert!(bc.indexed_transaction(w_hash).unwrap().is_none(), "W deleted");
5087		assert!(bc.indexed_transaction(x_hash).unwrap().is_none(), "X deleted");
5088		assert!(bc.indexed_transaction(y_hash).unwrap().is_none(), "Y deleted");
5089		assert!(bc.indexed_transaction(z_hash).unwrap().is_none(), "Z deleted");
5090	}
5091
5092	#[test]
5093	fn block_indexed_body_preserves_renew_op_submission_order() {
5094		// `block_indexed_body(N)` returns blobs in submission order of the underlying
5095		// Renew ops. Sorting (e.g. via BTreeSet) would desync off-chain proof
5096		// construction from on-chain verification.
5097		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::KeepAll, 10);
5098
5099		let payloads: Vec<Vec<u8>> = (0..5)
5100			.map(|i: u64| UncheckedXt::new_transaction(i.into(), ()).encode())
5101			.collect();
5102		let hashes: Vec<<HashingFor<Block> as sp_core::Hasher>::Out> = payloads
5103			.iter()
5104			.map(|p| <HashingFor<Block> as sp_core::Hasher>::hash(&p[1..]))
5105			.collect();
5106
5107		let mut prev_hash = Default::default();
5108		let insert_ops: Vec<IndexOperation> = (0..5)
5109			.map(|i| IndexOperation::Insert {
5110				extrinsic: i as u32,
5111				hash: hashes[i].as_ref().to_vec(),
5112				size: (payloads[i].len() - 1) as u32,
5113			})
5114			.collect();
5115		let body0: Vec<UncheckedXt> =
5116			(0..5).map(|i| UncheckedXt::new_transaction((i as u64).into(), ())).collect();
5117		prev_hash =
5118			insert_block(&backend, 0, prev_hash, None, Default::default(), body0, Some(insert_ops))
5119				.unwrap();
5120
5121		// Non-monotonic submission order so any sort would visibly disturb it.
5122		let submission_order = [4usize, 1, 0, 3, 2];
5123		let renew_ops: Vec<IndexOperation> = submission_order
5124			.iter()
5125			.map(|&i| IndexOperation::Renew { extrinsic: 0, hash: hashes[i].as_ref().to_vec() })
5126			.collect();
5127		let block1 = insert_block(
5128			&backend,
5129			1,
5130			prev_hash,
5131			None,
5132			Default::default(),
5133			vec![UncheckedXt::new_transaction(100.into(), ())],
5134			Some(renew_ops),
5135		)
5136		.unwrap();
5137
5138		let bc = backend.blockchain();
5139		let body_index_bytes = read_db(
5140			&*backend.storage.db,
5141			columns::KEY_LOOKUP,
5142			columns::BODY_INDEX,
5143			BlockId::<Block>::Hash(block1),
5144		)
5145		.unwrap()
5146		.expect("block 1 must have a BODY_INDEX entry");
5147		let decoded: Vec<DbExtrinsic<Block>> =
5148			Decode::decode(&mut &body_index_bytes[..]).expect("must decode");
5149		assert_eq!(decoded.len(), 1);
5150		match &decoded[0] {
5151			DbExtrinsic::MultiRenew { hashes: stored_hashes, .. } => {
5152				assert_eq!(stored_hashes.len(), 5);
5153				for (i, &order_idx) in submission_order.iter().enumerate() {
5154					assert_eq!(stored_hashes[i].as_ref(), hashes[order_idx].as_ref());
5155				}
5156			},
5157			other => panic!("expected MultiRenew; got {other:?}"),
5158		}
5159
5160		let blobs = bc.block_indexed_body(block1).unwrap().unwrap();
5161		assert_eq!(blobs.len(), 5);
5162		for (i, &order_idx) in submission_order.iter().enumerate() {
5163			assert_eq!(blobs[i].as_slice(), &payloads[order_idx][1..]);
5164		}
5165	}
5166
5167	#[test]
5168	fn insert_and_renew_same_index_renew_wins() {
5169		// Documents the pre-existing precedence in apply_index_ops: when both an Insert
5170		// and a Renew op target the same extrinsic_index, the Renew wins and the Insert
5171		// is silently discarded — the Insert's data write to the TRANSACTION column
5172		// never happens.
5173		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(10), 10);
5174
5175		let x = UncheckedXt::new_transaction(0.into(), ()).encode();
5176		let y = UncheckedXt::new_transaction(1.into(), ()).encode();
5177		let x_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x[1..]);
5178		let y_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&y[1..]);
5179
5180		// Block 0: Insert X normally — X is now stored.
5181		let block0 = insert_block(
5182			&backend,
5183			0,
5184			Default::default(),
5185			None,
5186			Default::default(),
5187			vec![UncheckedXt::new_transaction(0.into(), ())],
5188			Some(vec![IndexOperation::Insert {
5189				extrinsic: 0,
5190				hash: x_hash.as_ref().to_vec(),
5191				size: (x.len() - 1) as u32,
5192			}]),
5193		)
5194		.unwrap();
5195
5196		// Block 1: ops contain BOTH Insert{0, Y, ...} and Renew{0, X} for the same extrinsic.
5197		// Per apply_index_ops precedence, Renew wins and Insert{Y} is silently dropped.
5198		let block1 = insert_block(
5199			&backend,
5200			1,
5201			block0,
5202			None,
5203			Default::default(),
5204			vec![UncheckedXt::new_transaction(99.into(), ())],
5205			Some(vec![
5206				IndexOperation::Insert {
5207					extrinsic: 0,
5208					hash: y_hash.as_ref().to_vec(),
5209					size: (y.len() - 1) as u32,
5210				},
5211				IndexOperation::Renew { extrinsic: 0, hash: x_hash.as_ref().to_vec() },
5212			]),
5213		)
5214		.unwrap();
5215
5216		let bc = backend.blockchain();
5217
5218		assert!(bc.indexed_transaction(x_hash).unwrap().is_some());
5219		assert!(
5220			bc.indexed_transaction(y_hash).unwrap().is_none(),
5221			"Insert at the same extrinsic index as a Renew is silently dropped",
5222		);
5223
5224		let indexed = bc.block_indexed_body(block1).unwrap().unwrap();
5225		assert_eq!(indexed.len(), 1);
5226		assert_eq!(&indexed[0][..], &x[1..]);
5227	}
5228
5229	#[test]
5230	fn db_extrinsic_encoding_round_trip() {
5231		let entries: Vec<DbExtrinsic<Block>> = vec![
5232			DbExtrinsic::Indexed { hash: H256::repeat_byte(0xAA), header: vec![0x01, 0x02, 0x03] },
5233			DbExtrinsic::Full(UncheckedXt::new_transaction(42.into(), ())),
5234			DbExtrinsic::MultiRenew {
5235				hashes: vec![H256::repeat_byte(0xBB), H256::repeat_byte(0xCC)],
5236				extrinsic: vec![0x04, 0x05, 0x06, 0x07],
5237			},
5238		];
5239
5240		let encoded = entries.encode();
5241		let decoded: Vec<DbExtrinsic<Block>> =
5242			Decode::decode(&mut &encoded[..]).expect("encoded DbExtrinsic vec must decode");
5243		assert_eq!(encoded, decoded.encode());
5244	}
5245
5246	#[test]
5247	fn apply_index_ops_deterministic() {
5248		let body = vec![
5249			UncheckedXt::new_transaction(0.into(), ()),
5250			UncheckedXt::new_transaction(1.into(), ()),
5251		];
5252		let h1 = H256::repeat_byte(0x11).as_ref().to_vec();
5253		let h2 = H256::repeat_byte(0x22).as_ref().to_vec();
5254		let h3 = H256::repeat_byte(0x33).as_ref().to_vec();
5255
5256		let ops = vec![
5257			IndexOperation::Renew { extrinsic: 0, hash: h1.clone() },
5258			IndexOperation::Renew { extrinsic: 0, hash: h2.clone() },
5259			IndexOperation::Renew { extrinsic: 0, hash: h1.clone() },
5260			IndexOperation::Renew { extrinsic: 1, hash: h3.clone() },
5261		];
5262
5263		let mut tx1: Transaction<DbHash> = Transaction::new();
5264		let bytes1 = apply_index_ops::<Block>(&mut tx1, body.clone(), ops.clone(), HashMap::new());
5265
5266		let mut tx2: Transaction<DbHash> = Transaction::new();
5267		let bytes2 = apply_index_ops::<Block>(&mut tx2, body, ops, HashMap::new());
5268
5269		assert_eq!(bytes1, bytes2);
5270
5271		let decoded: Vec<DbExtrinsic<Block>> =
5272			Decode::decode(&mut &bytes1[..]).expect("apply_index_ops output must decode");
5273		assert_eq!(decoded.len(), 2);
5274		match &decoded[0] {
5275			DbExtrinsic::MultiRenew { hashes, .. } => {
5276				assert_eq!(hashes.len(), 3);
5277				assert_eq!(hashes[0].as_ref(), h1.as_slice());
5278				assert_eq!(hashes[1].as_ref(), h2.as_slice());
5279				assert_eq!(hashes[2].as_ref(), h1.as_slice());
5280			},
5281			other => panic!("expected MultiRenew, got {other:?}"),
5282		}
5283		assert!(matches!(decoded[1], DbExtrinsic::Indexed { .. }));
5284	}
5285
5286	#[test]
5287	fn multi_renew_in_one_block_indexed_in_another() {
5288		// X across three blocks: Insert, single Renew, duplicate Renew. Refcount peaks at 4.
5289		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(2), 10);
5290		let mut blocks = Vec::new();
5291		let mut prev_hash = Default::default();
5292
5293		let x = UncheckedXt::new_transaction(0.into(), ()).encode();
5294		let x_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x[1..]);
5295
5296		for i in 0..6 {
5297			let mut index = Vec::new();
5298			let body = if i == 0 {
5299				index.push(IndexOperation::Insert {
5300					extrinsic: 0,
5301					hash: x_hash.as_ref().to_vec(),
5302					size: (x.len() - 1) as u32,
5303				});
5304				vec![UncheckedXt::new_transaction(0.into(), ())]
5305			} else if i == 1 {
5306				index.push(IndexOperation::Renew { extrinsic: 0, hash: x_hash.as_ref().to_vec() });
5307				vec![UncheckedXt::new_transaction(10.into(), ())]
5308			} else if i == 2 {
5309				index.push(IndexOperation::Renew { extrinsic: 0, hash: x_hash.as_ref().to_vec() });
5310				index.push(IndexOperation::Renew { extrinsic: 0, hash: x_hash.as_ref().to_vec() });
5311				vec![UncheckedXt::new_transaction(20.into(), ())]
5312			} else {
5313				vec![UncheckedXt::new_transaction(i.into(), ())]
5314			};
5315			let hash =
5316				insert_block(&backend, i, prev_hash, None, Default::default(), body, Some(index))
5317					.unwrap();
5318			blocks.push(hash);
5319			prev_hash = hash;
5320		}
5321
5322		let bc = backend.blockchain();
5323		assert!(bc.indexed_transaction(x_hash).unwrap().is_some());
5324
5325		for i in 1..6 {
5326			let mut op = backend.begin_operation().unwrap();
5327			backend.begin_state_operation(&mut op, blocks[4]).unwrap();
5328			op.mark_finalized(blocks[i], None).unwrap();
5329			backend.commit_operation(op).unwrap();
5330		}
5331
5332		assert!(bc.indexed_transaction(x_hash).unwrap().is_none());
5333	}
5334
5335	#[test]
5336	fn remove_leaf_block_works() {
5337		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(2), 10);
5338		let mut blocks = Vec::new();
5339		let mut prev_hash = Default::default();
5340		for i in 0..2 {
5341			let hash = insert_block(
5342				&backend,
5343				i,
5344				prev_hash,
5345				None,
5346				Default::default(),
5347				vec![UncheckedXt::new_transaction(i.into(), ())],
5348				None,
5349			)
5350			.unwrap();
5351			blocks.push(hash);
5352			prev_hash = hash;
5353		}
5354
5355		for i in 0..2 {
5356			let hash = insert_block(
5357				&backend,
5358				2,
5359				blocks[1],
5360				None,
5361				sp_core::H256::random(),
5362				vec![UncheckedXt::new_transaction(i.into(), ())],
5363				None,
5364			)
5365			.unwrap();
5366			blocks.push(hash);
5367		}
5368
5369		// insert a fork at block 1, which becomes best block
5370		let best_hash = insert_block(
5371			&backend,
5372			1,
5373			blocks[0],
5374			None,
5375			sp_core::H256::random(),
5376			vec![UncheckedXt::new_transaction(42.into(), ())],
5377			None,
5378		)
5379		.unwrap();
5380
5381		assert_eq!(backend.blockchain().info().best_hash, best_hash);
5382		assert!(backend.remove_leaf_block(best_hash).is_err());
5383
5384		assert_eq!(backend.blockchain().leaves().unwrap(), vec![blocks[2], blocks[3], best_hash]);
5385		assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![blocks[2], blocks[3]]);
5386
5387		assert!(backend.have_state_at(blocks[3], 2));
5388		assert!(backend.blockchain().header(blocks[3]).unwrap().is_some());
5389		backend.remove_leaf_block(blocks[3]).unwrap();
5390		assert!(!backend.have_state_at(blocks[3], 2));
5391		assert!(backend.blockchain().header(blocks[3]).unwrap().is_none());
5392		assert_eq!(backend.blockchain().leaves().unwrap(), vec![blocks[2], best_hash]);
5393		assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![blocks[2]]);
5394
5395		assert!(backend.have_state_at(blocks[2], 2));
5396		assert!(backend.blockchain().header(blocks[2]).unwrap().is_some());
5397		backend.remove_leaf_block(blocks[2]).unwrap();
5398		assert!(!backend.have_state_at(blocks[2], 2));
5399		assert!(backend.blockchain().header(blocks[2]).unwrap().is_none());
5400		assert_eq!(backend.blockchain().leaves().unwrap(), vec![best_hash, blocks[1]]);
5401		assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![]);
5402
5403		assert!(backend.have_state_at(blocks[1], 1));
5404		assert!(backend.blockchain().header(blocks[1]).unwrap().is_some());
5405		backend.remove_leaf_block(blocks[1]).unwrap();
5406		assert!(!backend.have_state_at(blocks[1], 1));
5407		assert!(backend.blockchain().header(blocks[1]).unwrap().is_none());
5408		assert_eq!(backend.blockchain().leaves().unwrap(), vec![best_hash]);
5409		assert_eq!(backend.blockchain().children(blocks[0]).unwrap(), vec![best_hash]);
5410	}
5411
5412	#[test]
5413	fn test_import_existing_block_as_new_head() {
5414		let backend: Backend<Block> = Backend::new_test(10, 3);
5415		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
5416		let block1 = insert_header(&backend, 1, block0, None, Default::default());
5417		let block2 = insert_header(&backend, 2, block1, None, Default::default());
5418		let block3 = insert_header(&backend, 3, block2, None, Default::default());
5419		let block4 = insert_header(&backend, 4, block3, None, Default::default());
5420		let block5 = insert_header(&backend, 5, block4, None, Default::default());
5421		assert_eq!(backend.blockchain().info().best_hash, block5);
5422
5423		// Insert 1 as best again. This should fail because canonicalization_delay == 3 and best ==
5424		// 5
5425		let header = Header {
5426			number: 1,
5427			parent_hash: block0,
5428			state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1),
5429			digest: Default::default(),
5430			extrinsics_root: Default::default(),
5431		};
5432		let mut op = backend.begin_operation().unwrap();
5433		op.set_block_data(header, None, None, None, NewBlockState::Best, true).unwrap();
5434		assert!(matches!(backend.commit_operation(op), Err(sp_blockchain::Error::SetHeadTooOld)));
5435
5436		// Insert 2 as best again.
5437		let header = backend.blockchain().header(block2).unwrap().unwrap();
5438		let mut op = backend.begin_operation().unwrap();
5439		op.set_block_data(header, None, None, None, NewBlockState::Best, true).unwrap();
5440		backend.commit_operation(op).unwrap();
5441		assert_eq!(backend.blockchain().info().best_hash, block2);
5442	}
5443
5444	#[test]
5445	fn test_import_existing_block_as_final() {
5446		let backend: Backend<Block> = Backend::new_test(10, 10);
5447		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
5448		let block1 = insert_header(&backend, 1, block0, None, Default::default());
5449		let _block2 = insert_header(&backend, 2, block1, None, Default::default());
5450		// Genesis is auto finalized, the rest are not.
5451		assert_eq!(backend.blockchain().info().finalized_hash, block0);
5452
5453		// Insert 1 as final again.
5454		let header = backend.blockchain().header(block1).unwrap().unwrap();
5455
5456		let mut op = backend.begin_operation().unwrap();
5457		op.set_block_data(header, None, None, None, NewBlockState::Final, true).unwrap();
5458		backend.commit_operation(op).unwrap();
5459
5460		assert_eq!(backend.blockchain().info().finalized_hash, block1);
5461	}
5462
5463	#[test]
5464	fn test_import_existing_state_fails() {
5465		let backend: Backend<Block> = Backend::new_test(10, 10);
5466		let genesis =
5467			insert_block(&backend, 0, Default::default(), None, Default::default(), vec![], None)
5468				.unwrap();
5469
5470		insert_block(&backend, 1, genesis, None, Default::default(), vec![], None).unwrap();
5471		let err = insert_block(&backend, 1, genesis, None, Default::default(), vec![], None)
5472			.err()
5473			.unwrap();
5474		match err {
5475			sp_blockchain::Error::StateDatabase(m) if m == "Block already exists" => (),
5476			e @ _ => panic!("Unexpected error {:?}", e),
5477		}
5478	}
5479
5480	#[test]
5481	fn test_leaves_not_created_for_ancient_blocks() {
5482		let backend: Backend<Block> = Backend::new_test(10, 10);
5483		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
5484
5485		let block1_a = insert_header(&backend, 1, block0, None, Default::default());
5486		let block2_a = insert_header(&backend, 2, block1_a, None, Default::default());
5487		backend.finalize_block(block1_a, None).unwrap();
5488		assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2_a]);
5489
5490		// Insert a fork prior to finalization point. Leave should not be created.
5491		insert_header_no_head(&backend, 1, block0, [1; 32].into());
5492		assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2_a]);
5493	}
5494
5495	#[test]
5496	fn revert_non_best_blocks() {
5497		let backend = Backend::<Block>::new_test(10, 10);
5498
5499		let genesis =
5500			insert_block(&backend, 0, Default::default(), None, Default::default(), vec![], None)
5501				.unwrap();
5502
5503		let block1 =
5504			insert_block(&backend, 1, genesis, None, Default::default(), vec![], None).unwrap();
5505
5506		let block2 =
5507			insert_block(&backend, 2, block1, None, Default::default(), vec![], None).unwrap();
5508
5509		let block3 = {
5510			let mut op = backend.begin_operation().unwrap();
5511			backend.begin_state_operation(&mut op, block1).unwrap();
5512			let header = Header {
5513				number: 3,
5514				parent_hash: block2,
5515				state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1),
5516				digest: Default::default(),
5517				extrinsics_root: Default::default(),
5518			};
5519
5520			op.set_block_data(
5521				header.clone(),
5522				Some(Vec::new()),
5523				None,
5524				None,
5525				NewBlockState::Normal,
5526				true,
5527			)
5528			.unwrap();
5529
5530			backend.commit_operation(op).unwrap();
5531
5532			header.hash()
5533		};
5534
5535		let block4 = {
5536			let mut op = backend.begin_operation().unwrap();
5537			backend.begin_state_operation(&mut op, block2).unwrap();
5538			let header = Header {
5539				number: 4,
5540				parent_hash: block3,
5541				state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1),
5542				digest: Default::default(),
5543				extrinsics_root: Default::default(),
5544			};
5545
5546			op.set_block_data(
5547				header.clone(),
5548				Some(Vec::new()),
5549				None,
5550				None,
5551				NewBlockState::Normal,
5552				true,
5553			)
5554			.unwrap();
5555
5556			backend.commit_operation(op).unwrap();
5557
5558			header.hash()
5559		};
5560
5561		let block3_fork = {
5562			let mut op = backend.begin_operation().unwrap();
5563			backend.begin_state_operation(&mut op, block2).unwrap();
5564			let header = Header {
5565				number: 3,
5566				parent_hash: block2,
5567				state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1),
5568				digest: Default::default(),
5569				extrinsics_root: H256::from_low_u64_le(42),
5570			};
5571
5572			op.set_block_data(
5573				header.clone(),
5574				Some(Vec::new()),
5575				None,
5576				None,
5577				NewBlockState::Normal,
5578				true,
5579			)
5580			.unwrap();
5581
5582			backend.commit_operation(op).unwrap();
5583
5584			header.hash()
5585		};
5586
5587		assert!(backend.have_state_at(block1, 1));
5588		assert!(backend.have_state_at(block2, 2));
5589		assert!(backend.have_state_at(block3, 3));
5590		assert!(backend.have_state_at(block4, 4));
5591		assert!(backend.have_state_at(block3_fork, 3));
5592
5593		assert_eq!(backend.blockchain.leaves().unwrap(), vec![block4, block3_fork]);
5594		assert_eq!(4, backend.blockchain.leaves.read().highest_leaf().unwrap().0);
5595
5596		assert_eq!(3, backend.revert(1, false).unwrap().0);
5597
5598		assert!(backend.have_state_at(block1, 1));
5599
5600		let ensure_pruned = |hash, number: u32| {
5601			assert_eq!(
5602				backend.blockchain.status(hash).unwrap(),
5603				sc_client_api::blockchain::BlockStatus::Unknown
5604			);
5605			assert!(
5606				backend
5607					.blockchain
5608					.db
5609					.get(columns::BODY, &number_and_hash_to_lookup_key(number, hash).unwrap())
5610					.is_none(),
5611				"{number}"
5612			);
5613			assert!(
5614				backend
5615					.blockchain
5616					.db
5617					.get(columns::HEADER, &number_and_hash_to_lookup_key(number, hash).unwrap())
5618					.is_none(),
5619				"{number}"
5620			);
5621		};
5622
5623		ensure_pruned(block2, 2);
5624		ensure_pruned(block3, 3);
5625		ensure_pruned(block4, 4);
5626		ensure_pruned(block3_fork, 3);
5627
5628		assert_eq!(backend.blockchain.leaves().unwrap(), vec![block1]);
5629		assert_eq!(1, backend.blockchain.leaves.read().highest_leaf().unwrap().0);
5630	}
5631
5632	#[test]
5633	fn revert_finalized_blocks() {
5634		let pruning_modes = [BlocksPruning::Some(10), BlocksPruning::KeepAll];
5635
5636		// we will create a chain with 11 blocks, finalize block #8 and then
5637		// attempt to revert 5 blocks.
5638		for pruning_mode in pruning_modes {
5639			let backend = Backend::<Block>::new_test_with_tx_storage(pruning_mode, 1);
5640
5641			let mut parent = Default::default();
5642			for i in 0..=10 {
5643				parent = insert_block(&backend, i, parent, None, Default::default(), vec![], None)
5644					.unwrap();
5645			}
5646
5647			assert_eq!(backend.blockchain().info().best_number, 10);
5648
5649			let block8 = backend.blockchain().hash(8).unwrap().unwrap();
5650			backend.finalize_block(block8, None).unwrap();
5651			backend.revert(5, true).unwrap();
5652
5653			match pruning_mode {
5654				// we can only revert to blocks for which we have state, if pruning is enabled
5655				// then the last state available will be that of the latest finalized block
5656				BlocksPruning::Some(_) => {
5657					assert_eq!(backend.blockchain().info().finalized_number, 8)
5658				},
5659				// otherwise if we're not doing state pruning we can revert past finalized blocks
5660				_ => assert_eq!(backend.blockchain().info().finalized_number, 5),
5661			}
5662		}
5663	}
5664
5665	#[test]
5666	fn test_no_duplicated_leaves_allowed() {
5667		let backend: Backend<Block> = Backend::new_test(10, 10);
5668		let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
5669		let block1 = insert_header(&backend, 1, block0, None, Default::default());
5670		// Add block 2 not as the best block
5671		let block2 = insert_header_no_head(&backend, 2, block1, Default::default());
5672		assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2]);
5673		assert_eq!(backend.blockchain().info().best_hash, block1);
5674
5675		// Add block 2 as the best block
5676		let block2 = insert_header(&backend, 2, block1, None, Default::default());
5677		assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2]);
5678		assert_eq!(backend.blockchain().info().best_hash, block2);
5679	}
5680
5681	#[test]
5682	fn force_delayed_canonicalize_waiting_for_blocks_to_be_finalized() {
5683		let pruning_modes =
5684			[BlocksPruning::Some(10), BlocksPruning::KeepAll, BlocksPruning::KeepFinalized];
5685
5686		for pruning_mode in pruning_modes {
5687			eprintln!("Running with pruning mode: {:?}", pruning_mode);
5688
5689			let backend = Backend::<Block>::new_test_with_tx_storage(pruning_mode, 1);
5690
5691			let genesis = insert_block(
5692				&backend,
5693				0,
5694				Default::default(),
5695				None,
5696				Default::default(),
5697				vec![],
5698				None,
5699			)
5700			.unwrap();
5701
5702			let block1 = {
5703				let mut op = backend.begin_operation().unwrap();
5704				backend.begin_state_operation(&mut op, genesis).unwrap();
5705				let mut header = Header {
5706					number: 1,
5707					parent_hash: genesis,
5708					state_root: Default::default(),
5709					digest: Default::default(),
5710					extrinsics_root: Default::default(),
5711				};
5712
5713				let storage = vec![(vec![1, 3, 5], None), (vec![5, 5, 5], Some(vec![4, 5, 6]))];
5714
5715				let (root, overlay) = op.old_state.storage_root(
5716					storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))),
5717					StateVersion::V1,
5718				);
5719				op.update_db_storage(overlay).unwrap();
5720				header.state_root = root.into();
5721
5722				op.update_storage(storage, Vec::new()).unwrap();
5723
5724				op.set_block_data(
5725					header.clone(),
5726					Some(Vec::new()),
5727					None,
5728					None,
5729					NewBlockState::Normal,
5730					true,
5731				)
5732				.unwrap();
5733
5734				backend.commit_operation(op).unwrap();
5735
5736				header.hash()
5737			};
5738
5739			if matches!(pruning_mode, BlocksPruning::Some(_)) {
5740				assert_eq!(
5741					LastCanonicalized::Block(0),
5742					backend.storage.state_db.last_canonicalized()
5743				);
5744			}
5745
5746			// This should not trigger any forced canonicalization as we didn't have imported any
5747			// best block by now.
5748			let block2 = {
5749				let mut op = backend.begin_operation().unwrap();
5750				backend.begin_state_operation(&mut op, block1).unwrap();
5751				let mut header = Header {
5752					number: 2,
5753					parent_hash: block1,
5754					state_root: Default::default(),
5755					digest: Default::default(),
5756					extrinsics_root: Default::default(),
5757				};
5758
5759				let storage = vec![(vec![5, 5, 5], Some(vec![4, 5, 6, 2]))];
5760
5761				let (root, overlay) = op.old_state.storage_root(
5762					storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))),
5763					StateVersion::V1,
5764				);
5765				op.update_db_storage(overlay).unwrap();
5766				header.state_root = root.into();
5767
5768				op.update_storage(storage, Vec::new()).unwrap();
5769
5770				op.set_block_data(
5771					header.clone(),
5772					Some(Vec::new()),
5773					None,
5774					None,
5775					NewBlockState::Normal,
5776					true,
5777				)
5778				.unwrap();
5779
5780				backend.commit_operation(op).unwrap();
5781
5782				header.hash()
5783			};
5784
5785			if matches!(pruning_mode, BlocksPruning::Some(_)) {
5786				assert_eq!(
5787					LastCanonicalized::Block(0),
5788					backend.storage.state_db.last_canonicalized()
5789				);
5790			}
5791
5792			// This should also not trigger it yet, because we import a best block, but the best
5793			// block from the POV of the db is still at `0`.
5794			let block3 = {
5795				let mut op = backend.begin_operation().unwrap();
5796				backend.begin_state_operation(&mut op, block2).unwrap();
5797				let mut header = Header {
5798					number: 3,
5799					parent_hash: block2,
5800					state_root: Default::default(),
5801					digest: Default::default(),
5802					extrinsics_root: Default::default(),
5803				};
5804
5805				let storage = vec![(vec![5, 5, 5], Some(vec![4, 5, 6, 3]))];
5806
5807				let (root, overlay) = op.old_state.storage_root(
5808					storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))),
5809					StateVersion::V1,
5810				);
5811				op.update_db_storage(overlay).unwrap();
5812				header.state_root = root.into();
5813
5814				op.update_storage(storage, Vec::new()).unwrap();
5815
5816				op.set_block_data(
5817					header.clone(),
5818					Some(Vec::new()),
5819					None,
5820					None,
5821					NewBlockState::Best,
5822					true,
5823				)
5824				.unwrap();
5825
5826				backend.commit_operation(op).unwrap();
5827
5828				header.hash()
5829			};
5830
5831			// Now it should kick in.
5832			let block4 = {
5833				let mut op = backend.begin_operation().unwrap();
5834				backend.begin_state_operation(&mut op, block3).unwrap();
5835				let mut header = Header {
5836					number: 4,
5837					parent_hash: block3,
5838					state_root: Default::default(),
5839					digest: Default::default(),
5840					extrinsics_root: Default::default(),
5841				};
5842
5843				let storage = vec![(vec![5, 5, 5], Some(vec![4, 5, 6, 4]))];
5844
5845				let (root, overlay) = op.old_state.storage_root(
5846					storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))),
5847					StateVersion::V1,
5848				);
5849				op.update_db_storage(overlay).unwrap();
5850				header.state_root = root.into();
5851
5852				op.update_storage(storage, Vec::new()).unwrap();
5853
5854				op.set_block_data(
5855					header.clone(),
5856					Some(Vec::new()),
5857					None,
5858					None,
5859					NewBlockState::Best,
5860					true,
5861				)
5862				.unwrap();
5863
5864				backend.commit_operation(op).unwrap();
5865
5866				header.hash()
5867			};
5868
5869			if matches!(pruning_mode, BlocksPruning::Some(_)) {
5870				assert_eq!(
5871					LastCanonicalized::Block(2),
5872					backend.storage.state_db.last_canonicalized()
5873				);
5874			}
5875
5876			assert_eq!(block1, backend.blockchain().hash(1).unwrap().unwrap());
5877			assert_eq!(block2, backend.blockchain().hash(2).unwrap().unwrap());
5878			assert_eq!(block3, backend.blockchain().hash(3).unwrap().unwrap());
5879			assert_eq!(block4, backend.blockchain().hash(4).unwrap().unwrap());
5880		}
5881	}
5882
5883	#[test]
5884	fn test_pinned_blocks_on_finalize() {
5885		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(1), 10);
5886		let mut blocks = Vec::new();
5887		let mut prev_hash = Default::default();
5888
5889		let build_justification = |i: u64| ([0, 0, 0, 0], vec![i.try_into().unwrap()]);
5890		// Block tree:
5891		//   0 -> 1 -> 2 -> 3 -> 4
5892		for i in 0..5 {
5893			let hash = insert_block(
5894				&backend,
5895				i,
5896				prev_hash,
5897				None,
5898				Default::default(),
5899				vec![UncheckedXt::new_transaction(i.into(), ())],
5900				None,
5901			)
5902			.unwrap();
5903			blocks.push(hash);
5904			// Avoid block pruning.
5905			backend.pin_block(blocks[i as usize]).unwrap();
5906
5907			prev_hash = hash;
5908		}
5909
5910		let bc = backend.blockchain();
5911
5912		// Check that we can properly access values when there is reference count
5913		// but no value.
5914		assert_eq!(
5915			Some(vec![UncheckedXt::new_transaction(1.into(), ())]),
5916			bc.body(blocks[1]).unwrap()
5917		);
5918
5919		// Block 1 gets pinned three times
5920		backend.pin_block(blocks[1]).unwrap();
5921		backend.pin_block(blocks[1]).unwrap();
5922
5923		// Finalize all blocks. This will trigger pruning.
5924		let mut op = backend.begin_operation().unwrap();
5925		backend.begin_state_operation(&mut op, blocks[4]).unwrap();
5926		for i in 1..5 {
5927			op.mark_finalized(blocks[i], Some(build_justification(i.try_into().unwrap())))
5928				.unwrap();
5929		}
5930		backend.commit_operation(op).unwrap();
5931
5932		// Block 0, 1, 2, 3 are pinned, so all values should be cached.
5933		// Block 4 is inside the pruning window, its value is in db.
5934		assert_eq!(
5935			Some(vec![UncheckedXt::new_transaction(0.into(), ())]),
5936			bc.body(blocks[0]).unwrap()
5937		);
5938
5939		assert_eq!(
5940			Some(vec![UncheckedXt::new_transaction(1.into(), ())]),
5941			bc.body(blocks[1]).unwrap()
5942		);
5943		assert_eq!(
5944			Some(Justifications::from(build_justification(1))),
5945			bc.justifications(blocks[1]).unwrap()
5946		);
5947
5948		assert_eq!(
5949			Some(vec![UncheckedXt::new_transaction(2.into(), ())]),
5950			bc.body(blocks[2]).unwrap()
5951		);
5952		assert_eq!(
5953			Some(Justifications::from(build_justification(2))),
5954			bc.justifications(blocks[2]).unwrap()
5955		);
5956
5957		assert_eq!(
5958			Some(vec![UncheckedXt::new_transaction(3.into(), ())]),
5959			bc.body(blocks[3]).unwrap()
5960		);
5961		assert_eq!(
5962			Some(Justifications::from(build_justification(3))),
5963			bc.justifications(blocks[3]).unwrap()
5964		);
5965
5966		assert_eq!(
5967			Some(vec![UncheckedXt::new_transaction(4.into(), ())]),
5968			bc.body(blocks[4]).unwrap()
5969		);
5970		assert_eq!(
5971			Some(Justifications::from(build_justification(4))),
5972			bc.justifications(blocks[4]).unwrap()
5973		);
5974
5975		// Unpin all blocks. Values should be removed from cache.
5976		for block in &blocks {
5977			backend.unpin_block(*block);
5978		}
5979
5980		assert!(bc.body(blocks[0]).unwrap().is_none());
5981		// Block 1 was pinned twice, we expect it to be still cached
5982		assert!(bc.body(blocks[1]).unwrap().is_some());
5983		assert!(bc.justifications(blocks[1]).unwrap().is_some());
5984		// Headers should also be available while pinned
5985		assert!(bc.header(blocks[1]).ok().flatten().is_some());
5986		assert!(bc.body(blocks[2]).unwrap().is_none());
5987		assert!(bc.justifications(blocks[2]).unwrap().is_none());
5988		assert!(bc.body(blocks[3]).unwrap().is_none());
5989		assert!(bc.justifications(blocks[3]).unwrap().is_none());
5990
5991		// After these unpins, block 1 should also be removed
5992		backend.unpin_block(blocks[1]);
5993		assert!(bc.body(blocks[1]).unwrap().is_some());
5994		assert!(bc.justifications(blocks[1]).unwrap().is_some());
5995		backend.unpin_block(blocks[1]);
5996		assert!(bc.body(blocks[1]).unwrap().is_none());
5997		assert!(bc.justifications(blocks[1]).unwrap().is_none());
5998
5999		// Block 4 is inside the pruning window and still kept
6000		assert_eq!(
6001			Some(vec![UncheckedXt::new_transaction(4.into(), ())]),
6002			bc.body(blocks[4]).unwrap()
6003		);
6004		assert_eq!(
6005			Some(Justifications::from(build_justification(4))),
6006			bc.justifications(blocks[4]).unwrap()
6007		);
6008
6009		// Block tree:
6010		//   0 -> 1 -> 2 -> 3 -> 4 -> 5
6011		let hash = insert_block(
6012			&backend,
6013			5,
6014			prev_hash,
6015			None,
6016			Default::default(),
6017			vec![UncheckedXt::new_transaction(5.into(), ())],
6018			None,
6019		)
6020		.unwrap();
6021		blocks.push(hash);
6022
6023		backend.pin_block(blocks[4]).unwrap();
6024		// Mark block 5 as finalized.
6025		let mut op = backend.begin_operation().unwrap();
6026		backend.begin_state_operation(&mut op, blocks[5]).unwrap();
6027		op.mark_finalized(blocks[5], Some(build_justification(5))).unwrap();
6028		backend.commit_operation(op).unwrap();
6029
6030		assert!(bc.body(blocks[0]).unwrap().is_none());
6031		assert!(bc.body(blocks[1]).unwrap().is_none());
6032		assert!(bc.body(blocks[2]).unwrap().is_none());
6033		assert!(bc.body(blocks[3]).unwrap().is_none());
6034
6035		assert_eq!(
6036			Some(vec![UncheckedXt::new_transaction(4.into(), ())]),
6037			bc.body(blocks[4]).unwrap()
6038		);
6039		assert_eq!(
6040			Some(Justifications::from(build_justification(4))),
6041			bc.justifications(blocks[4]).unwrap()
6042		);
6043		assert_eq!(
6044			Some(vec![UncheckedXt::new_transaction(5.into(), ())]),
6045			bc.body(blocks[5]).unwrap()
6046		);
6047		assert!(bc.header(blocks[5]).ok().flatten().is_some());
6048
6049		backend.unpin_block(blocks[4]);
6050		assert!(bc.body(blocks[4]).unwrap().is_none());
6051		assert!(bc.justifications(blocks[4]).unwrap().is_none());
6052
6053		// Append a justification to block 5.
6054		backend.append_justification(blocks[5], ([0, 0, 0, 1], vec![42])).unwrap();
6055
6056		let hash = insert_block(
6057			&backend,
6058			6,
6059			blocks[5],
6060			None,
6061			Default::default(),
6062			vec![UncheckedXt::new_transaction(6.into(), ())],
6063			None,
6064		)
6065		.unwrap();
6066		blocks.push(hash);
6067
6068		// Pin block 5 so it gets loaded into the cache on prune
6069		backend.pin_block(blocks[5]).unwrap();
6070
6071		// Finalize block 6 so block 5 gets pruned. Since it is pinned both justifications should be
6072		// in memory.
6073		let mut op = backend.begin_operation().unwrap();
6074		backend.begin_state_operation(&mut op, blocks[6]).unwrap();
6075		op.mark_finalized(blocks[6], None).unwrap();
6076		backend.commit_operation(op).unwrap();
6077
6078		assert_eq!(
6079			Some(vec![UncheckedXt::new_transaction(5.into(), ())]),
6080			bc.body(blocks[5]).unwrap()
6081		);
6082		assert!(bc.header(blocks[5]).ok().flatten().is_some());
6083		let mut expected = Justifications::from(build_justification(5));
6084		expected.append(([0, 0, 0, 1], vec![42]));
6085		assert_eq!(Some(expected), bc.justifications(blocks[5]).unwrap());
6086	}
6087
6088	#[test]
6089	fn test_pinned_blocks_on_finalize_with_fork() {
6090		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(1), 10);
6091		let mut blocks = Vec::new();
6092		let mut prev_hash = Default::default();
6093
6094		// Block tree:
6095		//   0 -> 1 -> 2 -> 3 -> 4
6096		for i in 0..5 {
6097			let hash = insert_block(
6098				&backend,
6099				i,
6100				prev_hash,
6101				None,
6102				Default::default(),
6103				vec![UncheckedXt::new_transaction(i.into(), ())],
6104				None,
6105			)
6106			.unwrap();
6107			blocks.push(hash);
6108
6109			// Avoid block pruning.
6110			backend.pin_block(blocks[i as usize]).unwrap();
6111
6112			prev_hash = hash;
6113		}
6114
6115		// Insert a fork at the second block.
6116		// Block tree:
6117		//   0 -> 1 -> 2 -> 3 -> 4
6118		//        \ -> 2 -> 3
6119		let fork_hash_root = insert_block(
6120			&backend,
6121			2,
6122			blocks[1],
6123			None,
6124			H256::random(),
6125			vec![UncheckedXt::new_transaction(2.into(), ())],
6126			None,
6127		)
6128		.unwrap();
6129		let fork_hash_3 = insert_block(
6130			&backend,
6131			3,
6132			fork_hash_root,
6133			None,
6134			H256::random(),
6135			vec![
6136				UncheckedXt::new_transaction(3.into(), ()),
6137				UncheckedXt::new_transaction(11.into(), ()),
6138			],
6139			None,
6140		)
6141		.unwrap();
6142
6143		// Do not prune the fork hash.
6144		backend.pin_block(fork_hash_3).unwrap();
6145
6146		let mut op = backend.begin_operation().unwrap();
6147		backend.begin_state_operation(&mut op, blocks[4]).unwrap();
6148		op.mark_head(blocks[4]).unwrap();
6149		backend.commit_operation(op).unwrap();
6150
6151		for i in 1..5 {
6152			let mut op = backend.begin_operation().unwrap();
6153			backend.begin_state_operation(&mut op, blocks[4]).unwrap();
6154			op.mark_finalized(blocks[i], None).unwrap();
6155			backend.commit_operation(op).unwrap();
6156		}
6157
6158		let bc = backend.blockchain();
6159		assert_eq!(
6160			Some(vec![UncheckedXt::new_transaction(0.into(), ())]),
6161			bc.body(blocks[0]).unwrap()
6162		);
6163		assert_eq!(
6164			Some(vec![UncheckedXt::new_transaction(1.into(), ())]),
6165			bc.body(blocks[1]).unwrap()
6166		);
6167		assert_eq!(
6168			Some(vec![UncheckedXt::new_transaction(2.into(), ())]),
6169			bc.body(blocks[2]).unwrap()
6170		);
6171		assert_eq!(
6172			Some(vec![UncheckedXt::new_transaction(3.into(), ())]),
6173			bc.body(blocks[3]).unwrap()
6174		);
6175		assert_eq!(
6176			Some(vec![UncheckedXt::new_transaction(4.into(), ())]),
6177			bc.body(blocks[4]).unwrap()
6178		);
6179		// Check the fork hashes.
6180		assert_eq!(None, bc.body(fork_hash_root).unwrap());
6181		assert_eq!(
6182			Some(vec![
6183				UncheckedXt::new_transaction(3.into(), ()),
6184				UncheckedXt::new_transaction(11.into(), ())
6185			]),
6186			bc.body(fork_hash_3).unwrap()
6187		);
6188
6189		// Unpin all blocks, except the forked one.
6190		for block in &blocks {
6191			backend.unpin_block(*block);
6192		}
6193		assert!(bc.body(blocks[0]).unwrap().is_none());
6194		assert!(bc.body(blocks[1]).unwrap().is_none());
6195		assert!(bc.body(blocks[2]).unwrap().is_none());
6196		assert!(bc.body(blocks[3]).unwrap().is_none());
6197
6198		assert!(bc.body(fork_hash_3).unwrap().is_some());
6199		backend.unpin_block(fork_hash_3);
6200		assert!(bc.body(fork_hash_3).unwrap().is_none());
6201	}
6202
6203	#[test]
6204	fn prune_blocks_with_empty_predicates_prunes_all() {
6205		// Test backward compatibility: empty predicates means all blocks are pruned
6206		let backend = Backend::<Block>::new_test_with_tx_storage_and_filters(
6207			BlocksPruning::Some(2),
6208			0,
6209			vec![], // Empty predicates
6210		);
6211
6212		let mut blocks = Vec::new();
6213		let mut prev_hash = Default::default();
6214
6215		// Create 5 blocks
6216		for i in 0..5 {
6217			let hash = insert_block(
6218				&backend,
6219				i,
6220				prev_hash,
6221				None,
6222				Default::default(),
6223				vec![UncheckedXt::new_transaction(i.into(), ())],
6224				None,
6225			)
6226			.unwrap();
6227			blocks.push(hash);
6228			prev_hash = hash;
6229		}
6230
6231		// Justification - but no predicate to preserve it
6232		let justification = (CONS0_ENGINE_ID, vec![1, 2, 3]);
6233
6234		// Finalize blocks, adding justification to block 1
6235		{
6236			let mut op = backend.begin_operation().unwrap();
6237			backend.begin_state_operation(&mut op, blocks[4]).unwrap();
6238			op.mark_finalized(blocks[1], Some(justification.clone())).unwrap();
6239			op.mark_finalized(blocks[2], None).unwrap();
6240			op.mark_finalized(blocks[3], None).unwrap();
6241			op.mark_finalized(blocks[4], None).unwrap();
6242			backend.commit_operation(op).unwrap();
6243		}
6244
6245		let bc = backend.blockchain();
6246
6247		// All blocks outside pruning window should be pruned, even with justification
6248		assert_eq!(None, bc.body(blocks[0]).unwrap());
6249		assert_eq!(None, bc.body(blocks[1]).unwrap()); // Has justification but no predicate
6250		assert_eq!(None, bc.body(blocks[2]).unwrap());
6251
6252		// Blocks 3 and 4 are within the pruning window
6253		assert!(bc.body(blocks[3]).unwrap().is_some());
6254		assert!(bc.body(blocks[4]).unwrap().is_some());
6255	}
6256
6257	#[test]
6258	fn prune_blocks_multiple_filters_or_logic() {
6259		// Test that multiple filters use OR logic: if ANY filter matches, block is kept
6260		let backend = Backend::<Block>::new_test_with_tx_storage_and_filters(
6261			BlocksPruning::Some(2),
6262			0,
6263			vec![
6264				Arc::new(|j: &Justifications| j.get(CONS0_ENGINE_ID).is_some()),
6265				Arc::new(|j: &Justifications| j.get(CONS1_ENGINE_ID).is_some()),
6266			],
6267		);
6268
6269		let mut blocks = Vec::new();
6270		let mut prev_hash = Default::default();
6271
6272		// Create 7 blocks
6273		for i in 0..7 {
6274			let hash = insert_block(
6275				&backend,
6276				i,
6277				prev_hash,
6278				None,
6279				Default::default(),
6280				vec![UncheckedXt::new_transaction(i.into(), ())],
6281				None,
6282			)
6283			.unwrap();
6284			blocks.push(hash);
6285			prev_hash = hash;
6286		}
6287
6288		let cons0_justification = (CONS0_ENGINE_ID, vec![1, 2, 3]);
6289		let cons1_justification = (CONS1_ENGINE_ID, vec![4, 5, 6]);
6290
6291		// Finalize blocks with different justification patterns
6292		{
6293			let mut op = backend.begin_operation().unwrap();
6294			backend.begin_state_operation(&mut op, blocks[6]).unwrap();
6295			// Block 1: CONS0 only - should be preserved
6296			op.mark_finalized(blocks[1], Some(cons0_justification.clone())).unwrap();
6297			// Block 2: CONS1 only - should be preserved
6298			op.mark_finalized(blocks[2], Some(cons1_justification.clone())).unwrap();
6299			// Block 3: No justification - should be pruned
6300			op.mark_finalized(blocks[3], None).unwrap();
6301			// Block 4: Random/unknown engine ID - should be pruned
6302			op.mark_finalized(blocks[4], Some(([9, 9, 9, 9], vec![7, 8, 9]))).unwrap();
6303			op.mark_finalized(blocks[5], None).unwrap();
6304			op.mark_finalized(blocks[6], None).unwrap();
6305			backend.commit_operation(op).unwrap();
6306		}
6307
6308		let bc = backend.blockchain();
6309
6310		// Block 0 should be pruned (outside window, no justification)
6311		assert_eq!(None, bc.body(blocks[0]).unwrap());
6312
6313		// Block 1 should be preserved (has CONS0 justification)
6314		assert!(bc.body(blocks[1]).unwrap().is_some());
6315
6316		// Block 2 should be preserved (has CONS1 justification)
6317		assert!(bc.body(blocks[2]).unwrap().is_some());
6318
6319		// Block 3 should be pruned (no justification)
6320		assert_eq!(None, bc.body(blocks[3]).unwrap());
6321
6322		// Block 4 should be pruned (unknown engine ID)
6323		assert_eq!(None, bc.body(blocks[4]).unwrap());
6324
6325		// Blocks 5 and 6 are within the pruning window
6326		assert!(bc.body(blocks[5]).unwrap().is_some());
6327		assert!(bc.body(blocks[6]).unwrap().is_some());
6328	}
6329
6330	#[test]
6331	fn prune_blocks_filter_only_matches_specific_engine() {
6332		// Test that a filter for one engine ID does NOT preserve blocks with a different engine ID
6333		let backend = Backend::<Block>::new_test_with_tx_storage_and_filters(
6334			BlocksPruning::Some(2),
6335			0,
6336			vec![Arc::new(|j: &Justifications| j.get(CONS0_ENGINE_ID).is_some())],
6337		);
6338
6339		let mut blocks = Vec::new();
6340		let mut prev_hash = Default::default();
6341
6342		// Create 5 blocks
6343		for i in 0..5 {
6344			let hash = insert_block(
6345				&backend,
6346				i,
6347				prev_hash,
6348				None,
6349				Default::default(),
6350				vec![UncheckedXt::new_transaction(i.into(), ())],
6351				None,
6352			)
6353			.unwrap();
6354			blocks.push(hash);
6355			prev_hash = hash;
6356		}
6357
6358		let cons1_justification = (CONS1_ENGINE_ID, vec![4, 5, 6]);
6359
6360		// Finalize blocks, adding CONS1 justification to block 1
6361		{
6362			let mut op = backend.begin_operation().unwrap();
6363			backend.begin_state_operation(&mut op, blocks[4]).unwrap();
6364			// Block 1 gets CONS1 justification - should NOT be preserved by CONS0 filter
6365			op.mark_finalized(blocks[1], Some(cons1_justification.clone())).unwrap();
6366			op.mark_finalized(blocks[2], None).unwrap();
6367			op.mark_finalized(blocks[3], None).unwrap();
6368			op.mark_finalized(blocks[4], None).unwrap();
6369			backend.commit_operation(op).unwrap();
6370		}
6371
6372		let bc = backend.blockchain();
6373
6374		// Block 0 should be pruned
6375		assert_eq!(None, bc.body(blocks[0]).unwrap());
6376
6377		// Block 1 should also be pruned (CONS1 justification, but only CONS0 filter)
6378		assert_eq!(None, bc.body(blocks[1]).unwrap());
6379
6380		// Block 2 should be pruned
6381		assert_eq!(None, bc.body(blocks[2]).unwrap());
6382
6383		// Blocks 3 and 4 are within the pruning window
6384		assert!(bc.body(blocks[3]).unwrap().is_some());
6385		assert!(bc.body(blocks[4]).unwrap().is_some());
6386	}
6387
6388	/// Insert a header without body as best block. This triggers `MissingBody` gap creation
6389	/// when the parent header exists and `create_gap` is true.
6390	fn insert_header_no_body_as_best(
6391		backend: &Backend<Block>,
6392		number: u64,
6393		parent_hash: H256,
6394	) -> H256 {
6395		use sp_runtime::testing::Digest;
6396
6397		let digest = Digest::default();
6398		let header = Header {
6399			number,
6400			parent_hash,
6401			state_root: Default::default(),
6402			digest,
6403			extrinsics_root: Default::default(),
6404		};
6405
6406		let mut op = backend.begin_operation().unwrap();
6407		// body = None triggers MissingBody gap when parent exists
6408		op.set_block_data(header.clone(), None, None, None, NewBlockState::Best, true)
6409			.unwrap();
6410		backend.commit_operation(op).unwrap();
6411
6412		header.hash()
6413	}
6414
6415	/// Re-open a backend from an existing database with the given blocks pruning mode.
6416	fn reopen_backend(
6417		db: Arc<dyn sp_database::Database<DbHash>>,
6418		blocks_pruning: BlocksPruning,
6419	) -> Backend<Block> {
6420		let state_pruning = match blocks_pruning {
6421			BlocksPruning::KeepAll => PruningMode::ArchiveAll,
6422			BlocksPruning::KeepFinalized => PruningMode::ArchiveCanonical,
6423			BlocksPruning::Some(n) => PruningMode::blocks_pruning(n),
6424		};
6425		Backend::<Block>::new(
6426			DatabaseSettings {
6427				trie_cache_maximum_size: Some(16 * 1024 * 1024),
6428				state_pruning: Some(state_pruning),
6429				source: DatabaseSource::Custom { db, require_create_flag: false },
6430				blocks_pruning,
6431				pruning_filters: Default::default(),
6432				metrics_registry: None,
6433			},
6434			0,
6435		)
6436		.unwrap()
6437	}
6438
6439	#[test]
6440	fn missing_body_gap_is_removed_for_non_archive_node() {
6441		// Create a non-archive backend and produce a multi-block MissingBody gap.
6442		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(100), 0);
6443		assert!(!backend.is_archive);
6444
6445		let genesis_hash = insert_header(&backend, 0, Default::default(), None, Default::default());
6446
6447		// Insert blocks 1..3 without bodies — creates a MissingBody gap spanning blocks 1 to 3.
6448		let hash_1 = insert_header_no_body_as_best(&backend, 1, genesis_hash);
6449		let hash_2 = insert_header_no_body_as_best(&backend, 2, hash_1);
6450		insert_header_no_body_as_best(&backend, 3, hash_2);
6451
6452		let info = backend.blockchain().info();
6453		assert!(info.block_gap.is_some(), "MissingBody gap should have been created");
6454		let gap = info.block_gap.unwrap();
6455		assert!(matches!(gap.gap_type, BlockGapType::MissingBody));
6456		assert_eq!(gap.start, 1);
6457		assert_eq!(gap.end, 3);
6458
6459		// Re-open the same database as a non-archive node.
6460		let db = backend.storage.db.clone();
6461		let backend = reopen_backend(db, BlocksPruning::Some(100));
6462		assert!(!backend.is_archive);
6463
6464		// The multi-block gap should have been removed on re-open.
6465		let info = backend.blockchain().info();
6466		assert!(
6467			info.block_gap.is_none(),
6468			"MissingBody gap should be removed for non-archive nodes, got: {:?}",
6469			info.block_gap,
6470		);
6471	}
6472
6473	#[test]
6474	fn missing_body_gap_is_preserved_for_archive_node() {
6475		// Create a backend with archive pruning and produce a multi-block MissingBody gap.
6476		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::KeepAll, 0);
6477		assert!(backend.is_archive);
6478
6479		let genesis_hash = insert_header(&backend, 0, Default::default(), None, Default::default());
6480
6481		// Insert blocks 1..3 without bodies — creates a MissingBody gap spanning blocks 1 to 3.
6482		let hash_1 = insert_header_no_body_as_best(&backend, 1, genesis_hash);
6483		let hash_2 = insert_header_no_body_as_best(&backend, 2, hash_1);
6484		insert_header_no_body_as_best(&backend, 3, hash_2);
6485
6486		let info = backend.blockchain().info();
6487		assert!(info.block_gap.is_some(), "MissingBody gap should have been created");
6488		let gap = info.block_gap.unwrap();
6489		assert!(matches!(gap.gap_type, BlockGapType::MissingBody));
6490		assert_eq!(gap.start, 1);
6491		assert_eq!(gap.end, 3);
6492
6493		// Re-open the same database as an archive node.
6494		let db = backend.storage.db.clone();
6495		let backend = reopen_backend(db, BlocksPruning::KeepAll);
6496		assert!(backend.is_archive);
6497
6498		// The gap should be preserved for archive nodes.
6499		let info = backend.blockchain().info();
6500		assert!(info.block_gap.is_some(), "MissingBody gap should be preserved for archive nodes",);
6501		let gap = info.block_gap.unwrap();
6502		assert!(matches!(gap.gap_type, BlockGapType::MissingBody));
6503		assert_eq!(gap.start, 1);
6504		assert_eq!(gap.end, 3);
6505	}
6506
6507	#[test]
6508	fn missing_header_and_body_gap_is_preserved_for_non_archive_node() {
6509		// Create a non-archive backend and produce a MissingHeaderAndBody gap (from warp sync).
6510		let backend = Backend::<Block>::new_test_with_tx_storage(BlocksPruning::Some(100), 0);
6511		assert!(!backend.is_archive);
6512
6513		let _genesis_hash =
6514			insert_header(&backend, 0, Default::default(), None, Default::default());
6515
6516		// Insert a disconnected block at height 3 with a fake parent to create a
6517		// MissingHeaderAndBody gap (blocks 1..2 are missing).
6518		insert_disconnected_header(&backend, 3, H256::from([200; 32]), Default::default(), true);
6519
6520		let info = backend.blockchain().info();
6521		assert!(info.block_gap.is_some(), "Gap should have been created");
6522		let gap = info.block_gap.unwrap();
6523		assert!(matches!(gap.gap_type, BlockGapType::MissingHeaderAndBody));
6524		assert_eq!(gap.start, 1);
6525		assert_eq!(gap.end, 2);
6526
6527		// Re-open the same database as a non-archive node.
6528		let db = backend.storage.db.clone();
6529		let backend = reopen_backend(db, BlocksPruning::Some(100));
6530		assert!(!backend.is_archive);
6531
6532		// The MissingHeaderAndBody gap should NOT be removed — only MissingBody gaps are removed.
6533		let info = backend.blockchain().info();
6534		assert!(
6535			info.block_gap.is_some(),
6536			"MissingHeaderAndBody gap should be preserved for non-archive nodes",
6537		);
6538		let gap = info.block_gap.unwrap();
6539		assert!(matches!(gap.gap_type, BlockGapType::MissingHeaderAndBody));
6540		assert_eq!(gap.start, 1);
6541		assert_eq!(gap.end, 2);
6542	}
6543
6544	mod indexed_transaction_tests {
6545		use super::*;
6546		use crate::utils::NUM_COLUMNS;
6547		use rstest::rstest;
6548		use sp_database::Transaction as DbTransaction;
6549		use std::{path::PathBuf, sync::Arc};
6550		use tempfile::TempDir;
6551
6552		#[derive(Debug, Clone, Copy)]
6553		enum BackendKind {
6554			KvdbMemdb,
6555			ParityDb,
6556			RocksDb,
6557		}
6558
6559		enum DbFactory {
6560			Persistent(Arc<dyn Database<DbHash>>),
6561			OnDisk { path: PathBuf, kind: BackendKind, _tmp: TempDir },
6562		}
6563
6564		impl DbFactory {
6565			fn new(kind: BackendKind) -> Self {
6566				match kind {
6567					BackendKind::KvdbMemdb => Self::Persistent(sp_database::as_database(
6568						kvdb_memorydb::create(NUM_COLUMNS),
6569					)),
6570					BackendKind::ParityDb | BackendKind::RocksDb => {
6571						let tmp = TempDir::new().unwrap();
6572						let path = tmp.path().to_path_buf();
6573						Self::OnDisk { path, kind, _tmp: tmp }
6574					},
6575				}
6576			}
6577
6578			fn open(&self) -> Arc<dyn Database<DbHash>> {
6579				match self {
6580					Self::Persistent(arc) => arc.clone(),
6581					Self::OnDisk { path, kind: BackendKind::ParityDb, .. } => {
6582						crate::parity_db::open::<DbHash>(path, DatabaseType::Full, true, false)
6583							.expect("parity-db open succeeds in test")
6584					},
6585					Self::OnDisk { path, kind: BackendKind::RocksDb, .. } => {
6586						let mut cfg = kvdb_rocksdb::DatabaseConfig::with_columns(NUM_COLUMNS);
6587						cfg.create_if_missing = true;
6588						let db = kvdb_rocksdb::Database::open(&cfg, path)
6589							.expect("kvdb-rocksdb open succeeds in test");
6590						sp_database::as_database(db)
6591					},
6592					Self::OnDisk { kind: BackendKind::KvdbMemdb, .. } => unreachable!(),
6593				}
6594			}
6595		}
6596
6597		const TEST_COL: u32 = columns::TRANSACTION;
6598
6599		fn hash(seed: u8) -> DbHash {
6600			DbHash::repeat_byte(seed)
6601		}
6602
6603		fn commit_store(factory: &DbFactory, h: DbHash, bytes: Vec<u8>) {
6604			let db = factory.open();
6605			let mut tx = DbTransaction::new();
6606			tx.store(TEST_COL, h, bytes);
6607			db.commit(tx).unwrap();
6608		}
6609
6610		fn commit_reference(factory: &DbFactory, h: DbHash) {
6611			let db = factory.open();
6612			let mut tx = DbTransaction::new();
6613			tx.reference(TEST_COL, h);
6614			db.commit(tx).unwrap();
6615		}
6616
6617		fn commit_release(factory: &DbFactory, h: DbHash) {
6618			let db = factory.open();
6619			let mut tx = DbTransaction::new();
6620			tx.release(TEST_COL, h);
6621			db.commit(tx).unwrap();
6622		}
6623
6624		fn get_value(factory: &DbFactory, h: DbHash) -> Option<Vec<u8>> {
6625			factory.open().get(TEST_COL, h.as_ref())
6626		}
6627
6628		#[rstest]
6629		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
6630		#[case::paritydb(BackendKind::ParityDb)]
6631		#[case::rocksdb(BackendKind::RocksDb)]
6632		fn store_then_get(#[case] kind: BackendKind) {
6633			let factory = DbFactory::new(kind);
6634			let h = hash(0xA1);
6635			let bytes = b"a1-bytes".to_vec();
6636			commit_store(&factory, h, bytes.clone());
6637			assert_eq!(get_value(&factory, h).as_deref(), Some(bytes.as_slice()));
6638		}
6639
6640		#[rstest]
6641		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
6642		#[case::paritydb(BackendKind::ParityDb)]
6643		#[case::rocksdb(BackendKind::RocksDb)]
6644		fn store_release_separate_commits(#[case] kind: BackendKind) {
6645			let factory = DbFactory::new(kind);
6646			let h = hash(0xA2);
6647			let bytes = b"a2-bytes".to_vec();
6648			commit_store(&factory, h, bytes);
6649			assert!(get_value(&factory, h).is_some(), "present after store");
6650			commit_release(&factory, h);
6651			assert!(get_value(&factory, h).is_none(), "gone after release");
6652		}
6653
6654		#[rstest]
6655		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
6656		#[case::paritydb(BackendKind::ParityDb)]
6657		#[case::rocksdb(BackendKind::RocksDb)]
6658		fn store_reference_release_release_separate_commits(#[case] kind: BackendKind) {
6659			let factory = DbFactory::new(kind);
6660			let h = hash(0xA3);
6661			let bytes = b"a3-bytes".to_vec();
6662			commit_store(&factory, h, bytes);
6663			commit_reference(&factory, h);
6664			assert!(get_value(&factory, h).is_some(), "rc=2 after reference");
6665			commit_release(&factory, h);
6666			assert!(get_value(&factory, h).is_some(), "rc=1 still present");
6667			commit_release(&factory, h);
6668			assert!(get_value(&factory, h).is_none(), "rc=0 removed");
6669		}
6670
6671		#[rstest]
6672		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
6673		#[case::paritydb(BackendKind::ParityDb)]
6674		#[case::rocksdb(BackendKind::RocksDb)]
6675		fn store_then_reference_same_commit_keeps_value(#[case] kind: BackendKind) {
6676			let factory = DbFactory::new(kind);
6677			let h = hash(0xA4);
6678			let bytes = b"a4-bytes".to_vec();
6679			{
6680				let db = factory.open();
6681				let mut tx = DbTransaction::new();
6682				tx.store(TEST_COL, h, bytes.clone());
6683				tx.reference(TEST_COL, h);
6684				db.commit(tx).unwrap();
6685			}
6686			assert_eq!(
6687				get_value(&factory, h).as_deref(),
6688				Some(bytes.as_slice()),
6689				"Store + Reference on fresh hash in a single commit must keep the value \
6690				 (observed via fresh DB handle so overlay caching is bypassed)",
6691			);
6692		}
6693
6694		#[rstest]
6695		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
6696		#[case::paritydb(BackendKind::ParityDb)]
6697		#[case::rocksdb(BackendKind::RocksDb)]
6698		fn store_then_two_references_same_commit_keeps_value(#[case] kind: BackendKind) {
6699			let factory = DbFactory::new(kind);
6700			let h = hash(0xA5);
6701			let bytes = b"a5-bytes".to_vec();
6702			{
6703				let db = factory.open();
6704				let mut tx = DbTransaction::new();
6705				tx.store(TEST_COL, h, bytes.clone());
6706				tx.reference(TEST_COL, h);
6707				tx.reference(TEST_COL, h);
6708				db.commit(tx).unwrap();
6709			}
6710			assert_eq!(
6711				get_value(&factory, h).as_deref(),
6712				Some(bytes.as_slice()),
6713				"Store + 2x Reference on fresh hash must keep the value (post-sync observation)",
6714			);
6715		}
6716
6717		#[rstest]
6718		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
6719		#[case::paritydb(BackendKind::ParityDb)]
6720		#[case::rocksdb(BackendKind::RocksDb)]
6721		fn reference_on_missing_hash_is_noop(#[case] kind: BackendKind) {
6722			let factory = DbFactory::new(kind);
6723			let h = hash(0xA6);
6724			commit_reference(&factory, h);
6725			assert!(get_value(&factory, h).is_none(), "reference on missing key is a no-op");
6726			let bytes = b"a6-bytes".to_vec();
6727			commit_store(&factory, h, bytes.clone());
6728			assert_eq!(get_value(&factory, h).as_deref(), Some(bytes.as_slice()));
6729			commit_release(&factory, h);
6730			assert!(get_value(&factory, h).is_none(), "single release balances the store");
6731		}
6732
6733		#[rstest]
6734		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
6735		#[case::paritydb(BackendKind::ParityDb)]
6736		#[case::rocksdb(BackendKind::RocksDb)]
6737		fn release_on_missing_hash_is_noop(#[case] kind: BackendKind) {
6738			let factory = DbFactory::new(kind);
6739			let h = hash(0xA7);
6740			commit_release(&factory, h);
6741			assert!(get_value(&factory, h).is_none(), "release on missing key is a no-op");
6742			let bytes = b"a7-bytes".to_vec();
6743			commit_store(&factory, h, bytes.clone());
6744			assert_eq!(get_value(&factory, h).as_deref(), Some(bytes.as_slice()));
6745		}
6746
6747		struct BackendFactory {
6748			backend: Option<Backend<Block>>,
6749			kind: BackendKind,
6750			blocks_pruning: BlocksPruning,
6751			tmp_path: Option<PathBuf>,
6752			_tmp: Option<TempDir>,
6753		}
6754
6755		impl BackendFactory {
6756			fn new(kind: BackendKind, blocks_pruning: BlocksPruning) -> Self {
6757				match kind {
6758					BackendKind::KvdbMemdb => Self {
6759						backend: Some(Backend::new_test_with_tx_storage(blocks_pruning, 10)),
6760						kind,
6761						blocks_pruning,
6762						tmp_path: None,
6763						_tmp: None,
6764					},
6765					BackendKind::ParityDb => {
6766						let tmp = TempDir::new().unwrap();
6767						let tmp_path = tmp.path().to_path_buf();
6768						let backend = Backend::new_test_with_tx_storage_source(
6769							blocks_pruning,
6770							10,
6771							DatabaseSource::ParityDb { path: tmp_path.clone() },
6772							Default::default(),
6773						);
6774						Self {
6775							backend: Some(backend),
6776							kind,
6777							blocks_pruning,
6778							tmp_path: Some(tmp_path),
6779							_tmp: Some(tmp),
6780						}
6781					},
6782					BackendKind::RocksDb => {
6783						let tmp = TempDir::new().unwrap();
6784						let tmp_path = tmp.path().to_path_buf();
6785						let mut cfg = kvdb_rocksdb::DatabaseConfig::with_columns(NUM_COLUMNS);
6786						cfg.create_if_missing = true;
6787						let db = kvdb_rocksdb::Database::open(&cfg, &tmp_path)
6788							.expect("kvdb-rocksdb open succeeds in test");
6789						let db = sp_database::as_database(db);
6790						let backend = Backend::new_test_with_tx_storage_source(
6791							blocks_pruning,
6792							10,
6793							DatabaseSource::Custom { db, require_create_flag: true },
6794							Default::default(),
6795						);
6796						Self {
6797							backend: Some(backend),
6798							kind,
6799							blocks_pruning,
6800							tmp_path: Some(tmp_path),
6801							_tmp: Some(tmp),
6802						}
6803					},
6804				}
6805			}
6806
6807			fn backend(&self) -> &Backend<Block> {
6808				self.backend.as_ref().expect("backend present")
6809			}
6810
6811			// parity-db drains commit_overlay on `Drop`. Drop+reopen before
6812			// `is_none()` assertions to avoid flakes. No-op for memdb/rocksdb.
6813			fn refresh_for_assertion(&mut self) {
6814				if !matches!(self.kind, BackendKind::ParityDb) {
6815					return;
6816				}
6817				let path = self.tmp_path.clone().expect("paritydb has tmp_path");
6818				self.backend = None;
6819				self.backend = Some(Backend::new_test_with_tx_storage_source(
6820					self.blocks_pruning,
6821					10,
6822					DatabaseSource::ParityDb { path },
6823					Default::default(),
6824				));
6825			}
6826		}
6827
6828		#[rstest]
6829		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
6830		#[case::paritydb(BackendKind::ParityDb)]
6831		#[case::rocksdb(BackendKind::RocksDb)]
6832		fn prefetched_multi_renew_same_hash_balanced_lifecycle(#[case] kind: BackendKind) {
6833			let mut factory = BackendFactory::new(kind, BlocksPruning::Some(2));
6834			let payload = b"prefetched-blob".to_vec();
6835			let payload_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&payload);
6836			let payload_hash_arr: [u8; 32] = payload_hash.into();
6837
6838			let mut blocks = Vec::new();
6839			let block0 = insert_block_with_prefetched(
6840				factory.backend(),
6841				0,
6842				Default::default(),
6843				Default::default(),
6844				vec![UncheckedXt::new_transaction(0.into(), ())],
6845				Some(vec![
6846					IndexOperation::Renew { extrinsic: 0, hash: payload_hash_arr.into() },
6847					IndexOperation::Renew { extrinsic: 0, hash: payload_hash_arr.into() },
6848				]),
6849				HashMap::from([(payload_hash, payload.clone())]),
6850			)
6851			.unwrap();
6852			blocks.push(block0);
6853
6854			assert!(factory
6855				.backend()
6856				.blockchain()
6857				.indexed_transaction(payload_hash)
6858				.unwrap()
6859				.is_some());
6860
6861			let mut prev = block0;
6862			for i in 1..6u64 {
6863				prev = insert_block(
6864					factory.backend(),
6865					i,
6866					prev,
6867					None,
6868					Default::default(),
6869					vec![UncheckedXt::new_transaction(i.into(), ())],
6870					None,
6871				)
6872				.unwrap();
6873				blocks.push(prev);
6874			}
6875
6876			for i in 1..6 {
6877				let mut op = factory.backend().begin_operation().unwrap();
6878				factory.backend().begin_state_operation(&mut op, blocks[4]).unwrap();
6879				op.mark_finalized(blocks[i], None).unwrap();
6880				factory.backend().commit_operation(op).unwrap();
6881			}
6882
6883			factory.refresh_for_assertion();
6884			assert!(factory
6885				.backend()
6886				.blockchain()
6887				.indexed_transaction(payload_hash)
6888				.unwrap()
6889				.is_none());
6890		}
6891
6892		#[rstest]
6893		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
6894		#[case::paritydb(BackendKind::ParityDb)]
6895		#[case::rocksdb(BackendKind::RocksDb)]
6896		fn prefetched_single_renew_full_lifecycle(#[case] kind: BackendKind) {
6897			let mut factory = BackendFactory::new(kind, BlocksPruning::Some(2));
6898			let payload = b"prefetched-blob".to_vec();
6899			let payload_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&payload);
6900			let payload_hash_arr: [u8; 32] = payload_hash.into();
6901
6902			let mut blocks = Vec::new();
6903			let block0 = insert_block_with_prefetched(
6904				factory.backend(),
6905				0,
6906				Default::default(),
6907				Default::default(),
6908				vec![UncheckedXt::new_transaction(0.into(), ())],
6909				Some(vec![IndexOperation::Renew { extrinsic: 0, hash: payload_hash_arr.into() }]),
6910				HashMap::from([(payload_hash, payload.clone())]),
6911			)
6912			.unwrap();
6913			blocks.push(block0);
6914
6915			assert_eq!(
6916				factory
6917					.backend()
6918					.blockchain()
6919					.indexed_transaction(payload_hash)
6920					.unwrap()
6921					.as_deref(),
6922				Some(payload.as_slice()),
6923			);
6924
6925			let mut prev = block0;
6926			for i in 1..6u64 {
6927				prev = insert_block(
6928					factory.backend(),
6929					i,
6930					prev,
6931					None,
6932					Default::default(),
6933					vec![UncheckedXt::new_transaction(i.into(), ())],
6934					None,
6935				)
6936				.unwrap();
6937				blocks.push(prev);
6938			}
6939
6940			for i in 1..6 {
6941				let mut op = factory.backend().begin_operation().unwrap();
6942				factory.backend().begin_state_operation(&mut op, blocks[4]).unwrap();
6943				op.mark_finalized(blocks[i], None).unwrap();
6944				factory.backend().commit_operation(op).unwrap();
6945			}
6946
6947			factory.refresh_for_assertion();
6948			assert!(factory
6949				.backend()
6950				.blockchain()
6951				.indexed_transaction(payload_hash)
6952				.unwrap()
6953				.is_none());
6954		}
6955
6956		#[rstest]
6957		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
6958		#[case::paritydb(BackendKind::ParityDb)]
6959		#[case::rocksdb(BackendKind::RocksDb)]
6960		fn redundant_prefetch_on_local_data_balanced_lifecycle(#[case] kind: BackendKind) {
6961			let mut factory = BackendFactory::new(kind, BlocksPruning::Some(2));
6962			let payload_xt = UncheckedXt::new_transaction(5.into(), ()).encode();
6963			let payload = payload_xt[1..].to_vec();
6964			let payload_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&payload);
6965			let payload_hash_arr: [u8; 32] = payload_hash.into();
6966
6967			let mut blocks = Vec::new();
6968			let block0 = insert_block(
6969				factory.backend(),
6970				0,
6971				Default::default(),
6972				None,
6973				Default::default(),
6974				vec![UncheckedXt::new_transaction(5.into(), ())],
6975				Some(vec![IndexOperation::Insert {
6976					extrinsic: 0,
6977					hash: payload_hash_arr.into(),
6978					size: payload.len() as u32,
6979				}]),
6980			)
6981			.unwrap();
6982			blocks.push(block0);
6983
6984			let block1 = insert_block_with_prefetched(
6985				factory.backend(),
6986				1,
6987				block0,
6988				Default::default(),
6989				vec![UncheckedXt::new_transaction(99.into(), ())],
6990				Some(vec![IndexOperation::Renew { extrinsic: 0, hash: payload_hash_arr.into() }]),
6991				HashMap::from([(payload_hash, payload.clone())]),
6992			)
6993			.unwrap();
6994			blocks.push(block1);
6995
6996			assert!(factory
6997				.backend()
6998				.blockchain()
6999				.indexed_transaction(payload_hash)
7000				.unwrap()
7001				.is_some());
7002
7003			let mut prev = block1;
7004			for i in 2..7u64 {
7005				prev = insert_block(
7006					factory.backend(),
7007					i,
7008					prev,
7009					None,
7010					Default::default(),
7011					vec![UncheckedXt::new_transaction(i.into(), ())],
7012					None,
7013				)
7014				.unwrap();
7015				blocks.push(prev);
7016			}
7017
7018			for i in 1..7 {
7019				let mut op = factory.backend().begin_operation().unwrap();
7020				factory.backend().begin_state_operation(&mut op, blocks[5]).unwrap();
7021				op.mark_finalized(blocks[i], None).unwrap();
7022				factory.backend().commit_operation(op).unwrap();
7023			}
7024
7025			factory.refresh_for_assertion();
7026			assert!(
7027				factory
7028					.backend()
7029					.blockchain()
7030					.indexed_transaction(payload_hash)
7031					.unwrap()
7032					.is_none(),
7033				"redundant prefetch must not leak refcount through prune",
7034			);
7035		}
7036
7037		#[rstest]
7038		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
7039		#[case::paritydb(BackendKind::ParityDb)]
7040		#[case::rocksdb(BackendKind::RocksDb)]
7041		fn same_block_insert_and_renew_different_indices_with_prefetch(#[case] kind: BackendKind) {
7042			let mut factory = BackendFactory::new(kind, BlocksPruning::Some(2));
7043			let x_xt = UncheckedXt::new_transaction(0.into(), ()).encode();
7044			let x = x_xt[1..].to_vec();
7045			let x_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&x);
7046			let x_hash_arr: [u8; 32] = x_hash.into();
7047
7048			let mut blocks = Vec::new();
7049
7050			let block0 = insert_block(
7051				factory.backend(),
7052				0,
7053				Default::default(),
7054				None,
7055				Default::default(),
7056				vec![UncheckedXt::new_transaction(0.into(), ())],
7057				Some(vec![IndexOperation::Insert {
7058					extrinsic: 0,
7059					hash: x_hash_arr.into(),
7060					size: x.len() as u32,
7061				}]),
7062			)
7063			.unwrap();
7064			blocks.push(block0);
7065
7066			let block1 = insert_block_with_prefetched(
7067				factory.backend(),
7068				1,
7069				block0,
7070				Default::default(),
7071				vec![
7072					UncheckedXt::new_transaction(0.into(), ()),
7073					UncheckedXt::new_transaction(99.into(), ()),
7074				],
7075				Some(vec![
7076					IndexOperation::Insert {
7077						extrinsic: 0,
7078						hash: x_hash_arr.into(),
7079						size: x.len() as u32,
7080					},
7081					IndexOperation::Renew { extrinsic: 1, hash: x_hash_arr.into() },
7082				]),
7083				HashMap::from([(x_hash, x.clone())]),
7084			)
7085			.unwrap();
7086			blocks.push(block1);
7087
7088			assert!(factory.backend().blockchain().indexed_transaction(x_hash).unwrap().is_some());
7089
7090			let mut prev = block1;
7091			for i in 2..8u64 {
7092				prev = insert_block(
7093					factory.backend(),
7094					i,
7095					prev,
7096					None,
7097					Default::default(),
7098					vec![UncheckedXt::new_transaction(i.into(), ())],
7099					None,
7100				)
7101				.unwrap();
7102				blocks.push(prev);
7103			}
7104
7105			for i in 1..8 {
7106				let mut op = factory.backend().begin_operation().unwrap();
7107				factory.backend().begin_state_operation(&mut op, blocks[6]).unwrap();
7108				op.mark_finalized(blocks[i], None).unwrap();
7109				factory.backend().commit_operation(op).unwrap();
7110			}
7111
7112			factory.refresh_for_assertion();
7113			assert!(
7114				factory.backend().blockchain().indexed_transaction(x_hash).unwrap().is_none(),
7115				"same-block Insert+Renew with prefetch must balance refcount through prune",
7116			);
7117		}
7118
7119		#[rstest]
7120		#[case::kvdb_memdb(BackendKind::KvdbMemdb)]
7121		#[case::paritydb(BackendKind::ParityDb)]
7122		#[case::rocksdb(BackendKind::RocksDb)]
7123		fn sequential_renew_blocks_all_prefetched_eventually_pruned(#[case] kind: BackendKind) {
7124			let mut factory = BackendFactory::new(kind, BlocksPruning::Some(2));
7125			let payload = b"prefetched-blob".to_vec();
7126			let payload_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&payload);
7127			let payload_hash_arr: [u8; 32] = payload_hash.into();
7128
7129			let mut blocks = Vec::new();
7130			let mut prev = Default::default();
7131			for i in 0..4u64 {
7132				let block = insert_block_with_prefetched(
7133					factory.backend(),
7134					i,
7135					prev,
7136					Default::default(),
7137					vec![UncheckedXt::new_transaction(i.into(), ())],
7138					Some(vec![IndexOperation::Renew {
7139						extrinsic: 0,
7140						hash: payload_hash_arr.into(),
7141					}]),
7142					HashMap::from([(payload_hash, payload.clone())]),
7143				)
7144				.unwrap();
7145				blocks.push(block);
7146				prev = block;
7147			}
7148
7149			assert!(factory
7150				.backend()
7151				.blockchain()
7152				.indexed_transaction(payload_hash)
7153				.unwrap()
7154				.is_some());
7155
7156			for i in 4..10u64 {
7157				prev = insert_block(
7158					factory.backend(),
7159					i,
7160					prev,
7161					None,
7162					Default::default(),
7163					vec![UncheckedXt::new_transaction(i.into(), ())],
7164					None,
7165				)
7166				.unwrap();
7167				blocks.push(prev);
7168			}
7169
7170			for i in 1..10 {
7171				let mut op = factory.backend().begin_operation().unwrap();
7172				factory.backend().begin_state_operation(&mut op, blocks[8]).unwrap();
7173				op.mark_finalized(blocks[i], None).unwrap();
7174				factory.backend().commit_operation(op).unwrap();
7175			}
7176
7177			factory.refresh_for_assertion();
7178			assert!(
7179				factory
7180					.backend()
7181					.blockchain()
7182					.indexed_transaction(payload_hash)
7183					.unwrap()
7184					.is_none(),
7185				"sequential prefetched renews must all release through prune",
7186			);
7187		}
7188
7189		// Synthetic-ops precedence tests. kvdb-memdb only — backend-agnostic logic.
7190
7191		#[test]
7192		fn runtime_index_ops_win_over_synthetic() {
7193			let factory = BackendFactory::new(BackendKind::KvdbMemdb, BlocksPruning::KeepAll);
7194			let payload_xt = UncheckedXt::new_transaction(11.into(), ()).encode();
7195			let payload = payload_xt[1..].to_vec();
7196			let payload_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&payload);
7197			let payload_hash_arr: [u8; 32] = payload_hash.into();
7198
7199			let bogus_hash_arr = [0xAAu8; 32];
7200
7201			insert_block_with_synthetic_ops(
7202				factory.backend(),
7203				0,
7204				Default::default(),
7205				Default::default(),
7206				vec![UncheckedXt::new_transaction(11.into(), ())],
7207				vec![IndexOperation::Insert {
7208					extrinsic: 0,
7209					hash: payload_hash_arr.into(),
7210					size: payload.len() as u32,
7211				}],
7212				vec![IndexOperation::Insert {
7213					extrinsic: 0,
7214					hash: bogus_hash_arr.into(),
7215					size: payload.len() as u32,
7216				}],
7217				HashMap::new(),
7218			)
7219			.unwrap();
7220
7221			assert_eq!(
7222				factory
7223					.backend()
7224					.blockchain()
7225					.indexed_transaction(payload_hash)
7226					.unwrap()
7227					.as_deref(),
7228				Some(payload.as_slice()),
7229				"runtime ops win",
7230			);
7231			assert!(
7232				factory
7233					.backend()
7234					.blockchain()
7235					.indexed_transaction(bogus_hash_arr.into())
7236					.unwrap()
7237					.is_none(),
7238				"synthetic dropped",
7239			);
7240		}
7241
7242		#[test]
7243		fn empty_both_falls_back_to_plain_body() {
7244			let factory = BackendFactory::new(BackendKind::KvdbMemdb, BlocksPruning::KeepAll);
7245			let body = vec![UncheckedXt::new_transaction(42.into(), ())];
7246
7247			let block_hash = insert_block_with_synthetic_ops(
7248				factory.backend(),
7249				0,
7250				Default::default(),
7251				Default::default(),
7252				body.clone(),
7253				Vec::new(),
7254				Vec::new(),
7255				HashMap::new(),
7256			)
7257			.unwrap();
7258
7259			let stored_body = factory.backend().blockchain().body(block_hash).unwrap();
7260			assert_eq!(stored_body, Some(body));
7261		}
7262
7263		#[test]
7264		fn synthetic_renew_uses_prefetched_payload() {
7265			let factory = BackendFactory::new(BackendKind::KvdbMemdb, BlocksPruning::KeepAll);
7266			let payload = b"prefetched-blob".to_vec();
7267			let payload_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&payload);
7268			let payload_hash_arr: [u8; 32] = payload_hash.into();
7269
7270			insert_block_with_synthetic_ops(
7271				factory.backend(),
7272				0,
7273				Default::default(),
7274				Default::default(),
7275				vec![UncheckedXt::new_transaction(1.into(), ())],
7276				Vec::new(),
7277				vec![IndexOperation::Renew { extrinsic: 0, hash: payload_hash_arr.into() }],
7278				HashMap::from([(payload_hash, payload.clone())]),
7279			)
7280			.unwrap();
7281
7282			assert_eq!(
7283				factory
7284					.backend()
7285					.blockchain()
7286					.indexed_transaction(payload_hash)
7287					.unwrap()
7288					.as_deref(),
7289				Some(payload.as_slice()),
7290			);
7291		}
7292
7293		#[test]
7294		fn synthetic_renew_without_prefetched_references_existing() {
7295			let factory = BackendFactory::new(BackendKind::KvdbMemdb, BlocksPruning::KeepAll);
7296			let payload_xt = UncheckedXt::new_transaction(5.into(), ()).encode();
7297			let payload = payload_xt[1..].to_vec();
7298			let payload_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&payload);
7299			let payload_hash_arr: [u8; 32] = payload_hash.into();
7300
7301			let block0 = insert_block(
7302				factory.backend(),
7303				0,
7304				Default::default(),
7305				None,
7306				Default::default(),
7307				vec![UncheckedXt::new_transaction(5.into(), ())],
7308				Some(vec![IndexOperation::Insert {
7309					extrinsic: 0,
7310					hash: payload_hash_arr.into(),
7311					size: payload.len() as u32,
7312				}]),
7313			)
7314			.unwrap();
7315
7316			insert_block_with_synthetic_ops(
7317				factory.backend(),
7318				1,
7319				block0,
7320				Default::default(),
7321				vec![UncheckedXt::new_transaction(6.into(), ())],
7322				Vec::new(),
7323				vec![IndexOperation::Renew { extrinsic: 0, hash: payload_hash_arr.into() }],
7324				HashMap::new(),
7325			)
7326			.unwrap();
7327
7328			assert_eq!(
7329				factory
7330					.backend()
7331					.blockchain()
7332					.indexed_transaction(payload_hash)
7333					.unwrap()
7334					.as_deref(),
7335				Some(payload.as_slice()),
7336			);
7337		}
7338
7339		#[test]
7340		fn synthetic_insert_extracts_tail_from_body() {
7341			let factory = BackendFactory::new(BackendKind::KvdbMemdb, BlocksPruning::KeepAll);
7342			let payload_xt = UncheckedXt::new_transaction(13.into(), ()).encode();
7343			let tail_size = 4u32;
7344			let tail_start = payload_xt.len() - tail_size as usize;
7345			let expected_tail = payload_xt[tail_start..].to_vec();
7346			let tail_hash = <HashingFor<Block> as sp_core::Hasher>::hash(&expected_tail);
7347			let tail_hash_arr: [u8; 32] = tail_hash.into();
7348
7349			insert_block_with_synthetic_ops(
7350				factory.backend(),
7351				0,
7352				Default::default(),
7353				Default::default(),
7354				vec![UncheckedXt::new_transaction(13.into(), ())],
7355				Vec::new(),
7356				vec![IndexOperation::Insert {
7357					extrinsic: 0,
7358					hash: tail_hash_arr.into(),
7359					size: tail_size,
7360				}],
7361				HashMap::new(),
7362			)
7363			.unwrap();
7364
7365			assert_eq!(
7366				factory
7367					.backend()
7368					.blockchain()
7369					.indexed_transaction(tail_hash)
7370					.unwrap()
7371					.as_deref(),
7372				Some(expected_tail.as_slice()),
7373			);
7374		}
7375
7376		#[test]
7377		fn synthetic_insert_oversized_size_falls_back_to_full_extrinsic() {
7378			let factory = BackendFactory::new(BackendKind::KvdbMemdb, BlocksPruning::KeepAll);
7379			let payload_xt = UncheckedXt::new_transaction(17.into(), ()).encode();
7380			let bogus_hash_arr = [0xBBu8; 32];
7381			let oversized = (payload_xt.len() + 1) as u32;
7382
7383			let block_hash = insert_block_with_synthetic_ops(
7384				factory.backend(),
7385				0,
7386				Default::default(),
7387				Default::default(),
7388				vec![UncheckedXt::new_transaction(17.into(), ())],
7389				Vec::new(),
7390				vec![IndexOperation::Insert {
7391					extrinsic: 0,
7392					hash: bogus_hash_arr.into(),
7393					size: oversized,
7394				}],
7395				HashMap::new(),
7396			)
7397			.unwrap();
7398
7399			assert!(factory
7400				.backend()
7401				.blockchain()
7402				.indexed_transaction(bogus_hash_arr.into())
7403				.unwrap()
7404				.is_none());
7405			let stored_body = factory.backend().blockchain().body(block_hash).unwrap();
7406			assert_eq!(stored_body, Some(vec![UncheckedXt::new_transaction(17.into(), ())]));
7407		}
7408
7409		#[test]
7410		fn multiple_synthetic_ops_per_block_apply_in_order() {
7411			let factory = BackendFactory::new(BackendKind::KvdbMemdb, BlocksPruning::KeepAll);
7412			let xt_a = UncheckedXt::new_transaction(21.into(), ()).encode();
7413			let xt_b = UncheckedXt::new_transaction(22.into(), ()).encode();
7414			let payload_a = xt_a[1..].to_vec();
7415			let payload_b = xt_b[1..].to_vec();
7416			let hash_a = <HashingFor<Block> as sp_core::Hasher>::hash(&payload_a);
7417			let hash_b = <HashingFor<Block> as sp_core::Hasher>::hash(&payload_b);
7418			let hash_a_arr: [u8; 32] = hash_a.into();
7419			let hash_b_arr: [u8; 32] = hash_b.into();
7420
7421			insert_block_with_synthetic_ops(
7422				factory.backend(),
7423				0,
7424				Default::default(),
7425				Default::default(),
7426				vec![
7427					UncheckedXt::new_transaction(21.into(), ()),
7428					UncheckedXt::new_transaction(22.into(), ()),
7429				],
7430				Vec::new(),
7431				vec![
7432					IndexOperation::Insert {
7433						extrinsic: 0,
7434						hash: hash_a_arr.into(),
7435						size: payload_a.len() as u32,
7436					},
7437					IndexOperation::Insert {
7438						extrinsic: 1,
7439						hash: hash_b_arr.into(),
7440						size: payload_b.len() as u32,
7441					},
7442				],
7443				HashMap::new(),
7444			)
7445			.unwrap();
7446
7447			assert_eq!(
7448				factory.backend().blockchain().indexed_transaction(hash_a).unwrap().as_deref(),
7449				Some(payload_a.as_slice()),
7450				"first op",
7451			);
7452			assert_eq!(
7453				factory.backend().blockchain().indexed_transaction(hash_b).unwrap().as_deref(),
7454				Some(payload_b.as_slice()),
7455				"second op",
7456			);
7457		}
7458	}
7459}