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}