soil_consensus/
longest_chain.rs1use soil_client::blockchain::{Backend, HeaderBackend};
10use soil_client::client_api::backend;
11use soil_client::consensus::{Error as ConsensusError, SelectChain};
12use std::{marker::PhantomData, sync::Arc};
13use subsoil::runtime::traits::{Block as BlockT, Header, NumberFor};
14
15pub struct LongestChain<B, Block> {
18 backend: Arc<B>,
19 _phantom: PhantomData<Block>,
20}
21
22impl<B, Block> Clone for LongestChain<B, Block> {
23 fn clone(&self) -> Self {
24 let backend = self.backend.clone();
25 LongestChain { backend, _phantom: Default::default() }
26 }
27}
28
29impl<B, Block> LongestChain<B, Block>
30where
31 B: backend::Backend<Block>,
32 Block: BlockT,
33{
34 pub fn new(backend: Arc<B>) -> Self {
36 LongestChain { backend, _phantom: Default::default() }
37 }
38
39 fn best_hash(&self) -> soil_client::blockchain::Result<<Block as BlockT>::Hash> {
40 let info = self.backend.blockchain().info();
41 let import_lock = self.backend.get_import_lock();
42 let best_hash = self
43 .backend
44 .blockchain()
45 .longest_containing(info.best_hash, import_lock)?
46 .unwrap_or(info.best_hash);
47 Ok(best_hash)
48 }
49
50 fn best_header(&self) -> soil_client::blockchain::Result<<Block as BlockT>::Header> {
51 let best_hash = self.best_hash()?;
52 Ok(self
53 .backend
54 .blockchain()
55 .header(best_hash)?
56 .expect("given block hash was fetched from block in db; qed"))
57 }
58
59 fn finality_target(
69 &self,
70 base_hash: Block::Hash,
71 maybe_max_number: Option<NumberFor<Block>>,
72 ) -> soil_client::blockchain::Result<Block::Hash> {
73 use soil_client::blockchain::Error::{Application, MissingHeader};
74 let blockchain = self.backend.blockchain();
75
76 let mut current_head = self.best_header()?;
77 let mut best_hash = current_head.hash();
78
79 let base_header = blockchain
80 .header(base_hash)?
81 .ok_or_else(|| MissingHeader(base_hash.to_string()))?;
82 let base_number = *base_header.number();
83
84 if let Some(max_number) = maybe_max_number {
85 if max_number < base_number {
86 let msg = format!(
87 "Requested a finality target using max number {} below the base number {}",
88 max_number, base_number
89 );
90 return Err(Application(msg.into()));
91 }
92
93 while current_head.number() > &max_number {
94 best_hash = *current_head.parent_hash();
95 current_head = blockchain
96 .header(best_hash)?
97 .ok_or_else(|| MissingHeader(format!("{best_hash:?}")))?;
98 }
99 }
100
101 while current_head.hash() != base_hash {
102 if *current_head.number() < base_number {
103 let msg = format!(
104 "Requested a finality target using a base {:?} not in the best chain {:?}",
105 base_hash, best_hash,
106 );
107 return Err(Application(msg.into()));
108 }
109 let current_hash = *current_head.parent_hash();
110 current_head = blockchain
111 .header(current_hash)?
112 .ok_or_else(|| MissingHeader(format!("{best_hash:?}")))?;
113 }
114
115 Ok(best_hash)
116 }
117
118 fn leaves(&self) -> Result<Vec<<Block as BlockT>::Hash>, soil_client::blockchain::Error> {
119 self.backend.blockchain().leaves()
120 }
121}
122
123#[async_trait::async_trait]
124impl<B, Block> SelectChain<Block> for LongestChain<B, Block>
125where
126 B: backend::Backend<Block>,
127 Block: BlockT,
128{
129 async fn leaves(&self) -> Result<Vec<<Block as BlockT>::Hash>, ConsensusError> {
130 LongestChain::leaves(self).map_err(|e| ConsensusError::ChainLookup(e.to_string()))
131 }
132
133 async fn best_chain(&self) -> Result<<Block as BlockT>::Header, ConsensusError> {
134 LongestChain::best_header(self).map_err(|e| ConsensusError::ChainLookup(e.to_string()))
135 }
136
137 async fn finality_target(
138 &self,
139 base_hash: Block::Hash,
140 maybe_max_number: Option<NumberFor<Block>>,
141 ) -> Result<Block::Hash, ConsensusError> {
142 LongestChain::finality_target(self, base_hash, maybe_max_number)
143 .map_err(|e| ConsensusError::ChainLookup(e.to_string()))
144 }
145}