solana_svm/
nonce_info.rs

1use {
2    solana_sdk::{
3        account::AccountSharedData,
4        account_utils::StateMut,
5        nonce::state::{DurableNonce, State as NonceState, Versions as NonceVersions},
6        pubkey::Pubkey,
7    },
8    thiserror::Error,
9};
10
11/// Holds limited nonce info available during transaction checks
12#[derive(Clone, Debug, Default, PartialEq, Eq)]
13pub struct NonceInfo {
14    address: Pubkey,
15    account: AccountSharedData,
16}
17
18#[derive(Error, Debug, PartialEq)]
19pub enum AdvanceNonceError {
20    #[error("Invalid account")]
21    Invalid,
22    #[error("Uninitialized nonce")]
23    Uninitialized,
24}
25
26impl NonceInfo {
27    pub fn new(address: Pubkey, account: AccountSharedData) -> Self {
28        Self { address, account }
29    }
30
31    // Advance the stored blockhash to prevent fee theft by someone
32    // replaying nonce transactions that have failed with an
33    // `InstructionError`.
34    pub fn try_advance_nonce(
35        &mut self,
36        durable_nonce: DurableNonce,
37        lamports_per_signature: u64,
38    ) -> Result<(), AdvanceNonceError> {
39        let nonce_versions = StateMut::<NonceVersions>::state(&self.account)
40            .map_err(|_| AdvanceNonceError::Invalid)?;
41        if let NonceState::Initialized(ref data) = nonce_versions.state() {
42            let nonce_state =
43                NonceState::new_initialized(&data.authority, durable_nonce, lamports_per_signature);
44            let nonce_versions = NonceVersions::new(nonce_state);
45            self.account.set_state(&nonce_versions).unwrap();
46            Ok(())
47        } else {
48            Err(AdvanceNonceError::Uninitialized)
49        }
50    }
51
52    pub fn address(&self) -> &Pubkey {
53        &self.address
54    }
55
56    pub fn account(&self) -> &AccountSharedData {
57        &self.account
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use {
64        super::*,
65        solana_sdk::{
66            hash::Hash,
67            nonce::state::{
68                Data as NonceData, DurableNonce, State as NonceState, Versions as NonceVersions,
69            },
70            system_program,
71        },
72    };
73
74    fn create_nonce_account(state: NonceState) -> AccountSharedData {
75        AccountSharedData::new_data(1_000_000, &NonceVersions::new(state), &system_program::id())
76            .unwrap()
77    }
78
79    #[test]
80    fn test_nonce_info() {
81        let nonce_address = Pubkey::new_unique();
82        let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
83        let lamports_per_signature = 42;
84        let nonce_account = create_nonce_account(NonceState::Initialized(NonceData::new(
85            Pubkey::default(),
86            durable_nonce,
87            lamports_per_signature,
88        )));
89
90        let nonce_info = NonceInfo::new(nonce_address, nonce_account.clone());
91        assert_eq!(*nonce_info.address(), nonce_address);
92        assert_eq!(*nonce_info.account(), nonce_account);
93    }
94
95    #[test]
96    fn test_try_advance_nonce_success() {
97        let authority = Pubkey::new_unique();
98        let mut nonce_info = NonceInfo::new(
99            Pubkey::new_unique(),
100            create_nonce_account(NonceState::Initialized(NonceData::new(
101                authority,
102                DurableNonce::from_blockhash(&Hash::new_unique()),
103                42,
104            ))),
105        );
106
107        let new_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
108        let new_lamports_per_signature = 100;
109        let result = nonce_info.try_advance_nonce(new_nonce, new_lamports_per_signature);
110        assert_eq!(result, Ok(()));
111
112        let nonce_versions = StateMut::<NonceVersions>::state(&nonce_info.account).unwrap();
113        assert_eq!(
114            &NonceState::Initialized(NonceData::new(
115                authority,
116                new_nonce,
117                new_lamports_per_signature
118            )),
119            nonce_versions.state()
120        );
121    }
122
123    #[test]
124    fn test_try_advance_nonce_invalid() {
125        let mut nonce_info = NonceInfo::new(
126            Pubkey::new_unique(),
127            AccountSharedData::new(1_000_000, 0, &Pubkey::default()),
128        );
129
130        let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
131        let result = nonce_info.try_advance_nonce(durable_nonce, 5000);
132        assert_eq!(result, Err(AdvanceNonceError::Invalid));
133    }
134
135    #[test]
136    fn test_try_advance_nonce_uninitialized() {
137        let mut nonce_info = NonceInfo::new(
138            Pubkey::new_unique(),
139            create_nonce_account(NonceState::Uninitialized),
140        );
141
142        let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
143        let result = nonce_info.try_advance_nonce(durable_nonce, 5000);
144        assert_eq!(result, Err(AdvanceNonceError::Uninitialized));
145    }
146}