Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC: Support versioned txs in getFeeForMessage API (backport #28217) #28221

Merged
merged 2 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/src/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ pub fn get_fee_for_messages(
) -> Result<u64, CliError> {
Ok(messages
.iter()
.map(|message| rpc_client.get_fee_for_message(message))
.map(|message| rpc_client.get_fee_for_message(*message))
.collect::<Result<Vec<_>, _>>()?
.iter()
.sum())
Expand Down
42 changes: 17 additions & 25 deletions client/src/nonblocking/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use {
http_sender::HttpSender,
mock_sender::MockSender,
rpc_client::{
GetConfirmedSignaturesForAddress2Config, RpcClientConfig, SerializableTransaction,
GetConfirmedSignaturesForAddress2Config, RpcClientConfig, SerializableMessage,
SerializableTransaction,
},
rpc_config::{RpcAccountInfoConfig, *},
rpc_filter::{self, RpcFilterType},
Expand All @@ -42,10 +43,9 @@ use {
epoch_schedule::EpochSchedule,
fee_calculator::{FeeCalculator, FeeRateGovernor},
hash::Hash,
message::Message,
pubkey::Pubkey,
signature::Signature,
transaction::{self},
transaction,
},
solana_transaction_status::{
EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus,
Expand Down Expand Up @@ -5353,28 +5353,20 @@ impl RpcClient {
}

#[allow(deprecated)]
pub async fn get_fee_for_message(&self, message: &Message) -> ClientResult<u64> {
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>(message, UiTransactionEncoding::Base64)?;
let result = self
.send::<Response<Option<u64>>>(
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<u64> {
let serialized_encoded = serialize_and_encode(message, UiTransactionEncoding::Base64)?;
let result = self
.send::<Response<Option<u64>>>(
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<Hash> {
Expand Down
10 changes: 8 additions & 2 deletions client/src/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use {
epoch_schedule::EpochSchedule,
fee_calculator::{FeeCalculator, FeeRateGovernor},
hash::Hash,
message::Message,
message::{v0, Message as LegacyMessage},
pubkey::Pubkey,
signature::Signature,
transaction::{self, uses_durable_nonce, Transaction, VersionedTransaction},
Expand All @@ -61,6 +61,12 @@ impl RpcClientConfig {
}
}

/// 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 {
Expand Down Expand Up @@ -4123,7 +4129,7 @@ impl RpcClient {
}

#[allow(deprecated)]
pub fn get_fee_for_message(&self, message: &Message) -> ClientResult<u64> {
pub fn get_fee_for_message(&self, message: &impl SerializableMessage) -> ClientResult<u64> {
self.invoke(self.rpc_client.get_fee_for_message(message))
}

Expand Down
12 changes: 6 additions & 6 deletions programs/address-lookup-table/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[cfg(not(target_os = "solana"))]
use solana_sdk::transaction::TransactionError;
use solana_program::message::AddressLoaderError;
use thiserror::Error;

#[derive(Debug, Error, PartialEq, Eq, Clone)]
Expand All @@ -22,13 +22,13 @@ pub enum AddressLookupError {
}

#[cfg(not(target_os = "solana"))]
impl From<AddressLookupError> for TransactionError {
impl From<AddressLookupError> for AddressLoaderError {
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,
}
}
}
123 changes: 91 additions & 32 deletions rpc/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ use {
feature_set,
fee_calculator::FeeCalculator,
hash::Hash,
message::{Message, SanitizedMessage},
message::SanitizedMessage,
pubkey::{Pubkey, PUBKEY_BYTES},
signature::{Keypair, Signature, Signer},
stake::state::{StakeActivationStatus, StakeState},
Expand Down Expand Up @@ -2161,16 +2161,6 @@ impl JsonRpcRequestProcessor {
Ok(new_response(&bank, is_valid))
}

fn get_fee_for_message(
&self,
message: &SanitizedMessage,
config: RpcContextConfig,
) -> Result<RpcResponse<Option<u64>>> {
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<RpcResponse<u64>> {
let bank = self.get_bank_with_config(config)?;
let stake_minimum_delegation =
Expand Down Expand Up @@ -3270,7 +3260,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;
Expand Down Expand Up @@ -3978,12 +3971,21 @@ pub mod rpc_full {
config: Option<RpcContextConfig>,
) -> Result<RpcResponse<Option<u64>>> {
debug!("get_fee_for_message rpc request received");
let (_, message) =
decode_and_deserialize::<Message>(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::<VersionedMessage>(
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))
}

fn get_stake_minimum_delegation(
Expand Down Expand Up @@ -4602,10 +4604,13 @@ pub mod tests {
solana_sdk::{
account::{Account, WritableAccount},
clock::MAX_RECENT_BLOCKHASHES,
fee_calculator::DEFAULT_BURN_PERCENT,
fee_calculator::{FeeRateGovernor, DEFAULT_BURN_PERCENT},
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},
Expand Down Expand Up @@ -4636,7 +4641,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>) -> serde_json::Value {
Expand Down Expand Up @@ -4781,8 +4787,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],
Expand Down Expand Up @@ -5409,7 +5419,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
Expand Down Expand Up @@ -5486,7 +5496,7 @@ pub mod tests {
let expected = json!([
{
"owner": "11111111111111111111111111111111",
"lamports": 1_000_000,
"lamports": TEST_MINT_LAMPORTS,
"data": ["", "base64"],
"executable": false,
"rentEpoch": 0
Expand Down Expand Up @@ -5518,7 +5528,7 @@ pub mod tests {
let expected = json!([
{
"owner": "11111111111111111111111111111111",
"lamports": 1_000_000,
"lamports": TEST_MINT_LAMPORTS,
"data": ["", "base58"],
"executable": false,
"rentEpoch": 0
Expand Down Expand Up @@ -6105,7 +6115,7 @@ pub mod tests {
"value":{
"blockhash": recent_blockhash.to_string(),
"feeCalculator": {
"lamportsPerSignature": 0,
"lamportsPerSignature": TEST_SIGNATURE_FEE,
}
},
},
Expand Down Expand Up @@ -6134,7 +6144,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,
Expand Down Expand Up @@ -6214,9 +6224,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
}
},
Expand Down Expand Up @@ -6484,6 +6494,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);
(
Expand Down Expand Up @@ -8390,7 +8401,7 @@ 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(),
)
);
}
Expand All @@ -8411,4 +8422,52 @@ pub mod tests {
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<u64> = 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<u64> = parse_success_result(rpc.handle_request_sync(request));
assert_eq!(response.value, TEST_SIGNATURE_FEE);
}
}
}
Loading