referrerpolicy=no-referrer-when-downgrade

sc_transaction_pool/common/
enactment_state.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//! Substrate transaction pool implementation.
20
21use crate::LOG_TARGET;
22use sc_transaction_pool_api::ChainEvent;
23use sp_blockchain::TreeRoute;
24use sp_runtime::traits::{Block as BlockT, NumberFor, Saturating};
25use tracing::{debug, trace};
26
27/// The threshold since the last update where we will skip any maintenance for blocks.
28///
29/// This includes tracking re-orgs and sending out certain notifications. In general this shouldn't
30/// happen and may only happen when the node is doing a full sync.
31const SKIP_MAINTENANCE_THRESHOLD: u16 = 20;
32
33/// Helper struct for keeping track of the current state of processed new best
34/// block and finalized events. The main purpose of keeping track of this state
35/// is to figure out which phases (enactment / finalization) of transaction pool
36/// maintenance are needed.
37///
38/// Example: given the following chain:
39///
40///   B1-C1-D1-E1
41///  /
42/// A
43///  \
44///   B2-C2-D2-E2
45///
46/// the list presents scenarios and expected behavior for sequence of `NewBestBlock` (`nbb`)
47/// and `Finalized` (`f`) events. true/false means if enactiment is required:
48///
49/// - `nbb(C1)`, `f(C1)` -> false (enactment was already performed in `nbb(C1))`
50/// - `f(C1)`, `nbb(C1)` -> false (enactment was already performed in `f(C1))`
51/// - `f(C1)`, `nbb(D2)` -> false (enactment was already performed in `f(C1)`,
52/// we should not retract finalized block)
53/// - `f(C1)`, `f(C2)`, `nbb(C1)` -> false
54/// - `nbb(C1)`, `nbb(C2)` -> true (switching fork is OK)
55/// - `nbb(B1)`, `nbb(B2)` -> true
56/// - `nbb(B1)`, `nbb(C1)`, `f(C1)` -> false (enactment was already performed in `nbb(B1)`)
57/// - `nbb(C1)`, `f(B1)` -> false (enactment was already performed in `nbb(B2)`)
58pub struct EnactmentState<Block>
59where
60	Block: BlockT,
61{
62	recent_best_block: Block::Hash,
63	recent_finalized_block: Block::Hash,
64}
65
66/// Enactment action that should be performed after processing the `ChainEvent`
67#[derive(Debug)]
68pub enum EnactmentAction<Block: BlockT> {
69	/// Both phases of maintenance shall be skipped
70	Skip,
71	/// Both phases of maintenance shall be performed
72	HandleEnactment(TreeRoute<Block>),
73	/// Enactment phase of maintenance shall be skipped
74	HandleFinalization,
75}
76
77impl<Block> EnactmentState<Block>
78where
79	Block: BlockT,
80{
81	/// Returns a new `EnactmentState` initialized with the given parameters.
82	pub fn new(recent_best_block: Block::Hash, recent_finalized_block: Block::Hash) -> Self {
83		EnactmentState { recent_best_block, recent_finalized_block }
84	}
85
86	/// Returns the recently finalized block.
87	pub fn recent_finalized_block(&self) -> Block::Hash {
88		self.recent_finalized_block
89	}
90
91	/// Updates the state according to the given `ChainEvent`, returning
92	/// `Some(tree_route)` with a tree route including the blocks that need to
93	/// be enacted/retracted. If no enactment is needed then `None` is returned.
94	pub fn update<TreeRouteF, BlockNumberF>(
95		&mut self,
96		event: &ChainEvent<Block>,
97		tree_route: &TreeRouteF,
98		hash_to_number: &BlockNumberF,
99	) -> Result<EnactmentAction<Block>, String>
100	where
101		TreeRouteF: Fn(Block::Hash, Block::Hash) -> Result<TreeRoute<Block>, String>,
102		BlockNumberF: Fn(Block::Hash) -> Result<Option<NumberFor<Block>>, String>,
103	{
104		let new_hash = event.hash();
105		let finalized = event.is_finalized();
106
107		// do not proceed with txpool maintain if block distance is too high
108		let skip_maintenance =
109			match (hash_to_number(new_hash), hash_to_number(self.recent_best_block)) {
110				(Ok(Some(new)), Ok(Some(current))) =>
111					new.saturating_sub(current) > SKIP_MAINTENANCE_THRESHOLD.into(),
112				_ => true,
113			};
114
115		if skip_maintenance {
116			debug!(target: LOG_TARGET, "skip maintain: tree_route would be too long");
117			self.force_update(event);
118			return Ok(EnactmentAction::Skip)
119		}
120
121		// block was already finalized
122		if self.recent_finalized_block == new_hash {
123			trace!(target: LOG_TARGET, "handle_enactment: block already finalized");
124			return Ok(EnactmentAction::Skip)
125		}
126
127		// compute actual tree route from best_block to notified block, and use
128		// it instead of tree_route provided with event
129		let tree_route = tree_route(self.recent_best_block, new_hash)?;
130		trace!(
131			target: LOG_TARGET,
132			?new_hash,
133			?finalized,
134			common_block = ?tree_route.common_block(),
135			last_block = ?tree_route.last(),
136			best_block = ?self.recent_best_block,
137			finalized_block = ?self.recent_finalized_block,
138			"resolve hash"
139		);
140
141		// check if recently finalized block is on retracted path. this could be
142		// happening if we first received a finalization event and then a new
143		// best event for some old stale best head.
144		if tree_route.retracted().iter().any(|x| x.hash == self.recent_finalized_block) {
145			trace!(
146				target: LOG_TARGET,
147				recent_finalized_block = ?self.recent_finalized_block,
148				?new_hash,
149				"Recently finalized block would be retracted by ChainEvent, skipping"
150			);
151			return Ok(EnactmentAction::Skip)
152		}
153
154		if finalized {
155			self.recent_finalized_block = new_hash;
156
157			// if there are no enacted blocks in best_block -> hash tree_route,
158			// it means that block being finalized was already enacted (this
159			// case also covers best_block == new_hash), recent_best_block
160			// remains valid.
161			if tree_route.enacted().is_empty() {
162				trace!(target: LOG_TARGET, "handle_enactment: no newly enacted blocks since recent best block");
163				return Ok(EnactmentAction::HandleFinalization)
164			}
165
166			// otherwise enacted finalized block becomes best block...
167		}
168
169		self.recent_best_block = new_hash;
170
171		Ok(EnactmentAction::HandleEnactment(tree_route))
172	}
173
174	/// Forces update of the state according to the given `ChainEvent`. Intended to be used as a
175	/// fallback when tree_route cannot be computed.
176	pub fn force_update(&mut self, event: &ChainEvent<Block>) {
177		match event {
178			ChainEvent::NewBestBlock { hash, .. } => self.recent_best_block = *hash,
179			ChainEvent::Finalized { hash, .. } => self.recent_finalized_block = *hash,
180		};
181		trace!(
182			target: LOG_TARGET,
183			recent_best_block = ?self.recent_best_block,
184			recent_finalized_block = ?self.recent_finalized_block,
185			"forced update"
186		);
187	}
188}
189
190#[cfg(test)]
191mod enactment_state_tests {
192	use super::{EnactmentAction, EnactmentState};
193	use sc_transaction_pool_api::ChainEvent;
194	use sp_blockchain::{HashAndNumber, TreeRoute};
195	use sp_runtime::traits::NumberFor;
196	use std::sync::Arc;
197	use substrate_test_runtime_client::runtime::{Block, Hash};
198
199	// some helpers for convenient blocks' hash naming
200	fn a() -> HashAndNumber<Block> {
201		HashAndNumber { number: 1, hash: Hash::from([0xAA; 32]) }
202	}
203	fn b1() -> HashAndNumber<Block> {
204		HashAndNumber { number: 2, hash: Hash::from([0xB1; 32]) }
205	}
206	fn c1() -> HashAndNumber<Block> {
207		HashAndNumber { number: 3, hash: Hash::from([0xC1; 32]) }
208	}
209	fn d1() -> HashAndNumber<Block> {
210		HashAndNumber { number: 4, hash: Hash::from([0xD1; 32]) }
211	}
212	fn e1() -> HashAndNumber<Block> {
213		HashAndNumber { number: 5, hash: Hash::from([0xE1; 32]) }
214	}
215	fn x1() -> HashAndNumber<Block> {
216		HashAndNumber { number: 22, hash: Hash::from([0x1E; 32]) }
217	}
218	fn b2() -> HashAndNumber<Block> {
219		HashAndNumber { number: 2, hash: Hash::from([0xB2; 32]) }
220	}
221	fn c2() -> HashAndNumber<Block> {
222		HashAndNumber { number: 3, hash: Hash::from([0xC2; 32]) }
223	}
224	fn d2() -> HashAndNumber<Block> {
225		HashAndNumber { number: 4, hash: Hash::from([0xD2; 32]) }
226	}
227	fn e2() -> HashAndNumber<Block> {
228		HashAndNumber { number: 5, hash: Hash::from([0xE2; 32]) }
229	}
230	fn x2() -> HashAndNumber<Block> {
231		HashAndNumber { number: 22, hash: Hash::from([0x2E; 32]) }
232	}
233
234	fn test_chain() -> Vec<HashAndNumber<Block>> {
235		vec![x1(), e1(), d1(), c1(), b1(), a(), b2(), c2(), d2(), e2(), x2()]
236	}
237
238	fn block_hash_to_block_number(hash: Hash) -> Result<Option<NumberFor<Block>>, String> {
239		Ok(test_chain().iter().find(|x| x.hash == hash).map(|x| x.number))
240	}
241
242	/// mock tree_route computing function for simple two-forks chain
243	fn tree_route(from: Hash, to: Hash) -> Result<TreeRoute<Block>, String> {
244		let chain = test_chain();
245		let pivot = chain.iter().position(|x| x.number == a().number).unwrap();
246
247		let from = chain
248			.iter()
249			.position(|bn| bn.hash == from)
250			.ok_or("existing block should be given")?;
251		let to = chain
252			.iter()
253			.position(|bn| bn.hash == to)
254			.ok_or("existing block should be given")?;
255
256		//    B1-C1-D1-E1-..-X1
257		//   /
258		//  A
259		//   \
260		//    B2-C2-D2-E2-..-X2
261		//
262		//  [X1 E1 D1 C1 B1 A B2 C2 D2 E2 X2]
263
264		let vec: Vec<HashAndNumber<Block>> = if from < to {
265			chain.into_iter().skip(from).take(to - from + 1).collect()
266		} else {
267			chain.into_iter().skip(to).take(from - to + 1).rev().collect()
268		};
269
270		let pivot = if from <= pivot && to <= pivot {
271			if from < to {
272				to - from
273			} else {
274				0
275			}
276		} else if from >= pivot && to >= pivot {
277			if from < to {
278				0
279			} else {
280				from - to
281			}
282		} else {
283			if from < to {
284				pivot - from
285			} else {
286				from - pivot
287			}
288		};
289
290		TreeRoute::new(vec, pivot)
291	}
292
293	mod mock_tree_route_tests {
294		use super::*;
295
296		/// asserts that tree routes are equal
297		fn assert_tree_route_eq(
298			expected: Result<TreeRoute<Block>, String>,
299			result: Result<TreeRoute<Block>, String>,
300		) {
301			let expected = expected.unwrap();
302			let result = result.unwrap();
303			assert_eq!(result.common_block().hash, expected.common_block().hash);
304			assert_eq!(result.enacted().len(), expected.enacted().len());
305			assert_eq!(result.retracted().len(), expected.retracted().len());
306			assert!(result
307				.enacted()
308				.iter()
309				.zip(expected.enacted().iter())
310				.all(|(a, b)| a.hash == b.hash));
311			assert!(result
312				.retracted()
313				.iter()
314				.zip(expected.retracted().iter())
315				.all(|(a, b)| a.hash == b.hash));
316		}
317
318		// some tests for mock tree_route function
319
320		#[test]
321		fn tree_route_mock_test_01() {
322			let result = tree_route(b1().hash, a().hash);
323			let expected = TreeRoute::new(vec![b1(), a()], 1);
324			assert_tree_route_eq(result, expected);
325		}
326
327		#[test]
328		fn tree_route_mock_test_02() {
329			let result = tree_route(a().hash, b1().hash);
330			let expected = TreeRoute::new(vec![a(), b1()], 0);
331			assert_tree_route_eq(result, expected);
332		}
333
334		#[test]
335		fn tree_route_mock_test_03() {
336			let result = tree_route(a().hash, c2().hash);
337			let expected = TreeRoute::new(vec![a(), b2(), c2()], 0);
338			assert_tree_route_eq(result, expected);
339		}
340
341		#[test]
342		fn tree_route_mock_test_04() {
343			let result = tree_route(e2().hash, a().hash);
344			let expected = TreeRoute::new(vec![e2(), d2(), c2(), b2(), a()], 4);
345			assert_tree_route_eq(result, expected);
346		}
347
348		#[test]
349		fn tree_route_mock_test_05() {
350			let result = tree_route(d1().hash, b1().hash);
351			let expected = TreeRoute::new(vec![d1(), c1(), b1()], 2);
352			assert_tree_route_eq(result, expected);
353		}
354
355		#[test]
356		fn tree_route_mock_test_06() {
357			let result = tree_route(d2().hash, b2().hash);
358			let expected = TreeRoute::new(vec![d2(), c2(), b2()], 2);
359			assert_tree_route_eq(result, expected);
360		}
361
362		#[test]
363		fn tree_route_mock_test_07() {
364			let result = tree_route(b1().hash, d1().hash);
365			let expected = TreeRoute::new(vec![b1(), c1(), d1()], 0);
366			assert_tree_route_eq(result, expected);
367		}
368
369		#[test]
370		fn tree_route_mock_test_08() {
371			let result = tree_route(b2().hash, d2().hash);
372			let expected = TreeRoute::new(vec![b2(), c2(), d2()], 0);
373			assert_tree_route_eq(result, expected);
374		}
375
376		#[test]
377		fn tree_route_mock_test_09() {
378			let result = tree_route(e2().hash, e1().hash);
379			let expected =
380				TreeRoute::new(vec![e2(), d2(), c2(), b2(), a(), b1(), c1(), d1(), e1()], 4);
381			assert_tree_route_eq(result, expected);
382		}
383
384		#[test]
385		fn tree_route_mock_test_10() {
386			let result = tree_route(e1().hash, e2().hash);
387			let expected =
388				TreeRoute::new(vec![e1(), d1(), c1(), b1(), a(), b2(), c2(), d2(), e2()], 4);
389			assert_tree_route_eq(result, expected);
390		}
391		#[test]
392		fn tree_route_mock_test_11() {
393			let result = tree_route(b1().hash, c2().hash);
394			let expected = TreeRoute::new(vec![b1(), a(), b2(), c2()], 1);
395			assert_tree_route_eq(result, expected);
396		}
397
398		#[test]
399		fn tree_route_mock_test_12() {
400			let result = tree_route(d2().hash, b1().hash);
401			let expected = TreeRoute::new(vec![d2(), c2(), b2(), a(), b1()], 3);
402			assert_tree_route_eq(result, expected);
403		}
404
405		#[test]
406		fn tree_route_mock_test_13() {
407			let result = tree_route(c2().hash, e1().hash);
408			let expected = TreeRoute::new(vec![c2(), b2(), a(), b1(), c1(), d1(), e1()], 2);
409			assert_tree_route_eq(result, expected);
410		}
411
412		#[test]
413		fn tree_route_mock_test_14() {
414			let result = tree_route(b1().hash, b1().hash);
415			let expected = TreeRoute::new(vec![b1()], 0);
416			assert_tree_route_eq(result, expected);
417		}
418
419		#[test]
420		fn tree_route_mock_test_15() {
421			let result = tree_route(b2().hash, b2().hash);
422			let expected = TreeRoute::new(vec![b2()], 0);
423			assert_tree_route_eq(result, expected);
424		}
425
426		#[test]
427		fn tree_route_mock_test_16() {
428			let result = tree_route(a().hash, a().hash);
429			let expected = TreeRoute::new(vec![a()], 0);
430			assert_tree_route_eq(result, expected);
431		}
432
433		#[test]
434		fn tree_route_mock_test_17() {
435			let result = tree_route(x2().hash, b1().hash);
436			let expected = TreeRoute::new(vec![x2(), e2(), d2(), c2(), b2(), a(), b1()], 5);
437			assert_tree_route_eq(result, expected);
438		}
439	}
440
441	fn trigger_new_best_block(
442		state: &mut EnactmentState<Block>,
443		from: HashAndNumber<Block>,
444		acted_on: HashAndNumber<Block>,
445	) -> EnactmentAction<Block> {
446		let (from, acted_on) = (from.hash, acted_on.hash);
447
448		let event_tree_route = tree_route(from, acted_on).expect("Tree route exists");
449
450		state
451			.update(
452				&ChainEvent::NewBestBlock {
453					hash: acted_on,
454					tree_route: Some(Arc::new(event_tree_route)),
455				},
456				&tree_route,
457				&block_hash_to_block_number,
458			)
459			.unwrap()
460	}
461
462	fn trigger_finalized(
463		state: &mut EnactmentState<Block>,
464		from: HashAndNumber<Block>,
465		acted_on: HashAndNumber<Block>,
466	) -> EnactmentAction<Block> {
467		let (from, acted_on) = (from.hash, acted_on.hash);
468
469		let v = tree_route(from, acted_on)
470			.expect("Tree route exists")
471			.enacted()
472			.iter()
473			.map(|h| h.hash)
474			.collect::<Vec<_>>();
475
476		state
477			.update(
478				&ChainEvent::Finalized { hash: acted_on, tree_route: v.into() },
479				&tree_route,
480				&block_hash_to_block_number,
481			)
482			.unwrap()
483	}
484
485	fn assert_es_eq(
486		es: &EnactmentState<Block>,
487		expected_best_block: HashAndNumber<Block>,
488		expected_finalized_block: HashAndNumber<Block>,
489	) {
490		assert_eq!(es.recent_best_block, expected_best_block.hash);
491		assert_eq!(es.recent_finalized_block, expected_finalized_block.hash);
492	}
493
494	#[test]
495	fn test_enactment_helper() {
496		sp_tracing::try_init_simple();
497		let mut es = EnactmentState::new(a().hash, a().hash);
498
499		//   B1-C1-D1-E1
500		//  /
501		// A
502		//  \
503		//   B2-C2-D2-E2
504
505		let result = trigger_new_best_block(&mut es, a(), d1());
506		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
507		assert_es_eq(&es, d1(), a());
508
509		let result = trigger_new_best_block(&mut es, d1(), e1());
510		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
511		assert_es_eq(&es, e1(), a());
512
513		let result = trigger_finalized(&mut es, a(), d2());
514		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
515		assert_es_eq(&es, d2(), d2());
516
517		let result = trigger_new_best_block(&mut es, d2(), e1());
518		assert!(matches!(result, EnactmentAction::Skip));
519		assert_es_eq(&es, d2(), d2());
520
521		let result = trigger_finalized(&mut es, a(), b2());
522		assert!(matches!(result, EnactmentAction::Skip));
523		assert_es_eq(&es, d2(), d2());
524
525		let result = trigger_finalized(&mut es, a(), b1());
526		assert!(matches!(result, EnactmentAction::Skip));
527		assert_es_eq(&es, d2(), d2());
528
529		let result = trigger_new_best_block(&mut es, a(), d2());
530		assert!(matches!(result, EnactmentAction::Skip));
531		assert_es_eq(&es, d2(), d2());
532
533		let result = trigger_finalized(&mut es, a(), d2());
534		assert!(matches!(result, EnactmentAction::Skip));
535		assert_es_eq(&es, d2(), d2());
536
537		let result = trigger_new_best_block(&mut es, a(), c2());
538		assert!(matches!(result, EnactmentAction::Skip));
539		assert_es_eq(&es, d2(), d2());
540
541		let result = trigger_new_best_block(&mut es, a(), c1());
542		assert!(matches!(result, EnactmentAction::Skip));
543		assert_es_eq(&es, d2(), d2());
544
545		let result = trigger_new_best_block(&mut es, d2(), e2());
546		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
547		assert_es_eq(&es, e2(), d2());
548
549		let result = trigger_finalized(&mut es, d2(), e2());
550		assert!(matches!(result, EnactmentAction::HandleFinalization));
551		assert_es_eq(&es, e2(), e2());
552	}
553
554	#[test]
555	fn test_enactment_helper_2() {
556		sp_tracing::try_init_simple();
557		let mut es = EnactmentState::new(a().hash, a().hash);
558
559		// A-B1-C1-D1-E1
560
561		let result = trigger_new_best_block(&mut es, a(), b1());
562		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
563		assert_es_eq(&es, b1(), a());
564
565		let result = trigger_new_best_block(&mut es, b1(), c1());
566		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
567		assert_es_eq(&es, c1(), a());
568
569		let result = trigger_new_best_block(&mut es, c1(), d1());
570		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
571		assert_es_eq(&es, d1(), a());
572
573		let result = trigger_new_best_block(&mut es, d1(), e1());
574		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
575		assert_es_eq(&es, e1(), a());
576
577		let result = trigger_finalized(&mut es, a(), c1());
578		assert!(matches!(result, EnactmentAction::HandleFinalization));
579		assert_es_eq(&es, e1(), c1());
580
581		let result = trigger_finalized(&mut es, c1(), e1());
582		assert!(matches!(result, EnactmentAction::HandleFinalization));
583		assert_es_eq(&es, e1(), e1());
584	}
585
586	#[test]
587	fn test_enactment_helper_3() {
588		sp_tracing::try_init_simple();
589		let mut es = EnactmentState::new(a().hash, a().hash);
590
591		// A-B1-C1-D1-E1
592
593		let result = trigger_new_best_block(&mut es, a(), e1());
594		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
595		assert_es_eq(&es, e1(), a());
596
597		let result = trigger_finalized(&mut es, a(), b1());
598		assert!(matches!(result, EnactmentAction::HandleFinalization));
599		assert_es_eq(&es, e1(), b1());
600	}
601
602	#[test]
603	fn test_enactment_helper_4() {
604		sp_tracing::try_init_simple();
605		let mut es = EnactmentState::new(a().hash, a().hash);
606
607		// A-B1-C1-D1-E1
608
609		let result = trigger_finalized(&mut es, a(), e1());
610		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
611		assert_es_eq(&es, e1(), e1());
612
613		let result = trigger_finalized(&mut es, e1(), b1());
614		assert!(matches!(result, EnactmentAction::Skip));
615		assert_es_eq(&es, e1(), e1());
616	}
617
618	#[test]
619	fn test_enactment_helper_5() {
620		sp_tracing::try_init_simple();
621		let mut es = EnactmentState::new(a().hash, a().hash);
622
623		//   B1-C1-D1-E1
624		//  /
625		// A
626		//  \
627		//   B2-C2-D2-E2
628
629		let result = trigger_finalized(&mut es, a(), e1());
630		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
631		assert_es_eq(&es, e1(), e1());
632
633		let result = trigger_finalized(&mut es, e1(), e2());
634		assert!(matches!(result, EnactmentAction::Skip));
635		assert_es_eq(&es, e1(), e1());
636	}
637
638	#[test]
639	fn test_enactment_helper_6() {
640		sp_tracing::try_init_simple();
641		let mut es = EnactmentState::new(a().hash, a().hash);
642
643		// A-B1-C1-D1-E1
644
645		let result = trigger_new_best_block(&mut es, a(), b1());
646		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
647		assert_es_eq(&es, b1(), a());
648
649		let result = trigger_finalized(&mut es, a(), d1());
650		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
651		assert_es_eq(&es, d1(), d1());
652
653		let result = trigger_new_best_block(&mut es, a(), e1());
654		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
655		assert_es_eq(&es, e1(), d1());
656
657		let result = trigger_new_best_block(&mut es, a(), c1());
658		assert!(matches!(result, EnactmentAction::Skip));
659		assert_es_eq(&es, e1(), d1());
660	}
661
662	#[test]
663	fn test_enactment_forced_update_best_block() {
664		sp_tracing::try_init_simple();
665		let mut es = EnactmentState::new(a().hash, a().hash);
666
667		es.force_update(&ChainEvent::NewBestBlock { hash: b1().hash, tree_route: None });
668		assert_es_eq(&es, b1(), a());
669	}
670
671	#[test]
672	fn test_enactment_forced_update_finalize() {
673		sp_tracing::try_init_simple();
674		let mut es = EnactmentState::new(a().hash, a().hash);
675
676		es.force_update(&ChainEvent::Finalized { hash: b1().hash, tree_route: Arc::from([]) });
677		assert_es_eq(&es, a(), b1());
678	}
679
680	#[test]
681	fn test_enactment_skip_long_enacted_path() {
682		sp_tracing::try_init_simple();
683		let mut es = EnactmentState::new(a().hash, a().hash);
684
685		// A-B1-C1-..-X1
686		let result = trigger_new_best_block(&mut es, a(), x1());
687		assert!(matches!(result, EnactmentAction::Skip));
688		assert_es_eq(&es, x1(), a());
689	}
690
691	#[test]
692	fn test_enactment_proceed_with_enacted_path_at_threshold() {
693		sp_tracing::try_init_simple();
694		let mut es = EnactmentState::new(b1().hash, b1().hash);
695
696		// A-B1-C1-..-X1
697		let result = trigger_new_best_block(&mut es, b1(), x1());
698		assert!(matches!(result, EnactmentAction::HandleEnactment { .. }));
699		assert_es_eq(&es, x1(), b1());
700	}
701}