Skip to main content

solana_nonce_account/
lib.rs

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