Skip to content

Commit

Permalink
Partial fee estimates for SubmittableExtrinsic (paritytech#910)
Browse files Browse the repository at this point in the history
* add partial_fee estimation to submittable extrinsic

* add integration test

* make functions immune to doctest

* add doc test

* inline encoded_with_len, fix tests

* fix test fmt

* remove unused imoort

* Bump h2 from 0.3.16 to 0.3.17 (paritytech#911)

Bumps [h2](https://github.com/hyperium/h2) from 0.3.16 to 0.3.17.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/master/CHANGELOG.md)
- [Commits](hyperium/h2@v0.3.16...v0.3.17)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* call_raw returns Res: Decode

* remove import

* remove struct

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • Loading branch information
tadeohepperle and dependabot[bot] authored Apr 17, 2023
1 parent e642740 commit 2997b1b
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 69 deletions.
9 changes: 4 additions & 5 deletions subxt/src/client/online_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::{
tx::TxClient,
Config, Metadata,
};
use codec::{Compact, Decode};
use codec::Compact;
use derivative::Derivative;
use frame_metadata::RuntimeMetadataPrefixed;
use futures::future;
Expand Down Expand Up @@ -136,10 +136,9 @@ impl<T: Config> OnlineClient<T> {

/// Fetch the metadata from substrate using the runtime API.
async fn fetch_metadata(rpc: &Rpc<T>) -> Result<Metadata, Error> {
let bytes = rpc.state_call("Metadata_metadata", None, None).await?;
let cursor = &mut &*bytes;
let _ = <Compact<u32>>::decode(cursor)?;
let meta: RuntimeMetadataPrefixed = Decode::decode(cursor)?;
let (_, meta) = rpc
.state_call::<(Compact<u32>, RuntimeMetadataPrefixed)>("Metadata_metadata", None, None)
.await?;
Ok(meta.try_into()?)
}

Expand Down
41 changes: 13 additions & 28 deletions subxt/src/rpc/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,19 @@
//! # }
//! ```
use std::sync::Arc;

use codec::{Decode, Encode};
use frame_metadata::RuntimeMetadataPrefixed;
use serde::Serialize;

use crate::{error::Error, utils::PhantomDataSendSync, Config, Metadata};

use super::{
rpc_params,
types::{self, ChainHeadEvent, FollowEvent},
RpcClient, RpcClientT, Subscription,
};
use crate::{error::Error, utils::PhantomDataSendSync, Config, Metadata};
use codec::{Decode, Encode};
use frame_metadata::RuntimeMetadataPrefixed;
use serde::Serialize;
use std::sync::Arc;

/// Client for substrate rpc interfaces
pub struct Rpc<T: Config> {
Expand Down Expand Up @@ -151,25 +154,6 @@ impl<T: Config> Rpc<T> {
Ok(metadata)
}

/// Execute a runtime API call.
pub async fn call(
&self,
function: String,
call_parameters: Option<&[u8]>,
at: Option<T::Hash>,
) -> Result<types::Bytes, Error> {
let call_parameters = call_parameters.unwrap_or_default();

let bytes: types::Bytes = self
.client
.request(
"state_call",
rpc_params![function, to_hex(call_parameters), at],
)
.await?;
Ok(bytes)
}

/// Fetch system properties
pub async fn system_properties(&self) -> Result<types::SystemProperties, Error> {
self.client
Expand Down Expand Up @@ -364,22 +348,23 @@ impl<T: Config> Rpc<T> {
}

/// Execute a runtime API call.
pub async fn state_call(
pub async fn state_call<Res: Decode>(
&self,
function: &str,
call_parameters: Option<&[u8]>,
at: Option<T::Hash>,
) -> Result<types::Bytes, Error> {
) -> Result<Res, Error> {
let call_parameters = call_parameters.unwrap_or_default();

let bytes: types::Bytes = self
.client
.request(
"state_call",
rpc_params![function, to_hex(call_parameters), at],
)
.await?;
Ok(bytes)
let cursor = &mut &bytes[..];
let res: Res = Decode::decode(cursor)?;
Ok(res)
}

/// Create and submit an extrinsic and return a subscription to the events triggered.
Expand Down
9 changes: 5 additions & 4 deletions subxt/src/runtime_api/runtime_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// see LICENSE for license details.

use crate::{client::OnlineClientT, error::Error, Config};
use codec::Decode;
use derivative::Derivative;
use std::{future::Future, marker::PhantomData};

Expand Down Expand Up @@ -32,21 +33,21 @@ where
Client: OnlineClientT<T>,
{
/// Execute a raw runtime API call.
pub fn call_raw<'a>(
pub fn call_raw<'a, Res: Decode>(
&self,
function: &'a str,
call_parameters: Option<&'a [u8]>,
) -> impl Future<Output = Result<Vec<u8>, Error>> + 'a {
) -> impl Future<Output = Result<Res, Error>> + 'a {
let client = self.client.clone();
let block_hash = self.block_hash;
// Ensure that the returned future doesn't have a lifetime tied to api.runtime_api(),
// which is a temporary thing we'll be throwing away quickly:
async move {
let data = client
let data: Res = client
.rpc()
.state_call(function, call_parameters, Some(block_hash))
.await?;
Ok(data.0)
Ok(data)
}
}
}
30 changes: 25 additions & 5 deletions subxt/src/tx/tx_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use super::TxPayload;
use std::borrow::Cow;

use codec::{Compact, Encode};
use derivative::Derivative;

use crate::{
client::{OfflineClientT, OnlineClientT},
config::{Config, ExtrinsicParams, Hasher},
error::Error,
tx::{Signer as SignerT, TxProgress},
tx::{Signer as SignerT, TxPayload, TxProgress},
utils::{Encoded, PhantomDataSendSync},
};
use codec::{Compact, Encode};
use derivative::Derivative;
use std::borrow::Cow;

// This is returned from an API below, so expose it here.
pub use crate::rpc::types::DryRunResult;
Expand Down Expand Up @@ -465,4 +466,23 @@ where
let dry_run_bytes = self.client.rpc().dry_run(self.encoded(), at).await?;
dry_run_bytes.into_dry_run_result(&self.client.metadata())
}

/// This returns an estimate for what the extrinsic is expected to cost to execute, less any tips.
/// The actual amount paid can vary from block to block based on node traffic and other factors.
pub async fn partial_fee_estimate(&self) -> Result<u128, Error> {
let mut params = self.encoded().to_vec();
(self.encoded().len() as u32).encode_to(&mut params);
// destructuring RuntimeDispatchInfo, see type information <https://paritytech.github.io/substrate/master/pallet_transaction_payment_rpc_runtime_api/struct.RuntimeDispatchInfo.html>
// data layout: {weight_ref_time: Compact<u64>, weight_proof_size: Compact<u64>, class: u8, partial_fee: u128}
let (_, _, _, partial_fee) = self
.client
.rpc()
.state_call::<(Compact<u64>, Compact<u64>, u8, u128)>(
"TransactionPaymentApi_query_info",
Some(&params),
None,
)
.await?;
Ok(partial_fee)
}
}
29 changes: 13 additions & 16 deletions subxt/src/tx/tx_progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,11 +416,7 @@ mod test {

use crate::{
client::{OfflineClientT, OnlineClientT},
config::{
extrinsic_params::BaseExtrinsicParams,
polkadot::{PlainTip, PolkadotConfig},
WithExtrinsicParams,
},
config::{extrinsic_params::BaseExtrinsicParams, polkadot::PlainTip, WithExtrinsicParams},
error::RpcError,
rpc::{types::SubstrateTxStatus, RpcSubscription, Subscription},
tx::TxProgress,
Expand All @@ -429,15 +425,23 @@ mod test {

use serde_json::value::RawValue;

type MockTxProgress = TxProgress<SubstrateConfig, MockClient>;
type MockHash = <WithExtrinsicParams<
SubstrateConfig,
BaseExtrinsicParams<SubstrateConfig, PlainTip>,
> as Config>::Hash;
type MockSubstrateTxStatus = SubstrateTxStatus<MockHash, MockHash>;

/// a mock client to satisfy trait bounds in tests
#[derive(Clone, Debug)]
struct MockClient;

impl OfflineClientT<PolkadotConfig> for MockClient {
impl OfflineClientT<SubstrateConfig> for MockClient {
fn metadata(&self) -> crate::Metadata {
panic!("just a mock impl to satisfy trait bounds")
}

fn genesis_hash(&self) -> <PolkadotConfig as crate::Config>::Hash {
fn genesis_hash(&self) -> <SubstrateConfig as crate::Config>::Hash {
panic!("just a mock impl to satisfy trait bounds")
}

Expand All @@ -446,15 +450,8 @@ mod test {
}
}

type MockTxProgress = TxProgress<PolkadotConfig, MockClient>;
type MockHash = <WithExtrinsicParams<
SubstrateConfig,
BaseExtrinsicParams<SubstrateConfig, PlainTip>,
> as Config>::Hash;
type MockSubstrateTxStatus = SubstrateTxStatus<MockHash, MockHash>;

impl OnlineClientT<PolkadotConfig> for MockClient {
fn rpc(&self) -> &crate::rpc::Rpc<PolkadotConfig> {
impl OnlineClientT<SubstrateConfig> for MockClient {
fn rpc(&self) -> &crate::rpc::Rpc<SubstrateConfig> {
panic!("just a mock impl to satisfy trait bounds")
}
}
Expand Down
9 changes: 4 additions & 5 deletions testing/integration-tests/src/blocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// see LICENSE for license details.

use crate::test_context;
use codec::{Compact, Decode};
use codec::Compact;
use frame_metadata::RuntimeMetadataPrefixed;
use futures::StreamExt;

Expand Down Expand Up @@ -101,10 +101,9 @@ async fn runtime_api_call() -> Result<(), subxt::Error> {
let block = sub.next().await.unwrap()?;
let rt = block.runtime_api().await?;

let bytes = rt.call_raw("Metadata_metadata", None).await?;
let cursor = &mut &*bytes;
let _ = <Compact<u32>>::decode(cursor)?;
let meta: RuntimeMetadataPrefixed = Decode::decode(cursor)?;
let (_, meta) = rt
.call_raw::<(Compact<u32>, RuntimeMetadataPrefixed)>("Metadata_metadata", None)
.await?;
let metadata_call = match meta.1 {
frame_metadata::RuntimeMetadata::V14(metadata) => metadata,
_ => panic!("Metadata V14 unavailable"),
Expand Down
80 changes: 74 additions & 6 deletions testing/integration-tests/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,15 +388,11 @@ async fn rpc_state_call() {
let api = ctx.client();

// Call into the runtime of the chain to get the Metadata.
let metadata_bytes = api
let (_, meta) = api
.rpc()
.state_call("Metadata_metadata", None, None)
.state_call::<(Compact<u32>, RuntimeMetadataPrefixed)>("Metadata_metadata", None, None)
.await
.unwrap();

let cursor = &mut &*metadata_bytes;
let _ = <Compact<u32>>::decode(cursor).unwrap();
let meta: RuntimeMetadataPrefixed = Decode::decode(cursor).unwrap();
let metadata_call = match meta.1 {
frame_metadata::RuntimeMetadata::V14(metadata) => metadata,
_ => panic!("Metadata V14 unavailable"),
Expand Down Expand Up @@ -582,3 +578,75 @@ async fn chainhead_unstable_unpin() {
.await
.is_err());
}

/// taken from original type <https://docs.rs/pallet-transaction-payment/latest/pallet_transaction_payment/struct.FeeDetails.html>
#[derive(Encode, Decode, Debug, Clone, Eq, PartialEq)]
pub struct FeeDetails {
/// The minimum fee for a transaction to be included in a block.
pub inclusion_fee: Option<InclusionFee>,
/// tip
pub tip: u128,
}

/// taken from original type <https://docs.rs/pallet-transaction-payment/latest/pallet_transaction_payment/struct.InclusionFee.html>
/// The base fee and adjusted weight and length fees constitute the _inclusion fee_.
#[derive(Encode, Decode, Debug, Clone, Eq, PartialEq)]
pub struct InclusionFee {
/// minimum amount a user pays for a transaction.
pub base_fee: u128,
/// amount paid for the encoded length (in bytes) of the transaction.
pub len_fee: u128,
///
/// - `targeted_fee_adjustment`: This is a multiplier that can tune the final fee based on the
/// congestion of the network.
/// - `weight_fee`: This amount is computed based on the weight of the transaction. Weight
/// accounts for the execution time of a transaction.
///
/// adjusted_weight_fee = targeted_fee_adjustment * weight_fee
pub adjusted_weight_fee: u128,
}

#[tokio::test]
async fn partial_fee_estimate_correct() {
let ctx = test_context().await;
let api = ctx.client();

let alice = pair_signer(AccountKeyring::Alice.pair());
let hans = pair_signer(Sr25519Pair::generate().0);

let tx = node_runtime::tx()
.balances()
.transfer(hans.account_id().clone().into(), 1_000_000_000_000);

let signed_extrinsic = api
.tx()
.create_signed(&tx, &alice, Default::default())
.await
.unwrap();

// Method I: TransactionPaymentApi_query_info
let partial_fee_1 = signed_extrinsic.partial_fee_estimate().await.unwrap();

// Method II: TransactionPaymentApi_query_fee_details + calculations
let len_bytes: [u8; 4] = (signed_extrinsic.encoded().len() as u32).to_le_bytes();
let encoded_with_len = [signed_extrinsic.encoded(), &len_bytes[..]].concat();
let InclusionFee {
base_fee,
len_fee,
adjusted_weight_fee,
} = api
.rpc()
.state_call::<FeeDetails>(
"TransactionPaymentApi_query_fee_details",
Some(&encoded_with_len),
None,
)
.await
.unwrap()
.inclusion_fee
.unwrap();
let partial_fee_2 = base_fee + len_fee + adjusted_weight_fee;

// Both methods should yield the same fee
assert_eq!(partial_fee_1, partial_fee_2);
}

0 comments on commit 2997b1b

Please sign in to comment.