1use std::sync::Arc;
16
17use amaru_kernel::{BlockHeader, GlobalParameters, HeaderHash, Point, RawBlock};
18use amaru_ouroboros_traits::{ChainStore, Nonces, ReadOnlyChainStore, StoreError};
19use pure_stage::{BoxFuture, Effects, ExternalEffect, ExternalEffectAPI, ExternalEffectSync, Resources, SendData};
20
21pub struct Store<T> {
23 effects: Effects<T>,
24}
25
26impl<T> Clone for Store<T> {
27 fn clone(&self) -> Self {
28 Self { effects: self.effects.clone() }
29 }
30}
31
32impl<T> Store<T> {
33 pub fn new(effects: Effects<T>) -> Store<T> {
34 Store { effects }
35 }
36
37 pub fn external_sync<E: ExternalEffectSync + serde::Serialize + 'static>(&self, effect: E) -> E::Response {
39 self.effects.external_sync(effect)
40 }
41}
42
43impl<T> ReadOnlyChainStore<BlockHeader> for Store<T> {
44 fn load_header(&self, hash: &HeaderHash) -> Option<BlockHeader> {
45 self.external_sync(LoadHeaderEffect::new(*hash))
46 }
47
48 fn get_children(&self, hash: &HeaderHash) -> Vec<HeaderHash> {
49 self.external_sync(GetChildrenEffect::new(*hash))
50 }
51
52 fn get_anchor_hash(&self) -> HeaderHash {
53 self.external_sync(GetAnchorHashEffect::new())
54 }
55
56 fn get_best_chain_hash(&self) -> HeaderHash {
57 self.external_sync(GetBestChainHashEffect::new())
58 }
59
60 fn load_block(&self, hash: &HeaderHash) -> Result<Option<RawBlock>, StoreError> {
61 self.external_sync(LoadBlockEffect::new(*hash))
62 }
63
64 fn get_nonces(&self, hash: &HeaderHash) -> Option<Nonces> {
65 self.external_sync(GetNoncesEffect::new(*hash))
66 }
67
68 fn has_header(&self, hash: &HeaderHash) -> bool {
69 self.external_sync(HasHeaderEffect::new(*hash))
70 }
71
72 fn load_from_best_chain(&self, point: &Point) -> Option<HeaderHash> {
73 self.external_sync(LoadFromBestChainEffect::new(*point))
74 }
75
76 fn next_best_chain(&self, point: &Point) -> Option<Point> {
77 self.external_sync(NextBestChainEffect::new(*point))
78 }
79}
80
81impl<T: SendData + Sync> ChainStore<BlockHeader> for Store<T> {
82 fn set_anchor_hash(&self, hash: &HeaderHash) -> Result<(), StoreError> {
83 self.external_sync(SetAnchorHashEffect::new(*hash))
84 }
85
86 fn set_best_chain_hash(&self, hash: &HeaderHash) -> Result<(), StoreError> {
87 self.external_sync(SetBestChainHashEffect::new(*hash))
88 }
89
90 fn store_header(&self, header: &BlockHeader) -> Result<(), StoreError> {
91 self.external_sync(StoreHeaderEffect::new(header.clone()))
92 }
93
94 fn store_block(&self, hash: &HeaderHash, block: &RawBlock) -> Result<(), StoreError> {
95 self.external_sync(StoreBlockEffect::new(hash, block.clone()))
96 }
97
98 fn put_nonces(&self, header: &HeaderHash, nonces: &Nonces) -> Result<(), StoreError> {
99 self.external_sync(PutNoncesEffect::new(*header, nonces.clone()))
100 }
101
102 fn roll_forward_chain(&self, point: &Point) -> Result<(), StoreError> {
103 self.external_sync(RollForwardChainEffect::new(*point))
104 }
105
106 fn rollback_chain(&self, point: &Point) -> Result<usize, StoreError> {
107 self.external_sync(RollBackChainEffect::new(*point))
108 }
109}
110
111pub type ResourceHeaderStore = Arc<dyn ChainStore<BlockHeader>>;
114pub type ResourceParameters = GlobalParameters;
115
116#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
117pub struct StoreHeaderEffect {
118 header: BlockHeader,
119}
120
121impl StoreHeaderEffect {
122 pub fn new(header: BlockHeader) -> Self {
123 Self { header }
124 }
125}
126
127impl ExternalEffect for StoreHeaderEffect {
128 #[expect(clippy::expect_used)]
129 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
130 Self::wrap_sync({
131 let store =
132 resources.get::<ResourceHeaderStore>().expect("StoreHeaderEffect requires a chain store").clone();
133 store.store_header(&self.header)
134 })
135 }
136}
137
138impl ExternalEffectAPI for StoreHeaderEffect {
139 type Response = Result<(), StoreError>;
140}
141
142impl ExternalEffectSync for StoreHeaderEffect {}
143
144#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
145pub struct StoreBlockEffect {
146 hash: HeaderHash,
147 block: RawBlock,
148}
149
150impl StoreBlockEffect {
151 pub fn new(hash: &HeaderHash, block: RawBlock) -> Self {
152 Self { hash: *hash, block }
153 }
154}
155
156impl ExternalEffect for StoreBlockEffect {
157 #[expect(clippy::expect_used)]
158 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
159 Self::wrap_sync({
160 let store =
161 resources.get::<ResourceHeaderStore>().expect("StoreBlockEffect requires a chain store").clone();
162 store.store_block(&self.hash, &self.block)
163 })
164 }
165}
166
167impl ExternalEffectAPI for StoreBlockEffect {
168 type Response = Result<(), StoreError>;
169}
170
171impl ExternalEffectSync for StoreBlockEffect {}
172
173#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
174struct SetAnchorHashEffect {
175 hash: HeaderHash,
176}
177
178impl SetAnchorHashEffect {
179 pub fn new(hash: HeaderHash) -> Self {
180 Self { hash }
181 }
182}
183
184impl ExternalEffect for SetAnchorHashEffect {
185 #[expect(clippy::expect_used)]
186 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
187 Self::wrap_sync({
188 let store =
189 resources.get::<ResourceHeaderStore>().expect("SetAnchorHashEffect requires a chain store").clone();
190 store.set_anchor_hash(&self.hash)
191 })
192 }
193}
194
195impl ExternalEffectAPI for SetAnchorHashEffect {
196 type Response = Result<(), StoreError>;
197}
198
199impl ExternalEffectSync for SetAnchorHashEffect {}
200
201#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
202struct SetBestChainHashEffect {
203 hash: HeaderHash,
204}
205
206impl SetBestChainHashEffect {
207 pub fn new(hash: HeaderHash) -> Self {
208 Self { hash }
209 }
210}
211
212impl ExternalEffect for SetBestChainHashEffect {
213 #[expect(clippy::expect_used)]
214 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
215 Self::wrap_sync({
216 let store =
217 resources.get::<ResourceHeaderStore>().expect("SetBestChainHashEffect requires a chain store").clone();
218 store.set_best_chain_hash(&self.hash)
219 })
220 }
221}
222
223impl ExternalEffectAPI for SetBestChainHashEffect {
224 type Response = Result<(), StoreError>;
225}
226
227impl ExternalEffectSync for SetBestChainHashEffect {}
228
229#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
230struct PutNoncesEffect {
231 hash: HeaderHash,
232 nonces: Nonces,
233}
234
235impl PutNoncesEffect {
236 pub fn new(hash: HeaderHash, nonces: Nonces) -> Self {
237 Self { hash, nonces }
238 }
239}
240
241impl ExternalEffect for PutNoncesEffect {
242 #[expect(clippy::expect_used)]
243 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
244 Self::wrap_sync({
245 let store = resources.get::<ResourceHeaderStore>().expect("PutNoncesEffect requires a chain store").clone();
246 store.put_nonces(&self.hash, &self.nonces)
247 })
248 }
249}
250
251impl ExternalEffectAPI for PutNoncesEffect {
252 type Response = Result<(), StoreError>;
253}
254
255impl ExternalEffectSync for PutNoncesEffect {}
256
257#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
258pub struct HasHeaderEffect {
259 hash: HeaderHash,
260}
261
262impl HasHeaderEffect {
263 pub fn new(hash: HeaderHash) -> Self {
264 Self { hash }
265 }
266}
267
268impl ExternalEffect for HasHeaderEffect {
269 #[expect(clippy::expect_used)]
270 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
271 Self::wrap_sync({
272 let store = resources.get::<ResourceHeaderStore>().expect("HasHeaderEffect requires a chain store").clone();
273 store.has_header(&self.hash)
274 })
275 }
276}
277
278impl ExternalEffectAPI for HasHeaderEffect {
279 type Response = bool;
280}
281
282impl ExternalEffectSync for HasHeaderEffect {}
283
284#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
285pub struct LoadFromBestChainEffect {
286 point: Point,
287}
288
289impl LoadFromBestChainEffect {
290 pub fn new(point: Point) -> Self {
291 Self { point }
292 }
293}
294
295impl ExternalEffect for LoadFromBestChainEffect {
296 #[expect(clippy::expect_used)]
297 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
298 Self::wrap_sync({
299 let store =
300 resources.get::<ResourceHeaderStore>().expect("LoadFromBestChainEffect requires a chain store").clone();
301 store.load_from_best_chain(&self.point)
302 })
303 }
304}
305
306impl ExternalEffectAPI for LoadFromBestChainEffect {
307 type Response = Option<HeaderHash>;
308}
309
310impl ExternalEffectSync for LoadFromBestChainEffect {}
311
312#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
313pub struct NextBestChainEffect {
314 point: Point,
315}
316
317impl NextBestChainEffect {
318 pub fn new(point: Point) -> Self {
319 Self { point }
320 }
321}
322
323impl ExternalEffect for NextBestChainEffect {
324 #[expect(clippy::expect_used)]
325 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
326 Self::wrap_sync({
327 let store =
328 resources.get::<ResourceHeaderStore>().expect("NextBestChainEffect requires a chain store").clone();
329 store.next_best_chain(&self.point)
330 })
331 }
332}
333
334impl ExternalEffectAPI for NextBestChainEffect {
335 type Response = Option<Point>;
336}
337
338impl ExternalEffectSync for NextBestChainEffect {}
339
340#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
341pub struct LoadHeaderEffect {
342 hash: HeaderHash,
343}
344
345impl LoadHeaderEffect {
346 pub fn new(hash: HeaderHash) -> Self {
347 Self { hash }
348 }
349}
350
351impl ExternalEffect for LoadHeaderEffect {
352 #[expect(clippy::expect_used)]
353 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
354 Self::wrap_sync({
355 let store =
356 resources.get::<ResourceHeaderStore>().expect("LoadHeaderEffect requires a chain store").clone();
357 store.load_header(&self.hash)
358 })
359 }
360}
361
362impl ExternalEffectAPI for LoadHeaderEffect {
363 type Response = Option<BlockHeader>;
364}
365
366impl ExternalEffectSync for LoadHeaderEffect {}
367
368#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
369struct GetChildrenEffect {
370 hash: HeaderHash,
371}
372
373impl GetChildrenEffect {
374 pub fn new(hash: HeaderHash) -> Self {
375 Self { hash }
376 }
377}
378
379impl ExternalEffect for GetChildrenEffect {
380 #[expect(clippy::expect_used)]
381 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
382 Self::wrap_sync({
383 let store =
384 resources.get::<ResourceHeaderStore>().expect("GetChildrenEffect requires a chain store").clone();
385 store.get_children(&self.hash)
386 })
387 }
388}
389
390impl ExternalEffectAPI for GetChildrenEffect {
391 type Response = Vec<HeaderHash>;
392}
393
394impl ExternalEffectSync for GetChildrenEffect {}
395
396#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
397struct GetAnchorHashEffect;
398
399impl GetAnchorHashEffect {
400 pub fn new() -> Self {
401 Self {}
402 }
403}
404
405impl ExternalEffect for GetAnchorHashEffect {
406 #[expect(clippy::expect_used)]
407 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
408 Self::wrap_sync({
409 let store =
410 resources.get::<ResourceHeaderStore>().expect("GetAnchorHashEffect requires a chain store").clone();
411 store.get_anchor_hash()
412 })
413 }
414}
415
416impl ExternalEffectAPI for GetAnchorHashEffect {
417 type Response = HeaderHash;
418}
419
420impl ExternalEffectSync for GetAnchorHashEffect {}
421
422#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
423struct GetBestChainHashEffect;
424
425impl GetBestChainHashEffect {
426 pub fn new() -> Self {
427 Self {}
428 }
429}
430
431impl ExternalEffect for GetBestChainHashEffect {
432 #[expect(clippy::expect_used)]
433 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
434 Self::wrap_sync({
435 let store =
436 resources.get::<ResourceHeaderStore>().expect("GetBestChainHashEffect requires a chain store").clone();
437 store.get_best_chain_hash()
438 })
439 }
440}
441
442impl ExternalEffectAPI for GetBestChainHashEffect {
443 type Response = HeaderHash;
444}
445
446impl ExternalEffectSync for GetBestChainHashEffect {}
447
448#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
449struct LoadBlockEffect {
450 hash: HeaderHash,
451}
452
453impl LoadBlockEffect {
454 pub fn new(hash: HeaderHash) -> Self {
455 Self { hash }
456 }
457}
458
459impl ExternalEffect for LoadBlockEffect {
460 #[expect(clippy::expect_used)]
461 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
462 Self::wrap_sync({
463 let store = resources.get::<ResourceHeaderStore>().expect("LoadBlockEffect requires a chain store").clone();
464 store.load_block(&self.hash)
465 })
466 }
467}
468
469impl ExternalEffectAPI for LoadBlockEffect {
470 type Response = Result<Option<RawBlock>, StoreError>;
471}
472
473impl ExternalEffectSync for LoadBlockEffect {}
474
475#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
476struct GetNoncesEffect {
477 hash: HeaderHash,
478}
479
480impl GetNoncesEffect {
481 pub fn new(hash: HeaderHash) -> Self {
482 Self { hash }
483 }
484}
485
486impl ExternalEffect for GetNoncesEffect {
487 #[expect(clippy::expect_used)]
488 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
489 Self::wrap_sync({
490 let store = resources.get::<ResourceHeaderStore>().expect("GetNoncesEffect requires a chain store").clone();
491 store.get_nonces(&self.hash)
492 })
493 }
494}
495
496impl ExternalEffectAPI for GetNoncesEffect {
497 type Response = Option<Nonces>;
498}
499
500impl ExternalEffectSync for GetNoncesEffect {}
501
502#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
503struct RollForwardChainEffect {
504 point: Point,
505}
506
507impl RollForwardChainEffect {
508 pub fn new(point: Point) -> Self {
509 Self { point }
510 }
511}
512
513impl ExternalEffect for RollForwardChainEffect {
514 #[expect(clippy::expect_used)]
515 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
516 Self::wrap_sync({
517 let store =
518 resources.get::<ResourceHeaderStore>().expect("RollForwardChainEffect requires a chain store").clone();
519 store.roll_forward_chain(&self.point)
520 })
521 }
522}
523
524impl ExternalEffectAPI for RollForwardChainEffect {
525 type Response = Result<(), StoreError>;
526}
527
528impl ExternalEffectSync for RollForwardChainEffect {}
529
530#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
531struct RollBackChainEffect {
532 point: Point,
533}
534
535impl RollBackChainEffect {
536 pub fn new(point: Point) -> Self {
537 Self { point }
538 }
539}
540
541impl ExternalEffect for RollBackChainEffect {
542 #[expect(clippy::expect_used)]
543 fn run(self: Box<Self>, resources: Resources) -> BoxFuture<'static, Box<dyn SendData>> {
544 Self::wrap_sync({
545 let store =
546 resources.get::<ResourceHeaderStore>().expect("RollBackChainEffect requires a chain store").clone();
547 store.rollback_chain(&self.point)
548 })
549 }
550}
551
552impl ExternalEffectAPI for RollBackChainEffect {
553 type Response = Result<usize, StoreError>;
554}
555
556impl ExternalEffectSync for RollBackChainEffect {}