1#![no_std]
2#![forbid(unsafe_code)]
3#[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#[non_exhaustive]
40#[derive(Clone, Copy, Debug, Eq, PartialEq)]
41pub enum ProtocolError {
42 Feature(FeatureError),
44 Fork(ForkError),
46 InvalidStateTransition,
48}
49
50impl ProtocolError {
51 #[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 #[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 #[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#[non_exhaustive]
95#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96pub enum ProtocolErrorCategory {
97 Feature,
99 Fork,
101 State,
103}
104
105#[non_exhaustive]
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
108pub enum FeatureError {
109 Disabled,
111 Unsupported,
113}
114
115impl FeatureError {
116 #[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 #[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
147pub struct Decoded;
148
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
151pub struct Canonical;
152
153#[derive(Clone, Copy, Debug, Eq, PartialEq)]
155pub struct ForkValidated;
156
157#[derive(Clone, Copy, Debug, Eq, PartialEq)]
159pub struct SenderRecovered;
160
161#[derive(Clone, Copy, Debug, Eq, PartialEq)]
163pub struct Transaction<State> {
164 _state: PhantomData<State>,
165}
166
167impl Transaction<Decoded> {
168 #[must_use]
173 #[cfg(test)]
174 pub(crate) const fn decoded() -> Self {
175 Self {
176 _state: PhantomData,
177 }
178 }
179
180 #[must_use]
182 pub const fn into_canonical(self) -> Transaction<Canonical> {
183 Transaction {
184 _state: PhantomData,
185 }
186 }
187}
188
189impl Transaction<Canonical> {
190 #[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 #[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}