referrerpolicy=no-referrer-when-downgrade

sc_client_db/
lib.rs

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