Skip to main content

solana_nonce_account/
lib.rs

1//! Functions related to nonce accounts.
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4// Imported anonymously: either name would be wrong under the other feature, and both in
5// scope at once makes `state` ambiguous.
6#[cfg(all(feature = "bincode", not(feature = "wincode")))]
7use solana_account::state_traits::StateMut as _;
8#[cfg(feature = "wincode")]
9use solana_account::state_traits::StateMutWincode as _;
10use {
11    solana_account::{AccountSharedData, ReadableAccount},
12    solana_nonce::state::State,
13    solana_sdk_ids::system_program,
14};
15#[cfg(any(feature = "bincode", feature = "wincode"))]
16use {
17    solana_hash::Hash,
18    solana_nonce::{state::Data, versions::Versions},
19    std::cell::RefCell,
20};
21
22#[cfg(any(feature = "bincode", feature = "wincode"))]
23pub fn create_account(lamports: u64) -> RefCell<AccountSharedData> {
24    // bincode's `new_data_with_space` is inherent while wincode's is a trait method, so this
25    // shadows to bincode whenever that codec is compiled in. The encodings agree.
26    RefCell::new(
27        AccountSharedData::new_data_with_space(
28            lamports,
29            &Versions::new(State::Uninitialized),
30            State::size(),
31            &system_program::id(),
32        )
33        .expect("nonce_account"),
34    )
35}
36
37/// Checks if the recent_blockhash field in Transaction verifies, and returns
38/// nonce account data if so.
39#[cfg(any(feature = "bincode", feature = "wincode"))]
40pub fn verify_nonce_account(
41    account: &AccountSharedData,
42    recent_blockhash: &Hash, // Transaction.message.recent_blockhash
43) -> Option<Data> {
44    (account.owner() == &system_program::id())
45        .then(|| {
46            let versions: Versions = account.state().ok()?;
47            versions.verify_recent_blockhash(recent_blockhash).cloned()
48        })
49        .flatten()
50}
51
52#[cfg(any(feature = "bincode", feature = "wincode"))]
53pub fn lamports_per_signature_of(account: &AccountSharedData) -> Option<u64> {
54    let versions: Versions = account.state().ok()?;
55    match versions.state() {
56        State::Initialized(data) => Some(data.fee_calculator.lamports_per_signature),
57        State::Uninitialized => None,
58    }
59}
60
61#[derive(Copy, Clone, Debug, Eq, PartialEq)]
62pub enum SystemAccountKind {
63    System,
64    Nonce,
65}
66
67pub fn get_system_account_kind(account: &AccountSharedData) -> Option<SystemAccountKind> {
68    if !system_program::check_id(account.owner()) {
69        return None;
70    }
71
72    let data = account.data();
73
74    if data.is_empty() {
75        Some(SystemAccountKind::System)
76    } else if data.len() == State::size() {
77        const NONCE_VERSIONS_LEGACY: u32 = 0;
78        const NONCE_VERSIONS_CURRENT: u32 = 1;
79        const NONCE_STATE_INITIALIZED: u32 = 1;
80
81        let versions_tag = u32::from_le_bytes(data.get(..4)?.try_into().ok()?);
82        let state_tag = u32::from_le_bytes(data.get(4..8)?.try_into().ok()?);
83
84        match (versions_tag, state_tag) {
85            (NONCE_VERSIONS_LEGACY, NONCE_STATE_INITIALIZED) => Some(SystemAccountKind::Nonce),
86            (NONCE_VERSIONS_CURRENT, NONCE_STATE_INITIALIZED) => Some(SystemAccountKind::Nonce),
87            _ => None,
88        }
89    } else {
90        None
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use {
97        super::*,
98        solana_nonce::{state::Data, versions::Versions},
99        solana_pubkey::Pubkey,
100    };
101    #[cfg(any(feature = "bincode", feature = "wincode"))]
102    use {solana_fee_calculator::FeeCalculator, solana_nonce::state::DurableNonce};
103
104    // Written by bincode, always compiled in for tests; read back through the active codec.
105    #[cfg(any(feature = "bincode", feature = "wincode"))]
106    #[test]
107    fn test_create_account() {
108        let account = create_account(42);
109        let account = account.borrow();
110        assert_eq!(account.lamports(), 42);
111        assert_eq!(account.owner(), &system_program::id());
112        assert_eq!(account.data().len(), State::size());
113        let versions: Versions = account.state().unwrap();
114        assert_eq!(versions.state(), &State::Uninitialized);
115    }
116
117    #[cfg(any(feature = "bincode", feature = "wincode"))]
118    #[test]
119    fn test_verify_bad_account_owner_fails() {
120        let program_id = Pubkey::new_unique();
121        assert_ne!(program_id, system_program::id());
122        let account = AccountSharedData::new_data_with_space(
123            42,
124            &Versions::new(State::Uninitialized),
125            State::size(),
126            &program_id,
127        )
128        .expect("nonce_account");
129        assert_eq!(verify_nonce_account(&account, &Hash::default()), None);
130    }
131
132    #[cfg(any(feature = "bincode", feature = "wincode"))]
133    fn new_nonce_account(versions: Versions) -> AccountSharedData {
134        AccountSharedData::new_data(
135            1_000_000,             // lamports
136            &versions,             // state
137            &system_program::id(), // owner
138        )
139        .unwrap()
140    }
141
142    #[cfg(any(feature = "bincode", feature = "wincode"))]
143    #[test]
144    fn test_verify_nonce_account() {
145        let blockhash = Hash::from([171; 32]);
146        let versions = Versions::Legacy(Box::new(State::Uninitialized));
147        let account = new_nonce_account(versions);
148        assert_eq!(verify_nonce_account(&account, &blockhash), None);
149        assert_eq!(verify_nonce_account(&account, &Hash::default()), None);
150        let versions = Versions::Current(Box::new(State::Uninitialized));
151        let account = new_nonce_account(versions);
152        assert_eq!(verify_nonce_account(&account, &blockhash), None);
153        assert_eq!(verify_nonce_account(&account, &Hash::default()), None);
154        let durable_nonce = DurableNonce::from_blockhash(&blockhash);
155        let data = Data {
156            authority: Pubkey::new_unique(),
157            durable_nonce,
158            fee_calculator: FeeCalculator {
159                lamports_per_signature: 2718,
160            },
161        };
162        let versions = Versions::Legacy(Box::new(State::Initialized(data.clone())));
163        let account = new_nonce_account(versions);
164        assert_eq!(verify_nonce_account(&account, &blockhash), None);
165        assert_eq!(verify_nonce_account(&account, &Hash::default()), None);
166        assert_eq!(verify_nonce_account(&account, &data.blockhash()), None);
167        assert_eq!(
168            verify_nonce_account(&account, durable_nonce.as_hash()),
169            None
170        );
171        let durable_nonce = DurableNonce::from_blockhash(durable_nonce.as_hash());
172        assert_ne!(data.durable_nonce, durable_nonce);
173        let data = Data {
174            durable_nonce,
175            ..data
176        };
177        let versions = Versions::Current(Box::new(State::Initialized(data.clone())));
178        let account = new_nonce_account(versions);
179        assert_eq!(verify_nonce_account(&account, &blockhash), None);
180        assert_eq!(verify_nonce_account(&account, &Hash::default()), None);
181        assert_eq!(
182            verify_nonce_account(&account, &data.blockhash()),
183            Some(data.clone())
184        );
185        assert_eq!(
186            verify_nonce_account(&account, durable_nonce.as_hash()),
187            Some(data)
188        );
189    }
190
191    #[test]
192    fn test_get_system_account_kind() {
193        // protect `get_system_account_kind()` against the addition of new nonce variants.
194        // if anyone even attempts to add a new nonce variant however they should be punished
195        fn _assert_nonce_versions(v: Versions, s: State) {
196            match v {
197                Versions::Legacy(..) => {}
198                Versions::Current(..) => {}
199            }
200            match s {
201                State::Uninitialized => {}
202                State::Initialized(..) => {}
203            }
204        }
205
206        // assert our function produces the expected result
207        let assert_correct = |bytes: &[u8], kind: Option<SystemAccountKind>| {
208            let mut account = AccountSharedData::new(0, 0, &system_program::id());
209            account.set_data_from_slice(bytes);
210            assert_eq!(get_system_account_kind(&account), kind);
211        };
212
213        // the three (unfortunately rather than two) valid fee-payer types
214        let system_bytes = vec![];
215        let legacy_nonce_bytes = bincode::serialize(&Versions::Legacy(Box::new(
216            State::Initialized(Data::default()),
217        )))
218        .unwrap();
219        let current_nonce_bytes = bincode::serialize(&Versions::Current(Box::new(
220            State::Initialized(Data::default()),
221        )))
222        .unwrap();
223
224        // success
225        assert_correct(&system_bytes, Some(SystemAccountKind::System));
226        assert_correct(&legacy_nonce_bytes, Some(SystemAccountKind::Nonce));
227        assert_correct(&current_nonce_bytes, Some(SystemAccountKind::Nonce));
228
229        // non-system fails
230        for bytes in [&system_bytes, &legacy_nonce_bytes, &current_nonce_bytes] {
231            let mut non_system = AccountSharedData::new(0, 0, &Pubkey::new_unique());
232            non_system.set_data_from_slice(bytes);
233            assert_eq!(get_system_account_kind(&non_system), None);
234        }
235
236        // uninitialized nonce fails
237        for nonce in &[Versions::Legacy, Versions::Current] {
238            let mut bytes = bincode::serialize(&nonce(Box::new(State::Uninitialized))).unwrap();
239            bytes.resize(State::size(), 0);
240            assert_correct(&bytes, None);
241        }
242
243        for bytes in [&legacy_nonce_bytes, &current_nonce_bytes] {
244            // length too short fails
245            for len in 1..bytes.len() {
246                assert_correct(&bytes[..len], None);
247            }
248
249            // length too long fails
250            let mut extended = bytes.clone();
251            extended.push(0);
252            assert_correct(&extended, None);
253
254            // union tag variations fail
255            for byte in 0..=255 {
256                for i in 0..=7 {
257                    // bytes would not change
258                    if bytes[i] == byte {
259                        continue;
260                    }
261
262                    let mut corrupted = bytes.clone();
263                    corrupted[i] = byte;
264
265                    // legacy was changed to current or vice versa
266                    if corrupted == legacy_nonce_bytes || corrupted == current_nonce_bytes {
267                        continue;
268                    }
269
270                    assert_correct(&corrupted, None);
271                }
272            }
273
274            // data variation is ok
275            for i in 8..bytes.len() {
276                let mut with_data = bytes.clone();
277                with_data[i] = 255;
278                assert_correct(&with_data, Some(SystemAccountKind::Nonce));
279            }
280        }
281    }
282}