Add CLI support for versioned transactions (#23606)

This commit is contained in:
Justin Starry
2022-03-17 11:43:04 +08:00
committed by GitHub
parent 330d6db19a
commit 0eccacbd5b
10 changed files with 538 additions and 238 deletions

View File

@ -2,7 +2,7 @@ use {
crate::{
hash::Hash,
instruction::CompiledInstruction,
message::{legacy::Message as LegacyMessage, MessageHeader},
message::{legacy::Message as LegacyMessage, v0::MessageAddressTableLookup, MessageHeader},
pubkey::Pubkey,
sanitize::{Sanitize, SanitizeError},
short_vec,
@ -50,6 +50,55 @@ impl VersionedMessage {
}
}
pub fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]> {
match self {
Self::Legacy(_) => None,
Self::V0(message) => Some(&message.address_table_lookups),
}
}
/// Returns true if the account at the specified index signed this
/// message.
pub fn is_signer(&self, index: usize) -> bool {
index < usize::from(self.header().num_required_signatures)
}
/// Returns true if the account at the specified index is writable by the
/// instructions in this message. Since dynamically loaded addresses can't
/// have write locks demoted without loading addresses, this shouldn't be
/// used in the runtime.
pub fn is_maybe_writable(&self, index: usize) -> bool {
match self {
Self::Legacy(message) => message.is_writable(index),
Self::V0(message) => message.is_maybe_writable(index),
}
}
/// Returns true if the account at the specified index is an input to some
/// program instruction in this message.
fn is_key_passed_to_program(&self, key_index: usize) -> bool {
if let Ok(key_index) = u8::try_from(key_index) {
self.instructions()
.iter()
.any(|ix| ix.accounts.contains(&key_index))
} else {
false
}
}
pub fn is_invoked(&self, key_index: usize) -> bool {
match self {
Self::Legacy(message) => message.is_key_called_as_program(key_index),
Self::V0(message) => message.is_key_called_as_program(key_index),
}
}
/// Returns true if the account at the specified index is not invoked as a
/// program or, if invoked, is passed to a program.
pub fn is_non_loader_key(&self, key_index: usize) -> bool {
!self.is_invoked(key_index) || self.is_key_passed_to_program(key_index)
}
pub fn recent_blockhash(&self) -> &Hash {
match self {
Self::Legacy(message) => &message.recent_blockhash,
@ -64,6 +113,8 @@ impl VersionedMessage {
}
}
/// Program instructions that will be executed in sequence and committed in
/// one atomic transaction if all succeed.
pub fn instructions(&self) -> &[CompiledInstruction] {
match self {
Self::Legacy(message) => &message.instructions,

View File

@ -10,12 +10,13 @@
//! [future message format]: https://docs.solana.com/proposals/transactions-v2
use crate::{
bpf_loader_upgradeable,
hash::Hash,
instruction::CompiledInstruction,
message::{MessageHeader, MESSAGE_VERSION_PREFIX},
message::{legacy::BUILTIN_PROGRAMS_KEYS, MessageHeader, MESSAGE_VERSION_PREFIX},
pubkey::Pubkey,
sanitize::{Sanitize, SanitizeError},
short_vec,
short_vec, sysvar,
};
mod loaded;
@ -138,6 +139,70 @@ impl Message {
pub fn serialize(&self) -> Vec<u8> {
bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap()
}
/// 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.instructions
.iter()
.any(|ix| ix.program_id_index == key_index)
} else {
false
}
}
/// 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, key_index: usize) -> bool {
let header = &self.header;
let num_account_keys = self.account_keys.len();
let num_signed_accounts = usize::from(header.num_required_signatures);
if key_index >= num_account_keys {
let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
let num_writable_dynamic_addresses = self
.address_table_lookups
.iter()
.map(|lookup| lookup.writable_indexes.len())
.sum();
loaded_addresses_index < num_writable_dynamic_addresses
} else if key_index >= num_signed_accounts {
let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
let num_writable_unsigned_accounts = num_unsigned_accounts
.saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
unsigned_account_index < num_writable_unsigned_accounts
} else {
let num_writable_signed_accounts = num_signed_accounts
.saturating_sub(usize::from(header.num_readonly_signed_accounts));
key_index < num_writable_signed_accounts
}
}
/// Returns true if any static account key is the bpf upgradeable loader
fn is_upgradeable_loader_in_static_keys(&self) -> bool {
self.account_keys
.iter()
.any(|&key| key == bpf_loader_upgradeable::id())
}
/// Returns true if the account at the specified index was requested as writable.
/// Before loading addresses, we can't demote write locks for dynamically loaded
/// addresses so this should not be used by the runtime.
pub fn is_maybe_writable(&self, key_index: usize) -> bool {
self.is_writable_index(key_index)
&& !{
// demote reserved ids
self.account_keys
.get(key_index)
.map(|key| sysvar::is_sysvar_id(key) || BUILTIN_PROGRAMS_KEYS.contains(key))
.unwrap_or_default()
}
&& !{
// demote program ids
self.is_key_called_as_program(key_index)
&& !self.is_upgradeable_loader_in_static_keys()
}
}
}
#[cfg(test)]