solana_program/nonce/state/
mod.rs

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