tycho_core/block_strider/starter/
mod.rs

1use std::fs::File;
2use std::path::PathBuf;
3use std::sync::Arc;
4use std::time::Duration;
5
6use anyhow::{Context, Result};
7use serde::{Deserialize, Serialize};
8use tycho_block_util::state::{MinRefMcStateTracker, ShardStateStuff};
9use tycho_types::boc::Boc;
10use tycho_types::models::{BlockId, OutMsgQueueUpdates, ShardStateUnsplit};
11use tycho_util::serde_helpers;
12
13use crate::blockchain_rpc::BlockchainRpcClient;
14use crate::global_config::ZerostateId;
15use crate::storage::CoreStorage;
16
17mod cold_boot;
18
19#[derive(Default, Debug, Clone, Serialize, Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct StarterConfig {
22    /// Choose persistent state which is at least this old.
23    ///
24    /// Default: None
25    #[serde(with = "serde_helpers::humantime")]
26    pub custom_boot_offset: Option<Duration>,
27}
28
29pub struct StarterBuilder<
30    MandatoryFields = (CoreStorage, BlockchainRpcClient, ZerostateId, StarterConfig),
31> {
32    mandatory_fields: MandatoryFields,
33    optional_fields: BuilderFields,
34}
35
36impl Default for StarterBuilder<((), (), (), ())> {
37    #[inline]
38    fn default() -> Self {
39        Self {
40            mandatory_fields: Default::default(),
41            optional_fields: Default::default(),
42        }
43    }
44}
45
46impl StarterBuilder {
47    pub fn build(self) -> Starter {
48        let (storage, blockchain_rpc_client, zerostate, config) = self.mandatory_fields;
49        let BuilderFields {
50            queue_state_handler,
51        } = self.optional_fields;
52
53        Starter {
54            inner: Arc::new(StarterInner {
55                storage,
56                blockchain_rpc_client,
57                zerostate,
58                config,
59                queue_state_handler,
60            }),
61        }
62    }
63}
64
65impl<T2, T3, T4> StarterBuilder<((), T2, T3, T4)> {
66    // TODO: Use `CoreStorage`.
67    pub fn with_storage(self, storage: CoreStorage) -> StarterBuilder<(CoreStorage, T2, T3, T4)> {
68        let ((), client, id, config) = self.mandatory_fields;
69        StarterBuilder {
70            mandatory_fields: (storage, client, id, config),
71            optional_fields: self.optional_fields,
72        }
73    }
74}
75
76impl<T1, T3, T4> StarterBuilder<(T1, (), T3, T4)> {
77    pub fn with_blockchain_rpc_client(
78        self,
79        client: BlockchainRpcClient,
80    ) -> StarterBuilder<(T1, BlockchainRpcClient, T3, T4)> {
81        let (storage, (), id, config) = self.mandatory_fields;
82        StarterBuilder {
83            mandatory_fields: (storage, client, id, config),
84            optional_fields: self.optional_fields,
85        }
86    }
87}
88
89impl<T1, T2, T4> StarterBuilder<(T1, T2, (), T4)> {
90    pub fn with_zerostate_id(
91        self,
92        zerostate_id: ZerostateId,
93    ) -> StarterBuilder<(T1, T2, ZerostateId, T4)> {
94        let (storage, client, (), config) = self.mandatory_fields;
95        StarterBuilder {
96            mandatory_fields: (storage, client, zerostate_id, config),
97            optional_fields: self.optional_fields,
98        }
99    }
100}
101
102impl<T1, T2, T3> StarterBuilder<(T1, T2, T3, ())> {
103    pub fn with_config(self, config: StarterConfig) -> StarterBuilder<(T1, T2, T3, StarterConfig)> {
104        let (storage, client, id, ()) = self.mandatory_fields;
105        StarterBuilder {
106            mandatory_fields: (storage, client, id, config),
107            optional_fields: self.optional_fields,
108        }
109    }
110}
111
112impl<T> StarterBuilder<T> {
113    pub fn with_queue_state_handler<H: QueueStateHandler>(mut self, handler: H) -> Self {
114        self.optional_fields.queue_state_handler = Some(castaway::match_type!(handler, {
115            Box<dyn QueueStateHandler> as handler => handler,
116            handler => Box::new(handler),
117        }));
118        self
119    }
120}
121
122#[derive(Default)]
123struct BuilderFields {
124    queue_state_handler: Option<Box<dyn QueueStateHandler>>,
125}
126
127/// Bootstrapping utils.
128// TODO: Use it as a block provider?
129#[derive(Clone)]
130#[repr(transparent)]
131pub struct Starter {
132    inner: Arc<StarterInner>,
133}
134
135impl Starter {
136    pub fn builder() -> StarterBuilder<((), (), (), ())> {
137        StarterBuilder::default()
138    }
139
140    pub fn config(&self) -> &StarterConfig {
141        &self.inner.config
142    }
143
144    pub fn queue_state_handler(&self) -> Option<&dyn QueueStateHandler> {
145        self.inner.queue_state_handler.as_deref()
146    }
147
148    /// Boot type when the node has not yet started syncing
149    ///
150    /// Returns the last masterchain key block id.
151    pub async fn cold_boot<P>(
152        &self,
153        boot_type: ColdBootType,
154        zerostate_provider: Option<P>,
155    ) -> Result<BlockId>
156    where
157        P: ZerostateProvider,
158    {
159        self.inner.cold_boot(boot_type, zerostate_provider).await
160    }
161}
162
163pub enum ColdBootType {
164    Genesis,
165    LatestPersistent,
166}
167
168struct StarterInner {
169    storage: CoreStorage,
170    blockchain_rpc_client: BlockchainRpcClient,
171    zerostate: ZerostateId,
172    config: StarterConfig,
173    queue_state_handler: Option<Box<dyn QueueStateHandler>>,
174}
175
176pub trait ZerostateProvider {
177    fn load_zerostates(
178        &self,
179        tracker: &MinRefMcStateTracker,
180    ) -> impl Iterator<Item = Result<ShardStateStuff>>;
181}
182
183impl ZerostateProvider for () {
184    fn load_zerostates(
185        &self,
186        _: &MinRefMcStateTracker,
187    ) -> impl Iterator<Item = Result<ShardStateStuff>> {
188        std::iter::empty()
189    }
190}
191
192pub struct FileZerostateProvider(pub Vec<PathBuf>);
193
194impl ZerostateProvider for FileZerostateProvider {
195    fn load_zerostates(
196        &self,
197        tracker: &MinRefMcStateTracker,
198    ) -> impl Iterator<Item = Result<ShardStateStuff>> {
199        self.0.iter().map(move |path| load_zerostate(tracker, path))
200    }
201}
202
203fn load_zerostate(tracker: &MinRefMcStateTracker, path: &PathBuf) -> Result<ShardStateStuff> {
204    let data = std::fs::read(path).context("failed to read file")?;
205    let file_hash = Boc::file_hash_blake(&data);
206
207    let root = Boc::decode(data).context("failed to decode BOC")?;
208    let root_hash = *root.repr_hash();
209
210    let state = root
211        .parse::<ShardStateUnsplit>()
212        .context("failed to parse state")?;
213
214    anyhow::ensure!(state.seqno == 0, "not a zerostate");
215
216    let block_id = BlockId {
217        shard: state.shard_ident,
218        seqno: state.seqno,
219        root_hash,
220        file_hash,
221    };
222
223    ShardStateStuff::from_root(&block_id, root, tracker)
224}
225
226#[async_trait::async_trait]
227pub trait QueueStateHandler: Send + Sync + 'static {
228    async fn import_from_file(
229        &self,
230        top_update: &OutMsgQueueUpdates,
231        file: File,
232        block_id: &BlockId,
233    ) -> Result<()>;
234}
235
236#[async_trait::async_trait]
237impl<T: QueueStateHandler + ?Sized> QueueStateHandler for Arc<T> {
238    async fn import_from_file(
239        &self,
240        top_update: &OutMsgQueueUpdates,
241        file: File,
242        block_id: &BlockId,
243    ) -> Result<()> {
244        T::import_from_file(self, top_update, file, block_id).await
245    }
246}
247
248#[async_trait::async_trait]
249impl<T: QueueStateHandler + ?Sized> QueueStateHandler for Box<T> {
250    async fn import_from_file(
251        &self,
252        top_update: &OutMsgQueueUpdates,
253        file: File,
254        block_id: &BlockId,
255    ) -> Result<()> {
256        T::import_from_file(self, top_update, file, block_id).await
257    }
258}