amaru_ouroboros_traits/stores/consensus/
mod.rs1pub mod in_memory_consensus_store;
16
17use std::{fmt::Display, iter::successors, sync::Arc};
18
19use amaru_kernel::{
20 Block, BlockHeader, HeaderHash, IsHeader, Point, RawBlock, Tip, cardano::network_block::NetworkBlock,
21};
22use thiserror::Error;
23
24use crate::Nonces;
25
26pub trait ReadOnlyChainStore<H>
27where
28 H: IsHeader,
29{
30 fn load_header(&self, hash: &HeaderHash) -> Option<H>;
32
33 fn get_children(&self, hash: &HeaderHash) -> Vec<HeaderHash>;
34 fn get_anchor_hash(&self) -> HeaderHash;
35 fn get_best_chain_hash(&self) -> HeaderHash;
36
37 fn load_from_best_chain(&self, point: &Point) -> Option<HeaderHash>;
40
41 fn next_best_chain(&self, point: &Point) -> Option<Point>;
44
45 fn load_block(&self, hash: &HeaderHash) -> Result<Option<RawBlock>, StoreError>;
46 fn get_nonces(&self, header: &HeaderHash) -> Option<Nonces>;
47 fn has_header(&self, hash: &HeaderHash) -> bool;
48
49 fn load_tip(&self, hash: &HeaderHash) -> Option<Tip> {
51 self.load_header(hash).map(|h| h.tip())
52 }
53
54 fn retrieve_best_chain(&self) -> Vec<HeaderHash> {
56 let anchor = self.get_anchor_hash();
57 let mut best_chain = vec![];
58 let mut current_hash = self.get_best_chain_hash();
59 while let Some(header) = self.load_header(¤t_hash) {
60 best_chain.push(current_hash);
61 if header.hash() != anchor
62 && let Some(parent) = header.parent()
63 {
64 current_hash = parent;
65 } else {
66 break;
67 }
68 }
69 best_chain.reverse();
70 best_chain
71 }
72
73 fn ancestors<'a>(&'a self, start: H) -> Box<dyn Iterator<Item = H> + 'a>
76 where
77 H: 'a,
78 {
79 let anchor = self.get_anchor_hash();
80 let anchor_point = match self.load_header(&anchor) {
81 Some(header) => header.point(),
82 None => Point::Origin,
83 };
84
85 Box::new(successors(Some(start), move |h| {
86 if h.slot() <= anchor_point.slot_or_default() {
87 None
88 } else {
89 h.parent().and_then(|p| self.load_header(&p))
90 }
91 }))
92 }
93
94 fn ancestors_hashes<'a>(&'a self, hash: &HeaderHash) -> Box<dyn Iterator<Item = HeaderHash> + 'a>
96 where
97 H: 'a,
98 {
99 if let Some(header) = self.load_header(hash) {
100 Box::new(self.ancestors(header).map(|h| h.hash()))
101 } else {
102 Box::new(vec![*hash].into_iter())
103 }
104 }
105}
106
107pub trait DiagnosticChainStore {
109 fn load_headers(&self) -> Box<dyn Iterator<Item = BlockHeader> + '_>;
114
115 fn load_nonces(&self) -> Box<dyn Iterator<Item = (HeaderHash, Nonces)> + '_>;
117 fn load_blocks(&self) -> Box<dyn Iterator<Item = (HeaderHash, RawBlock)> + '_>;
118 fn load_parents_children(&self) -> Box<dyn Iterator<Item = (HeaderHash, Vec<HeaderHash>)> + '_>;
119}
120
121impl<H: IsHeader> ReadOnlyChainStore<H> for Box<dyn ChainStore<H>> {
122 fn load_header(&self, hash: &HeaderHash) -> Option<H> {
123 self.as_ref().load_header(hash)
124 }
125
126 fn get_children(&self, hash: &HeaderHash) -> Vec<HeaderHash> {
127 self.as_ref().get_children(hash)
128 }
129
130 fn get_anchor_hash(&self) -> HeaderHash {
131 self.as_ref().get_anchor_hash()
132 }
133
134 fn get_best_chain_hash(&self) -> HeaderHash {
135 self.as_ref().get_best_chain_hash()
136 }
137
138 fn load_block(&self, hash: &HeaderHash) -> Result<Option<RawBlock>, StoreError> {
139 self.as_ref().load_block(hash)
140 }
141
142 fn get_nonces(&self, header: &HeaderHash) -> Option<Nonces> {
143 self.as_ref().get_nonces(header)
144 }
145
146 fn has_header(&self, hash: &HeaderHash) -> bool {
147 self.as_ref().has_header(hash)
148 }
149
150 fn load_from_best_chain(&self, point: &Point) -> Option<HeaderHash> {
151 self.as_ref().load_from_best_chain(point)
152 }
153
154 fn next_best_chain(&self, point: &Point) -> Option<Point> {
155 self.as_ref().next_best_chain(point)
156 }
157}
158
159pub trait ChainStore<H>: ReadOnlyChainStore<H> + Send + Sync
161where
162 H: IsHeader,
163{
164 fn store_header(&self, header: &H) -> Result<(), StoreError>;
165 fn set_anchor_hash(&self, hash: &HeaderHash) -> Result<(), StoreError>;
166 fn set_best_chain_hash(&self, hash: &HeaderHash) -> Result<(), StoreError>;
167 fn store_block(&self, hash: &HeaderHash, block: &RawBlock) -> Result<(), StoreError>;
168 fn put_nonces(&self, header: &HeaderHash, nonces: &Nonces) -> Result<(), StoreError>;
169
170 fn roll_forward_chain(&self, point: &Point) -> Result<(), StoreError>;
172
173 fn rollback_chain(&self, point: &Point) -> Result<usize, StoreError>;
178}
179
180#[derive(Error, PartialEq, Debug, Clone, serde::Serialize, serde::Deserialize)]
181pub enum StoreError {
182 WriteError { error: String },
183 ReadError { error: String },
184 OpenError { error: String },
185 IncompatibleChainStoreVersions { stored: u16, current: u16 },
186}
187
188impl Display for StoreError {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 match self {
191 StoreError::WriteError { error } => write!(f, "WriteError: {}", error),
192 StoreError::ReadError { error } => write!(f, "ReadError: {}", error),
193 StoreError::OpenError { error } => write!(f, "OpenError: {}", error),
194 StoreError::IncompatibleChainStoreVersions { stored, current } => {
195 write!(f, "Incompatible DB Versions: found {}, expected {}", stored, current)
196 }
197 }
198 }
199}
200
201#[cfg(feature = "test-utils")]
203#[expect(clippy::expect_used)]
204pub fn get_blocks(store: Arc<dyn ChainStore<BlockHeader>>) -> Vec<(HeaderHash, Block)> {
205 store
206 .retrieve_best_chain()
207 .iter()
208 .map(|h| {
209 let b = store
210 .load_block(h)
211 .expect("load_block should not raise an error")
212 .expect("missing block for a header on the best chain");
213 (
214 *h,
215 NetworkBlock::try_from(b)
216 .expect("failed to decode raw block")
217 .decode_block()
218 .expect("failed to decode block"),
219 )
220 })
221 .collect()
222}
223
224#[cfg(feature = "test-utils")]
226#[expect(clippy::expect_used)]
227pub fn get_best_chain_block_headers(store: Arc<dyn ChainStore<BlockHeader>>) -> Vec<BlockHeader> {
228 store
229 .retrieve_best_chain()
230 .iter()
231 .map(|h| store.load_header(h).expect("missing header for the best chain"))
232 .collect()
233}