nonce: Unify `NonceError` with `SystemError`

This commit is contained in:
Trent Nelson 2021-06-01 17:25:53 -06:00
parent f2cf647c9f
commit 21bc43ed58
No known key found for this signature in database
GPG Key ID: 9A4ACDAE36292B58
11 changed files with 521 additions and 74 deletions

View File

@ -12,12 +12,10 @@ use solana_cli_output::{
};
use solana_client::{
blockhash_query::BlockhashQuery,
client_error::{ClientError, ClientErrorKind, Result as ClientResult},
client_error::{ClientError, Result as ClientResult},
nonce_utils,
rpc_client::RpcClient,
rpc_config::{RpcLargestAccountsFilter, RpcSendTransactionConfig, RpcTransactionLogsFilter},
rpc_request::{RpcError, RpcResponseErrorData},
rpc_response::RpcSimulateTransactionResult,
};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
@ -1536,43 +1534,41 @@ pub fn request_and_confirm_airdrop(
Ok(signature)
}
fn common_error_adapter<E>(ix_error: &InstructionError) -> Option<E>
where
E: 'static + std::error::Error + DecodeError<E> + FromPrimitive,
{
if let InstructionError::Custom(code) = ix_error {
E::decode_custom_error_to_enum(*code)
} else {
None
}
}
pub fn log_instruction_custom_error<E>(
result: ClientResult<Signature>,
config: &CliConfig,
) -> ProcessResult
where
E: 'static + std::error::Error + DecodeError<E> + FromPrimitive,
{
log_instruction_custom_error_ex::<E, _>(result, config, common_error_adapter)
}
pub fn log_instruction_custom_error_ex<E, F>(
result: ClientResult<Signature>,
config: &CliConfig,
error_adapter: F,
) -> ProcessResult
where
E: 'static + std::error::Error + DecodeError<E> + FromPrimitive,
F: Fn(&InstructionError) -> Option<E>,
{
match result {
Err(err) => {
// If transaction simulation returns a known Custom InstructionError, decode it
if let ClientErrorKind::RpcError(RpcError::RpcResponseError {
data:
RpcResponseErrorData::SendTransactionPreflightFailure(
RpcSimulateTransactionResult {
err:
Some(TransactionError::InstructionError(
_,
InstructionError::Custom(code),
)),
..
},
),
..
}) = err.kind()
{
if let Some(specific_error) = E::decode_custom_error_to_enum(*code) {
return Err(specific_error.into());
}
}
// If the transaction was instead submitted and returned a known Custom
// InstructionError, decode it
if let ClientErrorKind::TransactionError(TransactionError::InstructionError(
_,
InstructionError::Custom(code),
)) = err.kind()
{
if let Some(specific_error) = E::decode_custom_error_to_enum(*code) {
let maybe_tx_err = err.get_transaction_error();
if let Some(TransactionError::InstructionError(_, ix_error)) = maybe_tx_err {
if let Some(specific_error) = error_adapter(&ix_error) {
return Err(specific_error.into());
}
}

View File

@ -10,6 +10,7 @@ use solana_cli_output::{QuietDisplay, VerboseDisplay};
use solana_client::{client_error::ClientError, rpc_client::RpcClient};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account::Account,
clock::Slot,
feature::{self, Feature},
feature_set::FEATURE_NAMES,
@ -312,6 +313,31 @@ fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<boo
Ok(feature_activation_allowed)
}
fn status_from_account(account: Account) -> Option<CliFeatureStatus> {
feature::from_account(&account).map(|feature| match feature.activated_at {
None => CliFeatureStatus::Pending,
Some(activation_slot) => CliFeatureStatus::Active(activation_slot),
})
}
fn get_feature_status(
rpc_client: &RpcClient,
feature_id: &Pubkey,
) -> Result<Option<CliFeatureStatus>, Box<dyn std::error::Error>> {
rpc_client
.get_account(feature_id)
.map(status_from_account)
.map_err(|e| e.into())
}
pub fn get_feature_is_active(
rpc_client: &RpcClient,
feature_id: &Pubkey,
) -> Result<bool, Box<dyn std::error::Error>> {
get_feature_status(rpc_client, feature_id)
.map(|status| matches!(status, Some(CliFeatureStatus::Active(_))))
}
fn process_status(
rpc_client: &RpcClient,
config: &CliConfig,
@ -327,11 +353,7 @@ fn process_status(
let feature_id = &feature_ids[i];
let feature_name = FEATURE_NAMES.get(feature_id).unwrap();
if let Some(account) = account {
if let Some(feature) = feature::from_account(&account) {
let feature_status = match feature.activated_at {
None => CliFeatureStatus::Pending,
Some(activation_slot) => CliFeatureStatus::Active(activation_slot),
};
if let Some(feature_status) = status_from_account(account) {
features.push(CliFeature {
id: feature_id.to_string(),
description: feature_name.to_string(),

View File

@ -1,9 +1,10 @@
use crate::{
checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
cli::{
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
ProcessResult,
log_instruction_custom_error, log_instruction_custom_error_ex, CliCommand, CliCommandInfo,
CliConfig, CliError, ProcessResult,
},
feature::get_feature_is_active,
memo::WithMemo,
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
};
@ -20,16 +21,19 @@ use solana_client::{nonce_utils::*, rpc_client::RpcClient};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account::Account,
feature_set::merge_nonce_error_into_system_error,
hash::Hash,
instruction::InstructionError,
message::Message,
nonce::{self, State},
pubkey::Pubkey,
system_instruction::{
advance_nonce_account, authorize_nonce_account, create_nonce_account,
create_nonce_account_with_seed, withdraw_nonce_account, NonceError, SystemError,
create_nonce_account_with_seed, instruction_to_nonce_error, withdraw_nonce_account,
NonceError, SystemError,
},
system_program,
transaction::Transaction,
transaction::{Transaction, TransactionError},
};
use std::sync::Arc;
@ -367,8 +371,21 @@ pub fn process_authorize_nonce_account(
&tx.message,
config.commitment,
)?;
let merge_errors =
get_feature_is_active(rpc_client, &merge_nonce_error_into_system_error::id())?;
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
log_instruction_custom_error::<NonceError>(result, config)
if merge_errors {
log_instruction_custom_error::<SystemError>(result, config)
} else {
log_instruction_custom_error_ex::<NonceError, _>(result, config, |ix_error| {
if let InstructionError::Custom(_) = ix_error {
instruction_to_nonce_error(ix_error, merge_errors)
} else {
None
}
})
}
}
pub fn process_create_nonce_account(
@ -452,8 +469,40 @@ pub fn process_create_nonce_account(
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&config.signers, recent_blockhash)?;
let merge_errors =
get_feature_is_active(rpc_client, &merge_nonce_error_into_system_error::id())?;
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
log_instruction_custom_error::<SystemError>(result, config)
let err_ix_index = if let Err(err) = &result {
err.get_transaction_error().and_then(|tx_err| {
if let TransactionError::InstructionError(ix_index, _) = tx_err {
Some(ix_index)
} else {
None
}
})
} else {
None
};
match err_ix_index {
// SystemInstruction::InitializeNonceAccount failed
Some(1) => {
if merge_errors {
log_instruction_custom_error::<SystemError>(result, config)
} else {
log_instruction_custom_error_ex::<NonceError, _>(result, config, |ix_error| {
if let InstructionError::Custom(_) = ix_error {
instruction_to_nonce_error(ix_error, merge_errors)
} else {
None
}
})
}
}
// SystemInstruction::CreateAccount{,WithSeed} failed
_ => log_instruction_custom_error::<SystemError>(result, config),
}
}
pub fn process_get_nonce(
@ -506,8 +555,21 @@ pub fn process_new_nonce(
&tx.message,
config.commitment,
)?;
let merge_errors =
get_feature_is_active(rpc_client, &merge_nonce_error_into_system_error::id())?;
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
log_instruction_custom_error::<SystemError>(result, config)
if merge_errors {
log_instruction_custom_error::<SystemError>(result, config)
} else {
log_instruction_custom_error_ex::<NonceError, _>(result, config, |ix_error| {
if let InstructionError::Custom(_) = ix_error {
instruction_to_nonce_error(ix_error, merge_errors)
} else {
None
}
})
}
}
pub fn process_show_nonce_account(
@ -569,8 +631,21 @@ pub fn process_withdraw_from_nonce_account(
&tx.message,
config.commitment,
)?;
let merge_errors =
get_feature_is_active(rpc_client, &merge_nonce_error_into_system_error::id())?;
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
log_instruction_custom_error::<NonceError>(result, config)
if merge_errors {
log_instruction_custom_error::<SystemError>(result, config)
} else {
log_instruction_custom_error_ex::<NonceError, _>(result, config, |ix_error| {
if let InstructionError::Custom(_) = ix_error {
instruction_to_nonce_error(ix_error, merge_errors)
} else {
None
}
})
}
}
#[cfg(test)]

View File

@ -1,5 +1,5 @@
use {
crate::rpc_request,
crate::{rpc_request, rpc_response},
solana_faucet::faucet::FaucetError,
solana_sdk::{
signature::SignerError, transaction::TransactionError, transport::TransportError,
@ -30,6 +30,24 @@ pub enum ClientErrorKind {
Custom(String),
}
impl ClientErrorKind {
pub fn get_transaction_error(&self) -> Option<TransactionError> {
match self {
Self::RpcError(rpc_request::RpcError::RpcResponseError {
data:
rpc_request::RpcResponseErrorData::SendTransactionPreflightFailure(
rpc_response::RpcSimulateTransactionResult {
err: Some(tx_err), ..
},
),
..
}) => Some(tx_err.clone()),
Self::TransactionError(tx_err) => Some(tx_err.clone()),
_ => None,
}
}
}
impl From<TransportError> for ClientErrorKind {
fn from(err: TransportError) -> Self {
match err {
@ -86,6 +104,10 @@ impl ClientError {
pub fn kind(&self) -> &ClientErrorKind {
&self.kind
}
pub fn get_transaction_error(&self) -> Option<TransactionError> {
self.kind.get_transaction_error()
}
}
impl From<ClientErrorKind> for ClientError {

View File

@ -30,6 +30,7 @@ use solana_sdk::{
hash::Hash,
message::Message,
native_loader, nonce,
nonce::NONCED_TX_MARKER_IX_INDEX,
pubkey::Pubkey,
transaction::Result,
transaction::{Transaction, TransactionError},
@ -891,6 +892,7 @@ impl Accounts {
rent_collector: &RentCollector,
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
rent_for_sysvars: bool,
merge_nonce_error_into_system_error: bool,
) {
let accounts_to_store = self.collect_accounts_to_store(
txs,
@ -899,6 +901,7 @@ impl Accounts {
rent_collector,
last_blockhash_with_fee_calculator,
rent_for_sysvars,
merge_nonce_error_into_system_error,
);
self.accounts_db.store_cached(slot, &accounts_to_store);
}
@ -923,6 +926,7 @@ impl Accounts {
rent_collector: &RentCollector,
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
rent_for_sysvars: bool,
merge_nonce_error_into_system_error: bool,
) -> Vec<(&'a Pubkey, &'a AccountSharedData)> {
let mut accounts = Vec::with_capacity(loaded.len());
for (i, ((raccs, _nonce_rollback), tx)) in loaded.iter_mut().zip(txs).enumerate() {
@ -935,13 +939,19 @@ impl Accounts {
let pubkey = nonce_rollback.nonce_address();
let acc = nonce_rollback.nonce_account();
let maybe_fee_account = nonce_rollback.fee_account();
Some((pubkey, acc, maybe_fee_account))
Some((pubkey, acc, maybe_fee_account, true))
}
(Err(TransactionError::InstructionError(_, _)), Some(nonce_rollback)) => {
(Err(TransactionError::InstructionError(index, _)), Some(nonce_rollback)) => {
let nonce_marker_ix_failed = if merge_nonce_error_into_system_error {
// Don't advance stored blockhash when the nonce marker ix fails
*index == NONCED_TX_MARKER_IX_INDEX
} else {
false
};
let pubkey = nonce_rollback.nonce_address();
let acc = nonce_rollback.nonce_account();
let maybe_fee_account = nonce_rollback.fee_account();
Some((pubkey, acc, maybe_fee_account))
Some((pubkey, acc, maybe_fee_account, !nonce_marker_ix_failed))
}
(Ok(_), _nonce_rollback) => None,
(Err(_), _nonce_rollback) => continue,
@ -972,11 +982,11 @@ impl Accounts {
if res.is_err() {
match (is_nonce_account, is_fee_payer, maybe_nonce_rollback) {
// nonce is fee-payer, state updated in `prepare_if_nonce_account()`
(true, true, Some((_, _, None))) => (),
(true, true, Some((_, _, None, _))) => (),
// nonce not fee-payer, state updated in `prepare_if_nonce_account()`
(true, false, Some((_, _, Some(_)))) => (),
(true, false, Some((_, _, Some(_), _))) => (),
// not nonce, but fee-payer. rollback to cached state
(false, true, Some((_, _, Some(fee_payer_account)))) => {
(false, true, Some((_, _, Some(fee_payer_account), _))) => {
*account = fee_payer_account.clone();
}
_ => panic!("unexpected nonce_rollback condition"),
@ -1005,15 +1015,28 @@ pub fn prepare_if_nonce_account(
account: &mut AccountSharedData,
account_pubkey: &Pubkey,
tx_result: &Result<()>,
maybe_nonce_rollback: Option<(&Pubkey, &AccountSharedData, Option<&AccountSharedData>)>,
maybe_nonce_rollback: Option<(
&Pubkey,
&AccountSharedData,
Option<&AccountSharedData>,
bool,
)>,
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
) -> bool {
if let Some((nonce_key, nonce_acc, _maybe_fee_account)) = maybe_nonce_rollback {
if let Some((nonce_key, nonce_acc, _maybe_fee_account, advance_blockhash)) =
maybe_nonce_rollback
{
if account_pubkey == nonce_key {
if tx_result.is_err() {
// Nonce TX failed with an InstructionError. Roll back
// its account state
*account = nonce_acc.clone();
}
if advance_blockhash {
// Advance the stored blockhash to prevent fee theft by replaying
// transactions that have failed with an `InstructionError`
// Since hash_age_kind is DurableNonce, unwrap is safe here
let state = StateMut::<nonce::state::Versions>::state(nonce_acc)
.unwrap()
@ -1975,6 +1998,7 @@ mod tests {
&rent_collector,
&(Hash::default(), FeeCalculator::default()),
true,
true, // merge_nonce_error_into_system_error
);
assert_eq!(collected_accounts.len(), 2);
assert!(collected_accounts
@ -2102,18 +2126,23 @@ mod tests {
account: &mut AccountSharedData,
account_pubkey: &Pubkey,
tx_result: &Result<()>,
maybe_nonce_rollback: Option<(&Pubkey, &AccountSharedData, Option<&AccountSharedData>)>,
maybe_nonce_rollback: Option<(
&Pubkey,
&AccountSharedData,
Option<&AccountSharedData>,
bool,
)>,
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
expect_account: &AccountSharedData,
) -> bool {
// Verify expect_account's relationship
match maybe_nonce_rollback {
Some((nonce_pubkey, _nonce_account, _maybe_fee_account))
Some((nonce_pubkey, _nonce_account, _maybe_fee_account, _))
if nonce_pubkey == account_pubkey && tx_result.is_ok() =>
{
assert_eq!(expect_account, account) // Account update occurs in system_instruction_processor
}
Some((nonce_pubkey, nonce_account, _maybe_fee_account))
Some((nonce_pubkey, nonce_account, _maybe_fee_account, _))
if nonce_pubkey == account_pubkey =>
{
assert_ne!(expect_account, nonce_account)
@ -2156,7 +2185,8 @@ mod tests {
Some((
&pre_account_pubkey,
&pre_account,
maybe_fee_account.as_ref()
maybe_fee_account.as_ref(),
false,
)),
&(last_blockhash, last_fee_calculator),
&expect_account,
@ -2207,7 +2237,8 @@ mod tests {
Some((
&pre_account_pubkey,
&pre_account,
maybe_fee_account.as_ref()
maybe_fee_account.as_ref(),
true,
)),
&(last_blockhash, last_fee_calculator),
&expect_account,
@ -2247,7 +2278,8 @@ mod tests {
Some((
&pre_account_pubkey,
&pre_account,
maybe_fee_account.as_ref()
maybe_fee_account.as_ref(),
true,
)),
&(last_blockhash, last_fee_calculator),
&expect_account,
@ -2344,6 +2376,7 @@ mod tests {
&rent_collector,
&(next_blockhash, FeeCalculator::default()),
true,
true, // merge_nonce_error_into_system_error
);
assert_eq!(collected_accounts.len(), 2);
assert_eq!(
@ -2460,6 +2493,7 @@ mod tests {
&rent_collector,
&(next_blockhash, FeeCalculator::default()),
true,
true, // merge_nonce_error_into_system_error
);
assert_eq!(collected_accounts.len(), 1);
let collected_nonce_account = collected_accounts

View File

@ -3534,6 +3534,7 @@ impl Bank {
&self.rent_collector,
&self.last_blockhash_with_fee_calculator(),
self.rent_for_sysvars(),
self.merge_nonce_error_into_system_error(),
);
let rent_debits = self.collect_rent(executed, loaded_txs);
@ -5222,6 +5223,11 @@ impl Bank {
.is_active(&feature_set::libsecp256k1_0_5_upgrade_enabled::id())
}
pub fn merge_nonce_error_into_system_error(&self) -> bool {
self.feature_set
.is_active(&feature_set::merge_nonce_error_into_system_error::id())
}
// Check if the wallclock time from bank creation to now has exceeded the allotted
// time for transaction processing
pub fn should_bank_still_be_processing_txs(
@ -10194,6 +10200,60 @@ pub(crate) mod tests {
);
}
#[test]
fn test_nonce_authority() {
solana_logger::setup();
let (mut bank, _mint_keypair, custodian_keypair, nonce_keypair) =
setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000, None).unwrap();
let alice_keypair = Keypair::new();
let alice_pubkey = alice_keypair.pubkey();
let custodian_pubkey = custodian_keypair.pubkey();
let nonce_pubkey = nonce_keypair.pubkey();
let bad_nonce_authority_keypair = Keypair::new();
let bad_nonce_authority = bad_nonce_authority_keypair.pubkey();
let custodian_account = bank.get_account(&custodian_pubkey).unwrap();
debug!("alice: {}", alice_pubkey);
debug!("custodian: {}", custodian_pubkey);
debug!("nonce: {}", nonce_pubkey);
debug!("nonce account: {:?}", bank.get_account(&nonce_pubkey));
debug!("cust: {:?}", custodian_account);
let nonce_hash = get_nonce_account(&bank, &nonce_pubkey).unwrap();
Arc::get_mut(&mut bank)
.unwrap()
.activate_feature(&feature_set::merge_nonce_error_into_system_error::id());
for _ in 0..MAX_RECENT_BLOCKHASHES + 1 {
goto_end_of_slot(Arc::get_mut(&mut bank).unwrap());
bank = Arc::new(new_from_parent(&bank));
}
let durable_tx = Transaction::new_signed_with_payer(
&[
system_instruction::advance_nonce_account(&nonce_pubkey, &bad_nonce_authority),
system_instruction::transfer(&custodian_pubkey, &alice_pubkey, 42),
],
Some(&custodian_pubkey),
&[&custodian_keypair, &bad_nonce_authority_keypair],
nonce_hash,
);
debug!("{:?}", durable_tx);
let initial_custodian_balance = custodian_account.lamports();
assert_eq!(
bank.process_transaction(&durable_tx),
Err(TransactionError::InstructionError(
0,
InstructionError::MissingRequiredSignature,
))
);
/* Check fee charged and nonce has *not* advanced */
assert_eq!(
bank.get_balance(&custodian_pubkey),
initial_custodian_balance - 10_000
);
assert_eq!(nonce_hash, get_nonce_account(&bank, &nonce_pubkey).unwrap());
}
#[test]
fn test_nonce_payer() {
solana_logger::setup();

View File

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

View File

@ -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(),
);
}
}

View File

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

View File

@ -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));
})
}

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