1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
use std::{io, path::PathBuf};

use thiserror::Error;
use tracing::error;

use casper_hashing::Digest;
use casper_types::{bytesrepr, crypto, EraId};

use super::lmdb_ext::LmdbExtError;
use crate::types::{
    error::BlockValidationError, BlockBody, BlockHash, BlockHashAndHeight, BlockHeader, DeployHash,
    FinalitySignature, FinalitySignatureId,
};

/// A fatal storage component error.
///
/// An error of this kinds indicates that storage is corrupted or otherwise irrecoverably broken, at
/// least for the moment. It should usually be followed by swift termination of the node.
#[derive(Debug, Error)]
pub enum FatalStorageError {
    /// Failure to create the root database directory.
    #[error("failed to create database directory `{}`: {}", .0.display(), .1)]
    CreateDatabaseDirectory(PathBuf, io::Error),
    /// Found a duplicate block-at-height index entry.
    #[error("duplicate entries for block at height {height}: {first} / {second}")]
    DuplicateBlockIndex {
        /// Height at which duplicate was found.
        height: u64,
        /// First block hash encountered at `height`.
        first: BlockHash,
        /// Second block hash encountered at `height`.
        second: BlockHash,
    },
    /// Found a duplicate switch-block-at-era-id index entry.
    #[error("duplicate entries for switch block at era id {era_id}: {first} / {second}")]
    DuplicateEraIdIndex {
        /// Era ID at which duplicate was found.
        era_id: EraId,
        /// First block hash encountered at `era_id`.
        first: BlockHash,
        /// Second block hash encountered at `era_id`.
        second: BlockHash,
    },
    /// Found a duplicate switch-block-at-era-id index entry.
    #[error("duplicate entries for blocks for deploy {deploy_hash}: {first} / {second}")]
    DuplicateDeployIndex {
        /// Deploy hash at which duplicate was found.
        deploy_hash: DeployHash,
        /// First block hash encountered at `deploy_hash`.
        first: BlockHashAndHeight,
        /// Second block hash encountered at `deploy_hash`.
        second: BlockHashAndHeight,
    },
    /// LMDB error while operating.
    #[error("internal database error: {0}")]
    InternalStorage(#[from] LmdbExtError),
    /// An internal DB error - blocks should be overwritten.
    #[error("failed overwriting block")]
    FailedToOverwriteBlock,
    /// Filesystem error while trying to move file.
    #[error("unable to move file {source_path} to {dest_path}: {original_error}")]
    UnableToMoveFile {
        /// The path to the file that should have been moved.
        source_path: PathBuf,
        /// The path where the file should have been moved to.
        dest_path: PathBuf,
        /// The original `io::Error` from `fs::rename`.
        original_error: io::Error,
    },
    /// Mix of missing and found storage files.
    #[error("expected files to exist: {missing_files:?}.")]
    MissingStorageFiles {
        /// The files that were not be found in the storage directory.
        missing_files: Vec<PathBuf>,
    },
    /// Error when validating a block.
    #[error(transparent)]
    BlockValidation(#[from] BlockValidationError),
    /// A block header was not stored under its hash.
    #[error(
        "Block header not stored under its hash. \
         Queried block hash bytes: {queried_block_hash_bytes:x?}, \
         Found block header hash bytes: {found_block_header_hash:x?}, \
         Block header: {block_header}"
    )]
    BlockHeaderNotStoredUnderItsHash {
        /// The queried block hash.
        queried_block_hash_bytes: Vec<u8>,
        /// The actual header of the block hash.
        found_block_header_hash: BlockHash,
        /// The block header found in storage.
        block_header: Box<BlockHeader>,
    },
    /// Block body did not have a block header.
    #[error(
        "No block header corresponding to block body found in LMDB. \
         Block body hash: {block_body_hash:?}, \
         Block body: {block_body:?}"
    )]
    NoBlockHeaderForBlockBody {
        /// The block body hash.
        block_body_hash: Digest,
        /// The block body.
        block_body: Box<BlockBody>,
    },
    /// Could not verify finality signatures for block.
    #[error("{0} in signature verification. Database is corrupted.")]
    SignatureVerification(crypto::Error),
    /// Corrupted block signature index.
    #[error(
        "Block signatures not indexed by their block hash. \
         Key bytes in LMDB: {raw_key:x?}, \
         Block hash bytes in record: {block_hash_bytes:x?}"
    )]
    CorruptedBlockSignatureIndex {
        /// The key in the block signature index.
        raw_key: Vec<u8>,
        /// The block hash of the signatures found in the index.
        block_hash_bytes: Vec<u8>,
    },
    /// Switch block does not contain era end.
    #[error("switch block does not contain era end: {0:?}")]
    InvalidSwitchBlock(Box<BlockHeader>),
    /// A block body was found to have more parts than expected.
    #[error(
        "Found an unexpected part of a block body in the database: \
        {part_hash:?}"
    )]
    UnexpectedBlockBodyPart {
        /// The block body with the issue.
        block_body_hash: Digest,
        /// The hash of the superfluous body part.
        part_hash: Digest,
    },
    /// Failed to serialize an item that was found in local storage.
    #[error("failed to serialized stored item")]
    StoredItemSerializationFailure(#[source] bincode::Error),
    /// We tried to store finalized approvals for a nonexistent deploy.
    #[error(
        "Tried to store FinalizedApprovals for a nonexistent deploy. Deploy hash: {deploy_hash:?}"
    )]
    UnexpectedFinalizedApprovals {
        /// The missing deploy hash.
        deploy_hash: DeployHash,
    },
    /// `ToBytes` serialization failure of an item that should never fail to serialize.
    #[error("unexpected serialization failure: {0}")]
    UnexpectedSerializationFailure(bytesrepr::Error),
    /// `ToBytes` deserialization failure of an item that should never fail to serialize.
    #[error("unexpected deserialization failure: {0}")]
    UnexpectedDeserializationFailure(bytesrepr::Error),
    /// Stored finalized approvals hashes count doesn't match number of deploys.
    #[error(
        "stored finalized approvals hashes count doesn't match number of deploys: \
        block hash: {block_hash}, expected: {expected}, actual: {actual}"
    )]
    ApprovalsHashesLengthMismatch {
        /// The block hash.
        block_hash: BlockHash,
        /// The number of deploys in the block.
        expected: usize,
        /// The number of approvals hashes.
        actual: usize,
    },
    /// Error initializing metrics.
    #[error("failed to initialize metrics for storage: {0}")]
    Prometheus(#[from] prometheus::Error),
}

// We wholesale wrap lmdb errors and treat them as internal errors here.
impl From<lmdb::Error> for FatalStorageError {
    fn from(err: lmdb::Error) -> Self {
        LmdbExtError::from(err).into()
    }
}

impl From<Box<BlockValidationError>> for FatalStorageError {
    fn from(err: Box<BlockValidationError>) -> Self {
        Self::BlockValidation(*err)
    }
}

/// An error that may occur when handling a get request.
///
/// Wraps a fatal error, callers should check whether the variant is of the fatal or non-fatal kind.
#[derive(Debug, Error)]
pub(super) enum GetRequestError {
    /// A fatal error occurred.
    #[error(transparent)]
    Fatal(#[from] FatalStorageError),
    /// Failed to serialized an item ID on an incoming item request.
    #[error("failed to deserialize incoming item id")]
    MalformedIncomingItemId(#[source] bincode::Error),
    #[error(
        "id information not matching the finality signature: \
        requested id: {requested_id},\
        signature: {finality_signature}"
    )]
    FinalitySignatureIdMismatch {
        // the ID requested
        requested_id: Box<FinalitySignatureId>,
        // the finality signature read from storage
        finality_signature: Box<FinalitySignature>,
    },
}