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 DYNAMIC_FEE_TRANSACTION_FIELD_COUNT, DynamicFeeTransactionDecodeError,
19 DynamicFeeTransactionDecodeErrorCategory, DynamicFeeTransactionField, DynamicFeeTransactionTo,
20 EIP_2718_MAX_TYPED_PREFIX, EIP_2718_RESERVED_PREFIX, EIP_2718_SCALAR_PREFIX_START,
21 EIP_2718_TYPED_ZERO_PREFIX, InvalidSignatureYParity, LEGACY_TRANSACTION_FIELD_COUNT,
22 LEGACY_TRANSACTION_PREFIX_START, LegacyTransactionDecodeError,
23 LegacyTransactionDecodeErrorCategory, LegacyTransactionField, LegacyTransactionTo,
24 SignatureYParity, TransactionEnvelope, TransactionEnvelopeError,
25 TransactionEnvelopeErrorCategory, TypedTransactionEnvelope, UnvalidatedAccessListTransaction,
26 UnvalidatedDynamicFeeTransaction, UnvalidatedLegacyTransaction, decode_access_list_transaction,
27 decode_dynamic_fee_transaction, decode_legacy_transaction, decode_transaction_envelope,
28};
29
30#[non_exhaustive]
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub enum ProtocolError {
34 Feature(FeatureError),
36 Fork(ForkError),
38 InvalidStateTransition,
40}
41
42impl ProtocolError {
43 #[must_use]
45 pub const fn code(self) -> &'static str {
46 match self {
47 Self::Feature(error) => error.code(),
48 Self::Fork(error) => error.code(),
49 Self::InvalidStateTransition => "ETH_PROTOCOL_INVALID_STATE_TRANSITION",
50 }
51 }
52
53 #[must_use]
55 pub const fn message(self) -> &'static str {
56 match self {
57 Self::Feature(error) => error.message(),
58 Self::Fork(error) => error.message(),
59 Self::InvalidStateTransition => {
60 "validation state transition is not allowed from the current state"
61 }
62 }
63 }
64
65 #[must_use]
67 pub const fn category(self) -> ProtocolErrorCategory {
68 match self {
69 Self::Feature(_) => ProtocolErrorCategory::Feature,
70 Self::Fork(_) => ProtocolErrorCategory::Fork,
71 Self::InvalidStateTransition => ProtocolErrorCategory::State,
72 }
73 }
74}
75
76impl fmt::Display for ProtocolError {
77 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78 formatter.write_str(self.message())
79 }
80}
81
82#[cfg(feature = "std")]
83impl std::error::Error for ProtocolError {}
84
85#[non_exhaustive]
87#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88pub enum ProtocolErrorCategory {
89 Feature,
91 Fork,
93 State,
95}
96
97#[non_exhaustive]
99#[derive(Clone, Copy, Debug, Eq, PartialEq)]
100pub enum FeatureError {
101 Disabled,
103 Unsupported,
105}
106
107impl FeatureError {
108 #[must_use]
110 pub const fn code(self) -> &'static str {
111 match self {
112 Self::Disabled => "ETH_FEATURE_DISABLED",
113 Self::Unsupported => "ETH_FEATURE_UNSUPPORTED",
114 }
115 }
116
117 #[must_use]
119 pub const fn message(self) -> &'static str {
120 match self {
121 Self::Disabled => "feature is disabled for this operation",
122 Self::Unsupported => "feature is not supported by this crate version",
123 }
124 }
125}
126
127impl fmt::Display for FeatureError {
128 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
129 formatter.write_str(self.message())
130 }
131}
132
133#[cfg(feature = "std")]
134impl std::error::Error for FeatureError {}
135
136#[non_exhaustive]
138#[derive(Clone, Copy, Debug, Eq, PartialEq)]
139pub enum ForkError {
140 Unsupported,
142 Inactive,
144 MissingActivation,
146}
147
148impl ForkError {
149 #[must_use]
151 pub const fn code(self) -> &'static str {
152 match self {
153 Self::Unsupported => "ETH_FORK_UNSUPPORTED",
154 Self::Inactive => "ETH_FORK_INACTIVE",
155 Self::MissingActivation => "ETH_FORK_MISSING_ACTIVATION",
156 }
157 }
158
159 #[must_use]
161 pub const fn message(self) -> &'static str {
162 match self {
163 Self::Unsupported => "fork is not supported by this crate version",
164 Self::Inactive => "fork is not active for the validation context",
165 Self::MissingActivation => "fork activation data is incomplete",
166 }
167 }
168}
169
170impl fmt::Display for ForkError {
171 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
172 formatter.write_str(self.message())
173 }
174}
175
176#[cfg(feature = "std")]
177impl std::error::Error for ForkError {}
178
179#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181pub enum ForkActivation {
182 BlockOnly {
184 activation_block: BlockNumber,
186 },
187 BlockAndTimestamp {
189 activation_block: BlockNumber,
191 activation_timestamp: UnixTimestamp,
193 },
194}
195
196#[derive(Clone, Copy, Debug, Eq, PartialEq)]
198pub struct ForkSpec {
199 pub chain_id: ChainId,
201 pub activation: ForkActivation,
203}
204
205#[derive(Clone, Copy, Debug, Eq, PartialEq)]
207pub struct ValidationContext {
208 pub fork: ForkSpec,
210 pub block_number: BlockNumber,
212 pub timestamp: UnixTimestamp,
214}
215
216impl ValidationContext {
217 #[must_use]
219 pub fn fork_is_active(self) -> bool {
220 match self.fork.activation {
221 ForkActivation::BlockOnly { activation_block } => self.block_number >= activation_block,
222 ForkActivation::BlockAndTimestamp {
223 activation_block,
224 activation_timestamp,
225 } => self.block_number >= activation_block && self.timestamp >= activation_timestamp,
226 }
227 }
228}
229
230#[derive(Clone, Copy, Debug, Eq, PartialEq)]
232pub struct Decoded;
233
234#[derive(Clone, Copy, Debug, Eq, PartialEq)]
236pub struct Canonical;
237
238#[derive(Clone, Copy, Debug, Eq, PartialEq)]
240pub struct ForkValidated;
241
242#[derive(Clone, Copy, Debug, Eq, PartialEq)]
244pub struct SenderRecovered;
245
246#[derive(Clone, Copy, Debug, Eq, PartialEq)]
248pub struct Transaction<State> {
249 _state: PhantomData<State>,
250}
251
252impl Transaction<Decoded> {
253 #[must_use]
258 #[cfg(test)]
259 pub(crate) const fn decoded() -> Self {
260 Self {
261 _state: PhantomData,
262 }
263 }
264
265 #[must_use]
267 pub const fn into_canonical(self) -> Transaction<Canonical> {
268 Transaction {
269 _state: PhantomData,
270 }
271 }
272}
273
274impl Transaction<Canonical> {
275 #[must_use]
277 pub const fn into_fork_validated(self) -> Transaction<ForkValidated> {
278 Transaction {
279 _state: PhantomData,
280 }
281 }
282}
283
284impl Transaction<ForkValidated> {
285 #[must_use]
287 pub const fn into_sender_recovered(self) -> Transaction<SenderRecovered> {
288 Transaction {
289 _state: PhantomData,
290 }
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 extern crate std;
298 use std::string::ToString;
299
300 #[test]
301 fn activation_requires_block_and_time() {
302 let context = ValidationContext {
303 fork: ForkSpec {
304 chain_id: ChainId::new(1),
305 activation: ForkActivation::BlockAndTimestamp {
306 activation_block: BlockNumber::new(10),
307 activation_timestamp: UnixTimestamp::new(20),
308 },
309 },
310 block_number: BlockNumber::new(10),
311 timestamp: UnixTimestamp::new(19),
312 };
313 assert!(!context.fork_is_active());
314 }
315
316 #[test]
317 fn block_only_activation_ignores_timestamp() {
318 let context = ValidationContext {
319 fork: ForkSpec {
320 chain_id: ChainId::new(1),
321 activation: ForkActivation::BlockOnly {
322 activation_block: BlockNumber::new(10),
323 },
324 },
325 block_number: BlockNumber::new(10),
326 timestamp: UnixTimestamp::new(0),
327 };
328 assert!(context.fork_is_active());
329 }
330
331 #[test]
332 fn transaction_typestate_advances_in_order() {
333 let transaction = Transaction::decoded()
334 .into_canonical()
335 .into_fork_validated()
336 .into_sender_recovered();
337 assert_eq!(
338 transaction,
339 Transaction::<SenderRecovered> {
340 _state: PhantomData
341 }
342 );
343 }
344
345 #[test]
346 fn protocol_errors_have_stable_codes_and_categories() {
347 let error = ProtocolError::Fork(ForkError::Inactive);
348
349 assert_eq!(error.code(), "ETH_FORK_INACTIVE");
350 assert_eq!(
351 error.message(),
352 "fork is not active for the validation context"
353 );
354 assert_eq!(error.category(), ProtocolErrorCategory::Fork);
355 assert_eq!(
356 error.to_string(),
357 "fork is not active for the validation context"
358 );
359 }
360
361 #[test]
362 fn feature_errors_format_without_payloads() {
363 let error = ProtocolError::Feature(FeatureError::Unsupported);
364
365 assert_eq!(error.code(), "ETH_FEATURE_UNSUPPORTED");
366 assert_eq!(
367 FeatureError::Unsupported.to_string(),
368 "feature is not supported by this crate version"
369 );
370 }
371}