unc_client_primitives/
types.rs

1use actix::Message;
2use chrono::DateTime;
3use chrono::Utc;
4use std::collections::HashMap;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::Arc;
7use tracing::debug_span;
8use unc_chain_configs::{ClientConfig, ProtocolConfigView};
9use unc_primitives::errors::EpochError;
10use unc_primitives::hash::CryptoHash;
11use unc_primitives::merkle::{MerklePath, PartialMerkleTree};
12use unc_primitives::network::PeerId;
13use unc_primitives::sharding::ChunkHash;
14use unc_primitives::types::{
15    AccountId, BlockHeight, BlockReference, EpochId, EpochReference, MaybeBlockId, ShardId,
16    TransactionOrReceiptId,
17};
18use unc_primitives::views::validator_power_and_pledge_view::ValidatorPowerAndPledgeView;
19use unc_primitives::views::{
20    AllMinersView, BlockView, ChunkView, DownloadStatusView, EpochValidatorInfo,
21    ExecutionOutcomeWithIdView, GasPriceView, LightClientBlockLiteView, LightClientBlockView,
22    MaintenanceWindowsView, QueryRequest, QueryResponse, ReceiptView, ShardSyncDownloadView,
23    SplitStorageInfoView, StateChangesKindsView, StateChangesRequestView, StateChangesView,
24    SyncStatusView, TxStatusView,
25};
26pub use unc_primitives::views::{StatusResponse, StatusSyncInfo};
27use yansi::Color::Magenta;
28use yansi::Style;
29
30/// Combines errors coming from chain, tx pool and block producer.
31#[derive(Debug, thiserror::Error)]
32pub enum Error {
33    #[error("Chain: {0}")]
34    Chain(#[from] unc_chain_primitives::Error),
35    #[error("Chunk: {0}")]
36    Chunk(#[from] unc_chunks_primitives::Error),
37    #[error("Block Producer: {0}")]
38    BlockProducer(String),
39    #[error("Chunk Producer: {0}")]
40    ChunkProducer(String),
41    #[error("Other: {0}")]
42    Other(String),
43}
44
45impl From<unc_primitives::errors::EpochError> for Error {
46    fn from(err: unc_primitives::errors::EpochError) -> Self {
47        Error::Chain(err.into())
48    }
49}
50
51#[derive(Debug, serde::Serialize)]
52pub struct DownloadStatus {
53    pub start_time: DateTime<Utc>,
54    pub prev_update_time: DateTime<Utc>,
55    pub run_me: Arc<AtomicBool>,
56    pub error: bool,
57    pub done: bool,
58    pub state_requests_count: u64,
59    pub last_target: Option<PeerId>,
60}
61
62impl DownloadStatus {
63    pub fn new(now: DateTime<Utc>) -> Self {
64        Self {
65            start_time: now,
66            prev_update_time: now,
67            run_me: Arc::new(AtomicBool::new(true)),
68            error: false,
69            done: false,
70            state_requests_count: 0,
71            last_target: None,
72        }
73    }
74}
75
76impl Clone for DownloadStatus {
77    /// Clones an object, but it clones the value of `run_me` instead of the
78    /// `Arc` that wraps that value.
79    fn clone(&self) -> Self {
80        DownloadStatus {
81            start_time: self.start_time,
82            prev_update_time: self.prev_update_time,
83            // Creates a new `Arc` holding the same value.
84            run_me: Arc::new(AtomicBool::new(self.run_me.load(Ordering::SeqCst))),
85            error: self.error,
86            done: self.done,
87            state_requests_count: self.state_requests_count,
88            last_target: self.last_target.clone(),
89        }
90    }
91}
92
93/// Various status of syncing a specific shard.
94#[derive(Clone, Debug)]
95pub enum ShardSyncStatus {
96    StateDownloadHeader,
97    StateDownloadParts,
98    StateDownloadScheduling,
99    StateDownloadApplying,
100    StateDownloadComplete,
101    ReshardingScheduling,
102    ReshardingApplying,
103    StateSyncDone,
104}
105
106impl ShardSyncStatus {
107    pub fn repr(&self) -> u8 {
108        match self {
109            ShardSyncStatus::StateDownloadHeader => 0,
110            ShardSyncStatus::StateDownloadParts => 1,
111            ShardSyncStatus::StateDownloadScheduling => 2,
112            ShardSyncStatus::StateDownloadApplying => 3,
113            ShardSyncStatus::StateDownloadComplete => 4,
114            ShardSyncStatus::ReshardingScheduling => 5,
115            ShardSyncStatus::ReshardingApplying => 6,
116            ShardSyncStatus::StateSyncDone => 7,
117        }
118    }
119}
120
121/// Manually implement compare for ShardSyncStatus to compare only based on variant name
122impl PartialEq<Self> for ShardSyncStatus {
123    fn eq(&self, other: &Self) -> bool {
124        std::mem::discriminant(self) == std::mem::discriminant(other)
125    }
126}
127
128impl Eq for ShardSyncStatus {}
129
130impl ToString for ShardSyncStatus {
131    fn to_string(&self) -> String {
132        match self {
133            ShardSyncStatus::StateDownloadHeader => "header".to_string(),
134            ShardSyncStatus::StateDownloadParts => "parts".to_string(),
135            ShardSyncStatus::StateDownloadScheduling => "scheduling".to_string(),
136            ShardSyncStatus::StateDownloadApplying => "applying".to_string(),
137            ShardSyncStatus::StateDownloadComplete => "download complete".to_string(),
138            ShardSyncStatus::ReshardingScheduling => "resharding scheduling".to_string(),
139            ShardSyncStatus::ReshardingApplying => "resharding applying".to_string(),
140            ShardSyncStatus::StateSyncDone => "done".to_string(),
141        }
142    }
143}
144
145impl From<&DownloadStatus> for DownloadStatusView {
146    fn from(status: &DownloadStatus) -> Self {
147        DownloadStatusView { done: status.done, error: status.error }
148    }
149}
150
151impl From<ShardSyncDownload> for ShardSyncDownloadView {
152    fn from(download: ShardSyncDownload) -> Self {
153        ShardSyncDownloadView {
154            downloads: download.downloads.iter().map(|x| x.into()).collect(),
155            status: download.status.to_string(),
156        }
157    }
158}
159
160/// Stores status of shard sync and statuses of downloading shards.
161#[derive(Clone, Debug)]
162pub struct ShardSyncDownload {
163    /// Stores all download statuses. If we are downloading state parts, its
164    /// length equals the number of state parts. Otherwise it is 1, since we
165    /// have only one piece of data to download, like shard state header. It
166    /// could be 0 when we are not downloading anything but rather splitting a
167    /// shard as part of resharding.
168    pub downloads: Vec<DownloadStatus>,
169    pub status: ShardSyncStatus,
170}
171
172impl ShardSyncDownload {
173    /// Creates a instance of self which includes initial statuses for shard state header download at the given time.
174    pub fn new_download_state_header(now: DateTime<Utc>) -> Self {
175        Self {
176            downloads: vec![DownloadStatus::new(now)],
177            status: ShardSyncStatus::StateDownloadHeader,
178        }
179    }
180
181    /// Creates a instance of self which includes initial statuses for shard state parts download at the given time.
182    pub fn new_download_state_parts(now: DateTime<Utc>, num_parts: u64) -> Self {
183        // Avoid using `vec![x; num_parts]`, because each element needs to have
184        // its own independent value of `response`.
185        let mut downloads = Vec::with_capacity(num_parts as usize);
186        for _ in 0..num_parts {
187            downloads.push(DownloadStatus::new(now));
188        }
189        Self { downloads, status: ShardSyncStatus::StateDownloadParts }
190    }
191}
192
193pub fn format_shard_sync_phase_per_shard(
194    new_shard_sync: &HashMap<ShardId, ShardSyncDownload>,
195    use_colour: bool,
196) -> Vec<(ShardId, String)> {
197    new_shard_sync
198        .iter()
199        .map(|(&shard_id, shard_progress)| {
200            (shard_id, format_shard_sync_phase(shard_progress, use_colour))
201        })
202        .collect::<Vec<(_, _)>>()
203}
204
205/// Applies style if `use_colour` is enabled.
206fn paint(s: &str, style: Style, use_style: bool) -> String {
207    if use_style {
208        style.paint(s).to_string()
209    } else {
210        s.to_string()
211    }
212}
213
214/// Formats the given ShardSyncDownload for logging.
215pub fn format_shard_sync_phase(
216    shard_sync_download: &ShardSyncDownload,
217    use_colour: bool,
218) -> String {
219    match &shard_sync_download.status {
220        ShardSyncStatus::StateDownloadHeader => format!(
221            "{} requests sent {}, last target {:?}",
222            paint("HEADER", Magenta.style().bold(), use_colour),
223            shard_sync_download.downloads.get(0).map_or(0, |x| x.state_requests_count),
224            shard_sync_download.downloads.get(0).map_or(None, |x| x.last_target.as_ref()),
225        ),
226        ShardSyncStatus::StateDownloadParts => {
227            let mut num_parts_done = 0;
228            let mut num_parts_not_done = 0;
229            for download in shard_sync_download.downloads.iter() {
230                if download.done {
231                    num_parts_done += 1;
232                } else {
233                    num_parts_not_done += 1;
234                }
235            }
236            format!("num_parts_done={num_parts_done} num_parts_not_done={num_parts_not_done}")
237        }
238        status => format!("{status:?}"),
239    }
240}
241
242#[derive(Clone)]
243pub struct StateSyncStatus {
244    pub sync_hash: CryptoHash,
245    pub sync_status: HashMap<ShardId, ShardSyncDownload>,
246}
247
248/// If alternate flag was specified, write formatted sync_status per shard.
249impl std::fmt::Debug for StateSyncStatus {
250    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
251        if f.alternate() {
252            write!(
253                f,
254                "StateSyncStatus {{ sync_hash: {:?}, shard_sync: {:?} }}",
255                self.sync_hash,
256                format_shard_sync_phase_per_shard(&self.sync_status, false)
257            )
258        } else {
259            write!(
260                f,
261                "StateSyncStatus {{ sync_hash: {:?}, sync_status: {:?} }}",
262                self.sync_hash, self.sync_status
263            )
264        }
265    }
266}
267
268/// Various status sync can be in, whether it's fast sync or archival.
269#[derive(Clone, Debug, strum::AsRefStr)]
270pub enum SyncStatus {
271    /// Initial state. Not enough peers to do anything yet.
272    AwaitingPeers,
273    /// Not syncing / Done syncing.
274    NoSync,
275    /// Syncing using light-client headers to a recent epoch
276    // TODO #3488
277    // Bowen: why do we use epoch ordinal instead of epoch id?
278    EpochSync { epoch_ord: u64 },
279    /// Downloading block headers for fast sync.
280    HeaderSync {
281        /// Head height at the beginning. Not the header head height!
282        /// Used only for reporting the progress of the sync.
283        start_height: BlockHeight,
284        /// Current header head height.
285        current_height: BlockHeight,
286        /// Highest height of our peers.
287        highest_height: BlockHeight,
288    },
289    /// State sync, with different states of state sync for different shards.
290    StateSync(StateSyncStatus),
291    /// Sync state across all shards is done.
292    StateSyncDone,
293    /// Download and process blocks until the head reaches the head of the network.
294    BlockSync {
295        /// Header head height at the beginning.
296        /// Used only for reporting the progress of the sync.
297        start_height: BlockHeight,
298        /// Current head height.
299        current_height: BlockHeight,
300        /// Highest height of our peers.
301        highest_height: BlockHeight,
302    },
303}
304
305impl SyncStatus {
306    /// Get a string representation of the status variant
307    pub fn as_variant_name(&self) -> &str {
308        self.as_ref()
309    }
310
311    /// True if currently engaged in syncing the chain.
312    pub fn is_syncing(&self) -> bool {
313        match self {
314            SyncStatus::NoSync => false,
315            _ => true,
316        }
317    }
318
319    pub fn repr(&self) -> u8 {
320        match self {
321            // Represent NoSync as 0 because it is the state of a normal well-behaving node.
322            SyncStatus::NoSync => 0,
323            SyncStatus::AwaitingPeers => 1,
324            SyncStatus::EpochSync { .. } => 2,
325            SyncStatus::HeaderSync { .. } => 3,
326            SyncStatus::StateSync(_) => 4,
327            SyncStatus::StateSyncDone => 5,
328            SyncStatus::BlockSync { .. } => 6,
329        }
330    }
331
332    pub fn start_height(&self) -> Option<BlockHeight> {
333        match self {
334            SyncStatus::HeaderSync { start_height, .. } => Some(*start_height),
335            SyncStatus::BlockSync { start_height, .. } => Some(*start_height),
336            _ => None,
337        }
338    }
339
340    pub fn update(&mut self, new_value: Self) {
341        let _span =
342            debug_span!(target: "sync", "update_sync_status", old_value = ?self, ?new_value)
343                .entered();
344        *self = new_value;
345    }
346}
347
348impl From<SyncStatus> for SyncStatusView {
349    fn from(status: SyncStatus) -> Self {
350        match status {
351            SyncStatus::AwaitingPeers => SyncStatusView::AwaitingPeers,
352            SyncStatus::NoSync => SyncStatusView::NoSync,
353            SyncStatus::EpochSync { epoch_ord } => SyncStatusView::EpochSync { epoch_ord },
354            SyncStatus::HeaderSync { start_height, current_height, highest_height } => {
355                SyncStatusView::HeaderSync { start_height, current_height, highest_height }
356            }
357            SyncStatus::StateSync(state_sync_status) => SyncStatusView::StateSync(
358                state_sync_status.sync_hash,
359                state_sync_status
360                    .sync_status
361                    .into_iter()
362                    .map(|(shard_id, shard_sync)| (shard_id, shard_sync.into()))
363                    .collect(),
364            ),
365            SyncStatus::StateSyncDone => SyncStatusView::StateSyncDone,
366            SyncStatus::BlockSync { start_height, current_height, highest_height } => {
367                SyncStatusView::BlockSync { start_height, current_height, highest_height }
368            }
369        }
370    }
371}
372
373/// Actor message requesting block provider by EpochId and BlockHeight.
374#[derive(Debug)]
375pub struct GetProvider(pub EpochId, pub BlockHeight);
376
377#[derive(thiserror::Error, Debug)]
378pub enum GetProviderError {
379    #[error("IO Error: {error_message}")]
380    IOError { error_message: String },
381    #[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
382    UnknownBlock { error_message: String },
383    #[error("There are no fully synchronized blocks yet")]
384    NotSyncedYet,
385    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
386    // expected cases, we cannot statically guarantee that no other errors will be returned
387    // in the future.
388    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
389    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
390    Unreachable { error_message: String },
391}
392
393impl From<unc_chain_primitives::Error> for crate::types::GetProviderError {
394    fn from(error: unc_chain_primitives::Error) -> Self {
395        match error {
396            unc_chain_primitives::Error::IOErr(error) => {
397                Self::IOError { error_message: error.to_string() }
398            }
399            unc_chain_primitives::Error::DBNotFoundErr(error_message) => {
400                Self::UnknownBlock { error_message }
401            }
402            _ => Self::Unreachable { error_message: error.to_string() },
403        }
404    }
405}
406
407impl From<EpochError> for GetProviderError {
408    fn from(error: EpochError) -> Self {
409        Self::IOError { error_message: error.to_string() }
410    }
411}
412
413impl Message for crate::types::GetProvider {
414    type Result = Result<AccountId, crate::types::GetProviderError>;
415}
416
417/// Actor message requesting block provider by EpochId and BlockHeight.
418#[derive(Debug)]
419pub struct GetAllMiners(pub CryptoHash);
420
421#[derive(thiserror::Error, Debug)]
422pub enum GetAllMinersError {
423    #[error("IO Error: {error_message}")]
424    IOError { error_message: String },
425    #[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
426    UnknownBlock { error_message: String },
427    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
428    // expected cases, we cannot statically guarantee that no other errors will be returned
429    // in the future.
430    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
431    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
432    Unreachable { error_message: String },
433}
434
435impl From<unc_chain_primitives::Error> for crate::types::GetAllMinersError {
436    fn from(error: unc_chain_primitives::Error) -> Self {
437        match error {
438            unc_chain_primitives::Error::IOErr(error) => {
439                Self::IOError { error_message: error.to_string() }
440            }
441            unc_chain_primitives::Error::DBNotFoundErr(error_message) => {
442                Self::UnknownBlock { error_message }
443            }
444            _ => Self::Unreachable { error_message: error.to_string() },
445        }
446    }
447}
448
449impl From<EpochError> for crate::types::GetAllMinersError {
450    fn from(error: EpochError) -> Self {
451        Self::IOError { error_message: error.to_string() }
452    }
453}
454
455impl Message for crate::types::GetAllMiners {
456    type Result = Result<AllMinersView, crate::types::GetAllMinersError>;
457}
458
459/// Actor message requesting block by id, hash or sync state.
460#[derive(Debug)]
461pub struct GetBlock(pub BlockReference);
462
463#[derive(thiserror::Error, Debug)]
464pub enum GetBlockError {
465    #[error("IO Error: {error_message}")]
466    IOError { error_message: String },
467    #[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
468    UnknownBlock { error_message: String },
469    #[error("There are no fully synchronized blocks yet")]
470    NotSyncedYet,
471    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
472    // expected cases, we cannot statically guarantee that no other errors will be returned
473    // in the future.
474    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
475    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
476    Unreachable { error_message: String },
477}
478
479impl From<unc_chain_primitives::Error> for GetBlockError {
480    fn from(error: unc_chain_primitives::Error) -> Self {
481        match error {
482            unc_chain_primitives::Error::IOErr(error) => {
483                Self::IOError { error_message: error.to_string() }
484            }
485            unc_chain_primitives::Error::DBNotFoundErr(error_message) => {
486                Self::UnknownBlock { error_message }
487            }
488            _ => Self::Unreachable { error_message: error.to_string() },
489        }
490    }
491}
492
493impl GetBlock {
494    pub fn latest() -> Self {
495        Self(BlockReference::latest())
496    }
497}
498
499impl Message for GetBlock {
500    type Result = Result<BlockView, GetBlockError>;
501}
502
503/// Get block with the block merkle tree. Used for testing
504#[derive(Debug)]
505pub struct GetBlockWithMerkleTree(pub BlockReference);
506
507impl GetBlockWithMerkleTree {
508    pub fn latest() -> Self {
509        Self(BlockReference::latest())
510    }
511}
512
513impl Message for GetBlockWithMerkleTree {
514    type Result = Result<(BlockView, Arc<PartialMerkleTree>), GetBlockError>;
515}
516
517/// Actor message requesting a chunk by chunk hash and block hash + shard id.
518#[derive(Debug)]
519pub enum GetChunk {
520    Height(BlockHeight, ShardId),
521    BlockHash(CryptoHash, ShardId),
522    ChunkHash(ChunkHash),
523}
524
525impl Message for GetChunk {
526    type Result = Result<ChunkView, GetChunkError>;
527}
528
529#[derive(thiserror::Error, Debug)]
530pub enum GetChunkError {
531    #[error("IO Error: {error_message}")]
532    IOError { error_message: String },
533    #[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
534    UnknownBlock { error_message: String },
535    #[error("Shard ID {shard_id} is invalid")]
536    InvalidShardId { shard_id: u64 },
537    #[error("Chunk with hash {chunk_hash:?} has never been observed on this node")]
538    UnknownChunk { chunk_hash: ChunkHash },
539    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
540    // expected cases, we cannot statically guarantee that no other errors will be returned
541    // in the future.
542    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
543    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
544    Unreachable { error_message: String },
545}
546
547impl From<unc_chain_primitives::Error> for GetChunkError {
548    fn from(error: unc_chain_primitives::Error) -> Self {
549        match error {
550            unc_chain_primitives::Error::IOErr(error) => {
551                Self::IOError { error_message: error.to_string() }
552            }
553            unc_chain_primitives::Error::DBNotFoundErr(error_message) => {
554                Self::UnknownBlock { error_message }
555            }
556            unc_chain_primitives::Error::InvalidShardId(shard_id) => {
557                Self::InvalidShardId { shard_id }
558            }
559            unc_chain_primitives::Error::ChunkMissing(chunk_hash) => {
560                Self::UnknownChunk { chunk_hash }
561            }
562            _ => Self::Unreachable { error_message: error.to_string() },
563        }
564    }
565}
566
567/// Queries client for given path / data.
568#[derive(Clone, Debug)]
569pub struct Query {
570    pub block_reference: BlockReference,
571    pub request: QueryRequest,
572}
573
574impl Query {
575    pub fn new(block_reference: BlockReference, request: QueryRequest) -> Self {
576        Query { block_reference, request }
577    }
578}
579
580impl Message for Query {
581    type Result = Result<QueryResponse, QueryError>;
582}
583
584#[derive(thiserror::Error, Debug)]
585pub enum QueryError {
586    #[error("There are no fully synchronized blocks on the node yet")]
587    NoSyncedBlocks,
588    #[error("The node does not track the shard ID {requested_shard_id}")]
589    UnavailableShard { requested_shard_id: unc_primitives::types::ShardId },
590    #[error("Account ID {requested_account_id} is invalid")]
591    InvalidAccount {
592        requested_account_id: unc_primitives::types::AccountId,
593        block_height: unc_primitives::types::BlockHeight,
594        block_hash: unc_primitives::hash::CryptoHash,
595    },
596    #[error(
597        "Account {requested_account_id} does not exist while viewing at block #{block_height}"
598    )]
599    UnknownAccount {
600        requested_account_id: unc_primitives::types::AccountId,
601        block_height: unc_primitives::types::BlockHeight,
602        block_hash: unc_primitives::hash::CryptoHash,
603    },
604    #[error("Chip {public_key} does not exist while viewing at block #{block_height}")]
605    UnknownChip {
606        public_key: unc_crypto::PublicKey,
607        block_height: unc_primitives::types::BlockHeight,
608        block_hash: unc_primitives::hash::CryptoHash,
609    },
610    #[error(
611        "Contract code for contract ID {contract_account_id} has never been observed on the node at block #{block_height}"
612    )]
613    NoContractCode {
614        contract_account_id: unc_primitives::types::AccountId,
615        block_height: unc_primitives::types::BlockHeight,
616        block_hash: unc_primitives::hash::CryptoHash,
617    },
618    #[error("State of contract {contract_account_id} is too large to be viewed")]
619    TooLargeContractState {
620        contract_account_id: unc_primitives::types::AccountId,
621        block_height: unc_primitives::types::BlockHeight,
622        block_hash: unc_primitives::hash::CryptoHash,
623    },
624    #[error("Access key for public key {public_key} has never been observed on the node at block #{block_height}")]
625    UnknownAccessKey {
626        public_key: unc_crypto::PublicKey,
627        block_height: unc_primitives::types::BlockHeight,
628        block_hash: unc_primitives::hash::CryptoHash,
629    },
630    #[error("Function call returned an error: {vm_error}")]
631    ContractExecutionError {
632        vm_error: String,
633        block_height: unc_primitives::types::BlockHeight,
634        block_hash: unc_primitives::hash::CryptoHash,
635    },
636    #[error("The node reached its limits. Try again later. More details: {error_message}")]
637    InternalError { error_message: String },
638    #[error(
639        "The data for block #{block_height} is garbage collected on this node, use an archival node to fetch historical data"
640    )]
641    GarbageCollectedBlock {
642        block_height: unc_primitives::types::BlockHeight,
643        block_hash: unc_primitives::hash::CryptoHash,
644    },
645    #[error("Block either has never been observed on the node or has been garbage collected: {block_reference:?}")]
646    UnknownBlock { block_reference: unc_primitives::types::BlockReference },
647    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
648    // expected cases, we cannot statically guarantee that no other errors will be returned
649    // in the future.
650    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
651    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
652    Unreachable { error_message: String },
653}
654
655#[derive(Debug)]
656pub struct Status {
657    pub is_health_check: bool,
658    // If true - return more detailed information about the current status (recent blocks etc).
659    pub detailed: bool,
660}
661
662#[derive(thiserror::Error, Debug)]
663pub enum StatusError {
664    #[error("Node is syncing")]
665    NodeIsSyncing,
666    #[error("No blocks for {elapsed:?}")]
667    NoNewBlocks { elapsed: std::time::Duration },
668    #[error("Epoch Out Of Bounds {epoch_id:?}")]
669    EpochOutOfBounds { epoch_id: unc_primitives::types::EpochId },
670    #[error("The node reached its limits. Try again later. More details: {error_message}")]
671    InternalError { error_message: String },
672    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
673    // expected cases, we cannot statically guarantee that no other errors will be returned
674    // in the future.
675    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
676    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
677    Unreachable { error_message: String },
678}
679
680impl From<unc_chain_primitives::error::Error> for StatusError {
681    fn from(error: unc_chain_primitives::error::Error) -> Self {
682        match error {
683            unc_chain_primitives::error::Error::IOErr(error) => {
684                Self::InternalError { error_message: error.to_string() }
685            }
686            unc_chain_primitives::error::Error::DBNotFoundErr(error_message)
687            | unc_chain_primitives::error::Error::ValidatorError(error_message) => {
688                Self::InternalError { error_message }
689            }
690            unc_chain_primitives::error::Error::EpochOutOfBounds(epoch_id) => {
691                Self::EpochOutOfBounds { epoch_id }
692            }
693            _ => Self::Unreachable { error_message: error.to_string() },
694        }
695    }
696}
697
698impl Message for Status {
699    type Result = Result<StatusResponse, StatusError>;
700}
701
702#[derive(Debug)]
703pub struct GetNextLightClientBlock {
704    pub last_block_hash: CryptoHash,
705}
706
707#[derive(thiserror::Error, Debug)]
708pub enum GetNextLightClientBlockError {
709    #[error("Internal error: {error_message}")]
710    InternalError { error_message: String },
711    #[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
712    UnknownBlock { error_message: String },
713    #[error("Epoch Out Of Bounds {epoch_id:?}")]
714    EpochOutOfBounds { epoch_id: unc_primitives::types::EpochId },
715    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
716    // expected cases, we cannot statically guarantee that no other errors will be returned
717    // in the future.
718    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
719    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
720    Unreachable { error_message: String },
721}
722
723impl From<unc_chain_primitives::error::Error> for GetNextLightClientBlockError {
724    fn from(error: unc_chain_primitives::error::Error) -> Self {
725        match error {
726            unc_chain_primitives::error::Error::DBNotFoundErr(error_message) => {
727                Self::UnknownBlock { error_message }
728            }
729            unc_chain_primitives::error::Error::IOErr(error) => {
730                Self::InternalError { error_message: error.to_string() }
731            }
732            unc_chain_primitives::error::Error::EpochOutOfBounds(epoch_id) => {
733                Self::EpochOutOfBounds { epoch_id }
734            }
735            _ => Self::Unreachable { error_message: error.to_string() },
736        }
737    }
738}
739
740impl Message for GetNextLightClientBlock {
741    type Result = Result<Option<Arc<LightClientBlockView>>, GetNextLightClientBlockError>;
742}
743
744#[derive(Debug)]
745pub struct GetNetworkInfo {}
746
747impl Message for GetNetworkInfo {
748    type Result = Result<NetworkInfoResponse, String>;
749}
750
751#[derive(Debug)]
752pub struct GetGasPrice {
753    pub block_id: MaybeBlockId,
754}
755
756impl Message for GetGasPrice {
757    type Result = Result<GasPriceView, GetGasPriceError>;
758}
759
760#[derive(thiserror::Error, Debug)]
761pub enum GetGasPriceError {
762    #[error("Internal error: {error_message}")]
763    InternalError { error_message: String },
764    #[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
765    UnknownBlock { error_message: String },
766    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
767    // expected cases, we cannot statically guarantee that no other errors will be returned
768    // in the future.
769    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
770    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
771    Unreachable { error_message: String },
772}
773
774impl From<unc_chain_primitives::Error> for GetGasPriceError {
775    fn from(error: unc_chain_primitives::Error) -> Self {
776        match error {
777            unc_chain_primitives::Error::IOErr(error) => {
778                Self::InternalError { error_message: error.to_string() }
779            }
780            unc_chain_primitives::Error::DBNotFoundErr(error_message) => {
781                Self::UnknownBlock { error_message }
782            }
783            _ => Self::Unreachable { error_message: error.to_string() },
784        }
785    }
786}
787
788#[derive(Clone, Debug)]
789pub struct PeerInfo {
790    pub id: PeerId,
791    pub addr: Option<std::net::SocketAddr>,
792    pub account_id: Option<AccountId>,
793}
794
795#[derive(Clone, Debug)]
796pub struct KnownProducer {
797    pub account_id: AccountId,
798    pub addr: Option<std::net::SocketAddr>,
799    pub peer_id: PeerId,
800    pub next_hops: Option<Vec<PeerId>>,
801}
802
803#[derive(Debug)]
804pub struct NetworkInfoResponse {
805    pub connected_peers: Vec<PeerInfo>,
806    pub num_connected_peers: usize,
807    pub peer_max_count: u32,
808    pub sent_bytes_per_sec: u64,
809    pub received_bytes_per_sec: u64,
810    /// Accounts of known block and chunk producers from routing table.
811    pub known_producers: Vec<KnownProducer>,
812}
813
814/// Status of given transaction including all the subsequent receipts.
815#[derive(Debug)]
816pub struct TxStatus {
817    pub tx_hash: CryptoHash,
818    pub signer_account_id: AccountId,
819    pub fetch_receipt: bool,
820}
821
822#[derive(Debug)]
823pub enum TxStatusError {
824    ChainError(unc_chain_primitives::Error),
825    MissingTransaction(CryptoHash),
826    InternalError(String),
827    TimeoutError,
828}
829
830impl From<unc_chain_primitives::Error> for TxStatusError {
831    fn from(error: unc_chain_primitives::Error) -> Self {
832        Self::ChainError(error)
833    }
834}
835
836impl Message for TxStatus {
837    type Result = Result<TxStatusView, TxStatusError>;
838}
839
840#[derive(Debug)]
841pub struct GetValidatorInfo {
842    pub epoch_reference: EpochReference,
843}
844
845impl Message for GetValidatorInfo {
846    type Result = Result<EpochValidatorInfo, GetValidatorInfoError>;
847}
848
849#[derive(thiserror::Error, Debug)]
850pub enum GetProviderInfoError {
851    #[error("IO Error: {0}")]
852    IOError(String),
853    #[error("Unknown block")]
854    UnknownBlock,
855    #[error("Provider info unavailable")]
856    ProviderInfoUnavailable,
857    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
858    // expected cases, we cannot statically guarantee that no other errors will be returned
859    // in the future.
860    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
861    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {0}")]
862    Unreachable(String),
863}
864
865impl From<unc_chain_primitives::Error> for crate::types::GetProviderInfoError {
866    fn from(error: unc_chain_primitives::Error) -> Self {
867        match error {
868            unc_chain_primitives::Error::DBNotFoundErr(_)
869            | unc_chain_primitives::Error::BlockOutOfBounds(_) => Self::UnknownBlock,
870            unc_chain_primitives::Error::IOErr(s) => Self::IOError(s.to_string()),
871            _ => Self::Unreachable(error.to_string()),
872        }
873    }
874}
875#[derive(thiserror::Error, Debug)]
876pub enum GetValidatorInfoError {
877    #[error("IO Error: {0}")]
878    IOError(String),
879    #[error("Unknown epoch")]
880    UnknownEpoch,
881    #[error("Validator info unavailable")]
882    ValidatorInfoUnavailable,
883    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
884    // expected cases, we cannot statically guarantee that no other errors will be returned
885    // in the future.
886    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
887    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {0}")]
888    Unreachable(String),
889}
890
891impl From<unc_chain_primitives::Error> for GetValidatorInfoError {
892    fn from(error: unc_chain_primitives::Error) -> Self {
893        match error {
894            unc_chain_primitives::Error::DBNotFoundErr(_)
895            | unc_chain_primitives::Error::EpochOutOfBounds(_) => Self::UnknownEpoch,
896            unc_chain_primitives::Error::IOErr(s) => Self::IOError(s.to_string()),
897            _ => Self::Unreachable(error.to_string()),
898        }
899    }
900}
901
902#[derive(Debug)]
903pub struct GetValidatorOrdered {
904    pub block_id: MaybeBlockId,
905}
906
907impl Message for GetValidatorOrdered {
908    type Result = Result<Vec<ValidatorPowerAndPledgeView>, GetValidatorInfoError>;
909}
910
911#[derive(Debug)]
912pub struct GetStateChanges {
913    pub block_hash: CryptoHash,
914    pub state_changes_request: StateChangesRequestView,
915}
916
917#[derive(thiserror::Error, Debug)]
918pub enum GetStateChangesError {
919    #[error("IO Error: {error_message}")]
920    IOError { error_message: String },
921    #[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
922    UnknownBlock { error_message: String },
923    #[error("There are no fully synchronized blocks yet")]
924    NotSyncedYet,
925    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
926    // expected cases, we cannot statically guarantee that no other errors will be returned
927    // in the future.
928    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
929    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
930    Unreachable { error_message: String },
931}
932
933impl From<unc_chain_primitives::Error> for GetStateChangesError {
934    fn from(error: unc_chain_primitives::Error) -> Self {
935        match error {
936            unc_chain_primitives::Error::IOErr(error) => {
937                Self::IOError { error_message: error.to_string() }
938            }
939            unc_chain_primitives::Error::DBNotFoundErr(error_message) => {
940                Self::UnknownBlock { error_message }
941            }
942            _ => Self::Unreachable { error_message: error.to_string() },
943        }
944    }
945}
946
947impl Message for GetStateChanges {
948    type Result = Result<StateChangesView, GetStateChangesError>;
949}
950
951#[derive(Debug)]
952pub struct GetStateChangesInBlock {
953    pub block_hash: CryptoHash,
954}
955
956impl Message for GetStateChangesInBlock {
957    type Result = Result<StateChangesKindsView, GetStateChangesError>;
958}
959
960#[derive(Debug)]
961pub struct GetStateChangesWithCauseInBlock {
962    pub block_hash: CryptoHash,
963}
964
965impl Message for GetStateChangesWithCauseInBlock {
966    type Result = Result<StateChangesView, GetStateChangesError>;
967}
968
969#[derive(Debug)]
970pub struct GetStateChangesWithCauseInBlockForTrackedShards {
971    pub block_hash: CryptoHash,
972    pub epoch_id: EpochId,
973}
974
975impl Message for GetStateChangesWithCauseInBlockForTrackedShards {
976    type Result = Result<HashMap<ShardId, StateChangesView>, GetStateChangesError>;
977}
978
979#[derive(Debug)]
980pub struct GetExecutionOutcome {
981    pub id: TransactionOrReceiptId,
982}
983
984#[derive(thiserror::Error, Debug)]
985pub enum GetExecutionOutcomeError {
986    #[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
987    UnknownBlock { error_message: String },
988    #[error("Inconsistent state. Total number of shards is {number_or_shards} but the execution outcome is in shard {execution_outcome_shard_id}")]
989    InconsistentState {
990        number_or_shards: usize,
991        execution_outcome_shard_id: unc_primitives::types::ShardId,
992    },
993    #[error("{transaction_or_receipt_id} has not been confirmed")]
994    NotConfirmed { transaction_or_receipt_id: unc_primitives::hash::CryptoHash },
995    #[error("{transaction_or_receipt_id} does not exist")]
996    UnknownTransactionOrReceipt { transaction_or_receipt_id: unc_primitives::hash::CryptoHash },
997    #[error("Node doesn't track the shard where {transaction_or_receipt_id} is executed")]
998    UnavailableShard {
999        transaction_or_receipt_id: unc_primitives::hash::CryptoHash,
1000        shard_id: unc_primitives::types::ShardId,
1001    },
1002    #[error("Internal error: {error_message}")]
1003    InternalError { error_message: String },
1004    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
1005    // expected cases, we cannot statically guarantee that no other errors will be returned
1006    // in the future.
1007    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
1008    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
1009    Unreachable { error_message: String },
1010}
1011
1012impl From<TxStatusError> for GetExecutionOutcomeError {
1013    fn from(error: TxStatusError) -> Self {
1014        match error {
1015            TxStatusError::ChainError(err) => {
1016                Self::InternalError { error_message: err.to_string() }
1017            }
1018            _ => Self::Unreachable { error_message: format!("{:?}", error) },
1019        }
1020    }
1021}
1022
1023impl From<unc_chain_primitives::error::Error> for GetExecutionOutcomeError {
1024    fn from(error: unc_chain_primitives::error::Error) -> Self {
1025        match error {
1026            unc_chain_primitives::Error::IOErr(error) => {
1027                Self::InternalError { error_message: error.to_string() }
1028            }
1029            unc_chain_primitives::Error::DBNotFoundErr(error_message) => {
1030                Self::UnknownBlock { error_message }
1031            }
1032            _ => Self::Unreachable { error_message: error.to_string() },
1033        }
1034    }
1035}
1036
1037pub struct GetExecutionOutcomeResponse {
1038    pub outcome_proof: ExecutionOutcomeWithIdView,
1039    pub outcome_root_proof: MerklePath,
1040}
1041
1042impl Message for GetExecutionOutcome {
1043    type Result = Result<GetExecutionOutcomeResponse, GetExecutionOutcomeError>;
1044}
1045
1046#[derive(Debug)]
1047pub struct GetExecutionOutcomesForBlock {
1048    pub block_hash: CryptoHash,
1049}
1050
1051impl Message for GetExecutionOutcomesForBlock {
1052    type Result = Result<HashMap<ShardId, Vec<ExecutionOutcomeWithIdView>>, String>;
1053}
1054
1055#[derive(Debug)]
1056pub struct GetBlockProof {
1057    pub block_hash: CryptoHash,
1058    pub head_block_hash: CryptoHash,
1059}
1060
1061pub struct GetBlockProofResponse {
1062    pub block_header_lite: LightClientBlockLiteView,
1063    pub proof: MerklePath,
1064}
1065
1066#[derive(thiserror::Error, Debug)]
1067pub enum GetBlockProofError {
1068    #[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
1069    UnknownBlock { error_message: String },
1070    #[error("Internal error: {error_message}")]
1071    InternalError { error_message: String },
1072    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
1073    // expected cases, we cannot statically guarantee that no other errors will be returned
1074    // in the future.
1075    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
1076    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {error_message}")]
1077    Unreachable { error_message: String },
1078}
1079
1080impl From<unc_chain_primitives::error::Error> for GetBlockProofError {
1081    fn from(error: unc_chain_primitives::error::Error) -> Self {
1082        match error {
1083            unc_chain_primitives::error::Error::DBNotFoundErr(error_message) => {
1084                Self::UnknownBlock { error_message }
1085            }
1086            unc_chain_primitives::error::Error::Other(error_message) => {
1087                Self::InternalError { error_message }
1088            }
1089            err => Self::Unreachable { error_message: err.to_string() },
1090        }
1091    }
1092}
1093
1094impl Message for GetBlockProof {
1095    type Result = Result<GetBlockProofResponse, GetBlockProofError>;
1096}
1097
1098#[derive(Debug)]
1099pub struct GetReceipt {
1100    pub receipt_id: CryptoHash,
1101}
1102
1103#[derive(thiserror::Error, Debug)]
1104pub enum GetReceiptError {
1105    #[error("IO Error: {0}")]
1106    IOError(String),
1107    #[error("Receipt with id {0} has never been observed on this node")]
1108    UnknownReceipt(unc_primitives::hash::CryptoHash),
1109    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
1110    // expected cases, we cannot statically guarantee that no other errors will be returned
1111    // in the future.
1112    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
1113    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {0}")]
1114    Unreachable(String),
1115}
1116
1117impl From<unc_chain_primitives::Error> for GetReceiptError {
1118    fn from(error: unc_chain_primitives::Error) -> Self {
1119        match error {
1120            unc_chain_primitives::Error::IOErr(error) => Self::IOError(error.to_string()),
1121            _ => Self::Unreachable(error.to_string()),
1122        }
1123    }
1124}
1125
1126impl Message for GetReceipt {
1127    type Result = Result<Option<ReceiptView>, GetReceiptError>;
1128}
1129
1130#[derive(Debug)]
1131pub struct GetProtocolConfig(pub BlockReference);
1132
1133impl Message for GetProtocolConfig {
1134    type Result = Result<ProtocolConfigView, GetProtocolConfigError>;
1135}
1136
1137#[derive(thiserror::Error, Debug)]
1138pub enum GetProtocolConfigError {
1139    #[error("IO Error: {0}")]
1140    IOError(String),
1141    #[error("Block has never been observed: {0}")]
1142    UnknownBlock(String),
1143    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
1144    // expected cases, we cannot statically guarantee that no other errors will be returned
1145    // in the future.
1146    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
1147    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {0}")]
1148    Unreachable(String),
1149}
1150
1151impl From<unc_chain_primitives::Error> for GetProtocolConfigError {
1152    fn from(error: unc_chain_primitives::Error) -> Self {
1153        match error {
1154            unc_chain_primitives::Error::IOErr(error) => Self::IOError(error.to_string()),
1155            unc_chain_primitives::Error::DBNotFoundErr(s) => Self::UnknownBlock(s),
1156            _ => Self::Unreachable(error.to_string()),
1157        }
1158    }
1159}
1160
1161#[derive(Debug)]
1162pub struct GetMaintenanceWindows {
1163    pub account_id: AccountId,
1164}
1165
1166impl Message for GetMaintenanceWindows {
1167    type Result = Result<MaintenanceWindowsView, GetMaintenanceWindowsError>;
1168}
1169
1170#[derive(thiserror::Error, Debug)]
1171pub enum GetMaintenanceWindowsError {
1172    #[error("IO Error: {0}")]
1173    IOError(String),
1174    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {0}")]
1175    Unreachable(String),
1176}
1177
1178impl From<unc_chain_primitives::Error> for GetMaintenanceWindowsError {
1179    fn from(error: unc_chain_primitives::Error) -> Self {
1180        match error {
1181            unc_chain_primitives::Error::IOErr(error) => Self::IOError(error.to_string()),
1182            _ => Self::Unreachable(error.to_string()),
1183        }
1184    }
1185}
1186
1187#[derive(Debug)]
1188pub struct GetClientConfig {}
1189
1190impl Message for GetClientConfig {
1191    type Result = Result<ClientConfig, GetClientConfigError>;
1192}
1193
1194#[derive(thiserror::Error, Debug)]
1195pub enum GetClientConfigError {
1196    #[error("IO Error: {0}")]
1197    IOError(String),
1198    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
1199    // expected cases, we cannot statically guarantee that no other errors will be returned
1200    // in the future.
1201    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
1202    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {0}")]
1203    Unreachable(String),
1204}
1205
1206impl From<unc_chain_primitives::Error> for GetClientConfigError {
1207    fn from(error: unc_chain_primitives::Error) -> Self {
1208        match error {
1209            unc_chain_primitives::Error::IOErr(error) => Self::IOError(error.to_string()),
1210            _ => Self::Unreachable(error.to_string()),
1211        }
1212    }
1213}
1214
1215#[derive(Debug)]
1216pub struct GetSplitStorageInfo {}
1217
1218impl Message for GetSplitStorageInfo {
1219    type Result = Result<SplitStorageInfoView, GetSplitStorageInfoError>;
1220}
1221
1222#[derive(thiserror::Error, Debug)]
1223pub enum GetSplitStorageInfoError {
1224    #[error("IO Error: {0}")]
1225    IOError(String),
1226    // NOTE: Currently, the underlying errors are too broad, and while we tried to handle
1227    // expected cases, we cannot statically guarantee that no other errors will be returned
1228    // in the future.
1229    // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
1230    #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/utnet-org/utility/issues/new/choose. Details: {0}")]
1231    Unreachable(String),
1232}
1233
1234impl From<unc_chain_primitives::Error> for GetSplitStorageInfoError {
1235    fn from(error: unc_chain_primitives::Error) -> Self {
1236        match error {
1237            unc_chain_primitives::Error::IOErr(error) => Self::IOError(error.to_string()),
1238            _ => Self::Unreachable(error.to_string()),
1239        }
1240    }
1241}
1242
1243impl From<std::io::Error> for GetSplitStorageInfoError {
1244    fn from(error: std::io::Error) -> Self {
1245        Self::IOError(error.to_string())
1246    }
1247}
1248
1249#[cfg(feature = "sandbox")]
1250#[derive(Debug)]
1251pub enum SandboxMessage {
1252    SandboxPatchState(Vec<unc_primitives::state_record::StateRecord>),
1253    SandboxPatchStateStatus,
1254    SandboxFastForward(unc_primitives::types::BlockHeightDelta),
1255    SandboxFastForwardStatus,
1256}
1257
1258#[cfg(feature = "sandbox")]
1259#[derive(Eq, PartialEq, Debug, actix::MessageResponse)]
1260pub enum SandboxResponse {
1261    SandboxPatchStateFinished(bool),
1262    SandboxFastForwardFinished(bool),
1263    SandboxFastForwardFailed(String),
1264    SandboxNoResponse,
1265}
1266#[cfg(feature = "sandbox")]
1267impl Message for SandboxMessage {
1268    type Result = SandboxResponse;
1269}