mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 11:01:01 +00:00
Transaction queue integration + submission RPC (#99)
* Implement transaction queue RPC. * whitespace * Support without_std env in environmental! (#110) * Make environmental crate support without_std env. * Small doc fixes. * Remove dead code.
This commit is contained in:
Generated
+4
@@ -219,6 +219,7 @@ dependencies = [
|
|||||||
"substrate-codec 0.1.0",
|
"substrate-codec 0.1.0",
|
||||||
"substrate-executor 0.1.0",
|
"substrate-executor 0.1.0",
|
||||||
"substrate-primitives 0.1.0",
|
"substrate-primitives 0.1.0",
|
||||||
|
"substrate-rpc 0.1.0",
|
||||||
"substrate-rpc-servers 0.1.0",
|
"substrate-rpc-servers 0.1.0",
|
||||||
"substrate-runtime-io 0.1.0",
|
"substrate-runtime-io 0.1.0",
|
||||||
"substrate-state-machine 0.1.0",
|
"substrate-state-machine 0.1.0",
|
||||||
@@ -1364,8 +1365,10 @@ dependencies = [
|
|||||||
"polkadot-api 0.1.0",
|
"polkadot-api 0.1.0",
|
||||||
"polkadot-primitives 0.1.0",
|
"polkadot-primitives 0.1.0",
|
||||||
"polkadot-runtime 0.1.0",
|
"polkadot-runtime 0.1.0",
|
||||||
|
"substrate-client 0.1.0",
|
||||||
"substrate-codec 0.1.0",
|
"substrate-codec 0.1.0",
|
||||||
"substrate-primitives 0.1.0",
|
"substrate-primitives 0.1.0",
|
||||||
|
"substrate-rpc 0.1.0",
|
||||||
"substrate-runtime-primitives 0.1.0",
|
"substrate-runtime-primitives 0.1.0",
|
||||||
"transaction-pool 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"transaction-pool 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
@@ -1865,6 +1868,7 @@ dependencies = [
|
|||||||
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"jsonrpc-core 8.0.1 (git+https://github.com/paritytech/jsonrpc.git)",
|
"jsonrpc-core 8.0.1 (git+https://github.com/paritytech/jsonrpc.git)",
|
||||||
"jsonrpc-macros 8.0.0 (git+https://github.com/paritytech/jsonrpc.git)",
|
"jsonrpc-macros 8.0.0 (git+https://github.com/paritytech/jsonrpc.git)",
|
||||||
|
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"substrate-client 0.1.0",
|
"substrate-client 0.1.0",
|
||||||
"substrate-executor 0.1.0",
|
"substrate-executor 0.1.0",
|
||||||
"substrate-primitives 0.1.0",
|
"substrate-primitives 0.1.0",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ substrate-state-machine = { path = "../../substrate/state-machine" }
|
|||||||
substrate-executor = { path = "../../substrate/executor" }
|
substrate-executor = { path = "../../substrate/executor" }
|
||||||
substrate-primitives = { path = "../../substrate/primitives" }
|
substrate-primitives = { path = "../../substrate/primitives" }
|
||||||
substrate-rpc-servers = { path = "../../substrate/rpc-servers" }
|
substrate-rpc-servers = { path = "../../substrate/rpc-servers" }
|
||||||
|
substrate-rpc = { path = "../../substrate/rpc" }
|
||||||
demo-primitives = { path = "../primitives" }
|
demo-primitives = { path = "../primitives" }
|
||||||
demo-executor = { path = "../executor" }
|
demo-executor = { path = "../executor" }
|
||||||
demo-runtime = { path = "../runtime" }
|
demo-runtime = { path = "../runtime" }
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ extern crate substrate_runtime_io as runtime_io;
|
|||||||
extern crate substrate_state_machine as state_machine;
|
extern crate substrate_state_machine as state_machine;
|
||||||
extern crate substrate_client as client;
|
extern crate substrate_client as client;
|
||||||
extern crate substrate_primitives as primitives;
|
extern crate substrate_primitives as primitives;
|
||||||
|
extern crate substrate_rpc;
|
||||||
extern crate substrate_rpc_servers as rpc;
|
extern crate substrate_rpc_servers as rpc;
|
||||||
extern crate demo_primitives;
|
extern crate demo_primitives;
|
||||||
extern crate demo_executor;
|
extern crate demo_executor;
|
||||||
@@ -49,6 +50,13 @@ use demo_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfi
|
|||||||
SessionConfig, StakingConfig, BuildExternalities};
|
SessionConfig, StakingConfig, BuildExternalities};
|
||||||
use client::genesis;
|
use client::genesis;
|
||||||
|
|
||||||
|
struct DummyPool;
|
||||||
|
impl substrate_rpc::author::AuthorApi for DummyPool {
|
||||||
|
fn submit_extrinsic(&self, _: primitives::block::Extrinsic) -> substrate_rpc::author::error::Result<()> {
|
||||||
|
Err(substrate_rpc::author::error::ErrorKind::Unimplemented.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse command line arguments and start the node.
|
/// Parse command line arguments and start the node.
|
||||||
///
|
///
|
||||||
/// IANA unassigned port ranges that we could use:
|
/// IANA unassigned port ranges that we could use:
|
||||||
@@ -126,7 +134,7 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
|
|||||||
let client = Arc::new(client::new_in_mem(executor, prepare_genesis)?);
|
let client = Arc::new(client::new_in_mem(executor, prepare_genesis)?);
|
||||||
|
|
||||||
let address = "127.0.0.1:9933".parse().unwrap();
|
let address = "127.0.0.1:9933".parse().unwrap();
|
||||||
let handler = rpc::rpc_handler(client);
|
let handler = rpc::rpc_handler(client, DummyPool);
|
||||||
let server = rpc::start_http(&address, handler)?;
|
let server = rpc::start_http(&address, handler)?;
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("validator") {
|
if let Some(_) = matches.subcommand_matches("validator") {
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
@@ -118,7 +118,7 @@ pub fn run<I, T>(args: I, exit: mpsc::Receiver<()>) -> error::Result<()> where
|
|||||||
let rpc_port: u16 = port.parse().expect("Invalid RPC port value specified.");
|
let rpc_port: u16 = port.parse().expect("Invalid RPC port value specified.");
|
||||||
address.set_port(rpc_port);
|
address.set_port(rpc_port);
|
||||||
}
|
}
|
||||||
let handler = rpc::rpc_handler(service.client());
|
let handler = rpc::rpc_handler(service.client(), service.transaction_pool());
|
||||||
let _server = rpc::start_http(&address, handler)?;
|
let _server = rpc::start_http(&address, handler)?;
|
||||||
|
|
||||||
exit.recv().ok();
|
exit.recv().ok();
|
||||||
@@ -150,6 +150,7 @@ fn default_base_path() -> PathBuf {
|
|||||||
&app_info,
|
&app_info,
|
||||||
).expect("app directories exist on all supported platforms; qed")
|
).expect("app directories exist on all supported platforms; qed")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_logger(pattern: &str) {
|
fn init_logger(pattern: &str) {
|
||||||
let mut builder = env_logger::LogBuilder::new();
|
let mut builder = env_logger::LogBuilder::new();
|
||||||
// Disable info logging by default for some modules:
|
// Disable info logging by default for some modules:
|
||||||
|
|||||||
@@ -570,9 +570,13 @@ impl<C: PolkadotApi, R: TableRouter> bft::Proposer for Proposer<C, R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let polkadot_block = block_builder.bake();
|
let polkadot_block = block_builder.bake();
|
||||||
|
info!("Proposing block [number: {}; extrinsics: [{}], parent_hash: {}]", polkadot_block.header.number, polkadot_block.extrinsics.len(), polkadot_block.header.parent_hash);
|
||||||
|
|
||||||
let substrate_block = Slicable::decode(&mut polkadot_block.encode().as_slice())
|
let substrate_block = Slicable::decode(&mut polkadot_block.encode().as_slice())
|
||||||
.expect("polkadot blocks defined to serialize to substrate blocks correctly; qed");
|
.expect("polkadot blocks defined to serialize to substrate blocks correctly; qed");
|
||||||
|
|
||||||
|
assert!(evaluate_proposal(&substrate_block, &*self.client, current_timestamp(), &self.parent_hash, &self.parent_id).is_ok());
|
||||||
|
|
||||||
Ok(substrate_block)
|
Ok(substrate_block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -9,6 +9,8 @@ error-chain = "0.11"
|
|||||||
polkadot-api = { path = "../api" }
|
polkadot-api = { path = "../api" }
|
||||||
polkadot-primitives = { path = "../primitives" }
|
polkadot-primitives = { path = "../primitives" }
|
||||||
polkadot-runtime = { path = "../runtime" }
|
polkadot-runtime = { path = "../runtime" }
|
||||||
|
substrate-client = { path = "../../substrate/client" }
|
||||||
|
substrate-rpc = { path = "../../substrate/rpc" }
|
||||||
substrate-primitives = { path = "../../substrate/primitives" }
|
substrate-primitives = { path = "../../substrate/primitives" }
|
||||||
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" }
|
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" }
|
||||||
substrate-codec = { path = "../../substrate/codec" }
|
substrate-codec = { path = "../../substrate/codec" }
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
extern crate ed25519;
|
extern crate ed25519;
|
||||||
extern crate ethereum_types;
|
extern crate ethereum_types;
|
||||||
extern crate substrate_codec as codec;
|
extern crate substrate_codec as codec;
|
||||||
|
extern crate substrate_rpc;
|
||||||
extern crate substrate_primitives as substrate_primitives;
|
extern crate substrate_primitives as substrate_primitives;
|
||||||
extern crate substrate_runtime_primitives as substrate_runtime_primitives;
|
extern crate substrate_runtime_primitives as substrate_runtime_primitives;
|
||||||
extern crate polkadot_runtime as runtime;
|
extern crate polkadot_runtime as runtime;
|
||||||
@@ -31,8 +32,10 @@ use std::collections::HashMap;
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use codec::Slicable;
|
||||||
use polkadot_api::PolkadotApi;
|
use polkadot_api::PolkadotApi;
|
||||||
use primitives::{AccountId, Timestamp};
|
use primitives::{AccountId, Timestamp};
|
||||||
|
use substrate_primitives::block::Extrinsic;
|
||||||
use runtime::{Block, UncheckedExtrinsic, TimestampCall, Call};
|
use runtime::{Block, UncheckedExtrinsic, TimestampCall, Call};
|
||||||
use substrate_runtime_primitives::traits::Checkable;
|
use substrate_runtime_primitives::traits::Checkable;
|
||||||
use transaction_pool::{Pool, Readiness};
|
use transaction_pool::{Pool, Readiness};
|
||||||
@@ -371,6 +374,16 @@ impl TransactionPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl substrate_rpc::author::AsyncAuthorApi for TransactionPool {
|
||||||
|
fn submit_extrinsic(&mut self, xt: Extrinsic) -> substrate_rpc::author::error::Result<()> {
|
||||||
|
self.import(xt
|
||||||
|
.using_encoded(|ref mut s| UncheckedExtrinsic::decode(s))
|
||||||
|
.ok_or(substrate_rpc::author::error::ErrorKind::InvalidFormat)?)
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| substrate_rpc::author::error::ErrorKind::PoolError.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,21 @@ impl Slicable for Transaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simple generic extrinsic type.
|
||||||
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||||
|
pub struct Extrinsic(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||||
|
|
||||||
|
impl Slicable for Extrinsic {
|
||||||
|
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||||
|
Vec::<u8>::decode(input).map(Extrinsic)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||||
|
self.0.using_encoded(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Execution log (event)
|
/// Execution log (event)
|
||||||
#[derive(PartialEq, Eq, Clone, Default)]
|
#[derive(PartialEq, Eq, Clone, Default)]
|
||||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ extern crate jsonrpc_http_server as http;
|
|||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
/// Construct rpc `IoHandler`
|
/// Construct rpc `IoHandler`
|
||||||
pub fn rpc_handler<S>(state: S) -> rpc::IoHandler where
|
pub fn rpc_handler<S, T>(state: S, transaction_pool: T) -> rpc::IoHandler where
|
||||||
S: apis::state::StateApi,
|
S: apis::state::StateApi,
|
||||||
|
T: apis::author::AuthorApi,
|
||||||
{
|
{
|
||||||
let mut io = rpc::IoHandler::new();
|
let mut io = rpc::IoHandler::new();
|
||||||
io.extend_with(state.to_delegate());
|
io.extend_with(state.to_delegate());
|
||||||
|
io.extend_with(transaction_pool.to_delegate());
|
||||||
io
|
io
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ version = "0.1.0"
|
|||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
parking_lot = "0.4"
|
||||||
error-chain = "0.11"
|
error-chain = "0.11"
|
||||||
jsonrpc-core = { git="https://github.com/paritytech/jsonrpc.git" }
|
jsonrpc-core = { git="https://github.com/paritytech/jsonrpc.git" }
|
||||||
jsonrpc-macros = { git="https://github.com/paritytech/jsonrpc.git" }
|
jsonrpc-macros = { git="https://github.com/paritytech/jsonrpc.git" }
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Substrate is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Substrate is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use client;
|
||||||
|
use rpc;
|
||||||
|
|
||||||
|
error_chain! {
|
||||||
|
links {
|
||||||
|
Client(client::error::Error, client::error::ErrorKind) #[doc = "Client error"];
|
||||||
|
}
|
||||||
|
errors {
|
||||||
|
/// Not implemented yet
|
||||||
|
Unimplemented {
|
||||||
|
description("not yet implemented"),
|
||||||
|
display("Method Not Implemented"),
|
||||||
|
}
|
||||||
|
/// Invalid format
|
||||||
|
InvalidFormat {
|
||||||
|
description("invalid format"),
|
||||||
|
display("Invalid format for the extrinsic data"),
|
||||||
|
}
|
||||||
|
/// Some error with the pool since the import failed.
|
||||||
|
PoolError {
|
||||||
|
description("pool import failed"),
|
||||||
|
display("Pool import failed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Error> for rpc::Error {
|
||||||
|
fn from(e: Error) -> Self {
|
||||||
|
match e {
|
||||||
|
Error(ErrorKind::Unimplemented, _) => rpc::Error {
|
||||||
|
code: rpc::ErrorCode::ServerError(-1),
|
||||||
|
message: "Not implemented yet".into(),
|
||||||
|
data: None,
|
||||||
|
},
|
||||||
|
_ => rpc::Error::internal_error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Substrate is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Substrate is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Substrate block-author/full-node API.
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use primitives::block::Extrinsic;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
use self::error::Result;
|
||||||
|
|
||||||
|
build_rpc_trait! {
|
||||||
|
/// Substrate authoring RPC API
|
||||||
|
pub trait AuthorApi {
|
||||||
|
/// Submit extrinsic for inclusion in block.
|
||||||
|
#[rpc(name = "author_submitExtrinsic")]
|
||||||
|
fn submit_extrinsic(&self, Extrinsic) -> Result<()>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Variant of the AuthorApi that doesn't need to be Sync + Send + 'static.
|
||||||
|
pub trait AsyncAuthorApi: Send + 'static {
|
||||||
|
/// Submit extrinsic for inclusion in block.
|
||||||
|
fn submit_extrinsic(&mut self, Extrinsic) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncAuthorApi> AuthorApi for Arc<Mutex<T>> {
|
||||||
|
fn submit_extrinsic(&self, xt: Extrinsic) -> Result<()> {
|
||||||
|
self.as_ref().lock().submit_extrinsic(xt)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Substrate is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Substrate is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use primitives::block;
|
||||||
|
use substrate_executor as executor;
|
||||||
|
use super::*;
|
||||||
|
use super::error::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct DummyTxPool {
|
||||||
|
submitted: Vec<block::Extrinsic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncAuthorApi for DummyTxPool {
|
||||||
|
/// Submit extrinsic for inclusion in block.
|
||||||
|
fn submit_extrinsic(&mut self, xt: Extrinsic) -> Result<()> {
|
||||||
|
if self.submitted.len() < 1 {
|
||||||
|
self.submitted.push(xt);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ErrorKind::PoolError.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn submit_transaction_should_not_cause_error() {
|
||||||
|
let mut p = Arc::new(Mutex::new(DummyTxPool::default()));
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
AuthorApi::submit_extrinsic(&p, block::Extrinsic(vec![])),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
AuthorApi::submit_extrinsic(&p, block::Extrinsic(vec![])).is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
extern crate parking_lot;
|
||||||
extern crate jsonrpc_core as rpc;
|
extern crate jsonrpc_core as rpc;
|
||||||
extern crate substrate_client as client;
|
extern crate substrate_client as client;
|
||||||
extern crate substrate_primitives as primitives;
|
extern crate substrate_primitives as primitives;
|
||||||
@@ -38,3 +39,4 @@ extern crate substrate_runtime_support as runtime_support;
|
|||||||
|
|
||||||
pub mod chain;
|
pub mod chain;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
pub mod author;
|
||||||
|
|||||||
Reference in New Issue
Block a user