1use std::fmt::Debug;
22
23use codec::{Decode, Encode};
24use finality_grandpa::round::State as RoundState;
25use log::{info, warn};
26
27use fork_tree::ForkTree;
28use sc_client_api::backend::AuxStore;
29use sp_blockchain::{Error as ClientError, Result as ClientResult};
30use sp_consensus_grandpa::{AuthorityList, RoundNumber, SetId};
31use sp_runtime::traits::{Block as BlockT, NumberFor};
32
33use crate::{
34 authorities::{
35 AuthoritySet, AuthoritySetChanges, DelayKind, PendingChange, SharedAuthoritySet,
36 },
37 environment::{
38 CompletedRound, CompletedRounds, CurrentRounds, HasVoted, SharedVoterSetState,
39 VoterSetState,
40 },
41 GrandpaJustification, NewAuthoritySet, LOG_TARGET,
42};
43
44const VERSION_KEY: &[u8] = b"grandpa_schema_version";
45const SET_STATE_KEY: &[u8] = b"grandpa_completed_round";
46const CONCLUDED_ROUNDS: &[u8] = b"grandpa_concluded_rounds";
47const AUTHORITY_SET_KEY: &[u8] = b"grandpa_voters";
48const BEST_JUSTIFICATION: &[u8] = b"grandpa_best_justification";
49
50const CURRENT_VERSION: u32 = 3;
51
52#[derive(Debug, Clone, Encode, Decode)]
54#[cfg_attr(test, derive(PartialEq))]
55pub enum V1VoterSetState<H, N> {
56 Paused(RoundNumber, RoundState<H, N>),
58 Live(RoundNumber, RoundState<H, N>),
60}
61
62type V0VoterSetState<H, N> = (RoundNumber, RoundState<H, N>);
63
64#[derive(Debug, Clone, Encode, Decode, PartialEq)]
65struct V0PendingChange<H, N> {
66 next_authorities: AuthorityList,
67 delay: N,
68 canon_height: N,
69 canon_hash: H,
70}
71
72#[derive(Debug, Clone, Encode, Decode, PartialEq)]
73struct V0AuthoritySet<H, N> {
74 current_authorities: AuthorityList,
75 set_id: SetId,
76 pending_changes: Vec<V0PendingChange<H, N>>,
77}
78
79impl<H, N> Into<AuthoritySet<H, N>> for V0AuthoritySet<H, N>
80where
81 H: Clone + Debug + PartialEq,
82 N: Clone + Debug + Ord,
83{
84 fn into(self) -> AuthoritySet<H, N> {
85 let mut pending_standard_changes = ForkTree::new();
86
87 for old_change in self.pending_changes {
88 let new_change = PendingChange {
89 next_authorities: old_change.next_authorities,
90 delay: old_change.delay,
91 canon_height: old_change.canon_height,
92 canon_hash: old_change.canon_hash,
93 delay_kind: DelayKind::Finalized,
94 };
95
96 if let Err(err) = pending_standard_changes.import::<_, ClientError>(
97 new_change.canon_hash.clone(),
98 new_change.canon_height.clone(),
99 new_change,
100 &|_, _| Ok(false),
102 ) {
103 warn!(target: LOG_TARGET, "Error migrating pending authority set change: {}", err);
104 warn!(target: LOG_TARGET, "Node is in a potentially inconsistent state.");
105 }
106 }
107
108 let authority_set = AuthoritySet::new(
109 self.current_authorities,
110 self.set_id,
111 pending_standard_changes,
112 Vec::new(),
113 AuthoritySetChanges::empty(),
114 );
115
116 authority_set.expect("current_authorities is non-empty and weights are non-zero; qed.")
117 }
118}
119
120impl<H, N> Into<AuthoritySet<H, N>> for V2AuthoritySet<H, N>
121where
122 H: Clone + Debug + PartialEq,
123 N: Clone + Debug + Ord,
124{
125 fn into(self) -> AuthoritySet<H, N> {
126 AuthoritySet::new(
127 self.current_authorities,
128 self.set_id,
129 self.pending_standard_changes,
130 self.pending_forced_changes,
131 AuthoritySetChanges::empty(),
132 )
133 .expect("current_authorities is non-empty and weights are non-zero; qed.")
134 }
135}
136
137#[derive(Debug, Clone, Encode, Decode, PartialEq)]
138struct V2AuthoritySet<H, N> {
139 current_authorities: AuthorityList,
140 set_id: u64,
141 pending_standard_changes: ForkTree<H, N, PendingChange<H, N>>,
142 pending_forced_changes: Vec<PendingChange<H, N>>,
143}
144
145pub(crate) fn load_decode<B: AuxStore, T: Decode>(
146 backend: &B,
147 key: &[u8],
148) -> ClientResult<Option<T>> {
149 match backend.get_aux(key)? {
150 None => Ok(None),
151 Some(t) => T::decode(&mut &t[..])
152 .map_err(|e| ClientError::Backend(format!("GRANDPA DB is corrupted: {}", e)))
153 .map(Some),
154 }
155}
156
157pub(crate) struct PersistentData<Block: BlockT> {
159 pub(crate) authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
160 pub(crate) set_state: SharedVoterSetState<Block>,
161}
162
163fn migrate_from_version0<Block: BlockT, B, G>(
164 backend: &B,
165 genesis_round: &G,
166) -> ClientResult<Option<(AuthoritySet<Block::Hash, NumberFor<Block>>, VoterSetState<Block>)>>
167where
168 B: AuxStore,
169 G: Fn() -> RoundState<Block::Hash, NumberFor<Block>>,
170{
171 CURRENT_VERSION.using_encoded(|s| backend.insert_aux(&[(VERSION_KEY, s)], &[]))?;
172
173 if let Some(old_set) =
174 load_decode::<_, V0AuthoritySet<Block::Hash, NumberFor<Block>>>(backend, AUTHORITY_SET_KEY)?
175 {
176 let new_set: AuthoritySet<Block::Hash, NumberFor<Block>> = old_set.into();
177 backend.insert_aux(&[(AUTHORITY_SET_KEY, new_set.encode().as_slice())], &[])?;
178
179 let (last_round_number, last_round_state) = match load_decode::<
180 _,
181 V0VoterSetState<Block::Hash, NumberFor<Block>>,
182 >(backend, SET_STATE_KEY)?
183 {
184 Some((number, state)) => (number, state),
185 None => (0, genesis_round()),
186 };
187
188 let set_id = new_set.set_id;
189
190 let base = last_round_state.prevote_ghost.expect(
191 "state is for completed round; completed rounds must have a prevote ghost; qed.",
192 );
193
194 let mut current_rounds = CurrentRounds::<Block>::new();
195 current_rounds.insert(last_round_number + 1, HasVoted::No);
196
197 let set_state = VoterSetState::Live {
198 completed_rounds: CompletedRounds::new(
199 CompletedRound {
200 number: last_round_number,
201 state: last_round_state,
202 votes: Vec::new(),
203 base,
204 },
205 set_id,
206 &new_set,
207 ),
208 current_rounds,
209 };
210
211 backend.insert_aux(&[(SET_STATE_KEY, set_state.encode().as_slice())], &[])?;
212
213 return Ok(Some((new_set, set_state)));
214 }
215
216 Ok(None)
217}
218
219fn migrate_from_version1<Block: BlockT, B, G>(
220 backend: &B,
221 genesis_round: &G,
222) -> ClientResult<Option<(AuthoritySet<Block::Hash, NumberFor<Block>>, VoterSetState<Block>)>>
223where
224 B: AuxStore,
225 G: Fn() -> RoundState<Block::Hash, NumberFor<Block>>,
226{
227 CURRENT_VERSION.using_encoded(|s| backend.insert_aux(&[(VERSION_KEY, s)], &[]))?;
228
229 if let Some(set) =
230 load_decode::<_, AuthoritySet<Block::Hash, NumberFor<Block>>>(backend, AUTHORITY_SET_KEY)?
231 {
232 let set_id = set.set_id;
233
234 let completed_rounds = |number, state, base| {
235 CompletedRounds::new(
236 CompletedRound { number, state, votes: Vec::new(), base },
237 set_id,
238 &set,
239 )
240 };
241
242 let set_state = match load_decode::<_, V1VoterSetState<Block::Hash, NumberFor<Block>>>(
243 backend,
244 SET_STATE_KEY,
245 )? {
246 Some(V1VoterSetState::Paused(last_round_number, set_state)) => {
247 let base = set_state.prevote_ghost
248 .expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
249
250 VoterSetState::Paused {
251 completed_rounds: completed_rounds(last_round_number, set_state, base),
252 }
253 },
254 Some(V1VoterSetState::Live(last_round_number, set_state)) => {
255 let base = set_state.prevote_ghost
256 .expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
257
258 let mut current_rounds = CurrentRounds::<Block>::new();
259 current_rounds.insert(last_round_number + 1, HasVoted::No);
260
261 VoterSetState::Live {
262 completed_rounds: completed_rounds(last_round_number, set_state, base),
263 current_rounds,
264 }
265 },
266 None => {
267 let set_state = genesis_round();
268 let base = set_state.prevote_ghost
269 .expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
270
271 VoterSetState::live(set_id, &set, base)
272 },
273 };
274
275 backend.insert_aux(&[(SET_STATE_KEY, set_state.encode().as_slice())], &[])?;
276
277 return Ok(Some((set, set_state)));
278 }
279
280 Ok(None)
281}
282
283fn migrate_from_version2<Block: BlockT, B, G>(
284 backend: &B,
285 genesis_round: &G,
286) -> ClientResult<Option<(AuthoritySet<Block::Hash, NumberFor<Block>>, VoterSetState<Block>)>>
287where
288 B: AuxStore,
289 G: Fn() -> RoundState<Block::Hash, NumberFor<Block>>,
290{
291 CURRENT_VERSION.using_encoded(|s| backend.insert_aux(&[(VERSION_KEY, s)], &[]))?;
292
293 if let Some(old_set) =
294 load_decode::<_, V2AuthoritySet<Block::Hash, NumberFor<Block>>>(backend, AUTHORITY_SET_KEY)?
295 {
296 let new_set: AuthoritySet<Block::Hash, NumberFor<Block>> = old_set.into();
297 backend.insert_aux(&[(AUTHORITY_SET_KEY, new_set.encode().as_slice())], &[])?;
298
299 let set_state = match load_decode::<_, VoterSetState<Block>>(backend, SET_STATE_KEY)? {
300 Some(state) => state,
301 None => {
302 let state = genesis_round();
303 let base = state.prevote_ghost
304 .expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
305
306 VoterSetState::live(new_set.set_id, &new_set, base)
307 },
308 };
309
310 return Ok(Some((new_set, set_state)));
311 }
312
313 Ok(None)
314}
315
316pub(crate) fn load_persistent<Block: BlockT, B, G>(
318 backend: &B,
319 genesis_hash: Block::Hash,
320 genesis_number: NumberFor<Block>,
321 genesis_authorities: G,
322) -> ClientResult<PersistentData<Block>>
323where
324 B: AuxStore,
325 G: FnOnce() -> ClientResult<AuthorityList>,
326{
327 let version: Option<u32> = load_decode(backend, VERSION_KEY)?;
328
329 let make_genesis_round = move || RoundState::genesis((genesis_hash, genesis_number));
330
331 match version {
332 None => {
333 if let Some((new_set, set_state)) =
334 migrate_from_version0::<Block, _, _>(backend, &make_genesis_round)?
335 {
336 return Ok(PersistentData {
337 authority_set: new_set.into(),
338 set_state: set_state.into(),
339 });
340 }
341 },
342 Some(1) => {
343 if let Some((new_set, set_state)) =
344 migrate_from_version1::<Block, _, _>(backend, &make_genesis_round)?
345 {
346 return Ok(PersistentData {
347 authority_set: new_set.into(),
348 set_state: set_state.into(),
349 });
350 }
351 },
352 Some(2) => {
353 if let Some((new_set, set_state)) =
354 migrate_from_version2::<Block, _, _>(backend, &make_genesis_round)?
355 {
356 return Ok(PersistentData {
357 authority_set: new_set.into(),
358 set_state: set_state.into(),
359 });
360 }
361 },
362 Some(3) => {
363 if let Some(set) = load_decode::<_, AuthoritySet<Block::Hash, NumberFor<Block>>>(
364 backend,
365 AUTHORITY_SET_KEY,
366 )? {
367 let set_state =
368 match load_decode::<_, VoterSetState<Block>>(backend, SET_STATE_KEY)? {
369 Some(state) => state,
370 None => {
371 let state = make_genesis_round();
372 let base = state.prevote_ghost
373 .expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
374
375 VoterSetState::live(set.set_id, &set, base)
376 },
377 };
378
379 return Ok(PersistentData {
380 authority_set: set.into(),
381 set_state: set_state.into(),
382 });
383 }
384 },
385 Some(other) => {
386 return Err(ClientError::Backend(format!(
387 "Unsupported GRANDPA DB version: {:?}",
388 other
389 )))
390 },
391 }
392
393 info!(
395 target: LOG_TARGET,
396 "👴 Loading GRANDPA authority set \
397 from genesis on what appears to be first startup."
398 );
399
400 let genesis_authorities = genesis_authorities()?;
401 let genesis_set = AuthoritySet::genesis(genesis_authorities)
402 .expect("genesis authorities is non-empty; all weights are non-zero; qed.");
403 let state = make_genesis_round();
404 let base = state
405 .prevote_ghost
406 .expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
407
408 let genesis_state = VoterSetState::live(0, &genesis_set, base);
409
410 backend.insert_aux(
411 &[
412 (AUTHORITY_SET_KEY, genesis_set.encode().as_slice()),
413 (SET_STATE_KEY, genesis_state.encode().as_slice()),
414 ],
415 &[],
416 )?;
417
418 Ok(PersistentData { authority_set: genesis_set.into(), set_state: genesis_state.into() })
419}
420
421pub(crate) fn update_authority_set<Block: BlockT, F, R>(
427 set: &AuthoritySet<Block::Hash, NumberFor<Block>>,
428 new_set: Option<&NewAuthoritySet<Block::Hash, NumberFor<Block>>>,
429 write_aux: F,
430) -> R
431where
432 F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
433{
434 let encoded_set = set.encode();
436
437 if let Some(new_set) = new_set {
438 let set_state = VoterSetState::<Block>::live(
442 new_set.set_id,
443 set,
444 (new_set.canon_hash, new_set.canon_number),
445 );
446 let encoded = set_state.encode();
447
448 write_aux(&[(AUTHORITY_SET_KEY, &encoded_set[..]), (SET_STATE_KEY, &encoded[..])])
449 } else {
450 write_aux(&[(AUTHORITY_SET_KEY, &encoded_set[..])])
451 }
452}
453
454pub(crate) fn update_best_justification<Block: BlockT, F, R>(
460 justification: &GrandpaJustification<Block>,
461 write_aux: F,
462) -> R
463where
464 F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
465{
466 let encoded_justification = justification.encode();
467 write_aux(&[(BEST_JUSTIFICATION, &encoded_justification[..])])
468}
469
470pub fn best_justification<B, Block>(
472 backend: &B,
473) -> ClientResult<Option<GrandpaJustification<Block>>>
474where
475 B: AuxStore,
476 Block: BlockT,
477{
478 load_decode::<_, GrandpaJustification<Block>>(backend, BEST_JUSTIFICATION)
479}
480
481pub(crate) fn write_voter_set_state<Block: BlockT, B: AuxStore>(
483 backend: &B,
484 state: &VoterSetState<Block>,
485) -> ClientResult<()> {
486 backend.insert_aux(&[(SET_STATE_KEY, state.encode().as_slice())], &[])
487}
488
489pub(crate) fn write_concluded_round<Block: BlockT, B: AuxStore>(
491 backend: &B,
492 round_data: &CompletedRound<Block>,
493) -> ClientResult<()> {
494 let mut key = CONCLUDED_ROUNDS.to_vec();
495 let round_number = round_data.number;
496 round_number.using_encoded(|n| key.extend(n));
497
498 backend.insert_aux(&[(&key[..], round_data.encode().as_slice())], &[])
499}
500
501#[cfg(test)]
502pub(crate) fn load_authorities<B: AuxStore, H: Decode, N: Decode + Clone + Ord>(
503 backend: &B,
504) -> Option<AuthoritySet<H, N>> {
505 load_decode::<_, AuthoritySet<H, N>>(backend, AUTHORITY_SET_KEY).expect("backend error")
506}
507
508#[cfg(test)]
509mod test {
510 use super::*;
511 use sp_consensus_grandpa::AuthorityId;
512 use sp_core::{crypto::UncheckedFrom, H256};
513 use substrate_test_runtime_client::{self, runtime::Block};
514
515 fn dummy_id() -> AuthorityId {
516 AuthorityId::unchecked_from([1; 32])
517 }
518
519 #[test]
520 fn load_decode_from_v0_migrates_data_format() {
521 let client = substrate_test_runtime_client::new();
522
523 let authorities = vec![(dummy_id(), 100)];
524 let set_id = 3;
525 let round_number: RoundNumber = 42;
526 let round_state = RoundState::<H256, u64> {
527 prevote_ghost: Some((H256::random(), 32)),
528 finalized: None,
529 estimate: None,
530 completable: false,
531 };
532
533 {
534 let authority_set = V0AuthoritySet::<H256, u64> {
535 current_authorities: authorities.clone(),
536 pending_changes: Vec::new(),
537 set_id,
538 };
539
540 let voter_set_state = (round_number, round_state.clone());
541
542 client
543 .insert_aux(
544 &[
545 (AUTHORITY_SET_KEY, authority_set.encode().as_slice()),
546 (SET_STATE_KEY, voter_set_state.encode().as_slice()),
547 ],
548 &[],
549 )
550 .unwrap();
551 }
552
553 assert_eq!(load_decode::<_, u32>(&client, VERSION_KEY).unwrap(), None);
554
555 load_persistent::<substrate_test_runtime_client::runtime::Block, _, _>(
557 &client,
558 H256::random(),
559 0,
560 || unreachable!(),
561 )
562 .unwrap();
563
564 assert_eq!(load_decode::<_, u32>(&client, VERSION_KEY).unwrap(), Some(3));
565
566 let PersistentData { authority_set, set_state, .. } =
567 load_persistent::<substrate_test_runtime_client::runtime::Block, _, _>(
568 &client,
569 H256::random(),
570 0,
571 || unreachable!(),
572 )
573 .unwrap();
574
575 assert_eq!(
576 *authority_set.inner(),
577 AuthoritySet::new(
578 authorities.clone(),
579 set_id,
580 ForkTree::new(),
581 Vec::new(),
582 AuthoritySetChanges::empty(),
583 )
584 .unwrap(),
585 );
586
587 let mut current_rounds = CurrentRounds::<Block>::new();
588 current_rounds.insert(round_number + 1, HasVoted::No);
589
590 assert_eq!(
591 &*set_state.read(),
592 &VoterSetState::Live {
593 completed_rounds: CompletedRounds::new(
594 CompletedRound {
595 number: round_number,
596 state: round_state.clone(),
597 base: round_state.prevote_ghost.unwrap(),
598 votes: vec![],
599 },
600 set_id,
601 &*authority_set.inner(),
602 ),
603 current_rounds,
604 },
605 );
606 }
607
608 #[test]
609 fn load_decode_from_v1_migrates_data_format() {
610 let client = substrate_test_runtime_client::new();
611
612 let authorities = vec![(dummy_id(), 100)];
613 let set_id = 3;
614 let round_number: RoundNumber = 42;
615 let round_state = RoundState::<H256, u64> {
616 prevote_ghost: Some((H256::random(), 32)),
617 finalized: None,
618 estimate: None,
619 completable: false,
620 };
621
622 {
623 let authority_set = AuthoritySet::<H256, u64>::new(
624 authorities.clone(),
625 set_id,
626 ForkTree::new(),
627 Vec::new(),
628 AuthoritySetChanges::empty(),
629 )
630 .unwrap();
631
632 let voter_set_state = V1VoterSetState::Live(round_number, round_state.clone());
633
634 client
635 .insert_aux(
636 &[
637 (AUTHORITY_SET_KEY, authority_set.encode().as_slice()),
638 (SET_STATE_KEY, voter_set_state.encode().as_slice()),
639 (VERSION_KEY, 1u32.encode().as_slice()),
640 ],
641 &[],
642 )
643 .unwrap();
644 }
645
646 assert_eq!(load_decode::<_, u32>(&client, VERSION_KEY).unwrap(), Some(1));
647
648 load_persistent::<substrate_test_runtime_client::runtime::Block, _, _>(
650 &client,
651 H256::random(),
652 0,
653 || unreachable!(),
654 )
655 .unwrap();
656
657 assert_eq!(load_decode::<_, u32>(&client, VERSION_KEY).unwrap(), Some(3));
658
659 let PersistentData { authority_set, set_state, .. } =
660 load_persistent::<substrate_test_runtime_client::runtime::Block, _, _>(
661 &client,
662 H256::random(),
663 0,
664 || unreachable!(),
665 )
666 .unwrap();
667
668 assert_eq!(
669 *authority_set.inner(),
670 AuthoritySet::new(
671 authorities.clone(),
672 set_id,
673 ForkTree::new(),
674 Vec::new(),
675 AuthoritySetChanges::empty(),
676 )
677 .unwrap(),
678 );
679
680 let mut current_rounds = CurrentRounds::<Block>::new();
681 current_rounds.insert(round_number + 1, HasVoted::No);
682
683 assert_eq!(
684 &*set_state.read(),
685 &VoterSetState::Live {
686 completed_rounds: CompletedRounds::new(
687 CompletedRound {
688 number: round_number,
689 state: round_state.clone(),
690 base: round_state.prevote_ghost.unwrap(),
691 votes: vec![],
692 },
693 set_id,
694 &*authority_set.inner(),
695 ),
696 current_rounds,
697 },
698 );
699 }
700
701 #[test]
702 fn load_decode_from_v2_migrates_data_format() {
703 let client = substrate_test_runtime_client::new();
704
705 let authorities = vec![(dummy_id(), 100)];
706 let set_id = 3;
707
708 {
709 let authority_set = V2AuthoritySet::<H256, u64> {
710 current_authorities: authorities.clone(),
711 set_id,
712 pending_standard_changes: ForkTree::new(),
713 pending_forced_changes: Vec::new(),
714 };
715
716 let genesis_state = (H256::random(), 32);
717 let voter_set_state: VoterSetState<substrate_test_runtime_client::runtime::Block> =
718 VoterSetState::live(
719 set_id,
720 &authority_set.clone().into(), genesis_state,
722 );
723
724 client
725 .insert_aux(
726 &[
727 (AUTHORITY_SET_KEY, authority_set.encode().as_slice()),
728 (SET_STATE_KEY, voter_set_state.encode().as_slice()),
729 (VERSION_KEY, 2u32.encode().as_slice()),
730 ],
731 &[],
732 )
733 .unwrap();
734 }
735
736 assert_eq!(load_decode::<_, u32>(&client, VERSION_KEY).unwrap(), Some(2));
737
738 load_persistent::<substrate_test_runtime_client::runtime::Block, _, _>(
740 &client,
741 H256::random(),
742 0,
743 || unreachable!(),
744 )
745 .unwrap();
746
747 assert_eq!(load_decode::<_, u32>(&client, VERSION_KEY).unwrap(), Some(3));
748
749 let PersistentData { authority_set, .. } = load_persistent::<
750 substrate_test_runtime_client::runtime::Block,
751 _,
752 _,
753 >(
754 &client, H256::random(), 0, || unreachable!()
755 )
756 .unwrap();
757
758 assert_eq!(
759 *authority_set.inner(),
760 AuthoritySet::new(
761 authorities.clone(),
762 set_id,
763 ForkTree::new(),
764 Vec::new(),
765 AuthoritySetChanges::empty(),
766 )
767 .unwrap(),
768 );
769 }
770
771 #[test]
772 fn write_read_concluded_rounds() {
773 let client = substrate_test_runtime_client::new();
774 let hash = H256::random();
775 let round_state = RoundState::genesis((hash, 0));
776
777 let completed_round = CompletedRound::<substrate_test_runtime_client::runtime::Block> {
778 number: 42,
779 state: round_state.clone(),
780 base: round_state.prevote_ghost.unwrap(),
781 votes: vec![],
782 };
783
784 assert!(write_concluded_round(&client, &completed_round).is_ok());
785
786 let round_number = completed_round.number;
787 let mut key = CONCLUDED_ROUNDS.to_vec();
788 round_number.using_encoded(|n| key.extend(n));
789
790 assert_eq!(
791 load_decode::<_, CompletedRound::<substrate_test_runtime_client::runtime::Block>>(
792 &client, &key
793 )
794 .unwrap(),
795 Some(completed_round),
796 );
797 }
798}