Skip to main content

reth_snap_sync/
error.rs

1//! Failures raised while assembling a snap state generation.
2
3use alloy_primitives::B256;
4use reth_downloaders::snap::{
5    InvalidAccountRange, InvalidBlockAccessListRequest, InvalidStorageRangeRequest,
6};
7use reth_network_p2p::error::RequestError;
8use reth_storage_api::SnapAttemptId;
9use reth_storage_errors::{db::DatabaseError, provider::ProviderError};
10
11/// Error returned while assembling a snap state generation.
12#[derive(Debug, thiserror::Error)]
13pub enum SnapSyncError {
14    /// A header lookup failed.
15    #[error(transparent)]
16    Provider(#[from] ProviderError),
17    /// A request for state failed.
18    #[error(transparent)]
19    Request(#[from] RequestError),
20    /// An account range was requested with its origin past its limit.
21    #[error(transparent)]
22    AccountRangeRequest(#[from] InvalidAccountRange),
23    /// A storage request did not match the accounts it was built from.
24    #[error(transparent)]
25    StorageRequest(#[from] InvalidStorageRangeRequest),
26    /// A block access list request did not match the headers it was built from.
27    #[error(transparent)]
28    BlockAccessListRequest(#[from] InvalidBlockAccessListRequest),
29    /// The storage layout keys state by address, which snap cannot fill in without preimages.
30    #[error("snap synchronization requires the hashed state layout")]
31    UnsupportedStorage,
32    /// No attempt owns the persisted state.
33    #[error("no snap attempt owns the persisted state")]
34    NoAttempt,
35    /// A write was presented for an attempt or pivot that no longer owns the persisted state.
36    #[error("stale snap write for attempt {attempt} at state version {state_version}")]
37    StaleWrite {
38        /// Attempt the rejected write claims.
39        attempt: SnapAttemptId,
40        /// State version the rejected write was proved against.
41        state_version: u64,
42    },
43    /// A range was proved against a root other than the one the attempt downloads.
44    #[error("range proved against {got} while the attempt downloads {expected}")]
45    RootMismatch {
46        /// Root the attempt currently downloads.
47        expected: B256,
48        /// Root the range was proved against.
49        got: B256,
50    },
51    /// No catch-up progress is recorded for the attempt.
52    #[error("no catch-up progress is recorded for the attempt")]
53    NoCatchUpProgress,
54    /// A block the catch-up needs has no header.
55    #[error("block {block} has no header to authenticate its access list against")]
56    MissingHeader {
57        /// Block the header is missing for.
58        block: u64,
59    },
60    /// A list was applied for a block other than the one continuing the applied sequence.
61    #[error("block access list for block {got}, the applied state continues at {expected}")]
62    OutOfOrderBlock {
63        /// Block the applied state continues at.
64        expected: u64,
65        /// Block the list was applied for.
66        got: u64,
67    },
68    /// A list was applied for a block past the pivot.
69    #[error("block access list for block {block} past pivot {pivot}")]
70    BlockPastPivot {
71        /// Block the attempt is anchored to.
72        pivot: u64,
73        /// Block the list was applied for.
74        block: u64,
75    },
76    /// The pivot was moved to a block not past the current one.
77    #[error("pivot {pivot} cannot move to block {target}, which is not past it")]
78    PivotNotAdvanced {
79        /// Block the attempt is anchored to.
80        pivot: u64,
81        /// Block the pivot was moved to.
82        target: u64,
83    },
84    /// Catch-up has not carried the downloaded state to the pivot, which committing storage
85    /// persisted ahead of its range or completing the attempt requires.
86    #[error("catch-up applied block {applied}, the pivot is {pivot}")]
87    CatchUpBehindPivot {
88        /// Last block whose list is applied.
89        applied: u64,
90        /// Block the attempt is anchored to.
91        pivot: u64,
92    },
93    /// A list was applied for a block the canonical chain no longer holds.
94    #[error("block {block} ({hash}) is no longer canonical")]
95    NonCanonicalBlock {
96        /// Number of the block.
97        block: u64,
98        /// Hash of the block the list belongs to.
99        hash: B256,
100    },
101    /// A list was applied for a block building on another chain than the applied state.
102    #[error("block access list for a block building on {got}, the applied state is at {expected}")]
103    ForkedBlock {
104        /// Hash of the last applied block.
105        expected: B256,
106        /// Hash the block the list belongs to builds on.
107        got: B256,
108    },
109    /// No account coverage is recorded for the attempt.
110    #[error("no account coverage is recorded for the attempt")]
111    NoCoverage,
112    /// A persisted progress record this build cannot read.
113    #[error("{key} record version {version:?} is not supported")]
114    UnsupportedRecord {
115        /// Metadata key the record is stored under.
116        key: &'static str,
117        /// Version found on disk, absent when the record carries no numeric version.
118        version: Option<u64>,
119    },
120    /// A range was downloaded from somewhere other than the next key to cover.
121    #[error("account range starts at {got}, coverage continues at {expected:?}")]
122    OutOfOrderRange {
123        /// Key the coverage continues at, or none once it is complete.
124        expected: Option<B256>,
125        /// Key the range was requested from.
126        got: B256,
127    },
128    /// A verified range proved nothing past the key it was requested from.
129    #[error("range made no progress past {origin}")]
130    NoProgress {
131        /// Key the range was requested from.
132        origin: B256,
133    },
134    /// Storage was downloaded from somewhere other than where its contract's progress continues.
135    #[error("storage for {account} from {from} does not continue its persisted progress")]
136    OutOfOrderStorage {
137        /// Hashed address of the contract.
138        account: B256,
139        /// Slot the storage was requested from.
140        from: B256,
141    },
142    /// An account with storage was committed without it.
143    #[error("account {account} has storage that was not downloaded")]
144    MissingStorage {
145        /// Hashed address of the account.
146        account: B256,
147    },
148    /// The storage supplied for an account does not hash to the root the account commits to.
149    #[error("storage supplied for {account} hashes to {got}, its account commits to {expected}")]
150    StorageRootMismatch {
151        /// Hashed address of the account.
152        account: B256,
153        /// Storage root the account commits to.
154        expected: B256,
155        /// Root of the supplied storage.
156        got: B256,
157    },
158    /// Storage was supplied for an account that is not in the range or has none.
159    #[error("storage supplied for {account}, which is not a contract in the range")]
160    UnexpectedStorage {
161        /// Hashed address the storage was supplied for.
162        account: B256,
163    },
164    /// An account's code is neither supplied nor stored.
165    #[error("code {hash} is neither supplied nor stored")]
166    MissingCode {
167        /// Hash of the missing code.
168        hash: B256,
169    },
170    /// Code was supplied under a hash it does not hash to.
171    #[error("code supplied as {expected} hashes to {got}")]
172    CodeMismatch {
173        /// Hash the code was supplied under.
174        expected: B256,
175        /// Hash of the supplied code.
176        got: B256,
177    },
178    /// Scheduled accounts remain to be fetched again at the pivot.
179    #[error("{accounts} accounts remain to be fetched again at the pivot")]
180    PendingRepairs {
181        /// Number of accounts still scheduled.
182        accounts: usize,
183    },
184    /// Account ranges remain to be downloaded.
185    #[error("accounts from {next} are not downloaded yet")]
186    IncompleteAccounts {
187        /// Key the next range is requested from.
188        next: B256,
189    },
190    /// The pivot is the genesis block, whose trie the merkle stage never rebuilds.
191    #[error("snap synchronization cannot anchor to the genesis block")]
192    GenesisPivot,
193    /// Work stopped because its session was cancelled.
194    #[error("snap synchronization was cancelled")]
195    Cancelled,
196    /// A reorg was recovered to a pivot below the last block both branches share.
197    #[error("pivot {target} is below reorg ancestor {ancestor}")]
198    PivotBelowAncestor {
199        /// Last block both branches share.
200        ancestor: u64,
201        /// Block the pivot was moved to.
202        target: u64,
203    },
204}
205
206impl SnapSyncError {
207    /// Whether the node's progress resolves this error, as new peers serve the state or missing
208    /// headers are downloaded.
209    ///
210    /// A closed channel means the network is gone, not that peers lack the state.
211    pub const fn is_transient(&self) -> bool {
212        match self {
213            Self::Request(error) => !error.is_channel_closed(),
214            Self::MissingHeader { .. } => true,
215            _ => false,
216        }
217    }
218
219    /// Whether a block the attempt builds on left the canonical chain, so its downloaded state
220    /// belongs to another fork.
221    pub const fn is_reorg(&self) -> bool {
222        matches!(self, Self::NonCanonicalBlock { .. } | Self::ForkedBlock { .. })
223    }
224}
225
226impl From<DatabaseError> for SnapSyncError {
227    fn from(error: DatabaseError) -> Self {
228        Self::Provider(error.into())
229    }
230}