Demote write locks on transaction program ids (#19593)
* Add feature * Demote write lock on program ids * Fixup bpf tests * Update MappedMessage::is_writable * Comma nit * Review comments
This commit is contained in:
@ -353,7 +353,7 @@ impl Message {
|
||||
self.program_position(i).is_some()
|
||||
}
|
||||
|
||||
pub fn is_writable(&self, i: usize) -> bool {
|
||||
pub fn is_writable(&self, i: usize, demote_program_write_locks: bool) -> bool {
|
||||
(i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts)
|
||||
as usize
|
||||
|| (i >= self.header.num_required_signatures as usize
|
||||
@ -363,6 +363,7 @@ impl Message {
|
||||
let key = self.account_keys[i];
|
||||
sysvar::is_sysvar_id(&key) || BUILTIN_PROGRAMS_KEYS.contains(&key)
|
||||
}
|
||||
&& !(demote_program_write_locks && self.is_key_called_as_program(i))
|
||||
}
|
||||
|
||||
pub fn is_signer(&self, i: usize) -> bool {
|
||||
@ -374,7 +375,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_writable(i, /*demote_program_write_locks=*/ true) {
|
||||
writable_keys.push(key);
|
||||
} else {
|
||||
readonly_keys.push(key);
|
||||
@ -412,7 +413,8 @@ impl Message {
|
||||
for account_index in &instruction.accounts {
|
||||
let account_index = *account_index as usize;
|
||||
let is_signer = self.is_signer(account_index);
|
||||
let is_writable = self.is_writable(account_index);
|
||||
let is_writable =
|
||||
self.is_writable(account_index, /*demote_program_write_locks=*/ true);
|
||||
let mut meta_byte = 0;
|
||||
if is_signer {
|
||||
meta_byte |= 1 << Self::IS_SIGNER_BIT;
|
||||
@ -866,12 +868,13 @@ mod tests {
|
||||
recent_blockhash: Hash::default(),
|
||||
instructions: vec![],
|
||||
};
|
||||
assert!(message.is_writable(0));
|
||||
assert!(!message.is_writable(1));
|
||||
assert!(!message.is_writable(2));
|
||||
assert!(message.is_writable(3));
|
||||
assert!(message.is_writable(4));
|
||||
assert!(!message.is_writable(5));
|
||||
let demote_program_write_locks = true;
|
||||
assert!(message.is_writable(0, demote_program_write_locks));
|
||||
assert!(!message.is_writable(1, demote_program_write_locks));
|
||||
assert!(!message.is_writable(2, demote_program_write_locks));
|
||||
assert!(message.is_writable(3, demote_program_write_locks));
|
||||
assert!(message.is_writable(4, demote_program_write_locks));
|
||||
assert!(!message.is_writable(5, demote_program_write_locks));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -4,7 +4,7 @@ use {
|
||||
pubkey::Pubkey,
|
||||
sysvar,
|
||||
},
|
||||
std::collections::HashSet,
|
||||
std::{collections::HashSet, convert::TryFrom},
|
||||
};
|
||||
|
||||
/// Combination of a version #0 message and its mapped addresses
|
||||
@ -96,20 +96,32 @@ impl MappedMessage {
|
||||
}
|
||||
|
||||
/// Returns true if the account at the specified index was loaded as writable
|
||||
pub fn is_writable(&self, key_index: usize) -> bool {
|
||||
pub fn is_writable(&self, key_index: usize, demote_program_write_locks: bool) -> bool {
|
||||
if self.is_writable_index(key_index) {
|
||||
if let Some(key) = self.get_account_key(key_index) {
|
||||
return !(sysvar::is_sysvar_id(key) || BUILTIN_PROGRAMS_KEYS.contains(key));
|
||||
return !(sysvar::is_sysvar_id(key) || BUILTIN_PROGRAMS_KEYS.contains(key)
|
||||
|| (demote_program_write_locks && self.is_key_called_as_program(key_index)));
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the account at the specified index is called as a program by an instruction
|
||||
pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
|
||||
if let Ok(key_index) = u8::try_from(key_index) {
|
||||
self.message.instructions
|
||||
.iter()
|
||||
.any(|ix| ix.program_id_index == key_index)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{message::MessageHeader, system_program, sysvar};
|
||||
use crate::{instruction::CompiledInstruction, message::MessageHeader, system_program, sysvar};
|
||||
use itertools::Itertools;
|
||||
|
||||
fn create_test_mapped_message() -> (MappedMessage, [Pubkey; 6]) {
|
||||
@ -236,10 +248,42 @@ mod tests {
|
||||
|
||||
mapped_msg.message.account_keys[0] = sysvar::clock::id();
|
||||
assert!(mapped_msg.is_writable_index(0));
|
||||
assert!(!mapped_msg.is_writable(0));
|
||||
assert!(!mapped_msg.is_writable(0, /*demote_program_write_locks=*/ true));
|
||||
|
||||
mapped_msg.message.account_keys[0] = system_program::id();
|
||||
assert!(mapped_msg.is_writable_index(0));
|
||||
assert!(!mapped_msg.is_writable(0));
|
||||
assert!(!mapped_msg.is_writable(0, /*demote_program_write_locks=*/ true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_demote_writable_program() {
|
||||
let key0 = Pubkey::new_unique();
|
||||
let key1 = Pubkey::new_unique();
|
||||
let key2 = Pubkey::new_unique();
|
||||
let mapped_msg = MappedMessage {
|
||||
message: v0::Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 1,
|
||||
num_readonly_signed_accounts: 0,
|
||||
num_readonly_unsigned_accounts: 0,
|
||||
},
|
||||
account_keys: vec![key0],
|
||||
instructions: vec![
|
||||
CompiledInstruction {
|
||||
program_id_index: 2,
|
||||
accounts: vec![1],
|
||||
data: vec![],
|
||||
}
|
||||
],
|
||||
..v0::Message::default()
|
||||
},
|
||||
mapped_addresses: MappedAddresses {
|
||||
writable: vec![key1, key2],
|
||||
readonly: vec![],
|
||||
},
|
||||
};
|
||||
|
||||
assert!(mapped_msg.is_writable_index(2));
|
||||
assert!(!mapped_msg.is_writable(2, /*demote_program_write_locks=*/ true));
|
||||
}
|
||||
}
|
||||
|
@ -175,12 +175,9 @@ impl SanitizedMessage {
|
||||
/// Returns true if the account at the specified index is invoked as a
|
||||
/// program in this message.
|
||||
pub fn is_invoked(&self, key_index: usize) -> bool {
|
||||
if let Ok(key_index) = u8::try_from(key_index) {
|
||||
self.instructions()
|
||||
.iter()
|
||||
.any(|ix| ix.program_id_index == key_index)
|
||||
} else {
|
||||
false
|
||||
match self {
|
||||
Self::Legacy(message) => message.is_key_called_as_program(key_index),
|
||||
Self::V0(message) => message.is_key_called_as_program(key_index),
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,10 +189,10 @@ impl SanitizedMessage {
|
||||
|
||||
/// Returns true if the account at the specified index is writable by the
|
||||
/// instructions in this message.
|
||||
pub fn is_writable(&self, index: usize) -> bool {
|
||||
pub fn is_writable(&self, index: usize, demote_program_write_locks: bool) -> bool {
|
||||
match self {
|
||||
Self::Legacy(message) => message.is_writable(index),
|
||||
Self::V0(message) => message.is_writable(index),
|
||||
Self::Legacy(message) => message.is_writable(index, demote_program_write_locks),
|
||||
Self::V0(message) => message.is_writable(index, demote_program_write_locks),
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,7 +216,7 @@ impl SanitizedMessage {
|
||||
// 67..69 - data len - u16
|
||||
// 69..data_len - data
|
||||
#[allow(clippy::integer_arithmetic)]
|
||||
pub fn serialize_instructions(&self) -> Vec<u8> {
|
||||
pub fn serialize_instructions(&self, demote_program_write_locks: bool) -> Vec<u8> {
|
||||
// 64 bytes is a reasonable guess, calculating exactly is slower in benchmarks
|
||||
let mut data = Vec::with_capacity(self.instructions().len() * (32 * 2));
|
||||
append_u16(&mut data, self.instructions().len() as u16);
|
||||
@ -234,7 +231,7 @@ impl SanitizedMessage {
|
||||
for account_index in &instruction.accounts {
|
||||
let account_index = *account_index as usize;
|
||||
let is_signer = self.is_signer(account_index);
|
||||
let is_writable = self.is_writable(account_index);
|
||||
let is_writable = self.is_writable(account_index, demote_program_write_locks);
|
||||
let mut account_meta = InstructionsSysvarAccountMeta::NONE;
|
||||
if is_signer {
|
||||
account_meta |= InstructionsSysvarAccountMeta::IS_SIGNER;
|
||||
@ -440,9 +437,10 @@ mod tests {
|
||||
),
|
||||
];
|
||||
|
||||
let demote_program_write_locks = true;
|
||||
let message = Message::new(&instructions, Some(&id1));
|
||||
let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
|
||||
let serialized = sanitized_message.serialize_instructions();
|
||||
let serialized = sanitized_message.serialize_instructions(demote_program_write_locks);
|
||||
|
||||
// assert that SanitizedMessage::serialize_instructions has the same behavior as the
|
||||
// deprecated Message::serialize_instructions method
|
||||
|
Reference in New Issue
Block a user