Double map and plain storage support, introduce macros (#93)

* Support custom clients.

* Simplify trait bounds.

* Plain and double map storage support.

* Simplify more trait bounds.

* Add proc macro.

* Add Call, Event and Store traits.

* Update proc-macros.

* Add with_system for proc-macro.

* proc-macro: test: support signature and extra fields.

* proc-macro: test: support sharing state accross steps.

* proc-macro: test: fetch state sequentially.

* Elide lifetimes.

* Add test for plain storage.

* Run rustfmt.
This commit is contained in:
David Craven
2020-04-28 21:04:26 +02:00
committed by GitHub
parent 216b5614dd
commit 6f27489378
20 changed files with 1924 additions and 501 deletions
+186 -189
View File
@@ -46,7 +46,6 @@ use std::{
use codec::{
Codec,
Decode,
Encode,
};
use futures::future;
@@ -80,32 +79,35 @@ mod metadata;
mod rpc;
mod runtimes;
pub use self::{
pub use crate::{
error::Error,
events::RawEvent,
events::{
EventsDecoder,
EventsError,
RawEvent,
},
extrinsic::*,
frame::*,
metadata::{
Metadata,
MetadataError,
},
rpc::{
BlockNumber,
ExtrinsicSuccess,
},
runtimes::*,
};
use self::{
events::{
EventsDecoder,
EventsError,
},
use crate::{
frame::{
balances::Balances,
system::{
AccountStore,
Phase,
System,
SystemEvent,
SystemStore,
},
},
metadata::Metadata,
rpc::{
ChainBlock,
Rpc,
@@ -117,6 +119,7 @@ use self::{
pub struct ClientBuilder<T: System, S = MultiSignature, E = DefaultExtra<T>> {
_marker: std::marker::PhantomData<(T, S, E)>,
url: Option<String>,
client: Option<jsonrpsee::Client>,
}
impl<T: System, S, E> ClientBuilder<T, S, E> {
@@ -125,19 +128,39 @@ impl<T: System, S, E> ClientBuilder<T, S, E> {
Self {
_marker: std::marker::PhantomData,
url: None,
client: None,
}
}
/// Sets the jsonrpsee client.
pub fn set_client<P: Into<jsonrpsee::Client>>(mut self, client: P) -> Self {
self.client = Some(client.into());
self
}
/// Set the substrate rpc address.
pub fn set_url(mut self, url: &str) -> Self {
self.url = Some(url.to_string());
pub fn set_url<P: Into<String>>(mut self, url: P) -> Self {
self.url = Some(url.into());
self
}
/// Creates a new Client.
pub async fn build(self) -> Result<Client<T, S, E>, Error> {
let url = self.url.unwrap_or("ws://127.0.0.1:9944".to_string());
let rpc = Rpc::connect_ws(&url).await?;
let client = if let Some(client) = self.client {
client
} else {
let url = self
.url
.as_ref()
.map(|s| &**s)
.unwrap_or("ws://127.0.0.1:9944");
if url.starts_with("ws://") || url.starts_with("wss://") {
jsonrpsee::ws_client(url).await?
} else {
jsonrpsee::http_client(url)
}
};
let rpc = Rpc::new(client).await?;
let (metadata, genesis_hash, runtime_version) = future::join3(
rpc.metadata(),
@@ -176,43 +199,25 @@ impl<T: System, S, E> Clone for Client<T, S, E> {
}
}
impl<T: System + Balances + Sync + Send + 'static, S: 'static, E> Client<T, S, E>
where
E: SignedExtra<T> + SignedExtension + 'static,
{
impl<T: System, S, E> Client<T, S, E> {
/// Returns the chain metadata.
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
/// Fetch a StorageKey.
pub async fn fetch<V: Decode>(
pub async fn fetch<F: Store<T>>(
&self,
key: StorageKey,
store: F,
hash: Option<T::Hash>,
) -> Result<Option<V>, Error> {
self.rpc.storage::<V>(key, hash).await
}
/// Fetch a StorageKey or return the default.
pub async fn fetch_or<V: Decode>(
&self,
key: StorageKey,
hash: Option<T::Hash>,
default: V,
) -> Result<V, Error> {
let result = self.fetch(key, hash).await?;
Ok(result.unwrap_or(default))
}
/// Fetch a StorageKey or return the default.
pub async fn fetch_or_default<V: Decode + Default>(
&self,
key: StorageKey,
hash: Option<T::Hash>,
) -> Result<V, Error> {
let result = self.fetch(key, hash).await?;
Ok(result.unwrap_or_default())
) -> Result<Option<F::Returns>, Error> {
let key = store.key(&self.metadata)?;
let value = self.rpc.storage::<F::Returns>(key, hash).await?;
if let Some(v) = value {
Ok(Some(v))
} else {
Ok(store.default(&self.metadata)?)
}
}
/// Query historical storage entries
@@ -301,24 +306,31 @@ where
let headers = self.rpc.subscribe_finalized_blocks().await?;
Ok(headers)
}
}
impl<T, S, E> Client<T, S, E>
where
T: System + Balances + Send + Sync,
S: 'static,
E: SignedExtra<T> + SignedExtension + 'static,
{
/// Creates raw payload to be signed for the supplied `Call` without private key
pub async fn create_raw_payload<C: Encode>(
pub async fn create_raw_payload<C: Call<T>>(
&self,
account_id: <T as System>::AccountId,
call: Call<C>,
) -> Result<
SignedPayload<Encoded, <E as SignedExtra<T>>::Extra>,
Error
>
{
let account_nonce = self.account(account_id).await?.nonce;
account_id: &<T as System>::AccountId,
call: C,
) -> Result<SignedPayload<Encoded, <E as SignedExtra<T>>::Extra>, Error> {
let account_nonce = self
.fetch(AccountStore(account_id), None)
.await?
.unwrap()
.nonce;
let version = self.runtime_version.spec_version;
let genesis_hash = self.genesis_hash;
let call = self
.metadata()
.module_with_calls(&call.module)
.and_then(|module| module.call(&call.function, call.args))?;
.module_with_calls(C::MODULE)
.and_then(|module| module.call(C::FUNCTION, call))?;
let extra: E = E::new(version, account_nonce, genesis_hash);
let raw_payload = SignedPayload::new(call, extra.extra())?;
Ok(raw_payload)
@@ -339,7 +351,12 @@ where
let account_id = S::Signer::from(signer.public()).into_account();
let nonce = match nonce {
Some(nonce) => nonce,
None => self.account(account_id).await?.nonce,
None => {
self.fetch(AccountStore(&account_id), None)
.await?
.unwrap()
.nonce
}
};
let genesis_hash = self.genesis_hash;
@@ -364,11 +381,7 @@ pub struct XtBuilder<T: System, P, S, E> {
signer: P,
}
impl<T: System + Balances + Send + Sync + 'static, P, S: 'static, E> XtBuilder<T, P, S, E>
where
P: Pair,
E: SignedExtra<T> + SignedExtension + 'static,
{
impl<T: System, P, S, E> XtBuilder<T, P, S, E> {
/// Returns the chain metadata.
pub fn metadata(&self) -> &Metadata {
self.client.metadata()
@@ -392,7 +405,7 @@ where
}
}
impl<T: System + Balances + Send + Sync + 'static, P, S: 'static, E> XtBuilder<T, P, S, E>
impl<T: System + Send + Sync, P, S: 'static, E> XtBuilder<T, P, S, E>
where
P: Pair,
S: Verify + Codec + From<P::Signature>,
@@ -401,24 +414,21 @@ where
E: SignedExtra<T> + SignedExtension + 'static,
{
/// Creates and signs an Extrinsic for the supplied `Call`
pub fn create_and_sign<C>(
pub fn create_and_sign<C: Call<T>>(
&self,
call: Call<C>,
call: C,
) -> Result<
UncheckedExtrinsic<T::Address, Encoded, S, <E as SignedExtra<T>>::Extra>,
Error,
>
where
C: codec::Encode,
{
> {
let signer = self.signer.clone();
let account_nonce = self.nonce;
let version = self.runtime_version.spec_version;
let genesis_hash = self.genesis_hash;
let call = self
.metadata()
.module_with_calls(&call.module)
.and_then(|module| module.call(&call.function, call.args))?;
.module_with_calls(C::MODULE)
.and_then(|module| module.call(C::FUNCTION, call))?;
log::info!(
"Creating Extrinsic with genesis hash {:?} and account nonce {:?}",
@@ -432,7 +442,7 @@ where
}
/// Submits a transaction to the chain.
pub async fn submit<C: Encode>(&self, call: Call<C>) -> Result<T::Hash, Error> {
pub async fn submit<C: Call<T>>(&self, call: C) -> Result<T::Hash, Error> {
let extrinsic = self.create_and_sign(call)?;
let xt_hash = self.client.submit_extrinsic(extrinsic).await?;
Ok(xt_hash)
@@ -457,15 +467,7 @@ pub struct EventsSubscriber<T: System, P, S, E> {
decoder: Result<EventsDecoder<T>, EventsError>,
}
impl<T: System + Balances + Send + Sync + 'static, P, S: 'static, E>
EventsSubscriber<T, P, S, E>
where
P: Pair,
S: Verify + Codec + From<P::Signature>,
S::Signer: From<P::Public> + IdentifyAccount<AccountId = T::AccountId>,
T::Address: From<T::AccountId>,
E: SignedExtra<T> + SignedExtension + 'static,
{
impl<T: System, P, S, E> EventsSubscriber<T, P, S, E> {
/// Access the events decoder for registering custom type sizes
pub fn events_decoder<
F: FnOnce(&mut EventsDecoder<T>) -> Result<usize, EventsError>,
@@ -481,13 +483,20 @@ where
}
this
}
}
impl<T: System + Send + Sync, P, S: 'static, E> EventsSubscriber<T, P, S, E>
where
P: Pair,
S: Verify + Codec + From<P::Signature>,
S::Signer: From<P::Public> + IdentifyAccount<AccountId = T::AccountId>,
T::Address: From<T::AccountId>,
E: SignedExtra<T> + SignedExtension + 'static,
{
/// Submits transaction to the chain and watch for events.
pub async fn submit<C: Encode>(
self,
call: Call<C>,
) -> Result<ExtrinsicSuccess<T>, Error> {
let decoder = self.decoder?;
pub async fn submit<C: Call<T>>(self, call: C) -> Result<ExtrinsicSuccess<T>, Error> {
let mut decoder = self.decoder?;
C::events_decoder(&mut decoder)?;
let extrinsic = self.builder.create_and_sign(call)?;
let xt_success = self
.client
@@ -516,143 +525,131 @@ mod tests {
};
use super::*;
use crate::{
DefaultNodeRuntime as Runtime,
Error,
};
pub(crate) async fn test_client() -> Client<Runtime> {
ClientBuilder::<Runtime>::new()
pub(crate) async fn test_client() -> Client<crate::DefaultNodeRuntime> {
ClientBuilder::new()
.build()
.await
.expect("Error creating client")
}
#[test]
#[async_std::test]
#[ignore] // requires locally running substrate node
fn test_tx_transfer_balance() {
async fn test_tx_transfer_balance() {
env_logger::try_init().ok();
let transfer = async_std::task::block_on(async move {
let signer = AccountKeyring::Alice.pair();
let dest = AccountKeyring::Bob.to_account_id();
let signer = AccountKeyring::Alice.pair();
let dest = AccountKeyring::Bob.to_account_id().into();
let client = test_client().await;
let mut xt = client.xt(signer, None).await?;
let _ = xt
.submit(balances::transfer::<Runtime>(dest.clone().into(), 10_000))
.await?;
let client = test_client().await;
let mut xt = client.xt(signer, None).await.unwrap();
let _ = xt
.submit(balances::TransferCall {
to: &dest,
amount: 10_000,
})
.await
.unwrap();
// check that nonce is handled correctly
xt.increment_nonce()
.submit(balances::transfer::<Runtime>(dest.clone().into(), 10_000))
.await
});
assert!(transfer.is_ok())
// check that nonce is handled correctly
xt.increment_nonce()
.submit(balances::TransferCall {
to: &dest,
amount: 10_000,
})
.await
.unwrap();
}
#[test]
#[async_std::test]
#[ignore] // requires locally running substrate node
fn test_getting_hash() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let client = test_client().await;
let block_hash = client.block_hash(None).await?;
Ok(block_hash)
});
assert!(result.is_ok())
async fn test_getting_hash() {
let client = test_client().await;
client.block_hash(None).await.unwrap();
}
#[test]
#[async_std::test]
#[ignore] // requires locally running substrate node
fn test_getting_block() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let client = test_client().await;
let block_hash = client.block_hash(None).await?;
let block = client.block(block_hash).await?;
Ok(block)
});
assert!(result.is_ok())
async fn test_getting_block() {
let client = test_client().await;
let block_hash = client.block_hash(None).await.unwrap();
client.block(block_hash).await.unwrap();
}
#[test]
#[async_std::test]
#[ignore] // requires locally running substrate node
fn test_state_read_free_balance() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let account = AccountKeyring::Alice.to_account_id();
let client = test_client().await;
let balance = client.account(account.into()).await?.data.free;
Ok(balance)
});
assert!(result.is_ok())
async fn test_state_total_issuance() {
let client = test_client().await;
client
.fetch(balances::TotalIssuance(Default::default()), None)
.await
.unwrap()
.unwrap();
}
#[test]
#[async_std::test]
#[ignore] // requires locally running substrate node
fn test_chain_subscribe_blocks() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let client = test_client().await;
let mut blocks = client.subscribe_blocks().await?;
let block = blocks.next().await;
Ok(block)
});
assert!(result.is_ok())
async fn test_state_read_free_balance() {
let client = test_client().await;
let account = AccountKeyring::Alice.to_account_id();
client
.fetch(AccountStore(&account), None)
.await
.unwrap()
.unwrap();
}
#[test]
#[async_std::test]
#[ignore] // requires locally running substrate node
fn test_chain_subscribe_finalized_blocks() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let client = test_client().await;
let mut blocks = client.subscribe_finalized_blocks().await?;
let block = blocks.next().await;
Ok(block)
});
assert!(result.is_ok())
async fn test_chain_subscribe_blocks() {
let client = test_client().await;
let mut blocks = client.subscribe_blocks().await.unwrap();
blocks.next().await;
}
#[test]
#[async_std::test]
#[ignore] // requires locally running substrate node
fn test_create_raw_payload() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let signer_pair = Ed25519Keyring::Alice.pair();
let signer_account_id = Ed25519Keyring::Alice.to_account_id();
let dest = AccountKeyring::Bob.to_account_id();
async fn test_chain_subscribe_finalized_blocks() {
let client = test_client().await;
let mut blocks = client.subscribe_finalized_blocks().await.unwrap();
blocks.next().await;
}
let client = test_client().await;
#[async_std::test]
#[ignore] // requires locally running substrate node
async fn test_create_raw_payload() {
let signer_pair = Ed25519Keyring::Alice.pair();
let signer_account_id = Ed25519Keyring::Alice.to_account_id();
let dest = AccountKeyring::Bob.to_account_id().into();
// create raw payload with AccoundId and sign it
let raw_payload = client
.create_raw_payload(
signer_account_id,
balances::transfer::<Runtime>(dest.clone().into(), 10_000),
)
.await?;
let raw_signature =
signer_pair.sign(raw_payload.encode().as_slice());
let raw_multisig = MultiSignature::from(raw_signature);
let client = test_client().await;
// create signature with Xtbuilder
let xt = client.xt(signer_pair.clone(), None).await?;
let xt_multi_sig = xt
.create_and_sign(balances::transfer::<Runtime>(
dest.clone().into(),
10_000,
))?
.signature
.unwrap()
.1;
// create raw payload with AccoundId and sign it
let raw_payload = client
.create_raw_payload(
&signer_account_id,
balances::TransferCall {
to: &dest,
amount: 10_000,
},
)
.await
.unwrap();
let raw_signature = signer_pair.sign(raw_payload.encode().as_slice());
let raw_multisig = MultiSignature::from(raw_signature);
// compare signatures
assert_eq!(raw_multisig, xt_multi_sig);
// create signature with Xtbuilder
let xt = client.xt(signer_pair.clone(), None).await.unwrap();
let xt_multi_sig = xt
.create_and_sign(balances::TransferCall {
to: &dest,
amount: 10_000,
})
.unwrap()
.signature
.unwrap()
.1;
Ok(())
});
assert!(result.is_ok())
// compare signatures
assert_eq!(raw_multisig, xt_multi_sig);
}
}