mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-10 17:11:03 +00:00
Use RPC call to get account nonce (#476)
* remove code related to getting nonce from storage and use RPC call instead * cargo fmt * move nonce fetching into Rpc, since it's just an RPC call * clippy
This commit is contained in:
@@ -90,7 +90,7 @@ pub fn generate_calls(
|
||||
pub fn #fn_name(
|
||||
&self,
|
||||
#( #call_fn_args, )*
|
||||
) -> ::subxt::SubmittableExtrinsic<'a, T, X, A, #call_struct_name, DispatchError, root_mod::Event> {
|
||||
) -> ::subxt::SubmittableExtrinsic<'a, T, X, #call_struct_name, DispatchError, root_mod::Event> {
|
||||
let call = #call_struct_name { #( #call_args, )* };
|
||||
::subxt::SubmittableExtrinsic::new(self.client, call)
|
||||
}
|
||||
@@ -108,16 +108,15 @@ pub fn generate_calls(
|
||||
|
||||
#( #call_structs )*
|
||||
|
||||
pub struct TransactionApi<'a, T: ::subxt::Config, X, A> {
|
||||
pub struct TransactionApi<'a, T: ::subxt::Config, X> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
marker: ::core::marker::PhantomData<(X, A)>,
|
||||
marker: ::core::marker::PhantomData<X>,
|
||||
}
|
||||
|
||||
impl<'a, T, X, A> TransactionApi<'a, T, X, A>
|
||||
impl<'a, T, X> TransactionApi<'a, T, X>
|
||||
where
|
||||
T: ::subxt::Config,
|
||||
X: ::subxt::SignedExtra<T>,
|
||||
A: ::subxt::AccountData,
|
||||
{
|
||||
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
|
||||
Self { client, marker: ::core::marker::PhantomData }
|
||||
|
||||
+10
-95
@@ -49,10 +49,8 @@ use crate::{
|
||||
use codec::Decode;
|
||||
use frame_metadata::{
|
||||
v14::RuntimeMetadataV14,
|
||||
PalletMetadata,
|
||||
RuntimeMetadata,
|
||||
RuntimeMetadataPrefixed,
|
||||
StorageEntryType,
|
||||
};
|
||||
use heck::ToSnakeCase as _;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
@@ -61,7 +59,6 @@ use quote::{
|
||||
format_ident,
|
||||
quote,
|
||||
};
|
||||
use scale_info::form::PortableForm;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
@@ -268,16 +265,6 @@ impl RuntimeGenerator {
|
||||
let has_module_error_impl =
|
||||
errors::generate_has_module_error_impl(&self.metadata, types_mod_ident);
|
||||
|
||||
let default_account_data_ident = format_ident!("DefaultAccountData");
|
||||
let default_account_data_impl = generate_default_account_data_impl(
|
||||
&pallets_with_mod_names,
|
||||
&default_account_data_ident,
|
||||
&type_gen,
|
||||
);
|
||||
let type_parameter_default_impl = default_account_data_impl
|
||||
.as_ref()
|
||||
.map(|_| quote!( = #default_account_data_ident ));
|
||||
|
||||
quote! {
|
||||
#[allow(dead_code, unused_imports, non_camel_case_types)]
|
||||
pub mod #mod_ident {
|
||||
@@ -293,29 +280,25 @@ impl RuntimeGenerator {
|
||||
// Impl HasModuleError on DispatchError so we can pluck out module error details.
|
||||
#has_module_error_impl
|
||||
|
||||
#default_account_data_impl
|
||||
|
||||
pub struct RuntimeApi<T: ::subxt::Config, X, A #type_parameter_default_impl> {
|
||||
pub struct RuntimeApi<T: ::subxt::Config, X> {
|
||||
pub client: ::subxt::Client<T>,
|
||||
marker: ::core::marker::PhantomData<(X, A)>,
|
||||
marker: ::core::marker::PhantomData<X>,
|
||||
}
|
||||
|
||||
impl<T, X, A> ::core::convert::From<::subxt::Client<T>> for RuntimeApi<T, X, A>
|
||||
impl<T, X> ::core::convert::From<::subxt::Client<T>> for RuntimeApi<T, X>
|
||||
where
|
||||
T: ::subxt::Config,
|
||||
X: ::subxt::SignedExtra<T>,
|
||||
A: ::subxt::AccountData,
|
||||
X: ::subxt::SignedExtra<T>
|
||||
{
|
||||
fn from(client: ::subxt::Client<T>) -> Self {
|
||||
Self { client, marker: ::core::marker::PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, X, A> RuntimeApi<T, X, A>
|
||||
impl<'a, T, X> RuntimeApi<T, X>
|
||||
where
|
||||
T: ::subxt::Config,
|
||||
X: ::subxt::SignedExtra<T>,
|
||||
A: ::subxt::AccountData,
|
||||
{
|
||||
pub fn constants(&'a self) -> ConstantsApi {
|
||||
ConstantsApi
|
||||
@@ -325,7 +308,7 @@ impl RuntimeGenerator {
|
||||
StorageApi { client: &self.client }
|
||||
}
|
||||
|
||||
pub fn tx(&'a self) -> TransactionApi<'a, T, X, A> {
|
||||
pub fn tx(&'a self) -> TransactionApi<'a, T, X> {
|
||||
TransactionApi { client: &self.client, marker: ::core::marker::PhantomData }
|
||||
}
|
||||
|
||||
@@ -377,19 +360,18 @@ impl RuntimeGenerator {
|
||||
)*
|
||||
}
|
||||
|
||||
pub struct TransactionApi<'a, T: ::subxt::Config, X, A> {
|
||||
pub struct TransactionApi<'a, T: ::subxt::Config, X> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
marker: ::core::marker::PhantomData<(X, A)>,
|
||||
marker: ::core::marker::PhantomData<X>,
|
||||
}
|
||||
|
||||
impl<'a, T, X, A> TransactionApi<'a, T, X, A>
|
||||
impl<'a, T, X> TransactionApi<'a, T, X>
|
||||
where
|
||||
T: ::subxt::Config,
|
||||
X: ::subxt::SignedExtra<T>,
|
||||
A: ::subxt::AccountData,
|
||||
{
|
||||
#(
|
||||
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi<'a, T, X, A> {
|
||||
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi<'a, T, X> {
|
||||
#pallets_with_calls::calls::TransactionApi::new(self.client)
|
||||
}
|
||||
)*
|
||||
@@ -399,73 +381,6 @@ impl RuntimeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Most chains require a valid account nonce as part of the extrinsic, so the default behaviour of
|
||||
/// the client is to fetch the nonce for the current account.
|
||||
///
|
||||
/// The account index (aka nonce) is commonly stored in the `System` pallet's `Account` storage item.
|
||||
/// This function attempts to find that storage item, and if it is present will implement the
|
||||
/// `subxt::AccountData` trait for it. This allows the client to construct the appropriate
|
||||
/// storage key from the account id, and then retrieve the `nonce` from the resulting storage item.
|
||||
fn generate_default_account_data_impl(
|
||||
pallets_with_mod_names: &[(&PalletMetadata<PortableForm>, syn::Ident)],
|
||||
default_impl_name: &syn::Ident,
|
||||
type_gen: &TypeGenerator,
|
||||
) -> Option<TokenStream2> {
|
||||
let storage = pallets_with_mod_names
|
||||
.iter()
|
||||
.find(|(pallet, _)| pallet.name == "System")
|
||||
.and_then(|(pallet, _)| pallet.storage.as_ref())?;
|
||||
let storage_entry = storage
|
||||
.entries
|
||||
.iter()
|
||||
.find(|entry| entry.name == "Account")?;
|
||||
|
||||
// resolve the concrete types for `AccountId` (to build the key) and `Index` to extract the
|
||||
// account index (nonce) value from the result.
|
||||
let (account_id_ty, account_nonce_ty) =
|
||||
if let StorageEntryType::Map { key, value, .. } = &storage_entry.ty {
|
||||
let account_id_ty = type_gen.resolve_type_path(key.id(), &[]);
|
||||
let account_data_ty = type_gen.resolve_type(value.id());
|
||||
let nonce_field = if let scale_info::TypeDef::Composite(composite) =
|
||||
account_data_ty.type_def()
|
||||
{
|
||||
composite
|
||||
.fields()
|
||||
.iter()
|
||||
.find(|f| f.name() == Some(&"nonce".to_string()))?
|
||||
} else {
|
||||
abort_call_site!("Expected a `nonce` field in the account info struct")
|
||||
};
|
||||
let account_nonce_ty = type_gen.resolve_type_path(nonce_field.ty().id(), &[]);
|
||||
(account_id_ty, account_nonce_ty)
|
||||
} else {
|
||||
abort_call_site!("System::Account should be a `StorageEntryType::Map`")
|
||||
};
|
||||
|
||||
// this path to the storage entry depends on storage codegen.
|
||||
// AccountOwned contains the same data as Account does, but without references.
|
||||
let storage_entry_path = quote!(self::system::storage::AccountOwned);
|
||||
|
||||
Some(quote! {
|
||||
/// The default storage entry from which to fetch an account nonce, required for
|
||||
/// constructing a transaction.
|
||||
pub enum #default_impl_name {}
|
||||
|
||||
impl ::subxt::AccountData for #default_impl_name {
|
||||
type StorageEntry = #storage_entry_path;
|
||||
type AccountId = #account_id_ty;
|
||||
type Index = #account_nonce_ty;
|
||||
|
||||
fn storage_entry(account_id: Self::AccountId) -> Self::StorageEntry {
|
||||
#storage_entry_path(account_id)
|
||||
}
|
||||
fn nonce(result: &<Self::StorageEntry as ::subxt::StorageEntry>::Value) -> Self::Index {
|
||||
result.nonce
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn generate_structs_from_variants<'a, F>(
|
||||
type_gen: &'a TypeGenerator,
|
||||
type_id: u32,
|
||||
|
||||
@@ -74,8 +74,6 @@ fn generate_storage_entry_fns(
|
||||
storage_entry: &StorageEntryMetadata<PortableForm>,
|
||||
) -> (TokenStream2, TokenStream2) {
|
||||
let entry_struct_ident = format_ident!("{}", storage_entry.name);
|
||||
let is_account_wrapper = pallet.name == "System" && storage_entry.name == "Account";
|
||||
let wrapper_struct_ident = format_ident!("{}Owned", storage_entry.name);
|
||||
let (fields, entry_struct, constructor, key_impl, should_ref) = match storage_entry.ty
|
||||
{
|
||||
StorageEntryType::Plain(_) => {
|
||||
@@ -183,18 +181,6 @@ fn generate_storage_entry_fns(
|
||||
|
||||
let ty_path = type_gen.resolve_type_path(key.id(), &[]);
|
||||
let fields = vec![(format_ident!("_0"), ty_path.clone())];
|
||||
// `::system::storage::Account` was utilized as associated type `StorageEntry`
|
||||
// for `::subxt::AccountData` implementation by the generated `DefaultAccountData`.
|
||||
// Due to changes in the storage API, `::system::storage::Account` cannot be
|
||||
// used without specifying a lifetime. To satisfy `::subxt::AccountData`
|
||||
// implementation, a non-reference wrapper `AccountOwned` is generated.
|
||||
let wrapper_struct = if is_account_wrapper {
|
||||
quote!(
|
||||
pub struct #wrapper_struct_ident ( pub #ty_path );
|
||||
)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
// `ty_path` can be `std::vec::Vec<T>`. In such cases, the entry struct
|
||||
// should contain a slice reference.
|
||||
@@ -204,7 +190,6 @@ fn generate_storage_entry_fns(
|
||||
};
|
||||
let entry_struct = quote! {
|
||||
pub struct #entry_struct_ident #lifetime_param( pub #lifetime_ref #ty_slice );
|
||||
#wrapper_struct
|
||||
};
|
||||
let constructor = quote!( #entry_struct_ident(_0) );
|
||||
let hasher = hashers.get(0).unwrap_or_else(|| {
|
||||
@@ -256,27 +241,11 @@ fn generate_storage_entry_fns(
|
||||
}
|
||||
);
|
||||
|
||||
// The wrapper account must implement the same trait as the counter-part Account,
|
||||
// with the same pallet and storage. This continues the implementation of the wrapper
|
||||
// generated with the Account.
|
||||
let wrapper_entry_impl = if is_account_wrapper {
|
||||
quote!(
|
||||
impl ::subxt::StorageEntry for #wrapper_struct_ident {
|
||||
#storage_entry_impl
|
||||
}
|
||||
)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let storage_entry_type = quote! {
|
||||
#entry_struct
|
||||
|
||||
impl ::subxt::StorageEntry for #entry_struct_ident #anon_lifetime {
|
||||
#storage_entry_impl
|
||||
}
|
||||
|
||||
#wrapper_entry_impl
|
||||
};
|
||||
|
||||
let client_iter_fn = if matches!(storage_entry.ty, StorageEntryType::Map { .. }) {
|
||||
|
||||
+7
-29
@@ -37,7 +37,6 @@ use crate::{
|
||||
},
|
||||
storage::StorageClient,
|
||||
transaction::TransactionProgress,
|
||||
AccountData,
|
||||
Call,
|
||||
Config,
|
||||
Metadata,
|
||||
@@ -170,23 +169,6 @@ impl<T: Config> Client<T> {
|
||||
StorageClient::new(&self.rpc, &self.metadata, self.iter_page_size)
|
||||
}
|
||||
|
||||
/// Fetch the current nonce for the given account id.
|
||||
pub async fn fetch_nonce<A: AccountData>(
|
||||
&self,
|
||||
account: &T::AccountId,
|
||||
) -> Result<T::Index, BasicError>
|
||||
where
|
||||
<A as AccountData>::AccountId: From<<T as Config>::AccountId>,
|
||||
<A as AccountData>::Index: Into<<T as Config>::Index>,
|
||||
{
|
||||
let account_storage_entry = A::storage_entry(account.clone().into());
|
||||
let account_data = self
|
||||
.storage()
|
||||
.fetch_or_default(&account_storage_entry, None)
|
||||
.await?;
|
||||
Ok(A::nonce(&account_data).into())
|
||||
}
|
||||
|
||||
/// Convert the client to a runtime api wrapper for custom runtime access.
|
||||
///
|
||||
/// The `subxt` proc macro will provide methods to submit extrinsics and read storage specific
|
||||
@@ -197,17 +179,16 @@ impl<T: Config> Client<T> {
|
||||
}
|
||||
|
||||
/// A constructed call ready to be signed and submitted.
|
||||
pub struct SubmittableExtrinsic<'client, T: Config, X, A, C, E: Decode, Evs: Decode> {
|
||||
pub struct SubmittableExtrinsic<'client, T: Config, X, C, E: Decode, Evs: Decode> {
|
||||
client: &'client Client<T>,
|
||||
call: C,
|
||||
marker: std::marker::PhantomData<(X, A, E, Evs)>,
|
||||
marker: std::marker::PhantomData<(X, E, Evs)>,
|
||||
}
|
||||
|
||||
impl<'client, T, X, A, C, E, Evs> SubmittableExtrinsic<'client, T, X, A, C, E, Evs>
|
||||
impl<'client, T, X, C, E, Evs> SubmittableExtrinsic<'client, T, X, C, E, Evs>
|
||||
where
|
||||
T: Config,
|
||||
X: SignedExtra<T>,
|
||||
A: AccountData,
|
||||
C: Call + Send + Sync,
|
||||
E: Decode + HasModuleError,
|
||||
Evs: Decode,
|
||||
@@ -232,8 +213,6 @@ where
|
||||
where
|
||||
<<X as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned:
|
||||
Send + Sync + 'static,
|
||||
<A as AccountData>::AccountId: From<<T as Config>::AccountId>,
|
||||
<A as AccountData>::Index: Into<<T as Config>::Index>,
|
||||
{
|
||||
// Sign the call data to create our extrinsic.
|
||||
let extrinsic = self.create_signed(signer, Default::default()).await?;
|
||||
@@ -262,8 +241,6 @@ where
|
||||
where
|
||||
<<X as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned:
|
||||
Send + Sync + 'static,
|
||||
<A as AccountData>::AccountId: From<<T as Config>::AccountId>,
|
||||
<A as AccountData>::Index: Into<<T as Config>::Index>,
|
||||
{
|
||||
let extrinsic = self.create_signed(signer, Default::default()).await?;
|
||||
self.client.rpc().submit_extrinsic(extrinsic).await
|
||||
@@ -278,13 +255,14 @@ where
|
||||
where
|
||||
<<X as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned:
|
||||
Send + Sync + 'static,
|
||||
<A as AccountData>::AccountId: From<<T as Config>::AccountId>,
|
||||
<A as AccountData>::Index: Into<<T as Config>::Index>,
|
||||
{
|
||||
let account_nonce = if let Some(nonce) = signer.nonce() {
|
||||
nonce
|
||||
} else {
|
||||
self.client.fetch_nonce::<A>(signer.account_id()).await?
|
||||
self.client
|
||||
.rpc()
|
||||
.system_account_next_index(signer.account_id())
|
||||
.await?
|
||||
};
|
||||
let call = self
|
||||
.client
|
||||
|
||||
+8
-22
@@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::StorageEntry;
|
||||
use codec::{
|
||||
Codec,
|
||||
Encode,
|
||||
@@ -38,7 +37,13 @@ use sp_runtime::traits::{
|
||||
pub trait Config: 'static {
|
||||
/// Account index (aka nonce) type. This stores the number of previous
|
||||
/// transactions associated with a sender account.
|
||||
type Index: Parameter + Member + Default + AtLeast32Bit + Copy + scale_info::TypeInfo;
|
||||
type Index: Parameter
|
||||
+ Member
|
||||
+ serde::de::DeserializeOwned
|
||||
+ Default
|
||||
+ AtLeast32Bit
|
||||
+ Copy
|
||||
+ scale_info::TypeInfo;
|
||||
|
||||
/// The block number type used by the runtime.
|
||||
type BlockNumber: Parameter
|
||||
@@ -65,7 +70,7 @@ pub trait Config: 'static {
|
||||
type Hashing: Hash<Output = Self::Hash>;
|
||||
|
||||
/// The user account identifier type for the runtime.
|
||||
type AccountId: Parameter + Member;
|
||||
type AccountId: Parameter + Member + serde::Serialize;
|
||||
|
||||
/// The address type. This instead of `<frame_system::Trait::Lookup as StaticLookup>::Source`.
|
||||
type Address: Codec + Clone + PartialEq;
|
||||
@@ -103,22 +108,3 @@ impl Config for DefaultConfig {
|
||||
type Signature = sp_runtime::MultiSignature;
|
||||
type Extrinsic = sp_runtime::OpaqueExtrinsic;
|
||||
}
|
||||
|
||||
/// Trait to fetch data about an account.
|
||||
pub trait AccountData {
|
||||
/// The runtime storage entry from which the account data can be fetched.
|
||||
/// Usually generated by the `subxt` macro.
|
||||
type StorageEntry: StorageEntry;
|
||||
|
||||
/// The type of the account id to fetch the account data for.
|
||||
type AccountId;
|
||||
|
||||
/// The type of the account nonce returned from storage.
|
||||
type Index;
|
||||
|
||||
/// Create a new storage entry key from the account id.
|
||||
fn storage_entry(account_id: Self::AccountId) -> Self::StorageEntry;
|
||||
|
||||
/// Get the nonce from the storage entry value.
|
||||
fn nonce(result: &<Self::StorageEntry as StorageEntry>::Value) -> Self::Index;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ pub use crate::{
|
||||
SubmittableExtrinsic,
|
||||
},
|
||||
config::{
|
||||
AccountData,
|
||||
Config,
|
||||
DefaultConfig,
|
||||
},
|
||||
|
||||
@@ -324,6 +324,17 @@ impl<T: Config> Rpc<T> {
|
||||
Ok(self.client.request("system_version", rpc_params![]).await?)
|
||||
}
|
||||
|
||||
/// Fetch the current nonce for the given account ID.
|
||||
pub async fn system_account_next_index(
|
||||
&self,
|
||||
account: &T::AccountId,
|
||||
) -> Result<T::Index, BasicError> {
|
||||
Ok(self
|
||||
.client
|
||||
.request("system_accountNextIndex", rpc_params![account])
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Get a header
|
||||
pub async fn header(
|
||||
&self,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,10 @@ use sp_core::{
|
||||
Pair as _,
|
||||
};
|
||||
use sp_keyring::AccountKeyring;
|
||||
use sp_runtime::{
|
||||
AccountId32,
|
||||
MultiAddress,
|
||||
};
|
||||
use subxt::{
|
||||
Error,
|
||||
Signer,
|
||||
@@ -94,6 +98,44 @@ async fn tx_basic_transfer() -> Result<(), subxt::Error<DispatchError>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn multiple_transfers_work_nonce_incremented(
|
||||
) -> Result<(), subxt::Error<DispatchError>> {
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob = pair_signer(AccountKeyring::Bob.pair());
|
||||
let bob_address: MultiAddress<AccountId32, u32> = bob.account_id().clone().into();
|
||||
let cxt = test_context().await;
|
||||
let api = &cxt.api;
|
||||
|
||||
let bob_pre = api
|
||||
.storage()
|
||||
.system()
|
||||
.account(bob.account_id(), None)
|
||||
.await?;
|
||||
|
||||
for _ in 0..3 {
|
||||
api
|
||||
.tx()
|
||||
.balances()
|
||||
.transfer(bob_address.clone(), 10_000)
|
||||
.sign_and_submit_then_watch(&alice)
|
||||
.await?
|
||||
.wait_for_in_block() // Don't need to wait for finalization; this is quicker.
|
||||
.await?
|
||||
.wait_for_success()
|
||||
.await?;
|
||||
}
|
||||
|
||||
let bob_post = api
|
||||
.storage()
|
||||
.system()
|
||||
.account(bob.account_id(), None)
|
||||
.await?;
|
||||
|
||||
assert_eq!(bob_pre.data.free + 30_000, bob_post.data.free);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn storage_total_issuance() {
|
||||
let cxt = test_context().await;
|
||||
|
||||
@@ -25,7 +25,6 @@ use crate::{
|
||||
storage,
|
||||
},
|
||||
system,
|
||||
DefaultAccountData,
|
||||
DispatchError,
|
||||
},
|
||||
test_context,
|
||||
@@ -63,9 +62,7 @@ impl ContractsTestContext {
|
||||
self.cxt.client()
|
||||
}
|
||||
|
||||
fn contracts_tx(
|
||||
&self,
|
||||
) -> TransactionApi<DefaultConfig, NodeRuntimeSignedExtra, DefaultAccountData> {
|
||||
fn contracts_tx(&self) -> TransactionApi<DefaultConfig, NodeRuntimeSignedExtra> {
|
||||
self.cxt.api.tx().contracts()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user