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:
Tyera Eulberg
2021-09-03 21:05:30 -06:00
committed by GitHub
parent 7578db7ee3
commit decec3cd8b
20 changed files with 297 additions and 177 deletions

View File

@ -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]

View File

@ -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));
}
}

View File

@ -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