reth_consensus/lib.rs
1//! Consensus protocol functions
2//!
3//! # Trait hierarchy
4//!
5//! Consensus validation is split across three traits, each adding a layer:
6//!
7//! - [`HeaderValidator`] — validates a header in isolation and against its parent. Used early in
8//! the validation pipeline before block execution.
9//!
10//! - [`Consensus`] — extends `HeaderValidator` with block body validation. Checks that the body
11//! matches the header (tx root, ommer hash, withdrawals) and runs pre-execution checks. Used
12//! before a block is executed.
13//!
14//! - [`FullConsensus`] — extends `Consensus` with post-execution validation. Checks execution
15//! results against the header (gas used, receipt root, logs bloom). Used after block execution to
16//! verify the outcome.
17//!
18//! In the engine, these are applied in order during payload validation (`engine_newPayload`).
19//! Payload attribute validation for block building (`engine_forkchoiceUpdated`) is handled
20//! separately at the engine API layer and does not use these traits.
21
22#![doc(
23 html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
24 html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
25 issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
26)]
27#![cfg_attr(not(test), warn(unused_crate_dependencies))]
28#![cfg_attr(docsrs, feature(doc_cfg))]
29#![cfg_attr(not(feature = "std"), no_std)]
30
31extern crate alloc;
32
33use alloc::{
34 boxed::Box,
35 fmt::Debug,
36 string::{String, ToString},
37 sync::Arc,
38 vec::Vec,
39};
40use alloy_consensus::Header;
41use alloy_eip7928::BlockAccessListGasError;
42use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256};
43use core::{error::Error, fmt::Display};
44
45/// Pre-computed receipt root and logs bloom.
46///
47/// When provided to [`FullConsensus::validate_block_post_execution`], this allows skipping
48/// the receipt root computation and using the pre-computed values instead.
49pub type ReceiptRootBloom = (B256, Bloom);
50
51/// Pre-computed transaction root.
52///
53/// When provided to [`Consensus::validate_block_pre_execution_with_tx_root`], this allows
54/// skipping transaction trie reconstruction from the block body.
55pub type TransactionRoot = B256;
56use reth_execution_types::BlockExecutionResult;
57use reth_primitives_traits::{
58 constants::{GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK, MINIMUM_GAS_LIMIT},
59 transaction::error::InvalidTransactionError,
60 Block, GotExpected, GotExpectedBoxed, NodePrimitives, RecoveredBlock, SealedBlock,
61 SealedHeader,
62};
63
64/// A consensus implementation that does nothing.
65pub mod noop;
66
67#[cfg(any(test, feature = "test-utils"))]
68/// test helpers for mocking consensus
69pub mod test_utils;
70
71/// [`Consensus`] implementation which knows full node primitives and is able to validation block's
72/// execution outcome.
73#[auto_impl::auto_impl(&, Arc)]
74pub trait FullConsensus<N: NodePrimitives>: Consensus<N::Block> {
75 /// Validate a block considering world state, i.e. things that can not be checked before
76 /// execution.
77 ///
78 /// See the Yellow Paper sections 4.3.2 "Holistic Validity".
79 ///
80 /// If `receipt_root_bloom` is provided, the implementation should use the pre-computed
81 /// receipt root and logs bloom instead of computing them from the receipts.
82 ///
83 /// Note: validating blocks does not include other validations of the Consensus
84 fn validate_block_post_execution(
85 &self,
86 block: &RecoveredBlock<N::Block>,
87 result: &BlockExecutionResult<N::Receipt>,
88 receipt_root_bloom: Option<ReceiptRootBloom>,
89 block_access_list_hash: Option<B256>,
90 ) -> Result<(), ConsensusError>;
91}
92
93/// Consensus is a protocol that chooses canonical chain.
94#[auto_impl::auto_impl(&, Arc)]
95pub trait Consensus<B: Block>: HeaderValidator<B::Header> {
96 /// Ensures that body field values match the header.
97 fn validate_body_against_header(
98 &self,
99 body: &B::Body,
100 header: &SealedHeader<B::Header>,
101 ) -> Result<(), ConsensusError>;
102
103 /// Validate a block disregarding world state, i.e. things that can be checked before sender
104 /// recovery and execution.
105 ///
106 /// See the Yellow Paper sections 4.4.2 "Holistic Validity", 4.4.4 "Block Header Validity".
107 /// Note: Ommer Validation (previously section 11.1) has been deprecated since the Paris hard
108 /// fork transition to proof of stake.
109 ///
110 /// **This should not be called for the genesis block**.
111 ///
112 /// Note: validating blocks does not include other validations of the Consensus
113 fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), ConsensusError>;
114
115 /// Returns `true` if the given consensus error is transient and may resolve on its own.
116 ///
117 /// On fast chains, clock skew between nodes can cause a valid block's timestamp to
118 /// appear briefly in the future. Caching such blocks as permanently invalid would
119 /// prevent them from being re-validated once the local clock catches up.
120 ///
121 /// Transient errors will not cause the block hash to be cached as permanently invalid,
122 /// allowing the block to be re-validated later.
123 fn is_transient_error(&self, _error: &ConsensusError) -> bool {
124 false
125 }
126
127 /// Validate a block disregarding world state using an optional pre-computed transaction root.
128 ///
129 /// If `transaction_root` is provided, the implementation should use the pre-computed
130 /// transaction root instead of recomputing it from the block body. The value must have been
131 /// derived from `block.body().calculate_tx_root()`.
132 ///
133 /// By default this falls back to [`Self::validate_block_pre_execution`].
134 fn validate_block_pre_execution_with_tx_root(
135 &self,
136 block: &SealedBlock<B>,
137 transaction_root: Option<TransactionRoot>,
138 ) -> Result<(), ConsensusError> {
139 let _ = transaction_root;
140 self.validate_block_pre_execution(block)
141 }
142}
143
144/// `HeaderValidator` is a protocol that validates headers and their relationships.
145#[auto_impl::auto_impl(&, Arc)]
146pub trait HeaderValidator<H = Header>: Debug + Send + Sync {
147 /// Validate if header is correct and follows consensus specification.
148 ///
149 /// This is called on standalone header to check if all hashes are correct.
150 fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError>;
151
152 /// Validate that the header information regarding parent are correct.
153 /// This checks the block number, timestamp, basefee and gas limit increment.
154 ///
155 /// This is called before properties that are not in the header itself (like total difficulty)
156 /// have been computed.
157 ///
158 /// **This should not be called for the genesis block**.
159 ///
160 /// Note: Validating header against its parent does not include other `HeaderValidator`
161 /// validations.
162 fn validate_header_against_parent(
163 &self,
164 header: &SealedHeader<H>,
165 parent: &SealedHeader<H>,
166 ) -> Result<(), ConsensusError>;
167
168 /// Validates the given headers
169 ///
170 /// This ensures that the first header is valid on its own and all subsequent headers are valid
171 /// on its own and valid against its parent.
172 ///
173 /// Note: this expects that the headers are in natural order (ascending block number)
174 fn validate_header_range(
175 &self,
176 headers: &[SealedHeader<H>],
177 ) -> Result<(), HeaderConsensusError<H>>
178 where
179 H: Clone,
180 {
181 if let Some((initial_header, remaining_headers)) = headers.split_first() {
182 self.validate_header(initial_header)
183 .map_err(|e| HeaderConsensusError(e, initial_header.clone()))?;
184 let mut parent = initial_header;
185 for child in remaining_headers {
186 self.validate_header(child).map_err(|e| HeaderConsensusError(e, child.clone()))?;
187 self.validate_header_against_parent(child, parent)
188 .map_err(|e| HeaderConsensusError(e, child.clone()))?;
189 parent = child;
190 }
191 }
192 Ok(())
193 }
194}
195
196/// Consensus Errors
197#[derive(Debug, Clone, thiserror::Error)]
198pub enum ConsensusError {
199 /// Error when the gas used in the header exceeds the gas limit.
200 #[error("block used gas ({gas_used}) is greater than gas limit ({gas_limit})")]
201 HeaderGasUsedExceedsGasLimit {
202 /// The gas used in the block header.
203 gas_used: u64,
204 /// The gas limit in the block header.
205 gas_limit: u64,
206 },
207 /// Error when the gas limit is more than the maximum allowed.
208 #[error(
209 "header gas limit ({gas_limit}) exceed the maximum allowed gas limit ({MAXIMUM_GAS_LIMIT_BLOCK})"
210 )]
211 HeaderGasLimitExceedsMax {
212 /// The gas limit in the block header.
213 gas_limit: u64,
214 },
215
216 /// Error when block gas used doesn't match expected value
217 #[error("block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}")]
218 BlockGasUsed {
219 /// The gas diff.
220 gas: GotExpected<u64>,
221 /// Gas spent by each transaction
222 gas_spent_by_tx: Vec<(u64, u64)>,
223 },
224
225 /// Error when the hash of block ommer is different from the expected hash.
226 #[error("mismatched block ommer hash: {0}")]
227 BodyOmmersHashDiff(GotExpectedBoxed<B256>),
228
229 /// Error when the state root in the block is different from the expected state root.
230 #[error("mismatched block state root: {0}")]
231 BodyStateRootDiff(GotExpectedBoxed<B256>),
232
233 /// Error when the transaction root in the block is different from the expected transaction
234 /// root.
235 #[error("mismatched block transaction root: {0}")]
236 BodyTransactionRootDiff(GotExpectedBoxed<B256>),
237
238 /// Error when the receipt root in the block is different from the expected receipt root.
239 #[error("receipt root mismatch: {0}")]
240 BodyReceiptRootDiff(GotExpectedBoxed<B256>),
241
242 /// Error when header bloom filter is different from the expected bloom filter.
243 #[error("header bloom filter mismatch: {0}")]
244 BodyBloomLogDiff(GotExpectedBoxed<Bloom>),
245
246 /// Error when the withdrawals root in the block is different from the expected withdrawals
247 /// root.
248 #[error("mismatched block withdrawals root: {0}")]
249 BodyWithdrawalsRootDiff(GotExpectedBoxed<B256>),
250
251 /// Error when the requests hash in the block is different from the expected requests
252 /// hash.
253 #[error("mismatched block requests hash: {0}")]
254 BodyRequestsHashDiff(GotExpectedBoxed<B256>),
255
256 /// Error when a block with a specific hash and number is already known.
257 #[error("block with [hash={hash}, number={number}] is already known")]
258 BlockKnown {
259 /// The hash of the known block.
260 hash: BlockHash,
261 /// The block number of the known block.
262 number: BlockNumber,
263 },
264
265 /// Error when the parent hash of a block is not known.
266 #[error("block parent [hash={hash}] is not known")]
267 ParentUnknown {
268 /// The hash of the unknown parent block.
269 hash: BlockHash,
270 },
271
272 /// Error when the block number does not match the parent block number.
273 #[error(
274 "block number {block_number} does not match parent block number {parent_block_number}"
275 )]
276 ParentBlockNumberMismatch {
277 /// The parent block number.
278 parent_block_number: BlockNumber,
279 /// The block number.
280 block_number: BlockNumber,
281 },
282
283 /// Error when the parent hash does not match the expected parent hash.
284 #[error("mismatched parent hash: {0}")]
285 ParentHashMismatch(GotExpectedBoxed<B256>),
286
287 /// Error when the block timestamp is in the future compared to our clock time.
288 #[error(
289 "block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}"
290 )]
291 TimestampIsInFuture {
292 /// The block's timestamp.
293 timestamp: u64,
294 /// The current timestamp.
295 present_timestamp: u64,
296 },
297
298 /// Error when the base fee is missing.
299 #[error("base fee missing")]
300 BaseFeeMissing,
301
302 /// Error when there is a transaction signer recovery error.
303 #[error("transaction signer recovery error")]
304 TransactionSignerRecoveryError,
305
306 /// Error when the extra data length exceeds the maximum allowed.
307 #[error("extra data {len} exceeds max length")]
308 ExtraDataExceedsMax {
309 /// The length of the extra data.
310 len: usize,
311 },
312
313 /// Error when the difficulty after a merge is not zero.
314 #[error("difficulty after merge is not zero")]
315 TheMergeDifficultyIsNotZero,
316
317 /// Error when the nonce after a merge is not zero.
318 #[error("nonce after merge is not zero")]
319 TheMergeNonceIsNotZero,
320
321 /// Error when the ommer root after a merge is not empty.
322 #[error("ommer root after merge is not empty")]
323 TheMergeOmmerRootIsNotEmpty,
324
325 /// Error when the withdrawals root is missing.
326 #[error("missing withdrawals root")]
327 WithdrawalsRootMissing,
328
329 /// Error when the requests hash is missing.
330 #[error("missing requests hash")]
331 RequestsHashMissing,
332
333 /// Error when the block access list hash is missing.
334 #[error("missing block access list hash")]
335 BlockAccessListHashMissing,
336
337 /// Error when the slot number is missing.
338 #[error("missing slot number")]
339 SlotNumberMissing,
340
341 /// Error when an unexpected withdrawals root is encountered.
342 #[error("unexpected withdrawals root")]
343 WithdrawalsRootUnexpected,
344
345 /// Error when an unexpected requests hash is encountered.
346 #[error("unexpected requests hash")]
347 RequestsHashUnexpected,
348
349 /// Error when an unexpected block access list hash is encountered.
350 #[error("unexpected block access list hash")]
351 BlockAccessListHashUnexpected,
352
353 /// Error when an unexpected slot number is encountered.
354 #[error("unexpected slot number")]
355 SlotNumberUnexpected,
356
357 /// Error when withdrawals are missing.
358 #[error("missing withdrawals")]
359 BodyWithdrawalsMissing,
360
361 /// Error when requests are missing.
362 #[error("missing requests")]
363 BodyRequestsMissing,
364
365 /// Error when blob gas used is missing.
366 #[error("missing blob gas used")]
367 BlobGasUsedMissing,
368
369 /// Error when unexpected blob gas used is encountered.
370 #[error("unexpected blob gas used")]
371 BlobGasUsedUnexpected,
372
373 /// Error when excess blob gas is missing.
374 #[error("missing excess blob gas")]
375 ExcessBlobGasMissing,
376
377 /// Error when unexpected excess blob gas is encountered.
378 #[error("unexpected excess blob gas")]
379 ExcessBlobGasUnexpected,
380
381 /// Error when the parent beacon block root is missing.
382 #[error("missing parent beacon block root")]
383 ParentBeaconBlockRootMissing,
384
385 /// Error when an unexpected parent beacon block root is encountered.
386 #[error("unexpected parent beacon block root")]
387 ParentBeaconBlockRootUnexpected,
388
389 /// Error when blob gas used exceeds the maximum allowed.
390 #[error("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
391 BlobGasUsedExceedsMaxBlobGasPerBlock {
392 /// The actual blob gas used.
393 blob_gas_used: u64,
394 /// The maximum allowed blob gas per block.
395 max_blob_gas_per_block: u64,
396 },
397
398 /// Error when blob gas used is not a multiple of blob gas per blob.
399 #[error(
400 "blob gas used {blob_gas_used} is not a multiple of blob gas per blob {blob_gas_per_blob}"
401 )]
402 BlobGasUsedNotMultipleOfBlobGasPerBlob {
403 /// The actual blob gas used.
404 blob_gas_used: u64,
405 /// The blob gas per blob.
406 blob_gas_per_blob: u64,
407 },
408
409 /// Error when the blob gas used in the header does not match the expected blob gas used.
410 #[error("blob gas used mismatch: {0}")]
411 BlobGasUsedDiff(GotExpected<u64>),
412
413 /// Error for a transaction that violates consensus.
414 #[error(transparent)]
415 InvalidTransaction(InvalidTransactionError),
416
417 /// Error when the block's base fee is different from the expected base fee.
418 #[error("block base fee mismatch: {0}")]
419 BaseFeeDiff(GotExpected<u64>),
420
421 /// Error when there is an invalid excess blob gas.
422 #[error(
423 "invalid excess blob gas: {diff}; \
424 parent excess blob gas: {parent_excess_blob_gas}, \
425 parent blob gas used: {parent_blob_gas_used}"
426 )]
427 ExcessBlobGasDiff {
428 /// The excess blob gas diff.
429 diff: GotExpected<u64>,
430 /// The parent excess blob gas.
431 parent_excess_blob_gas: u64,
432 /// The parent blob gas used.
433 parent_blob_gas_used: u64,
434 },
435
436 /// Error when the child gas limit exceeds the maximum allowed increase.
437 #[error("child gas_limit {child_gas_limit} exceeds the max allowed increase ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
438 GasLimitInvalidIncrease {
439 /// The parent gas limit.
440 parent_gas_limit: u64,
441 /// The child gas limit.
442 child_gas_limit: u64,
443 },
444
445 /// Error indicating that the child gas limit is below the minimum allowed limit.
446 ///
447 /// This error occurs when the child gas limit is less than the specified minimum gas limit.
448 #[error(
449 "child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})"
450 )]
451 GasLimitInvalidMinimum {
452 /// The child gas limit.
453 child_gas_limit: u64,
454 },
455
456 /// Error indicating that the block gas limit is above the allowed maximum.
457 ///
458 /// This error occurs when the gas limit is more than the specified maximum gas limit.
459 #[error("child gas limit {block_gas_limit} is above the maximum allowed limit ({MAXIMUM_GAS_LIMIT_BLOCK})")]
460 GasLimitInvalidBlockMaximum {
461 /// block gas limit.
462 block_gas_limit: u64,
463 },
464
465 /// Error when the child gas limit exceeds the maximum allowed decrease.
466 #[error("child gas_limit {child_gas_limit} is below the max allowed decrease ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
467 GasLimitInvalidDecrease {
468 /// The parent gas limit.
469 parent_gas_limit: u64,
470 /// The child gas limit.
471 child_gas_limit: u64,
472 },
473
474 /// Error when the block timestamp is in the past compared to the parent timestamp.
475 #[error(
476 "block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}"
477 )]
478 TimestampIsInPast {
479 /// The parent block's timestamp.
480 parent_timestamp: u64,
481 /// The block's timestamp.
482 timestamp: u64,
483 },
484 /// Error when the block is too large.
485 #[error("block is too large: {rlp_length} > {max_rlp_length}")]
486 BlockTooLarge {
487 /// The actual RLP length of the block.
488 rlp_length: usize,
489 /// The maximum allowed RLP length.
490 max_rlp_length: usize,
491 },
492 /// EIP-7825: Transaction gas limit exceeds maximum allowed
493 #[error(transparent)]
494 TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
495 /// EIP-7928: Error when an unexpected block access list cost is encountered.
496 #[error(transparent)]
497 BlockAccessListCostMoreThanGasLimit(Box<BlockAccessListGasError>),
498 /// EIP-7928: Error when the block access list hash doesn't match the expected value.
499 #[error("block access list hash mismatch: {0}")]
500 BlockAccessListHashMismatch(GotExpectedBoxed<B256>),
501 /// EIP-7928: Error when the raw block access list bytes cannot be RLP-decoded.
502 ///
503 /// Note: this is not a block validity error, the received block access list bytes themselves
504 /// are malformed request params.
505 #[error("failed to decode block access list: {0}")]
506 BlockAccessListDecode(alloy_rlp::Error),
507 /// EIP-7928: Error when the block access list cannot be decoded or converted.
508 #[error("invalid block access list: {0}")]
509 BlockAccessListInvalid(String),
510 /// Any additional consensus error, for example L2-specific errors.
511 #[error(transparent)]
512 Other(#[from] Arc<dyn Error + Send + Sync>),
513}
514
515impl ConsensusError {
516 /// Returns a new [`ConsensusError::Other`] instance with the given error.
517 pub fn other<E>(error: E) -> Self
518 where
519 E: Error + Send + Sync + 'static,
520 {
521 Self::Other(Arc::new(error))
522 }
523
524 /// Returns a new [`ConsensusError::Other`] instance with the given message.
525 pub fn msg(msg: impl Display) -> Self {
526 Self::other(MessageError(msg.to_string()))
527 }
528
529 /// Returns `true` if the error is a state root error.
530 pub const fn is_state_root_error(&self) -> bool {
531 matches!(self, Self::BodyStateRootDiff(_))
532 }
533
534 /// Returns the arbitrary error if it is [`ConsensusError::Other`].
535 pub fn as_other(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
536 match self {
537 Self::Other(err) => Some(err.as_ref()),
538 _ => None,
539 }
540 }
541
542 /// Returns a reference to the [`ConsensusError::Other`] value if it is of that type.
543 /// Returns `None` otherwise.
544 pub fn downcast_other_ref<T: Error + 'static>(&self) -> Option<&T> {
545 let other = self.as_other()?;
546 other.downcast_ref()
547 }
548
549 /// Returns `true` if this type is a [`ConsensusError::Other`] of that error type.
550 pub fn is_other<T: Error + 'static>(&self) -> bool {
551 self.as_other().map(|err| err.is::<T>()).unwrap_or(false)
552 }
553}
554
555impl From<InvalidTransactionError> for ConsensusError {
556 fn from(value: InvalidTransactionError) -> Self {
557 Self::InvalidTransaction(value)
558 }
559}
560
561impl From<TxGasLimitTooHighErr> for ConsensusError {
562 fn from(value: TxGasLimitTooHighErr) -> Self {
563 Self::TransactionGasLimitTooHigh(Box::new(value))
564 }
565}
566
567impl From<BlockAccessListGasError> for ConsensusError {
568 fn from(value: BlockAccessListGasError) -> Self {
569 Self::BlockAccessListCostMoreThanGasLimit(Box::new(value))
570 }
571}
572
573/// `HeaderConsensusError` combines a `ConsensusError` with the `SealedHeader` it relates to.
574#[derive(thiserror::Error, Debug)]
575#[error("Consensus error: {0}, Invalid header: {1:?}")]
576pub struct HeaderConsensusError<H>(ConsensusError, SealedHeader<H>);
577
578/// EIP-7825: Transaction gas limit exceeds maximum allowed
579#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)]
580#[error("transaction gas limit ({gas_limit}) is greater than the cap ({max_allowed})")]
581pub struct TxGasLimitTooHighErr {
582 /// Hash of the transaction that violates the rule
583 pub tx_hash: B256,
584 /// The gas limit of the transaction
585 pub gas_limit: u64,
586 /// The maximum allowed gas limit
587 pub max_allowed: u64,
588}
589
590#[derive(Debug, thiserror::Error)]
591#[error("{0}")]
592struct MessageError(String);
593
594#[cfg(test)]
595mod tests {
596 use super::*;
597
598 #[derive(thiserror::Error, Debug)]
599 #[error("Custom L2 consensus error")]
600 struct CustomL2Error;
601
602 #[test]
603 fn test_other_error_conversion() {
604 let consensus_err = ConsensusError::other(CustomL2Error);
605 assert!(matches!(consensus_err, ConsensusError::Other(_)));
606 }
607
608 #[test]
609 fn test_other_error_display() {
610 let consensus_err = ConsensusError::other(CustomL2Error);
611 let error_message = format!("{}", consensus_err);
612 assert_eq!(error_message, "Custom L2 consensus error");
613 }
614
615 #[test]
616 fn test_other_error_downcast() {
617 let consensus_err = ConsensusError::other(CustomL2Error);
618
619 assert!(consensus_err.is_other::<CustomL2Error>());
620 assert!(consensus_err.downcast_other_ref::<CustomL2Error>().is_some());
621 }
622
623 #[test]
624 fn test_other_msg() {
625 let consensus_err = ConsensusError::msg("consensus message");
626
627 assert_eq!(consensus_err.to_string(), "consensus message");
628 assert!(consensus_err.downcast_other_ref::<MessageError>().is_some());
629 }
630}