1#![no_std]
2#![forbid(unsafe_code)]
3#[cfg(feature = "std")]
6extern crate std;
7
8use core::{fmt, marker::PhantomData};
9
10use eth_valkyoth_primitives::{BlockNumber, ChainId, UnixTimestamp};
11
12mod transaction;
13
14pub use transaction::{
15 ACCESS_LIST_TRANSACTION_FIELD_COUNT, AccessList, AccessListEntries, AccessListEntry,
16 AccessListStorageKeyItems, AccessListStorageKeys, AccessListTransactionDecodeError,
17 AccessListTransactionDecodeErrorCategory, AccessListTransactionField, AccessListTransactionTo,
18 BLOB_TRANSACTION_FIELD_COUNT, BlobTransactionDecodeError, BlobTransactionDecodeErrorCategory,
19 BlobTransactionField, BlobVersionedHashItems, BlobVersionedHashes,
20 DYNAMIC_FEE_TRANSACTION_FIELD_COUNT, DynamicFeeTransactionDecodeError,
21 DynamicFeeTransactionDecodeErrorCategory, DynamicFeeTransactionField, DynamicFeeTransactionTo,
22 EIP_2718_MAX_TYPED_PREFIX, EIP_2718_RESERVED_PREFIX, EIP_2718_SCALAR_PREFIX_START,
23 EIP_2718_TYPED_ZERO_PREFIX, InvalidSignatureYParity, LEGACY_TRANSACTION_FIELD_COUNT,
24 LEGACY_TRANSACTION_PREFIX_START, LegacyTransactionDecodeError,
25 LegacyTransactionDecodeErrorCategory, LegacyTransactionField, LegacyTransactionTo,
26 SignatureYParity, TransactionEnvelope, TransactionEnvelopeError,
27 TransactionEnvelopeErrorCategory, TypedTransactionEnvelope, UnvalidatedAccessListTransaction,
28 UnvalidatedBlobTransaction, UnvalidatedDynamicFeeTransaction, UnvalidatedLegacyTransaction,
29 decode_access_list_transaction, decode_blob_transaction, decode_dynamic_fee_transaction,
30 decode_legacy_transaction, decode_transaction_envelope,
31};
32
33#[non_exhaustive]
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum ProtocolError {
37 Feature(FeatureError),
39 Fork(ForkError),
41 InvalidStateTransition,
43}
44
45impl ProtocolError {
46 #[must_use]
48 pub const fn code(self) -> &'static str {
49 match self {
50 Self::Feature(error) => error.code(),
51 Self::Fork(error) => error.code(),
52 Self::InvalidStateTransition => "ETH_PROTOCOL_INVALID_STATE_TRANSITION",
53 }
54 }
55
56 #[must_use]
58 pub const fn message(self) -> &'static str {
59 match self {
60 Self::Feature(error) => error.message(),
61 Self::Fork(error) => error.message(),
62 Self::InvalidStateTransition => {
63 "validation state transition is not allowed from the current state"
64 }
65 }
66 }
67
68 #[must_use]
70 pub const fn category(self) -> ProtocolErrorCategory {
71 match self {
72 Self::Feature(_) => ProtocolErrorCategory::Feature,
73 Self::Fork(_) => ProtocolErrorCategory::Fork,
74 Self::InvalidStateTransition => ProtocolErrorCategory::State,
75 }
76 }
77}
78
79impl fmt::Display for ProtocolError {
80 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81 formatter.write_str(self.message())
82 }
83}
84
85#[cfg(feature = "std")]
86impl std::error::Error for ProtocolError {}
87
88#[non_exhaustive]
90#[derive(Clone, Copy, Debug, Eq, PartialEq)]
91pub enum ProtocolErrorCategory {
92 Feature,
94 Fork,
96 State,
98}
99
100#[non_exhaustive]
102#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub enum FeatureError {
104 Disabled,
106 Unsupported,
108}
109
110impl FeatureError {
111 #[must_use]
113 pub const fn code(self) -> &'static str {
114 match self {
115 Self::Disabled => "ETH_FEATURE_DISABLED",
116 Self::Unsupported => "ETH_FEATURE_UNSUPPORTED",
117 }
118 }
119
120 #[must_use]
122 pub const fn message(self) -> &'static str {
123 match self {
124 Self::Disabled => "feature is disabled for this operation",
125 Self::Unsupported => "feature is not supported by this crate version",
126 }
127 }
128}
129
130impl fmt::Display for FeatureError {
131 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
132 formatter.write_str(self.message())
133 }
134}
135
136#[cfg(feature = "std")]
137impl std::error::Error for FeatureError {}
138
139#[non_exhaustive]
141#[derive(Clone, Copy, Debug, Eq, PartialEq)]
142pub enum ForkError {
143 Unsupported,
145 Inactive,
147 MissingActivation,
149}
150
151impl ForkError {
152 #[must_use]
154 pub const fn code(self) -> &'static str {
155 match self {
156 Self::Unsupported => "ETH_FORK_UNSUPPORTED",
157 Self::Inactive => "ETH_FORK_INACTIVE",
158 Self::MissingActivation => "ETH_FORK_MISSING_ACTIVATION",
159 }
160 }
161
162 #[must_use]
164 pub const fn message(self) -> &'static str {
165 match self {
166 Self::Unsupported => "fork is not supported by this crate version",
167 Self::Inactive => "fork is not active for the validation context",
168 Self::MissingActivation => "fork activation data is incomplete",
169 }
170 }
171}
172
173impl fmt::Display for ForkError {
174 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
175 formatter.write_str(self.message())
176 }
177}
178
179#[cfg(feature = "std")]
180impl std::error::Error for ForkError {}
181
182#[derive(Clone, Copy, Debug, Eq, PartialEq)]
184pub enum ForkActivation {
185 BlockOnly {
187 activation_block: BlockNumber,
189 },
190 BlockAndTimestamp {
192 activation_block: BlockNumber,
194 activation_timestamp: UnixTimestamp,
196 },
197}
198
199#[derive(Clone, Copy, Debug, Eq, PartialEq)]
201pub struct ForkSpec {
202 pub chain_id: ChainId,
204 pub activation: ForkActivation,
206}
207
208#[derive(Clone, Copy, Debug, Eq, PartialEq)]
210pub struct ValidationContext {
211 pub fork: ForkSpec,
213 pub block_number: BlockNumber,
215 pub timestamp: UnixTimestamp,
217}
218
219impl ValidationContext {
220 #[must_use]
222 pub fn fork_is_active(self) -> bool {
223 match self.fork.activation {
224 ForkActivation::BlockOnly { activation_block } => self.block_number >= activation_block,
225 ForkActivation::BlockAndTimestamp {
226 activation_block,
227 activation_timestamp,
228 } => self.block_number >= activation_block && self.timestamp >= activation_timestamp,
229 }
230 }
231}
232
233#[derive(Clone, Copy, Debug, Eq, PartialEq)]
235pub struct Decoded;
236
237#[derive(Clone, Copy, Debug, Eq, PartialEq)]
239pub struct Canonical;
240
241#[derive(Clone, Copy, Debug, Eq, PartialEq)]
243pub struct ForkValidated;
244
245#[derive(Clone, Copy, Debug, Eq, PartialEq)]
247pub struct SenderRecovered;
248
249#[derive(Clone, Copy, Debug, Eq, PartialEq)]
251pub struct Transaction<State> {
252 _state: PhantomData<State>,
253}
254
255impl Transaction<Decoded> {
256 #[must_use]
261 #[cfg(test)]
262 pub(crate) const fn decoded() -> Self {
263 Self {
264 _state: PhantomData,
265 }
266 }
267
268 #[must_use]
270 pub const fn into_canonical(self) -> Transaction<Canonical> {
271 Transaction {
272 _state: PhantomData,
273 }
274 }
275}
276
277impl Transaction<Canonical> {
278 #[must_use]
280 pub const fn into_fork_validated(self) -> Transaction<ForkValidated> {
281 Transaction {
282 _state: PhantomData,
283 }
284 }
285}
286
287impl Transaction<ForkValidated> {
288 #[must_use]
290 pub const fn into_sender_recovered(self) -> Transaction<SenderRecovered> {
291 Transaction {
292 _state: PhantomData,
293 }
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 extern crate std;
301 use std::string::ToString;
302
303 #[test]
304 fn activation_requires_block_and_time() {
305 let context = ValidationContext {
306 fork: ForkSpec {
307 chain_id: ChainId::new(1),
308 activation: ForkActivation::BlockAndTimestamp {
309 activation_block: BlockNumber::new(10),
310 activation_timestamp: UnixTimestamp::new(20),
311 },
312 },
313 block_number: BlockNumber::new(10),
314 timestamp: UnixTimestamp::new(19),
315 };
316 assert!(!context.fork_is_active());
317 }
318
319 #[test]
320 fn block_only_activation_ignores_timestamp() {
321 let context = ValidationContext {
322 fork: ForkSpec {
323 chain_id: ChainId::new(1),
324 activation: ForkActivation::BlockOnly {
325 activation_block: BlockNumber::new(10),
326 },
327 },
328 block_number: BlockNumber::new(10),
329 timestamp: UnixTimestamp::new(0),
330 };
331 assert!(context.fork_is_active());
332 }
333
334 #[test]
335 fn transaction_typestate_advances_in_order() {
336 let transaction = Transaction::decoded()
337 .into_canonical()
338 .into_fork_validated()
339 .into_sender_recovered();
340 assert_eq!(
341 transaction,
342 Transaction::<SenderRecovered> {
343 _state: PhantomData
344 }
345 );
346 }
347
348 #[test]
349 fn protocol_errors_have_stable_codes_and_categories() {
350 let error = ProtocolError::Fork(ForkError::Inactive);
351
352 assert_eq!(error.code(), "ETH_FORK_INACTIVE");
353 assert_eq!(
354 error.message(),
355 "fork is not active for the validation context"
356 );
357 assert_eq!(error.category(), ProtocolErrorCategory::Fork);
358 assert_eq!(
359 error.to_string(),
360 "fork is not active for the validation context"
361 );
362 }
363
364 #[test]
365 fn feature_errors_format_without_payloads() {
366 let error = ProtocolError::Feature(FeatureError::Unsupported);
367
368 assert_eq!(error.code(), "ETH_FEATURE_UNSUPPORTED");
369 assert_eq!(
370 FeatureError::Unsupported.to_string(),
371 "feature is not supported by this crate version"
372 );
373 }
374}