From 59f2416332ef2f4403e537554e9d264ffc9a7163 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Fri, 21 Jun 2024 03:56:14 +0800 Subject: [PATCH] Add optional support for locking the signer. --- Cargo.lock | 4 +- Cargo.toml | 2 +- crates/polymesh-api-client/Cargo.toml | 2 +- crates/polymesh-api-client/src/basic_types.rs | 7 +- crates/polymesh-api-client/src/signer.rs | 39 +++++++++++ crates/polymesh-api-client/src/transaction.rs | 11 ++++ ...47e206a346499321ba14b9198bdede4ebb4af.json | 12 ++++ crates/polymesh-api-tester/Cargo.toml | 2 +- crates/polymesh-api-tester/src/account.rs | 65 +++++++++++++++---- crates/polymesh-api-tester/src/db.rs | 17 +++++ 10 files changed, 143 insertions(+), 18 deletions(-) create mode 100644 crates/polymesh-api-tester/.sqlx/query-3b6dde8f8a3f67635c5d4b6079447e206a346499321ba14b9198bdede4ebb4af.json diff --git a/Cargo.lock b/Cargo.lock index f2e3ef5..14e9666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3329,7 +3329,7 @@ dependencies = [ [[package]] name = "polymesh-api-client" -version = "3.5.1" +version = "3.6.0" dependencies = [ "anyhow", "async-stream", @@ -3429,7 +3429,7 @@ dependencies = [ [[package]] name = "polymesh-api-tester" -version = "0.4.4" +version = "0.5.0" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index ee90583..c6f61ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ members = [ # Our crates polymesh-api-codegen-macro = { version = "3.4.0", path = "crates/polymesh-api-codegen-macro", default-features = false } polymesh-api-codegen = { version = "3.4.0", path = "crates/polymesh-api-codegen", default-features = false } -polymesh-api-client = { version = "3.5.1", path = "crates/polymesh-api-client", default-features = false } +polymesh-api-client = { version = "3.6.0", path = "crates/polymesh-api-client", default-features = false } polymesh-api-client-extras = { version = "3.3.0", path = "crates/polymesh-api-client-extras", default-features = false } polymesh-api-ink = { version = "1.3.0", path = "crates/polymesh-api-ink", default-features = false } polymesh-api = { version = "3.7.0", path = "./", default-features = false } diff --git a/crates/polymesh-api-client/Cargo.toml b/crates/polymesh-api-client/Cargo.toml index 4e53497..1579d64 100644 --- a/crates/polymesh-api-client/Cargo.toml +++ b/crates/polymesh-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "polymesh-api-client" -version = "3.5.1" +version = "3.6.0" edition = "2021" authors = ["Robert G. Jakabosky "] license = "Apache-2.0" diff --git a/crates/polymesh-api-client/src/basic_types.rs b/crates/polymesh-api-client/src/basic_types.rs index daef51c..42c4d99 100644 --- a/crates/polymesh-api-client/src/basic_types.rs +++ b/crates/polymesh-api-client/src/basic_types.rs @@ -623,8 +623,11 @@ pub type GenericAddress = MultiAddress; #[cfg_attr(all(feature = "std", feature = "type_info"), derive(TypeInfo))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] pub struct IdentityId( - #[cfg_attr(feature = "utoipa", schema(example = "0x0600000000000000000000000000000000000000000000000000000000000000"))] - pub [u8; 32] + #[cfg_attr( + feature = "utoipa", + schema(example = "0x0600000000000000000000000000000000000000000000000000000000000000") + )] + pub [u8; 32], ); impl fmt::Display for IdentityId { diff --git a/crates/polymesh-api-client/src/signer.rs b/crates/polymesh-api-client/src/signer.rs index 6798d9c..ba05945 100644 --- a/crates/polymesh-api-client/src/signer.rs +++ b/crates/polymesh-api-client/src/signer.rs @@ -4,6 +4,8 @@ use sp_core::Pair; use sp_runtime::MultiSignature; use sp_std::prelude::*; +use tokio::sync::{Mutex, MutexGuard}; + use async_trait::async_trait; #[cfg(not(feature = "std"))] @@ -76,6 +78,39 @@ pub mod dev { } } +pub struct LockableSigner(Mutex>); + +impl LockableSigner { + pub fn new(signer: S) -> Self { + Self(Mutex::new(Box::new(signer))) + } + + pub async fn lock(&self) -> LockedSigner<'_> { + LockedSigner(self.0.lock().await) + } +} + +pub struct LockedSigner<'a>(MutexGuard<'a, Box>); + +#[async_trait] +impl<'a> Signer for LockedSigner<'a> { + fn account(&self) -> AccountId { + self.0.account() + } + + async fn nonce(&self) -> Option { + self.0.nonce().await + } + + async fn set_nonce(&mut self, nonce: u32) { + self.0.set_nonce(nonce).await + } + + async fn sign(&self, msg: &[u8]) -> Result { + self.0.sign(msg).await + } +} + #[async_trait] pub trait Signer: Send + Sync { fn account(&self) -> AccountId; @@ -92,6 +127,10 @@ pub trait Signer: Send + Sync { async fn set_nonce(&mut self, _nonce: u32) {} async fn sign(&self, msg: &[u8]) -> Result; + + async fn lock(&self) -> Option> { + None + } } pub trait KeypairSigner: Send + Sync + Sized + Clone { diff --git a/crates/polymesh-api-client/src/transaction.rs b/crates/polymesh-api-client/src/transaction.rs index c02e460..2e942ff 100644 --- a/crates/polymesh-api-client/src/transaction.rs +++ b/crates/polymesh-api-client/src/transaction.rs @@ -352,6 +352,17 @@ impl Call { pub async fn submit_and_watch( &self, signer: &mut impl Signer, + ) -> Result> { + // First try using a locked signer. + if let Some(mut signer) = signer.lock().await { + return self.submit_and_watch_inner(&mut signer).await; + } + self.submit_and_watch_inner(signer).await + } + + async fn submit_and_watch_inner( + &self, + signer: &mut impl Signer, ) -> Result> { let client = self.api.client(); let account = signer.account(); diff --git a/crates/polymesh-api-tester/.sqlx/query-3b6dde8f8a3f67635c5d4b6079447e206a346499321ba14b9198bdede4ebb4af.json b/crates/polymesh-api-tester/.sqlx/query-3b6dde8f8a3f67635c5d4b6079447e206a346499321ba14b9198bdede4ebb4af.json new file mode 100644 index 0000000..603674e --- /dev/null +++ b/crates/polymesh-api-tester/.sqlx/query-3b6dde8f8a3f67635c5d4b6079447e206a346499321ba14b9198bdede4ebb4af.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE accounts SET nonce = ? WHERE account = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "3b6dde8f8a3f67635c5d4b6079447e206a346499321ba14b9198bdede4ebb4af" +} diff --git a/crates/polymesh-api-tester/Cargo.toml b/crates/polymesh-api-tester/Cargo.toml index 157859e..414815c 100644 --- a/crates/polymesh-api-tester/Cargo.toml +++ b/crates/polymesh-api-tester/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "polymesh-api-tester" -version = "0.4.4" +version = "0.5.0" edition = "2021" authors = ["Robert G. Jakabosky "] license = "Apache-2.0" diff --git a/crates/polymesh-api-tester/src/account.rs b/crates/polymesh-api-tester/src/account.rs index 53399be..65356e8 100644 --- a/crates/polymesh-api-tester/src/account.rs +++ b/crates/polymesh-api-tester/src/account.rs @@ -3,17 +3,52 @@ use std::sync::Arc; use sp_keyring::{ed25519, sr25519}; use sp_runtime::MultiSignature; -use polymesh_api::client::{AccountId, KeypairSigner, PairSigner, Signer}; +use polymesh_api::client::{ + AccountId, KeypairSigner, LockableSigner, LockedSigner, PairSigner, Signer, +}; use crate::error::Result; use crate::Db; +struct AccountSignerInner { + signer: Box, + db: Option, +} + +#[async_trait::async_trait] +impl Signer for AccountSignerInner { + fn account(&self) -> AccountId { + self.signer.account() + } + + async fn nonce(&self) -> Option { + match &self.db { + Some(db) => db.get_nonce(self.account()).await.ok(), + None => None, + } + } + + async fn set_nonce(&mut self, nonce: u32) { + match &self.db { + Some(db) => { + if let Err(err) = db.set_nonce(self.account(), nonce).await { + log::error!("Failed to update account nonce in DB: {err:?}"); + } + } + None => (), + } + } + + async fn sign(&self, msg: &[u8]) -> polymesh_api::client::Result { + Ok(self.signer.sign(msg).await?) + } +} + /// AccountSigner is wrapper for signing keys (sr25519, ed25519, etc...). #[derive(Clone)] pub struct AccountSigner { - signer: Arc, + signer: Arc, account: AccountId, - db: Option, } impl AccountSigner { @@ -21,9 +56,11 @@ impl AccountSigner { let signer = PairSigner::new(pair); let account = signer.account(); Self { - signer: Arc::new(signer), + signer: Arc::new(LockableSigner::new(AccountSignerInner { + signer: Box::new(signer), + db: db.clone(), + })), account, - db, } } @@ -63,15 +100,21 @@ impl Signer for AccountSigner { } async fn nonce(&self) -> Option { - match &self.db { - Some(db) => db.get_nonce(self.account).await.ok(), - None => None, - } + let inner = self.signer.lock().await; + inner.nonce().await } - async fn set_nonce(&mut self, _nonce: u32) {} + async fn set_nonce(&mut self, nonce: u32) { + let mut inner = self.signer.lock().await; + inner.set_nonce(nonce).await + } async fn sign(&self, msg: &[u8]) -> polymesh_api::client::Result { - Ok(self.signer.sign(msg).await?) + let inner = self.signer.lock().await; + Ok(inner.sign(msg).await?) + } + + async fn lock(&self) -> Option> { + Some(self.signer.lock().await) } } diff --git a/crates/polymesh-api-tester/src/db.rs b/crates/polymesh-api-tester/src/db.rs index 8d7597f..063afc9 100644 --- a/crates/polymesh-api-tester/src/db.rs +++ b/crates/polymesh-api-tester/src/db.rs @@ -37,4 +37,21 @@ impl Db { Ok(rec.nonce as u32) } + + pub async fn set_nonce(&self, account: AccountId, nonce: u32) -> Result { + let id = account.to_string(); + // Save the nonce to the database. + let rows = sqlx::query!( + r#" + UPDATE accounts SET nonce = ? WHERE account = ? + "#, + nonce, + id + ) + .execute(&self.pool) + .await? + .rows_affected(); + + Ok(rows > 0) + } }