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