Skip to content

Commit

Permalink
RPC: Support versioned txs in getFeeForMessage API (backport #28217) (#…
Browse files Browse the repository at this point in the history
…28221)

* 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 <[email protected]>

Co-authored-by: Tyera Eulberg <[email protected]>
(cherry picked from commit ddf95c1)

# Conflicts:
#	client/src/rpc_client.rs
#	rpc/src/rpc.rs
#	runtime/src/bank/address_lookup_table.rs
#	sdk/src/transaction/sanitized.rs

* resolve conflicts

Co-authored-by: Justin Starry <[email protected]>
  • Loading branch information
mergify[bot] and jstarry authored Oct 4, 2022
1 parent 09aa69f commit 05ffda9
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 96 deletions.
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

0 comments on commit 05ffda9

Please sign in to comment.