From dd1c4aaa3c2d3d8c1e7164a83749cf8d429e67e7 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 5 Oct 2022 00:00:34 +0800 Subject: [PATCH] RPC: Support versioned txs in getFeeForMessage API (#28217) * RPC: Support versioned txs in getFeeForMessage API * Update sdk/program/src/message/sanitized.rs Co-authored-by: Tyera Eulberg Co-authored-by: Tyera Eulberg (cherry picked from commit ddf95c181c4732ba1f67e24b9f122b2e69e81005) # Conflicts: # client/src/nonblocking/rpc_client.rs # client/src/rpc_client.rs # programs/address-lookup-table/src/error.rs # rpc/src/rpc.rs # runtime/src/bank/address_lookup_table.rs # sdk/src/transaction/sanitized.rs --- cli/src/checks.rs | 2 +- client/src/nonblocking/rpc_client.rs | 48 +-- client/src/rpc_client.rs | 46 ++- programs/address-lookup-table/src/error.rs | 18 +- rpc/src/rpc.rs | 322 +++++++++++++++++++-- runtime/src/bank/address_lookup_table.rs | 14 +- sdk/program/src/message/address_loader.rs | 56 ++++ sdk/program/src/message/mod.rs | 3 +- sdk/program/src/message/sanitized.rs | 24 +- sdk/src/transaction/error.rs | 24 +- sdk/src/transaction/sanitized.rs | 26 +- 11 files changed, 506 insertions(+), 77 deletions(-) create mode 100644 sdk/program/src/message/address_loader.rs diff --git a/cli/src/checks.rs b/cli/src/checks.rs index 8c45f6e0483e93..dea87e5c4a13db 100644 --- a/cli/src/checks.rs +++ b/cli/src/checks.rs @@ -114,7 +114,7 @@ pub fn get_fee_for_messages( ) -> Result { Ok(messages .iter() - .map(|message| rpc_client.get_fee_for_message(message)) + .map(|message| rpc_client.get_fee_for_message(*message)) .collect::, _>>()? .iter() .sum()) diff --git a/client/src/nonblocking/rpc_client.rs b/client/src/nonblocking/rpc_client.rs index c03dfec1be4b28..bb7ff574d4900d 100644 --- a/client/src/nonblocking/rpc_client.rs +++ b/client/src/nonblocking/rpc_client.rs @@ -17,10 +17,17 @@ use { client_error::{ClientError, ClientErrorKind, Result as ClientResult}, http_sender::HttpSender, mock_sender::MockSender, +<<<<<<< HEAD:client/src/nonblocking/rpc_client.rs rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClientConfig}, rpc_config::{RpcAccountInfoConfig, *}, rpc_request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter}, rpc_response::*, +======= + rpc_client::{ + GetConfirmedSignaturesForAddress2Config, RpcClientConfig, SerializableMessage, + SerializableTransaction, + }, +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)):rpc-client/src/nonblocking/rpc_client.rs rpc_sender::*, spinner, }, @@ -39,10 +46,13 @@ use { epoch_schedule::EpochSchedule, fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, - message::Message, pubkey::Pubkey, signature::Signature, +<<<<<<< HEAD:client/src/nonblocking/rpc_client.rs transaction::{self, uses_durable_nonce, Transaction}, +======= + transaction, +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)):rpc-client/src/nonblocking/rpc_client.rs }, solana_transaction_status::{ EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, @@ -5276,28 +5286,20 @@ impl RpcClient { } #[allow(deprecated)] - pub async fn get_fee_for_message(&self, message: &Message) -> ClientResult { - if self.get_node_version().await? < semver::Version::new(1, 9, 0) { - let fee_calculator = self - .get_fee_calculator_for_blockhash(&message.recent_blockhash) - .await? - .ok_or_else(|| ClientErrorKind::Custom("Invalid blockhash".to_string()))?; - Ok(fee_calculator - .lamports_per_signature - .saturating_mul(message.header.num_required_signatures as u64)) - } else { - let serialized_encoded = - serialize_and_encode::(message, UiTransactionEncoding::Base64)?; - let result = self - .send::>>( - RpcRequest::GetFeeForMessage, - json!([serialized_encoded, self.commitment()]), - ) - .await?; - result - .value - .ok_or_else(|| ClientErrorKind::Custom("Invalid blockhash".to_string()).into()) - } + pub async fn get_fee_for_message( + &self, + message: &impl SerializableMessage, + ) -> ClientResult { + let serialized_encoded = serialize_and_encode(message, UiTransactionEncoding::Base64)?; + let result = self + .send::>>( + RpcRequest::GetFeeForMessage, + json!([serialized_encoded, self.commitment()]), + ) + .await?; + result + .value + .ok_or_else(|| ClientErrorKind::Custom("Invalid blockhash".to_string()).into()) } pub async fn get_new_latest_blockhash(&self, blockhash: &Hash) -> ClientResult { diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 89959de20e56fa..81e64f6e53993e 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -33,7 +33,7 @@ use { epoch_schedule::EpochSchedule, fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, - message::Message, + message::{v0, Message as LegacyMessage}, pubkey::Pubkey, signature::Signature, transaction::{self, Transaction}, @@ -60,6 +60,45 @@ impl RpcClientConfig { } } +<<<<<<< HEAD:client/src/rpc_client.rs +======= +/// Trait used to add support for versioned messages to RPC APIs while +/// retaining backwards compatibility +pub trait SerializableMessage: Serialize {} +impl SerializableMessage for LegacyMessage {} +impl SerializableMessage for v0::Message {} + +/// Trait used to add support for versioned transactions to RPC APIs while +/// retaining backwards compatibility +pub trait SerializableTransaction: Serialize { + fn get_signature(&self) -> &Signature; + fn get_recent_blockhash(&self) -> &Hash; + fn uses_durable_nonce(&self) -> bool; +} +impl SerializableTransaction for Transaction { + fn get_signature(&self) -> &Signature { + &self.signatures[0] + } + fn get_recent_blockhash(&self) -> &Hash { + &self.message.recent_blockhash + } + fn uses_durable_nonce(&self) -> bool { + uses_durable_nonce(self).is_some() + } +} +impl SerializableTransaction for VersionedTransaction { + fn get_signature(&self) -> &Signature { + &self.signatures[0] + } + fn get_recent_blockhash(&self) -> &Hash { + self.message.recent_blockhash() + } + fn uses_durable_nonce(&self) -> bool { + self.uses_durable_nonce() + } +} + +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)):rpc-client/src/rpc_client.rs #[derive(Debug, Default)] pub struct GetConfirmedSignaturesForAddress2Config { pub before: Option, @@ -4035,8 +4074,13 @@ impl RpcClient { } #[allow(deprecated)] +<<<<<<< HEAD:client/src/rpc_client.rs pub fn get_fee_for_message(&self, message: &Message) -> ClientResult { self.invoke(self.rpc_client.get_fee_for_message(message)) +======= + pub fn get_fee_for_message(&self, message: &impl SerializableMessage) -> ClientResult { + self.invoke((self.rpc_client.as_ref()).get_fee_for_message(message)) +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)):rpc-client/src/rpc_client.rs } pub fn get_new_latest_blockhash(&self, blockhash: &Hash) -> ClientResult { diff --git a/programs/address-lookup-table/src/error.rs b/programs/address-lookup-table/src/error.rs index d0a063ec44c7a6..ff972f5193dcd7 100644 --- a/programs/address-lookup-table/src/error.rs +++ b/programs/address-lookup-table/src/error.rs @@ -1,5 +1,10 @@ +<<<<<<< HEAD #[cfg(not(target_arch = "bpf"))] use solana_sdk::transaction::TransactionError; +======= +#[cfg(not(target_os = "solana"))] +use solana_program::message::AddressLoaderError; +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)) use thiserror::Error; #[derive(Debug, Error, PartialEq, Eq, Clone)] @@ -21,14 +26,19 @@ pub enum AddressLookupError { InvalidLookupIndex, } +<<<<<<< HEAD #[cfg(not(target_arch = "bpf"))] impl From for TransactionError { +======= +#[cfg(not(target_os = "solana"))] +impl From for AddressLoaderError { +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)) fn from(err: AddressLookupError) -> Self { match err { - AddressLookupError::LookupTableAccountNotFound => Self::AddressLookupTableNotFound, - AddressLookupError::InvalidAccountOwner => Self::InvalidAddressLookupTableOwner, - AddressLookupError::InvalidAccountData => Self::InvalidAddressLookupTableData, - AddressLookupError::InvalidLookupIndex => Self::InvalidAddressLookupTableIndex, + AddressLookupError::LookupTableAccountNotFound => Self::LookupTableAccountNotFound, + AddressLookupError::InvalidAccountOwner => Self::InvalidAccountOwner, + AddressLookupError::InvalidAccountData => Self::InvalidAccountData, + AddressLookupError::InvalidLookupIndex => Self::InvalidLookupIndex, } } } diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 6c53b47b990a18..23374f5a2a40fb 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -64,7 +64,7 @@ use { feature_set::{self, nonce_must_be_writable}, fee_calculator::FeeCalculator, hash::Hash, - message::{Message, SanitizedMessage}, + message::SanitizedMessage, pubkey::{Pubkey, PUBKEY_BYTES}, signature::{Keypair, Signature, Signer}, stake::state::{StakeActivationStatus, StakeState}, @@ -2158,6 +2158,7 @@ impl JsonRpcRequestProcessor { Ok(new_response(&bank, is_valid)) } +<<<<<<< HEAD fn get_fee_for_message( &self, message: &SanitizedMessage, @@ -2166,6 +2167,28 @@ impl JsonRpcRequestProcessor { let bank = self.get_bank_with_config(config)?; let fee = bank.get_fee_for_message(message); Ok(new_response(&bank, fee)) +======= + fn get_stake_minimum_delegation(&self, config: RpcContextConfig) -> Result> { + let bank = self.get_bank_with_config(config)?; + let stake_minimum_delegation = + solana_stake_program::get_minimum_delegation(&bank.feature_set); + Ok(new_response(&bank, stake_minimum_delegation)) + } + + fn get_recent_prioritization_fees( + &self, + pubkeys: Vec, + ) -> Result> { + Ok(self + .prioritization_fee_cache + .get_prioritization_fees(&pubkeys) + .into_iter() + .map(|(slot, prioritization_fee)| RpcPrioritizationFee { + slot, + prioritization_fee, + }) + .collect()) +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)) } } @@ -3255,7 +3278,10 @@ pub mod rpc_accounts { // Full RPC interface that an API node is expected to provide // (rpc_minimal should also be provided by an API node) pub mod rpc_full { - use super::*; + use { + super::*, + solana_sdk::message::{SanitizedVersionedMessage, VersionedMessage}, + }; #[rpc] pub trait Full { type Metadata; @@ -3956,12 +3982,21 @@ pub mod rpc_full { config: Option, ) -> Result>> { debug!("get_fee_for_message rpc request received"); - let (_, message) = - decode_and_deserialize::(data, TransactionBinaryEncoding::Base64)?; - let sanitized_message = SanitizedMessage::try_from(message).map_err(|err| { - Error::invalid_params(format!("invalid transaction message: {}", err)) - })?; - meta.get_fee_for_message(&sanitized_message, config.unwrap_or_default()) + let (_, message) = decode_and_deserialize::( + data, + TransactionBinaryEncoding::Base64, + )?; + let bank = &*meta.get_bank_with_config(config.unwrap_or_default())?; + let sanitized_versioned_message = SanitizedVersionedMessage::try_from(message) + .map_err(|err| { + Error::invalid_params(format!("invalid transaction message: {}", err)) + })?; + let sanitized_message = SanitizedMessage::try_new(sanitized_versioned_message, bank) + .map_err(|err| { + Error::invalid_params(format!("invalid transaction message: {}", err)) + })?; + let fee = bank.get_fee_for_message(&sanitized_message); + Ok(new_response(bank, fee)) } } } @@ -4565,10 +4600,18 @@ pub mod tests { solana_sdk::{ account::{Account, WritableAccount}, clock::MAX_RECENT_BLOCKHASHES, +<<<<<<< HEAD fee_calculator::DEFAULT_BURN_PERCENT, +======= + compute_budget::ComputeBudgetInstruction, + fee_calculator::{FeeRateGovernor, DEFAULT_BURN_PERCENT}, +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)) hash::{hash, Hash}, instruction::InstructionError, - message::{v0, v0::MessageAddressTableLookup, MessageHeader, VersionedMessage}, + message::{ + v0::{self, MessageAddressTableLookup}, + Message, MessageHeader, VersionedMessage, + }, nonce::{self, state::DurableNonce}, rpc_port, signature::{Keypair, Signer}, @@ -4599,7 +4642,8 @@ pub mod tests { std::{borrow::Cow, collections::HashMap}, }; - const TEST_MINT_LAMPORTS: u64 = 1_000_000; + const TEST_MINT_LAMPORTS: u64 = 1_000_000_000; + const TEST_SIGNATURE_FEE: u64 = 5_000; const TEST_SLOTS_PER_EPOCH: u64 = DELINQUENT_VALIDATOR_SLOT_DISTANCE + 1; fn create_test_request(method: &str, params: Option) -> serde_json::Value { @@ -4744,8 +4788,12 @@ pub mod tests { let keypair3 = Keypair::new(); let bank = self.working_bank(); let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0); - bank.transfer(rent_exempt_amount, mint_keypair, &keypair2.pubkey()) - .unwrap(); + bank.transfer( + rent_exempt_amount + TEST_SIGNATURE_FEE, + mint_keypair, + &keypair2.pubkey(), + ) + .unwrap(); let (entries, signatures) = create_test_transaction_entries( vec![&self.mint_keypair, &keypair1, &keypair2, &keypair3], @@ -5373,7 +5421,7 @@ pub mod tests { "context": {"slot": 0, "apiVersion": RpcApiVersion::default()}, "value":{ "owner": "11111111111111111111111111111111", - "lamports": 1_000_000, + "lamports": TEST_MINT_LAMPORTS, "data": "", "executable": false, "rentEpoch": 0 @@ -5450,7 +5498,7 @@ pub mod tests { let expected = json!([ { "owner": "11111111111111111111111111111111", - "lamports": 1_000_000, + "lamports": TEST_MINT_LAMPORTS, "data": ["", "base64"], "executable": false, "rentEpoch": 0 @@ -5482,7 +5530,7 @@ pub mod tests { let expected = json!([ { "owner": "11111111111111111111111111111111", - "lamports": 1_000_000, + "lamports": TEST_MINT_LAMPORTS, "data": ["", "base58"], "executable": false, "rentEpoch": 0 @@ -6063,7 +6111,7 @@ pub mod tests { "value":{ "blockhash": recent_blockhash.to_string(), "feeCalculator": { - "lamportsPerSignature": 0, + "lamportsPerSignature": TEST_SIGNATURE_FEE, } }, }, @@ -6092,7 +6140,7 @@ pub mod tests { "value": { "blockhash": recent_blockhash.to_string(), "feeCalculator": { - "lamportsPerSignature": 0, + "lamportsPerSignature": TEST_SIGNATURE_FEE, }, "lastValidSlot": MAX_RECENT_BLOCKHASHES, "lastValidBlockHeight": MAX_RECENT_BLOCKHASHES, @@ -6172,9 +6220,9 @@ pub mod tests { "value":{ "feeRateGovernor": { "burnPercent": DEFAULT_BURN_PERCENT, - "maxLamportsPerSignature": 0, - "minLamportsPerSignature": 0, - "targetLamportsPerSignature": 0, + "maxLamportsPerSignature": TEST_SIGNATURE_FEE, + "minLamportsPerSignature": TEST_SIGNATURE_FEE, + "targetLamportsPerSignature": TEST_SIGNATURE_FEE, "targetSignaturesPerSlot": 0 } }, @@ -6440,6 +6488,7 @@ pub mod tests { genesis_config.rent.exemption_threshold = 2.0; genesis_config.epoch_schedule = EpochSchedule::custom(TEST_SLOTS_PER_EPOCH, TEST_SLOTS_PER_EPOCH, false); + genesis_config.fee_rate_governor = FeeRateGovernor::new(TEST_SIGNATURE_FEE, 0); let bank = Bank::new_for_tests(&genesis_config); ( @@ -8430,8 +8479,239 @@ pub mod tests { assert_eq!( sanitize_transaction(versioned_tx, SimpleAddressLoader::Disabled).unwrap_err(), Error::invalid_params( - "invalid transaction: Transaction loads an address table account that doesn't exist".to_string(), + "invalid transaction: Transaction version is unsupported".to_string(), ) ); } +<<<<<<< HEAD +======= + + #[test] + fn test_rpc_get_stake_minimum_delegation() { + let rpc = RpcHandler::start(); + let bank = rpc.working_bank(); + let expected_stake_minimum_delegation = + solana_stake_program::get_minimum_delegation(&bank.feature_set); + + let request = create_test_request("getStakeMinimumDelegation", None); + let response: RpcResponse = parse_success_result(rpc.handle_request_sync(request)); + let actual_stake_minimum_delegation = response.value; + + assert_eq!( + actual_stake_minimum_delegation, + expected_stake_minimum_delegation + ); + } + + #[test] + fn test_get_fee_for_message() { + let rpc = RpcHandler::start(); + let bank = rpc.working_bank(); + // Slot hashes is necessary for processing versioned txs. + bank.set_sysvar_for_tests(&SlotHashes::default()); + // Correct blockhash is needed because fees are specific to blockhashes + let recent_blockhash = bank.last_blockhash(); + + { + let legacy_msg = VersionedMessage::Legacy(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + recent_blockhash, + account_keys: vec![Pubkey::new_unique()], + ..Message::default() + }); + + let request = create_test_request( + "getFeeForMessage", + Some(json!([base64::encode(&serialize(&legacy_msg).unwrap())])), + ); + let response: RpcResponse = parse_success_result(rpc.handle_request_sync(request)); + assert_eq!(response.value, TEST_SIGNATURE_FEE); + } + + { + let v0_msg = VersionedMessage::V0(v0::Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + recent_blockhash, + account_keys: vec![Pubkey::new_unique()], + ..v0::Message::default() + }); + + let request = create_test_request( + "getFeeForMessage", + Some(json!([base64::encode(&serialize(&v0_msg).unwrap())])), + ); + let response: RpcResponse = parse_success_result(rpc.handle_request_sync(request)); + assert_eq!(response.value, TEST_SIGNATURE_FEE); + } + } + + #[test] + fn test_rpc_get_recent_prioritization_fees() { + fn wait_for_cache_blocks(cache: &PrioritizationFeeCache, num_blocks: usize) { + while cache.available_block_count() < num_blocks { + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } + + fn assert_fee_vec_eq( + expected: &mut Vec, + actual: &mut Vec, + ) { + expected.sort_by(|a, b| a.slot.partial_cmp(&b.slot).unwrap()); + actual.sort_by(|a, b| a.slot.partial_cmp(&b.slot).unwrap()); + assert_eq!(expected, actual); + } + + let rpc = RpcHandler::start(); + assert_eq!( + rpc.get_prioritization_fee_cache().available_block_count(), + 0 + ); + let slot0 = rpc.working_bank().slot(); + let account0 = Pubkey::new_unique(); + let account1 = Pubkey::new_unique(); + let account2 = Pubkey::new_unique(); + let price0 = 42; + let transactions = vec![ + Transaction::new_unsigned(Message::new( + &[ + system_instruction::transfer(&account0, &account1, 1), + ComputeBudgetInstruction::set_compute_unit_price(price0), + ], + Some(&account0), + )), + Transaction::new_unsigned(Message::new( + &[system_instruction::transfer(&account0, &account2, 1)], + Some(&account0), + )), + ]; + rpc.update_prioritization_fee_cache(transactions); + let cache = rpc.get_prioritization_fee_cache(); + cache.finalize_priority_fee(slot0); + wait_for_cache_blocks(cache, 1); + + let request = create_test_request("getRecentPrioritizationFees", None); + let mut response: Vec = + parse_success_result(rpc.handle_request_sync(request)); + assert_fee_vec_eq( + &mut response, + &mut vec![RpcPrioritizationFee { + slot: slot0, + prioritization_fee: 0, + }], + ); + + let request = create_test_request( + "getRecentPrioritizationFees", + Some(json!([[account1.to_string()]])), + ); + let mut response: Vec = + parse_success_result(rpc.handle_request_sync(request)); + assert_fee_vec_eq( + &mut response, + &mut vec![RpcPrioritizationFee { + slot: slot0, + prioritization_fee: price0, + }], + ); + + let request = create_test_request( + "getRecentPrioritizationFees", + Some(json!([[account2.to_string()]])), + ); + let mut response: Vec = + parse_success_result(rpc.handle_request_sync(request)); + assert_fee_vec_eq( + &mut response, + &mut vec![RpcPrioritizationFee { + slot: slot0, + prioritization_fee: 0, + }], + ); + + rpc.advance_bank_to_confirmed_slot(1); + let slot1 = rpc.working_bank().slot(); + let price1 = 11; + let transactions = vec![ + Transaction::new_unsigned(Message::new( + &[ + system_instruction::transfer(&account0, &account2, 1), + ComputeBudgetInstruction::set_compute_unit_price(price1), + ], + Some(&account0), + )), + Transaction::new_unsigned(Message::new( + &[system_instruction::transfer(&account0, &account1, 1)], + Some(&account0), + )), + ]; + rpc.update_prioritization_fee_cache(transactions); + let cache = rpc.get_prioritization_fee_cache(); + cache.finalize_priority_fee(slot1); + wait_for_cache_blocks(cache, 2); + + let request = create_test_request("getRecentPrioritizationFees", None); + let mut response: Vec = + parse_success_result(rpc.handle_request_sync(request)); + assert_fee_vec_eq( + &mut response, + &mut vec![ + RpcPrioritizationFee { + slot: slot0, + prioritization_fee: 0, + }, + RpcPrioritizationFee { + slot: slot1, + prioritization_fee: 0, + }, + ], + ); + + let request = create_test_request( + "getRecentPrioritizationFees", + Some(json!([[account1.to_string()]])), + ); + let mut response: Vec = + parse_success_result(rpc.handle_request_sync(request)); + assert_fee_vec_eq( + &mut response, + &mut vec![ + RpcPrioritizationFee { + slot: slot0, + prioritization_fee: price0, + }, + RpcPrioritizationFee { + slot: slot1, + prioritization_fee: 0, + }, + ], + ); + + let request = create_test_request( + "getRecentPrioritizationFees", + Some(json!([[account2.to_string()]])), + ); + let mut response: Vec = + parse_success_result(rpc.handle_request_sync(request)); + assert_fee_vec_eq( + &mut response, + &mut vec![ + RpcPrioritizationFee { + slot: slot0, + prioritization_fee: 0, + }, + RpcPrioritizationFee { + slot: slot1, + prioritization_fee: price1, + }, + ], + ); + } +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)) } diff --git a/runtime/src/bank/address_lookup_table.rs b/runtime/src/bank/address_lookup_table.rs index 0a4bc732f45ba6..6a7d8eadc238ec 100644 --- a/runtime/src/bank/address_lookup_table.rs +++ b/runtime/src/bank/address_lookup_table.rs @@ -3,9 +3,17 @@ use { crate::accounts_db::LoadZeroLamports, solana_address_lookup_table_program::error::AddressLookupError, solana_sdk::{ +<<<<<<< HEAD feature_set::return_none_for_zero_lamport_accounts, message::v0::{LoadedAddresses, MessageAddressTableLookup}, transaction::{AddressLoader, Result as TransactionResult, TransactionError}, +======= + message::{ + v0::{LoadedAddresses, MessageAddressTableLookup}, + AddressLoaderError, + }, + transaction::AddressLoader, +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)) }, }; @@ -13,9 +21,9 @@ impl AddressLoader for &Bank { fn load_addresses( self, address_table_lookups: &[MessageAddressTableLookup], - ) -> TransactionResult { + ) -> Result { if !self.versioned_tx_message_enabled() { - return Err(TransactionError::UnsupportedVersion); + return Err(AddressLoaderError::Disabled); } let load_zero_lamports = if self @@ -32,7 +40,7 @@ impl AddressLoader for &Bank { .read() .unwrap() .get_slot_hashes() - .map_err(|_| TransactionError::AccountNotFound)?; + .map_err(|_| AddressLoaderError::SlotHashesSysvarNotFound)?; Ok(address_table_lookups .iter() diff --git a/sdk/program/src/message/address_loader.rs b/sdk/program/src/message/address_loader.rs new file mode 100644 index 00000000000000..83e514cd0bd63b --- /dev/null +++ b/sdk/program/src/message/address_loader.rs @@ -0,0 +1,56 @@ +use { + super::v0::{LoadedAddresses, MessageAddressTableLookup}, + thiserror::Error, +}; + +#[derive(Debug, Error, PartialEq, Eq, Clone)] +pub enum AddressLoaderError { + /// Address loading from lookup tables is disabled + #[error("Address loading from lookup tables is disabled")] + Disabled, + + /// Failed to load slot hashes sysvar + #[error("Failed to load slot hashes sysvar")] + SlotHashesSysvarNotFound, + + /// Attempted to lookup addresses from a table that does not exist + #[error("Attempted to lookup addresses from a table that does not exist")] + LookupTableAccountNotFound, + + /// Attempted to lookup addresses from an account owned by the wrong program + #[error("Attempted to lookup addresses from an account owned by the wrong program")] + InvalidAccountOwner, + + /// Attempted to lookup addresses from an invalid account + #[error("Attempted to lookup addresses from an invalid account")] + InvalidAccountData, + + /// Address lookup contains an invalid index + #[error("Address lookup contains an invalid index")] + InvalidLookupIndex, +} + +pub trait AddressLoader: Clone { + fn load_addresses( + self, + lookups: &[MessageAddressTableLookup], + ) -> Result; +} + +#[derive(Clone)] +pub enum SimpleAddressLoader { + Disabled, + Enabled(LoadedAddresses), +} + +impl AddressLoader for SimpleAddressLoader { + fn load_addresses( + self, + _lookups: &[MessageAddressTableLookup], + ) -> Result { + match self { + Self::Disabled => Err(AddressLoaderError::Disabled), + Self::Enabled(loaded_addresses) => Ok(loaded_addresses), + } + } +} diff --git a/sdk/program/src/message/mod.rs b/sdk/program/src/message/mod.rs index be1a0db4fef44f..7f343ff067dd34 100644 --- a/sdk/program/src/message/mod.rs +++ b/sdk/program/src/message/mod.rs @@ -44,10 +44,11 @@ pub mod legacy; #[path = ""] mod non_bpf_modules { mod account_keys; + mod address_loader; mod sanitized; mod versions; - pub use {account_keys::*, sanitized::*, versions::*}; + pub use {account_keys::*, address_loader::*, sanitized::*, versions::*}; } use compiled_keys::*; diff --git a/sdk/program/src/message/sanitized.rs b/sdk/program/src/message/sanitized.rs index 4e938eb93f1e61..d0038cd97d3fca 100644 --- a/sdk/program/src/message/sanitized.rs +++ b/sdk/program/src/message/sanitized.rs @@ -5,7 +5,8 @@ use { message::{ legacy::Message as LegacyMessage, v0::{self, LoadedAddresses}, - AccountKeys, MessageHeader, + AccountKeys, AddressLoader, AddressLoaderError, MessageHeader, + SanitizedVersionedMessage, VersionedMessage, }, nonce::NONCED_TX_MARKER_IX_INDEX, program_utils::limited_deserialize, @@ -35,6 +36,8 @@ pub enum SanitizeMessageError { ValueOutOfBounds, #[error("invalid value")] InvalidValue, + #[error("{0}")] + AddressLoaderError(#[from] AddressLoaderError), } impl From for SanitizeMessageError { @@ -56,6 +59,25 @@ impl TryFrom for SanitizedMessage { } impl SanitizedMessage { + /// Create a sanitized message from a sanitized versioned message. + /// If the input message uses address tables, attempt to look up the + /// address for each table index. + pub fn try_new( + sanitized_msg: SanitizedVersionedMessage, + address_loader: impl AddressLoader, + ) -> Result { + Ok(match sanitized_msg.message { + VersionedMessage::Legacy(message) => { + SanitizedMessage::Legacy(LegacyMessage::new(message)) + } + VersionedMessage::V0(message) => { + let loaded_addresses = + address_loader.load_addresses(&message.address_table_lookups)?; + SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses)) + } + }) + } + /// Return true if this message contains duplicate account keys pub fn has_duplicates(&self) -> bool { match self { diff --git a/sdk/src/transaction/error.rs b/sdk/src/transaction/error.rs index 2fe4e0e3756adf..3f25fa5a62541a 100644 --- a/sdk/src/transaction/error.rs +++ b/sdk/src/transaction/error.rs @@ -1,6 +1,8 @@ use { crate::{ - instruction::InstructionError, message::SanitizeMessageError, sanitize::SanitizeError, + instruction::InstructionError, + message::{AddressLoaderError, SanitizeMessageError}, + sanitize::SanitizeError, }, serde::Serialize, thiserror::Error, @@ -156,7 +158,23 @@ impl From for TransactionError { } impl From for TransactionError { - fn from(_err: SanitizeMessageError) -> Self { - Self::SanitizeFailure + fn from(err: SanitizeMessageError) -> Self { + match err { + SanitizeMessageError::AddressLoaderError(err) => Self::from(err), + _ => Self::SanitizeFailure, + } + } +} + +impl From for TransactionError { + fn from(err: AddressLoaderError) -> Self { + match err { + AddressLoaderError::Disabled => Self::UnsupportedVersion, + AddressLoaderError::SlotHashesSysvarNotFound => Self::AccountNotFound, + AddressLoaderError::LookupTableAccountNotFound => Self::AddressLookupTableNotFound, + AddressLoaderError::InvalidAccountOwner => Self::InvalidAddressLookupTableOwner, + AddressLoaderError::InvalidAccountData => Self::InvalidAddressLookupTableData, + AddressLoaderError::InvalidLookupIndex => Self::InvalidAddressLookupTableIndex, + } } } diff --git a/sdk/src/transaction/sanitized.rs b/sdk/src/transaction/sanitized.rs index 28136fdc79498a..a26b716e64a688 100644 --- a/sdk/src/transaction/sanitized.rs +++ b/sdk/src/transaction/sanitized.rs @@ -1,12 +1,19 @@ #![cfg(feature = "full")] +pub use crate::message::{AddressLoader, SimpleAddressLoader}; use { super::SanitizedVersionedTransaction, crate::{ hash::Hash, message::{ +<<<<<<< HEAD v0::{self, LoadedAddresses, MessageAddressTableLookup}, SanitizedMessage, VersionedMessage, +======= + legacy, + v0::{self, LoadedAddresses}, + LegacyMessage, SanitizedMessage, VersionedMessage, +>>>>>>> ddf95c181 (RPC: Support versioned txs in getFeeForMessage API (#28217)) }, precompiles::verify_if_precompile, pubkey::Pubkey, @@ -42,25 +49,6 @@ pub struct TransactionAccountLocks<'a> { pub writable: Vec<&'a Pubkey>, } -pub trait AddressLoader: Clone { - fn load_addresses(self, lookups: &[MessageAddressTableLookup]) -> Result; -} - -#[derive(Clone)] -pub enum SimpleAddressLoader { - Disabled, - Enabled(LoadedAddresses), -} - -impl AddressLoader for SimpleAddressLoader { - fn load_addresses(self, _lookups: &[MessageAddressTableLookup]) -> Result { - match self { - Self::Disabled => Err(TransactionError::AddressLookupTableNotFound), - Self::Enabled(loaded_addresses) => Ok(loaded_addresses), - } - } -} - /// Type that represents whether the transaction message has been precomputed or /// not. pub enum MessageHash {