solana/sdk/program/src/nonce/state/mod.rs

255 lines
9.0 KiB
Rust

//! State for durable transaction nonces.
mod current;
pub use current::{Data, DurableNonce, State};
use {
crate::{hash::Hash, pubkey::Pubkey},
serde_derive::{Deserialize, Serialize},
std::collections::HashSet,
};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub enum Versions {
Legacy(Box<State>),
/// Current variants have durable nonce and blockhash domains separated.
Current(Box<State>),
}
#[derive(Debug, Eq, PartialEq)]
pub enum AuthorizeNonceError {
MissingRequiredSignature(/*account authority:*/ Pubkey),
Uninitialized,
}
impl Versions {
pub fn new(state: State) -> Self {
Self::Current(Box::new(state))
}
pub fn state(&self) -> &State {
match self {
Self::Legacy(state) => state,
Self::Current(state) => state,
}
}
/// Checks if the recent_blockhash field in Transaction verifies, and
/// returns nonce account data if so.
pub fn verify_recent_blockhash(
&self,
recent_blockhash: &Hash, // Transaction.message.recent_blockhash
) -> Option<&Data> {
match self {
// Legacy durable nonces are invalid and should not
// allow durable transactions.
Self::Legacy(_) => None,
Self::Current(state) => match **state {
State::Uninitialized => None,
State::Initialized(ref data) => {
(recent_blockhash == &data.blockhash()).then_some(data)
}
},
}
}
/// Upgrades legacy nonces out of chain blockhash domains.
pub fn upgrade(self) -> Option<Self> {
match self {
Self::Legacy(mut state) => {
match *state {
// An Uninitialized legacy nonce cannot verify a durable
// transaction. The nonce will be upgraded to Current
// version when initialized. Therefore there is no need to
// upgrade Uninitialized legacy nonces.
State::Uninitialized => None,
State::Initialized(ref mut data) => {
data.durable_nonce = DurableNonce::from_blockhash(&data.blockhash());
Some(Self::Current(state))
}
}
}
Self::Current(_) => None,
}
}
/// Updates the authority pubkey on the nonce account.
pub fn authorize(
self,
signers: &HashSet<Pubkey>,
authority: Pubkey,
) -> Result<Self, AuthorizeNonceError> {
let data = match self.state() {
State::Uninitialized => return Err(AuthorizeNonceError::Uninitialized),
State::Initialized(data) => data,
};
if !signers.contains(&data.authority) {
return Err(AuthorizeNonceError::MissingRequiredSignature(
data.authority,
));
}
let data = Data::new(
authority,
data.durable_nonce,
data.get_lamports_per_signature(),
);
let state = Box::new(State::Initialized(data));
// Preserve Version variant since cannot
// change durable_nonce field here.
Ok(match self {
Self::Legacy(_) => Self::Legacy,
Self::Current(_) => Self::Current,
}(state))
}
}
impl From<Versions> for State {
fn from(versions: Versions) -> Self {
match versions {
Versions::Legacy(state) => *state,
Versions::Current(state) => *state,
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{fee_calculator::FeeCalculator, pubkey::Pubkey},
std::iter::repeat_with,
};
#[test]
fn test_verify_recent_blockhash() {
let blockhash = Hash::from([171; 32]);
let versions = Versions::Legacy(Box::new(State::Uninitialized));
assert_eq!(versions.verify_recent_blockhash(&blockhash), None);
assert_eq!(versions.verify_recent_blockhash(&Hash::default()), None);
let versions = Versions::Current(Box::new(State::Uninitialized));
assert_eq!(versions.verify_recent_blockhash(&blockhash), None);
assert_eq!(versions.verify_recent_blockhash(&Hash::default()), None);
let durable_nonce = DurableNonce::from_blockhash(&blockhash);
let data = Data {
authority: Pubkey::new_unique(),
durable_nonce,
fee_calculator: FeeCalculator {
lamports_per_signature: 2718,
},
};
let versions = Versions::Legacy(Box::new(State::Initialized(data.clone())));
assert_eq!(versions.verify_recent_blockhash(&Hash::default()), None);
assert_eq!(versions.verify_recent_blockhash(&blockhash), None);
assert_eq!(versions.verify_recent_blockhash(&data.blockhash()), None);
assert_eq!(
versions.verify_recent_blockhash(durable_nonce.as_hash()),
None
);
let durable_nonce = DurableNonce::from_blockhash(durable_nonce.as_hash());
assert_ne!(data.durable_nonce, durable_nonce);
let data = Data {
durable_nonce,
..data
};
let versions = Versions::Current(Box::new(State::Initialized(data.clone())));
assert_eq!(versions.verify_recent_blockhash(&blockhash), None);
assert_eq!(versions.verify_recent_blockhash(&Hash::default()), None);
assert_eq!(
versions.verify_recent_blockhash(&data.blockhash()),
Some(&data)
);
assert_eq!(
versions.verify_recent_blockhash(durable_nonce.as_hash()),
Some(&data)
);
}
#[test]
fn test_nonce_versions_upgrade() {
// Uninitialized
let versions = Versions::Legacy(Box::new(State::Uninitialized));
assert_eq!(versions.upgrade(), None);
// Initialized
let blockhash = Hash::from([171; 32]);
let durable_nonce = DurableNonce::from_blockhash(&blockhash);
let data = Data {
authority: Pubkey::new_unique(),
durable_nonce,
fee_calculator: FeeCalculator {
lamports_per_signature: 2718,
},
};
let versions = Versions::Legacy(Box::new(State::Initialized(data.clone())));
let durable_nonce = DurableNonce::from_blockhash(durable_nonce.as_hash());
assert_ne!(data.durable_nonce, durable_nonce);
let data = Data {
durable_nonce,
..data
};
let versions = versions.upgrade().unwrap();
assert_eq!(
versions,
Versions::Current(Box::new(State::Initialized(data)))
);
assert_eq!(versions.upgrade(), None);
}
#[test]
fn test_nonce_versions_authorize() {
// Uninitialized
let mut signers = repeat_with(Pubkey::new_unique).take(16).collect();
let versions = Versions::Legacy(Box::new(State::Uninitialized));
assert_eq!(
versions.authorize(&signers, Pubkey::new_unique()),
Err(AuthorizeNonceError::Uninitialized)
);
let versions = Versions::Current(Box::new(State::Uninitialized));
assert_eq!(
versions.authorize(&signers, Pubkey::new_unique()),
Err(AuthorizeNonceError::Uninitialized)
);
// Initialized, Legacy
let blockhash = Hash::from([171; 32]);
let durable_nonce = DurableNonce::from_blockhash(&blockhash);
let data = Data {
authority: Pubkey::new_unique(),
durable_nonce,
fee_calculator: FeeCalculator {
lamports_per_signature: 2718,
},
};
let account_authority = data.authority;
let versions = Versions::Legacy(Box::new(State::Initialized(data.clone())));
let authority = Pubkey::new_unique();
assert_ne!(authority, account_authority);
let data = Data { authority, ..data };
assert_eq!(
versions.clone().authorize(&signers, authority),
Err(AuthorizeNonceError::MissingRequiredSignature(
account_authority
)),
);
assert!(signers.insert(account_authority));
assert_eq!(
versions.authorize(&signers, authority),
Ok(Versions::Legacy(Box::new(State::Initialized(data.clone()))))
);
// Initialized, Current
let account_authority = data.authority;
let versions = Versions::Current(Box::new(State::Initialized(data.clone())));
let authority = Pubkey::new_unique();
assert_ne!(authority, account_authority);
let data = Data { authority, ..data };
assert_eq!(
versions.clone().authorize(&signers, authority),
Err(AuthorizeNonceError::MissingRequiredSignature(
account_authority
)),
);
assert!(signers.insert(account_authority));
assert_eq!(
versions.authorize(&signers, authority),
Ok(Versions::Current(Box::new(State::Initialized(data))))
);
}
}