Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions cost-model/src/transaction_cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ impl solana_svm_transaction::svm_message::SVMMessage for WritableKeysTransaction
core::iter::empty()
}

fn static_account_keys(&self) -> &[Pubkey] {
&self.0
}

fn account_keys(&self) -> solana_message::AccountKeys {
solana_message::AccountKeys::new(&self.0, None)
}
Expand Down
5 changes: 5 additions & 0 deletions feature-set/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,10 @@ pub mod enable_vote_address_leader_schedule {
solana_pubkey::declare_id!("5JsG4NWH8Jbrqdd8uL6BNwnyZK3dQSoieRXG5vmofj9y");
}

pub mod require_static_nonce_account {
solana_pubkey::declare_id!("7VVhpg5oAjAmnmz1zCcSHb2Z9ecZB2FQqpnEwReka9Zm");
}

pub mod raise_block_limits_to_60m {
solana_pubkey::declare_id!("6oMCUgfY6BzZ6jwB681J6ju5Bh6CjVXbd7NeWYqiXBSu");
}
Expand Down Expand Up @@ -1255,6 +1259,7 @@ pub static FEATURE_NAMES: LazyLock<AHashMap<Pubkey, &'static str>> = LazyLock::n
(create_slashing_program::id(), "creates an enshrined slashing program SIMD-0204"),
(disable_partitioned_rent_collection::id(), "Disable partitioned rent collection SIMD-0175 #4562"),
(enable_vote_address_leader_schedule::id(), "Enable vote address leader schedule SIMD-0180 #4573"),
(require_static_nonce_account::id(), "SIMD-0242: Static Nonce Account Only"),
(raise_block_limits_to_60m::id(), "Raise block limit to 60M SIMD-0256"),
/*************** ADD NEW FEATURES HERE ***************/
Comment on lines +1262 to 1264
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to not put this at the end?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to avoid git conflicts

]
Expand Down
4 changes: 4 additions & 0 deletions runtime-transaction/src/runtime_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ impl<T: SVMMessage> SVMMessage for RuntimeTransaction<T> {
self.transaction.program_instructions_iter()
}

fn static_account_keys(&self) -> &[Pubkey] {
self.transaction.static_account_keys()
}

fn account_keys(&self) -> AccountKeys {
self.transaction.account_keys()
}
Expand Down
92 changes: 89 additions & 3 deletions runtime/src/bank/check_transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,10 @@ impl Bank {
&self,
message: &impl SVMMessage,
) -> Option<(Pubkey, AccountSharedData, NonceData)> {
let nonce_address = message.get_durable_nonce()?;
let require_static_nonce_account = self
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we pass this in instead of lookup in feature-set per transaction?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did consider that. I didn't love that it would technically allow calling a bank method with a feature gate enabled which the bank didn't have enabled. Also this will only get called for transactions which don't have a recent blockhash, so not too bad I think. Would be nice to do some refactoring in this area tho, wdyt?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sg

.feature_set
.is_active(&agave_feature_set::require_static_nonce_account::id());
let nonce_address = message.get_durable_nonce(require_static_nonce_account)?;
let nonce_account = self.get_account_with_fixed_root(nonce_address)?;
let nonce_data =
nonce_account::verify_nonce_account(&nonce_account, message.recent_blockhash())?;
Expand Down Expand Up @@ -300,8 +303,20 @@ mod tests {
setup_nonce_with_bank,
},
solana_sdk::{
hash::Hash, message::Message, signature::Keypair, signer::Signer, system_instruction,
hash::Hash,
instruction::CompiledInstruction,
message::{
v0::{self, LoadedAddresses, MessageAddressTableLookup},
Message, MessageHeader, SanitizedMessage, SanitizedVersionedMessage,
SimpleAddressLoader, VersionedMessage,
},
signature::Keypair,
signer::Signer,
system_instruction::{self, SystemInstruction},
system_program,
},
std::collections::HashSet,
test_case::test_case,
};

#[test]
Expand Down Expand Up @@ -491,8 +506,79 @@ mod tests {
.check_load_and_advance_message_nonce_account(
&message,
&bank.next_durable_nonce(),
lamports_per_signature
lamports_per_signature,
)
.is_none());
}

#[test_case(true; "test_check_and_load_message_nonce_account_nonce_is_alt_disallowed")]
#[test_case(false; "test_check_and_load_message_nonce_account_nonce_is_alt_allowed")]
fn test_check_and_load_message_nonce_account_nonce_is_alt(require_static_nonce_account: bool) {
let feature_set = if require_static_nonce_account {
FeatureSet::all_enabled()
} else {
FeatureSet::default()
};
let nonce_authority = Pubkey::new_unique();
let (bank, _mint_keypair, _custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
10_000_000,
|_| {},
5_000_000,
250_000,
Some(nonce_authority),
feature_set,
)
.unwrap();

let nonce_pubkey = nonce_keypair.pubkey();
let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
let loaded_addresses = LoadedAddresses {
writable: vec![nonce_pubkey],
readonly: vec![],
};

let message = SanitizedMessage::try_new(
SanitizedVersionedMessage::try_new(VersionedMessage::V0(v0::Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![nonce_authority, system_program::id()],
recent_blockhash: nonce_hash,
instructions: vec![CompiledInstruction::new(
1, // index of system program
&SystemInstruction::AdvanceNonceAccount,
vec![
2, // index of alt nonce account
0, // index of nonce_authority
],
)],
address_table_lookups: vec![MessageAddressTableLookup {
account_key: Pubkey::new_unique(),
writable_indexes: (0..loaded_addresses.writable.len())
.map(|x| x as u8)
.collect(),
readonly_indexes: (0..loaded_addresses.readonly.len())
.map(|x| (loaded_addresses.writable.len() + x) as u8)
.collect(),
}],
}))
.unwrap(),
SimpleAddressLoader::Enabled(loaded_addresses),
&HashSet::new(),
)
.unwrap();

let (_, lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
assert_eq!(
bank.check_load_and_advance_message_nonce_account(
&message,
&bank.next_durable_nonce(),
lamports_per_signature
)
.is_none(),
require_static_nonce_account,
);
}
}
1 change: 1 addition & 0 deletions svm-transaction/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ solana-message = { workspace = true, features = ["bincode"] }
solana-nonce = { workspace = true }
solana-system-interface = { workspace = true }
static_assertions = { workspace = true }
test-case = { workspace = true }
9 changes: 7 additions & 2 deletions svm-transaction/src/svm_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ pub trait SVMMessage: Debug {
/// the pubkey of the program.
fn program_instructions_iter(&self) -> impl Iterator<Item = (&Pubkey, SVMInstruction)> + Clone;

/// Return the list of static account keys.
fn static_account_keys(&self) -> &[Pubkey];

/// Return the account keys.
fn account_keys(&self) -> AccountKeys;

Expand Down Expand Up @@ -81,7 +84,7 @@ pub trait SVMMessage: Debug {
}

/// If the message uses a durable nonce, return the pubkey of the nonce account
fn get_durable_nonce(&self) -> Option<&Pubkey> {
fn get_durable_nonce(&self, require_static_nonce_account: bool) -> Option<&Pubkey> {
let account_keys = self.account_keys();
self.instructions_iter()
.nth(usize::from(NONCED_TX_MARKER_IX_INDEX))
Expand All @@ -104,7 +107,9 @@ pub trait SVMMessage: Debug {
.and_then(|ix| {
ix.accounts.first().and_then(|idx| {
let index = usize::from(*idx);
if !self.is_writable(index) {
if (require_static_nonce_account && index >= self.static_account_keys().len())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we really only require the number of static account keys;

I think exposing static account keys is reasonable. Can you think of any reason we'd want to limit the interface to only the number of static account keys?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we really only require the number of static account keys;

Yeah, just figured we will be using static_account_keys more in the future so I exposed the full slice rather than just the length.

Can you think of any reason we'd want to limit the interface to only the number of static account keys?

Nope, we always have the full list of static account keys available, seems fine to expose it

|| !self.is_writable(index)
{
None
} else {
account_keys.get(index)
Expand Down
4 changes: 4 additions & 0 deletions svm-transaction/src/svm_message/sanitized_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ impl SVMMessage for SanitizedMessage {
.map(|(pubkey, ix)| (pubkey, SVMInstruction::from(ix)))
}

fn static_account_keys(&self) -> &[Pubkey] {
SanitizedMessage::static_account_keys(self)
}

fn account_keys(&self) -> AccountKeys {
SanitizedMessage::account_keys(self)
}
Expand Down
4 changes: 4 additions & 0 deletions svm-transaction/src/svm_message/sanitized_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ impl SVMMessage for SanitizedTransaction {
SVMMessage::program_instructions_iter(SanitizedTransaction::message(self))
}

fn static_account_keys(&self) -> &[Pubkey] {
SVMMessage::static_account_keys(SanitizedTransaction::message(self))
}

fn account_keys(&self) -> AccountKeys {
SVMMessage::account_keys(SanitizedTransaction::message(self))
}
Expand Down
49 changes: 34 additions & 15 deletions svm-transaction/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ use {
solana_sdk_ids::system_program,
solana_system_interface::instruction::SystemInstruction,
std::collections::HashSet,
test_case::test_case,
};

#[test]
fn test_get_durable_nonce() {
#[test_case(true; "test_get_durable_nonce_static_nonce_account")]
#[test_case(false; "test_get_durable_nonce_alt_nonce_account")]
fn test_get_durable_nonce(require_static_nonce_account: bool) {
fn create_message_for_test(
num_signers: u8,
num_writable: u8,
Expand All @@ -27,8 +29,10 @@ fn test_get_durable_nonce() {
let header = MessageHeader {
num_required_signatures: num_signers,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: u8::try_from(account_keys.len()).unwrap()
- num_writable,
num_readonly_unsigned_accounts: u8::try_from(account_keys.len())
.unwrap()
.checked_sub(num_writable)
.unwrap(),
};
let (versioned_message, loader) = match loaded_addresses {
None => (
Expand All @@ -52,7 +56,7 @@ fn test_get_durable_nonce() {
.map(|x| x as u8)
.collect(),
readonly_indexes: (0..loaded_addresses.readonly.len())
.map(|x| (loaded_addresses.writable.len() + x) as u8)
.map(|x| loaded_addresses.writable.len().saturating_add(x) as u8)
.collect(),
}],
}),
Expand All @@ -71,7 +75,7 @@ fn test_get_durable_nonce() {
{
let message = create_message_for_test(1, 1, vec![Pubkey::new_unique()], vec![], None);
assert!(SanitizedMessage::get_durable_nonce(&message).is_none());
assert!(SVMMessage::get_durable_nonce(&message).is_none());
assert!(SVMMessage::get_durable_nonce(&message, require_static_nonce_account).is_none());
}

// system program id instruction - invalid
Expand All @@ -84,7 +88,7 @@ fn test_get_durable_nonce() {
None,
);
assert!(SanitizedMessage::get_durable_nonce(&message).is_none());
assert!(SVMMessage::get_durable_nonce(&message).is_none());
assert!(SVMMessage::get_durable_nonce(&message, require_static_nonce_account).is_none());
}

// system program id instruction - not nonce
Expand All @@ -101,7 +105,7 @@ fn test_get_durable_nonce() {
None,
);
assert!(SanitizedMessage::get_durable_nonce(&message).is_none());
assert!(SVMMessage::get_durable_nonce(&message).is_none());
assert!(SVMMessage::get_durable_nonce(&message, require_static_nonce_account).is_none());
}

// system program id - nonce instruction (no accounts)
Expand All @@ -118,7 +122,7 @@ fn test_get_durable_nonce() {
None,
);
assert!(SanitizedMessage::get_durable_nonce(&message).is_none());
assert!(SVMMessage::get_durable_nonce(&message).is_none());
assert!(SVMMessage::get_durable_nonce(&message, require_static_nonce_account).is_none());
}

// system program id - nonce instruction (non-fee-payer, non-writable)
Expand All @@ -137,7 +141,7 @@ fn test_get_durable_nonce() {
None,
);
assert!(SanitizedMessage::get_durable_nonce(&message).is_none());
assert!(SVMMessage::get_durable_nonce(&message).is_none());
assert!(SVMMessage::get_durable_nonce(&message, require_static_nonce_account).is_none());
}

// system program id - nonce instruction fee-payer
Expand All @@ -158,7 +162,10 @@ fn test_get_durable_nonce() {
SanitizedMessage::get_durable_nonce(&message),
Some(&payer_nonce)
);
assert_eq!(SVMMessage::get_durable_nonce(&message), Some(&payer_nonce));
assert_eq!(
SVMMessage::get_durable_nonce(&message, require_static_nonce_account),
Some(&payer_nonce)
);
}

// system program id - nonce instruction w/ trailing bytes fee-payer
Expand All @@ -181,7 +188,10 @@ fn test_get_durable_nonce() {
SanitizedMessage::get_durable_nonce(&message),
Some(&payer_nonce)
);
assert_eq!(SVMMessage::get_durable_nonce(&message), Some(&payer_nonce));
assert_eq!(
SVMMessage::get_durable_nonce(&message, require_static_nonce_account),
Some(&payer_nonce)
);
}

// system program id - nonce instruction (non-fee-payer)
Expand All @@ -200,7 +210,10 @@ fn test_get_durable_nonce() {
None,
);
assert_eq!(SanitizedMessage::get_durable_nonce(&message), Some(&nonce));
assert_eq!(SVMMessage::get_durable_nonce(&message), Some(&nonce));
assert_eq!(
SVMMessage::get_durable_nonce(&message, require_static_nonce_account),
Some(&nonce)
);
}

// system program id - nonce instruction (non-fee-payer, multiple accounts)
Expand All @@ -220,7 +233,10 @@ fn test_get_durable_nonce() {
None,
);
assert_eq!(SanitizedMessage::get_durable_nonce(&message), Some(&nonce));
assert_eq!(SVMMessage::get_durable_nonce(&message), Some(&nonce));
assert_eq!(
SVMMessage::get_durable_nonce(&message, require_static_nonce_account),
Some(&nonce)
);
}

// system program id - nonce instruction (non-fee-payer, loaded account)
Expand All @@ -242,7 +258,10 @@ fn test_get_durable_nonce() {
}),
);
assert_eq!(SanitizedMessage::get_durable_nonce(&message), Some(&nonce));
assert_eq!(SVMMessage::get_durable_nonce(&message), Some(&nonce));
assert_eq!(
SVMMessage::get_durable_nonce(&message, require_static_nonce_account),
(!require_static_nonce_account).then_some(&nonce)
);
}
}

Expand Down
4 changes: 4 additions & 0 deletions transaction-view/src/resolved_transaction_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ impl<D: TransactionData> SVMMessage for ResolvedTransactionView<D> {
self.view.program_instructions_iter()
}

fn static_account_keys(&self) -> &[Pubkey] {
self.view.static_account_keys()
}

fn account_keys(&self) -> AccountKeys {
AccountKeys::new(
self.view.static_account_keys(),
Expand Down