Skip to main content

amaru_protocols/
store_effects.rs

1// Copyright 2025 PRAGMA
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
21/// Implementation of ChainStore using pure_stage::Effects.
22pub 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    /// This function runs an external effect synchronously.
38    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
111// EXTERNAL EFFECTS DEFINITIONS
112
113pub 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 {}