Add sanitized types for use in banking stage (#25067)
This commit is contained in:
parent
85f4a3e4c4
commit
e3bdc38f0a
|
@ -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()?;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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>,
|
||||
|
|
|
@ -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),
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue