diff --git a/message/src/compiled_keys.rs b/message/src/compiled_keys.rs index c97337479..91928e905 100644 --- a/message/src/compiled_keys.rs +++ b/message/src/compiled_keys.rs @@ -5,7 +5,7 @@ use crate::{ }; use { crate::MessageHeader, core::fmt, solana_instruction::Instruction, solana_pubkey::Pubkey, - std::collections::BTreeMap, + solana_sdk_ids::system_program, std::collections::BTreeMap, }; /// A helper struct to collect pubkeys compiled for a set of instructions @@ -47,6 +47,7 @@ struct CompiledKeyMeta { is_signer: bool, is_writable: bool, is_invoked: bool, + is_nonce: bool, } impl CompiledKeys { @@ -63,6 +64,10 @@ impl CompiledKeys { meta.is_writable |= account_meta.is_writable; } } + if let Some(nonce_pubkey) = get_nonce_pubkey(instructions) { + let meta = key_meta_map.entry(*nonce_pubkey).or_default(); + meta.is_nonce = true; + } if let Some(payer) = &payer { let meta = key_meta_map.entry(*payer).or_default(); meta.is_signer = true; @@ -138,11 +143,11 @@ impl CompiledKeys { ) -> Result, CompileError> { let (writable_indexes, drained_writable_keys) = self .try_drain_keys_found_in_lookup_table(&lookup_table_account.addresses, |meta| { - !meta.is_signer && !meta.is_invoked && meta.is_writable + !meta.is_signer && !meta.is_invoked && !meta.is_nonce && meta.is_writable })?; let (readonly_indexes, drained_readonly_keys) = self .try_drain_keys_found_in_lookup_table(&lookup_table_account.addresses, |meta| { - !meta.is_signer && !meta.is_invoked && !meta.is_writable + !meta.is_signer && !meta.is_invoked && !meta.is_nonce && !meta.is_writable })?; // Don't extract lookup if no keys were found @@ -197,9 +202,38 @@ impl CompiledKeys { } } +// inlined to avoid solana_nonce dep +const NONCED_TX_MARKER_IX_INDEX: usize = 0; +// inlined to avoid solana_system_interface and bincode deps +const ADVANCE_NONCE_PREFIX: [u8; 4] = [4, 0, 0, 0]; + +fn get_nonce_pubkey(instructions: &[Instruction]) -> Option<&Pubkey> { + let ix = instructions.get(NONCED_TX_MARKER_IX_INDEX)?; + if !system_program::check_id(&ix.program_id) { + return None; + } + + if ix.data.get(0..4) != Some(&ADVANCE_NONCE_PREFIX[..]) { + return None; + } + + ix.accounts.first().map(|meta| &meta.pubkey) +} + #[cfg(test)] mod tests { - use {super::*, bitflags::bitflags, solana_instruction::AccountMeta}; + use { + super::*, + bitflags::bitflags, + solana_instruction::AccountMeta, + solana_sdk_ids::sysvar::recent_blockhashes, + solana_system_interface::instruction::{advance_nonce_account, SystemInstruction}, + }; + + static_assertions::const_assert_eq!( + NONCED_TX_MARKER_IX_INDEX, + solana_nonce::NONCED_TX_MARKER_IX_INDEX as usize + ); bitflags! { #[derive(Clone, Copy)] @@ -207,6 +241,7 @@ mod tests { const SIGNER = 0b00000001; const WRITABLE = 0b00000010; const INVOKED = 0b00000100; + const NONCE = 0b00001000; } } @@ -216,10 +251,21 @@ mod tests { is_signer: flags.contains(KeyFlags::SIGNER), is_writable: flags.contains(KeyFlags::WRITABLE), is_invoked: flags.contains(KeyFlags::INVOKED), + is_nonce: flags.contains(KeyFlags::NONCE), } } } + #[test] + fn test_advance_nonce_ix_prefix() { + let advance_nonce_ix: SystemInstruction = solana_bincode::limited_deserialize( + &ADVANCE_NONCE_PREFIX[..], + 4, /* serialized size of AdvanceNonceAccount */ + ) + .unwrap(); + assert_eq!(advance_nonce_ix, SystemInstruction::AdvanceNonceAccount); + } + #[test] fn test_compile_with_dups() { let program_id0 = Pubkey::new_unique(); @@ -271,7 +317,10 @@ mod tests { (program_id0, KeyFlags::INVOKED.into()), (program_id1, (KeyFlags::INVOKED | KeyFlags::SIGNER).into()), (program_id2, (KeyFlags::INVOKED | KeyFlags::WRITABLE).into()), - (program_id3, KeyFlags::all().into()), + ( + program_id3, + (KeyFlags::INVOKED | KeyFlags::WRITABLE | KeyFlags::SIGNER).into() + ), ]), } ); @@ -388,6 +437,32 @@ mod tests { ); } + #[test] + fn test_compile_with_nonce_instruction() { + let nonce_pubkey = Pubkey::new_unique(); + let nonce_authority = Pubkey::new_unique(); + let compiled_keys = CompiledKeys::compile( + &[advance_nonce_account(&nonce_pubkey, &nonce_authority)], + Some(nonce_authority), + ); + + assert_eq!( + compiled_keys, + CompiledKeys { + payer: Some(nonce_authority), + key_meta_map: BTreeMap::from([ + (nonce_pubkey, (KeyFlags::NONCE | KeyFlags::WRITABLE).into()), + ( + nonce_authority, + (KeyFlags::SIGNER | KeyFlags::WRITABLE).into() + ), + (system_program::id(), KeyFlags::INVOKED.into()), + (recent_blockhashes::id(), CompiledKeyMeta::default()) + ]), + } + ); + } + #[test] fn test_try_into_message_components() { let keys = vec![