Skip to main content

eth_valkyoth_protocol/
lib.rs

1#![no_std]
2#![forbid(unsafe_code)]
3//! Fork-aware Ethereum protocol validation state.
4
5#[cfg(feature = "std")]
6extern crate std;
7
8use core::{fmt, marker::PhantomData};
9
10mod fork;
11mod transaction;
12
13pub use fork::{ChainSpec, ForkActivation, ForkError, ForkSpec, Hardfork, ValidationContext};
14pub use transaction::{
15    ACCESS_LIST_TRANSACTION_FIELD_COUNT, ACCESS_LIST_TRANSACTION_TYPE, AccessList,
16    AccessListEntries, AccessListEntry, AccessListStorageKeyItems, AccessListStorageKeys,
17    AccessListTransactionDecodeError, AccessListTransactionDecodeErrorCategory,
18    AccessListTransactionField, AccessListTransactionTo, BLOB_TRANSACTION_FIELD_COUNT,
19    BLOB_TRANSACTION_TYPE, BlobTransactionDecodeError, BlobTransactionDecodeErrorCategory,
20    BlobTransactionField, BlobVersionedHashItems, BlobVersionedHashes,
21    DYNAMIC_FEE_TRANSACTION_FIELD_COUNT, DYNAMIC_FEE_TRANSACTION_TYPE,
22    DynamicFeeTransactionDecodeError, DynamicFeeTransactionDecodeErrorCategory,
23    DynamicFeeTransactionField, DynamicFeeTransactionTo, EIP_2718_MAX_TYPED_PREFIX,
24    EIP_2718_RESERVED_PREFIX, EIP_2718_SCALAR_PREFIX_START, EIP_2718_TYPED_ZERO_PREFIX,
25    InvalidSignatureYParity, LEGACY_TRANSACTION_FIELD_COUNT, LEGACY_TRANSACTION_PREFIX_START,
26    LegacyTransactionDecodeError, LegacyTransactionDecodeErrorCategory, LegacyTransactionField,
27    LegacyTransactionTo, SignatureYParity, TransactionEncodeError, TransactionEncodeErrorCategory,
28    TransactionEnvelope, TransactionEnvelopeError, TransactionEnvelopeErrorCategory,
29    TypedTransactionEnvelope, UnvalidatedAccessListTransaction, UnvalidatedBlobTransaction,
30    UnvalidatedDynamicFeeTransaction, UnvalidatedLegacyTransaction, UnvalidatedTransaction,
31    decode_access_list_transaction, decode_blob_transaction, decode_dynamic_fee_transaction,
32    decode_legacy_transaction, decode_transaction_envelope, encode_access_list_transaction,
33    encode_blob_transaction, encode_dynamic_fee_transaction, encode_legacy_transaction,
34    encode_transaction, encoded_access_list_transaction_len, encoded_blob_transaction_len,
35    encoded_dynamic_fee_transaction_len, encoded_legacy_transaction_len, encoded_transaction_len,
36};
37
38/// Protocol validation failure.
39#[non_exhaustive]
40#[derive(Clone, Copy, Debug, Eq, PartialEq)]
41pub enum ProtocolError {
42    /// A feature is disabled or unsupported for the selected operation.
43    Feature(FeatureError),
44    /// Fork context is missing, unsupported, or inactive.
45    Fork(ForkError),
46    /// A validation state transition was attempted out of order.
47    InvalidStateTransition,
48}
49
50impl ProtocolError {
51    /// Stable machine-readable error code.
52    #[must_use]
53    pub const fn code(self) -> &'static str {
54        match self {
55            Self::Feature(error) => error.code(),
56            Self::Fork(error) => error.code(),
57            Self::InvalidStateTransition => "ETH_PROTOCOL_INVALID_STATE_TRANSITION",
58        }
59    }
60
61    /// Stable human-readable error message.
62    #[must_use]
63    pub const fn message(self) -> &'static str {
64        match self {
65            Self::Feature(error) => error.message(),
66            Self::Fork(error) => error.message(),
67            Self::InvalidStateTransition => {
68                "validation state transition is not allowed from the current state"
69            }
70        }
71    }
72
73    /// Stable high-level category for policy decisions.
74    #[must_use]
75    pub const fn category(self) -> ProtocolErrorCategory {
76        match self {
77            Self::Feature(_) => ProtocolErrorCategory::Feature,
78            Self::Fork(_) => ProtocolErrorCategory::Fork,
79            Self::InvalidStateTransition => ProtocolErrorCategory::State,
80        }
81    }
82}
83
84impl fmt::Display for ProtocolError {
85    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86        formatter.write_str(self.message())
87    }
88}
89
90#[cfg(feature = "std")]
91impl std::error::Error for ProtocolError {}
92
93/// Stable high-level protocol error categories.
94#[non_exhaustive]
95#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96pub enum ProtocolErrorCategory {
97    /// Feature support or configuration failure.
98    Feature,
99    /// Fork selection or activation failure.
100    Fork,
101    /// Validation state failure.
102    State,
103}
104
105/// Feature availability failure.
106#[non_exhaustive]
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
108pub enum FeatureError {
109    /// The feature is intentionally not enabled for this build or context.
110    Disabled,
111    /// The feature is not implemented by this crate version.
112    Unsupported,
113}
114
115impl FeatureError {
116    /// Stable machine-readable error code.
117    #[must_use]
118    pub const fn code(self) -> &'static str {
119        match self {
120            Self::Disabled => "ETH_FEATURE_DISABLED",
121            Self::Unsupported => "ETH_FEATURE_UNSUPPORTED",
122        }
123    }
124
125    /// Stable human-readable error message.
126    #[must_use]
127    pub const fn message(self) -> &'static str {
128        match self {
129            Self::Disabled => "feature is disabled for this operation",
130            Self::Unsupported => "feature is not supported by this crate version",
131        }
132    }
133}
134
135impl fmt::Display for FeatureError {
136    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
137        formatter.write_str(self.message())
138    }
139}
140
141#[cfg(feature = "std")]
142impl std::error::Error for FeatureError {}
143
144/// Fork selection or activation failure.
145/// Raw wire input was accepted by the codec.
146#[derive(Clone, Copy, Debug, Eq, PartialEq)]
147pub struct Decoded;
148
149/// Canonical wire form and type-specific structure were checked.
150#[derive(Clone, Copy, Debug, Eq, PartialEq)]
151pub struct Canonical;
152
153/// Fork-specific validity was checked.
154#[derive(Clone, Copy, Debug, Eq, PartialEq)]
155pub struct ForkValidated;
156
157/// Sender recovery succeeded.
158#[derive(Clone, Copy, Debug, Eq, PartialEq)]
159pub struct SenderRecovered;
160
161/// A transaction token whose validation state is tracked at compile time.
162#[derive(Clone, Copy, Debug, Eq, PartialEq)]
163pub struct Transaction<State> {
164    _state: PhantomData<State>,
165}
166
167impl Transaction<Decoded> {
168    /// Creates a token for a decoded transaction in internal tests.
169    ///
170    /// A public decoded-transaction entry point will be added only together
171    /// with the real codec output that proves the decoded state.
172    #[must_use]
173    #[cfg(test)]
174    pub(crate) const fn decoded() -> Self {
175        Self {
176            _state: PhantomData,
177        }
178    }
179
180    /// Advances to canonical form after canonical checks pass.
181    #[must_use]
182    pub const fn into_canonical(self) -> Transaction<Canonical> {
183        Transaction {
184            _state: PhantomData,
185        }
186    }
187}
188
189impl Transaction<Canonical> {
190    /// Advances after fork-specific validation passes.
191    #[must_use]
192    pub const fn into_fork_validated(self) -> Transaction<ForkValidated> {
193        Transaction {
194            _state: PhantomData,
195        }
196    }
197}
198
199impl Transaction<ForkValidated> {
200    /// Advances after sender recovery succeeds.
201    #[must_use]
202    pub const fn into_sender_recovered(self) -> Transaction<SenderRecovered> {
203        Transaction {
204            _state: PhantomData,
205        }
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    extern crate std;
213    use std::string::ToString;
214
215    #[test]
216    fn transaction_typestate_advances_in_order() {
217        let transaction = Transaction::decoded()
218            .into_canonical()
219            .into_fork_validated()
220            .into_sender_recovered();
221        assert_eq!(
222            transaction,
223            Transaction::<SenderRecovered> {
224                _state: PhantomData
225            }
226        );
227    }
228
229    #[test]
230    fn protocol_errors_have_stable_codes_and_categories() {
231        let error = ProtocolError::Fork(ForkError::Inactive);
232
233        assert_eq!(error.code(), "ETH_FORK_INACTIVE");
234        assert_eq!(
235            error.message(),
236            "fork is not active for the validation context"
237        );
238        assert_eq!(error.category(), ProtocolErrorCategory::Fork);
239        assert_eq!(
240            error.to_string(),
241            "fork is not active for the validation context"
242        );
243    }
244
245    #[test]
246    fn feature_errors_format_without_payloads() {
247        let error = ProtocolError::Feature(FeatureError::Unsupported);
248
249        assert_eq!(error.code(), "ETH_FEATURE_UNSUPPORTED");
250        assert_eq!(
251            FeatureError::Unsupported.to_string(),
252            "feature is not supported by this crate version"
253        );
254    }
255}