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, ShardStateStorage};
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    pub async fn init_allowed_workchains(
163        shard_state_storage: &ShardStateStorage,
164        last_mc_block_id: &BlockId,
165    ) -> Result<()> {
166        StarterInner::init_allowed_workchains(shard_state_storage, last_mc_block_id).await
167    }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
171#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
172pub enum ColdBootType {
173    Genesis,
174    LatestPersistent,
175}
176
177struct StarterInner {
178    storage: CoreStorage,
179    blockchain_rpc_client: BlockchainRpcClient,
180    zerostate: ZerostateId,
181    config: StarterConfig,
182    queue_state_handler: Option<Box<dyn QueueStateHandler>>,
183}
184
185pub trait ZerostateProvider {
186    fn load_zerostates(
187        &self,
188        tracker: &MinRefMcStateTracker,
189    ) -> impl Iterator<Item = Result<ShardStateStuff>>;
190}
191
192impl ZerostateProvider for () {
193    fn load_zerostates(
194        &self,
195        _: &MinRefMcStateTracker,
196    ) -> impl Iterator<Item = Result<ShardStateStuff>> {
197        std::iter::empty()
198    }
199}
200
201pub struct FileZerostateProvider(pub Vec<PathBuf>);
202
203impl ZerostateProvider for FileZerostateProvider {
204    fn load_zerostates(
205        &self,
206        tracker: &MinRefMcStateTracker,
207    ) -> impl Iterator<Item = Result<ShardStateStuff>> {
208        self.0.iter().map(move |path| load_zerostate(tracker, path))
209    }
210}
211
212fn load_zerostate(tracker: &MinRefMcStateTracker, path: &PathBuf) -> Result<ShardStateStuff> {
213    let data = std::fs::read(path).context("failed to read file")?;
214    let file_hash = Boc::file_hash_blake(&data);
215
216    let root = Boc::decode(data).context("failed to decode BOC")?;
217    let root_hash = *root.repr_hash();
218
219    let state = root
220        .parse::<ShardStateUnsplit>()
221        .context("failed to parse state")?;
222
223    anyhow::ensure!(state.seqno == 0, "not a zerostate");
224
225    let block_id = BlockId {
226        shard: state.shard_ident,
227        seqno: state.seqno,
228        root_hash,
229        file_hash,
230    };
231
232    ShardStateStuff::from_root(&block_id, root, tracker.insert_untracked())
233}
234
235#[async_trait::async_trait]
236pub trait QueueStateHandler: Send + Sync + 'static {
237    async fn import_from_file(
238        &self,
239        top_update: &OutMsgQueueUpdates,
240        file: File,
241        block_id: &BlockId,
242    ) -> Result<()>;
243}
244
245#[async_trait::async_trait]
246impl<T: QueueStateHandler + ?Sized> QueueStateHandler for Arc<T> {
247    async fn import_from_file(
248        &self,
249        top_update: &OutMsgQueueUpdates,
250        file: File,
251        block_id: &BlockId,
252    ) -> Result<()> {
253        T::import_from_file(self, top_update, file, block_id).await
254    }
255}
256
257#[async_trait::async_trait]
258impl<T: QueueStateHandler + ?Sized> QueueStateHandler for Box<T> {
259    async fn import_from_file(
260        &self,
261        top_update: &OutMsgQueueUpdates,
262        file: File,
263        block_id: &BlockId,
264    ) -> Result<()> {
265        T::import_from_file(self, top_update, file, block_id).await
266    }
267}