Use reserved account keys list to restrict tx write locks (#541)

* Plumb through reserved account keys set

* Plumb through tests
This commit is contained in:
Justin Starry 2024-04-13 09:37:58 +08:00 committed by GitHub
parent cb13b39118
commit 09241ae9c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 355 additions and 137 deletions

View File

@ -178,6 +178,7 @@ fn simulate_transaction(
MessageHash::Compute,
Some(false), // is_simple_vote_tx
bank,
bank.get_reserved_account_keys(),
) {
Err(err) => {
return BanksTransactionResultWithSimulation {
@ -332,6 +333,7 @@ impl Banks for BanksServer {
MessageHash::Compute,
Some(false), // is_simple_vote_tx
bank.as_ref(),
bank.get_reserved_account_keys(),
) {
Ok(tx) => tx,
Err(err) => return Some(Err(err)),
@ -417,7 +419,9 @@ impl Banks for BanksServer {
commitment: CommitmentLevel,
) -> Option<u64> {
let bank = self.bank(commitment);
let sanitized_message = SanitizedMessage::try_from_legacy_message(message).ok()?;
let sanitized_message =
SanitizedMessage::try_from_legacy_message(message, bank.get_reserved_account_keys())
.ok()?;
bank.get_fee_for_message(&sanitized_message)
}
}

View File

@ -868,6 +868,7 @@ mod tests {
nonce_account::verify_nonce_account,
poh_config::PohConfig,
pubkey::Pubkey,
reserved_account_keys::ReservedAccountKeys,
signature::Keypair,
signer::Signer,
system_instruction, system_program, system_transaction,
@ -2026,6 +2027,7 @@ mod tests {
MessageHash::Compute,
Some(false),
bank.as_ref(),
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();

View File

@ -6,6 +6,7 @@ use {
feature_set,
hash::Hash,
message::Message,
pubkey::Pubkey,
sanitize::SanitizeError,
saturating_add_assign,
short_vec::decode_shortu16_len,
@ -15,7 +16,7 @@ use {
VersionedTransaction,
},
},
std::{cmp::Ordering, mem::size_of, sync::Arc},
std::{cmp::Ordering, collections::HashSet, mem::size_of, sync::Arc},
thiserror::Error,
};
@ -123,6 +124,7 @@ impl ImmutableDeserializedPacket {
feature_set: &Arc<feature_set::FeatureSet>,
votes_only: bool,
address_loader: impl AddressLoader,
reserved_account_keys: &HashSet<Pubkey>,
) -> Option<SanitizedTransaction> {
if votes_only && !self.is_simple_vote() {
return None;
@ -132,6 +134,7 @@ impl ImmutableDeserializedPacket {
*self.message_hash(),
self.is_simple_vote(),
address_loader,
reserved_account_keys,
)
.ok()?;
tx.verify_precompiles(feature_set).ok()?;

View File

@ -283,6 +283,7 @@ impl LatestUnprocessedVotes {
&bank.feature_set,
bank.vote_only_bank(),
bank.as_ref(),
bank.get_reserved_account_keys(),
)
{
if forward_packet_batches_by_accounts.try_add_packet(

View File

@ -138,6 +138,7 @@ mod tests {
MessageHash::Compute,
Some(false),
bank,
bank.get_reserved_account_keys(),
)
.unwrap()
}

View File

@ -381,7 +381,12 @@ impl SchedulerController {
let (transactions, fee_budget_limits_vec): (Vec<_>, Vec<_>) = chunk
.iter()
.filter_map(|packet| {
packet.build_sanitized_transaction(feature_set, vote_only, bank.as_ref())
packet.build_sanitized_transaction(
feature_set,
vote_only,
bank.as_ref(),
bank.get_reserved_account_keys(),
)
})
.inspect(|_| saturating_add_assign!(post_sanitization_count, 1))
.filter(|tx| {

View File

@ -310,6 +310,7 @@ mod tests {
solana_sdk::{
compute_budget::ComputeBudgetInstruction,
message::Message,
reserved_account_keys::ReservedAccountKeys,
signature::{Keypair, Signer},
system_instruction, system_transaction,
transaction::{SimpleAddressLoader, Transaction},
@ -490,6 +491,7 @@ mod tests {
&Arc::new(FeatureSet::default()),
votes_only,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
});
assert_eq!(2, txs.count());
@ -500,6 +502,7 @@ mod tests {
&Arc::new(FeatureSet::default()),
votes_only,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
});
assert_eq!(0, txs.count());
@ -519,6 +522,7 @@ mod tests {
&Arc::new(FeatureSet::default()),
votes_only,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
});
assert_eq!(3, txs.count());
@ -529,6 +533,7 @@ mod tests {
&Arc::new(FeatureSet::default()),
votes_only,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
});
assert_eq!(2, txs.count());
@ -548,6 +553,7 @@ mod tests {
&Arc::new(FeatureSet::default()),
votes_only,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
});
assert_eq!(3, txs.count());
@ -558,6 +564,7 @@ mod tests {
&Arc::new(FeatureSet::default()),
votes_only,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
});
assert_eq!(3, txs.count());

View File

@ -153,9 +153,13 @@ fn consume_scan_should_process_packet(
}
// Try to sanitize the packet
let (maybe_sanitized_transaction, sanitization_time_us) = measure_us!(
packet.build_sanitized_transaction(&bank.feature_set, bank.vote_only_bank(), bank)
);
let (maybe_sanitized_transaction, sanitization_time_us) = measure_us!(packet
.build_sanitized_transaction(
&bank.feature_set,
bank.vote_only_bank(),
bank,
bank.get_reserved_account_keys(),
));
payload
.slot_metrics_tracker
@ -770,7 +774,12 @@ impl ThreadLocalUnprocessedPackets {
.enumerate()
.filter_map(|(packet_index, deserialized_packet)| {
deserialized_packet
.build_sanitized_transaction(&bank.feature_set, bank.vote_only_bank(), bank)
.build_sanitized_transaction(
&bank.feature_set,
bank.vote_only_bank(),
bank,
bank.get_reserved_account_keys(),
)
.map(|transaction| (transaction, packet_index))
})
.unzip();

View File

@ -339,6 +339,7 @@ mod tests {
crate::transaction_cost::*,
solana_sdk::{
hash::Hash,
reserved_account_keys::ReservedAccountKeys,
signature::{Keypair, Signer},
system_transaction,
transaction::{
@ -401,6 +402,7 @@ mod tests {
MessageHash::Compute,
Some(true),
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();

View File

@ -203,6 +203,7 @@ mod tests {
feature_set::FeatureSet,
hash::Hash,
message::SimpleAddressLoader,
reserved_account_keys::ReservedAccountKeys,
signer::keypair::Keypair,
transaction::{MessageHash, SanitizedTransaction, VersionedTransaction},
},
@ -231,6 +232,7 @@ mod tests {
MessageHash::Compute,
Some(true),
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
@ -240,6 +242,7 @@ mod tests {
MessageHash::Compute,
Some(false),
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();

View File

@ -5,6 +5,7 @@ use {
solana_perf::test_tx::test_tx,
solana_sdk::{
hash::Hash,
reserved_account_keys::ReservedAccountKeys,
transaction::{
Result, SanitizedTransaction, SimpleAddressLoader, TransactionVerificationMode,
VersionedTransaction,
@ -41,6 +42,7 @@ fn bench_gpusigverify(bencher: &mut Bencher) {
message_hash,
None,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
}?;
@ -84,6 +86,7 @@ fn bench_cpusigverify(bencher: &mut Bencher) {
message_hash,
None,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
}?;

View File

@ -982,6 +982,7 @@ mod tests {
solana_sdk::{
hash::{hash, Hash},
pubkey::Pubkey,
reserved_account_keys::ReservedAccountKeys,
signature::{Keypair, Signer},
system_transaction,
transaction::{
@ -1084,6 +1085,7 @@ mod tests {
message_hash,
None,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
}?;

View File

@ -67,6 +67,7 @@ use {
native_token::{lamports_to_sol, sol_to_lamports, Sol},
pubkey::Pubkey,
rent::Rent,
reserved_account_keys::ReservedAccountKeys,
shred_version::compute_shred_version,
stake::{self, state::StakeStateV2},
system_program,
@ -461,6 +462,9 @@ fn compute_slot_cost(
let mut program_ids = HashMap::new();
let mut cost_tracker = CostTracker::default();
let feature_set = FeatureSet::all_enabled();
let reserved_account_keys = ReservedAccountKeys::new_all_activated();
for entry in entries {
num_transactions += entry.transactions.len();
entry
@ -472,6 +476,7 @@ fn compute_slot_cost(
MessageHash::Compute,
None,
SimpleAddressLoader::Disabled,
&reserved_account_keys.active,
)
.map_err(|err| {
warn!("Failed to compute cost of transaction: {:?}", err);
@ -481,7 +486,7 @@ fn compute_slot_cost(
.for_each(|transaction| {
num_programs += transaction.message().instructions().len();
let tx_cost = CostModel::calculate_cost(&transaction, &FeatureSet::all_enabled());
let tx_cost = CostModel::calculate_cost(&transaction, &feature_set);
let result = cost_tracker.try_add(&tx_cost);
if result.is_err() {
println!(

View File

@ -81,6 +81,7 @@ use {
message::Message,
pubkey::Pubkey,
rent::Rent,
reserved_account_keys::ReservedAccountKeys,
signature::{Keypair, Signer},
system_program,
transaction::{SanitizedTransaction, Transaction, TransactionError},
@ -201,7 +202,11 @@ fn execute_transactions(
}
.expect("lamports_per_signature must be available");
let fee = bank.get_fee_for_message_with_lamports_per_signature(
&SanitizedMessage::try_from_legacy_message(tx.message().clone()).unwrap(),
&SanitizedMessage::try_from_legacy_message(
tx.message().clone(),
&ReservedAccountKeys::empty_key_set(),
)
.unwrap(),
lamports_per_signature,
);
@ -3706,7 +3711,11 @@ fn test_program_fees() {
Some(&mint_keypair.pubkey()),
);
let sanitized_message = SanitizedMessage::try_from_legacy_message(message.clone()).unwrap();
let sanitized_message = SanitizedMessage::try_from_legacy_message(
message.clone(),
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
let expected_normal_fee = fee_structure.calculate_fee(
&sanitized_message,
congestion_multiplier,
@ -3730,7 +3739,11 @@ fn test_program_fees() {
],
Some(&mint_keypair.pubkey()),
);
let sanitized_message = SanitizedMessage::try_from_legacy_message(message.clone()).unwrap();
let sanitized_message = SanitizedMessage::try_from_legacy_message(
message.clone(),
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
let expected_prioritized_fee = fee_structure.calculate_fee(
&sanitized_message,
congestion_multiplier,

View File

@ -3657,7 +3657,11 @@ pub mod rpc_full {
min_context_slot,
})?;
let transaction = sanitize_transaction(unsanitized_tx, preflight_bank)?;
let transaction = sanitize_transaction(
unsanitized_tx,
preflight_bank,
preflight_bank.get_reserved_account_keys(),
)?;
let signature = *transaction.signature();
let mut last_valid_block_height = preflight_bank
@ -3789,7 +3793,8 @@ pub mod rpc_full {
});
}
let transaction = sanitize_transaction(unsanitized_tx, bank)?;
let transaction =
sanitize_transaction(unsanitized_tx, bank, bank.get_reserved_account_keys())?;
if sig_verify {
verify_transaction(&transaction, &bank.feature_set)?;
}
@ -4041,10 +4046,12 @@ pub mod rpc_full {
.map_err(|err| {
Error::invalid_params(format!("invalid transaction message: {err}"))
})?;
let sanitized_message = SanitizedMessage::try_new(sanitized_versioned_message, bank)
.map_err(|err| {
Error::invalid_params(format!("invalid transaction message: {err}"))
})?;
let sanitized_message = SanitizedMessage::try_new(
sanitized_versioned_message,
bank,
bank.get_reserved_account_keys(),
)
.map_err(|err| Error::invalid_params(format!("invalid transaction message: {err}")))?;
let fee = bank.get_fee_for_message(&sanitized_message);
Ok(new_response(bank, fee))
}
@ -4623,9 +4630,16 @@ where
fn sanitize_transaction(
transaction: VersionedTransaction,
address_loader: impl AddressLoader,
reserved_account_keys: &HashSet<Pubkey>,
) -> Result<SanitizedTransaction> {
SanitizedTransaction::try_create(transaction, MessageHash::Compute, None, address_loader)
.map_err(|err| Error::invalid_params(format!("invalid transaction: {err}")))
SanitizedTransaction::try_create(
transaction,
MessageHash::Compute,
None,
address_loader,
reserved_account_keys,
)
.map_err(|err| Error::invalid_params(format!("invalid transaction: {err}")))
}
pub fn create_validator_exit(exit: Arc<AtomicBool>) -> Arc<RwLock<Exit>> {
@ -4771,6 +4785,7 @@ pub mod tests {
Message, MessageHeader, VersionedMessage,
},
nonce::{self, state::DurableNonce},
reserved_account_keys::ReservedAccountKeys,
rpc_port,
signature::{Keypair, Signer},
slot_hashes::SlotHashes,
@ -9049,8 +9064,12 @@ pub mod tests {
.to_string(),
);
assert_eq!(
sanitize_transaction(unsanitary_versioned_tx, SimpleAddressLoader::Disabled)
.unwrap_err(),
sanitize_transaction(
unsanitary_versioned_tx,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set()
)
.unwrap_err(),
expect58
);
}
@ -9070,7 +9089,12 @@ pub mod tests {
};
assert_eq!(
sanitize_transaction(versioned_tx, SimpleAddressLoader::Disabled).unwrap_err(),
sanitize_transaction(
versioned_tx,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set()
)
.unwrap_err(),
Error::invalid_params(
"invalid transaction: Transaction version is unsupported".to_string(),
)

View File

@ -225,6 +225,7 @@ pub(crate) mod tests {
nonce_info::{NonceFull, NoncePartial},
pubkey::Pubkey,
rent_debits::RentDebits,
reserved_account_keys::ReservedAccountKeys,
signature::{Keypair, Signature, Signer},
system_transaction,
transaction::{
@ -335,6 +336,7 @@ pub(crate) mod tests {
MessageHash::Compute,
None,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
@ -364,7 +366,10 @@ pub(crate) mod tests {
durable_nonce_fee: Some(DurableNonceFee::from(
&NonceFull::from_partial(
&rollback_partial,
&SanitizedMessage::Legacy(LegacyMessage::new(message)),
&SanitizedMessage::Legacy(LegacyMessage::new(
message,
&ReservedAccountKeys::empty_key_set(),
)),
&[(pubkey, nonce_account)],
&rent_debits,
)

View File

@ -17,10 +17,12 @@ use {
solana_sdk::{
hash::Hash,
message::{AddressLoader, SanitizedMessage, SanitizedVersionedMessage},
pubkey::Pubkey,
signature::Signature,
simple_vote_transaction_checker::is_simple_vote_transaction,
transaction::{Result, SanitizedVersionedTransaction},
},
std::collections::HashSet,
};
#[derive(Debug, Clone, Eq, PartialEq)]
@ -101,12 +103,14 @@ impl RuntimeTransaction<SanitizedMessage> {
pub fn try_from(
statically_loaded_runtime_tx: RuntimeTransaction<SanitizedVersionedMessage>,
address_loader: impl AddressLoader,
reserved_account_keys: &HashSet<Pubkey>,
) -> Result<Self> {
let mut tx = Self {
signatures: statically_loaded_runtime_tx.signatures,
message: SanitizedMessage::try_new(
statically_loaded_runtime_tx.message,
address_loader,
reserved_account_keys,
)?,
meta: statically_loaded_runtime_tx.meta,
};
@ -132,6 +136,7 @@ mod tests {
compute_budget::ComputeBudgetInstruction,
instruction::Instruction,
message::Message,
reserved_account_keys::ReservedAccountKeys,
signer::{keypair::Keypair, Signer},
transaction::{SimpleAddressLoader, Transaction, VersionedTransaction},
},
@ -256,6 +261,7 @@ mod tests {
let dynamically_loaded_transaction = RuntimeTransaction::<SanitizedMessage>::try_from(
statically_loaded_transaction,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
);
let dynamically_loaded_transaction =
dynamically_loaded_transaction.expect("created from statically loaded tx");

View File

@ -3470,7 +3470,15 @@ impl Bank {
pub fn prepare_entry_batch(&self, txs: Vec<VersionedTransaction>) -> Result<TransactionBatch> {
let sanitized_txs = txs
.into_iter()
.map(|tx| SanitizedTransaction::try_create(tx, MessageHash::Compute, None, self))
.map(|tx| {
SanitizedTransaction::try_create(
tx,
MessageHash::Compute,
None,
self,
self.get_reserved_account_keys(),
)
})
.collect::<Result<Vec<_>>>()?;
let tx_account_lock_limit = self.get_transaction_account_lock_limit();
let lock_results = self
@ -5901,7 +5909,13 @@ impl Bank {
tx.message.hash()
};
SanitizedTransaction::try_create(tx, message_hash, None, self)
SanitizedTransaction::try_create(
tx,
message_hash,
None,
self,
self.get_reserved_account_keys(),
)
}?;
if verification_mode == TransactionVerificationMode::HashAndVerifyPrecompiles

View File

@ -189,7 +189,8 @@ pub(in crate::bank) fn create_genesis_config(lamports: u64) -> (GenesisConfig, K
}
fn new_sanitized_message(message: Message) -> SanitizedMessage {
SanitizedMessage::try_from_legacy_message(message).unwrap()
SanitizedMessage::try_from_legacy_message(message, &ReservedAccountKeys::empty_key_set())
.unwrap()
}
#[test]

View File

@ -285,15 +285,15 @@ impl SyncClient for BankClient {
}
fn get_fee_for_message(&self, message: &Message) -> Result<u64> {
SanitizedMessage::try_from_legacy_message(message.clone())
.ok()
.and_then(|sanitized_message| self.bank.get_fee_for_message(&sanitized_message))
.ok_or_else(|| {
TransportError::IoError(io::Error::new(
io::ErrorKind::Other,
"Unable calculate fee",
))
})
SanitizedMessage::try_from_legacy_message(
message.clone(),
self.bank.get_reserved_account_keys(),
)
.ok()
.and_then(|sanitized_message| self.bank.get_fee_for_message(&sanitized_message))
.ok_or_else(|| {
TransportError::IoError(io::Error::new(io::ErrorKind::Other, "Unable calculate fee"))
})
}
}

View File

@ -7,6 +7,7 @@ use {
instruction::{AccountMeta, Instruction},
message::{Message, SanitizedMessage},
pubkey::{self, Pubkey},
reserved_account_keys::ReservedAccountKeys,
sysvar::instructions::{self, construct_instructions_data},
},
test::Bencher,
@ -29,10 +30,10 @@ fn bench_bincode_instruction_serialize(b: &mut Bencher) {
#[bench]
fn bench_construct_instructions_data(b: &mut Bencher) {
let instructions = make_instructions();
let message = SanitizedMessage::try_from_legacy_message(Message::new(
&instructions,
Some(&Pubkey::new_unique()),
))
let message = SanitizedMessage::try_from_legacy_message(
Message::new(&instructions, Some(&Pubkey::new_unique())),
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
b.iter(|| {
let instructions = message.decompile_instructions();
@ -52,10 +53,10 @@ fn bench_bincode_instruction_deserialize(b: &mut Bencher) {
#[bench]
fn bench_manual_instruction_deserialize(b: &mut Bencher) {
let instructions = make_instructions();
let message = SanitizedMessage::try_from_legacy_message(Message::new(
&instructions,
Some(&Pubkey::new_unique()),
))
let message = SanitizedMessage::try_from_legacy_message(
Message::new(&instructions, Some(&Pubkey::new_unique())),
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
let serialized = construct_instructions_data(&message.decompile_instructions());
b.iter(|| {
@ -69,10 +70,10 @@ fn bench_manual_instruction_deserialize(b: &mut Bencher) {
#[bench]
fn bench_manual_instruction_deserialize_single(b: &mut Bencher) {
let instructions = make_instructions();
let message = SanitizedMessage::try_from_legacy_message(Message::new(
&instructions,
Some(&Pubkey::new_unique()),
))
let message = SanitizedMessage::try_from_legacy_message(
Message::new(&instructions, Some(&Pubkey::new_unique())),
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
let serialized = construct_instructions_data(&message.decompile_instructions());
b.iter(|| {

View File

@ -550,7 +550,7 @@ impl Message {
/// Returns true if the account at the specified index was requested to be
/// writable. This method should not be used directly.
fn is_writable_index(&self, i: usize) -> bool {
pub(super) fn is_writable_index(&self, i: usize) -> bool {
i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts)
as usize
|| (i >= self.header.num_required_signatures as usize
@ -558,10 +558,11 @@ impl Message {
- self.header.num_readonly_unsigned_accounts as usize)
}
/// Returns true if the account at the specified index should be write
/// locked when loaded for transaction processing in the runtime. This
/// method differs from `is_maybe_writable` because it is aware of the
/// latest reserved accounts which are not allowed to be write locked.
/// Returns true if the account at the specified index is writable by the
/// instructions in this message. Since the dynamic set of reserved accounts
/// isn't used here to demote write locks, this shouldn't be used in the
/// runtime.
#[deprecated(since = "2.0.0", note = "Please use `is_maybe_writable` instead")]
pub fn is_writable(&self, i: usize) -> bool {
(self.is_writable_index(i))
&& !is_builtin_key_or_sysvar(&self.account_keys[i])
@ -587,7 +588,7 @@ impl Message {
let mut writable_keys = vec![];
let mut readonly_keys = vec![];
for (i, key) in self.account_keys.iter().enumerate() {
if self.is_writable(i) {
if self.is_maybe_writable(i) {
writable_keys.push(key);
} else {
readonly_keys.push(key);

View File

@ -17,7 +17,7 @@ use {
solana_program::{system_instruction::SystemInstruction, system_program},
sysvar::instructions::{BorrowedAccountMeta, BorrowedInstruction},
},
std::{borrow::Cow, convert::TryFrom},
std::{borrow::Cow, collections::HashSet, convert::TryFrom},
thiserror::Error,
};
@ -31,12 +31,16 @@ pub struct LegacyMessage<'a> {
}
impl<'a> LegacyMessage<'a> {
pub fn new(message: legacy::Message) -> Self {
pub fn new(message: legacy::Message, reserved_account_keys: &HashSet<Pubkey>) -> Self {
let is_writable_account_cache = message
.account_keys
.iter()
.enumerate()
.map(|(i, _key)| message.is_writable(i))
.map(|(i, _key)| {
message.is_writable_index(i)
&& !reserved_account_keys.contains(&message.account_keys[i])
&& !message.demote_program_id(i)
})
.collect::<Vec<_>>();
Self {
message: Cow::Owned(message),
@ -105,23 +109,34 @@ impl SanitizedMessage {
pub fn try_new(
sanitized_msg: SanitizedVersionedMessage,
address_loader: impl AddressLoader,
reserved_account_keys: &HashSet<Pubkey>,
) -> Result<Self, SanitizeMessageError> {
Ok(match sanitized_msg.message {
VersionedMessage::Legacy(message) => {
SanitizedMessage::Legacy(LegacyMessage::new(message))
SanitizedMessage::Legacy(LegacyMessage::new(message, reserved_account_keys))
}
VersionedMessage::V0(message) => {
let loaded_addresses =
address_loader.load_addresses(&message.address_table_lookups)?;
SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses))
SanitizedMessage::V0(v0::LoadedMessage::new(
message,
loaded_addresses,
reserved_account_keys,
))
}
})
}
/// Create a sanitized legacy message
pub fn try_from_legacy_message(message: legacy::Message) -> Result<Self, SanitizeMessageError> {
pub fn try_from_legacy_message(
message: legacy::Message,
reserved_account_keys: &HashSet<Pubkey>,
) -> Result<Self, SanitizeMessageError> {
message.sanitize()?;
Ok(Self::Legacy(LegacyMessage::new(message)))
Ok(Self::Legacy(LegacyMessage::new(
message,
reserved_account_keys,
)))
}
/// Return true if this message contains duplicate account keys
@ -431,7 +446,11 @@ mod tests {
};
assert_eq!(
SanitizedMessage::try_from_legacy_message(legacy_message_with_no_signers).err(),
SanitizedMessage::try_from_legacy_message(
legacy_message_with_no_signers,
&HashSet::default(),
)
.err(),
Some(SanitizeMessageError::IndexOutOfBounds),
);
}
@ -455,6 +474,7 @@ mod tests {
Hash::default(),
instructions,
),
&HashSet::default(),
)
.unwrap();
@ -472,15 +492,18 @@ mod tests {
let key4 = Pubkey::new_unique();
let key5 = Pubkey::new_unique();
let legacy_message = SanitizedMessage::try_from_legacy_message(legacy::Message {
header: MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
let legacy_message = SanitizedMessage::try_from_legacy_message(
legacy::Message {
header: MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![key0, key1, key2, key3],
..legacy::Message::default()
},
account_keys: vec![key0, key1, key2, key3],
..legacy::Message::default()
})
&HashSet::default(),
)
.unwrap();
assert_eq!(legacy_message.num_readonly_accounts(), 2);
@ -499,6 +522,7 @@ mod tests {
writable: vec![key4],
readonly: vec![key5],
},
&HashSet::default(),
));
assert_eq!(v0_message.num_readonly_accounts(), 3);
@ -525,6 +549,7 @@ mod tests {
Hash::default(),
instructions,
),
&HashSet::default(),
)
.unwrap();
@ -556,15 +581,18 @@ mod tests {
let key4 = Pubkey::new_unique();
let key5 = Pubkey::new_unique();
let legacy_message = SanitizedMessage::try_from_legacy_message(legacy::Message {
header: MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
let legacy_message = SanitizedMessage::try_from_legacy_message(
legacy::Message {
header: MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![key0, key1, key2, key3],
..legacy::Message::default()
},
account_keys: vec![key0, key1, key2, key3],
..legacy::Message::default()
})
&HashSet::default(),
)
.unwrap();
match legacy_message {
SanitizedMessage::Legacy(message) => {
@ -596,6 +624,7 @@ mod tests {
writable: vec![key4],
readonly: vec![key5],
},
&HashSet::default(),
));
match v0_message {
SanitizedMessage::V0(message) => {
@ -646,6 +675,7 @@ mod tests {
mock_secp256k1_instr,
],
),
&HashSet::new(),
)
.unwrap();

View File

@ -1,7 +1,7 @@
use {
crate::{
bpf_loader_upgradeable,
message::{legacy::is_builtin_key_or_sysvar, v0, AccountKeys},
message::{v0, AccountKeys},
pubkey::Pubkey,
},
std::{borrow::Cow, collections::HashSet},
@ -55,32 +55,40 @@ impl LoadedAddresses {
}
impl<'a> LoadedMessage<'a> {
pub fn new(message: v0::Message, loaded_addresses: LoadedAddresses) -> Self {
pub fn new(
message: v0::Message,
loaded_addresses: LoadedAddresses,
reserved_account_keys: &HashSet<Pubkey>,
) -> Self {
let mut loaded_message = Self {
message: Cow::Owned(message),
loaded_addresses: Cow::Owned(loaded_addresses),
is_writable_account_cache: Vec::default(),
};
loaded_message.set_is_writable_account_cache();
loaded_message.set_is_writable_account_cache(reserved_account_keys);
loaded_message
}
pub fn new_borrowed(message: &'a v0::Message, loaded_addresses: &'a LoadedAddresses) -> Self {
pub fn new_borrowed(
message: &'a v0::Message,
loaded_addresses: &'a LoadedAddresses,
reserved_account_keys: &HashSet<Pubkey>,
) -> Self {
let mut loaded_message = Self {
message: Cow::Borrowed(message),
loaded_addresses: Cow::Borrowed(loaded_addresses),
is_writable_account_cache: Vec::default(),
};
loaded_message.set_is_writable_account_cache();
loaded_message.set_is_writable_account_cache(reserved_account_keys);
loaded_message
}
fn set_is_writable_account_cache(&mut self) {
fn set_is_writable_account_cache(&mut self, reserved_account_keys: &HashSet<Pubkey>) {
let is_writable_account_cache = self
.account_keys()
.iter()
.enumerate()
.map(|(i, _key)| self.is_writable_internal(i))
.map(|(i, _key)| self.is_writable_internal(i, reserved_account_keys))
.collect::<Vec<_>>();
let _ = std::mem::replace(
&mut self.is_writable_account_cache,
@ -127,10 +135,14 @@ impl<'a> LoadedMessage<'a> {
}
/// Returns true if the account at the specified index was loaded as writable
fn is_writable_internal(&self, key_index: usize) -> bool {
fn is_writable_internal(
&self,
key_index: usize,
reserved_account_keys: &HashSet<Pubkey>,
) -> bool {
if self.is_writable_index(key_index) {
if let Some(key) = self.account_keys().get(key_index) {
return !(is_builtin_key_or_sysvar(key) || self.demote_program_id(key_index));
return !(reserved_account_keys.contains(key) || self.demote_program_id(key_index));
}
}
false
@ -201,6 +213,7 @@ mod tests {
writable: vec![key4],
readonly: vec![key5],
},
&HashSet::default(),
);
(message, [key0, key1, key2, key3, key4, key5])
@ -225,6 +238,7 @@ mod tests {
writable: keys.split_off(2),
readonly: keys,
},
&HashSet::default(),
)
};
@ -257,6 +271,8 @@ mod tests {
#[test]
fn test_is_writable() {
solana_logger::setup();
let reserved_account_keys = HashSet::from_iter([sysvar::clock::id(), system_program::id()]);
let create_message_with_keys = |keys: Vec<Pubkey>| {
LoadedMessage::new(
v0::Message {
@ -272,6 +288,7 @@ mod tests {
writable: keys[2..=2].to_vec(),
readonly: keys[3..].to_vec(),
},
&reserved_account_keys,
)
};
@ -321,6 +338,7 @@ mod tests {
writable: vec![key1, key2],
readonly: vec![],
},
&HashSet::default(),
);
assert!(message.is_writable_index(2));

View File

@ -302,10 +302,11 @@ mod tests {
message::{Message as LegacyMessage, SanitizedMessage},
pubkey::Pubkey,
},
std::collections::HashSet,
};
fn new_sanitized_message(message: LegacyMessage) -> SanitizedMessage {
SanitizedMessage::try_from_legacy_message(message).unwrap()
SanitizedMessage::try_from_legacy_message(message, &HashSet::default()).unwrap()
}
#[test]

View File

@ -124,6 +124,7 @@ mod tests {
instruction::Instruction,
message::Message,
nonce::{self, state::DurableNonce},
reserved_account_keys::ReservedAccountKeys,
signature::{keypair_from_seed, Signer},
system_instruction, system_program,
},
@ -133,7 +134,11 @@ mod tests {
instructions: &[Instruction],
payer: Option<&Pubkey>,
) -> SanitizedMessage {
SanitizedMessage::try_from_legacy_message(Message::new(instructions, payer)).unwrap()
SanitizedMessage::try_from_legacy_message(
Message::new(instructions, payer),
&ReservedAccountKeys::empty_key_set(),
)
.unwrap()
}
#[test]

View File

@ -12,6 +12,7 @@ use {
},
precompiles::verify_if_precompile,
pubkey::Pubkey,
reserved_account_keys::ReservedAccountKeys,
sanitize::Sanitize,
signature::Signature,
simple_vote_transaction_checker::is_simple_vote_transaction,
@ -19,6 +20,7 @@ use {
transaction::{Result, Transaction, TransactionError, VersionedTransaction},
},
solana_program::message::SanitizedVersionedMessage,
std::collections::HashSet,
};
/// Maximum number of accounts that a transaction may lock.
@ -66,17 +68,22 @@ impl SanitizedTransaction {
message_hash: Hash,
is_simple_vote_tx: bool,
address_loader: impl AddressLoader,
reserved_account_keys: &HashSet<Pubkey>,
) -> Result<Self> {
let signatures = tx.signatures;
let SanitizedVersionedMessage { message } = tx.message;
let message = match message {
VersionedMessage::Legacy(message) => {
SanitizedMessage::Legacy(LegacyMessage::new(message))
SanitizedMessage::Legacy(LegacyMessage::new(message, reserved_account_keys))
}
VersionedMessage::V0(message) => {
let loaded_addresses =
address_loader.load_addresses(&message.address_table_lookups)?;
SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses))
SanitizedMessage::V0(v0::LoadedMessage::new(
message,
loaded_addresses,
reserved_account_keys,
))
}
};
@ -96,6 +103,7 @@ impl SanitizedTransaction {
message_hash: impl Into<MessageHash>,
is_simple_vote_tx: Option<bool>,
address_loader: impl AddressLoader,
reserved_account_keys: &HashSet<Pubkey>,
) -> Result<Self> {
let sanitized_versioned_tx = SanitizedVersionedTransaction::try_from(tx)?;
let is_simple_vote_tx = is_simple_vote_tx
@ -109,15 +117,23 @@ impl SanitizedTransaction {
message_hash,
is_simple_vote_tx,
address_loader,
reserved_account_keys,
)
}
pub fn try_from_legacy_transaction(tx: Transaction) -> Result<Self> {
/// Create a sanitized transaction from a legacy transaction
pub fn try_from_legacy_transaction(
tx: Transaction,
reserved_account_keys: &HashSet<Pubkey>,
) -> Result<Self> {
tx.sanitize()?;
Ok(Self {
message_hash: tx.message.hash(),
message: SanitizedMessage::Legacy(LegacyMessage::new(tx.message)),
message: SanitizedMessage::Legacy(LegacyMessage::new(
tx.message,
reserved_account_keys,
)),
is_simple_vote_tx: false,
signatures: tx.signatures,
})
@ -125,7 +141,7 @@ impl SanitizedTransaction {
/// Create a sanitized transaction from a legacy transaction. Used for tests only.
pub fn from_transaction_for_tests(tx: Transaction) -> Self {
Self::try_from_legacy_transaction(tx).unwrap()
Self::try_from_legacy_transaction(tx, &ReservedAccountKeys::empty_key_set()).unwrap()
}
/// Return the first signature for this transaction.
@ -292,7 +308,10 @@ impl SanitizedTransaction {
mod tests {
use {
super::*,
crate::signer::{keypair::Keypair, Signer},
crate::{
reserved_account_keys::ReservedAccountKeys,
signer::{keypair::Keypair, Signer},
},
solana_program::vote::{self, state::Vote},
};
@ -317,6 +336,7 @@ mod tests {
MessageHash::Compute,
None,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
assert!(vote_transaction.is_simple_vote_transaction());
@ -329,6 +349,7 @@ mod tests {
MessageHash::Compute,
Some(false),
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
assert!(!vote_transaction.is_simple_vote_transaction());
@ -343,6 +364,7 @@ mod tests {
MessageHash::Compute,
None,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
assert!(!vote_transaction.is_simple_vote_transaction());
@ -355,6 +377,7 @@ mod tests {
MessageHash::Compute,
Some(true),
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
.unwrap();
assert!(vote_transaction.is_simple_vote_transaction());

View File

@ -491,6 +491,7 @@ mod tests {
rent::Rent,
rent_collector::{RentCollector, RENT_EXEMPT_RENT_EPOCH},
rent_debits::RentDebits,
reserved_account_keys::ReservedAccountKeys,
signature::{Keypair, Signature, Signer},
system_program, system_transaction, sysvar,
transaction::{Result, SanitizedTransaction, Transaction, TransactionError},
@ -570,6 +571,18 @@ mod tests {
features
}
fn new_sanitized_message(message: Message) -> SanitizedMessage {
SanitizedMessage::try_from_legacy_message(message, &ReservedAccountKeys::empty_key_set())
.unwrap()
}
fn new_unchecked_sanitized_message(message: Message) -> SanitizedMessage {
SanitizedMessage::Legacy(LegacyMessage::new(
message,
&ReservedAccountKeys::empty_key_set(),
))
}
fn load_accounts_with_fee(
tx: Transaction,
ka: &[TransactionAccount],
@ -689,7 +702,7 @@ mod tests {
instructions,
);
let message = SanitizedMessage::try_from_legacy_message(tx.message().clone()).unwrap();
let message = new_sanitized_message(tx.message().clone());
let fee = FeeStructure::default().calculate_fee(
&message,
lamports_per_signature,
@ -1217,7 +1230,7 @@ mod tests {
Hash::default(),
);
let message = SanitizedMessage::try_from_legacy_message(tx.message().clone()).unwrap();
let message = new_sanitized_message(tx.message().clone());
let fee = FeeStructure::default().calculate_fee(
&message,
lamports_per_signature,
@ -1441,8 +1454,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mock_bank = TestCallbacks::default();
let mut error_counter = TransactionErrorMetrics::default();
let loaded_programs = LoadedProgramsForTxBatch::default();
@ -1479,8 +1491,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mut mock_bank = TestCallbacks::default();
mock_bank
.accounts_map
@ -1548,8 +1559,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mut mock_bank = TestCallbacks::default();
let mut account_data = AccountSharedData::default();
account_data.set_lamports(200);
@ -1593,8 +1603,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mut mock_bank = TestCallbacks::default();
let mut account_data = AccountSharedData::default();
account_data.set_lamports(200);
@ -1637,8 +1646,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mut mock_bank = TestCallbacks::default();
let mut account_data = AccountSharedData::default();
account_data.set_lamports(200);
@ -1684,8 +1692,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mut mock_bank = TestCallbacks::default();
let mut account_data = AccountSharedData::default();
account_data.set_owner(native_loader::id());
@ -1754,8 +1761,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mut mock_bank = TestCallbacks::default();
let mut account_data = AccountSharedData::default();
account_data.set_executable(true);
@ -1807,8 +1813,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mut mock_bank = TestCallbacks::default();
let mut account_data = AccountSharedData::default();
account_data.set_executable(true);
@ -1868,8 +1873,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mut mock_bank = TestCallbacks::default();
let mut account_data = AccountSharedData::default();
account_data.set_executable(true);
@ -1957,8 +1961,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mut mock_bank = TestCallbacks::default();
let mut account_data = AccountSharedData::default();
account_data.set_executable(true);
@ -2110,8 +2113,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let mut mock_bank = TestCallbacks::default();
let mut account_data = AccountSharedData::default();
account_data.set_executable(true);
@ -2202,8 +2204,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let sanitized_transaction = SanitizedTransaction::new_for_tests(
sanitized_message,
vec![Signature::new_unique()],

View File

@ -160,6 +160,7 @@ mod tests {
native_loader::{self, create_loadable_account_for_test},
pubkey::Pubkey,
rent::Rent,
reserved_account_keys::ReservedAccountKeys,
secp256k1_instruction::new_secp256k1_instruction,
secp256k1_program, system_program,
transaction_context::TransactionContext,
@ -177,7 +178,8 @@ mod tests {
}
fn new_sanitized_message(message: Message) -> SanitizedMessage {
SanitizedMessage::try_from_legacy_message(message).unwrap()
SanitizedMessage::try_from_legacy_message(message, &ReservedAccountKeys::empty_key_set())
.unwrap()
}
#[test]

View File

@ -82,6 +82,7 @@ mod test {
instruction::CompiledInstruction,
message::{LegacyMessage, Message, MessageHeader, SanitizedMessage},
rent::Rent,
reserved_account_keys::ReservedAccountKeys,
signature::{Keypair, Signer},
transaction::TransactionError,
transaction_context::TransactionContext,
@ -114,8 +115,10 @@ mod test {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(
message,
&ReservedAccountKeys::empty_key_set(),
));
let transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
@ -166,8 +169,10 @@ mod test {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(
message,
&ReservedAccountKeys::empty_key_set(),
));
let transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),

View File

@ -995,6 +995,7 @@ mod tests {
fee_calculator::FeeCalculator,
message::{LegacyMessage, Message, MessageHeader},
rent_debits::RentDebits,
reserved_account_keys::ReservedAccountKeys,
signature::{Keypair, Signature},
sysvar::{self, rent::Rent},
transaction::{SanitizedTransaction, Transaction, TransactionError},
@ -1007,6 +1008,13 @@ mod tests {
},
};
fn new_unchecked_sanitized_message(message: Message) -> SanitizedMessage {
SanitizedMessage::Legacy(LegacyMessage::new(
message,
&ReservedAccountKeys::empty_key_set(),
))
}
struct TestForkGraph {}
impl ForkGraph for TestForkGraph {
@ -1711,8 +1719,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let loaded_programs = LoadedProgramsForTxBatch::default();
let mock_bank = MockBankCallback::default();
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
@ -1836,8 +1843,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let loaded_programs = LoadedProgramsForTxBatch::default();
let mock_bank = MockBankCallback::default();
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
@ -1954,8 +1960,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let sanitized_transaction_1 = SanitizedTransaction::new_for_tests(
sanitized_message,
@ -1985,8 +1990,7 @@ mod tests {
recent_blockhash: Hash::default(),
};
let legacy = LegacyMessage::new(message);
let sanitized_message = SanitizedMessage::Legacy(legacy);
let sanitized_message = new_unchecked_sanitized_message(message);
let sanitized_transaction_2 = SanitizedTransaction::new_for_tests(
sanitized_message,

View File

@ -4,6 +4,7 @@ use {
instruction::{AccountMeta, CompiledInstruction},
message::{Message, MessageHeader},
pubkey::Pubkey,
reserved_account_keys::ReservedAccountKeys,
signature::Signature,
transaction::{SanitizedTransaction, Transaction},
},
@ -126,7 +127,11 @@ impl SanitizedTransactionBuilder {
message,
};
SanitizedTransaction::try_from_legacy_transaction(transaction).unwrap()
SanitizedTransaction::try_from_legacy_transaction(
transaction,
&ReservedAccountKeys::new_all_activated().active,
)
.unwrap()
}
}

View File

@ -19,6 +19,7 @@ use {
AccountKeys, Message, MessageHeader, VersionedMessage,
},
pubkey::Pubkey,
reserved_account_keys::ReservedAccountKeys,
signature::Signature,
transaction::{
Result as TransactionResult, Transaction, TransactionError, TransactionVersion,
@ -980,12 +981,16 @@ impl VersionedTransactionWithStatusMeta {
show_rewards: bool,
) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
let version = self.validate_version(max_supported_transaction_version)?;
let reserved_account_keys = ReservedAccountKeys::new_all_activated();
let account_keys = match &self.transaction.message {
VersionedMessage::Legacy(message) => parse_legacy_message_accounts(message),
VersionedMessage::V0(message) => {
let loaded_message =
LoadedMessage::new_borrowed(message, &self.meta.loaded_addresses);
let loaded_message = LoadedMessage::new_borrowed(
message,
&self.meta.loaded_addresses,
&reserved_account_keys.active,
);
parse_v0_message_accounts(&loaded_message)
}
};
@ -1228,8 +1233,13 @@ impl EncodableWithMeta for v0::Message {
meta: &TransactionStatusMeta,
) -> Self::Encoded {
if encoding == UiTransactionEncoding::JsonParsed {
let reserved_account_keys = ReservedAccountKeys::new_all_activated();
let account_keys = AccountKeys::new(&self.account_keys, Some(&meta.loaded_addresses));
let loaded_message = LoadedMessage::new_borrowed(self, &meta.loaded_addresses);
let loaded_message = LoadedMessage::new_borrowed(
self,
&meta.loaded_addresses,
&reserved_account_keys.active,
);
UiMessage::Parsed(UiParsedMessage {
account_keys: parse_v0_message_accounts(&loaded_message),
recent_blockhash: self.recent_blockhash.to_string(),

View File

@ -54,6 +54,7 @@ mod test {
solana_sdk::{
message::{v0, v0::LoadedAddresses, MessageHeader},
pubkey::Pubkey,
reserved_account_keys::ReservedAccountKeys,
},
};
@ -126,6 +127,7 @@ mod test {
writable: vec![pubkey4],
readonly: vec![pubkey5],
},
&ReservedAccountKeys::empty_key_set(),
);
assert_eq!(