mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 23:31:07 +00:00
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
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
use parity_scale_codec::{
|
||||
Encode,
|
||||
EncodeAsRef,
|
||||
HasCompact,
|
||||
};
|
||||
|
||||
pub struct Encoded(pub Vec<u8>);
|
||||
|
||||
impl Encode for Encoded {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
self.0.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compact<T: HasCompact>(t: T) -> Encoded {
|
||||
let encodable: <<T as HasCompact>::Type as EncodeAsRef<'_, T>>::RefType =
|
||||
From::from(&t);
|
||||
Encoded(encodable.encode())
|
||||
}
|
||||
@@ -14,17 +14,26 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with substrate-subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
|
||||
+232
-156
@@ -14,7 +14,17 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with substrate-subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<T: srml_system::Trait> {
|
||||
pub struct ExtrinsicSuccess<T: System> {
|
||||
/// Block hash.
|
||||
pub block: T::Hash,
|
||||
/// Extinsic hash.
|
||||
pub extrinsic: T::Hash,
|
||||
/// List of events.
|
||||
pub events: Vec<T::Event>,
|
||||
}
|
||||
|
||||
fn connect<T: srml_system::Trait, SE: SignedExtension>(
|
||||
url: &Url,
|
||||
) -> impl Future<Item = rpc::Rpc<T, SE>, Error = error::Error> {
|
||||
fn connect<T: System>(url: &Url) -> impl Future<Item = Rpc<T>, Error = Error> {
|
||||
ws::connect(url.as_str())
|
||||
.expect("Url is a valid url; qed")
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub struct ClientBuilder<T: srml_system::Trait, SE: SignedExtension> {
|
||||
_marker: std::marker::PhantomData<(T, SE)>,
|
||||
/// ClientBuilder for constructing a Client.
|
||||
pub struct ClientBuilder<T: System> {
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
url: Option<Url>,
|
||||
}
|
||||
|
||||
impl<T: srml_system::Trait, SE: SignedExtension> ClientBuilder<T, SE> {
|
||||
impl<T: System> ClientBuilder<T> {
|
||||
/// Creates a new ClientBuilder.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_marker: std::marker::PhantomData,
|
||||
@@ -67,21 +92,22 @@ impl<T: srml_system::Trait, SE: SignedExtension> ClientBuilder<T, SE> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Item = Client<T, SE>, Error = error::Error> {
|
||||
/// Creates a new Client.
|
||||
pub fn build(self) -> impl Future<Item = Client<T>, Error = Error> {
|
||||
let url = self.url.unwrap_or_else(|| {
|
||||
Url::parse("ws://127.0.0.1:9944").expect("Is valid url; qed")
|
||||
});
|
||||
connect::<T, SE>(&url).and_then(|rpc| {
|
||||
connect::<T>(&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<T: srml_system::Trait, SE: SignedExtension> ClientBuilder<T, SE> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Client<T: srml_system::Trait, SE: SignedExtension> {
|
||||
_marker: std::marker::PhantomData<SE>,
|
||||
/// Client to interface with a substrate node.
|
||||
pub struct Client<T: System> {
|
||||
url: Url,
|
||||
genesis_hash: T::Hash,
|
||||
metadata: Metadata,
|
||||
}
|
||||
|
||||
impl<T: srml_system::Trait, SE: SignedExtension> Client<T, SE> {
|
||||
fn connect(&self) -> impl Future<Item = rpc::Rpc<T, SE>, Error = error::Error> {
|
||||
impl<T: System> Clone for Client<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
url: self.url.clone(),
|
||||
genesis_hash: self.genesis_hash.clone(),
|
||||
metadata: self.metadata.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: System + 'static> Client<T> {
|
||||
fn connect(&self) -> impl Future<Item = Rpc<T>, 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<V: Decode>(
|
||||
&self,
|
||||
key: StorageKey,
|
||||
) -> impl Future<Item = Option<V>, Error = error::Error> {
|
||||
) -> impl Future<Item = Option<V>, Error = Error> {
|
||||
self.connect().and_then(|rpc| rpc.storage::<V>(key))
|
||||
}
|
||||
|
||||
/// Fetch a StorageKey or return the default.
|
||||
pub fn fetch_or<V: Decode>(
|
||||
&self,
|
||||
key: StorageKey,
|
||||
default: V,
|
||||
) -> impl Future<Item = V, Error = error::Error> {
|
||||
) -> impl Future<Item = V, Error = Error> {
|
||||
self.fetch(key).map(|value| value.unwrap_or(default))
|
||||
}
|
||||
|
||||
/// Fetch a StorageKey or return the default.
|
||||
pub fn fetch_or_default<V: Decode + Default>(
|
||||
&self,
|
||||
key: StorageKey,
|
||||
) -> impl Future<Item = V, Error = error::Error> {
|
||||
) -> impl Future<Item = V, Error = Error> {
|
||||
self.fetch(key).map(|value| value.unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn xt<P, E>(
|
||||
/// Subscribe to events.
|
||||
pub fn subscribe_events(
|
||||
&self,
|
||||
) -> impl Future<Item = MapStream<StorageChangeSet<T::Hash>>, Error = Error> {
|
||||
self.connect().and_then(|rpc| rpc.subscribe_events())
|
||||
}
|
||||
|
||||
/// Subscribe to new blocks.
|
||||
pub fn subscribe_blocks(
|
||||
&self,
|
||||
) -> impl Future<Item = MapStream<T::Header>, Error = Error> {
|
||||
self.connect().and_then(|rpc| rpc.subscribe_blocks())
|
||||
}
|
||||
|
||||
/// Subscribe to finalized blocks.
|
||||
pub fn subscribe_finalized_blocks(
|
||||
&self,
|
||||
) -> impl Future<Item = MapStream<T::Header>, Error = Error> {
|
||||
self.connect()
|
||||
.and_then(|rpc| rpc.subscribe_finalized_blocks())
|
||||
}
|
||||
|
||||
/// Create a transaction builder for a private key.
|
||||
pub fn xt<P>(
|
||||
&self,
|
||||
signer: P,
|
||||
extra: E,
|
||||
) -> impl Future<Item = XtBuilder<T, SE, P, E>, Error = error::Error>
|
||||
nonce: Option<T::Index>,
|
||||
) -> impl Future<Item = XtBuilder<T, P>, Error = Error>
|
||||
where
|
||||
P: Pair,
|
||||
P::Public: Into<<T::Lookup as StaticLookup>::Source>,
|
||||
P::Public: Into<T::AccountId> + Into<<T::Lookup as StaticLookup>::Source>,
|
||||
P::Signature: Codec,
|
||||
E: Fn(T::Index) -> SE,
|
||||
{
|
||||
let account_id: <T::Lookup as StaticLookup>::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<T: srml_system::Trait, SE: SignedExtension, P, E> {
|
||||
client: Client<T, SE>,
|
||||
/// Transaction builder.
|
||||
pub struct XtBuilder<T: System, P> {
|
||||
client: Client<T>,
|
||||
nonce: T::Index,
|
||||
signer: P,
|
||||
extra: E,
|
||||
}
|
||||
|
||||
impl<T: srml_system::Trait, SE: SignedExtension, P, E> XtBuilder<T, SE, P, E>
|
||||
impl<T: System + 'static, P> XtBuilder<T, P>
|
||||
where
|
||||
P: Pair,
|
||||
P::Public: Into<<T::Lookup as StaticLookup>::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<C: Encode + Send>(
|
||||
&self,
|
||||
&mut self,
|
||||
call: C,
|
||||
) -> impl Future<Item = T::Hash, Error = error::Error> {
|
||||
) -> impl Future<Item = T::Hash, Error = Error> {
|
||||
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<C: Encode + Send>(
|
||||
&mut self,
|
||||
call: C,
|
||||
) -> impl Future<Item = ExtrinsicSuccess<T>, 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 = <node_runtime::Runtime as srml_system::Trait>::Call;
|
||||
type Origin = <node_runtime::Runtime as srml_system::Trait>::Origin;
|
||||
impl System for Runtime {
|
||||
type Index = <node_runtime::Runtime as srml_system::Trait>::Index;
|
||||
type BlockNumber = <node_runtime::Runtime as srml_system::Trait>::BlockNumber;
|
||||
type Hash = <node_runtime::Runtime as srml_system::Trait>::Hash;
|
||||
type Hashing = <node_runtime::Runtime as srml_system::Trait>::Hashing;
|
||||
type AccountId = <node_runtime::Runtime as srml_system::Trait>::AccountId;
|
||||
type Lookup = <node_runtime::Runtime as srml_system::Trait>::Lookup;
|
||||
type WeightMultiplierUpdate =
|
||||
<node_runtime::Runtime as srml_system::Trait>::WeightMultiplierUpdate;
|
||||
type Header = <node_runtime::Runtime as srml_system::Trait>::Header;
|
||||
type Event = <node_runtime::Runtime as srml_system::Trait>::Event;
|
||||
type BlockHashCount =
|
||||
<node_runtime::Runtime as srml_system::Trait>::BlockHashCount;
|
||||
type MaximumBlockWeight =
|
||||
<node_runtime::Runtime as srml_system::Trait>::MaximumBlockWeight;
|
||||
type MaximumBlockLength =
|
||||
<node_runtime::Runtime as srml_system::Trait>::MaximumBlockLength;
|
||||
type AvailableBlockRatio =
|
||||
<node_runtime::Runtime as srml_system::Trait>::AvailableBlockRatio;
|
||||
}
|
||||
|
||||
impl srml_balances::Trait for Runtime {
|
||||
type Balance = <node_runtime::Runtime as srml_balances::Trait>::Balance;
|
||||
type OnFreeBalanceZero = ();
|
||||
type OnNewAccount = ();
|
||||
type TransactionPayment = ();
|
||||
type TransferPayment =
|
||||
<node_runtime::Runtime as srml_balances::Trait>::TransferPayment;
|
||||
type DustRemoval = <node_runtime::Runtime as srml_balances::Trait>::DustRemoval;
|
||||
type Event = <node_runtime::Runtime as srml_balances::Trait>::Event;
|
||||
type ExistentialDeposit =
|
||||
<node_runtime::Runtime as srml_balances::Trait>::ExistentialDeposit;
|
||||
type TransferFee = <node_runtime::Runtime as srml_balances::Trait>::TransferFee;
|
||||
type CreationFee = <node_runtime::Runtime as srml_balances::Trait>::CreationFee;
|
||||
type TransactionBaseFee =
|
||||
<node_runtime::Runtime as srml_balances::Trait>::TransactionBaseFee;
|
||||
type TransactionByteFee =
|
||||
<node_runtime::Runtime as srml_balances::Trait>::TransactionByteFee;
|
||||
type WeightToFee = <node_runtime::Runtime as srml_balances::Trait>::WeightToFee;
|
||||
}
|
||||
|
||||
type SignedExtra = (
|
||||
srml_system::CheckGenesis<Runtime>,
|
||||
srml_system::CheckEra<Runtime>,
|
||||
srml_system::CheckNonce<Runtime>,
|
||||
srml_system::CheckWeight<Runtime>,
|
||||
srml_balances::TakeFees<Runtime>,
|
||||
);
|
||||
|
||||
#[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::<Runtime, SignedExtra>::new().build())
|
||||
.unwrap();
|
||||
|
||||
let signer = substrate_keyring::AccountKeyring::Alice.pair();
|
||||
let extra = |nonce| {
|
||||
type SignedExtra = (
|
||||
srml_system::CheckGenesis<node_runtime::Runtime>,
|
||||
srml_system::CheckEra<node_runtime::Runtime>,
|
||||
srml_system::CheckNonce<node_runtime::Runtime>,
|
||||
srml_system::CheckWeight<node_runtime::Runtime>,
|
||||
srml_balances::TakeFees<node_runtime::Runtime>,
|
||||
);
|
||||
fn extra(nonce: Self::Index) -> Self::SignedExtra {
|
||||
(
|
||||
srml_system::CheckGenesis::<Runtime>::new(),
|
||||
srml_system::CheckEra::<Runtime>::from(Era::Immortal),
|
||||
srml_system::CheckNonce::<Runtime>::from(nonce),
|
||||
srml_system::CheckWeight::<Runtime>::new(),
|
||||
srml_balances::TakeFees::<Runtime>::from(0),
|
||||
srml_system::CheckGenesis::<node_runtime::Runtime>::new(),
|
||||
srml_system::CheckEra::<node_runtime::Runtime>::from(Era::Immortal),
|
||||
srml_system::CheckNonce::<node_runtime::Runtime>::from(nonce),
|
||||
srml_system::CheckWeight::<node_runtime::Runtime>::new(),
|
||||
srml_balances::TakeFees::<node_runtime::Runtime>::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::<Runtime>(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 = <node_runtime::Runtime as srml_balances::Trait>::Balance;
|
||||
}
|
||||
|
||||
type Index = <Runtime as System>::Index;
|
||||
type AccountId = <Runtime as System>::AccountId;
|
||||
type Address = <<Runtime as System>::Lookup as StaticLookup>::Source;
|
||||
type Balance = <Runtime as Balances>::Balance;
|
||||
|
||||
fn test_setup() -> (tokio::runtime::Runtime, Client<Runtime>) {
|
||||
env_logger::try_init().ok();
|
||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let client_future = ClientBuilder::<Runtime>::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::<Runtime, SignedExtra>::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: <Runtime as srml_system::Trait>::AccountId =
|
||||
substrate_keyring::AccountKeyring::Alice
|
||||
.pair()
|
||||
.public()
|
||||
.into();
|
||||
let key = client
|
||||
.metadata()
|
||||
.module("Balances")
|
||||
.unwrap()
|
||||
.storage("FreeBalance")
|
||||
.unwrap()
|
||||
.map()
|
||||
.unwrap()
|
||||
.key(&account);
|
||||
type Balance = <Runtime as srml_balances::Trait>::Balance;
|
||||
rt.block_on(client.fetch::<Balance>(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::<Runtime, SignedExtra>::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 = <srml_balances::FreeBalance<Runtime>>::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 =
|
||||
<srml_balances::FreeBalance<node_runtime::Runtime>>::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::<AccountId, Balance>()
|
||||
.unwrap()
|
||||
.key(&dest);
|
||||
.key(dest.clone());
|
||||
assert_eq!(free_balance_key, free_balance_key2);
|
||||
|
||||
let account_nonce = <srml_system::AccountNonce<Runtime>>::key_for(&dest);
|
||||
let account_nonce =
|
||||
<srml_system::AccountNonce<node_runtime::Runtime>>::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::<AccountId, Index>()
|
||||
.unwrap()
|
||||
.key(&dest);
|
||||
.key(dest);
|
||||
assert_eq!(account_nonce_key, account_nonce_key2);
|
||||
}
|
||||
}
|
||||
|
||||
+105
-33
@@ -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<u8>);
|
||||
|
||||
impl Encode for Encoded {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
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<u8>,
|
||||
storage: HashMap<String, StorageMetadata>,
|
||||
// calls, event, constants
|
||||
calls: HashMap<String, Vec<u8>>,
|
||||
events: HashMap<String, Vec<u8>>,
|
||||
// constants
|
||||
}
|
||||
|
||||
impl ModuleMetadata {
|
||||
pub fn call<T: Encode>(&self, call: T) -> Encoded {
|
||||
let mut bytes = vec![self.index];
|
||||
bytes.extend(call.encode());
|
||||
Encoded(bytes)
|
||||
pub fn call<T: Encode>(
|
||||
&self,
|
||||
function: &'static str,
|
||||
params: T,
|
||||
) -> Result<Encoded, MetadataError> {
|
||||
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<StorageMap> {
|
||||
pub fn get_map<K: Encode, V: Decode + Clone>(
|
||||
&self,
|
||||
) -> Result<StorageMap<K, V>, 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<K, V> {
|
||||
_marker: PhantomData<K>,
|
||||
prefix: Vec<u8>,
|
||||
hasher: StorageHasher,
|
||||
default: V,
|
||||
}
|
||||
|
||||
impl StorageMap {
|
||||
pub fn key<K: Encode>(&self, key: K) -> StorageKey {
|
||||
impl<K: Encode, V: Decode + Clone> StorageMap<K, V> {
|
||||
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<RuntimeMetadataPrefixed> 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<B: 'static, O: 'static>(dd: DecodeDifferent<B, O>) -> Result<O, Error
|
||||
}
|
||||
|
||||
fn convert_module(
|
||||
index: u8,
|
||||
index: usize,
|
||||
module: runtime_metadata::ModuleMetadata,
|
||||
) -> Result<ModuleMetadata, Error> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+73
-78
@@ -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<u8>);
|
||||
|
||||
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<D>(de: D) -> std::result::Result<Self, D::Error>
|
||||
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<OpaqueExtrinsic>,
|
||||
}
|
||||
|
||||
/// 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<T> = SignedBlock<Block<<T as System>::Header, OpaqueExtrinsic>>;
|
||||
|
||||
/// Client for substrate rpc interfaces
|
||||
pub struct Rpc<T: srml_system::Trait, SE: SignedExtension> {
|
||||
_marker: std::marker::PhantomData<SE>,
|
||||
pub struct Rpc<T: System> {
|
||||
state: StateClient<T::Hash>,
|
||||
chain: ChainClient<T::BlockNumber, T::Hash, (), SignedBlock>,
|
||||
chain: ChainClient<T::BlockNumber, T::Hash, T::Header, ChainBlock<T>>,
|
||||
author: AuthorClient<T::Hash, T::Hash>,
|
||||
}
|
||||
|
||||
/// Allows connecting to all inner interfaces on the same RpcChannel
|
||||
impl<T: srml_system::Trait, SE: SignedExtension> From<RpcChannel> for Rpc<T, SE> {
|
||||
impl<T: System> From<RpcChannel> for Rpc<T> {
|
||||
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<T: srml_system::Trait, SE: SignedExtension> From<RpcChannel> for Rpc<T, SE>
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
impl<T: System> Rpc<T> {
|
||||
/// Fetch a storage key
|
||||
pub fn storage<V: Decode>(
|
||||
&self,
|
||||
@@ -156,13 +114,12 @@ impl<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
impl<T: System> Rpc<T> {
|
||||
/// Create and submit an extrinsic and return corresponding Hash if successful
|
||||
pub fn create_and_submit_extrinsic<C, P>(
|
||||
pub fn submit_extrinsic<C, P>(
|
||||
self,
|
||||
signer: P,
|
||||
call: C,
|
||||
extra: SE,
|
||||
account_nonce: T::Index,
|
||||
genesis_hash: T::Hash,
|
||||
) -> impl Future<Item = T::Hash, Error = Error>
|
||||
@@ -172,13 +129,8 @@ impl<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
P::Public: Into<<T::Lookup as StaticLookup>::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<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
fn create_and_sign_extrinsic<C, P>(
|
||||
signer: &P,
|
||||
call: C,
|
||||
extra: SE,
|
||||
account_nonce: T::Index,
|
||||
genesis_hash: T::Hash,
|
||||
) -> UncheckedExtrinsic<<T::Lookup as StaticLookup>::Source, C, P::Signature, SE>
|
||||
) -> UncheckedExtrinsic<
|
||||
<T::Lookup as StaticLookup>::Source,
|
||||
C,
|
||||
P::Signature,
|
||||
T::SignedExtra,
|
||||
>
|
||||
where
|
||||
C: Encode + Send,
|
||||
P: Pair,
|
||||
@@ -205,6 +161,7 @@ impl<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
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<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
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<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
type MapClosure<T> = Box<dyn Fn(T) -> T + Send>;
|
||||
pub type MapStream<T> = stream::Map<TypedSubscriptionStream<T>, MapClosure<T>>;
|
||||
|
||||
impl<T: System> Rpc<T> {
|
||||
/// Subscribe to substrate System Events
|
||||
fn subscribe_events(
|
||||
pub fn subscribe_events(
|
||||
&self,
|
||||
) -> impl Future<Item = TypedSubscriptionStream<StorageChangeSet<T::Hash>>, Error = Error>
|
||||
) -> impl Future<Item = MapStream<StorageChangeSet<<T as System>::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<StorageChangeSet<<T as System>::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<Item = MapStream<T::Header>, Error = Error> {
|
||||
let closure: MapClosure<T::Header> = 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<Item = MapStream<T::Header>, Error = Error> {
|
||||
let closure: MapClosure<T::Header> = 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<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
<T::Lookup as StaticLookup>::Source,
|
||||
C,
|
||||
P::Signature,
|
||||
SE,
|
||||
T::SignedExtra,
|
||||
>,
|
||||
) -> impl Future<Item = T::Hash, Error = Error>
|
||||
where
|
||||
@@ -275,6 +272,7 @@ impl<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
.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<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
}
|
||||
|
||||
/// Create and submit an extrinsic and return corresponding Event if successful
|
||||
#[allow(unused)]
|
||||
pub fn create_and_watch_extrinsic<C, P>(
|
||||
pub fn submit_and_watch_extrinsic<C, P>(
|
||||
self,
|
||||
signer: P,
|
||||
call: C,
|
||||
extra: SE,
|
||||
account_nonce: T::Index,
|
||||
genesis_hash: T::Hash,
|
||||
) -> impl Future<Item = ExtrinsicSuccess<T>, Error = Error>
|
||||
@@ -316,7 +312,6 @@ impl<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
let extrinsic = Self::create_and_sign_extrinsic(
|
||||
&signer,
|
||||
call,
|
||||
extra,
|
||||
account_nonce,
|
||||
genesis_hash,
|
||||
);
|
||||
@@ -350,11 +345,11 @@ impl<T: srml_system::Trait, SE: SignedExtension> Rpc<T, SE> {
|
||||
}
|
||||
|
||||
/// Waits for events for the block triggered by the extrinsic
|
||||
fn wait_for_block_events<T: srml_system::Trait>(
|
||||
fn wait_for_block_events<T: System>(
|
||||
ext_hash: T::Hash,
|
||||
signed_block: &SignedBlock,
|
||||
signed_block: &ChainBlock<T>,
|
||||
block_hash: T::Hash,
|
||||
events_stream: TypedSubscriptionStream<StorageChangeSet<T::Hash>>,
|
||||
events_stream: MapStream<StorageChangeSet<T::Hash>>,
|
||||
) -> impl Future<Item = ExtrinsicSuccess<T>, Error = Error> {
|
||||
let ext_index = signed_block
|
||||
.block
|
||||
|
||||
@@ -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<<Self as System>::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: <Self::Balances as System>::AccountId,
|
||||
) -> Box<dyn Future<Item = <Self::Balances as Balances>::Balance, Error = Error> + Send>;
|
||||
}
|
||||
|
||||
impl<T: Balances + 'static> BalancesStore for Client<T> {
|
||||
type Balances = T;
|
||||
|
||||
fn free_balance(
|
||||
&self,
|
||||
account_id: <Self::Balances as System>::AccountId,
|
||||
) -> Box<dyn Future<Item = <Self::Balances as Balances>::Balance, Error = Error> + Send>
|
||||
{
|
||||
let free_balance_map = || {
|
||||
Ok(self
|
||||
.metadata()
|
||||
.module("Balances")?
|
||||
.storage("FreeBalance")?
|
||||
.get_map::<
|
||||
<Self::Balances as System>::AccountId,
|
||||
<Self::Balances as Balances>::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: <<Self::Balances as System>::Lookup as StaticLookup>::Source,
|
||||
amount: <Self::Balances as Balances>::Balance,
|
||||
) -> Box<dyn Future<Item = <Self::Balances as System>::Hash, Error = Error> + Send>;
|
||||
}
|
||||
|
||||
impl<T: Balances + 'static, P> BalancesCalls for XtBuilder<T, P>
|
||||
where
|
||||
P: Pair,
|
||||
P::Public: Into<<<T as System>::Lookup as StaticLookup>::Source>,
|
||||
P::Signature: Codec,
|
||||
{
|
||||
type Balances = T;
|
||||
|
||||
fn transfer(
|
||||
&mut self,
|
||||
to: <<Self::Balances as System>::Lookup as StaticLookup>::Source,
|
||||
amount: <Self::Balances as Balances>::Balance,
|
||||
) -> Box<dyn Future<Item = <Self::Balances as System>::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))
|
||||
}
|
||||
}
|
||||
@@ -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<u8>,
|
||||
) -> Box<dyn Future<Item = <Self::Contracts as System>::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: <Self::Contracts as Balances>::Balance,
|
||||
gas_limit: Gas,
|
||||
code_hash: <Self::Contracts as System>::Hash,
|
||||
data: Vec<u8>,
|
||||
) -> Box<dyn Future<Item = <Self::Contracts as System>::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: <<Self::Contracts as System>::Lookup as StaticLookup>::Source,
|
||||
value: <Self::Contracts as Balances>::Balance,
|
||||
gas_limit: Gas,
|
||||
data: Vec<u8>,
|
||||
) -> Box<dyn Future<Item = <Self::Contracts as System>::Hash, Error = Error> + Send>;
|
||||
}
|
||||
|
||||
impl<T: Contracts + 'static, P> ContractsCall for XtBuilder<T, P>
|
||||
where
|
||||
P: Pair,
|
||||
P::Public: Into<<<T as System>::Lookup as StaticLookup>::Source>,
|
||||
P::Signature: Codec,
|
||||
{
|
||||
type Contracts = T;
|
||||
|
||||
fn put_code(
|
||||
&mut self,
|
||||
gas_limit: Gas,
|
||||
code: Vec<u8>,
|
||||
) -> Box<dyn Future<Item = <Self::Contracts as System>::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: <Self::Contracts as Balances>::Balance,
|
||||
gas_limit: Gas,
|
||||
code_hash: <Self::Contracts as System>::Hash,
|
||||
data: Vec<u8>,
|
||||
) -> Box<dyn Future<Item = <Self::Contracts as System>::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: <<Self::Contracts as System>::Lookup as StaticLookup>::Source,
|
||||
value: <Self::Contracts as Balances>::Balance,
|
||||
gas_limit: Gas,
|
||||
data: Vec<u8>,
|
||||
) -> Box<dyn Future<Item = <Self::Contracts as System>::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))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements support for built-in runtime modules.
|
||||
|
||||
pub mod balances;
|
||||
pub mod contracts;
|
||||
pub mod system;
|
||||
@@ -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<Output = Self::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<Target = Self::AccountId>;
|
||||
|
||||
/// The block header.
|
||||
type Header: Parameter
|
||||
+ Header<Number = Self::BlockNumber, Hash = Self::Hash>
|
||||
+ DeserializeOwned;
|
||||
|
||||
/// The aggregated event type of the runtime.
|
||||
type Event: Parameter + Member + From<Event>;
|
||||
|
||||
/// 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: <Self::System as System>::AccountId,
|
||||
) -> Box<dyn Future<Item = <Self::System as System>::Index, Error = Error> + Send>;
|
||||
}
|
||||
|
||||
impl<T: System + 'static> SystemStore for Client<T> {
|
||||
type System = T;
|
||||
|
||||
fn account_nonce(
|
||||
&self,
|
||||
account_id: <Self::System as System>::AccountId,
|
||||
) -> Box<dyn Future<Item = <Self::System as System>::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<u8>,
|
||||
) -> Box<dyn Future<Item = <Self::System as System>::Hash, Error = Error> + Send>;
|
||||
}
|
||||
|
||||
impl<T: System + 'static, P> SystemCalls for XtBuilder<T, P>
|
||||
where
|
||||
P: Pair,
|
||||
P::Public: Into<<<T as System>::Lookup as StaticLookup>::Source>,
|
||||
P::Signature: Codec,
|
||||
{
|
||||
type System = T;
|
||||
|
||||
fn set_code(
|
||||
&mut self,
|
||||
code: Vec<u8>,
|
||||
) -> Box<dyn Future<Item = <Self::System as System>::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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user