Fail tx sanitization when ix program id uses lookup table (#25035)
* Fail tx sanitization when ix program id uses lookup table * feedback
This commit is contained in:
parent
cb6cd5d60f
commit
082502d4f3
|
@ -1742,6 +1742,7 @@ impl BankingStage {
|
||||||
*deserialized_packet.message_hash(),
|
*deserialized_packet.message_hash(),
|
||||||
Some(deserialized_packet.is_simple_vote()),
|
Some(deserialized_packet.is_simple_vote()),
|
||||||
address_loader,
|
address_loader,
|
||||||
|
feature_set.is_active(&feature_set::require_static_program_ids_in_transaction::ID),
|
||||||
)
|
)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
tx.verify_precompiles(feature_set).ok()?;
|
tx.verify_precompiles(feature_set).ok()?;
|
||||||
|
@ -3667,6 +3668,7 @@ mod tests {
|
||||||
MessageHash::Compute,
|
MessageHash::Compute,
|
||||||
Some(false),
|
Some(false),
|
||||||
bank.as_ref(),
|
bank.as_ref(),
|
||||||
|
true, // require_static_program_ids
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ fn bench_gpusigverify(bencher: &mut Bencher) {
|
||||||
message_hash,
|
message_hash,
|
||||||
None,
|
None,
|
||||||
SimpleAddressLoader::Disabled,
|
SimpleAddressLoader::Disabled,
|
||||||
|
true, // require_static_program_ids
|
||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
@ -81,6 +82,7 @@ fn bench_cpusigverify(bencher: &mut Bencher) {
|
||||||
message_hash,
|
message_hash,
|
||||||
None,
|
None,
|
||||||
SimpleAddressLoader::Disabled,
|
SimpleAddressLoader::Disabled,
|
||||||
|
true, // require_static_program_ids
|
||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
|
|
@ -1014,6 +1014,7 @@ mod tests {
|
||||||
message_hash,
|
message_hash,
|
||||||
None,
|
None,
|
||||||
SimpleAddressLoader::Disabled,
|
SimpleAddressLoader::Disabled,
|
||||||
|
true, // require_static_program_ids
|
||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
|
|
@ -243,6 +243,7 @@ fn output_slot(
|
||||||
MessageHash::Compute,
|
MessageHash::Compute,
|
||||||
None,
|
None,
|
||||||
SimpleAddressLoader::Disabled,
|
SimpleAddressLoader::Disabled,
|
||||||
|
true, // require_static_program_ids
|
||||||
);
|
);
|
||||||
|
|
||||||
match sanitize_result {
|
match sanitize_result {
|
||||||
|
@ -813,6 +814,7 @@ fn compute_slot_cost(blockstore: &Blockstore, slot: Slot) -> Result<(), String>
|
||||||
MessageHash::Compute,
|
MessageHash::Compute,
|
||||||
None,
|
None,
|
||||||
SimpleAddressLoader::Disabled,
|
SimpleAddressLoader::Disabled,
|
||||||
|
true, // require_static_program_ids
|
||||||
)
|
)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
warn!("Failed to compute cost of transaction: {:?}", err);
|
warn!("Failed to compute cost of transaction: {:?}", err);
|
||||||
|
|
|
@ -35,7 +35,6 @@ use {
|
||||||
genesis_config::{GenesisConfig, DEFAULT_GENESIS_ARCHIVE, DEFAULT_GENESIS_FILE},
|
genesis_config::{GenesisConfig, DEFAULT_GENESIS_ARCHIVE, DEFAULT_GENESIS_FILE},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
sanitize::Sanitize,
|
|
||||||
signature::{Keypair, Signature, Signer},
|
signature::{Keypair, Signature, Signer},
|
||||||
timing::timestamp,
|
timing::timestamp,
|
||||||
transaction::VersionedTransaction,
|
transaction::VersionedTransaction,
|
||||||
|
@ -1979,7 +1978,12 @@ impl Blockstore {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|entry| entry.transactions)
|
.flat_map(|entry| entry.transactions)
|
||||||
.map(|transaction| {
|
.map(|transaction| {
|
||||||
if let Err(err) = transaction.sanitize() {
|
if let Err(err) = transaction.sanitize(
|
||||||
|
// Don't enable additional sanitization checks until
|
||||||
|
// all clusters have activated the static program id
|
||||||
|
// feature gate so that bigtable upload isn't affected
|
||||||
|
false, // require_static_program_ids
|
||||||
|
) {
|
||||||
warn!(
|
warn!(
|
||||||
"Blockstore::get_block sanitize failed: {:?}, \
|
"Blockstore::get_block sanitize failed: {:?}, \
|
||||||
slot: {:?}, \
|
slot: {:?}, \
|
||||||
|
@ -2358,7 +2362,9 @@ impl Blockstore {
|
||||||
.cloned()
|
.cloned()
|
||||||
.flat_map(|entry| entry.transactions)
|
.flat_map(|entry| entry.transactions)
|
||||||
.map(|transaction| {
|
.map(|transaction| {
|
||||||
if let Err(err) = transaction.sanitize() {
|
if let Err(err) = transaction.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
) {
|
||||||
warn!(
|
warn!(
|
||||||
"Blockstore::find_transaction_in_slot sanitize failed: {:?}, \
|
"Blockstore::find_transaction_in_slot sanitize failed: {:?}, \
|
||||||
slot: {:?}, \
|
slot: {:?}, \
|
||||||
|
|
|
@ -4291,8 +4291,14 @@ fn sanitize_transaction(
|
||||||
transaction: VersionedTransaction,
|
transaction: VersionedTransaction,
|
||||||
address_loader: impl AddressLoader,
|
address_loader: impl AddressLoader,
|
||||||
) -> Result<SanitizedTransaction> {
|
) -> Result<SanitizedTransaction> {
|
||||||
SanitizedTransaction::try_create(transaction, MessageHash::Compute, None, address_loader)
|
SanitizedTransaction::try_create(
|
||||||
.map_err(|err| Error::invalid_params(format!("invalid transaction: {}", err)))
|
transaction,
|
||||||
|
MessageHash::Compute,
|
||||||
|
None,
|
||||||
|
address_loader,
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
|
.map_err(|err| Error::invalid_params(format!("invalid transaction: {}", err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn create_validator_exit(exit: &Arc<AtomicBool>) -> Arc<RwLock<Exit>> {
|
pub(crate) fn create_validator_exit(exit: &Arc<AtomicBool>) -> Arc<RwLock<Exit>> {
|
||||||
|
|
|
@ -315,6 +315,7 @@ pub(crate) mod tests {
|
||||||
MessageHash::Compute,
|
MessageHash::Compute,
|
||||||
None,
|
None,
|
||||||
SimpleAddressLoader::Disabled,
|
SimpleAddressLoader::Disabled,
|
||||||
|
true, // require_static_program_ids
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -3800,7 +3800,16 @@ impl Bank {
|
||||||
pub fn prepare_entry_batch(&self, txs: Vec<VersionedTransaction>) -> Result<TransactionBatch> {
|
pub fn prepare_entry_batch(&self, txs: Vec<VersionedTransaction>) -> Result<TransactionBatch> {
|
||||||
let sanitized_txs = txs
|
let sanitized_txs = txs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tx| SanitizedTransaction::try_create(tx, MessageHash::Compute, None, self))
|
.map(|tx| {
|
||||||
|
SanitizedTransaction::try_create(
|
||||||
|
tx,
|
||||||
|
MessageHash::Compute,
|
||||||
|
None,
|
||||||
|
self,
|
||||||
|
self.feature_set
|
||||||
|
.is_active(&feature_set::require_static_program_ids_in_transaction::ID),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
let lock_results = self
|
let lock_results = self
|
||||||
.rc
|
.rc
|
||||||
|
@ -6327,7 +6336,14 @@ impl Bank {
|
||||||
tx.message.hash()
|
tx.message.hash()
|
||||||
};
|
};
|
||||||
|
|
||||||
SanitizedTransaction::try_create(tx, message_hash, None, self)
|
SanitizedTransaction::try_create(
|
||||||
|
tx,
|
||||||
|
message_hash,
|
||||||
|
None,
|
||||||
|
self,
|
||||||
|
self.feature_set
|
||||||
|
.is_active(&feature_set::require_static_program_ids_in_transaction::ID),
|
||||||
|
)
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
if verification_mode == TransactionVerificationMode::HashAndVerifyPrecompiles
|
if verification_mode == TransactionVerificationMode::HashAndVerifyPrecompiles
|
||||||
|
|
|
@ -356,6 +356,7 @@ mod tests {
|
||||||
MessageHash::Compute,
|
MessageHash::Compute,
|
||||||
Some(true),
|
Some(true),
|
||||||
SimpleAddressLoader::Disabled,
|
SimpleAddressLoader::Disabled,
|
||||||
|
true, // require_static_program_ids
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut tx_cost = TransactionCost::new_with_capacity(1);
|
let mut tx_cost = TransactionCost::new_with_capacity(1);
|
||||||
|
|
|
@ -36,6 +36,13 @@ pub enum VersionedMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VersionedMessage {
|
impl VersionedMessage {
|
||||||
|
pub fn sanitize(&self, require_static_program_ids: bool) -> Result<(), SanitizeError> {
|
||||||
|
match self {
|
||||||
|
Self::Legacy(message) => message.sanitize(),
|
||||||
|
Self::V0(message) => message.sanitize(require_static_program_ids),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn header(&self) -> &MessageHeader {
|
pub fn header(&self) -> &MessageHeader {
|
||||||
match self {
|
match self {
|
||||||
Self::Legacy(message) => &message.header,
|
Self::Legacy(message) => &message.header,
|
||||||
|
@ -148,15 +155,6 @@ impl Default for VersionedMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sanitize for VersionedMessage {
|
|
||||||
fn sanitize(&self) -> Result<(), SanitizeError> {
|
|
||||||
match self {
|
|
||||||
Self::Legacy(message) => message.sanitize(),
|
|
||||||
Self::V0(message) => message.sanitize(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for VersionedMessage {
|
impl Serialize for VersionedMessage {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::{
|
||||||
MessageHeader, MESSAGE_VERSION_PREFIX,
|
MessageHeader, MESSAGE_VERSION_PREFIX,
|
||||||
},
|
},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
sanitize::{Sanitize, SanitizeError},
|
sanitize::SanitizeError,
|
||||||
short_vec, sysvar,
|
short_vec, sysvar,
|
||||||
};
|
};
|
||||||
pub use loaded::*;
|
pub use loaded::*;
|
||||||
|
@ -52,7 +52,9 @@ pub struct MessageAddressTableLookup {
|
||||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
/// The message header, identifying signed and read-only `account_keys`
|
/// The message header, identifying signed and read-only `account_keys`.
|
||||||
|
/// Header values only describe static `account_keys`, they do not describe
|
||||||
|
/// any additional account keys loaded via address table lookups.
|
||||||
pub header: MessageHeader,
|
pub header: MessageHeader,
|
||||||
|
|
||||||
/// List of accounts loaded by this transaction.
|
/// List of accounts loaded by this transaction.
|
||||||
|
@ -67,7 +69,10 @@ pub struct Message {
|
||||||
///
|
///
|
||||||
/// # Notes
|
/// # Notes
|
||||||
///
|
///
|
||||||
/// Account and program indexes will index into the list of addresses
|
/// Program indexes must index into the list of message `account_keys` because
|
||||||
|
/// program id's cannot be dynamically loaded from a lookup table.
|
||||||
|
///
|
||||||
|
/// Account indexes must index into the list of addresses
|
||||||
/// constructed from the concatenation of three key lists:
|
/// constructed from the concatenation of three key lists:
|
||||||
/// 1) message `account_keys`
|
/// 1) message `account_keys`
|
||||||
/// 2) ordered list of keys loaded from `writable` lookup table indexes
|
/// 2) ordered list of keys loaded from `writable` lookup table indexes
|
||||||
|
@ -81,13 +86,13 @@ pub struct Message {
|
||||||
pub address_table_lookups: Vec<MessageAddressTableLookup>,
|
pub address_table_lookups: Vec<MessageAddressTableLookup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sanitize for Message {
|
impl Message {
|
||||||
fn sanitize(&self) -> Result<(), SanitizeError> {
|
/// Sanitize message fields and compiled instruction indexes
|
||||||
// signing area and read-only non-signing area should not
|
pub fn sanitize(&self, reject_dynamic_program_ids: bool) -> Result<(), SanitizeError> {
|
||||||
// overlap
|
let num_static_account_keys = self.account_keys.len();
|
||||||
if usize::from(self.header.num_required_signatures)
|
if usize::from(self.header.num_required_signatures)
|
||||||
.saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
|
.saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
|
||||||
> self.account_keys.len()
|
> num_static_account_keys
|
||||||
{
|
{
|
||||||
return Err(SanitizeError::IndexOutOfBounds);
|
return Err(SanitizeError::IndexOutOfBounds);
|
||||||
}
|
}
|
||||||
|
@ -97,29 +102,59 @@ impl Sanitize for Message {
|
||||||
return Err(SanitizeError::InvalidValue);
|
return Err(SanitizeError::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut num_loaded_accounts = self.account_keys.len();
|
let num_dynamic_account_keys = {
|
||||||
for lookup in &self.address_table_lookups {
|
let mut total_lookup_keys: usize = 0;
|
||||||
let num_table_loaded_accounts = lookup
|
for lookup in &self.address_table_lookups {
|
||||||
.writable_indexes
|
let num_lookup_indexes = lookup
|
||||||
.len()
|
.writable_indexes
|
||||||
.saturating_add(lookup.readonly_indexes.len());
|
.len()
|
||||||
|
.saturating_add(lookup.readonly_indexes.len());
|
||||||
|
|
||||||
// each lookup table must be used to load at least one account
|
// each lookup table must be used to load at least one account
|
||||||
if num_table_loaded_accounts == 0 {
|
if num_lookup_indexes == 0 {
|
||||||
return Err(SanitizeError::InvalidValue);
|
return Err(SanitizeError::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
total_lookup_keys = total_lookup_keys.saturating_add(num_lookup_indexes);
|
||||||
}
|
}
|
||||||
|
total_lookup_keys
|
||||||
|
};
|
||||||
|
|
||||||
num_loaded_accounts = num_loaded_accounts.saturating_add(num_table_loaded_accounts);
|
// this is redundant with the above sanitization checks which require that:
|
||||||
|
// 1) the header describes at least 1 RW account
|
||||||
|
// 2) the header doesn't describe more account keys than the number of account keys
|
||||||
|
if num_static_account_keys == 0 {
|
||||||
|
return Err(SanitizeError::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the number of loaded accounts must be <= 256 since account indices are
|
// the combined number of static and dynamic account keys must be <= 256
|
||||||
// encoded as `u8`
|
// since account indices are encoded as `u8`
|
||||||
if num_loaded_accounts > 256 {
|
let total_account_keys = num_static_account_keys.saturating_add(num_dynamic_account_keys);
|
||||||
|
if total_account_keys > 256 {
|
||||||
return Err(SanitizeError::IndexOutOfBounds);
|
return Err(SanitizeError::IndexOutOfBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `expect` is safe because of earlier check that
|
||||||
|
// `num_static_account_keys` is non-zero
|
||||||
|
let max_account_ix = total_account_keys
|
||||||
|
.checked_sub(1)
|
||||||
|
.expect("message doesn't contain any account keys");
|
||||||
|
|
||||||
|
// switch to rejecting program ids loaded from lookup tables so that
|
||||||
|
// static analysis on program instructions can be performed without
|
||||||
|
// loading on-chain data from a bank
|
||||||
|
let max_program_id_ix = if reject_dynamic_program_ids {
|
||||||
|
// `expect` is safe because of earlier check that
|
||||||
|
// `num_static_account_keys` is non-zero
|
||||||
|
num_static_account_keys
|
||||||
|
.checked_sub(1)
|
||||||
|
.expect("message doesn't contain any static account keys")
|
||||||
|
} else {
|
||||||
|
max_account_ix
|
||||||
|
};
|
||||||
|
|
||||||
for ci in &self.instructions {
|
for ci in &self.instructions {
|
||||||
if usize::from(ci.program_id_index) >= num_loaded_accounts {
|
if usize::from(ci.program_id_index) > max_program_id_ix {
|
||||||
return Err(SanitizeError::IndexOutOfBounds);
|
return Err(SanitizeError::IndexOutOfBounds);
|
||||||
}
|
}
|
||||||
// A program cannot be a payer.
|
// A program cannot be a payer.
|
||||||
|
@ -127,7 +162,7 @@ impl Sanitize for Message {
|
||||||
return Err(SanitizeError::IndexOutOfBounds);
|
return Err(SanitizeError::IndexOutOfBounds);
|
||||||
}
|
}
|
||||||
for ai in &ci.accounts {
|
for ai in &ci.accounts {
|
||||||
if usize::from(*ai) >= num_loaded_accounts {
|
if usize::from(*ai) > max_account_ix {
|
||||||
return Err(SanitizeError::IndexOutOfBounds);
|
return Err(SanitizeError::IndexOutOfBounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,7 +372,9 @@ mod tests {
|
||||||
account_keys: vec![Pubkey::new_unique()],
|
account_keys: vec![Pubkey::new_unique()],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +393,9 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,13 +414,15 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sanitize_with_table_lookup_and_ix() {
|
fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
|
||||||
assert!(Message {
|
let message = Message {
|
||||||
header: MessageHeader {
|
header: MessageHeader {
|
||||||
num_required_signatures: 1,
|
num_required_signatures: 1,
|
||||||
..MessageHeader::default()
|
..MessageHeader::default()
|
||||||
|
@ -395,11 +436,43 @@ mod tests {
|
||||||
instructions: vec![CompiledInstruction {
|
instructions: vec![CompiledInstruction {
|
||||||
program_id_index: 4,
|
program_id_index: 4,
|
||||||
accounts: vec![0, 1, 2, 3],
|
accounts: vec![0, 1, 2, 3],
|
||||||
|
data: vec![],
|
||||||
|
}],
|
||||||
|
..Message::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(message.sanitize(
|
||||||
|
false, // require_static_program_ids
|
||||||
|
).is_ok());
|
||||||
|
|
||||||
|
assert!(message.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
|
||||||
|
assert!(Message {
|
||||||
|
header: MessageHeader {
|
||||||
|
num_required_signatures: 1,
|
||||||
|
..MessageHeader::default()
|
||||||
|
},
|
||||||
|
account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
|
||||||
|
address_table_lookups: vec![MessageAddressTableLookup {
|
||||||
|
account_key: Pubkey::new_unique(),
|
||||||
|
writable_indexes: vec![1, 2, 3],
|
||||||
|
readonly_indexes: vec![0],
|
||||||
|
}],
|
||||||
|
instructions: vec![CompiledInstruction {
|
||||||
|
program_id_index: 1,
|
||||||
|
accounts: vec![2, 3, 4, 5],
|
||||||
data: vec![]
|
data: vec![]
|
||||||
}],
|
}],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,7 +483,9 @@ mod tests {
|
||||||
account_keys: vec![Pubkey::new_unique()],
|
account_keys: vec![Pubkey::new_unique()],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,7 +500,9 @@ mod tests {
|
||||||
account_keys: vec![Pubkey::new_unique()],
|
account_keys: vec![Pubkey::new_unique()],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,7 +521,9 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,7 +537,9 @@ mod tests {
|
||||||
account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
|
account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,7 +553,9 @@ mod tests {
|
||||||
account_keys: (0..=256).map(|_| Pubkey::new_unique()).collect(),
|
account_keys: (0..=256).map(|_| Pubkey::new_unique()).collect(),
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,7 +574,9 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,13 +595,15 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sanitize_with_invalid_ix_program_id() {
|
fn test_sanitize_with_invalid_ix_program_id() {
|
||||||
assert!(Message {
|
let message = Message {
|
||||||
header: MessageHeader {
|
header: MessageHeader {
|
||||||
num_required_signatures: 1,
|
num_required_signatures: 1,
|
||||||
..MessageHeader::default()
|
..MessageHeader::default()
|
||||||
|
@ -530,12 +617,17 @@ mod tests {
|
||||||
instructions: vec![CompiledInstruction {
|
instructions: vec![CompiledInstruction {
|
||||||
program_id_index: 2,
|
program_id_index: 2,
|
||||||
accounts: vec![],
|
accounts: vec![],
|
||||||
data: vec![]
|
data: vec![],
|
||||||
}],
|
}],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
};
|
||||||
.sanitize()
|
|
||||||
.is_err());
|
assert!(message
|
||||||
|
.sanitize(true /* require_static_program_ids */)
|
||||||
|
.is_err());
|
||||||
|
assert!(message
|
||||||
|
.sanitize(false /* require_static_program_ids */)
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -545,7 +637,7 @@ mod tests {
|
||||||
num_required_signatures: 1,
|
num_required_signatures: 1,
|
||||||
..MessageHeader::default()
|
..MessageHeader::default()
|
||||||
},
|
},
|
||||||
account_keys: vec![Pubkey::new_unique()],
|
account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
|
||||||
address_table_lookups: vec![MessageAddressTableLookup {
|
address_table_lookups: vec![MessageAddressTableLookup {
|
||||||
account_key: Pubkey::new_unique(),
|
account_key: Pubkey::new_unique(),
|
||||||
writable_indexes: vec![],
|
writable_indexes: vec![],
|
||||||
|
@ -553,12 +645,14 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
instructions: vec![CompiledInstruction {
|
instructions: vec![CompiledInstruction {
|
||||||
program_id_index: 1,
|
program_id_index: 1,
|
||||||
accounts: vec![2],
|
accounts: vec![3],
|
||||||
data: vec![]
|
data: vec![]
|
||||||
}],
|
}],
|
||||||
..Message::default()
|
..Message::default()
|
||||||
}
|
}
|
||||||
.sanitize()
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -383,6 +383,10 @@ pub mod stake_allow_zero_undelegated_amount {
|
||||||
solana_sdk::declare_id!("sTKz343FM8mqtyGvYWvbLpTThw3ixRM4Xk8QvZ985mw");
|
solana_sdk::declare_id!("sTKz343FM8mqtyGvYWvbLpTThw3ixRM4Xk8QvZ985mw");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod require_static_program_ids_in_transaction {
|
||||||
|
solana_sdk::declare_id!("8FdwgyHFEjhAdjWfV2vfqk7wA1g9X3fQpKH7SBpEv3kC");
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Map of feature identifiers to user-visible description
|
/// Map of feature identifiers to user-visible description
|
||||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||||
|
@ -472,7 +476,8 @@ lazy_static! {
|
||||||
(spl_token_v3_4_0::id(), "SPL Token Program version 3.4.0 release #24740"),
|
(spl_token_v3_4_0::id(), "SPL Token Program version 3.4.0 release #24740"),
|
||||||
(spl_associated_token_account_v1_1_0::id(), "SPL Associated Token Account Program version 1.1.0 release #24741"),
|
(spl_associated_token_account_v1_1_0::id(), "SPL Associated Token Account Program version 1.1.0 release #24741"),
|
||||||
(default_units_per_instruction::id(), "Default max tx-wide compute units calculated per instruction"),
|
(default_units_per_instruction::id(), "Default max tx-wide compute units calculated per instruction"),
|
||||||
(stake_allow_zero_undelegated_amount::id(), "Allow zero-lamport undelegated amount for initialized stakes #24670")
|
(stake_allow_zero_undelegated_amount::id(), "Allow zero-lamport undelegated amount for initialized stakes #24670"),
|
||||||
|
(require_static_program_ids_in_transaction::id(), "require static program ids in versioned transactions"),
|
||||||
/*************** ADD NEW FEATURES HERE ***************/
|
/*************** ADD NEW FEATURES HERE ***************/
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -80,8 +80,9 @@ impl SanitizedTransaction {
|
||||||
message_hash: impl Into<MessageHash>,
|
message_hash: impl Into<MessageHash>,
|
||||||
is_simple_vote_tx: Option<bool>,
|
is_simple_vote_tx: Option<bool>,
|
||||||
address_loader: impl AddressLoader,
|
address_loader: impl AddressLoader,
|
||||||
|
require_static_program_ids: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
tx.sanitize()?;
|
tx.sanitize(require_static_program_ids)?;
|
||||||
|
|
||||||
let message_hash = match message_hash.into() {
|
let message_hash = match message_hash.into() {
|
||||||
MessageHash::Compute => tx.message.hash(),
|
MessageHash::Compute => tx.message.hash(),
|
||||||
|
|
|
@ -6,7 +6,7 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
message::VersionedMessage,
|
message::VersionedMessage,
|
||||||
sanitize::{Sanitize, SanitizeError},
|
sanitize::SanitizeError,
|
||||||
short_vec,
|
short_vec,
|
||||||
signature::Signature,
|
signature::Signature,
|
||||||
signer::SignerError,
|
signer::SignerError,
|
||||||
|
@ -46,27 +46,6 @@ pub struct VersionedTransaction {
|
||||||
pub message: VersionedMessage,
|
pub message: VersionedMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sanitize for VersionedTransaction {
|
|
||||||
fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
|
|
||||||
self.message.sanitize()?;
|
|
||||||
|
|
||||||
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),
|
|
||||||
Ordering::Less => Err(SanitizeError::InvalidValue),
|
|
||||||
Ordering::Equal => Ok(()),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
// Signatures are verified before message keys are loaded so all signers
|
|
||||||
// must correspond to static account keys.
|
|
||||||
if self.signatures.len() > self.message.static_account_keys().len() {
|
|
||||||
return Err(SanitizeError::IndexOutOfBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Transaction> for VersionedTransaction {
|
impl From<Transaction> for VersionedTransaction {
|
||||||
fn from(transaction: Transaction) -> Self {
|
fn from(transaction: Transaction) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -111,6 +90,28 @@ impl VersionedTransaction {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sanitize(
|
||||||
|
&self,
|
||||||
|
require_static_program_ids: bool,
|
||||||
|
) -> std::result::Result<(), SanitizeError> {
|
||||||
|
self.message.sanitize(require_static_program_ids)?;
|
||||||
|
|
||||||
|
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),
|
||||||
|
Ordering::Less => Err(SanitizeError::InvalidValue),
|
||||||
|
Ordering::Equal => Ok(()),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// Signatures are verified before message keys are loaded so all signers
|
||||||
|
// must correspond to static account keys.
|
||||||
|
if self.signatures.len() > self.message.static_account_keys().len() {
|
||||||
|
return Err(SanitizeError::IndexOutOfBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the version of the transaction
|
/// Returns the version of the transaction
|
||||||
pub fn version(&self) -> TransactionVersion {
|
pub fn version(&self) -> TransactionVersion {
|
||||||
match self.message {
|
match self.message {
|
||||||
|
|
|
@ -16,7 +16,6 @@ use {
|
||||||
AccountKeys, Message, MessageHeader, VersionedMessage,
|
AccountKeys, Message, MessageHeader, VersionedMessage,
|
||||||
},
|
},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
sanitize::Sanitize,
|
|
||||||
signature::Signature,
|
signature::Signature,
|
||||||
transaction::{
|
transaction::{
|
||||||
Result as TransactionResult, Transaction, TransactionError, TransactionVersion,
|
Result as TransactionResult, Transaction, TransactionError, TransactionVersion,
|
||||||
|
@ -867,7 +866,13 @@ impl EncodedTransaction {
|
||||||
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
|
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
|
||||||
};
|
};
|
||||||
|
|
||||||
transaction.filter(|transaction| transaction.sanitize().is_ok())
|
transaction.filter(|transaction| {
|
||||||
|
transaction
|
||||||
|
.sanitize(
|
||||||
|
true, // require_static_program_ids
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue