From 151c7f7e0a6f2b2be2f2951240f90122166540f6 Mon Sep 17 00:00:00 2001 From: David Craven Date: Mon, 12 Aug 2019 17:07:28 +0200 Subject: [PATCH] Refactor (#7) * Increment nonce when submitting transaction. * Refactor. * use default values from metadata * use functions from metadata * Nonce fixup * Add docs. * Add contracts module. * Add set_code. * Add events to metadata. * Support pretty printing metadata. * Add subscriptions. * Add submit_and_watch to XtBuilder. * nits from review --- src/codec.rs | 19 +++ src/error.rs | 9 + src/lib.rs | 388 +++++++++++++++++++++++++----------------- src/metadata.rs | 138 +++++++++++---- src/rpc.rs | 151 ++++++++-------- src/srml/balances.rs | 129 ++++++++++++++ src/srml/contracts.rs | 146 ++++++++++++++++ src/srml/mod.rs | 5 + src/srml/system.rs | 170 ++++++++++++++++++ 9 files changed, 888 insertions(+), 267 deletions(-) create mode 100644 src/codec.rs create mode 100644 src/srml/balances.rs create mode 100644 src/srml/contracts.rs create mode 100644 src/srml/mod.rs create mode 100644 src/srml/system.rs diff --git a/src/codec.rs b/src/codec.rs new file mode 100644 index 0000000000..801a1dd0aa --- /dev/null +++ b/src/codec.rs @@ -0,0 +1,19 @@ +use parity_scale_codec::{ + Encode, + EncodeAsRef, + HasCompact, +}; + +pub struct Encoded(pub Vec); + +impl Encode for Encoded { + fn encode(&self) -> Vec { + self.0.to_owned() + } +} + +pub fn compact(t: T) -> Encoded { + let encodable: <::Type as EncodeAsRef<'_, T>>::RefType = + From::from(&t); + Encoded(encodable.encode()) +} diff --git a/src/error.rs b/src/error.rs index 3f6165c857..fce89cc311 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,17 +14,26 @@ // You should have received a copy of the GNU General Public License // along with substrate-subxt. If not, see . +use crate::metadata::MetadataError; use jsonrpc_core_client::RpcError; use parity_scale_codec::Error as CodecError; use std::io::Error as IoError; use substrate_primitives::crypto::SecretStringError; +/// Error enum. #[derive(Debug, derive_more::From)] pub enum Error { + /// Codec error. Codec(CodecError), + /// Io error. Io(IoError), + /// Rpc error. Rpc(RpcError), + /// Secret string error. SecretString(SecretStringError), + /// Metadata error. + Metadata(MetadataError), + /// Other error. Other(String), } diff --git a/src/lib.rs b/src/lib.rs index f8f9b0a82d..e6bad354ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,17 @@ // You should have received a copy of the GNU General Public License // along with substrate-subxt. If not, see . -use futures::future::Future; +//! A library to **sub**mit e**xt**rinsics to a +//! [substrate](https://github.com/paritytech/substrate) node via RPC. + +#![deny(missing_docs)] +#![deny(warnings)] + +use futures::future::{ + self, + Either, + Future, +}; use jsonrpc_core_client::transports::ws; use metadata::Metadata; use parity_scale_codec::{ @@ -22,44 +32,59 @@ use parity_scale_codec::{ Decode, Encode, }; -use runtime_primitives::traits::{ - SignedExtension, - StaticLookup, -}; +use runtime_primitives::traits::StaticLookup; use substrate_primitives::{ - storage::StorageKey, + storage::{ + StorageChangeSet, + StorageKey, + }, Pair, }; use url::Url; +use crate::{ + rpc::{ + MapStream, + Rpc, + }, + srml::system::{ + System, + SystemStore, + }, +}; pub use error::Error; +mod codec; mod error; mod metadata; mod rpc; +pub mod srml; /// Captures data for when an extrinsic is successfully included in a block #[derive(Debug)] -pub struct ExtrinsicSuccess { +pub struct ExtrinsicSuccess { + /// Block hash. pub block: T::Hash, + /// Extinsic hash. pub extrinsic: T::Hash, + /// List of events. pub events: Vec, } -fn connect( - url: &Url, -) -> impl Future, Error = error::Error> { +fn connect(url: &Url) -> impl Future, Error = Error> { ws::connect(url.as_str()) .expect("Url is a valid url; qed") .map_err(Into::into) } -pub struct ClientBuilder { - _marker: std::marker::PhantomData<(T, SE)>, +/// ClientBuilder for constructing a Client. +pub struct ClientBuilder { + _marker: std::marker::PhantomData, url: Option, } -impl ClientBuilder { +impl ClientBuilder { + /// Creates a new ClientBuilder. pub fn new() -> Self { Self { _marker: std::marker::PhantomData, @@ -67,21 +92,22 @@ impl ClientBuilder { } } + /// Set the substrate rpc address. pub fn set_url(mut self, url: Url) -> Self { self.url = Some(url); self } - pub fn build(self) -> impl Future, Error = error::Error> { + /// Creates a new Client. + pub fn build(self) -> impl Future, Error = Error> { let url = self.url.unwrap_or_else(|| { Url::parse("ws://127.0.0.1:9944").expect("Is valid url; qed") }); - connect::(&url).and_then(|rpc| { + connect::(&url).and_then(|rpc| { rpc.metadata() .join(rpc.genesis_hash()) .map(|(metadata, genesis_hash)| { Client { - _marker: std::marker::PhantomData, url, genesis_hash, metadata, @@ -91,102 +117,159 @@ impl ClientBuilder { } } -#[derive(Clone)] -pub struct Client { - _marker: std::marker::PhantomData, +/// Client to interface with a substrate node. +pub struct Client { url: Url, genesis_hash: T::Hash, metadata: Metadata, } -impl Client { - fn connect(&self) -> impl Future, Error = error::Error> { +impl Clone for Client { + fn clone(&self) -> Self { + Self { + url: self.url.clone(), + genesis_hash: self.genesis_hash.clone(), + metadata: self.metadata.clone(), + } + } +} + +impl Client { + fn connect(&self) -> impl Future, Error = Error> { connect(&self.url) } - pub fn metadata(&self) -> &metadata::Metadata { + /// Returns the chain metadata. + pub fn metadata(&self) -> &Metadata { &self.metadata } + /// Fetch a StorageKey. pub fn fetch( &self, key: StorageKey, - ) -> impl Future, Error = error::Error> { + ) -> impl Future, Error = Error> { self.connect().and_then(|rpc| rpc.storage::(key)) } + /// Fetch a StorageKey or return the default. pub fn fetch_or( &self, key: StorageKey, default: V, - ) -> impl Future { + ) -> impl Future { self.fetch(key).map(|value| value.unwrap_or(default)) } + /// Fetch a StorageKey or return the default. pub fn fetch_or_default( &self, key: StorageKey, - ) -> impl Future { + ) -> impl Future { self.fetch(key).map(|value| value.unwrap_or_default()) } - pub fn xt( + /// Subscribe to events. + pub fn subscribe_events( + &self, + ) -> impl Future>, Error = Error> { + self.connect().and_then(|rpc| rpc.subscribe_events()) + } + + /// Subscribe to new blocks. + pub fn subscribe_blocks( + &self, + ) -> impl Future, Error = Error> { + self.connect().and_then(|rpc| rpc.subscribe_blocks()) + } + + /// Subscribe to finalized blocks. + pub fn subscribe_finalized_blocks( + &self, + ) -> impl Future, Error = Error> { + self.connect() + .and_then(|rpc| rpc.subscribe_finalized_blocks()) + } + + /// Create a transaction builder for a private key. + pub fn xt

( &self, signer: P, - extra: E, - ) -> impl Future, Error = error::Error> + nonce: Option, + ) -> impl Future, Error = Error> where P: Pair, - P::Public: Into<::Source>, + P::Public: Into + Into<::Source>, P::Signature: Codec, - E: Fn(T::Index) -> SE, { - let account_id: ::Source = signer.public().into(); - let account_nonce_key = self - .metadata - .module("System") - .expect("srml_system is present") - .storage("AccountNonce") - .expect("srml_system has account nonce") - .map() - .expect("account nonce is a map") - .key(&account_id); - let client = (*self).clone(); - self.fetch_or_default(account_nonce_key).map(|nonce| { + let client = self.clone(); + match nonce { + Some(nonce) => Either::A(future::ok(nonce)), + None => Either::B(self.account_nonce(signer.public().into())), + } + .map(|nonce| { XtBuilder { client, nonce, signer, - extra, } }) } } -pub struct XtBuilder { - client: Client, +/// Transaction builder. +pub struct XtBuilder { + client: Client, nonce: T::Index, signer: P, - extra: E, } -impl XtBuilder +impl XtBuilder where P: Pair, P::Public: Into<::Source>, P::Signature: Codec, - E: Fn(T::Index) -> SE, { + /// Returns the chain metadata. + pub fn metadata(&self) -> &Metadata { + self.client.metadata() + } + + /// Returns the nonce. + pub fn nonce(&self) -> T::Index { + self.nonce.clone() + } + + /// Sets the nonce to a new value. + pub fn set_nonce(&mut self, nonce: T::Index) { + self.nonce = nonce; + } + + /// Submits a transaction to the chain. pub fn submit( - &self, + &mut self, call: C, - ) -> impl Future { + ) -> impl Future { let signer = self.signer.clone(); let nonce = self.nonce.clone(); - let extra = (self.extra)(nonce.clone()); let genesis_hash = self.client.genesis_hash.clone(); + self.set_nonce(nonce + 1.into()); + self.client + .connect() + .and_then(move |rpc| rpc.submit_extrinsic(signer, call, nonce, genesis_hash)) + } + + /// Submits transaction to the chain and watch for events. + pub fn submit_and_watch( + &mut self, + call: C, + ) -> impl Future, Error = Error> { + let signer = self.signer.clone(); + let nonce = self.nonce.clone(); + let genesis_hash = self.client.genesis_hash.clone(); + self.set_nonce(nonce + 1.into()); self.client.connect().and_then(move |rpc| { - rpc.create_and_submit_extrinsic(signer, call, extra, nonce, genesis_hash) + rpc.submit_and_watch_extrinsic(signer, call, nonce, genesis_hash) }) } } @@ -194,153 +277,146 @@ where #[cfg(test)] mod tests { use super::*; + use crate::srml::balances::{ + Balances, + BalancesCalls, + BalancesStore, + }; + use futures::stream::Stream; use parity_scale_codec::Encode; use runtime_primitives::generic::Era; use runtime_support::StorageMap; + use substrate_keyring::AccountKeyring; use substrate_primitives::{ blake2_256, storage::StorageKey, Pair, }; - #[derive(Clone, PartialEq, Eq)] struct Runtime; - impl srml_system::Trait for Runtime { - type Call = ::Call; - type Origin = ::Origin; + impl System for Runtime { type Index = ::Index; type BlockNumber = ::BlockNumber; type Hash = ::Hash; type Hashing = ::Hashing; type AccountId = ::AccountId; type Lookup = ::Lookup; - type WeightMultiplierUpdate = - ::WeightMultiplierUpdate; type Header = ::Header; type Event = ::Event; - type BlockHashCount = - ::BlockHashCount; - type MaximumBlockWeight = - ::MaximumBlockWeight; - type MaximumBlockLength = - ::MaximumBlockLength; - type AvailableBlockRatio = - ::AvailableBlockRatio; - } - impl srml_balances::Trait for Runtime { - type Balance = ::Balance; - type OnFreeBalanceZero = (); - type OnNewAccount = (); - type TransactionPayment = (); - type TransferPayment = - ::TransferPayment; - type DustRemoval = ::DustRemoval; - type Event = ::Event; - type ExistentialDeposit = - ::ExistentialDeposit; - type TransferFee = ::TransferFee; - type CreationFee = ::CreationFee; - type TransactionBaseFee = - ::TransactionBaseFee; - type TransactionByteFee = - ::TransactionByteFee; - type WeightToFee = ::WeightToFee; - } - - type SignedExtra = ( - srml_system::CheckGenesis, - srml_system::CheckEra, - srml_system::CheckNonce, - srml_system::CheckWeight, - srml_balances::TakeFees, - ); - - #[test] - #[ignore] // requires locally running substrate node - fn node_runtime_balance_transfer() { - env_logger::try_init().ok(); - let mut rt = tokio::runtime::Runtime::new().unwrap(); - let client = rt - .block_on(ClientBuilder::::new().build()) - .unwrap(); - - let signer = substrate_keyring::AccountKeyring::Alice.pair(); - let extra = |nonce| { + type SignedExtra = ( + srml_system::CheckGenesis, + srml_system::CheckEra, + srml_system::CheckNonce, + srml_system::CheckWeight, + srml_balances::TakeFees, + ); + fn extra(nonce: Self::Index) -> Self::SignedExtra { ( - srml_system::CheckGenesis::::new(), - srml_system::CheckEra::::from(Era::Immortal), - srml_system::CheckNonce::::from(nonce), - srml_system::CheckWeight::::new(), - srml_balances::TakeFees::::from(0), + srml_system::CheckGenesis::::new(), + srml_system::CheckEra::::from(Era::Immortal), + srml_system::CheckNonce::::from(nonce), + srml_system::CheckWeight::::new(), + srml_balances::TakeFees::::from(0), ) - }; - let xt = rt.block_on(client.xt(signer, extra)).unwrap(); + } + } - let dest = substrate_keyring::AccountKeyring::Bob.pair().public(); - let transfer = srml_balances::Call::transfer::(dest.into(), 10_000); - let call = client.metadata().module("Balances").unwrap().call(transfer); - rt.block_on(xt.submit(call)).unwrap(); + impl Balances for Runtime { + type Balance = ::Balance; + } + + type Index = ::Index; + type AccountId = ::AccountId; + type Address = <::Lookup as StaticLookup>::Source; + type Balance = ::Balance; + + fn test_setup() -> (tokio::runtime::Runtime, Client) { + env_logger::try_init().ok(); + let mut rt = tokio::runtime::Runtime::new().unwrap(); + let client_future = ClientBuilder::::new().build(); + let client = rt.block_on(client_future).unwrap(); + (rt, client) } #[test] #[ignore] // requires locally running substrate node - fn node_runtime_fetch_account_balance() { - env_logger::try_init().ok(); - let mut rt = tokio::runtime::Runtime::new().unwrap(); - let client = rt - .block_on(ClientBuilder::::new().build()) + fn test_tx_transfer_balance() { + let (mut rt, client) = test_setup(); + + let signer = AccountKeyring::Alice.pair(); + let mut xt = rt.block_on(client.xt(signer, None)).unwrap(); + + let dest = AccountKeyring::Bob.pair().public(); + rt.block_on(xt.transfer(dest.clone().into(), 10_000)) .unwrap(); - let account: ::AccountId = - substrate_keyring::AccountKeyring::Alice - .pair() - .public() - .into(); - let key = client - .metadata() - .module("Balances") - .unwrap() - .storage("FreeBalance") - .unwrap() - .map() - .unwrap() - .key(&account); - type Balance = ::Balance; - rt.block_on(client.fetch::(key)).unwrap(); + // check that nonce is handled correctly + rt.block_on(xt.transfer(dest.into(), 10_000)).unwrap(); } #[test] #[ignore] // requires locally running substrate node - fn node_runtime_fetch_metadata() { - env_logger::try_init().ok(); - let mut rt = tokio::runtime::Runtime::new().unwrap(); - let client = rt - .block_on(ClientBuilder::::new().build()) + fn test_state_read_free_balance() { + let (mut rt, client) = test_setup(); + + let account = AccountKeyring::Alice.pair().public(); + rt.block_on(client.free_balance(account.into())).unwrap(); + } + + #[test] + #[ignore] // requires locally running substrate node + fn test_chain_subscribe_blocks() { + let (mut rt, client) = test_setup(); + + let stream = rt.block_on(client.subscribe_blocks()).unwrap(); + let (_header, _) = rt + .block_on(stream.into_future().map_err(|(e, _)| e)) .unwrap(); + } + + #[test] + #[ignore] // requires locally running substrate node + fn test_chain_subscribe_finalized_blocks() { + let (mut rt, client) = test_setup(); + + let stream = rt.block_on(client.subscribe_finalized_blocks()).unwrap(); + let (_header, _) = rt + .block_on(stream.into_future().map_err(|(e, _)| e)) + .unwrap(); + } + + #[test] + #[ignore] // requires locally running substrate node + fn test_chain_read_metadata() { + let (_, client) = test_setup(); let balances = client.metadata().module("Balances").unwrap(); - let dest = substrate_keyring::AccountKeyring::Bob.pair().public(); - let transfer = srml_balances::Call::transfer(dest.clone().into(), 10_000); - let call = node_runtime::Call::Balances(transfer.clone()) - .encode() - .to_vec(); - let call2 = balances.call(transfer).0; - assert_eq!(call, call2); + let address: Address = dest.clone().into(); + let amount: Balance = 10_000; - let free_balance = >::key_for(&dest); + let transfer = srml_balances::Call::transfer(address.clone(), amount); + let call = node_runtime::Call::Balances(transfer); + let call2 = balances + .call("transfer", (address, codec::compact(amount))) + .unwrap(); + assert_eq!(call.encode().to_vec(), call2.0); + + let free_balance = + >::key_for(&dest); let free_balance_key = StorageKey(blake2_256(&free_balance).to_vec()); let free_balance_key2 = balances .storage("FreeBalance") .unwrap() - .map() + .get_map::() .unwrap() - .key(&dest); + .key(dest.clone()); assert_eq!(free_balance_key, free_balance_key2); - let account_nonce = >::key_for(&dest); + let account_nonce = + >::key_for(&dest); let account_nonce_key = StorageKey(blake2_256(&account_nonce).to_vec()); let account_nonce_key2 = client .metadata() @@ -348,9 +424,9 @@ mod tests { .unwrap() .storage("AccountNonce") .unwrap() - .map() + .get_map::() .unwrap() - .key(&dest); + .key(dest); assert_eq!(account_nonce_key, account_nonce_key2); } } diff --git a/src/metadata.rs b/src/metadata.rs index 0a28dda15d..a81d03939c 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,4 +1,8 @@ -use parity_scale_codec::Encode; +use crate::codec::Encoded; +use parity_scale_codec::{ + Decode, + Encode, +}; use runtime_metadata::{ DecodeDifferent, RuntimeMetadata, @@ -11,15 +15,17 @@ use runtime_metadata::{ use std::{ collections::HashMap, convert::TryFrom, + marker::PhantomData, }; use substrate_primitives::storage::StorageKey; -pub struct Encoded(pub Vec); - -impl Encode for Encoded { - fn encode(&self) -> Vec { - self.0.to_owned() - } +#[derive(Debug)] +pub enum MetadataError { + ModuleNotFound(&'static str), + CallNotFound(&'static str), + StorageNotFound(&'static str), + StorageTypeError, + MapValueTypeError, } #[derive(Clone, Debug)] @@ -28,27 +34,66 @@ pub struct Metadata { } impl Metadata { - pub fn module(&self, name: &str) -> Option<&ModuleMetadata> { - self.modules.get(name) + pub fn module(&self, name: &'static str) -> Result<&ModuleMetadata, MetadataError> { + self.modules + .get(name) + .ok_or(MetadataError::ModuleNotFound(name)) + } + + pub fn pretty(&self) -> String { + let mut string = String::new(); + for (name, module) in &self.modules { + string.push_str(name.as_str()); + string.push('\n'); + for (storage, _) in &module.storage { + string.push_str(" s "); + string.push_str(storage.as_str()); + string.push('\n'); + } + for (call, _) in &module.calls { + string.push_str(" c "); + string.push_str(call.as_str()); + string.push('\n'); + } + for (event, _) in &module.events { + string.push_str(" e "); + string.push_str(event.as_str()); + string.push('\n'); + } + } + string } } #[derive(Clone, Debug)] pub struct ModuleMetadata { - index: u8, + index: Vec, storage: HashMap, - // calls, event, constants + calls: HashMap>, + events: HashMap>, + // constants } impl ModuleMetadata { - pub fn call(&self, call: T) -> Encoded { - let mut bytes = vec![self.index]; - bytes.extend(call.encode()); - Encoded(bytes) + pub fn call( + &self, + function: &'static str, + params: T, + ) -> Result { + let fn_bytes = self + .calls + .get(function) + .ok_or(MetadataError::CallNotFound(function))?; + let mut bytes = self.index.clone(); + bytes.extend(fn_bytes); + bytes.extend(params.encode()); + Ok(Encoded(bytes)) } - pub fn storage(&self, key: &str) -> Option<&StorageMetadata> { - self.storage.get(key) + pub fn storage(&self, key: &'static str) -> Result<&StorageMetadata, MetadataError> { + self.storage + .get(key) + .ok_or(MetadataError::StorageNotFound(key)) } } @@ -61,26 +106,37 @@ pub struct StorageMetadata { } impl StorageMetadata { - pub fn map(&self) -> Option { + pub fn get_map( + &self, + ) -> Result, MetadataError> { match &self.ty { StorageEntryType::Map { hasher, .. } => { let prefix = self.prefix.as_bytes().to_vec(); let hasher = hasher.to_owned(); - Some(StorageMap { prefix, hasher }) + let default = Decode::decode(&mut &self.default[..]) + .map_err(|_| MetadataError::MapValueTypeError)?; + Ok(StorageMap { + _marker: PhantomData, + prefix, + hasher, + default, + }) } - _ => None, + _ => Err(MetadataError::StorageTypeError), } } } #[derive(Clone, Debug)] -pub struct StorageMap { +pub struct StorageMap { + _marker: PhantomData, prefix: Vec, hasher: StorageHasher, + default: V, } -impl StorageMap { - pub fn key(&self, key: K) -> StorageKey { +impl StorageMap { + pub fn key(&self, key: K) -> StorageKey { let mut bytes = self.prefix.clone(); bytes.extend(key.encode()); let hash = match self.hasher { @@ -96,6 +152,10 @@ impl StorageMap { }; StorageKey(hash) } + + pub fn default(&self) -> V { + self.default.clone() + } } #[derive(Debug)] @@ -118,10 +178,7 @@ impl TryFrom for Metadata { }; let mut modules = HashMap::new(); for (i, module) in convert(meta.modules)?.into_iter().enumerate() { - modules.insert( - convert(module.name.clone())?, - convert_module(i as u8, module)?, - ); + modules.insert(convert(module.name.clone())?, convert_module(i, module)?); } Ok(Metadata { modules }) } @@ -135,10 +192,10 @@ fn convert(dd: DecodeDifferent) -> Result Result { - let mut entries = HashMap::new(); + let mut storage_map = HashMap::new(); if let Some(storage) = module.storage { let storage = convert(storage)?; let prefix = convert(storage.prefix)?; @@ -146,13 +203,28 @@ fn convert_module( let entry_name = convert(entry.name.clone())?; let entry_prefix = format!("{} {}", prefix, entry_name); let entry = convert_entry(entry_prefix, entry)?; - entries.insert(entry_name, entry); + storage_map.insert(entry_name, entry); + } + } + let mut call_map = HashMap::new(); + if let Some(calls) = module.calls { + for (index, call) in convert(calls)?.into_iter().enumerate() { + let name = convert(call.name)?; + call_map.insert(name, vec![index as u8]); + } + } + let mut event_map = HashMap::new(); + if let Some(events) = module.event { + for (index, event) in convert(events)?.into_iter().enumerate() { + let name = convert(event.name)?; + event_map.insert(name, vec![index as u8]); } } - Ok(ModuleMetadata { - index, - storage: entries, + index: vec![index as u8], + storage: storage_map, + calls: call_map, + events: event_map, }) } diff --git a/src/rpc.rs b/src/rpc.rs index ab0238d337..9d27a92491 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -17,6 +17,7 @@ use crate::{ error::Error, metadata::Metadata, + srml::system::System, }; use futures::future::{ self, @@ -33,16 +34,13 @@ use parity_scale_codec::{ use runtime_metadata::RuntimeMetadataPrefixed; use runtime_primitives::{ - generic::UncheckedExtrinsic, - traits::{ - SignedExtension, - StaticLookup, + generic::{ + Block, + SignedBlock, + UncheckedExtrinsic, }, -}; -use serde::{ - self, - de::Error as DeError, - Deserialize, + traits::StaticLookup, + OpaqueExtrinsic, }; use std::convert::TryInto; use substrate_primitives::{ @@ -59,59 +57,19 @@ use substrate_rpc::{ state::StateClient, }; -/// Copy of runtime_primitives::OpaqueExtrinsic to allow a local Deserialize impl -#[derive(PartialEq, Eq, Clone, Default, Encode, Decode)] -pub struct OpaqueExtrinsic(pub Vec); - -impl std::fmt::Debug for OpaqueExtrinsic { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - fmt, - "{}", - substrate_primitives::hexdisplay::HexDisplay::from(&self.0) - ) - } -} - -impl<'a> serde::Deserialize<'a> for OpaqueExtrinsic { - fn deserialize(de: D) -> std::result::Result - where - D: serde::Deserializer<'a>, - { - let r = substrate_primitives::bytes::deserialize(de)?; - Decode::decode(&mut &r[..]) - .map_err(|e| DeError::custom(format!("Decode error: {}", e))) - } -} - -/// Copy of runtime_primitives::generic::Block with Deserialize implemented -#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug, Deserialize)] -pub struct Block { - // not included: pub header: Header, - /// The accompanying extrinsics. - pub extrinsics: Vec, -} - -/// Copy of runtime_primitives::generic::SignedBlock with Deserialize implemented -#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug, Deserialize)] -pub struct SignedBlock { - /// Full block. - pub block: Block, -} +type ChainBlock = SignedBlock::Header, OpaqueExtrinsic>>; /// Client for substrate rpc interfaces -pub struct Rpc { - _marker: std::marker::PhantomData, +pub struct Rpc { state: StateClient, - chain: ChainClient, + chain: ChainClient>, author: AuthorClient, } /// Allows connecting to all inner interfaces on the same RpcChannel -impl From for Rpc { +impl From for Rpc { fn from(channel: RpcChannel) -> Self { Self { - _marker: std::marker::PhantomData, state: channel.clone().into(), chain: channel.clone().into(), author: channel.into(), @@ -119,7 +77,7 @@ impl From for Rpc } } -impl Rpc { +impl Rpc { /// Fetch a storage key pub fn storage( &self, @@ -156,13 +114,12 @@ impl Rpc { } } -impl Rpc { +impl Rpc { /// Create and submit an extrinsic and return corresponding Hash if successful - pub fn create_and_submit_extrinsic( + pub fn submit_extrinsic( self, signer: P, call: C, - extra: SE, account_nonce: T::Index, genesis_hash: T::Hash, ) -> impl Future @@ -172,13 +129,8 @@ impl Rpc { P::Public: Into<::Source>, P::Signature: Codec, { - let extrinsic = Self::create_and_sign_extrinsic( - &signer, - call, - extra, - account_nonce, - genesis_hash, - ); + let extrinsic = + Self::create_and_sign_extrinsic(&signer, call, account_nonce, genesis_hash); self.author .submit_extrinsic(extrinsic.encode().into()) @@ -189,10 +141,14 @@ impl Rpc { fn create_and_sign_extrinsic( signer: &P, call: C, - extra: SE, account_nonce: T::Index, genesis_hash: T::Hash, - ) -> UncheckedExtrinsic<::Source, C, P::Signature, SE> + ) -> UncheckedExtrinsic< + ::Source, + C, + P::Signature, + T::SignedExtra, + > where C: Encode + Send, P: Pair, @@ -205,6 +161,7 @@ impl Rpc { account_nonce ); + let extra = T::extra(account_nonce); let raw_payload = (call, extra.clone(), (&genesis_hash, &genesis_hash)); let signature = raw_payload.using_encoded(|payload| { if payload.len() > 256 { @@ -226,7 +183,10 @@ impl Rpc { use crate::ExtrinsicSuccess; use futures::{ future::IntoFuture, - stream::Stream, + stream::{ + self, + Stream, + }, }; use jsonrpc_core_client::TypedSubscriptionStream; use runtime_primitives::traits::Hash; @@ -237,18 +197,55 @@ use substrate_primitives::{ }; use transaction_pool::txpool::watcher::Status; -impl Rpc { +type MapClosure = Box T + Send>; +pub type MapStream = stream::Map, MapClosure>; + +impl Rpc { /// Subscribe to substrate System Events - fn subscribe_events( + pub fn subscribe_events( &self, - ) -> impl Future>, Error = Error> + ) -> impl Future::Hash>>, Error = Error> { let events_key = b"System Events"; let storage_key = twox_128(events_key); log::debug!("Events storage key {:?}", storage_key); + let closure: MapClosure::Hash>> = + Box::new(|event| { + log::info!("Event {:?}", event); + event + }); self.state .subscribe_storage(Some(vec![StorageKey(storage_key.to_vec())])) + .map(|stream: TypedSubscriptionStream<_>| stream.map(closure)) + .map_err(Into::into) + } + + /// Subscribe to blocks. + pub fn subscribe_blocks( + &self, + ) -> impl Future, Error = Error> { + let closure: MapClosure = Box::new(|event| { + log::info!("New block {:?}", event); + event + }); + self.chain + .subscribe_new_head() + .map(|stream| stream.map(closure)) + .map_err(Into::into) + } + + /// Subscribe to finalized blocks. + pub fn subscribe_finalized_blocks( + &self, + ) -> impl Future, Error = Error> { + let closure: MapClosure = Box::new(|event| { + log::info!("Finalized block {:?}", event); + event + }); + self.chain + .subscribe_finalized_heads() + .map(|stream| stream.map(closure)) .map_err(Into::into) } @@ -260,7 +257,7 @@ impl Rpc { ::Source, C, P::Signature, - SE, + T::SignedExtra, >, ) -> impl Future where @@ -275,6 +272,7 @@ impl Rpc { .and_then(|stream| { stream .filter_map(|status| { + log::info!("received status {:?}", status); match status { // ignore in progress extrinsic for now Status::Future | Status::Ready | Status::Broadcast(_) => None, @@ -296,12 +294,10 @@ impl Rpc { } /// Create and submit an extrinsic and return corresponding Event if successful - #[allow(unused)] - pub fn create_and_watch_extrinsic( + pub fn submit_and_watch_extrinsic( self, signer: P, call: C, - extra: SE, account_nonce: T::Index, genesis_hash: T::Hash, ) -> impl Future, Error = Error> @@ -316,7 +312,6 @@ impl Rpc { let extrinsic = Self::create_and_sign_extrinsic( &signer, call, - extra, account_nonce, genesis_hash, ); @@ -350,11 +345,11 @@ impl Rpc { } /// Waits for events for the block triggered by the extrinsic -fn wait_for_block_events( +fn wait_for_block_events( ext_hash: T::Hash, - signed_block: &SignedBlock, + signed_block: &ChainBlock, block_hash: T::Hash, - events_stream: TypedSubscriptionStream>, + events_stream: MapStream>, ) -> impl Future, Error = Error> { let ext_index = signed_block .block diff --git a/src/srml/balances.rs b/src/srml/balances.rs new file mode 100644 index 0000000000..a7d4ce0a85 --- /dev/null +++ b/src/srml/balances.rs @@ -0,0 +1,129 @@ +//! Implements support for the srml_balances module. +use crate::{ + codec::compact, + error::Error, + srml::system::System, + Client, + XtBuilder, +}; +use futures::future::{ + self, + Future, +}; +use parity_scale_codec::Codec; +use runtime_primitives::traits::{ + MaybeSerializeDebug, + Member, + SimpleArithmetic, + StaticLookup, +}; +use runtime_support::Parameter; +use substrate_primitives::Pair; + +/// The subset of the `srml_balances::Trait` that a client must implement. +pub trait Balances: System { + /// The balance of an account. + type Balance: Parameter + + Member + + SimpleArithmetic + + Codec + + Default + + Copy + + MaybeSerializeDebug + + From<::BlockNumber>; +} + +/// The Balances extension trait for the Client. +pub trait BalancesStore { + /// Balances type. + type Balances: Balances; + + /// The 'free' balance of a given account. + /// + /// This is the only balance that matters in terms of most operations on + /// tokens. It alone is used to determine the balance when in the contract + /// execution environment. When this balance falls below the value of + /// `ExistentialDeposit`, then the 'current account' is deleted: + /// specifically `FreeBalance`. Further, the `OnFreeBalanceZero` callback + /// is invoked, giving a chance to external modules to clean up data + /// associated with the deleted account. + /// + /// `system::AccountNonce` is also deleted if `ReservedBalance` is also + /// zero. It also gets collapsed to zero if it ever becomes less than + /// `ExistentialDeposit`. + fn free_balance( + &self, + account_id: ::AccountId, + ) -> Box::Balance, Error = Error> + Send>; +} + +impl BalancesStore for Client { + type Balances = T; + + fn free_balance( + &self, + account_id: ::AccountId, + ) -> Box::Balance, Error = Error> + Send> + { + let free_balance_map = || { + Ok(self + .metadata() + .module("Balances")? + .storage("FreeBalance")? + .get_map::< + ::AccountId, + ::Balance>()?) + }; + let map = match free_balance_map() { + Ok(map) => map, + Err(err) => return Box::new(future::err(err)), + }; + Box::new(self.fetch_or(map.key(account_id), map.default())) + } +} + +/// The Balances extension trait for the XtBuilder. +pub trait BalancesCalls { + /// Balances type. + type Balances: Balances; + + /// Transfer some liquid free balance to another account. + /// + /// `transfer` will set the `FreeBalance` of the sender and receiver. + /// It will decrease the total issuance of the system by the `TransferFee`. + /// If the sender's account is below the existential deposit as a result + /// of the transfer, the account will be reaped. + fn transfer( + &mut self, + to: <::Lookup as StaticLookup>::Source, + amount: ::Balance, + ) -> Box::Hash, Error = Error> + Send>; +} + +impl BalancesCalls for XtBuilder +where + P: Pair, + P::Public: Into<<::Lookup as StaticLookup>::Source>, + P::Signature: Codec, +{ + type Balances = T; + + fn transfer( + &mut self, + to: <::Lookup as StaticLookup>::Source, + amount: ::Balance, + ) -> Box::Hash, Error = Error> + Send> + { + let transfer_call = || { + Ok(self + .metadata() + .module("Balances")? + .call("transfer", (to, compact(amount)))?) + }; + let call = match transfer_call() { + Ok(call) => call, + Err(err) => return Box::new(future::err(err)), + }; + Box::new(self.submit(call)) + } +} diff --git a/src/srml/contracts.rs b/src/srml/contracts.rs new file mode 100644 index 0000000000..bcea12cda2 --- /dev/null +++ b/src/srml/contracts.rs @@ -0,0 +1,146 @@ +//! Implements support for the srml_contracts module. +use crate::{ + codec::compact, + error::Error, + srml::{ + balances::Balances, + system::System, + }, + XtBuilder, +}; +use futures::future::{ + self, + Future, +}; +use parity_scale_codec::Codec; +use runtime_primitives::traits::StaticLookup; +use substrate_primitives::Pair; + +/// Gas units are chosen to be represented by u64 so that gas metering +/// instructions can operate on them efficiently. +pub type Gas = u64; + +/// The subset of the `srml_contracts::Trait` that a client must implement. +pub trait Contracts: System + Balances {} + +/// The Contracts extension trait for the XtBuilder. +pub trait ContractsCall { + /// Contracts type. + type Contracts: Contracts; + + /// Stores the given binary Wasm code into the chain's storage and returns + /// its `codehash`. + /// You can instantiate contracts only with stored code. + fn put_code( + &mut self, + gas_limit: Gas, + code: Vec, + ) -> Box::Hash, Error = Error> + Send>; + + /// Creates a new contract from the `codehash` generated by `put_code`, + /// optionally transferring some balance. + /// + /// Creation is executed as follows: + /// + /// - The destination address is computed based on the sender and hash of + /// the code. + /// - The smart-contract account is created at the computed address. + /// - The `ctor_code` is executed in the context of the newly-created + /// account. Buffer returned after the execution is saved as the `code` + /// of the account. That code will be invoked upon any call received by + /// this account. + /// - The contract is initialized. + fn create( + &mut self, + endowment: ::Balance, + gas_limit: Gas, + code_hash: ::Hash, + data: Vec, + ) -> Box::Hash, Error = Error> + Send>; + + /// Makes a call to an account, optionally transferring some balance. + /// + /// * If the account is a smart-contract account, the associated code will + /// be executed and any value will be transferred. + /// * If the account is a regular account, any value will be transferred. + /// * If no account exists and the call value is not less than + /// `existential_deposit`, a regular account will be created and any value + /// will be transferred. + fn call( + &mut self, + dest: <::Lookup as StaticLookup>::Source, + value: ::Balance, + gas_limit: Gas, + data: Vec, + ) -> Box::Hash, Error = Error> + Send>; +} + +impl ContractsCall for XtBuilder +where + P: Pair, + P::Public: Into<<::Lookup as StaticLookup>::Source>, + P::Signature: Codec, +{ + type Contracts = T; + + fn put_code( + &mut self, + gas_limit: Gas, + code: Vec, + ) -> Box::Hash, Error = Error> + Send> + { + let put_code_call = || { + Ok(self + .metadata() + .module("Contracts")? + .call("put_code", (compact(gas_limit), code))?) + }; + let call = match put_code_call() { + Ok(call) => call, + Err(err) => return Box::new(future::err(err)), + }; + Box::new(self.submit(call)) + } + + fn create( + &mut self, + endowment: ::Balance, + gas_limit: Gas, + code_hash: ::Hash, + data: Vec, + ) -> Box::Hash, Error = Error> + Send> + { + let create_call = || { + Ok(self.metadata().module("Contracts")?.call( + "create", + (compact(endowment), compact(gas_limit), code_hash, data), + )?) + }; + let call = match create_call() { + Ok(call) => call, + Err(err) => return Box::new(future::err(err)), + }; + Box::new(self.submit(call)) + } + + fn call( + &mut self, + dest: <::Lookup as StaticLookup>::Source, + value: ::Balance, + gas_limit: Gas, + data: Vec, + ) -> Box::Hash, Error = Error> + Send> + { + let call_call = || { + Ok(self + .metadata() + .module("Contracts")? + .call("call", (dest, compact(value), compact(gas_limit), data))?) + }; + let call = match call_call() { + Ok(call) => call, + Err(err) => return Box::new(future::err(err)), + }; + Box::new(self.submit(call)) + } +} diff --git a/src/srml/mod.rs b/src/srml/mod.rs new file mode 100644 index 0000000000..38cfc8eb1a --- /dev/null +++ b/src/srml/mod.rs @@ -0,0 +1,5 @@ +//! Implements support for built-in runtime modules. + +pub mod balances; +pub mod contracts; +pub mod system; diff --git a/src/srml/system.rs b/src/srml/system.rs new file mode 100644 index 0000000000..d7cde34470 --- /dev/null +++ b/src/srml/system.rs @@ -0,0 +1,170 @@ +//! Implements support for the srml_system module. +use crate::{ + error::Error, + Client, + XtBuilder, +}; +use futures::future::{ + self, + Future, +}; +use parity_scale_codec::Codec; +use runtime_primitives::traits::{ + Bounded, + CheckEqual, + Hash, + Header, + MaybeDisplay, + MaybeSerializeDebug, + MaybeSerializeDebugButNotDeserialize, + Member, + SignedExtension, + SimpleArithmetic, + SimpleBitOps, + StaticLookup, +}; +use runtime_support::Parameter; +use serde::de::DeserializeOwned; +use srml_system::Event; +use substrate_primitives::Pair; + +/// The subset of the `srml_system::Trait` that a client must implement. +pub trait System { + /// Account index (aka nonce) type. This stores the number of previous + /// transactions associated with a sender account. + type Index: Parameter + + Member + + MaybeSerializeDebugButNotDeserialize + + Default + + MaybeDisplay + + SimpleArithmetic + + Copy; + + /// The block number type used by the runtime. + type BlockNumber: Parameter + + Member + + MaybeSerializeDebug + + MaybeDisplay + + SimpleArithmetic + + Default + + Bounded + + Copy + + std::hash::Hash; + + /// The output of the `Hashing` function. + type Hash: Parameter + + Member + + MaybeSerializeDebug + + MaybeDisplay + + SimpleBitOps + + Default + + Copy + + CheckEqual + + std::hash::Hash + + AsRef<[u8]> + + AsMut<[u8]>; + + /// The hashing system (algorithm) being used in the runtime (e.g. Blake2). + type Hashing: Hash; + + /// The user account identifier type for the runtime. + type AccountId: Parameter + + Member + + MaybeSerializeDebug + + MaybeDisplay + + Ord + + Default; + + /// Converting trait to take a source type and convert to `AccountId`. + /// + /// Used to define the type and conversion mechanism for referencing + /// accounts in transactions. It's perfectly reasonable for this to be an + /// identity conversion (with the source type being `AccountId`), but other + /// modules (e.g. Indices module) may provide more functional/efficient + /// alternatives. + type Lookup: StaticLookup; + + /// The block header. + type Header: Parameter + + Header + + DeserializeOwned; + + /// The aggregated event type of the runtime. + type Event: Parameter + Member + From; + + /// The `SignedExtension` to the basic transaction logic. + type SignedExtra: SignedExtension; + + /// Creates the `SignedExtra` from the account nonce. + fn extra(nonce: Self::Index) -> Self::SignedExtra; +} + +/// The System extension trait for the Client. +pub trait SystemStore { + /// System type. + type System: System; + + /// Returns the account nonce for an account_id. + fn account_nonce( + &self, + account_id: ::AccountId, + ) -> Box::Index, Error = Error> + Send>; +} + +impl SystemStore for Client { + type System = T; + + fn account_nonce( + &self, + account_id: ::AccountId, + ) -> Box::Index, Error = Error> + Send> + { + let account_nonce_map = || { + Ok(self + .metadata + .module("System")? + .storage("AccountNonce")? + .get_map()?) + }; + let map = match account_nonce_map() { + Ok(map) => map, + Err(err) => return Box::new(future::err(err)), + }; + Box::new(self.fetch_or(map.key(account_id), map.default())) + } +} + +/// The System extension trait for the XtBuilder. +pub trait SystemCalls { + /// System type. + type System: System; + + /// Sets the new code. + fn set_code( + &mut self, + code: Vec, + ) -> Box::Hash, Error = Error> + Send>; +} + +impl SystemCalls for XtBuilder +where + P: Pair, + P::Public: Into<<::Lookup as StaticLookup>::Source>, + P::Signature: Codec, +{ + type System = T; + + fn set_code( + &mut self, + code: Vec, + ) -> Box::Hash, Error = Error> + Send> + { + let set_code_call = + || Ok(self.metadata().module("System")?.call("set_code", code)?); + let call = match set_code_call() { + Ok(call) => call, + Err(err) => return Box::new(future::err(err)), + }; + Box::new(self.submit(call)) + } +}