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 #[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 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#[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 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}