@@ -406,7 +406,7 @@ pub struct CliCommandInfo {
 | 
			
		||||
    pub signers: CliSigners,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub enum CliError {
 | 
			
		||||
    BadParameter(String),
 | 
			
		||||
    CommandNotRecognized(String),
 | 
			
		||||
@@ -440,6 +440,17 @@ impl From<Box<dyn error::Error>> for CliError {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<CliNonceError> for CliError {
 | 
			
		||||
    fn from(error: CliNonceError) -> Self {
 | 
			
		||||
        match error {
 | 
			
		||||
            CliNonceError::Client(client_error) => {
 | 
			
		||||
                Self::RpcRequestError(format!("{:?}", client_error))
 | 
			
		||||
            }
 | 
			
		||||
            _ => Self::InvalidNonce(error),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum SettingType {
 | 
			
		||||
    Explicit,
 | 
			
		||||
    Computed,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										213
									
								
								cli/src/nonce.rs
									
									
									
									
									
								
							
							
						
						
									
										213
									
								
								cli/src/nonce.rs
									
									
									
									
									
								
							@@ -14,7 +14,11 @@ use solana_sdk::{
 | 
			
		||||
    account_utils::StateMut,
 | 
			
		||||
    hash::Hash,
 | 
			
		||||
    message::Message,
 | 
			
		||||
    nonce::{self, state::Versions, State},
 | 
			
		||||
    nonce::{
 | 
			
		||||
        self,
 | 
			
		||||
        state::{Data, Versions},
 | 
			
		||||
        State,
 | 
			
		||||
    },
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    system_instruction::{
 | 
			
		||||
        advance_nonce_account, authorize_nonce_account, create_address_with_seed,
 | 
			
		||||
@@ -25,14 +29,24 @@ use solana_sdk::{
 | 
			
		||||
    transaction::Transaction,
 | 
			
		||||
};
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
#[derive(Debug, Error, PartialEq)]
 | 
			
		||||
pub enum CliNonceError {
 | 
			
		||||
    #[error("invalid account owner")]
 | 
			
		||||
    InvalidAccountOwner,
 | 
			
		||||
    #[error("invalid account data")]
 | 
			
		||||
    InvalidAccountData,
 | 
			
		||||
    #[error("unexpected account data size")]
 | 
			
		||||
    UnexpectedDataSize,
 | 
			
		||||
    #[error("query hash does not match stored hash")]
 | 
			
		||||
    InvalidHash,
 | 
			
		||||
    #[error("query authority does not match account authority")]
 | 
			
		||||
    InvalidAuthority,
 | 
			
		||||
    InvalidState,
 | 
			
		||||
    #[error("invalid state for requested operation")]
 | 
			
		||||
    InvalidStateForOperation,
 | 
			
		||||
    #[error("client error: {0}")]
 | 
			
		||||
    Client(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const NONCE_ARG: ArgConstant<'static> = ArgConstant {
 | 
			
		||||
@@ -218,6 +232,48 @@ impl NonceSubCommands for App<'_, '_> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_account(
 | 
			
		||||
    rpc_client: &RpcClient,
 | 
			
		||||
    nonce_pubkey: &Pubkey,
 | 
			
		||||
) -> Result<Account, CliNonceError> {
 | 
			
		||||
    rpc_client
 | 
			
		||||
        .get_account(nonce_pubkey)
 | 
			
		||||
        .map_err(|e| CliNonceError::Client(format!("{:?}", e)))
 | 
			
		||||
        .and_then(|a| match account_identity_ok(&a) {
 | 
			
		||||
            Ok(()) => Ok(a),
 | 
			
		||||
            Err(e) => Err(e),
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn account_identity_ok(account: &Account) -> Result<(), CliNonceError> {
 | 
			
		||||
    if account.owner != system_program::id() {
 | 
			
		||||
        Err(CliNonceError::InvalidAccountOwner)
 | 
			
		||||
    } else if account.data.is_empty() {
 | 
			
		||||
        Err(CliNonceError::UnexpectedDataSize)
 | 
			
		||||
    } else {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn state_from_account(account: &Account) -> Result<State, CliNonceError> {
 | 
			
		||||
    account_identity_ok(account)?;
 | 
			
		||||
    StateMut::<Versions>::state(account)
 | 
			
		||||
        .map_err(|_| CliNonceError::InvalidAccountData)
 | 
			
		||||
        .map(|v| v.convert_to_current())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn data_from_account(account: &Account) -> Result<Data, CliNonceError> {
 | 
			
		||||
    account_identity_ok(account)?;
 | 
			
		||||
    state_from_account(account).and_then(|ref s| data_from_state(s).map(|d| d.clone()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn data_from_state(state: &State) -> Result<&Data, CliNonceError> {
 | 
			
		||||
    match state {
 | 
			
		||||
        State::Uninitialized => Err(CliNonceError::InvalidStateForOperation),
 | 
			
		||||
        State::Initialized(data) => Ok(data),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_authorize_nonce_account(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
@@ -359,24 +415,18 @@ pub fn check_nonce_account(
 | 
			
		||||
    nonce_account: &Account,
 | 
			
		||||
    nonce_authority: &Pubkey,
 | 
			
		||||
    nonce_hash: &Hash,
 | 
			
		||||
) -> Result<(), Box<CliError>> {
 | 
			
		||||
    if nonce_account.owner != system_program::ID {
 | 
			
		||||
        return Err(CliError::InvalidNonce(CliNonceError::InvalidAccountOwner).into());
 | 
			
		||||
    }
 | 
			
		||||
    let nonce_state = StateMut::<Versions>::state(nonce_account)
 | 
			
		||||
        .map(|v| v.convert_to_current())
 | 
			
		||||
        .map_err(|_| Box::new(CliError::InvalidNonce(CliNonceError::InvalidAccountData)))?;
 | 
			
		||||
    match nonce_state {
 | 
			
		||||
) -> Result<(), CliError> {
 | 
			
		||||
    match state_from_account(nonce_account)? {
 | 
			
		||||
        State::Initialized(ref data) => {
 | 
			
		||||
            if &data.blockhash != nonce_hash {
 | 
			
		||||
                Err(CliError::InvalidNonce(CliNonceError::InvalidHash).into())
 | 
			
		||||
                Err(CliNonceError::InvalidHash.into())
 | 
			
		||||
            } else if nonce_authority != &data.authority {
 | 
			
		||||
                Err(CliError::InvalidNonce(CliNonceError::InvalidAuthority).into())
 | 
			
		||||
                Err(CliNonceError::InvalidAuthority.into())
 | 
			
		||||
            } else {
 | 
			
		||||
                Ok(())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        State::Uninitialized => Err(CliError::InvalidNonce(CliNonceError::InvalidState).into()),
 | 
			
		||||
        State::Uninitialized => Err(CliNonceError::InvalidStateForOperation.into()),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -425,10 +475,8 @@ pub fn process_create_nonce_account(
 | 
			
		||||
        (&nonce_account_address, "nonce_account".to_string()),
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
    if let Ok(nonce_account) = rpc_client.get_account(&nonce_account_address) {
 | 
			
		||||
        let err_msg = if nonce_account.owner == system_program::id()
 | 
			
		||||
            && StateMut::<Versions>::state(&nonce_account).is_ok()
 | 
			
		||||
        {
 | 
			
		||||
    if let Ok(nonce_account) = get_account(rpc_client, &nonce_account_address) {
 | 
			
		||||
        let err_msg = if state_from_account(&nonce_account).is_ok() {
 | 
			
		||||
            format!("Nonce account {} already exists", nonce_account_address)
 | 
			
		||||
        } else {
 | 
			
		||||
            format!(
 | 
			
		||||
@@ -485,23 +533,9 @@ pub fn process_create_nonce_account(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn process_get_nonce(rpc_client: &RpcClient, nonce_account_pubkey: &Pubkey) -> ProcessResult {
 | 
			
		||||
    let nonce_account = rpc_client.get_account(nonce_account_pubkey)?;
 | 
			
		||||
    if nonce_account.owner != system_program::id() {
 | 
			
		||||
        return Err(CliError::RpcRequestError(format!(
 | 
			
		||||
            "{:?} is not a nonce account",
 | 
			
		||||
            nonce_account_pubkey
 | 
			
		||||
        ))
 | 
			
		||||
        .into());
 | 
			
		||||
    }
 | 
			
		||||
    let nonce_state = StateMut::<Versions>::state(&nonce_account).map(|v| v.convert_to_current());
 | 
			
		||||
    match nonce_state {
 | 
			
		||||
        Ok(State::Uninitialized) => Ok("Nonce account is uninitialized".to_string()),
 | 
			
		||||
        Ok(State::Initialized(ref data)) => Ok(format!("{:?}", data.blockhash)),
 | 
			
		||||
        Err(err) => Err(CliError::RpcRequestError(format!(
 | 
			
		||||
            "Account data could not be deserialized to nonce state: {:?}",
 | 
			
		||||
            err
 | 
			
		||||
        ))
 | 
			
		||||
        .into()),
 | 
			
		||||
    match get_account(rpc_client, nonce_account_pubkey).and_then(|ref a| state_from_account(a))? {
 | 
			
		||||
        State::Uninitialized => Ok("Nonce account is uninitialized".to_string()),
 | 
			
		||||
        State::Initialized(ref data) => Ok(format!("{:?}", data.blockhash)),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -545,14 +579,7 @@ pub fn process_show_nonce_account(
 | 
			
		||||
    nonce_account_pubkey: &Pubkey,
 | 
			
		||||
    use_lamports_unit: bool,
 | 
			
		||||
) -> ProcessResult {
 | 
			
		||||
    let nonce_account = rpc_client.get_account(nonce_account_pubkey)?;
 | 
			
		||||
    if nonce_account.owner != system_program::id() {
 | 
			
		||||
        return Err(CliError::RpcRequestError(format!(
 | 
			
		||||
            "{:?} is not a nonce account",
 | 
			
		||||
            nonce_account_pubkey
 | 
			
		||||
        ))
 | 
			
		||||
        .into());
 | 
			
		||||
    }
 | 
			
		||||
    let nonce_account = get_account(rpc_client, nonce_account_pubkey)?;
 | 
			
		||||
    let print_account = |data: Option<&nonce::state::Data>| {
 | 
			
		||||
        println!(
 | 
			
		||||
            "Balance: {}",
 | 
			
		||||
@@ -583,15 +610,9 @@ pub fn process_show_nonce_account(
 | 
			
		||||
        }
 | 
			
		||||
        Ok("".to_string())
 | 
			
		||||
    };
 | 
			
		||||
    let nonce_state = StateMut::<Versions>::state(&nonce_account).map(|v| v.convert_to_current());
 | 
			
		||||
    match nonce_state {
 | 
			
		||||
        Ok(State::Uninitialized) => print_account(None),
 | 
			
		||||
        Ok(State::Initialized(ref data)) => print_account(Some(data)),
 | 
			
		||||
        Err(err) => Err(CliError::RpcRequestError(format!(
 | 
			
		||||
            "Account data could not be deserialized to nonce state: {:?}",
 | 
			
		||||
            err
 | 
			
		||||
        ))
 | 
			
		||||
        .into()),
 | 
			
		||||
    match state_from_account(&nonce_account)? {
 | 
			
		||||
        State::Uninitialized => print_account(None),
 | 
			
		||||
        State::Initialized(ref data) => print_account(Some(data)),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -920,17 +941,13 @@ mod tests {
 | 
			
		||||
        let invalid_owner = Account::new_data(1, &data, &Pubkey::new(&[1u8; 32]));
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            check_nonce_account(&invalid_owner.unwrap(), &nonce_pubkey, &blockhash),
 | 
			
		||||
            Err(Box::new(CliError::InvalidNonce(
 | 
			
		||||
                CliNonceError::InvalidAccountOwner
 | 
			
		||||
            ))),
 | 
			
		||||
            Err(CliNonceError::InvalidAccountOwner.into()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let invalid_data = Account::new_data(1, &"invalid", &system_program::ID);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            check_nonce_account(&invalid_data.unwrap(), &nonce_pubkey, &blockhash),
 | 
			
		||||
            Err(Box::new(CliError::InvalidNonce(
 | 
			
		||||
                CliNonceError::InvalidAccountData
 | 
			
		||||
            ))),
 | 
			
		||||
            Err(CliNonceError::InvalidAccountData.into()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let data = Versions::new_current(State::Initialized(nonce::state::Data {
 | 
			
		||||
@@ -941,7 +958,7 @@ mod tests {
 | 
			
		||||
        let invalid_hash = Account::new_data(1, &data, &system_program::ID);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            check_nonce_account(&invalid_hash.unwrap(), &nonce_pubkey, &blockhash),
 | 
			
		||||
            Err(Box::new(CliError::InvalidNonce(CliNonceError::InvalidHash))),
 | 
			
		||||
            Err(CliNonceError::InvalidHash.into()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let data = Versions::new_current(State::Initialized(nonce::state::Data {
 | 
			
		||||
@@ -952,18 +969,84 @@ mod tests {
 | 
			
		||||
        let invalid_authority = Account::new_data(1, &data, &system_program::ID);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash),
 | 
			
		||||
            Err(Box::new(CliError::InvalidNonce(
 | 
			
		||||
                CliNonceError::InvalidAuthority
 | 
			
		||||
            ))),
 | 
			
		||||
            Err(CliNonceError::InvalidAuthority.into()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let data = Versions::new_current(State::Uninitialized);
 | 
			
		||||
        let invalid_state = Account::new_data(1, &data, &system_program::ID);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash),
 | 
			
		||||
            Err(Box::new(CliError::InvalidNonce(
 | 
			
		||||
                CliNonceError::InvalidState
 | 
			
		||||
            ))),
 | 
			
		||||
            Err(CliNonceError::InvalidStateForOperation.into()),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_account_identity_ok() {
 | 
			
		||||
        let nonce_account = nonce::create_account(1).into_inner();
 | 
			
		||||
        assert_eq!(account_identity_ok(&nonce_account), Ok(()));
 | 
			
		||||
 | 
			
		||||
        let system_account = Account::new(1, 0, &system_program::id());
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            account_identity_ok(&system_account),
 | 
			
		||||
            Err(CliNonceError::UnexpectedDataSize),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let other_program = Pubkey::new(&[1u8; 32]);
 | 
			
		||||
        let other_account_no_data = Account::new(1, 0, &other_program);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            account_identity_ok(&other_account_no_data),
 | 
			
		||||
            Err(CliNonceError::InvalidAccountOwner),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_state_from_account() {
 | 
			
		||||
        let mut nonce_account = nonce::create_account(1).into_inner();
 | 
			
		||||
        assert_eq!(state_from_account(&nonce_account), Ok(State::Uninitialized));
 | 
			
		||||
 | 
			
		||||
        let data = nonce::state::Data {
 | 
			
		||||
            authority: Pubkey::new(&[1u8; 32]),
 | 
			
		||||
            blockhash: Hash::new(&[42u8; 32]),
 | 
			
		||||
            fee_calculator: FeeCalculator::new(42),
 | 
			
		||||
        };
 | 
			
		||||
        nonce_account
 | 
			
		||||
            .set_state(&Versions::new_current(State::Initialized(data.clone())))
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            state_from_account(&nonce_account),
 | 
			
		||||
            Ok(State::Initialized(data))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let wrong_data_size_account = Account::new(1, 1, &system_program::id());
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            state_from_account(&wrong_data_size_account),
 | 
			
		||||
            Err(CliNonceError::InvalidAccountData),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_data_from_helpers() {
 | 
			
		||||
        let mut nonce_account = nonce::create_account(1).into_inner();
 | 
			
		||||
        let state = state_from_account(&nonce_account).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            data_from_state(&state),
 | 
			
		||||
            Err(CliNonceError::InvalidStateForOperation)
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            data_from_account(&nonce_account),
 | 
			
		||||
            Err(CliNonceError::InvalidStateForOperation)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let data = nonce::state::Data {
 | 
			
		||||
            authority: Pubkey::new(&[1u8; 32]),
 | 
			
		||||
            blockhash: Hash::new(&[42u8; 32]),
 | 
			
		||||
            fee_calculator: FeeCalculator::new(42),
 | 
			
		||||
        };
 | 
			
		||||
        nonce_account
 | 
			
		||||
            .set_state(&Versions::new_current(State::Initialized(data.clone())))
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        let state = state_from_account(&nonce_account).unwrap();
 | 
			
		||||
        assert_eq!(data_from_state(&state), Ok(&data));
 | 
			
		||||
        assert_eq!(data_from_account(&nonce_account), Ok(data));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user