Add sanitized types for use in banking stage (#25067)

This commit is contained in:
Justin Starry 2022-05-11 00:30:48 +08:00 committed by GitHub
parent 85f4a3e4c4
commit e3bdc38f0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 187 additions and 17 deletions

View File

@ -1740,12 +1740,11 @@ impl BankingStage {
return None;
}
let tx = SanitizedTransaction::try_create(
deserialized_packet.versioned_transaction().clone(),
let tx = SanitizedTransaction::try_new(
deserialized_packet.transaction().clone(),
*deserialized_packet.message_hash(),
Some(deserialized_packet.is_simple_vote()),
deserialized_packet.is_simple_vote(),
address_loader,
feature_set.is_active(&feature_set::require_static_program_ids_in_transaction::ID),
)
.ok()?;
tx.verify_precompiles(feature_set).ok()?;

View File

@ -4,10 +4,11 @@ use {
solana_runtime::bank::Bank,
solana_sdk::{
hash::Hash,
message::{Message, VersionedMessage},
message::{Message, SanitizedVersionedMessage},
sanitize::SanitizeError,
short_vec::decode_shortu16_len,
signature::Signature,
transaction::{Transaction, VersionedTransaction},
transaction::{SanitizedVersionedTransaction, Transaction, VersionedTransaction},
},
std::{
cmp::Ordering,
@ -28,12 +29,14 @@ pub enum DeserializedPacketError {
DeserializationError(#[from] bincode::Error),
#[error("overflowed on signature size {0}")]
SignatureOverflowed(usize),
#[error("packet failed sanitization {0}")]
SanitizeError(#[from] SanitizeError),
}
#[derive(Debug, Default, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq)]
pub struct ImmutableDeserializedPacket {
original_packet: Packet,
versioned_transaction: VersionedTransaction,
transaction: SanitizedVersionedTransaction,
message_hash: Hash,
is_simple_vote: bool,
fee_per_cu: u64,
@ -44,8 +47,8 @@ impl ImmutableDeserializedPacket {
&self.original_packet
}
pub fn versioned_transaction(&self) -> &VersionedTransaction {
&self.versioned_transaction
pub fn transaction(&self) -> &SanitizedVersionedTransaction {
&self.transaction
}
pub fn sender_stake(&self) -> u64 {
@ -67,7 +70,7 @@ impl ImmutableDeserializedPacket {
/// Holds deserialized messages, as well as computed message_hash and other things needed to create
/// SanitizedTransaction
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeserializedPacket {
immutable_section: Rc<ImmutableDeserializedPacket>,
pub forwarded: bool,
@ -93,19 +96,20 @@ impl DeserializedPacket {
) -> Result<Self, DeserializedPacketError> {
let versioned_transaction: VersionedTransaction =
limited_deserialize(&packet.data[0..packet.meta.size])?;
let sanitized_transaction = SanitizedVersionedTransaction::try_from(versioned_transaction)?;
let message_bytes = packet_message(&packet)?;
let message_hash = Message::hash_raw_message(message_bytes);
let is_simple_vote = packet.meta.is_simple_vote_tx();
let fee_per_cu = fee_per_cu.unwrap_or_else(|| {
bank.as_ref()
.map(|bank| compute_fee_per_cu(&versioned_transaction.message, bank))
.map(|bank| compute_fee_per_cu(sanitized_transaction.get_message(), bank))
.unwrap_or(0)
});
Ok(Self {
immutable_section: Rc::new(ImmutableDeserializedPacket {
original_packet: packet,
versioned_transaction,
transaction: sanitized_transaction,
message_hash,
is_simple_vote,
fee_per_cu,
@ -369,7 +373,7 @@ pub fn packet_message(packet: &Packet) -> Result<&[u8], DeserializedPacketError>
}
/// Computes `(addition_fee + base_fee / requested_cu)` for `deserialized_packet`
fn compute_fee_per_cu(_message: &VersionedMessage, _bank: &Bank) -> u64 {
fn compute_fee_per_cu(_message: &SanitizedVersionedMessage, _bank: &Bank) -> u64 {
1
}

View File

@ -15,8 +15,11 @@ use {
std::fmt,
};
mod sanitized;
pub mod v0;
pub use sanitized::*;
/// Bit mask that indicates whether a serialized message is versioned.
pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;

View File

@ -0,0 +1,46 @@
use {
super::VersionedMessage,
crate::{instruction::CompiledInstruction, pubkey::Pubkey, sanitize::SanitizeError},
};
/// Wraps a sanitized `VersionedMessage` to provide a safe API
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SanitizedVersionedMessage {
pub message: VersionedMessage,
}
impl TryFrom<VersionedMessage> for SanitizedVersionedMessage {
type Error = SanitizeError;
fn try_from(message: VersionedMessage) -> Result<Self, Self::Error> {
Self::try_new(message)
}
}
impl SanitizedVersionedMessage {
pub fn try_new(message: VersionedMessage) -> Result<Self, SanitizeError> {
message.sanitize(true /* require_static_program_ids */)?;
Ok(Self { message })
}
/// Program instructions that will be executed in sequence and committed in
/// one atomic transaction if all succeed.
pub fn instructions(&self) -> &[CompiledInstruction] {
self.message.instructions()
}
/// Program instructions iterator which includes each instruction's program
/// id.
pub fn program_instructions_iter(
&self,
) -> impl Iterator<Item = (&Pubkey, &CompiledInstruction)> {
self.message.instructions().iter().map(move |ix| {
(
self.message
.static_account_keys()
.get(usize::from(ix.program_id_index))
.expect("program id index is sanitized"),
ix,
)
})
}
}

View File

@ -1,5 +1,7 @@
#![cfg(feature = "full")]
use {
super::SanitizedVersionedTransaction,
crate::{
hash::Hash,
message::{
@ -13,6 +15,7 @@ use {
solana_sdk::feature_set,
transaction::{Result, Transaction, TransactionError, VersionedTransaction},
},
solana_program::message::SanitizedVersionedMessage,
std::sync::Arc,
};
@ -72,9 +75,37 @@ impl From<Hash> for MessageHash {
}
impl SanitizedTransaction {
/// Create a sanitized transaction from an unsanitized transaction.
/// If the input transaction uses address tables, attempt to lookup
/// the address for each table index.
/// Create a sanitized transaction from a sanitized versioned transaction.
/// If the input transaction uses address tables, attempt to lookup the
/// address for each table index.
pub fn try_new(
tx: SanitizedVersionedTransaction,
message_hash: Hash,
is_simple_vote_tx: bool,
address_loader: impl AddressLoader,
) -> Result<Self> {
let signatures = tx.signatures;
let SanitizedVersionedMessage { message } = tx.message;
let message = match message {
VersionedMessage::Legacy(message) => SanitizedMessage::Legacy(message),
VersionedMessage::V0(message) => {
let loaded_addresses =
address_loader.load_addresses(&message.address_table_lookups)?;
SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses))
}
};
Ok(Self {
message,
message_hash,
is_simple_vote_tx,
signatures,
})
}
/// Create a sanitized transaction from an un-sanitized versioned
/// transaction. If the input transaction uses address tables, attempt to
/// lookup the address for each table index.
pub fn try_create(
tx: VersionedTransaction,
message_hash: impl Into<MessageHash>,

View File

@ -17,6 +17,10 @@ use {
std::cmp::Ordering,
};
mod sanitized;
pub use sanitized::*;
/// Type that serializes to the string "legacy"
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -95,7 +99,11 @@ impl VersionedTransaction {
require_static_program_ids: bool,
) -> std::result::Result<(), SanitizeError> {
self.message.sanitize(require_static_program_ids)?;
self.sanitize_signatures()?;
Ok(())
}
pub(crate) fn sanitize_signatures(&self) -> std::result::Result<(), SanitizeError> {
let num_required_signatures = usize::from(self.message.header().num_required_signatures);
match num_required_signatures.cmp(&self.signatures.len()) {
Ordering::Greater => Err(SanitizeError::IndexOutOfBounds),

View File

@ -0,0 +1,79 @@
use {
super::VersionedTransaction,
crate::{sanitize::SanitizeError, signature::Signature},
solana_program::message::SanitizedVersionedMessage,
};
/// Wraps a sanitized `VersionedTransaction` to provide a safe API
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SanitizedVersionedTransaction {
/// List of signatures
pub(crate) signatures: Vec<Signature>,
/// Message to sign.
pub(crate) message: SanitizedVersionedMessage,
}
impl TryFrom<VersionedTransaction> for SanitizedVersionedTransaction {
type Error = SanitizeError;
fn try_from(tx: VersionedTransaction) -> Result<Self, Self::Error> {
Self::try_new(tx)
}
}
impl SanitizedVersionedTransaction {
pub fn try_new(tx: VersionedTransaction) -> Result<Self, SanitizeError> {
tx.sanitize_signatures()?;
Ok(Self {
signatures: tx.signatures,
message: SanitizedVersionedMessage::try_from(tx.message)?,
})
}
pub fn get_message(&self) -> &SanitizedVersionedMessage {
&self.message
}
}
#[cfg(test)]
mod tests {
use {
super::*,
solana_program::{
hash::Hash,
message::{v0, VersionedMessage},
pubkey::Pubkey,
},
};
#[test]
fn test_try_new_with_invalid_signatures() {
let tx = VersionedTransaction {
signatures: vec![],
message: VersionedMessage::V0(
v0::Message::try_compile(&Pubkey::new_unique(), &[], &[], Hash::default()).unwrap(),
),
};
assert_eq!(
SanitizedVersionedTransaction::try_new(tx),
Err(SanitizeError::IndexOutOfBounds)
);
}
#[test]
fn test_try_new() {
let mut message =
v0::Message::try_compile(&Pubkey::new_unique(), &[], &[], Hash::default()).unwrap();
message.header.num_readonly_signed_accounts += 1;
let tx = VersionedTransaction {
signatures: vec![Signature::default()],
message: VersionedMessage::V0(message),
};
assert_eq!(
SanitizedVersionedTransaction::try_new(tx),
Err(SanitizeError::InvalidValue)
);
}
}