solana_svm/
nonce_info.rs

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