1use std::{
6 collections::{BTreeMap, HashMap, HashSet, VecDeque},
7 sync::Arc,
8};
9
10use futures::{stream::FuturesUnordered, TryStreamExt as _};
11use linera_base::{
12 crypto::{CryptoHash, ValidatorPublicKey},
13 data_types::{ArithmeticError, Blob, BlockHeight},
14 identifiers::{BlobId, ChainId, EventId, StreamId},
15};
16use linera_chain::{
17 data_types::{BlockProposal, BundleExecutionPolicy, ProposedBlock},
18 types::{Block, ConfirmedBlockCertificate, GenericCertificate},
19};
20use linera_execution::{BlobState, Query, QueryOutcome, ResourceTracker};
21use linera_storage::{Arc as CacheArc, Storage};
22use linera_views::ViewError;
23use thiserror::Error;
24use tracing::{instrument, warn};
25
26use crate::{
27 chain_worker::ProcessConfirmedBlockMode,
28 data_types::{ChainInfo, ChainInfoQuery, ChainInfoResponse},
29 notifier::Notifier,
30 worker::{ProcessableCertificate, WorkerError, WorkerState},
31};
32
33pub struct LocalNode<S>
35where
36 S: Storage,
37{
38 state: WorkerState<S>,
39}
40
41#[derive(Clone)]
43pub struct LocalNodeClient<S>
44where
45 S: Storage,
46{
47 node: Arc<LocalNode<S>>,
48}
49
50#[derive(Debug, Error, strum::IntoStaticStr)]
52#[allow(missing_docs)]
53pub enum LocalNodeError {
54 #[error(transparent)]
55 ArithmeticError(#[from] ArithmeticError),
56
57 #[error(transparent)]
58 ViewError(#[from] ViewError),
59
60 #[error("Worker operation failed: {0}")]
61 WorkerError(WorkerError),
62
63 #[error("The local node doesn't have an active chain {0}")]
64 InactiveChain(ChainId),
65
66 #[error("The chain info response received from the local node is invalid")]
67 InvalidChainInfoResponse,
68
69 #[error("Blobs not found: {0:?}")]
70 BlobsNotFound(Vec<BlobId>),
71
72 #[error("Events not found: {0:?}")]
73 EventsNotFound(Vec<EventId>),
74}
75
76impl LocalNodeError {
77 pub fn error_type(&self) -> String {
80 match self {
81 LocalNodeError::WorkerError(worker_error) => worker_error.error_type(),
82 other => {
83 let variant: &'static str = other.into();
84 format!("LocalNodeError::{variant}")
85 }
86 }
87 }
88}
89
90impl From<WorkerError> for LocalNodeError {
91 fn from(error: WorkerError) -> Self {
92 match error {
93 WorkerError::BlobsNotFound(blob_ids) => LocalNodeError::BlobsNotFound(blob_ids),
94 WorkerError::EventsNotFound(event_ids) => LocalNodeError::EventsNotFound(event_ids),
95 error => LocalNodeError::WorkerError(error),
96 }
97 }
98}
99
100impl<S> LocalNodeClient<S>
101where
102 S: Storage + Clone + 'static,
103{
104 #[instrument(level = "trace", skip_all)]
105 pub async fn handle_block_proposal(
106 &self,
107 proposal: BlockProposal,
108 ) -> Result<ChainInfoResponse, LocalNodeError> {
109 let (response, _actions) = Box::pin(self.node.state.handle_block_proposal(proposal)).await;
111 Ok(response?)
112 }
113
114 #[instrument(level = "trace", skip_all)]
115 pub async fn handle_certificate<T>(
116 &self,
117 certificate: GenericCertificate<T>,
118 notifier: &impl Notifier,
119 ) -> Result<ChainInfoResponse, LocalNodeError>
120 where
121 T: ProcessableCertificate,
122 {
123 Ok(Box::pin(
124 self.node
125 .state
126 .fully_handle_certificate_with_notifications(certificate, notifier),
127 )
128 .await?)
129 }
130
131 #[instrument(level = "trace", skip_all)]
135 pub async fn handle_confirmed_certificate(
136 &self,
137 certificate: ConfirmedBlockCertificate,
138 mode: ProcessConfirmedBlockMode,
139 notifier: &impl Notifier,
140 ) -> Result<ChainInfoResponse, LocalNodeError> {
141 Ok(Box::pin(
142 self.node
143 .state
144 .fully_handle_confirmed_certificate_with_notifications(certificate, mode, notifier),
145 )
146 .await?)
147 }
148
149 #[instrument(level = "trace", skip_all)]
150 pub async fn handle_chain_info_query(
151 &self,
152 query: ChainInfoQuery,
153 ) -> Result<ChainInfoResponse, LocalNodeError> {
154 let (response, _actions) = self.node.state.handle_chain_info_query(query).await?;
156 Ok(response)
157 }
158
159 #[instrument(level = "trace", skip_all)]
160 pub fn new(state: WorkerState<S>) -> Self {
161 Self {
162 node: Arc::new(LocalNode { state }),
163 }
164 }
165
166 #[instrument(level = "trace", skip_all)]
167 pub(crate) fn storage_client(&self) -> S {
168 self.node.state.storage_client().clone()
169 }
170
171 #[instrument(level = "trace", skip_all)]
176 pub async fn stage_block_execution(
177 &self,
178 block: ProposedBlock,
179 round: Option<u32>,
180 published_blobs: Vec<Blob>,
181 policy: BundleExecutionPolicy,
182 ) -> Result<
183 (
184 ProposedBlock,
185 Block,
186 ChainInfoResponse,
187 ResourceTracker,
188 HashSet<ChainId>,
189 ),
190 LocalNodeError,
191 > {
192 Ok(self
193 .node
194 .state
195 .stage_block_execution(block, round, published_blobs, policy)
196 .await?)
197 }
198
199 pub async fn read_blobs_from_storage(
201 &self,
202 blob_ids: &[BlobId],
203 ) -> Result<Option<Vec<CacheArc<Blob>>>, LocalNodeError> {
204 let storage = self.storage_client();
205 Ok(storage.read_blobs(blob_ids).await?.into_iter().collect())
206 }
207
208 pub async fn read_blob_states_from_storage(
210 &self,
211 blob_ids: &[BlobId],
212 ) -> Result<Vec<BlobState>, LocalNodeError> {
213 let storage = self.storage_client();
214 let mut blobs_not_found = Vec::new();
215 let mut blob_states = Vec::new();
216 for (blob_state, blob_id) in storage
217 .read_blob_states(blob_ids)
218 .await?
219 .into_iter()
220 .zip(blob_ids)
221 {
222 match blob_state {
223 None => blobs_not_found.push(*blob_id),
224 Some(blob_state) => blob_states.push(blob_state),
225 }
226 }
227 if !blobs_not_found.is_empty() {
228 return Err(LocalNodeError::BlobsNotFound(blobs_not_found));
229 }
230 Ok(blob_states)
231 }
232
233 pub async fn get_locking_blobs(
236 &self,
237 blob_ids: impl IntoIterator<Item = &BlobId>,
238 chain_id: ChainId,
239 ) -> Result<Option<Vec<Blob>>, LocalNodeError> {
240 let blob_ids_vec: Vec<_> = blob_ids.into_iter().copied().collect();
241 Ok(self
242 .node
243 .state
244 .get_locking_blobs(chain_id, blob_ids_vec)
245 .await?)
246 }
247
248 pub async fn store_blobs(&self, blobs: &[Blob]) -> Result<(), LocalNodeError> {
250 let storage = self.storage_client();
251 storage.maybe_write_blobs(blobs).await?;
252 Ok(())
253 }
254
255 pub async fn handle_pending_blobs(
256 &self,
257 chain_id: ChainId,
258 blobs: Vec<Blob>,
259 ) -> Result<(), LocalNodeError> {
260 for blob in blobs {
261 self.node.state.handle_pending_blob(chain_id, blob).await?;
262 }
263 Ok(())
264 }
265
266 #[instrument(level = "trace", skip(self))]
272 pub async fn chain_state_view(
273 &self,
274 chain_id: ChainId,
275 ) -> Result<crate::worker::ChainStateViewReadGuard<S>, LocalNodeError> {
276 Ok(self.node.state.chain_state_view(chain_id).await?)
277 }
278
279 #[instrument(level = "trace", skip(self))]
280 pub(crate) async fn chain_info(
281 &self,
282 chain_id: ChainId,
283 ) -> Result<Box<ChainInfo>, LocalNodeError> {
284 let query = ChainInfoQuery::new(chain_id);
285 Ok(self.handle_chain_info_query(query).await?.info)
286 }
287
288 #[instrument(level = "trace", skip(self, query))]
289 pub async fn query_application(
290 &self,
291 chain_id: ChainId,
292 query: Query,
293 block_hash: Option<CryptoHash>,
294 ) -> Result<(QueryOutcome, BlockHeight), LocalNodeError> {
295 let result = self
296 .node
297 .state
298 .query_application(chain_id, query, block_hash)
299 .await?;
300 Ok(result)
301 }
302
303 #[instrument(level = "trace", skip(self, notifier))]
311 pub async fn retry_pending_cross_chain_requests(
312 &self,
313 sender_chain: ChainId,
314 notifier: &impl Notifier,
315 ) -> Result<(), LocalNodeError> {
316 let actions = self
317 .node
318 .state
319 .cross_chain_network_actions(sender_chain)
320 .await?;
321 let mut requests = VecDeque::from_iter(actions.cross_chain_requests);
322 while let Some(request) = requests.pop_front() {
323 let new_actions = self.node.state.handle_cross_chain_request(request).await?;
324 notifier.notify(&new_actions.notifications);
325 requests.extend(new_actions.cross_chain_requests);
326 }
327 Ok(())
328 }
329
330 pub async fn next_outbox_heights(
334 &self,
335 chain_ids: impl IntoIterator<Item = &ChainId>,
336 receiver_id: ChainId,
337 ) -> Result<BTreeMap<ChainId, BlockHeight>, LocalNodeError> {
338 let futures = chain_ids
339 .into_iter()
340 .map(|chain_id| async move {
341 let (next_block_height, next_height_to_schedule) = match self
342 .get_tip_state_and_outbox_info(*chain_id, receiver_id)
343 .await
344 {
345 Ok(info) => info,
346 Err(LocalNodeError::BlobsNotFound(_) | LocalNodeError::InactiveChain(_)) => {
347 return Ok((*chain_id, BlockHeight::ZERO))
348 }
349 Err(err) => Err(err)?,
350 };
351 let next_height = if let Some(scheduled_height) = next_height_to_schedule {
352 next_block_height.max(scheduled_height)
353 } else {
354 next_block_height
355 };
356 Ok::<_, LocalNodeError>((*chain_id, next_height))
357 })
358 .collect::<FuturesUnordered<_>>();
359 futures.try_collect().await
360 }
361
362 pub async fn update_received_certificate_trackers(
363 &self,
364 chain_id: ChainId,
365 new_trackers: BTreeMap<ValidatorPublicKey, u64>,
366 ) -> Result<(), LocalNodeError> {
367 self.node
368 .state
369 .update_received_certificate_trackers(chain_id, new_trackers)
370 .await?;
371 Ok(())
372 }
373
374 pub async fn get_preprocessed_block_hashes(
375 &self,
376 chain_id: ChainId,
377 start: BlockHeight,
378 end: BlockHeight,
379 ) -> Result<Vec<linera_base::crypto::CryptoHash>, LocalNodeError> {
380 Ok(self
381 .node
382 .state
383 .get_preprocessed_block_hashes(chain_id, start, end)
384 .await?)
385 }
386
387 pub async fn get_inbox_next_height(
388 &self,
389 chain_id: ChainId,
390 origin: ChainId,
391 ) -> Result<BlockHeight, LocalNodeError> {
392 Ok(self
393 .node
394 .state
395 .get_inbox_next_height(chain_id, origin)
396 .await?)
397 }
398
399 pub async fn get_block_hashes(
401 &self,
402 chain_id: ChainId,
403 heights: Vec<BlockHeight>,
404 ) -> Result<Vec<CryptoHash>, LocalNodeError> {
405 Ok(self.node.state.get_block_hashes(chain_id, heights).await?)
406 }
407
408 pub async fn get_proposed_blobs(
410 &self,
411 chain_id: ChainId,
412 blob_ids: Vec<BlobId>,
413 ) -> Result<Vec<Blob>, LocalNodeError> {
414 Ok(self
415 .node
416 .state
417 .get_proposed_blobs(chain_id, blob_ids)
418 .await?)
419 }
420
421 pub async fn get_event_subscriptions(
423 &self,
424 chain_id: ChainId,
425 ) -> Result<crate::worker::EventSubscriptionsResult, LocalNodeError> {
426 Ok(self.node.state.get_event_subscriptions(chain_id).await?)
427 }
428
429 pub async fn next_expected_events(
431 &self,
432 chain_id: ChainId,
433 stream_ids: Vec<StreamId>,
434 ) -> Result<BTreeMap<StreamId, u32>, LocalNodeError> {
435 Ok(self
436 .node
437 .state
438 .next_expected_events(chain_id, stream_ids)
439 .await?)
440 }
441
442 pub async fn get_stream_event_count(
444 &self,
445 chain_id: ChainId,
446 stream_id: StreamId,
447 ) -> Result<Option<u32>, LocalNodeError> {
448 Ok(self
449 .node
450 .state
451 .get_stream_event_count(chain_id, stream_id)
452 .await?)
453 }
454
455 pub async fn get_received_certificate_trackers(
457 &self,
458 chain_id: ChainId,
459 ) -> Result<HashMap<ValidatorPublicKey, u64>, LocalNodeError> {
460 Ok(self
461 .node
462 .state
463 .get_received_certificate_trackers(chain_id)
464 .await?)
465 }
466
467 pub async fn get_tip_state_and_outbox_info(
469 &self,
470 chain_id: ChainId,
471 receiver_id: ChainId,
472 ) -> Result<(BlockHeight, Option<BlockHeight>), LocalNodeError> {
473 Ok(self
474 .node
475 .state
476 .get_tip_state_and_outbox_info(chain_id, receiver_id)
477 .await?)
478 }
479
480 pub async fn get_next_height_to_preprocess(
482 &self,
483 chain_id: ChainId,
484 ) -> Result<BlockHeight, LocalNodeError> {
485 Ok(self
486 .node
487 .state
488 .get_next_height_to_preprocess(chain_id)
489 .await?)
490 }
491
492 pub async fn get_manager_seed(&self, chain_id: ChainId) -> Result<u64, LocalNodeError> {
494 Ok(self.node.state.get_manager_seed(chain_id).await?)
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501
502 #[test]
503 fn error_type_delegates_to_worker_error() {
504 assert_eq!(
505 LocalNodeError::WorkerError(WorkerError::InvalidOwner).error_type(),
506 "WorkerError::InvalidOwner"
507 );
508 }
509
510 #[test]
511 fn error_type_falls_back_to_local_node_variant() {
512 assert_eq!(
513 LocalNodeError::InvalidChainInfoResponse.error_type(),
514 "LocalNodeError::InvalidChainInfoResponse"
515 );
516 }
517}