nonce: Unify NonceError
with SystemError
This commit is contained in:
@ -12,12 +12,10 @@ use solana_cli_output::{
|
|||||||
};
|
};
|
||||||
use solana_client::{
|
use solana_client::{
|
||||||
blockhash_query::BlockhashQuery,
|
blockhash_query::BlockhashQuery,
|
||||||
client_error::{ClientError, ClientErrorKind, Result as ClientResult},
|
client_error::{ClientError, Result as ClientResult},
|
||||||
nonce_utils,
|
nonce_utils,
|
||||||
rpc_client::RpcClient,
|
rpc_client::RpcClient,
|
||||||
rpc_config::{RpcLargestAccountsFilter, RpcSendTransactionConfig, RpcTransactionLogsFilter},
|
rpc_config::{RpcLargestAccountsFilter, RpcSendTransactionConfig, RpcTransactionLogsFilter},
|
||||||
rpc_request::{RpcError, RpcResponseErrorData},
|
|
||||||
rpc_response::RpcSimulateTransactionResult,
|
|
||||||
};
|
};
|
||||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
@ -1536,43 +1534,41 @@ pub fn request_and_confirm_airdrop(
|
|||||||
Ok(signature)
|
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>(
|
pub fn log_instruction_custom_error<E>(
|
||||||
result: ClientResult<Signature>,
|
result: ClientResult<Signature>,
|
||||||
config: &CliConfig,
|
config: &CliConfig,
|
||||||
) -> ProcessResult
|
) -> ProcessResult
|
||||||
where
|
where
|
||||||
E: 'static + std::error::Error + DecodeError<E> + FromPrimitive,
|
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 {
|
match result {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// If transaction simulation returns a known Custom InstructionError, decode it
|
let maybe_tx_err = err.get_transaction_error();
|
||||||
if let ClientErrorKind::RpcError(RpcError::RpcResponseError {
|
if let Some(TransactionError::InstructionError(_, ix_error)) = maybe_tx_err {
|
||||||
data:
|
if let Some(specific_error) = error_adapter(&ix_error) {
|
||||||
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) {
|
|
||||||
return Err(specific_error.into());
|
return Err(specific_error.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ use solana_cli_output::{QuietDisplay, VerboseDisplay};
|
|||||||
use solana_client::{client_error::ClientError, rpc_client::RpcClient};
|
use solana_client::{client_error::ClientError, rpc_client::RpcClient};
|
||||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
|
account::Account,
|
||||||
clock::Slot,
|
clock::Slot,
|
||||||
feature::{self, Feature},
|
feature::{self, Feature},
|
||||||
feature_set::FEATURE_NAMES,
|
feature_set::FEATURE_NAMES,
|
||||||
@ -312,6 +313,31 @@ fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<boo
|
|||||||
Ok(feature_activation_allowed)
|
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(
|
fn process_status(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
config: &CliConfig,
|
config: &CliConfig,
|
||||||
@ -327,11 +353,7 @@ fn process_status(
|
|||||||
let feature_id = &feature_ids[i];
|
let feature_id = &feature_ids[i];
|
||||||
let feature_name = FEATURE_NAMES.get(feature_id).unwrap();
|
let feature_name = FEATURE_NAMES.get(feature_id).unwrap();
|
||||||
if let Some(account) = account {
|
if let Some(account) = account {
|
||||||
if let Some(feature) = feature::from_account(&account) {
|
if let Some(feature_status) = status_from_account(account) {
|
||||||
let feature_status = match feature.activated_at {
|
|
||||||
None => CliFeatureStatus::Pending,
|
|
||||||
Some(activation_slot) => CliFeatureStatus::Active(activation_slot),
|
|
||||||
};
|
|
||||||
features.push(CliFeature {
|
features.push(CliFeature {
|
||||||
id: feature_id.to_string(),
|
id: feature_id.to_string(),
|
||||||
description: feature_name.to_string(),
|
description: feature_name.to_string(),
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
|
checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
|
||||||
cli::{
|
cli::{
|
||||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
log_instruction_custom_error, log_instruction_custom_error_ex, CliCommand, CliCommandInfo,
|
||||||
ProcessResult,
|
CliConfig, CliError, ProcessResult,
|
||||||
},
|
},
|
||||||
|
feature::get_feature_is_active,
|
||||||
memo::WithMemo,
|
memo::WithMemo,
|
||||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
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_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
|
feature_set::merge_nonce_error_into_system_error,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
|
instruction::InstructionError,
|
||||||
message::Message,
|
message::Message,
|
||||||
nonce::{self, State},
|
nonce::{self, State},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
system_instruction::{
|
system_instruction::{
|
||||||
advance_nonce_account, authorize_nonce_account, create_nonce_account,
|
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,
|
system_program,
|
||||||
transaction::Transaction,
|
transaction::{Transaction, TransactionError},
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -367,8 +371,21 @@ pub fn process_authorize_nonce_account(
|
|||||||
&tx.message,
|
&tx.message,
|
||||||
config.commitment,
|
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);
|
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(
|
pub fn process_create_nonce_account(
|
||||||
@ -452,8 +469,40 @@ pub fn process_create_nonce_account(
|
|||||||
|
|
||||||
let mut tx = Transaction::new_unsigned(message);
|
let mut tx = Transaction::new_unsigned(message);
|
||||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
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);
|
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||||
|
|
||||||
|
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)
|
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(
|
pub fn process_get_nonce(
|
||||||
@ -506,8 +555,21 @@ pub fn process_new_nonce(
|
|||||||
&tx.message,
|
&tx.message,
|
||||||
config.commitment,
|
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);
|
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||||
|
|
||||||
|
if merge_errors {
|
||||||
log_instruction_custom_error::<SystemError>(result, config)
|
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(
|
pub fn process_show_nonce_account(
|
||||||
@ -569,8 +631,21 @@ pub fn process_withdraw_from_nonce_account(
|
|||||||
&tx.message,
|
&tx.message,
|
||||||
config.commitment,
|
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);
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use {
|
use {
|
||||||
crate::rpc_request,
|
crate::{rpc_request, rpc_response},
|
||||||
solana_faucet::faucet::FaucetError,
|
solana_faucet::faucet::FaucetError,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
signature::SignerError, transaction::TransactionError, transport::TransportError,
|
signature::SignerError, transaction::TransactionError, transport::TransportError,
|
||||||
@ -30,6 +30,24 @@ pub enum ClientErrorKind {
|
|||||||
Custom(String),
|
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 {
|
impl From<TransportError> for ClientErrorKind {
|
||||||
fn from(err: TransportError) -> Self {
|
fn from(err: TransportError) -> Self {
|
||||||
match err {
|
match err {
|
||||||
@ -86,6 +104,10 @@ impl ClientError {
|
|||||||
pub fn kind(&self) -> &ClientErrorKind {
|
pub fn kind(&self) -> &ClientErrorKind {
|
||||||
&self.kind
|
&self.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_transaction_error(&self) -> Option<TransactionError> {
|
||||||
|
self.kind.get_transaction_error()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ClientErrorKind> for ClientError {
|
impl From<ClientErrorKind> for ClientError {
|
||||||
|
@ -30,6 +30,7 @@ use solana_sdk::{
|
|||||||
hash::Hash,
|
hash::Hash,
|
||||||
message::Message,
|
message::Message,
|
||||||
native_loader, nonce,
|
native_loader, nonce,
|
||||||
|
nonce::NONCED_TX_MARKER_IX_INDEX,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
transaction::Result,
|
transaction::Result,
|
||||||
transaction::{Transaction, TransactionError},
|
transaction::{Transaction, TransactionError},
|
||||||
@ -891,6 +892,7 @@ impl Accounts {
|
|||||||
rent_collector: &RentCollector,
|
rent_collector: &RentCollector,
|
||||||
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
|
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
|
||||||
rent_for_sysvars: bool,
|
rent_for_sysvars: bool,
|
||||||
|
merge_nonce_error_into_system_error: bool,
|
||||||
) {
|
) {
|
||||||
let accounts_to_store = self.collect_accounts_to_store(
|
let accounts_to_store = self.collect_accounts_to_store(
|
||||||
txs,
|
txs,
|
||||||
@ -899,6 +901,7 @@ impl Accounts {
|
|||||||
rent_collector,
|
rent_collector,
|
||||||
last_blockhash_with_fee_calculator,
|
last_blockhash_with_fee_calculator,
|
||||||
rent_for_sysvars,
|
rent_for_sysvars,
|
||||||
|
merge_nonce_error_into_system_error,
|
||||||
);
|
);
|
||||||
self.accounts_db.store_cached(slot, &accounts_to_store);
|
self.accounts_db.store_cached(slot, &accounts_to_store);
|
||||||
}
|
}
|
||||||
@ -923,6 +926,7 @@ impl Accounts {
|
|||||||
rent_collector: &RentCollector,
|
rent_collector: &RentCollector,
|
||||||
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
|
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
|
||||||
rent_for_sysvars: bool,
|
rent_for_sysvars: bool,
|
||||||
|
merge_nonce_error_into_system_error: bool,
|
||||||
) -> Vec<(&'a Pubkey, &'a AccountSharedData)> {
|
) -> Vec<(&'a Pubkey, &'a AccountSharedData)> {
|
||||||
let mut accounts = Vec::with_capacity(loaded.len());
|
let mut accounts = Vec::with_capacity(loaded.len());
|
||||||
for (i, ((raccs, _nonce_rollback), tx)) in loaded.iter_mut().zip(txs).enumerate() {
|
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 pubkey = nonce_rollback.nonce_address();
|
||||||
let acc = nonce_rollback.nonce_account();
|
let acc = nonce_rollback.nonce_account();
|
||||||
let maybe_fee_account = nonce_rollback.fee_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 pubkey = nonce_rollback.nonce_address();
|
||||||
let acc = nonce_rollback.nonce_account();
|
let acc = nonce_rollback.nonce_account();
|
||||||
let maybe_fee_account = nonce_rollback.fee_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,
|
(Ok(_), _nonce_rollback) => None,
|
||||||
(Err(_), _nonce_rollback) => continue,
|
(Err(_), _nonce_rollback) => continue,
|
||||||
@ -972,11 +982,11 @@ impl Accounts {
|
|||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
match (is_nonce_account, is_fee_payer, maybe_nonce_rollback) {
|
match (is_nonce_account, is_fee_payer, maybe_nonce_rollback) {
|
||||||
// nonce is fee-payer, state updated in `prepare_if_nonce_account()`
|
// 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()`
|
// 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
|
// 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();
|
*account = fee_payer_account.clone();
|
||||||
}
|
}
|
||||||
_ => panic!("unexpected nonce_rollback condition"),
|
_ => panic!("unexpected nonce_rollback condition"),
|
||||||
@ -1005,15 +1015,28 @@ pub fn prepare_if_nonce_account(
|
|||||||
account: &mut AccountSharedData,
|
account: &mut AccountSharedData,
|
||||||
account_pubkey: &Pubkey,
|
account_pubkey: &Pubkey,
|
||||||
tx_result: &Result<()>,
|
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),
|
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
|
||||||
) -> bool {
|
) -> 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 account_pubkey == nonce_key {
|
||||||
if tx_result.is_err() {
|
if tx_result.is_err() {
|
||||||
// Nonce TX failed with an InstructionError. Roll back
|
// Nonce TX failed with an InstructionError. Roll back
|
||||||
// its account state
|
// its account state
|
||||||
*account = nonce_acc.clone();
|
*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
|
// Since hash_age_kind is DurableNonce, unwrap is safe here
|
||||||
let state = StateMut::<nonce::state::Versions>::state(nonce_acc)
|
let state = StateMut::<nonce::state::Versions>::state(nonce_acc)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -1975,6 +1998,7 @@ mod tests {
|
|||||||
&rent_collector,
|
&rent_collector,
|
||||||
&(Hash::default(), FeeCalculator::default()),
|
&(Hash::default(), FeeCalculator::default()),
|
||||||
true,
|
true,
|
||||||
|
true, // merge_nonce_error_into_system_error
|
||||||
);
|
);
|
||||||
assert_eq!(collected_accounts.len(), 2);
|
assert_eq!(collected_accounts.len(), 2);
|
||||||
assert!(collected_accounts
|
assert!(collected_accounts
|
||||||
@ -2102,18 +2126,23 @@ mod tests {
|
|||||||
account: &mut AccountSharedData,
|
account: &mut AccountSharedData,
|
||||||
account_pubkey: &Pubkey,
|
account_pubkey: &Pubkey,
|
||||||
tx_result: &Result<()>,
|
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),
|
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
|
||||||
expect_account: &AccountSharedData,
|
expect_account: &AccountSharedData,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// Verify expect_account's relationship
|
// Verify expect_account's relationship
|
||||||
match maybe_nonce_rollback {
|
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() =>
|
if nonce_pubkey == account_pubkey && tx_result.is_ok() =>
|
||||||
{
|
{
|
||||||
assert_eq!(expect_account, account) // Account update occurs in system_instruction_processor
|
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 =>
|
if nonce_pubkey == account_pubkey =>
|
||||||
{
|
{
|
||||||
assert_ne!(expect_account, nonce_account)
|
assert_ne!(expect_account, nonce_account)
|
||||||
@ -2156,7 +2185,8 @@ mod tests {
|
|||||||
Some((
|
Some((
|
||||||
&pre_account_pubkey,
|
&pre_account_pubkey,
|
||||||
&pre_account,
|
&pre_account,
|
||||||
maybe_fee_account.as_ref()
|
maybe_fee_account.as_ref(),
|
||||||
|
false,
|
||||||
)),
|
)),
|
||||||
&(last_blockhash, last_fee_calculator),
|
&(last_blockhash, last_fee_calculator),
|
||||||
&expect_account,
|
&expect_account,
|
||||||
@ -2207,7 +2237,8 @@ mod tests {
|
|||||||
Some((
|
Some((
|
||||||
&pre_account_pubkey,
|
&pre_account_pubkey,
|
||||||
&pre_account,
|
&pre_account,
|
||||||
maybe_fee_account.as_ref()
|
maybe_fee_account.as_ref(),
|
||||||
|
true,
|
||||||
)),
|
)),
|
||||||
&(last_blockhash, last_fee_calculator),
|
&(last_blockhash, last_fee_calculator),
|
||||||
&expect_account,
|
&expect_account,
|
||||||
@ -2247,7 +2278,8 @@ mod tests {
|
|||||||
Some((
|
Some((
|
||||||
&pre_account_pubkey,
|
&pre_account_pubkey,
|
||||||
&pre_account,
|
&pre_account,
|
||||||
maybe_fee_account.as_ref()
|
maybe_fee_account.as_ref(),
|
||||||
|
true,
|
||||||
)),
|
)),
|
||||||
&(last_blockhash, last_fee_calculator),
|
&(last_blockhash, last_fee_calculator),
|
||||||
&expect_account,
|
&expect_account,
|
||||||
@ -2344,6 +2376,7 @@ mod tests {
|
|||||||
&rent_collector,
|
&rent_collector,
|
||||||
&(next_blockhash, FeeCalculator::default()),
|
&(next_blockhash, FeeCalculator::default()),
|
||||||
true,
|
true,
|
||||||
|
true, // merge_nonce_error_into_system_error
|
||||||
);
|
);
|
||||||
assert_eq!(collected_accounts.len(), 2);
|
assert_eq!(collected_accounts.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -2460,6 +2493,7 @@ mod tests {
|
|||||||
&rent_collector,
|
&rent_collector,
|
||||||
&(next_blockhash, FeeCalculator::default()),
|
&(next_blockhash, FeeCalculator::default()),
|
||||||
true,
|
true,
|
||||||
|
true, // merge_nonce_error_into_system_error
|
||||||
);
|
);
|
||||||
assert_eq!(collected_accounts.len(), 1);
|
assert_eq!(collected_accounts.len(), 1);
|
||||||
let collected_nonce_account = collected_accounts
|
let collected_nonce_account = collected_accounts
|
||||||
|
@ -3534,6 +3534,7 @@ impl Bank {
|
|||||||
&self.rent_collector,
|
&self.rent_collector,
|
||||||
&self.last_blockhash_with_fee_calculator(),
|
&self.last_blockhash_with_fee_calculator(),
|
||||||
self.rent_for_sysvars(),
|
self.rent_for_sysvars(),
|
||||||
|
self.merge_nonce_error_into_system_error(),
|
||||||
);
|
);
|
||||||
let rent_debits = self.collect_rent(executed, loaded_txs);
|
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())
|
.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
|
// Check if the wallclock time from bank creation to now has exceeded the allotted
|
||||||
// time for transaction processing
|
// time for transaction processing
|
||||||
pub fn should_bank_still_be_processing_txs(
|
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]
|
#[test]
|
||||||
fn test_nonce_payer() {
|
fn test_nonce_payer() {
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
pub mod state;
|
pub mod state;
|
||||||
pub use state::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::sysvar::recent_blockhashes;
|
||||||
use crate::{
|
use crate::{
|
||||||
decode_error::DecodeError,
|
decode_error::DecodeError,
|
||||||
instruction::{AccountMeta, Instruction},
|
instruction::{AccountMeta, Instruction, InstructionError},
|
||||||
nonce,
|
nonce,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
system_program,
|
system_program,
|
||||||
@ -25,6 +25,12 @@ pub enum SystemError {
|
|||||||
MaxSeedLengthExceeded,
|
MaxSeedLengthExceeded,
|
||||||
#[error("provided address does not match addressed derived from seed")]
|
#[error("provided address does not match addressed derived from seed")]
|
||||||
AddressWithSeedMismatch,
|
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 {
|
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
|
/// maximum permitted size of data: 10 MB
|
||||||
pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
|
pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
|
||||||
|
|
||||||
@ -492,6 +575,7 @@ pub fn authorize_nonce_account(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::instruction::{Instruction, InstructionError};
|
use crate::instruction::{Instruction, InstructionError};
|
||||||
|
use num_traits::ToPrimitive;
|
||||||
|
|
||||||
fn get_keys(instruction: &Instruction) -> Vec<Pubkey> {
|
fn get_keys(instruction: &Instruction) -> Vec<Pubkey> {
|
||||||
instruction.accounts.iter().map(|x| x.pubkey).collect()
|
instruction.accounts.iter().map(|x| x.pubkey).collect()
|
||||||
@ -561,4 +645,127 @@ mod tests {
|
|||||||
pretty_err::<NonceError>(NonceError::BadAccountState.into())
|
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");
|
solana_sdk::declare_id!("FToKNBYyiF4ky9s8WsmLBXHCht17Ek7RXaLZGHzzQhJ1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod merge_nonce_error_into_system_error {
|
||||||
|
solana_sdk::declare_id!("21AWDosvp3pBamFW91KB35pNoaoZVTM7ess8nr2nt53B");
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Map of feature identifiers to user-visible description
|
/// Map of feature identifiers to user-visible description
|
||||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
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"),
|
(libsecp256k1_0_5_upgrade_enabled::id(), "upgrade libsecp256k1 to v0.5.0"),
|
||||||
(tx_wide_compute_cap::id(), "Transaction wide compute cap"),
|
(tx_wide_compute_cap::id(), "Transaction wide compute cap"),
|
||||||
(spl_token_v2_set_authority_fix::id(), "spl-token set_authority fix"),
|
(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 ***************/
|
/*************** ADD NEW FEATURES HERE ***************/
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
account::{ReadableAccount, WritableAccount},
|
account::{ReadableAccount, WritableAccount},
|
||||||
account_utils::State as AccountUtilsState,
|
account_utils::State as AccountUtilsState,
|
||||||
ic_msg,
|
feature_set, ic_msg,
|
||||||
keyed_account::KeyedAccount,
|
keyed_account::KeyedAccount,
|
||||||
nonce_account::create_account,
|
nonce_account::create_account,
|
||||||
process_instruction::InvokeContext,
|
process_instruction::InvokeContext,
|
||||||
@ -12,7 +12,7 @@ use solana_program::{
|
|||||||
instruction::{checked_add, InstructionError},
|
instruction::{checked_add, InstructionError},
|
||||||
nonce::{self, state::Versions, State},
|
nonce::{self, state::Versions, State},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
system_instruction::NonceError,
|
system_instruction::{nonce_to_instruction_error, NonceError},
|
||||||
sysvar::rent::Rent,
|
sysvar::rent::Rent,
|
||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@ -51,6 +51,8 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
|||||||
signers: &HashSet<Pubkey>,
|
signers: &HashSet<Pubkey>,
|
||||||
invoke_context: &dyn InvokeContext,
|
invoke_context: &dyn InvokeContext,
|
||||||
) -> Result<(), InstructionError> {
|
) -> 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();
|
let state = AccountUtilsState::<Versions>::state(self)?.convert_to_current();
|
||||||
match state {
|
match state {
|
||||||
State::Initialized(data) => {
|
State::Initialized(data) => {
|
||||||
@ -68,7 +70,10 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
|||||||
invoke_context,
|
invoke_context,
|
||||||
"Advance nonce account: nonce can only advance once per slot"
|
"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 {
|
let new_data = nonce::state::Data {
|
||||||
@ -84,7 +89,10 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
|||||||
"Advance nonce account: Account {} state is invalid",
|
"Advance nonce account: Account {} state is invalid",
|
||||||
self.unsigned_key()
|
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>,
|
signers: &HashSet<Pubkey>,
|
||||||
invoke_context: &dyn InvokeContext,
|
invoke_context: &dyn InvokeContext,
|
||||||
) -> Result<(), InstructionError> {
|
) -> 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() {
|
let signer = match AccountUtilsState::<Versions>::state(self)?.convert_to_current() {
|
||||||
State::Uninitialized => {
|
State::Uninitialized => {
|
||||||
if lamports > self.lamports()? {
|
if lamports > self.lamports()? {
|
||||||
@ -117,7 +127,10 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
|||||||
invoke_context,
|
invoke_context,
|
||||||
"Withdraw nonce account: nonce can only advance once per slot"
|
"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))?;
|
self.set_state(&Versions::new_current(State::Uninitialized))?;
|
||||||
} else {
|
} else {
|
||||||
@ -168,6 +181,8 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
|||||||
rent: &Rent,
|
rent: &Rent,
|
||||||
invoke_context: &dyn InvokeContext,
|
invoke_context: &dyn InvokeContext,
|
||||||
) -> Result<(), InstructionError> {
|
) -> 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() {
|
match AccountUtilsState::<Versions>::state(self)?.convert_to_current() {
|
||||||
State::Uninitialized => {
|
State::Uninitialized => {
|
||||||
let min_balance = rent.minimum_balance(self.data_len()?);
|
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",
|
"Initialize nonce account: Account {} state is invalid",
|
||||||
self.unsigned_key()
|
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>,
|
signers: &HashSet<Pubkey>,
|
||||||
invoke_context: &dyn InvokeContext,
|
invoke_context: &dyn InvokeContext,
|
||||||
) -> Result<(), InstructionError> {
|
) -> 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() {
|
match AccountUtilsState::<Versions>::state(self)?.convert_to_current() {
|
||||||
State::Initialized(data) => {
|
State::Initialized(data) => {
|
||||||
if !signers.contains(&data.authority) {
|
if !signers.contains(&data.authority) {
|
||||||
@ -226,7 +246,10 @@ impl<'a> NonceKeyedAccount for KeyedAccount<'a> {
|
|||||||
"Authorize nonce account: Account {} state is invalid",
|
"Authorize nonce account: Account {} state is invalid",
|
||||||
self.unsigned_key()
|
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::{self, State},
|
||||||
nonce_account::verify_nonce_account,
|
nonce_account::verify_nonce_account,
|
||||||
process_instruction::MockInvokeContext,
|
process_instruction::MockInvokeContext,
|
||||||
system_instruction::NonceError,
|
system_instruction::SystemError,
|
||||||
};
|
};
|
||||||
use solana_program::hash::{hash, Hash};
|
use solana_program::hash::{hash, Hash};
|
||||||
|
|
||||||
@ -419,7 +442,7 @@ mod test {
|
|||||||
.initialize_nonce_account(&authorized, &rent, &invoke_context)
|
.initialize_nonce_account(&authorized, &rent, &invoke_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let result = keyed_account.advance_nonce_account(&signers, &invoke_context);
|
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());
|
signers.insert(*keyed_account.signer_key().unwrap());
|
||||||
let invoke_context = create_invoke_context_with_blockhash(63);
|
let invoke_context = create_invoke_context_with_blockhash(63);
|
||||||
let result = keyed_account.advance_nonce_account(&signers, &invoke_context);
|
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,
|
&signers,
|
||||||
&invoke_context,
|
&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 invoke_context = create_invoke_context_with_blockhash(0);
|
||||||
let result =
|
let result =
|
||||||
keyed_account.initialize_nonce_account(&authorized, &rent, &invoke_context);
|
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,
|
&signers,
|
||||||
&invoke_context,
|
&invoke_context,
|
||||||
);
|
);
|
||||||
assert_eq!(result, Err(NonceError::BadAccountState.into()));
|
assert_eq!(result, Err(InstructionError::InvalidAccountData));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ use crate::{
|
|||||||
hash::Hash,
|
hash::Hash,
|
||||||
instruction::{CompiledInstruction, Instruction, InstructionError},
|
instruction::{CompiledInstruction, Instruction, InstructionError},
|
||||||
message::Message,
|
message::Message,
|
||||||
|
nonce::NONCED_TX_MARKER_IX_INDEX,
|
||||||
program_utils::limited_deserialize,
|
program_utils::limited_deserialize,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
short_vec,
|
short_vec,
|
||||||
@ -464,7 +465,7 @@ pub fn uses_durable_nonce(tx: &Transaction) -> Option<&CompiledInstruction> {
|
|||||||
let message = tx.message();
|
let message = tx.message();
|
||||||
message
|
message
|
||||||
.instructions
|
.instructions
|
||||||
.get(0)
|
.get(NONCED_TX_MARKER_IX_INDEX as usize)
|
||||||
.filter(|maybe_ix| {
|
.filter(|maybe_ix| {
|
||||||
let prog_id_idx = maybe_ix.program_id_index as usize;
|
let prog_id_idx = maybe_ix.program_id_index as usize;
|
||||||
match message.account_keys.get(prog_id_idx) {
|
match message.account_keys.get(prog_id_idx) {
|
||||||
|
Reference in New Issue
Block a user