nonce: Unify NonceError with SystemError

This commit is contained in:
Trent Nelson
2021-06-01 17:25:53 -06:00
parent 1570afe493
commit 336c1c1d37
11 changed files with 531 additions and 80 deletions

View File

@@ -1,2 +1,4 @@
pub mod state;
pub use state::State;
pub const NONCED_TX_MARKER_IX_INDEX: u8 = 0;

View File

@@ -1,6 +1,6 @@
use crate::{
decode_error::DecodeError,
instruction::{AccountMeta, Instruction},
instruction::{AccountMeta, Instruction, InstructionError},
nonce,
pubkey::Pubkey,
system_program,
@@ -23,6 +23,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 {
@@ -49,6 +55,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;
@@ -486,6 +569,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()
@@ -555,4 +639,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(),
);
}
}

View File

@@ -167,6 +167,10 @@ pub mod libsecp256k1_0_5_upgrade_enabled {
solana_sdk::declare_id!("DhsYfRjxfnh2g7HKJYSzT79r74Afa1wbHkAgHndrA1oy");
}
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> = [
@@ -208,6 +212,7 @@ lazy_static! {
(neon_evm_compute_budget::id(), "bump neon_evm's compute budget"),
(rent_for_sysvars::id(), "collect rent from accounts owned by sysvars"),
(libsecp256k1_0_5_upgrade_enabled::id(), "upgrade libsecp256k1 to v0.5.0"),
(merge_nonce_error_into_system_error::id(), "merge NonceError into SystemError"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()

View File

@@ -1,7 +1,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,
@@ -10,7 +10,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::{recent_blockhashes::RecentBlockhashes, rent::Rent},
};
use std::collections::HashSet;
@@ -53,12 +53,17 @@ 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());
if recent_blockhashes.is_empty() {
ic_msg!(
invoke_context,
"Advance nonce account: recent blockhash list is empty",
);
return Err(NonceError::NoRecentBlockhashes.into());
return Err(nonce_to_instruction_error(
NonceError::NoRecentBlockhashes,
merge_nonce_error_into_system_error,
));
}
let state = AccountUtilsState::<Versions>::state(self)?.convert_to_current();
@@ -78,7 +83,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 {
@@ -94,7 +102,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,
))
}
}
}
@@ -108,6 +119,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()? {
@@ -128,7 +141,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 {
@@ -180,12 +196,17 @@ 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());
if recent_blockhashes.is_empty() {
ic_msg!(
invoke_context,
"Initialize nonce account: recent blockhash list is empty",
);
return Err(NonceError::NoRecentBlockhashes.into());
return Err(nonce_to_instruction_error(
NonceError::NoRecentBlockhashes,
merge_nonce_error_into_system_error,
));
}
match AccountUtilsState::<Versions>::state(self)?.convert_to_current() {
@@ -213,7 +234,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,
))
}
}
}
@@ -224,6 +248,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) {
@@ -246,7 +272,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,
))
}
}
}
@@ -273,7 +302,7 @@ mod test {
nonce::{self, State},
nonce_account::verify_nonce_account,
process_instruction::MockInvokeContext,
system_instruction::NonceError,
system_instruction::SystemError,
sysvar::recent_blockhashes::create_test_recent_blockhashes,
};
use solana_program::hash::Hash;
@@ -456,7 +485,7 @@ mod test {
&signers,
&MockInvokeContext::new(vec![]),
);
assert_eq!(result, Err(NonceError::NoRecentBlockhashes.into()));
assert_eq!(result, Err(SystemError::NonceNoRecentBlockhashes.into()));
})
}
@@ -485,7 +514,7 @@ mod test {
&signers,
&MockInvokeContext::new(vec![]),
);
assert_eq!(result, Err(NonceError::NotExpired.into()));
assert_eq!(result, Err(SystemError::NonceBlockhashNotExpired.into()));
})
}
@@ -505,7 +534,7 @@ mod test {
&signers,
&MockInvokeContext::new(vec![]),
);
assert_eq!(result, Err(NonceError::BadAccountState.into()));
assert_eq!(result, Err(InstructionError::InvalidAccountData));
})
}
@@ -858,7 +887,7 @@ mod test {
&signers,
&MockInvokeContext::new(vec![]),
);
assert_eq!(result, Err(NonceError::NotExpired.into()));
assert_eq!(result, Err(SystemError::NonceBlockhashNotExpired.into()));
})
})
}
@@ -1024,7 +1053,7 @@ mod test {
&rent,
&MockInvokeContext::new(vec![]),
);
assert_eq!(result, Err(NonceError::NoRecentBlockhashes.into()));
assert_eq!(result, Err(SystemError::NonceNoRecentBlockhashes.into()));
})
}
@@ -1053,7 +1082,7 @@ mod test {
&rent,
&MockInvokeContext::new(vec![]),
);
assert_eq!(result, Err(NonceError::BadAccountState.into()));
assert_eq!(result, Err(InstructionError::InvalidAccountData));
})
}
@@ -1131,7 +1160,7 @@ mod test {
&signers,
&MockInvokeContext::new(vec![]),
);
assert_eq!(result, Err(NonceError::BadAccountState.into()));
assert_eq!(result, Err(InstructionError::InvalidAccountData));
})
}

View File

@@ -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) {