1use parking_lot::RwLock;
22use sp_blockchain::{CachedHeaderMetadata, HeaderMetadata};
23use sp_core::{
24 offchain::storage::InMemOffchainStorage as OffchainStorage, storage::well_known_keys,
25};
26use sp_runtime::{
27 generic::BlockId,
28 traits::{Block as BlockT, HashingFor, Header as HeaderT, NumberFor, Zero},
29 Justification, Justifications, StateVersion, Storage,
30};
31use sp_state_machine::{
32 Backend as StateBackend, BackendTransaction, ChildStorageCollection, InMemoryBackend,
33 IndexOperation, StorageCollection,
34};
35use std::{
36 collections::{HashMap, HashSet},
37 ptr,
38 sync::Arc,
39};
40
41use crate::{
42 backend::{self, NewBlockState},
43 blockchain::{self, BlockStatus, HeaderBackend},
44 leaves::LeafSet,
45 TrieCacheContext, UsageInfo,
46};
47
48struct PendingBlock<B: BlockT> {
49 block: StoredBlock<B>,
50 state: NewBlockState,
51}
52
53#[derive(PartialEq, Eq, Clone)]
54enum StoredBlock<B: BlockT> {
55 Header(B::Header, Option<Justifications>),
56 Full(B, Option<Justifications>),
57}
58
59impl<B: BlockT> StoredBlock<B> {
60 fn new(
61 header: B::Header,
62 body: Option<Vec<B::Extrinsic>>,
63 just: Option<Justifications>,
64 ) -> Self {
65 match body {
66 Some(body) => StoredBlock::Full(B::new(header, body), just),
67 None => StoredBlock::Header(header, just),
68 }
69 }
70
71 fn header(&self) -> &B::Header {
72 match *self {
73 StoredBlock::Header(ref h, _) => h,
74 StoredBlock::Full(ref b, _) => b.header(),
75 }
76 }
77
78 fn justifications(&self) -> Option<&Justifications> {
79 match *self {
80 StoredBlock::Header(_, ref j) | StoredBlock::Full(_, ref j) => j.as_ref(),
81 }
82 }
83
84 fn extrinsics(&self) -> Option<&[B::Extrinsic]> {
85 match *self {
86 StoredBlock::Header(_, _) => None,
87 StoredBlock::Full(ref b, _) => Some(b.extrinsics()),
88 }
89 }
90
91 fn into_inner(self) -> (B::Header, Option<Vec<B::Extrinsic>>, Option<Justifications>) {
92 match self {
93 StoredBlock::Header(header, just) => (header, None, just),
94 StoredBlock::Full(block, just) => {
95 let (header, body) = block.deconstruct();
96 (header, Some(body), just)
97 },
98 }
99 }
100}
101
102#[derive(Clone)]
103struct BlockchainStorage<Block: BlockT> {
104 blocks: HashMap<Block::Hash, StoredBlock<Block>>,
105 hashes: HashMap<NumberFor<Block>, Block::Hash>,
106 best_hash: Block::Hash,
107 best_number: NumberFor<Block>,
108 finalized_hash: Block::Hash,
109 finalized_number: NumberFor<Block>,
110 genesis_hash: Block::Hash,
111 header_cht_roots: HashMap<NumberFor<Block>, Block::Hash>,
112 leaves: LeafSet<Block::Hash, NumberFor<Block>>,
113 aux: HashMap<Vec<u8>, Vec<u8>>,
114}
115
116#[derive(Clone)]
118pub struct Blockchain<Block: BlockT> {
119 storage: Arc<RwLock<BlockchainStorage<Block>>>,
120}
121
122impl<Block: BlockT> Default for Blockchain<Block> {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl<Block: BlockT> Blockchain<Block> {
129 pub fn id(&self, id: BlockId<Block>) -> Option<Block::Hash> {
131 match id {
132 BlockId::Hash(h) => Some(h),
133 BlockId::Number(n) => self.storage.read().hashes.get(&n).cloned(),
134 }
135 }
136
137 pub fn new() -> Blockchain<Block> {
139 let storage = Arc::new(RwLock::new(BlockchainStorage {
140 blocks: HashMap::new(),
141 hashes: HashMap::new(),
142 best_hash: Default::default(),
143 best_number: Zero::zero(),
144 finalized_hash: Default::default(),
145 finalized_number: Zero::zero(),
146 genesis_hash: Default::default(),
147 header_cht_roots: HashMap::new(),
148 leaves: LeafSet::new(),
149 aux: HashMap::new(),
150 }));
151 Blockchain { storage }
152 }
153
154 pub fn insert(
156 &self,
157 hash: Block::Hash,
158 header: <Block as BlockT>::Header,
159 justifications: Option<Justifications>,
160 body: Option<Vec<<Block as BlockT>::Extrinsic>>,
161 new_state: NewBlockState,
162 ) -> sp_blockchain::Result<()> {
163 let number = *header.number();
164 if new_state.is_best() {
165 self.apply_head(&header)?;
166 }
167
168 {
169 let mut storage = self.storage.write();
170 storage.leaves.import(hash, number, *header.parent_hash());
171 storage.blocks.insert(hash, StoredBlock::new(header, body, justifications));
172
173 if let NewBlockState::Final = new_state {
174 storage.finalized_hash = hash;
175 storage.finalized_number = number;
176 }
177
178 if number == Zero::zero() {
179 storage.genesis_hash = hash;
180 }
181 }
182
183 Ok(())
184 }
185
186 pub fn blocks_count(&self) -> usize {
188 self.storage.read().blocks.len()
189 }
190
191 pub fn equals_to(&self, other: &Self) -> bool {
193 if ptr::eq(self, other) {
195 return true
196 }
197 self.canon_equals_to(other) && self.storage.read().blocks == other.storage.read().blocks
198 }
199
200 pub fn canon_equals_to(&self, other: &Self) -> bool {
202 if ptr::eq(self, other) {
204 return true
205 }
206 let this = self.storage.read();
207 let other = other.storage.read();
208 this.hashes == other.hashes &&
209 this.best_hash == other.best_hash &&
210 this.best_number == other.best_number &&
211 this.genesis_hash == other.genesis_hash
212 }
213
214 pub fn insert_cht_root(&self, block: NumberFor<Block>, cht_root: Block::Hash) {
216 self.storage.write().header_cht_roots.insert(block, cht_root);
217 }
218
219 pub fn set_head(&self, hash: Block::Hash) -> sp_blockchain::Result<()> {
221 let header = self
222 .header(hash)?
223 .ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", hash)))?;
224
225 self.apply_head(&header)
226 }
227
228 fn apply_head(&self, header: &<Block as BlockT>::Header) -> sp_blockchain::Result<()> {
229 let hash = header.hash();
230 let number = header.number();
231
232 let best_tree_route = {
235 let best_hash = self.storage.read().best_hash;
236 if &best_hash == header.parent_hash() {
237 None
238 } else {
239 let route = sp_blockchain::tree_route(self, best_hash, *header.parent_hash())?;
240 Some(route)
241 }
242 };
243
244 let mut storage = self.storage.write();
245
246 if let Some(tree_route) = best_tree_route {
247 let enacted = tree_route.enacted();
249
250 for entry in enacted {
251 storage.hashes.insert(entry.number, entry.hash);
252 }
253
254 for entry in tree_route.retracted().iter().skip(enacted.len()) {
255 storage.hashes.remove(&entry.number);
256 }
257 }
258
259 storage.best_hash = hash;
260 storage.best_number = *number;
261 storage.hashes.insert(*number, hash);
262
263 Ok(())
264 }
265
266 fn finalize_header(
267 &self,
268 block: Block::Hash,
269 justification: Option<Justification>,
270 ) -> sp_blockchain::Result<()> {
271 let mut storage = self.storage.write();
272 storage.finalized_hash = block;
273
274 if justification.is_some() {
275 let block = storage
276 .blocks
277 .get_mut(&block)
278 .expect("hash was fetched from a block in the db; qed");
279
280 let block_justifications = match block {
281 StoredBlock::Header(_, ref mut j) | StoredBlock::Full(_, ref mut j) => j,
282 };
283
284 *block_justifications = justification.map(Justifications::from);
285 }
286
287 Ok(())
288 }
289
290 fn append_justification(
291 &self,
292 hash: Block::Hash,
293 justification: Justification,
294 ) -> sp_blockchain::Result<()> {
295 let mut storage = self.storage.write();
296
297 let block = storage
298 .blocks
299 .get_mut(&hash)
300 .expect("hash was fetched from a block in the db; qed");
301
302 let block_justifications = match block {
303 StoredBlock::Header(_, ref mut j) | StoredBlock::Full(_, ref mut j) => j,
304 };
305
306 if let Some(stored_justifications) = block_justifications {
307 if !stored_justifications.append(justification) {
308 return Err(sp_blockchain::Error::BadJustification(
309 "Duplicate consensus engine ID".into(),
310 ))
311 }
312 } else {
313 *block_justifications = Some(Justifications::from(justification));
314 };
315
316 Ok(())
317 }
318
319 fn write_aux(&self, ops: Vec<(Vec<u8>, Option<Vec<u8>>)>) {
320 let mut storage = self.storage.write();
321 for (k, v) in ops {
322 match v {
323 Some(v) => storage.aux.insert(k, v),
324 None => storage.aux.remove(&k),
325 };
326 }
327 }
328}
329
330impl<Block: BlockT> HeaderBackend<Block> for Blockchain<Block> {
331 fn header(
332 &self,
333 hash: Block::Hash,
334 ) -> sp_blockchain::Result<Option<<Block as BlockT>::Header>> {
335 Ok(self.storage.read().blocks.get(&hash).map(|b| b.header().clone()))
336 }
337
338 fn info(&self) -> blockchain::Info<Block> {
339 let storage = self.storage.read();
340 blockchain::Info {
341 best_hash: storage.best_hash,
342 best_number: storage.best_number,
343 genesis_hash: storage.genesis_hash,
344 finalized_hash: storage.finalized_hash,
345 finalized_number: storage.finalized_number,
346 finalized_state: if storage.finalized_hash != Default::default() {
347 Some((storage.finalized_hash, storage.finalized_number))
348 } else {
349 None
350 },
351 number_leaves: storage.leaves.count(),
352 block_gap: None,
353 }
354 }
355
356 fn status(&self, hash: Block::Hash) -> sp_blockchain::Result<BlockStatus> {
357 match self.storage.read().blocks.contains_key(&hash) {
358 true => Ok(BlockStatus::InChain),
359 false => Ok(BlockStatus::Unknown),
360 }
361 }
362
363 fn number(&self, hash: Block::Hash) -> sp_blockchain::Result<Option<NumberFor<Block>>> {
364 Ok(self.storage.read().blocks.get(&hash).map(|b| *b.header().number()))
365 }
366
367 fn hash(
368 &self,
369 number: <<Block as BlockT>::Header as HeaderT>::Number,
370 ) -> sp_blockchain::Result<Option<Block::Hash>> {
371 Ok(self.id(BlockId::Number(number)))
372 }
373}
374
375impl<Block: BlockT> HeaderMetadata<Block> for Blockchain<Block> {
376 type Error = sp_blockchain::Error;
377
378 fn header_metadata(
379 &self,
380 hash: Block::Hash,
381 ) -> Result<CachedHeaderMetadata<Block>, Self::Error> {
382 self.header(hash)?
383 .map(|header| CachedHeaderMetadata::from(&header))
384 .ok_or_else(|| {
385 sp_blockchain::Error::UnknownBlock(format!("header not found: {}", hash))
386 })
387 }
388
389 fn insert_header_metadata(&self, _hash: Block::Hash, _metadata: CachedHeaderMetadata<Block>) {
390 }
392 fn remove_header_metadata(&self, _hash: Block::Hash) {
393 }
395}
396
397impl<Block: BlockT> blockchain::Backend<Block> for Blockchain<Block> {
398 fn body(
399 &self,
400 hash: Block::Hash,
401 ) -> sp_blockchain::Result<Option<Vec<<Block as BlockT>::Extrinsic>>> {
402 Ok(self
403 .storage
404 .read()
405 .blocks
406 .get(&hash)
407 .and_then(|b| b.extrinsics().map(|x| x.to_vec())))
408 }
409
410 fn justifications(&self, hash: Block::Hash) -> sp_blockchain::Result<Option<Justifications>> {
411 Ok(self.storage.read().blocks.get(&hash).and_then(|b| b.justifications().cloned()))
412 }
413
414 fn last_finalized(&self) -> sp_blockchain::Result<Block::Hash> {
415 Ok(self.storage.read().finalized_hash)
416 }
417
418 fn leaves(&self) -> sp_blockchain::Result<Vec<Block::Hash>> {
419 Ok(self.storage.read().leaves.hashes())
420 }
421
422 fn children(&self, _parent_hash: Block::Hash) -> sp_blockchain::Result<Vec<Block::Hash>> {
423 unimplemented!()
424 }
425
426 fn indexed_transaction(&self, _hash: Block::Hash) -> sp_blockchain::Result<Option<Vec<u8>>> {
427 unimplemented!("Not supported by the in-mem backend.")
428 }
429
430 fn block_indexed_body(
431 &self,
432 _hash: Block::Hash,
433 ) -> sp_blockchain::Result<Option<Vec<Vec<u8>>>> {
434 unimplemented!("Not supported by the in-mem backend.")
435 }
436}
437
438impl<Block: BlockT> backend::AuxStore for Blockchain<Block> {
439 fn insert_aux<
440 'a,
441 'b: 'a,
442 'c: 'a,
443 I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
444 D: IntoIterator<Item = &'a &'b [u8]>,
445 >(
446 &self,
447 insert: I,
448 delete: D,
449 ) -> sp_blockchain::Result<()> {
450 let mut storage = self.storage.write();
451 for (k, v) in insert {
452 storage.aux.insert(k.to_vec(), v.to_vec());
453 }
454 for k in delete {
455 storage.aux.remove(*k);
456 }
457 Ok(())
458 }
459
460 fn get_aux(&self, key: &[u8]) -> sp_blockchain::Result<Option<Vec<u8>>> {
461 Ok(self.storage.read().aux.get(key).cloned())
462 }
463}
464
465pub struct BlockImportOperation<Block: BlockT> {
467 pending_block: Option<PendingBlock<Block>>,
468 old_state: InMemoryBackend<HashingFor<Block>>,
469 new_state: Option<BackendTransaction<HashingFor<Block>>>,
470 aux: Vec<(Vec<u8>, Option<Vec<u8>>)>,
471 finalized_blocks: Vec<(Block::Hash, Option<Justification>)>,
472 set_head: Option<Block::Hash>,
473}
474
475impl<Block: BlockT> BlockImportOperation<Block> {
476 fn apply_storage(
477 &mut self,
478 storage: Storage,
479 commit: bool,
480 state_version: StateVersion,
481 ) -> sp_blockchain::Result<Block::Hash> {
482 check_genesis_storage(&storage)?;
483
484 let child_delta = storage.children_default.values().map(|child_content| {
485 (
486 &child_content.child_info,
487 child_content.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))),
488 )
489 });
490
491 let (root, transaction) = self.old_state.full_storage_root(
492 storage.top.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))),
493 child_delta,
494 state_version,
495 );
496
497 if commit {
498 self.new_state = Some(transaction);
499 }
500 Ok(root)
501 }
502}
503
504impl<Block: BlockT> backend::BlockImportOperation<Block> for BlockImportOperation<Block> {
505 type State = InMemoryBackend<HashingFor<Block>>;
506
507 fn state(&self) -> sp_blockchain::Result<Option<&Self::State>> {
508 Ok(Some(&self.old_state))
509 }
510
511 fn set_block_data(
512 &mut self,
513 header: <Block as BlockT>::Header,
514 body: Option<Vec<<Block as BlockT>::Extrinsic>>,
515 _indexed_body: Option<Vec<Vec<u8>>>,
516 justifications: Option<Justifications>,
517 state: NewBlockState,
518 ) -> sp_blockchain::Result<()> {
519 assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
520 self.pending_block =
521 Some(PendingBlock { block: StoredBlock::new(header, body, justifications), state });
522 Ok(())
523 }
524
525 fn update_db_storage(
526 &mut self,
527 update: BackendTransaction<HashingFor<Block>>,
528 ) -> sp_blockchain::Result<()> {
529 self.new_state = Some(update);
530 Ok(())
531 }
532
533 fn set_genesis_state(
534 &mut self,
535 storage: Storage,
536 commit: bool,
537 state_version: StateVersion,
538 ) -> sp_blockchain::Result<Block::Hash> {
539 self.apply_storage(storage, commit, state_version)
540 }
541
542 fn reset_storage(
543 &mut self,
544 storage: Storage,
545 state_version: StateVersion,
546 ) -> sp_blockchain::Result<Block::Hash> {
547 self.apply_storage(storage, true, state_version)
548 }
549
550 fn insert_aux<I>(&mut self, ops: I) -> sp_blockchain::Result<()>
551 where
552 I: IntoIterator<Item = (Vec<u8>, Option<Vec<u8>>)>,
553 {
554 self.aux.append(&mut ops.into_iter().collect());
555 Ok(())
556 }
557
558 fn update_storage(
559 &mut self,
560 _update: StorageCollection,
561 _child_update: ChildStorageCollection,
562 ) -> sp_blockchain::Result<()> {
563 Ok(())
564 }
565
566 fn mark_finalized(
567 &mut self,
568 hash: Block::Hash,
569 justification: Option<Justification>,
570 ) -> sp_blockchain::Result<()> {
571 self.finalized_blocks.push((hash, justification));
572 Ok(())
573 }
574
575 fn mark_head(&mut self, hash: Block::Hash) -> sp_blockchain::Result<()> {
576 assert!(self.pending_block.is_none(), "Only one set block per operation is allowed");
577 self.set_head = Some(hash);
578 Ok(())
579 }
580
581 fn update_transaction_index(
582 &mut self,
583 _index: Vec<IndexOperation>,
584 ) -> sp_blockchain::Result<()> {
585 Ok(())
586 }
587
588 fn set_create_gap(&mut self, _create_gap: bool) {}
589}
590
591pub struct Backend<Block: BlockT> {
596 states: RwLock<HashMap<Block::Hash, InMemoryBackend<HashingFor<Block>>>>,
597 blockchain: Blockchain<Block>,
598 import_lock: RwLock<()>,
599 pinned_blocks: RwLock<HashMap<Block::Hash, i64>>,
600}
601
602impl<Block: BlockT> Backend<Block> {
603 pub fn new() -> Self {
609 Backend {
610 states: RwLock::new(HashMap::new()),
611 blockchain: Blockchain::new(),
612 import_lock: Default::default(),
613 pinned_blocks: Default::default(),
614 }
615 }
616
617 pub fn pin_refs(&self, hash: &<Block as BlockT>::Hash) -> Option<i64> {
623 let blocks = self.pinned_blocks.read();
624 blocks.get(hash).map(|value| *value)
625 }
626}
627
628impl<Block: BlockT> backend::AuxStore for Backend<Block> {
629 fn insert_aux<
630 'a,
631 'b: 'a,
632 'c: 'a,
633 I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
634 D: IntoIterator<Item = &'a &'b [u8]>,
635 >(
636 &self,
637 insert: I,
638 delete: D,
639 ) -> sp_blockchain::Result<()> {
640 self.blockchain.insert_aux(insert, delete)
641 }
642
643 fn get_aux(&self, key: &[u8]) -> sp_blockchain::Result<Option<Vec<u8>>> {
644 self.blockchain.get_aux(key)
645 }
646}
647
648impl<Block: BlockT> backend::Backend<Block> for Backend<Block> {
649 type BlockImportOperation = BlockImportOperation<Block>;
650 type Blockchain = Blockchain<Block>;
651 type State = InMemoryBackend<HashingFor<Block>>;
652 type OffchainStorage = OffchainStorage;
653
654 fn begin_operation(&self) -> sp_blockchain::Result<Self::BlockImportOperation> {
655 let old_state = self.state_at(Default::default(), TrieCacheContext::Untrusted)?;
656 Ok(BlockImportOperation {
657 pending_block: None,
658 old_state,
659 new_state: None,
660 aux: Default::default(),
661 finalized_blocks: Default::default(),
662 set_head: None,
663 })
664 }
665
666 fn begin_state_operation(
667 &self,
668 operation: &mut Self::BlockImportOperation,
669 block: Block::Hash,
670 ) -> sp_blockchain::Result<()> {
671 operation.old_state = self.state_at(block, TrieCacheContext::Untrusted)?;
672 Ok(())
673 }
674
675 fn commit_operation(&self, operation: Self::BlockImportOperation) -> sp_blockchain::Result<()> {
676 if !operation.finalized_blocks.is_empty() {
677 for (block, justification) in operation.finalized_blocks {
678 self.blockchain.finalize_header(block, justification)?;
679 }
680 }
681
682 if let Some(pending_block) = operation.pending_block {
683 let old_state = &operation.old_state;
684 let (header, body, justification) = pending_block.block.into_inner();
685
686 let hash = header.hash();
687
688 let new_state = match operation.new_state {
689 Some(state) => old_state.update_backend(*header.state_root(), state),
690 None => old_state.clone(),
691 };
692
693 self.states.write().insert(hash, new_state);
694
695 self.blockchain.insert(hash, header, justification, body, pending_block.state)?;
696 }
697
698 if !operation.aux.is_empty() {
699 self.blockchain.write_aux(operation.aux);
700 }
701
702 if let Some(set_head) = operation.set_head {
703 self.blockchain.set_head(set_head)?;
704 }
705
706 Ok(())
707 }
708
709 fn finalize_block(
710 &self,
711 hash: Block::Hash,
712 justification: Option<Justification>,
713 ) -> sp_blockchain::Result<()> {
714 self.blockchain.finalize_header(hash, justification)
715 }
716
717 fn append_justification(
718 &self,
719 hash: Block::Hash,
720 justification: Justification,
721 ) -> sp_blockchain::Result<()> {
722 self.blockchain.append_justification(hash, justification)
723 }
724
725 fn blockchain(&self) -> &Self::Blockchain {
726 &self.blockchain
727 }
728
729 fn usage_info(&self) -> Option<UsageInfo> {
730 None
731 }
732
733 fn offchain_storage(&self) -> Option<Self::OffchainStorage> {
734 None
735 }
736
737 fn state_at(
738 &self,
739 hash: Block::Hash,
740 _trie_cache_context: TrieCacheContext,
741 ) -> sp_blockchain::Result<Self::State> {
742 if hash == Default::default() {
743 return Ok(Self::State::default())
744 }
745
746 self.states
747 .read()
748 .get(&hash)
749 .cloned()
750 .ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", hash)))
751 }
752
753 fn revert(
754 &self,
755 _n: NumberFor<Block>,
756 _revert_finalized: bool,
757 ) -> sp_blockchain::Result<(NumberFor<Block>, HashSet<Block::Hash>)> {
758 Ok((Zero::zero(), HashSet::new()))
759 }
760
761 fn remove_leaf_block(&self, _hash: Block::Hash) -> sp_blockchain::Result<()> {
762 Ok(())
763 }
764
765 fn get_import_lock(&self) -> &RwLock<()> {
766 &self.import_lock
767 }
768
769 fn requires_full_sync(&self) -> bool {
770 false
771 }
772
773 fn pin_block(&self, hash: <Block as BlockT>::Hash) -> blockchain::Result<()> {
774 let mut blocks = self.pinned_blocks.write();
775 *blocks.entry(hash).or_default() += 1;
776 Ok(())
777 }
778
779 fn unpin_block(&self, hash: <Block as BlockT>::Hash) {
780 let mut blocks = self.pinned_blocks.write();
781 blocks.entry(hash).and_modify(|counter| *counter -= 1).or_insert(-1);
782 }
783}
784
785impl<Block: BlockT> backend::LocalBackend<Block> for Backend<Block> {}
786
787pub fn check_genesis_storage(storage: &Storage) -> sp_blockchain::Result<()> {
789 if storage.top.iter().any(|(k, _)| well_known_keys::is_child_storage_key(k)) {
790 return Err(sp_blockchain::Error::InvalidState)
791 }
792
793 if storage
794 .children_default
795 .keys()
796 .any(|child_key| !well_known_keys::is_child_storage_key(child_key))
797 {
798 return Err(sp_blockchain::Error::InvalidState)
799 }
800
801 Ok(())
802}
803
804#[cfg(test)]
805mod tests {
806 use crate::{in_mem::Blockchain, NewBlockState};
807 use sp_blockchain::Backend;
808 use sp_runtime::{traits::Header as HeaderT, ConsensusEngineId, Justifications};
809 use substrate_test_runtime::{Block, Header, H256};
810
811 pub const ID1: ConsensusEngineId = *b"TST1";
812 pub const ID2: ConsensusEngineId = *b"TST2";
813
814 fn header(number: u64) -> Header {
815 let parent_hash = match number {
816 0 => Default::default(),
817 _ => header(number - 1).hash(),
818 };
819 Header::new(
820 number,
821 H256::from_low_u64_be(0),
822 H256::from_low_u64_be(0),
823 parent_hash,
824 Default::default(),
825 )
826 }
827
828 fn test_blockchain() -> Blockchain<Block> {
829 let blockchain = Blockchain::<Block>::new();
830 let just0 = Some(Justifications::from((ID1, vec![0])));
831 let just1 = Some(Justifications::from((ID1, vec![1])));
832 let just2 = None;
833 let just3 = Some(Justifications::from((ID1, vec![3])));
834 blockchain
835 .insert(header(0).hash(), header(0), just0, None, NewBlockState::Final)
836 .unwrap();
837 blockchain
838 .insert(header(1).hash(), header(1), just1, None, NewBlockState::Final)
839 .unwrap();
840 blockchain
841 .insert(header(2).hash(), header(2), just2, None, NewBlockState::Best)
842 .unwrap();
843 blockchain
844 .insert(header(3).hash(), header(3), just3, None, NewBlockState::Final)
845 .unwrap();
846 blockchain
847 }
848
849 #[test]
850 fn append_and_retrieve_justifications() {
851 let blockchain = test_blockchain();
852 let last_finalized = blockchain.last_finalized().unwrap();
853
854 blockchain.append_justification(last_finalized, (ID2, vec![4])).unwrap();
855 let justifications = {
856 let mut just = Justifications::from((ID1, vec![3]));
857 just.append((ID2, vec![4]));
858 just
859 };
860 assert_eq!(blockchain.justifications(last_finalized).unwrap(), Some(justifications));
861 }
862
863 #[test]
864 fn store_duplicate_justifications_is_forbidden() {
865 let blockchain = test_blockchain();
866 let last_finalized = blockchain.last_finalized().unwrap();
867
868 blockchain.append_justification(last_finalized, (ID2, vec![0])).unwrap();
869 assert!(matches!(
870 blockchain.append_justification(last_finalized, (ID2, vec![1])),
871 Err(sp_blockchain::Error::BadJustification(_)),
872 ));
873 }
874}