nonce: Unify NonceError
with SystemError
This commit is contained in:
@ -1,2 +1,4 @@
|
||||
pub mod state;
|
||||
pub use state::State;
|
||||
|
||||
pub const NONCED_TX_MARKER_IX_INDEX: u8 = 0;
|
||||
|
@ -2,7 +2,7 @@
|
||||
use crate::sysvar::recent_blockhashes;
|
||||
use crate::{
|
||||
decode_error::DecodeError,
|
||||
instruction::{AccountMeta, Instruction},
|
||||
instruction::{AccountMeta, Instruction, InstructionError},
|
||||
nonce,
|
||||
pubkey::Pubkey,
|
||||
system_program,
|
||||
@ -25,6 +25,12 @@ pub enum SystemError {
|
||||
MaxSeedLengthExceeded,
|
||||
#[error("provided address does not match addressed derived from seed")]
|
||||
AddressWithSeedMismatch,
|
||||
#[error("advancing stored nonce requires a populated RecentBlockhashes sysvar")]
|
||||
NonceNoRecentBlockhashes,
|
||||
#[error("stored nonce is still in recent_blockhashes")]
|
||||
NonceBlockhashNotExpired,
|
||||
#[error("specified nonce does not match stored nonce")]
|
||||
NonceUnexpectedBlockhashValue,
|
||||
}
|
||||
|
||||
impl<T> DecodeError<T> for SystemError {
|
||||
@ -51,6 +57,83 @@ impl<E> DecodeError<E> for NonceError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
|
||||
enum NonceErrorAdapter {
|
||||
#[error("recent blockhash list is empty")]
|
||||
NoRecentBlockhashes,
|
||||
#[error("stored nonce is still in recent_blockhashes")]
|
||||
NotExpired,
|
||||
#[error("specified nonce does not match stored nonce")]
|
||||
UnexpectedValue,
|
||||
#[error("cannot handle request in current account state")]
|
||||
BadAccountState,
|
||||
}
|
||||
|
||||
impl<E> DecodeError<E> for NonceErrorAdapter {
|
||||
fn type_of() -> &'static str {
|
||||
"NonceErrorAdapter"
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonceErrorAdapter> for NonceError {
|
||||
fn from(e: NonceErrorAdapter) -> Self {
|
||||
match e {
|
||||
NonceErrorAdapter::NoRecentBlockhashes => NonceError::NoRecentBlockhashes,
|
||||
NonceErrorAdapter::NotExpired => NonceError::NotExpired,
|
||||
NonceErrorAdapter::UnexpectedValue => NonceError::UnexpectedValue,
|
||||
NonceErrorAdapter::BadAccountState => NonceError::BadAccountState,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nonce_to_instruction_error(error: NonceError, use_system_variant: bool) -> InstructionError {
|
||||
if use_system_variant {
|
||||
match error {
|
||||
NonceError::NoRecentBlockhashes => SystemError::NonceNoRecentBlockhashes.into(),
|
||||
NonceError::NotExpired => SystemError::NonceBlockhashNotExpired.into(),
|
||||
NonceError::UnexpectedValue => SystemError::NonceUnexpectedBlockhashValue.into(),
|
||||
NonceError::BadAccountState => InstructionError::InvalidAccountData,
|
||||
}
|
||||
} else {
|
||||
match error {
|
||||
NonceError::NoRecentBlockhashes => NonceErrorAdapter::NoRecentBlockhashes.into(),
|
||||
NonceError::NotExpired => NonceErrorAdapter::NotExpired.into(),
|
||||
NonceError::UnexpectedValue => NonceErrorAdapter::UnexpectedValue.into(),
|
||||
NonceError::BadAccountState => NonceErrorAdapter::BadAccountState.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instruction_to_nonce_error(
|
||||
error: &InstructionError,
|
||||
use_system_variant: bool,
|
||||
) -> Option<NonceError> {
|
||||
if use_system_variant {
|
||||
match error {
|
||||
InstructionError::Custom(discriminant) => {
|
||||
match SystemError::decode_custom_error_to_enum(*discriminant) {
|
||||
Some(SystemError::NonceNoRecentBlockhashes) => {
|
||||
Some(NonceError::NoRecentBlockhashes)
|
||||
}
|
||||
Some(SystemError::NonceBlockhashNotExpired) => Some(NonceError::NotExpired),
|
||||
Some(SystemError::NonceUnexpectedBlockhashValue) => {
|
||||
Some(NonceError::UnexpectedValue)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
InstructionError::InvalidAccountData => Some(NonceError::BadAccountState),
|
||||
_ => None,
|
||||
}
|
||||
} else if let InstructionError::Custom(discriminant) = error {
|
||||
let maybe: Option<NonceErrorAdapter> =
|
||||
NonceErrorAdapter::decode_custom_error_to_enum(*discriminant);
|
||||
maybe.map(NonceError::from)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// maximum permitted size of data: 10 MB
|
||||
pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
|
||||
|
||||
@ -492,6 +575,7 @@ pub fn authorize_nonce_account(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::instruction::{Instruction, InstructionError};
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
fn get_keys(instruction: &Instruction) -> Vec<Pubkey> {
|
||||
instruction.accounts.iter().map(|x| x.pubkey).collect()
|
||||
@ -561,4 +645,127 @@ mod tests {
|
||||
pretty_err::<NonceError>(NonceError::BadAccountState.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonce_to_instruction_error() {
|
||||
assert_eq!(
|
||||
nonce_to_instruction_error(NonceError::NoRecentBlockhashes, false),
|
||||
NonceError::NoRecentBlockhashes.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
nonce_to_instruction_error(NonceError::NotExpired, false),
|
||||
NonceError::NotExpired.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
nonce_to_instruction_error(NonceError::UnexpectedValue, false),
|
||||
NonceError::UnexpectedValue.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
nonce_to_instruction_error(NonceError::BadAccountState, false),
|
||||
NonceError::BadAccountState.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
nonce_to_instruction_error(NonceError::NoRecentBlockhashes, true),
|
||||
SystemError::NonceNoRecentBlockhashes.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
nonce_to_instruction_error(NonceError::NotExpired, true),
|
||||
SystemError::NonceBlockhashNotExpired.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
nonce_to_instruction_error(NonceError::UnexpectedValue, true),
|
||||
SystemError::NonceUnexpectedBlockhashValue.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
nonce_to_instruction_error(NonceError::BadAccountState, true),
|
||||
InstructionError::InvalidAccountData,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_to_nonce_error() {
|
||||
assert_eq!(
|
||||
instruction_to_nonce_error(
|
||||
&InstructionError::Custom(NonceErrorAdapter::NoRecentBlockhashes.to_u32().unwrap(),),
|
||||
false,
|
||||
),
|
||||
Some(NonceError::NoRecentBlockhashes),
|
||||
);
|
||||
assert_eq!(
|
||||
instruction_to_nonce_error(
|
||||
&InstructionError::Custom(NonceErrorAdapter::NotExpired.to_u32().unwrap(),),
|
||||
false,
|
||||
),
|
||||
Some(NonceError::NotExpired),
|
||||
);
|
||||
assert_eq!(
|
||||
instruction_to_nonce_error(
|
||||
&InstructionError::Custom(NonceErrorAdapter::UnexpectedValue.to_u32().unwrap(),),
|
||||
false,
|
||||
),
|
||||
Some(NonceError::UnexpectedValue),
|
||||
);
|
||||
assert_eq!(
|
||||
instruction_to_nonce_error(
|
||||
&InstructionError::Custom(NonceErrorAdapter::BadAccountState.to_u32().unwrap(),),
|
||||
false,
|
||||
),
|
||||
Some(NonceError::BadAccountState),
|
||||
);
|
||||
assert_eq!(
|
||||
instruction_to_nonce_error(&InstructionError::Custom(u32::MAX), false),
|
||||
None,
|
||||
);
|
||||
assert_eq!(
|
||||
instruction_to_nonce_error(
|
||||
&InstructionError::Custom(SystemError::NonceNoRecentBlockhashes.to_u32().unwrap(),),
|
||||
true,
|
||||
),
|
||||
Some(NonceError::NoRecentBlockhashes),
|
||||
);
|
||||
assert_eq!(
|
||||
instruction_to_nonce_error(
|
||||
&InstructionError::Custom(SystemError::NonceBlockhashNotExpired.to_u32().unwrap(),),
|
||||
true,
|
||||
),
|
||||
Some(NonceError::NotExpired),
|
||||
);
|
||||
assert_eq!(
|
||||
instruction_to_nonce_error(
|
||||
&InstructionError::Custom(
|
||||
SystemError::NonceUnexpectedBlockhashValue.to_u32().unwrap(),
|
||||
),
|
||||
true,
|
||||
),
|
||||
Some(NonceError::UnexpectedValue),
|
||||
);
|
||||
assert_eq!(
|
||||
instruction_to_nonce_error(&InstructionError::InvalidAccountData, true),
|
||||
Some(NonceError::BadAccountState),
|
||||
);
|
||||
assert_eq!(
|
||||
instruction_to_nonce_error(&InstructionError::Custom(u32::MAX), true),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonce_error_adapter_compat() {
|
||||
assert_eq!(
|
||||
NonceError::NoRecentBlockhashes.to_u32(),
|
||||
NonceErrorAdapter::NoRecentBlockhashes.to_u32(),
|
||||
);
|
||||
assert_eq!(
|
||||
NonceError::NotExpired.to_u32(),
|
||||
NonceErrorAdapter::NotExpired.to_u32(),
|
||||
);
|
||||
assert_eq!(
|
||||
NonceError::UnexpectedValue.to_u32(),
|
||||
NonceErrorAdapter::UnexpectedValue.to_u32(),
|
||||
);
|
||||
assert_eq!(
|
||||
NonceError::BadAccountState.to_u32(),
|
||||
NonceErrorAdapter::BadAccountState.to_u32(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -175,6 +175,10 @@ pub mod spl_token_v2_set_authority_fix {
|
||||
solana_sdk::declare_id!("FToKNBYyiF4ky9s8WsmLBXHCht17Ek7RXaLZGHzzQhJ1");
|
||||
}
|
||||
|
||||
pub mod merge_nonce_error_into_system_error {
|
||||
solana_sdk::declare_id!("21AWDosvp3pBamFW91KB35pNoaoZVTM7ess8nr2nt53B");
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Map of feature identifiers to user-visible description
|
||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||
@ -213,6 +217,7 @@ lazy_static! {
|
||||
(libsecp256k1_0_5_upgrade_enabled::id(), "upgrade libsecp256k1 to v0.5.0"),
|
||||
(tx_wide_compute_cap::id(), "Transaction wide compute cap"),
|
||||
(spl_token_v2_set_authority_fix::id(), "spl-token set_authority fix"),
|
||||
(merge_nonce_error_into_system_error::id(), "merge NonceError into SystemError"),
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
|
@ -3,7 +3,7 @@
|
||||
use crate::{
|
||||
account::{ReadableAccount, WritableAccount},
|
||||
account_utils::State as AccountUtilsState,
|
||||
ic_msg,
|
||||
feature_set, ic_msg,
|
||||
keyed_account::KeyedAccount,
|
||||
nonce_account::create_account,
|
||||
process_instruction::InvokeContext,
|
||||
@ -12,7 +12,7 @@ use solana_program::{
|
||||
instruction::{checked_add, InstructionError},
|
||||
nonce::{self, state::Versions, State},
|
||||
pubkey::Pubkey,
|
||||
system_instruction::NonceError,
|
||||
system_instruction::{nonce_to_instruction_error, NonceError},
|
||||
sysvar::rent::Rent,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
@ -51,6 +51,8 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
||||
signers: &HashSet<Pubkey>,
|
||||
invoke_context: &dyn InvokeContext,
|
||||
) -> Result<(), InstructionError> {
|
||||
let merge_nonce_error_into_system_error = invoke_context
|
||||
.is_feature_active(&feature_set::merge_nonce_error_into_system_error::id());
|
||||
let state = AccountUtilsState::<Versions>::state(self)?.convert_to_current();
|
||||
match state {
|
||||
State::Initialized(data) => {
|
||||
@ -68,7 +70,10 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
||||
invoke_context,
|
||||
"Advance nonce account: nonce can only advance once per slot"
|
||||
);
|
||||
return Err(NonceError::NotExpired.into());
|
||||
return Err(nonce_to_instruction_error(
|
||||
NonceError::NotExpired,
|
||||
merge_nonce_error_into_system_error,
|
||||
));
|
||||
}
|
||||
|
||||
let new_data = nonce::state::Data {
|
||||
@ -84,7 +89,10 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
||||
"Advance nonce account: Account {} state is invalid",
|
||||
self.unsigned_key()
|
||||
);
|
||||
Err(NonceError::BadAccountState.into())
|
||||
Err(nonce_to_instruction_error(
|
||||
NonceError::BadAccountState,
|
||||
merge_nonce_error_into_system_error,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,6 +105,8 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
||||
signers: &HashSet<Pubkey>,
|
||||
invoke_context: &dyn InvokeContext,
|
||||
) -> Result<(), InstructionError> {
|
||||
let merge_nonce_error_into_system_error = invoke_context
|
||||
.is_feature_active(&feature_set::merge_nonce_error_into_system_error::id());
|
||||
let signer = match AccountUtilsState::<Versions>::state(self)?.convert_to_current() {
|
||||
State::Uninitialized => {
|
||||
if lamports > self.lamports()? {
|
||||
@ -117,7 +127,10 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
||||
invoke_context,
|
||||
"Withdraw nonce account: nonce can only advance once per slot"
|
||||
);
|
||||
return Err(NonceError::NotExpired.into());
|
||||
return Err(nonce_to_instruction_error(
|
||||
NonceError::NotExpired,
|
||||
merge_nonce_error_into_system_error,
|
||||
));
|
||||
}
|
||||
self.set_state(&Versions::new_current(State::Uninitialized))?;
|
||||
} else {
|
||||
@ -168,6 +181,8 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
||||
rent: &Rent,
|
||||
invoke_context: &dyn InvokeContext,
|
||||
) -> Result<(), InstructionError> {
|
||||
let merge_nonce_error_into_system_error = invoke_context
|
||||
.is_feature_active(&feature_set::merge_nonce_error_into_system_error::id());
|
||||
match AccountUtilsState::<Versions>::state(self)?.convert_to_current() {
|
||||
State::Uninitialized => {
|
||||
let min_balance = rent.minimum_balance(self.data_len()?);
|
||||
@ -193,7 +208,10 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
||||
"Initialize nonce account: Account {} state is invalid",
|
||||
self.unsigned_key()
|
||||
);
|
||||
Err(NonceError::BadAccountState.into())
|
||||
Err(nonce_to_instruction_error(
|
||||
NonceError::BadAccountState,
|
||||
merge_nonce_error_into_system_error,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -204,6 +222,8 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
||||
signers: &HashSet<Pubkey>,
|
||||
invoke_context: &dyn InvokeContext,
|
||||
) -> Result<(), InstructionError> {
|
||||
let merge_nonce_error_into_system_error = invoke_context
|
||||
.is_feature_active(&feature_set::merge_nonce_error_into_system_error::id());
|
||||
match AccountUtilsState::<Versions>::state(self)?.convert_to_current() {
|
||||
State::Initialized(data) => {
|
||||
if !signers.contains(&data.authority) {
|
||||
@ -226,7 +246,10 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
||||
"Authorize nonce account: Account {} state is invalid",
|
||||
self.unsigned_key()
|
||||
);
|
||||
Err(NonceError::BadAccountState.into())
|
||||
Err(nonce_to_instruction_error(
|
||||
NonceError::BadAccountState,
|
||||
merge_nonce_error_into_system_error,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -254,7 +277,7 @@ mod test {
|
||||
nonce::{self, State},
|
||||
nonce_account::verify_nonce_account,
|
||||
process_instruction::MockInvokeContext,
|
||||
system_instruction::NonceError,
|
||||
system_instruction::SystemError,
|
||||
};
|
||||
use solana_program::hash::{hash, Hash};
|
||||
|
||||
@ -419,7 +442,7 @@ mod test {
|
||||
.initialize_nonce_account(&authorized, &rent, &invoke_context)
|
||||
.unwrap();
|
||||
let result = keyed_account.advance_nonce_account(&signers, &invoke_context);
|
||||
assert_eq!(result, Err(NonceError::NotExpired.into()));
|
||||
assert_eq!(result, Err(SystemError::NonceBlockhashNotExpired.into()));
|
||||
})
|
||||
}
|
||||
|
||||
@ -435,7 +458,7 @@ mod test {
|
||||
signers.insert(*keyed_account.signer_key().unwrap());
|
||||
let invoke_context = create_invoke_context_with_blockhash(63);
|
||||
let result = keyed_account.advance_nonce_account(&signers, &invoke_context);
|
||||
assert_eq!(result, Err(NonceError::BadAccountState.into()));
|
||||
assert_eq!(result, Err(InstructionError::InvalidAccountData));
|
||||
})
|
||||
}
|
||||
|
||||
@ -752,7 +775,7 @@ mod test {
|
||||
&signers,
|
||||
&invoke_context,
|
||||
);
|
||||
assert_eq!(result, Err(NonceError::NotExpired.into()));
|
||||
assert_eq!(result, Err(SystemError::NonceBlockhashNotExpired.into()));
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -893,7 +916,7 @@ mod test {
|
||||
let invoke_context = create_invoke_context_with_blockhash(0);
|
||||
let result =
|
||||
keyed_account.initialize_nonce_account(&authorized, &rent, &invoke_context);
|
||||
assert_eq!(result, Err(NonceError::BadAccountState.into()));
|
||||
assert_eq!(result, Err(InstructionError::InvalidAccountData));
|
||||
})
|
||||
}
|
||||
|
||||
@ -963,7 +986,7 @@ mod test {
|
||||
&signers,
|
||||
&invoke_context,
|
||||
);
|
||||
assert_eq!(result, Err(NonceError::BadAccountState.into()));
|
||||
assert_eq!(result, Err(InstructionError::InvalidAccountData));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ use crate::{
|
||||
hash::Hash,
|
||||
instruction::{CompiledInstruction, Instruction, InstructionError},
|
||||
message::Message,
|
||||
nonce::NONCED_TX_MARKER_IX_INDEX,
|
||||
program_utils::limited_deserialize,
|
||||
pubkey::Pubkey,
|
||||
short_vec,
|
||||
@ -464,7 +465,7 @@ pub fn uses_durable_nonce(tx: &Transaction) -> Option<&CompiledInstruction> {
|
||||
let message = tx.message();
|
||||
message
|
||||
.instructions
|
||||
.get(0)
|
||||
.get(NONCED_TX_MARKER_IX_INDEX as usize)
|
||||
.filter(|maybe_ix| {
|
||||
let prog_id_idx = maybe_ix.program_id_index as usize;
|
||||
match message.account_keys.get(prog_id_idx) {
|
||||
|
Reference in New Issue
Block a user