Skip to main content

eth_valkyoth_protocol/
lib.rs

1#![no_std]
2#![forbid(unsafe_code)]
3//! Fork-aware Ethereum protocol validation state.
4
5use core::marker::PhantomData;
6
7use eth_valkyoth_primitives::{BlockNumber, ChainId, UnixTimestamp};
8
9/// Unambiguous fork activation rule.
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub enum ForkActivation {
12    /// Block number alone determines activation.
13    BlockOnly {
14        /// Activation block for this fork view.
15        activation_block: BlockNumber,
16    },
17    /// Both block number and timestamp must be satisfied.
18    BlockAndTimestamp {
19        /// Activation block for this fork view.
20        activation_block: BlockNumber,
21        /// Activation timestamp for timestamp-based forks.
22        activation_timestamp: UnixTimestamp,
23    },
24}
25
26/// Ethereum fork rules selected for a validation operation.
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub struct ForkSpec {
29    /// Chain being validated.
30    pub chain_id: ChainId,
31    /// Activation rule for this fork view.
32    pub activation: ForkActivation,
33}
34
35/// Validation context that must be explicit for consensus-sensitive operations.
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub struct ValidationContext {
38    /// Fork rules.
39    pub fork: ForkSpec,
40    /// Current block number.
41    pub block_number: BlockNumber,
42    /// Current block timestamp.
43    pub timestamp: UnixTimestamp,
44}
45
46impl ValidationContext {
47    /// Returns whether the configured fork is active for this context.
48    #[must_use]
49    pub fn fork_is_active(self) -> bool {
50        match self.fork.activation {
51            ForkActivation::BlockOnly { activation_block } => self.block_number >= activation_block,
52            ForkActivation::BlockAndTimestamp {
53                activation_block,
54                activation_timestamp,
55            } => self.block_number >= activation_block && self.timestamp >= activation_timestamp,
56        }
57    }
58}
59
60/// Raw wire input was accepted by the codec.
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62pub struct Decoded;
63
64/// Canonical wire form and type-specific structure were checked.
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub struct Canonical;
67
68/// Fork-specific validity was checked.
69#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub struct ForkValidated;
71
72/// Sender recovery succeeded.
73#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74pub struct SenderRecovered;
75
76/// A transaction token whose validation state is tracked at compile time.
77#[derive(Clone, Copy, Debug, Eq, PartialEq)]
78pub struct Transaction<State> {
79    _state: PhantomData<State>,
80}
81
82impl Transaction<Decoded> {
83    /// Creates a token for a decoded transaction.
84    #[must_use]
85    pub const fn decoded() -> Self {
86        Self {
87            _state: PhantomData,
88        }
89    }
90
91    /// Advances to canonical form after canonical checks pass.
92    #[must_use]
93    pub const fn into_canonical(self) -> Transaction<Canonical> {
94        Transaction {
95            _state: PhantomData,
96        }
97    }
98}
99
100impl Transaction<Canonical> {
101    /// Advances after fork-specific validation passes.
102    #[must_use]
103    pub const fn into_fork_validated(self) -> Transaction<ForkValidated> {
104        Transaction {
105            _state: PhantomData,
106        }
107    }
108}
109
110impl Transaction<ForkValidated> {
111    /// Advances after sender recovery succeeds.
112    #[must_use]
113    pub const fn into_sender_recovered(self) -> Transaction<SenderRecovered> {
114        Transaction {
115            _state: PhantomData,
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn activation_requires_block_and_time() {
126        let context = ValidationContext {
127            fork: ForkSpec {
128                chain_id: ChainId::new(1),
129                activation: ForkActivation::BlockAndTimestamp {
130                    activation_block: BlockNumber::new(10),
131                    activation_timestamp: UnixTimestamp::new(20),
132                },
133            },
134            block_number: BlockNumber::new(10),
135            timestamp: UnixTimestamp::new(19),
136        };
137        assert!(!context.fork_is_active());
138    }
139
140    #[test]
141    fn block_only_activation_ignores_timestamp() {
142        let context = ValidationContext {
143            fork: ForkSpec {
144                chain_id: ChainId::new(1),
145                activation: ForkActivation::BlockOnly {
146                    activation_block: BlockNumber::new(10),
147                },
148            },
149            block_number: BlockNumber::new(10),
150            timestamp: UnixTimestamp::new(0),
151        };
152        assert!(context.fork_is_active());
153    }
154
155    #[test]
156    fn transaction_typestate_advances_in_order() {
157        let transaction = Transaction::decoded()
158            .into_canonical()
159            .into_fork_validated()
160            .into_sender_recovered();
161        assert_eq!(
162            transaction,
163            Transaction::<SenderRecovered> {
164                _state: PhantomData
165            }
166        );
167    }
168}