solana_nonce/
versions.rs

1//! State for durable transaction nonces.
2
3use {
4    crate::state::{Data, DurableNonce, State},
5    solana_hash::Hash,
6    solana_pubkey::Pubkey,
7    std::collections::HashSet,
8};
9
10#[cfg_attr(
11    feature = "serde",
12    derive(serde_derive::Deserialize, serde_derive::Serialize)
13)]
14#[derive(Debug, PartialEq, Eq, Clone)]
15pub enum Versions {
16    Legacy(Box<State>),
17    /// Current variants have durable nonce and blockhash domains separated.
18    Current(Box<State>),
19}
20
21#[derive(Debug, Eq, PartialEq)]
22pub enum AuthorizeNonceError {
23    MissingRequiredSignature(/*account authority:*/ Pubkey),
24    Uninitialized,
25}
26
27impl Versions {
28    pub fn new(state: State) -> Self {
29        Self::Current(Box::new(state))
30    }
31
32    pub fn state(&self) -> &State {
33        match self {
34            Self::Legacy(state) => state,
35            Self::Current(state) => state,
36        }
37    }
38
39    /// Checks if the recent_blockhash field in Transaction verifies, and
40    /// returns nonce account data if so.
41    pub fn verify_recent_blockhash(
42        &self,
43        recent_blockhash: &Hash, // Transaction.message.recent_blockhash
44    ) -> Option<&Data> {
45        match self {
46            // Legacy durable nonces are invalid and should not
47            // allow durable transactions.
48            Self::Legacy(_) => None,
49            Self::Current(state) => match **state {
50                State::Uninitialized => None,
51                State::Initialized(ref data) => {
52                    (recent_blockhash == &data.blockhash()).then_some(data)
53                }
54            },
55        }
56    }
57
58    /// Upgrades legacy nonces out of chain blockhash domains.
59    pub fn upgrade(self) -> Option<Self> {
60        match self {
61            Self::Legacy(mut state) => {
62                match *state {
63                    // An Uninitialized legacy nonce cannot verify a durable
64                    // transaction. The nonce will be upgraded to Current
65                    // version when initialized. Therefore there is no need to
66                    // upgrade Uninitialized legacy nonces.
67                    State::Uninitialized => None,
68                    State::Initialized(ref mut data) => {
69                        data.durable_nonce = DurableNonce::from_blockhash(&data.blockhash());
70                        Some(Self::Current(state))
71                    }
72                }
73            }
74            Self::Current(_) => None,
75        }
76    }
77
78    /// Updates the authority pubkey on the nonce account.
79    pub fn authorize(
80        self,
81        signers: &HashSet<Pubkey>,
82        authority: Pubkey,
83    ) -> Result<Self, AuthorizeNonceError> {
84        let data = match self.state() {
85            State::Uninitialized => return Err(AuthorizeNonceError::Uninitialized),
86            State::Initialized(data) => data,
87        };
88        if !signers.contains(&data.authority) {
89            return Err(AuthorizeNonceError::MissingRequiredSignature(
90                data.authority,
91            ));
92        }
93        let data = Data::new(
94            authority,
95            data.durable_nonce,
96            data.get_lamports_per_signature(),
97        );
98        let state = Box::new(State::Initialized(data));
99        // Preserve Version variant since cannot
100        // change durable_nonce field here.
101        Ok(match self {
102            Self::Legacy(_) => Self::Legacy,
103            Self::Current(_) => Self::Current,
104        }(state))
105    }
106}
107
108impl From<Versions> for State {
109    fn from(versions: Versions) -> Self {
110        match versions {
111            Versions::Legacy(state) => *state,
112            Versions::Current(state) => *state,
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use {
120        super::*, solana_fee_calculator::FeeCalculator, solana_pubkey::Pubkey,
121        std::iter::repeat_with,
122    };
123
124    #[test]
125    fn test_verify_recent_blockhash() {
126        let blockhash = Hash::from([171; 32]);
127        let versions = Versions::Legacy(Box::new(State::Uninitialized));
128        assert_eq!(versions.verify_recent_blockhash(&blockhash), None);
129        assert_eq!(versions.verify_recent_blockhash(&Hash::default()), None);
130        let versions = Versions::Current(Box::new(State::Uninitialized));
131        assert_eq!(versions.verify_recent_blockhash(&blockhash), None);
132        assert_eq!(versions.verify_recent_blockhash(&Hash::default()), None);
133        let durable_nonce = DurableNonce::from_blockhash(&blockhash);
134        let data = Data {
135            authority: Pubkey::new_unique(),
136            durable_nonce,
137            fee_calculator: FeeCalculator {
138                lamports_per_signature: 2718,
139            },
140        };
141        let versions = Versions::Legacy(Box::new(State::Initialized(data.clone())));
142        assert_eq!(versions.verify_recent_blockhash(&Hash::default()), None);
143        assert_eq!(versions.verify_recent_blockhash(&blockhash), None);
144        assert_eq!(versions.verify_recent_blockhash(&data.blockhash()), None);
145        assert_eq!(
146            versions.verify_recent_blockhash(durable_nonce.as_hash()),
147            None
148        );
149        let durable_nonce = DurableNonce::from_blockhash(durable_nonce.as_hash());
150        assert_ne!(data.durable_nonce, durable_nonce);
151        let data = Data {
152            durable_nonce,
153            ..data
154        };
155        let versions = Versions::Current(Box::new(State::Initialized(data.clone())));
156        assert_eq!(versions.verify_recent_blockhash(&blockhash), None);
157        assert_eq!(versions.verify_recent_blockhash(&Hash::default()), None);
158        assert_eq!(
159            versions.verify_recent_blockhash(&data.blockhash()),
160            Some(&data)
161        );
162        assert_eq!(
163            versions.verify_recent_blockhash(durable_nonce.as_hash()),
164            Some(&data)
165        );
166    }
167
168    #[test]
169    fn test_nonce_versions_upgrade() {
170        // Uninitialized
171        let versions = Versions::Legacy(Box::new(State::Uninitialized));
172        assert_eq!(versions.upgrade(), None);
173        // Initialized
174        let blockhash = Hash::from([171; 32]);
175        let durable_nonce = DurableNonce::from_blockhash(&blockhash);
176        let data = Data {
177            authority: Pubkey::new_unique(),
178            durable_nonce,
179            fee_calculator: FeeCalculator {
180                lamports_per_signature: 2718,
181            },
182        };
183        let versions = Versions::Legacy(Box::new(State::Initialized(data.clone())));
184        let durable_nonce = DurableNonce::from_blockhash(durable_nonce.as_hash());
185        assert_ne!(data.durable_nonce, durable_nonce);
186        let data = Data {
187            durable_nonce,
188            ..data
189        };
190        let versions = versions.upgrade().unwrap();
191        assert_eq!(
192            versions,
193            Versions::Current(Box::new(State::Initialized(data)))
194        );
195        assert_eq!(versions.upgrade(), None);
196    }
197
198    #[test]
199    fn test_nonce_versions_authorize() {
200        // Uninitialized
201        let mut signers = repeat_with(Pubkey::new_unique).take(16).collect();
202        let versions = Versions::Legacy(Box::new(State::Uninitialized));
203        assert_eq!(
204            versions.authorize(&signers, Pubkey::new_unique()),
205            Err(AuthorizeNonceError::Uninitialized)
206        );
207        let versions = Versions::Current(Box::new(State::Uninitialized));
208        assert_eq!(
209            versions.authorize(&signers, Pubkey::new_unique()),
210            Err(AuthorizeNonceError::Uninitialized)
211        );
212        // Initialized, Legacy
213        let blockhash = Hash::from([171; 32]);
214        let durable_nonce = DurableNonce::from_blockhash(&blockhash);
215        let data = Data {
216            authority: Pubkey::new_unique(),
217            durable_nonce,
218            fee_calculator: FeeCalculator {
219                lamports_per_signature: 2718,
220            },
221        };
222        let account_authority = data.authority;
223        let versions = Versions::Legacy(Box::new(State::Initialized(data.clone())));
224        let authority = Pubkey::new_unique();
225        assert_ne!(authority, account_authority);
226        let data = Data { authority, ..data };
227        assert_eq!(
228            versions.clone().authorize(&signers, authority),
229            Err(AuthorizeNonceError::MissingRequiredSignature(
230                account_authority
231            )),
232        );
233        assert!(signers.insert(account_authority));
234        assert_eq!(
235            versions.authorize(&signers, authority),
236            Ok(Versions::Legacy(Box::new(State::Initialized(data.clone()))))
237        );
238        // Initialized, Current
239        let account_authority = data.authority;
240        let versions = Versions::Current(Box::new(State::Initialized(data.clone())));
241        let authority = Pubkey::new_unique();
242        assert_ne!(authority, account_authority);
243        let data = Data { authority, ..data };
244        assert_eq!(
245            versions.clone().authorize(&signers, authority),
246            Err(AuthorizeNonceError::MissingRequiredSignature(
247                account_authority
248            )),
249        );
250        assert!(signers.insert(account_authority));
251        assert_eq!(
252            versions.authorize(&signers, authority),
253            Ok(Versions::Current(Box::new(State::Initialized(data))))
254        );
255    }
256}