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