mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 03:31:05 +00:00
Introduce srml/im-online (#3079)
* Fix grammar and typo * Extend network service * Extend offchain API * Support creating unsigned UncheckedExtrinsic * Introduce srml/im-online * Bump impl and spec version * Fix web-wasm test * Apply suggestions from code review Remove parity-multiaddr dependency Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com> * Replace transmute with from_raw_parts * Replace PeerId.to_string() with .to_base58() Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com> * Update Cargo.lock * Bump impl and spec version (again) It was updated in master in the meantime. * Apply suggestions from code review Co-Authored-By: Sergei Pepyakin <sergei@parity.io> * Address comments * Add public function is_online_in_current_session() * Bump spec_version * Fix doc tests * Improve comments * Remove superfluous line * Name parameters consistently * Implement comments * Switch From to TryFrom * Use Vec instead of HashSet * Fix tests * Revert me: local testing * Fix check if already sent during session We gossip each session, hence we need to check if already sent in this session (not era). * Fix typos * Consistent terminology * Revert "Revert me: local testing" This reverts commit 73fbc29ff3e5ed71d99436318260b4f007e837f4. * Introduce IsMember trait * Implement misc comments * Remove unused function * Fix test * Fix external_addresses being written * Fix test * Add necessary trait bound * Do not increment version * Update lib.rs
This commit is contained in:
committed by
Gavin Wood
parent
a757dfb222
commit
c70b81444a
Generated
+18
@@ -2360,6 +2360,7 @@ dependencies = [
|
|||||||
"srml-executive 2.0.0",
|
"srml-executive 2.0.0",
|
||||||
"srml-finality-tracker 2.0.0",
|
"srml-finality-tracker 2.0.0",
|
||||||
"srml-grandpa 2.0.0",
|
"srml-grandpa 2.0.0",
|
||||||
|
"srml-im-online 0.1.0",
|
||||||
"srml-indices 2.0.0",
|
"srml-indices 2.0.0",
|
||||||
"srml-session 2.0.0",
|
"srml-session 2.0.0",
|
||||||
"srml-staking 2.0.0",
|
"srml-staking 2.0.0",
|
||||||
@@ -3844,6 +3845,21 @@ dependencies = [
|
|||||||
"substrate-primitives 2.0.0",
|
"substrate-primitives 2.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "srml-im-online"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"parity-codec 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"sr-io 2.0.0",
|
||||||
|
"sr-primitives 2.0.0",
|
||||||
|
"sr-std 2.0.0",
|
||||||
|
"srml-session 2.0.0",
|
||||||
|
"srml-support 2.0.0",
|
||||||
|
"srml-system 2.0.0",
|
||||||
|
"substrate-primitives 2.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "srml-indices"
|
name = "srml-indices"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -4564,6 +4580,7 @@ dependencies = [
|
|||||||
"sr-primitives 2.0.0",
|
"sr-primitives 2.0.0",
|
||||||
"substrate-client 2.0.0",
|
"substrate-client 2.0.0",
|
||||||
"substrate-client-db 2.0.0",
|
"substrate-client-db 2.0.0",
|
||||||
|
"substrate-network 2.0.0",
|
||||||
"substrate-offchain-primitives 2.0.0",
|
"substrate-offchain-primitives 2.0.0",
|
||||||
"substrate-primitives 2.0.0",
|
"substrate-primitives 2.0.0",
|
||||||
"substrate-test-runtime-client 2.0.0",
|
"substrate-test-runtime-client 2.0.0",
|
||||||
@@ -4710,6 +4727,7 @@ dependencies = [
|
|||||||
"node-primitives 2.0.0",
|
"node-primitives 2.0.0",
|
||||||
"node-runtime 2.0.0",
|
"node-runtime 2.0.0",
|
||||||
"parity-codec 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-codec 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"parity-multiaddr 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ members = [
|
|||||||
"srml/system",
|
"srml/system",
|
||||||
"srml/timestamp",
|
"srml/timestamp",
|
||||||
"srml/treasury",
|
"srml/treasury",
|
||||||
|
"srml/im-online",
|
||||||
"node/cli",
|
"node/cli",
|
||||||
"node/executor",
|
"node/executor",
|
||||||
"node/primitives",
|
"node/primitives",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ use wasmi::{
|
|||||||
};
|
};
|
||||||
use state_machine::{Externalities, ChildStorageKey};
|
use state_machine::{Externalities, ChildStorageKey};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
use parity_codec::Encode;
|
||||||
use primitives::{blake2_128, blake2_256, twox_64, twox_128, twox_256, ed25519, sr25519, Pair};
|
use primitives::{blake2_128, blake2_256, twox_64, twox_128, twox_256, ed25519, sr25519, Pair};
|
||||||
use primitives::offchain;
|
use primitives::offchain;
|
||||||
use primitives::hexdisplay::HexDisplay;
|
use primitives::hexdisplay::HexDisplay;
|
||||||
@@ -767,6 +768,42 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
|
|||||||
|
|
||||||
Ok(offset)
|
Ok(offset)
|
||||||
},
|
},
|
||||||
|
ext_network_state(written_out: *mut u32) -> *mut u8 => {
|
||||||
|
let res = this.ext.offchain()
|
||||||
|
.map(|api| api.network_state())
|
||||||
|
.ok_or_else(|| "Calling unavailable API ext_network_state: wasm")?;
|
||||||
|
|
||||||
|
let encoded = res.encode();
|
||||||
|
let len = encoded.len() as u32;
|
||||||
|
let offset = this.heap.allocate(len)? as u32;
|
||||||
|
this.memory.set(offset, &encoded)
|
||||||
|
.map_err(|_| "Invalid attempt to set memory in ext_network_state")?;
|
||||||
|
|
||||||
|
this.memory.write_primitive(written_out, len)
|
||||||
|
.map_err(|_| "Invalid attempt to write written_out in ext_network_state")?;
|
||||||
|
|
||||||
|
Ok(offset)
|
||||||
|
},
|
||||||
|
ext_authority_pubkey(
|
||||||
|
kind: u32,
|
||||||
|
written_out: *mut u32
|
||||||
|
) -> *mut u8 => {
|
||||||
|
let kind = offchain::CryptoKind::try_from(kind)
|
||||||
|
.map_err(|_| "crypto kind OOB while ext_authority_pubkey: wasm")?;
|
||||||
|
|
||||||
|
let res = this.ext.offchain()
|
||||||
|
.map(|api| api.authority_pubkey(kind))
|
||||||
|
.ok_or_else(|| "Calling unavailable API ext_authority_pubkey: wasm")?;
|
||||||
|
|
||||||
|
let encoded = res.encode();
|
||||||
|
let len = encoded.len() as u32;
|
||||||
|
let offset = this.heap.allocate(len)? as u32;
|
||||||
|
this.memory.set(offset, &encoded)
|
||||||
|
.map_err(|_| "Invalid attempt to set memory in ext_authority_pubkey")?;
|
||||||
|
this.memory.write_primitive(written_out, len)
|
||||||
|
.map_err(|_| "Invalid attempt to write written_out in ext_authority_pubkey")?;
|
||||||
|
Ok(offset)
|
||||||
|
},
|
||||||
ext_decrypt(
|
ext_decrypt(
|
||||||
key: u32,
|
key: u32,
|
||||||
kind: u32,
|
kind: u32,
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ pub mod test;
|
|||||||
pub use chain::{Client as ClientHandle, FinalityProofProvider};
|
pub use chain::{Client as ClientHandle, FinalityProofProvider};
|
||||||
pub use service::{
|
pub use service::{
|
||||||
NetworkService, NetworkWorker, TransactionPool, ExHashT, ReportHandle,
|
NetworkService, NetworkWorker, TransactionPool, ExHashT, ReportHandle,
|
||||||
|
NetworkStateInfo,
|
||||||
};
|
};
|
||||||
pub use protocol::{PeerInfo, Context, consensus_gossip, message, specialization};
|
pub use protocol::{PeerInfo, Context, consensus_gossip, message, specialization};
|
||||||
pub use protocol::sync::SyncState;
|
pub use protocol::sync::SyncState;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ use futures::{prelude::*, sync::mpsc};
|
|||||||
use log::{warn, error, info};
|
use log::{warn, error, info};
|
||||||
use libp2p::core::{swarm::NetworkBehaviour, transport::boxed::Boxed, muxing::StreamMuxerBox};
|
use libp2p::core::{swarm::NetworkBehaviour, transport::boxed::Boxed, muxing::StreamMuxerBox};
|
||||||
use libp2p::{PeerId, Multiaddr, multihash::Multihash};
|
use libp2p::{PeerId, Multiaddr, multihash::Multihash};
|
||||||
|
use parking_lot::Mutex;
|
||||||
use peerset::PeersetHandle;
|
use peerset::PeersetHandle;
|
||||||
use runtime_primitives::{traits::{Block as BlockT, NumberFor}, ConsensusEngineId};
|
use runtime_primitives::{traits::{Block as BlockT, NumberFor}, ConsensusEngineId};
|
||||||
|
|
||||||
@@ -86,6 +87,8 @@ impl ReportHandle {
|
|||||||
pub struct NetworkService<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> {
|
pub struct NetworkService<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> {
|
||||||
/// Number of peers we're connected to.
|
/// Number of peers we're connected to.
|
||||||
num_connected: Arc<AtomicUsize>,
|
num_connected: Arc<AtomicUsize>,
|
||||||
|
/// The local external addresses.
|
||||||
|
external_addresses: Arc<Mutex<Vec<Multiaddr>>>,
|
||||||
/// Are we actively catching up with the chain?
|
/// Are we actively catching up with the chain?
|
||||||
is_major_syncing: Arc<AtomicBool>,
|
is_major_syncing: Arc<AtomicBool>,
|
||||||
/// Local copy of the `PeerId` of the local node.
|
/// Local copy of the `PeerId` of the local node.
|
||||||
@@ -215,8 +218,11 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> NetworkWorker
|
|||||||
Swarm::<B, S, H>::add_external_address(&mut swarm, addr.clone());
|
Swarm::<B, S, H>::add_external_address(&mut swarm, addr.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let external_addresses = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
let service = Arc::new(NetworkService {
|
let service = Arc::new(NetworkService {
|
||||||
bandwidth,
|
bandwidth,
|
||||||
|
external_addresses: external_addresses.clone(),
|
||||||
num_connected: num_connected.clone(),
|
num_connected: num_connected.clone(),
|
||||||
is_major_syncing: is_major_syncing.clone(),
|
is_major_syncing: is_major_syncing.clone(),
|
||||||
peerset: peerset_handle,
|
peerset: peerset_handle,
|
||||||
@@ -226,6 +232,7 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> NetworkWorker
|
|||||||
});
|
});
|
||||||
|
|
||||||
Ok(NetworkWorker {
|
Ok(NetworkWorker {
|
||||||
|
external_addresses,
|
||||||
num_connected,
|
num_connected,
|
||||||
is_major_syncing,
|
is_major_syncing,
|
||||||
network_service: swarm,
|
network_service: swarm,
|
||||||
@@ -295,7 +302,7 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> NetworkWorker
|
|||||||
/// Get network state.
|
/// Get network state.
|
||||||
///
|
///
|
||||||
/// **Note**: Use this only for debugging. This API is unstable. There are warnings literaly
|
/// **Note**: Use this only for debugging. This API is unstable. There are warnings literaly
|
||||||
/// everywhere about this. Please don't use this function to retreive actual information.
|
/// everywhere about this. Please don't use this function to retrieve actual information.
|
||||||
pub fn network_state(&mut self) -> NetworkState {
|
pub fn network_state(&mut self) -> NetworkState {
|
||||||
let swarm = &mut self.network_service;
|
let swarm = &mut self.network_service;
|
||||||
let open = swarm.user_protocol().open_peers().cloned().collect::<Vec<_>>();
|
let open = swarm.user_protocol().open_peers().cloned().collect::<Vec<_>>();
|
||||||
@@ -487,6 +494,11 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> NetworkServic
|
|||||||
pub fn num_connected(&self) -> usize {
|
pub fn num_connected(&self) -> usize {
|
||||||
self.num_connected.load(Ordering::Relaxed)
|
self.num_connected.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the local external addresses.
|
||||||
|
pub fn external_addresses(&self) -> Vec<Multiaddr> {
|
||||||
|
self.external_addresses.lock().clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT>
|
impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT>
|
||||||
@@ -500,6 +512,32 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for providing information about the local network state
|
||||||
|
pub trait NetworkStateInfo {
|
||||||
|
/// Returns the local external addresses.
|
||||||
|
fn external_addresses(&self) -> Vec<Multiaddr>;
|
||||||
|
|
||||||
|
/// Returns the local Peer ID.
|
||||||
|
fn peer_id(&self) -> PeerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B, S, H> NetworkStateInfo for NetworkService<B, S, H>
|
||||||
|
where
|
||||||
|
B: runtime_primitives::traits::Block,
|
||||||
|
S: NetworkSpecialization<B>,
|
||||||
|
H: ExHashT,
|
||||||
|
{
|
||||||
|
/// Returns the local external addresses.
|
||||||
|
fn external_addresses(&self) -> Vec<Multiaddr> {
|
||||||
|
self.external_addresses.lock().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the local Peer ID.
|
||||||
|
fn peer_id(&self) -> PeerId {
|
||||||
|
self.local_peer_id.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Messages sent from the `NetworkService` to the `NetworkWorker`.
|
/// Messages sent from the `NetworkService` to the `NetworkWorker`.
|
||||||
///
|
///
|
||||||
/// Each entry corresponds to a method of `NetworkService`.
|
/// Each entry corresponds to a method of `NetworkService`.
|
||||||
@@ -520,6 +558,8 @@ enum ServerToWorkerMsg<B: BlockT, S: NetworkSpecialization<B>> {
|
|||||||
/// You are encouraged to poll this in a separate background thread or task.
|
/// You are encouraged to poll this in a separate background thread or task.
|
||||||
#[must_use = "The NetworkWorker must be polled in order for the network to work"]
|
#[must_use = "The NetworkWorker must be polled in order for the network to work"]
|
||||||
pub struct NetworkWorker<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> {
|
pub struct NetworkWorker<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> {
|
||||||
|
/// Updated by the `NetworkWorker` and loaded by the `NetworkService`.
|
||||||
|
external_addresses: Arc<Mutex<Vec<Multiaddr>>>,
|
||||||
/// Updated by the `NetworkWorker` and loaded by the `NetworkService`.
|
/// Updated by the `NetworkWorker` and loaded by the `NetworkService`.
|
||||||
num_connected: Arc<AtomicUsize>,
|
num_connected: Arc<AtomicUsize>,
|
||||||
/// Updated by the `NetworkWorker` and loaded by the `NetworkService`.
|
/// Updated by the `NetworkWorker` and loaded by the `NetworkService`.
|
||||||
@@ -621,6 +661,10 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> Future for Ne
|
|||||||
|
|
||||||
// Update the variables shared with the `NetworkService`.
|
// Update the variables shared with the `NetworkService`.
|
||||||
self.num_connected.store(self.network_service.user_protocol_mut().num_connected_peers(), Ordering::Relaxed);
|
self.num_connected.store(self.network_service.user_protocol_mut().num_connected_peers(), Ordering::Relaxed);
|
||||||
|
{
|
||||||
|
let external_addresses = Swarm::<B, S, H>::external_addresses(&self.network_service).cloned().collect();
|
||||||
|
*self.external_addresses.lock() = external_addresses;
|
||||||
|
}
|
||||||
self.is_major_syncing.store(match self.network_service.user_protocol_mut().sync_state() {
|
self.is_major_syncing.store(match self.network_service.user_protocol_mut().sync_state() {
|
||||||
SyncState::Idle => false,
|
SyncState::Idle => false,
|
||||||
SyncState::Downloading => true,
|
SyncState::Downloading => true,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ parking_lot = "0.8.0"
|
|||||||
primitives = { package = "substrate-primitives", path = "../../core/primitives" }
|
primitives = { package = "substrate-primitives", path = "../../core/primitives" }
|
||||||
runtime_primitives = { package = "sr-primitives", path = "../../core/sr-primitives" }
|
runtime_primitives = { package = "sr-primitives", path = "../../core/sr-primitives" }
|
||||||
transaction_pool = { package = "substrate-transaction-pool", path = "../../core/transaction-pool" }
|
transaction_pool = { package = "substrate-transaction-pool", path = "../../core/transaction-pool" }
|
||||||
|
network = { package = "substrate-network", path = "../../core/network" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.6"
|
env_logger = "0.6"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::{str::FromStr, sync::Arc, convert::TryFrom};
|
||||||
use client::backend::OffchainStorage;
|
use client::backend::OffchainStorage;
|
||||||
use crate::AuthorityKeyProvider;
|
use crate::AuthorityKeyProvider;
|
||||||
use futures::{Stream, Future, sync::mpsc};
|
use futures::{Stream, Future, sync::mpsc};
|
||||||
@@ -25,6 +25,7 @@ use primitives::offchain::{
|
|||||||
Externalities as OffchainExt,
|
Externalities as OffchainExt,
|
||||||
CryptoKind, CryptoKeyId,
|
CryptoKind, CryptoKeyId,
|
||||||
StorageKind,
|
StorageKind,
|
||||||
|
OpaqueNetworkState, OpaquePeerId, OpaqueMultiaddr,
|
||||||
};
|
};
|
||||||
use primitives::crypto::{Pair, Protected};
|
use primitives::crypto::{Pair, Protected};
|
||||||
use primitives::{ed25519, sr25519};
|
use primitives::{ed25519, sr25519};
|
||||||
@@ -33,6 +34,8 @@ use runtime_primitives::{
|
|||||||
traits::{self, Extrinsic},
|
traits::{self, Extrinsic},
|
||||||
};
|
};
|
||||||
use transaction_pool::txpool::{Pool, ChainApi};
|
use transaction_pool::txpool::{Pool, ChainApi};
|
||||||
|
use network::NetworkStateInfo;
|
||||||
|
use network::{PeerId, Multiaddr};
|
||||||
|
|
||||||
/// A message between the offchain extension and the processing thread.
|
/// A message between the offchain extension and the processing thread.
|
||||||
enum ExtMessage {
|
enum ExtMessage {
|
||||||
@@ -59,6 +62,7 @@ pub(crate) struct Api<Storage, KeyProvider> {
|
|||||||
db: Storage,
|
db: Storage,
|
||||||
keys_password: Protected<String>,
|
keys_password: Protected<String>,
|
||||||
key_provider: KeyProvider,
|
key_provider: KeyProvider,
|
||||||
|
network_state: Arc<dyn NetworkStateInfo + Send + Sync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unavailable_yet<R: Default>(name: &str) -> R {
|
fn unavailable_yet<R: Default>(name: &str) -> R {
|
||||||
@@ -158,6 +162,26 @@ impl<Storage, KeyProvider> OffchainExt for Api<Storage, KeyProvider> where
|
|||||||
Ok(CryptoKeyId(id))
|
Ok(CryptoKeyId(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn authority_pubkey(&self, kind: CryptoKind) -> Result<Vec<u8>, ()> {
|
||||||
|
let key = self.read_key(None, kind)?;
|
||||||
|
let public = match key {
|
||||||
|
Key::Sr25519(pair) => pair.public().encode(),
|
||||||
|
Key::Ed25519(pair) => pair.public().encode(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(public)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn network_state(&self) -> Result<OpaqueNetworkState, ()> {
|
||||||
|
let external_addresses = self.network_state.external_addresses();
|
||||||
|
|
||||||
|
let state = NetworkState::new(
|
||||||
|
self.network_state.peer_id(),
|
||||||
|
external_addresses,
|
||||||
|
);
|
||||||
|
Ok(OpaqueNetworkState::from(state))
|
||||||
|
}
|
||||||
|
|
||||||
fn encrypt(&mut self, _key: Option<CryptoKeyId>, _kind: CryptoKind, _data: &[u8]) -> Result<Vec<u8>, ()> {
|
fn encrypt(&mut self, _key: Option<CryptoKeyId>, _kind: CryptoKind, _data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||||
unavailable_yet::<()>("encrypt");
|
unavailable_yet::<()>("encrypt");
|
||||||
Err(())
|
Err(())
|
||||||
@@ -285,6 +309,71 @@ impl<Storage, KeyProvider> OffchainExt for Api<Storage, KeyProvider> where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Information about the local node's network state.
|
||||||
|
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||||
|
pub struct NetworkState {
|
||||||
|
peer_id: PeerId,
|
||||||
|
external_addresses: Vec<Multiaddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkState {
|
||||||
|
fn new(peer_id: PeerId, external_addresses: Vec<Multiaddr>) -> Self {
|
||||||
|
NetworkState {
|
||||||
|
peer_id,
|
||||||
|
external_addresses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NetworkState> for OpaqueNetworkState {
|
||||||
|
fn from(state: NetworkState) -> OpaqueNetworkState {
|
||||||
|
let enc = Encode::encode(&state.peer_id.into_bytes());
|
||||||
|
let peer_id = OpaquePeerId::new(enc);
|
||||||
|
|
||||||
|
let external_addresses: Vec<OpaqueMultiaddr> = state
|
||||||
|
.external_addresses
|
||||||
|
.iter()
|
||||||
|
.map(|multiaddr| {
|
||||||
|
let e = Encode::encode(&multiaddr.to_string());
|
||||||
|
OpaqueMultiaddr::new(e)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
OpaqueNetworkState {
|
||||||
|
peer_id,
|
||||||
|
external_addresses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<OpaqueNetworkState> for NetworkState {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(state: OpaqueNetworkState) -> Result<Self, Self::Error> {
|
||||||
|
let inner_vec = state.peer_id.0;
|
||||||
|
|
||||||
|
let bytes: Vec<u8> = Decode::decode(&mut &inner_vec[..]).ok_or(())?;
|
||||||
|
let peer_id = PeerId::from_bytes(bytes).map_err(|_| ())?;
|
||||||
|
|
||||||
|
let external_addresses: Result<Vec<Multiaddr>, Self::Error> = state.external_addresses
|
||||||
|
.iter()
|
||||||
|
.map(|enc_multiaddr| -> Result<Multiaddr, Self::Error> {
|
||||||
|
let inner_vec = &enc_multiaddr.0;
|
||||||
|
let bytes = <Vec<u8>>::decode(&mut &inner_vec[..]).ok_or(())?;
|
||||||
|
let multiaddr_str = String::from_utf8(bytes).map_err(|_| ())?;
|
||||||
|
let multiaddr = Multiaddr::from_str(&multiaddr_str).map_err(|_| ())?;
|
||||||
|
Ok(multiaddr)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let external_addresses = external_addresses?;
|
||||||
|
|
||||||
|
Ok(NetworkState {
|
||||||
|
peer_id,
|
||||||
|
external_addresses,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Offchain extensions implementation API
|
/// Offchain extensions implementation API
|
||||||
///
|
///
|
||||||
/// This is the asynchronous processing part of the API.
|
/// This is the asynchronous processing part of the API.
|
||||||
@@ -302,6 +391,7 @@ impl<A: ChainApi> AsyncApi<A> {
|
|||||||
keys_password: Protected<String>,
|
keys_password: Protected<String>,
|
||||||
key_provider: P,
|
key_provider: P,
|
||||||
at: BlockId<A::Block>,
|
at: BlockId<A::Block>,
|
||||||
|
network_state: Arc<dyn NetworkStateInfo + Send + Sync>,
|
||||||
) -> (Api<S, P>, AsyncApi<A>) {
|
) -> (Api<S, P>, AsyncApi<A>) {
|
||||||
let (sender, rx) = mpsc::unbounded();
|
let (sender, rx) = mpsc::unbounded();
|
||||||
|
|
||||||
@@ -310,6 +400,7 @@ impl<A: ChainApi> AsyncApi<A> {
|
|||||||
db,
|
db,
|
||||||
keys_password,
|
keys_password,
|
||||||
key_provider,
|
key_provider,
|
||||||
|
network_state,
|
||||||
};
|
};
|
||||||
|
|
||||||
let async_api = AsyncApi {
|
let async_api = AsyncApi {
|
||||||
@@ -355,8 +446,22 @@ impl<A: ChainApi> AsyncApi<A> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::{collections::HashSet, convert::TryFrom};
|
||||||
use client_db::offchain::LocalStorage;
|
use client_db::offchain::LocalStorage;
|
||||||
use crate::tests::TestProvider;
|
use crate::tests::TestProvider;
|
||||||
|
use network::PeerId;
|
||||||
|
|
||||||
|
struct MockNetworkStateInfo();
|
||||||
|
|
||||||
|
impl NetworkStateInfo for MockNetworkStateInfo {
|
||||||
|
fn external_addresses(&self) -> Vec<Multiaddr> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peer_id(&self) -> PeerId {
|
||||||
|
PeerId::random()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn offchain_api() -> (Api<LocalStorage, TestProvider>, AsyncApi<impl ChainApi>) {
|
fn offchain_api() -> (Api<LocalStorage, TestProvider>, AsyncApi<impl ChainApi>) {
|
||||||
let _ = env_logger::try_init();
|
let _ = env_logger::try_init();
|
||||||
@@ -366,7 +471,8 @@ mod tests {
|
|||||||
Pool::new(Default::default(), transaction_pool::ChainApi::new(client.clone()))
|
Pool::new(Default::default(), transaction_pool::ChainApi::new(client.clone()))
|
||||||
);
|
);
|
||||||
|
|
||||||
AsyncApi::new(pool, db, "pass".to_owned().into(), TestProvider::default(), BlockId::Number(0))
|
let mock = Arc::new(MockNetworkStateInfo());
|
||||||
|
AsyncApi::new(pool, db, "pass".to_owned().into(), TestProvider::default(), BlockId::Number(0), mock)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -455,4 +561,23 @@ mod tests {
|
|||||||
"Invalid kind should trigger a missing key error."
|
"Invalid kind should trigger a missing key error."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_convert_network_states() {
|
||||||
|
// given
|
||||||
|
let state = NetworkState::new(
|
||||||
|
PeerId::random(),
|
||||||
|
vec![
|
||||||
|
Multiaddr::try_from("/ip4/127.0.0.1/tcp/1234".to_string()).unwrap(),
|
||||||
|
Multiaddr::try_from("/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21").unwrap(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// when
|
||||||
|
let opaque_state = OpaqueNetworkState::from(state.clone());
|
||||||
|
let converted_back_state = NetworkState::try_from(opaque_state).unwrap();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(state, converted_back_state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ use runtime_primitives::{
|
|||||||
};
|
};
|
||||||
use futures::future::Future;
|
use futures::future::Future;
|
||||||
use transaction_pool::txpool::{Pool, ChainApi};
|
use transaction_pool::txpool::{Pool, ChainApi};
|
||||||
|
use network::NetworkStateInfo;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
|
|
||||||
@@ -130,6 +131,7 @@ impl<Client, Storage, KeyProvider, Block> OffchainWorkers<
|
|||||||
&self,
|
&self,
|
||||||
number: &<Block::Header as traits::Header>::Number,
|
number: &<Block::Header as traits::Header>::Number,
|
||||||
pool: &Arc<Pool<A>>,
|
pool: &Arc<Pool<A>>,
|
||||||
|
network_state: Arc<dyn NetworkStateInfo + Send + Sync>,
|
||||||
) -> impl Future<Item = (), Error = ()> where
|
) -> impl Future<Item = (), Error = ()> where
|
||||||
A: ChainApi<Block=Block> + 'static,
|
A: ChainApi<Block=Block> + 'static,
|
||||||
{
|
{
|
||||||
@@ -145,6 +147,7 @@ impl<Client, Storage, KeyProvider, Block> OffchainWorkers<
|
|||||||
self.keys_password.clone(),
|
self.keys_password.clone(),
|
||||||
self.authority_key.clone(),
|
self.authority_key.clone(),
|
||||||
at.clone(),
|
at.clone(),
|
||||||
|
network_state.clone(),
|
||||||
);
|
);
|
||||||
debug!("Running offchain workers at {:?}", at);
|
debug!("Running offchain workers at {:?}", at);
|
||||||
let api = Box::new(api);
|
let api = Box::new(api);
|
||||||
@@ -161,6 +164,20 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use primitives::{ed25519, sr25519, crypto::{TypedKey, Pair}};
|
use primitives::{ed25519, sr25519, crypto::{TypedKey, Pair}};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use network::{Multiaddr, PeerId};
|
||||||
|
|
||||||
|
struct MockNetworkStateInfo();
|
||||||
|
|
||||||
|
impl NetworkStateInfo for MockNetworkStateInfo {
|
||||||
|
fn external_addresses(&self) -> Vec<Multiaddr> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peer_id(&self) -> PeerId {
|
||||||
|
PeerId::random()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub(crate) struct TestProvider {
|
pub(crate) struct TestProvider {
|
||||||
@@ -186,10 +203,11 @@ mod tests {
|
|||||||
let client = Arc::new(test_client::new());
|
let client = Arc::new(test_client::new());
|
||||||
let pool = Arc::new(Pool::new(Default::default(), ::transaction_pool::ChainApi::new(client.clone())));
|
let pool = Arc::new(Pool::new(Default::default(), ::transaction_pool::ChainApi::new(client.clone())));
|
||||||
let db = client_db::offchain::LocalStorage::new_test();
|
let db = client_db::offchain::LocalStorage::new_test();
|
||||||
|
let mock = Arc::new(MockNetworkStateInfo());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let offchain = OffchainWorkers::new(client, db, TestProvider::default(), "".to_owned().into());
|
let offchain = OffchainWorkers::new(client, db, TestProvider::default(), "".to_owned().into());
|
||||||
runtime.executor().spawn(offchain.on_block_imported(&0u64, &pool));
|
runtime.executor().spawn(offchain.on_block_imported(&0u64, &pool, mock.clone()));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
runtime.shutdown_on_idle().wait().unwrap();
|
runtime.shutdown_on_idle().wait().unwrap();
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ use primitives::offchain::{
|
|||||||
CryptoKind,
|
CryptoKind,
|
||||||
CryptoKeyId,
|
CryptoKeyId,
|
||||||
StorageKind,
|
StorageKind,
|
||||||
|
OpaqueNetworkState,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Pending request.
|
/// Pending request.
|
||||||
@@ -139,6 +140,14 @@ impl offchain::Externalities for TestOffchainExt {
|
|||||||
unimplemented!("not needed in tests so far")
|
unimplemented!("not needed in tests so far")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn network_state(&self) -> Result<OpaqueNetworkState, ()> {
|
||||||
|
unimplemented!("not needed in tests so far")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authority_pubkey(&self, _kind: CryptoKind) -> Result<Vec<u8>, ()> {
|
||||||
|
unimplemented!("not needed in tests so far")
|
||||||
|
}
|
||||||
|
|
||||||
fn new_crypto_key(&mut self, _crypto: CryptoKind) -> Result<CryptoKeyId, ()> {
|
fn new_crypto_key(&mut self, _crypto: CryptoKind) -> Result<CryptoKeyId, ()> {
|
||||||
unimplemented!("not needed in tests so far")
|
unimplemented!("not needed in tests so far")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,6 +185,41 @@ impl TryFrom<u32> for HttpRequestStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A blob to hold information about the local node's network state
|
||||||
|
/// without committing to its format.
|
||||||
|
#[derive(Clone, Eq, PartialEq, Encode, Decode)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Debug))]
|
||||||
|
pub struct OpaqueNetworkState {
|
||||||
|
/// PeerId of the local node.
|
||||||
|
pub peer_id: OpaquePeerId,
|
||||||
|
/// List of addresses the node knows it can be reached as.
|
||||||
|
pub external_addresses: Vec<OpaqueMultiaddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple blob to hold a `PeerId` without committing to its format.
|
||||||
|
#[derive(Clone, Eq, PartialEq, Encode, Decode)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Debug))]
|
||||||
|
pub struct OpaquePeerId(pub Vec<u8>);
|
||||||
|
|
||||||
|
impl OpaquePeerId {
|
||||||
|
/// Create new `OpaquePeerId`
|
||||||
|
pub fn new(vec: Vec<u8>) -> Self {
|
||||||
|
OpaquePeerId(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple blob to hold a `Multiaddr` without committing to its format.
|
||||||
|
#[derive(Clone, Eq, PartialEq, Encode, Decode)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Debug))]
|
||||||
|
pub struct OpaqueMultiaddr(pub Vec<u8>);
|
||||||
|
|
||||||
|
impl OpaqueMultiaddr {
|
||||||
|
/// Create new `OpaqueMultiaddr`
|
||||||
|
pub fn new(vec: Vec<u8>) -> Self {
|
||||||
|
OpaqueMultiaddr(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Opaque timestamp type
|
/// Opaque timestamp type
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default)]
|
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default)]
|
||||||
#[cfg_attr(feature = "std", derive(Debug))]
|
#[cfg_attr(feature = "std", derive(Debug))]
|
||||||
@@ -241,6 +276,12 @@ pub trait Externalities {
|
|||||||
/// The transaction will end up in the pool and be propagated to others.
|
/// The transaction will end up in the pool and be propagated to others.
|
||||||
fn submit_transaction(&mut self, extrinsic: Vec<u8>) -> Result<(), ()>;
|
fn submit_transaction(&mut self, extrinsic: Vec<u8>) -> Result<(), ()>;
|
||||||
|
|
||||||
|
/// Returns information about the local node's network state.
|
||||||
|
fn network_state(&self) -> Result<OpaqueNetworkState, ()>;
|
||||||
|
|
||||||
|
/// Returns the locally configured authority public key, if available.
|
||||||
|
fn authority_pubkey(&self, crypto: CryptoKind) -> Result<Vec<u8>, ()>;
|
||||||
|
|
||||||
/// Create new key(pair) for signing/encryption/decryption.
|
/// Create new key(pair) for signing/encryption/decryption.
|
||||||
///
|
///
|
||||||
/// Returns an error if given crypto kind is not supported.
|
/// Returns an error if given crypto kind is not supported.
|
||||||
@@ -319,7 +360,7 @@ pub trait Externalities {
|
|||||||
/// offchain worker tasks running on the same machine. It IS persisted between runs.
|
/// offchain worker tasks running on the same machine. It IS persisted between runs.
|
||||||
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>>;
|
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>>;
|
||||||
|
|
||||||
/// Initiaties a http request given HTTP verb and the URL.
|
/// Initiates a http request given HTTP verb and the URL.
|
||||||
///
|
///
|
||||||
/// Meta is a future-reserved field containing additional, parity-codec encoded parameters.
|
/// Meta is a future-reserved field containing additional, parity-codec encoded parameters.
|
||||||
/// Returns the id of newly started request.
|
/// Returns the id of newly started request.
|
||||||
@@ -398,6 +439,14 @@ impl<T: Externalities + ?Sized> Externalities for Box<T> {
|
|||||||
(&mut **self).encrypt(key, kind, data)
|
(&mut **self).encrypt(key, kind, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn network_state(&self) -> Result<OpaqueNetworkState, ()> {
|
||||||
|
(& **self).network_state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authority_pubkey(&self, key:CryptoKind) -> Result<Vec<u8>, ()> {
|
||||||
|
(&**self).authority_pubkey(key)
|
||||||
|
}
|
||||||
|
|
||||||
fn decrypt(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()> {
|
fn decrypt(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||||
(&mut **self).decrypt(key, kind, data)
|
(&mut **self).decrypt(key, kind, data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ transaction_pool = { package = "substrate-transaction-pool", path = "../../core/
|
|||||||
rpc = { package = "substrate-rpc-servers", path = "../../core/rpc-servers" }
|
rpc = { package = "substrate-rpc-servers", path = "../../core/rpc-servers" }
|
||||||
tel = { package = "substrate-telemetry", path = "../../core/telemetry" }
|
tel = { package = "substrate-telemetry", path = "../../core/telemetry" }
|
||||||
offchain = { package = "substrate-offchain", path = "../../core/offchain" }
|
offchain = { package = "substrate-offchain", path = "../../core/offchain" }
|
||||||
|
parity-multiaddr = { package = "parity-multiaddr", version = "0.5.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
substrate-test-runtime-client = { path = "../test-runtime/client" }
|
substrate-test-runtime-client = { path = "../test-runtime/client" }
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use client_db;
|
|||||||
use client::{self, Client, runtime_api};
|
use client::{self, Client, runtime_api};
|
||||||
use crate::{error, Service, AuthorityKeyProvider};
|
use crate::{error, Service, AuthorityKeyProvider};
|
||||||
use consensus_common::{import_queue::ImportQueue, SelectChain};
|
use consensus_common::{import_queue::ImportQueue, SelectChain};
|
||||||
use network::{self, OnDemand, FinalityProofProvider, config::BoxFinalityProofRequestBuilder};
|
use network::{self, OnDemand, FinalityProofProvider, NetworkStateInfo, config::BoxFinalityProofRequestBuilder};
|
||||||
use substrate_executor::{NativeExecutor, NativeExecutionDispatch};
|
use substrate_executor::{NativeExecutor, NativeExecutionDispatch};
|
||||||
use transaction_pool::txpool::{self, Options as TransactionPoolOptions, Pool as TransactionPool};
|
use transaction_pool::txpool::{self, Options as TransactionPoolOptions, Pool as TransactionPool};
|
||||||
use runtime_primitives::{
|
use runtime_primitives::{
|
||||||
@@ -235,6 +235,7 @@ pub trait OffchainWorker<C: Components> {
|
|||||||
ComponentBlock<C>
|
ComponentBlock<C>
|
||||||
>,
|
>,
|
||||||
pool: &Arc<TransactionPool<C::TransactionPoolApi>>,
|
pool: &Arc<TransactionPool<C::TransactionPoolApi>>,
|
||||||
|
network_state: &Arc<dyn NetworkStateInfo + Send + Sync>,
|
||||||
) -> error::Result<Box<dyn Future<Item = (), Error = ()> + Send>>;
|
) -> error::Result<Box<dyn Future<Item = (), Error = ()> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,8 +252,9 @@ impl<C: Components> OffchainWorker<Self> for C where
|
|||||||
ComponentBlock<C>
|
ComponentBlock<C>
|
||||||
>,
|
>,
|
||||||
pool: &Arc<TransactionPool<C::TransactionPoolApi>>,
|
pool: &Arc<TransactionPool<C::TransactionPoolApi>>,
|
||||||
|
network_state: &Arc<dyn NetworkStateInfo + Send + Sync>,
|
||||||
) -> error::Result<Box<dyn Future<Item = (), Error = ()> + Send>> {
|
) -> error::Result<Box<dyn Future<Item = (), Error = ()> + Send>> {
|
||||||
Ok(Box::new(offchain.on_block_imported(number, pool)))
|
Ok(Box::new(offchain.on_block_imported(number, pool, network_state.clone())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ use exit_future::Signal;
|
|||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use futures03::stream::{StreamExt as _, TryStreamExt as _};
|
use futures03::stream::{StreamExt as _, TryStreamExt as _};
|
||||||
use keystore::Store as Keystore;
|
use keystore::Store as Keystore;
|
||||||
use network::NetworkState;
|
use network::{NetworkState, NetworkStateInfo};
|
||||||
use log::{info, warn, debug, error};
|
use log::{info, warn, debug, error};
|
||||||
use parity_codec::{Encode, Decode};
|
use parity_codec::{Encode, Decode};
|
||||||
use primitives::{Pair, ed25519, crypto};
|
use primitives::{Pair, ed25519, crypto};
|
||||||
@@ -293,6 +293,7 @@ impl<Components: components::Components> Service<Components> {
|
|||||||
let wclient = Arc::downgrade(&client);
|
let wclient = Arc::downgrade(&client);
|
||||||
let offchain = offchain_workers.as_ref().map(Arc::downgrade);
|
let offchain = offchain_workers.as_ref().map(Arc::downgrade);
|
||||||
let to_spawn_tx_ = to_spawn_tx.clone();
|
let to_spawn_tx_ = to_spawn_tx.clone();
|
||||||
|
let network_state_info: Arc<dyn NetworkStateInfo + Send + Sync> = network.clone();
|
||||||
|
|
||||||
let events = client.import_notification_stream()
|
let events = client.import_notification_stream()
|
||||||
.map(|v| Ok::<_, ()>(v)).compat()
|
.map(|v| Ok::<_, ()>(v)).compat()
|
||||||
@@ -312,6 +313,7 @@ impl<Components: components::Components> Service<Components> {
|
|||||||
&number,
|
&number,
|
||||||
&offchain,
|
&offchain,
|
||||||
&txpool,
|
&txpool,
|
||||||
|
&network_state_info,
|
||||||
).map_err(|e| warn!("Offchain workers error processing new block: {:?}", e))?;
|
).map_err(|e| warn!("Offchain workers error processing new block: {:?}", e))?;
|
||||||
let _ = to_spawn_tx_.unbounded_send(future);
|
let _ = to_spawn_tx_.unbounded_send(future);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ use primitives::offchain::{
|
|||||||
HttpRequestId, HttpRequestStatus, HttpError,
|
HttpRequestId, HttpRequestStatus, HttpError,
|
||||||
CryptoKind, CryptoKeyId,
|
CryptoKind, CryptoKeyId,
|
||||||
StorageKind,
|
StorageKind,
|
||||||
|
OpaqueNetworkState,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Error verifying ECDSA signature
|
/// Error verifying ECDSA signature
|
||||||
@@ -239,6 +240,13 @@ export_api! {
|
|||||||
/// The transaction will end up in the pool.
|
/// The transaction will end up in the pool.
|
||||||
fn submit_transaction<T: codec::Encode>(data: &T) -> Result<(), ()>;
|
fn submit_transaction<T: codec::Encode>(data: &T) -> Result<(), ()>;
|
||||||
|
|
||||||
|
/// Returns information about the local node's network state.
|
||||||
|
fn network_state() -> Result<OpaqueNetworkState, ()>;
|
||||||
|
|
||||||
|
/// Returns the currently configured authority public key, if available.
|
||||||
|
// TODO [#3139] change into crypto_pubkey(&self, key: Option<CryptoKeyId>, kind: CryptoKind)
|
||||||
|
fn authority_pubkey(crypto: CryptoKind) -> Result<Vec<u8>, ()>;
|
||||||
|
|
||||||
/// Create new key(pair) for signing/encryption/decryption.
|
/// Create new key(pair) for signing/encryption/decryption.
|
||||||
///
|
///
|
||||||
/// Returns an error if given crypto kind is not supported.
|
/// Returns an error if given crypto kind is not supported.
|
||||||
@@ -313,7 +321,7 @@ export_api! {
|
|||||||
/// offchain worker tasks running on the same machine. It IS persisted between runs.
|
/// offchain worker tasks running on the same machine. It IS persisted between runs.
|
||||||
fn local_storage_get(kind: StorageKind, key: &[u8]) -> Option<Vec<u8>>;
|
fn local_storage_get(kind: StorageKind, key: &[u8]) -> Option<Vec<u8>>;
|
||||||
|
|
||||||
/// Initiaties a http request given HTTP verb and the URL.
|
/// Initiates a http request given HTTP verb and the URL.
|
||||||
///
|
///
|
||||||
/// Meta is a future-reserved field containing additional, parity-codec encoded parameters.
|
/// Meta is a future-reserved field containing additional, parity-codec encoded parameters.
|
||||||
/// Returns the id of newly started request.
|
/// Returns the id of newly started request.
|
||||||
|
|||||||
@@ -269,6 +269,18 @@ impl OffchainApi for () {
|
|||||||
}, "submit_transaction can be called only in the offchain worker context")
|
}, "submit_transaction can be called only in the offchain worker context")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn network_state() -> Result<OpaqueNetworkState, ()> {
|
||||||
|
with_offchain(|ext| {
|
||||||
|
ext.network_state()
|
||||||
|
}, "network_state can be called only in the offchain worker context")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authority_pubkey(crypto: offchain::CryptoKind) -> Result<Vec<u8>, ()> {
|
||||||
|
with_offchain(|ext| {
|
||||||
|
ext.authority_pubkey(crypto)
|
||||||
|
}, "authority_pubkey can be called only in the offchain worker context")
|
||||||
|
}
|
||||||
|
|
||||||
fn new_crypto_key(crypto: offchain::CryptoKind) -> Result<offchain::CryptoKeyId, ()> {
|
fn new_crypto_key(crypto: offchain::CryptoKind) -> Result<offchain::CryptoKeyId, ()> {
|
||||||
with_offchain(|ext| {
|
with_offchain(|ext| {
|
||||||
ext.new_crypto_key(crypto)
|
ext.new_crypto_key(crypto)
|
||||||
|
|||||||
@@ -385,6 +385,33 @@ pub mod ext {
|
|||||||
/// - nonzero otherwise.
|
/// - nonzero otherwise.
|
||||||
fn ext_submit_transaction(data: *const u8, len: u32) -> u32;
|
fn ext_submit_transaction(data: *const u8, len: u32) -> u32;
|
||||||
|
|
||||||
|
/// Returns information about the local node's network state.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The encoded `Result<offchain::OpaqueNetworkState, ()>`.
|
||||||
|
/// `written_out` contains the length of the message.
|
||||||
|
///
|
||||||
|
/// The ownership of the returned buffer is transferred to the runtime
|
||||||
|
/// code and the runtime is responsible for freeing it. This is always
|
||||||
|
/// a properly allocated pointer (which cannot be NULL), hence the
|
||||||
|
/// runtime code can always rely on it.
|
||||||
|
fn ext_network_state(written_out: *mut u32) -> *mut u8;
|
||||||
|
|
||||||
|
/// Returns the locally configured authority public key, if available.
|
||||||
|
/// The `crypto` argument is `offchain::CryptoKind` converted to `u32`.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The encoded `Result<PublicKey encoded to Vec<u8>, ()>`.
|
||||||
|
/// `written_out` contains the length of the message.
|
||||||
|
///
|
||||||
|
/// The ownership of the returned buffer is transferred to the runtime
|
||||||
|
/// code and the runtime is responsible for freeing it. This is always
|
||||||
|
/// a properly allocated pointer (which cannot be NULL), hence the
|
||||||
|
/// runtime code can always rely on it.
|
||||||
|
fn ext_authority_pubkey(crypto: u32, written_out: *mut u32) -> *mut u8;
|
||||||
|
|
||||||
/// Create new key(pair) for signing/encryption/decryption.
|
/// Create new key(pair) for signing/encryption/decryption.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
@@ -504,7 +531,7 @@ pub mod ext {
|
|||||||
/// - Otherwise, pointer to the value in memory. `value_len` contains the length of the value.
|
/// - Otherwise, pointer to the value in memory. `value_len` contains the length of the value.
|
||||||
fn ext_local_storage_get(kind: u32, key: *const u8, key_len: u32, value_len: *mut u32) -> *mut u8;
|
fn ext_local_storage_get(kind: u32, key: *const u8, key_len: u32, value_len: *mut u32) -> *mut u8;
|
||||||
|
|
||||||
/// Initiaties a http request.
|
/// Initiates a http request.
|
||||||
///
|
///
|
||||||
/// `meta` is parity-codec encoded additional parameters to the request (like redirection policy,
|
/// `meta` is parity-codec encoded additional parameters to the request (like redirection policy,
|
||||||
/// timeouts, certificates policy, etc). The format is not yet specified and the field is currently
|
/// timeouts, certificates policy, etc). The format is not yet specified and the field is currently
|
||||||
@@ -888,6 +915,39 @@ impl OffchainApi for () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn network_state() -> Result<offchain::OpaqueNetworkState, ()> {
|
||||||
|
let mut len = 0_u32;
|
||||||
|
let raw_result = unsafe {
|
||||||
|
let ptr = ext_network_state.get()(&mut len);
|
||||||
|
|
||||||
|
from_raw_parts(ptr, len)
|
||||||
|
};
|
||||||
|
|
||||||
|
match raw_result {
|
||||||
|
Some(raw_result) => codec::Decode::decode(&mut &*raw_result).unwrap_or(Err(())),
|
||||||
|
None => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authority_pubkey(kind: offchain::CryptoKind) -> Result<Vec<u8>, ()> {
|
||||||
|
let kind = kind as isize as u32;
|
||||||
|
|
||||||
|
let mut len = 0u32;
|
||||||
|
let raw_result = unsafe {
|
||||||
|
let ptr = ext_authority_pubkey.get()(
|
||||||
|
kind,
|
||||||
|
&mut len,
|
||||||
|
);
|
||||||
|
|
||||||
|
from_raw_parts(ptr, len)
|
||||||
|
};
|
||||||
|
|
||||||
|
match raw_result {
|
||||||
|
Some(raw_result) => codec::Decode::decode(&mut &*raw_result).unwrap_or(Err(())),
|
||||||
|
None => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn new_crypto_key(crypto: offchain::CryptoKind) -> Result<offchain::CryptoKeyId, ()> {
|
fn new_crypto_key(crypto: offchain::CryptoKind) -> Result<offchain::CryptoKeyId, ()> {
|
||||||
let crypto = crypto.into();
|
let crypto = crypto.into();
|
||||||
let ret = unsafe {
|
let ret = unsafe {
|
||||||
|
|||||||
@@ -114,9 +114,15 @@ impl<
|
|||||||
Signature: Codec,
|
Signature: Codec,
|
||||||
Call,
|
Call,
|
||||||
> Extrinsic for UncheckedExtrinsic<Address, Index, Call, Signature> {
|
> Extrinsic for UncheckedExtrinsic<Address, Index, Call, Signature> {
|
||||||
|
type Call = Call;
|
||||||
|
|
||||||
fn is_signed(&self) -> Option<bool> {
|
fn is_signed(&self) -> Option<bool> {
|
||||||
Some(self.signature.is_some())
|
Some(self.signature.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_unsigned(call: Self::Call) -> Option<Self> {
|
||||||
|
Some(UncheckedExtrinsic::new_unsigned(call))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Address: Codec, Index: HasCompact + Codec, Signature: Codec, Call: Decode> Decode
|
impl<Address: Codec, Index: HasCompact + Codec, Signature: Codec, Call: Decode> Decode
|
||||||
|
|||||||
@@ -59,9 +59,15 @@ impl<Address, Index, Call, Signature> UncheckedMortalCompactExtrinsic<Address, I
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Address: Encode, Index: Encode, Call: Encode, Signature: Encode> Extrinsic for UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature> {
|
impl<Address: Encode, Index: Encode, Call: Encode, Signature: Encode> Extrinsic for UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature> {
|
||||||
|
type Call = Call;
|
||||||
|
|
||||||
fn is_signed(&self) -> Option<bool> {
|
fn is_signed(&self) -> Option<bool> {
|
||||||
Some(self.signature.is_some())
|
Some(self.signature.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_unsigned(call: Self::Call) -> Option<Self> {
|
||||||
|
Some(UncheckedMortalCompactExtrinsic::new_unsigned(call))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Address, AccountId, Index, Call, Signature, Context, Hash, BlockNumber> Checkable<Context>
|
impl<Address, AccountId, Index, Call, Signature, Context, Hash, BlockNumber> Checkable<Context>
|
||||||
|
|||||||
@@ -61,9 +61,15 @@ impl<Address, Index, Call, Signature> UncheckedMortalExtrinsic<Address, Index, C
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Address: Encode, Index: Encode, Call: Encode, Signature: Encode> Extrinsic for UncheckedMortalExtrinsic<Address, Index, Call, Signature> {
|
impl<Address: Encode, Index: Encode, Call: Encode, Signature: Encode> Extrinsic for UncheckedMortalExtrinsic<Address, Index, Call, Signature> {
|
||||||
|
type Call = Call;
|
||||||
|
|
||||||
fn is_signed(&self) -> Option<bool> {
|
fn is_signed(&self) -> Option<bool> {
|
||||||
Some(self.signature.is_some())
|
Some(self.signature.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_unsigned(call: Self::Call) -> Option<Self> {
|
||||||
|
Some(UncheckedMortalExtrinsic::new_unsigned(call))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Address, AccountId, Index, Call, Signature, Context, Hash, BlockNumber> Checkable<Context>
|
impl<Address, AccountId, Index, Call, Signature, Context, Hash, BlockNumber> Checkable<Context>
|
||||||
|
|||||||
@@ -869,9 +869,13 @@ impl ::serde::Serialize for OpaqueExtrinsic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl traits::Extrinsic for OpaqueExtrinsic {
|
impl traits::Extrinsic for OpaqueExtrinsic {
|
||||||
|
type Call = ();
|
||||||
|
|
||||||
fn is_signed(&self) -> Option<bool> {
|
fn is_signed(&self) -> Option<bool> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_unsigned(_call: Self::Call) -> Option<Self> { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ impl TypedKey for UintAuthorityId {
|
|||||||
const KEY_TYPE: KeyTypeId = UINT_DUMMY_KEY;
|
const KEY_TYPE: KeyTypeId = UINT_DUMMY_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for UintAuthorityId {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
let ptr = self.0 as *const _;
|
||||||
|
// It's safe to do this here since `UintAuthorityId` is `u64`.
|
||||||
|
unsafe { std::slice::from_raw_parts(ptr, 8) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl OpaqueKeys for UintAuthorityId {
|
impl OpaqueKeys for UintAuthorityId {
|
||||||
type KeyTypeIds = std::iter::Cloned<std::slice::Iter<'static, KeyTypeId>>;
|
type KeyTypeIds = std::iter::Cloned<std::slice::Iter<'static, KeyTypeId>>;
|
||||||
|
|
||||||
@@ -133,6 +141,8 @@ impl<'a> Deserialize<'a> for Header {
|
|||||||
pub struct ExtrinsicWrapper<Xt>(Xt);
|
pub struct ExtrinsicWrapper<Xt>(Xt);
|
||||||
|
|
||||||
impl<Xt> traits::Extrinsic for ExtrinsicWrapper<Xt> {
|
impl<Xt> traits::Extrinsic for ExtrinsicWrapper<Xt> {
|
||||||
|
type Call = ();
|
||||||
|
|
||||||
fn is_signed(&self) -> Option<bool> {
|
fn is_signed(&self) -> Option<bool> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -219,6 +229,8 @@ impl<Call: Codec + Sync + Send, Context> Checkable<Context> for TestXt<Call> {
|
|||||||
fn check(self, _: &Context) -> Result<Self::Checked, &'static str> { Ok(self) }
|
fn check(self, _: &Context) -> Result<Self::Checked, &'static str> { Ok(self) }
|
||||||
}
|
}
|
||||||
impl<Call: Codec + Sync + Send> traits::Extrinsic for TestXt<Call> {
|
impl<Call: Codec + Sync + Send> traits::Extrinsic for TestXt<Call> {
|
||||||
|
type Call = Call;
|
||||||
|
|
||||||
fn is_signed(&self) -> Option<bool> {
|
fn is_signed(&self) -> Option<bool> {
|
||||||
Some(self.0.is_some())
|
Some(self.0.is_some())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -622,6 +622,12 @@ pub trait RandomnessBeacon {
|
|||||||
pub trait Member: Send + Sync + Sized + MaybeDebug + Eq + PartialEq + Clone + 'static {}
|
pub trait Member: Send + Sync + Sized + MaybeDebug + Eq + PartialEq + Clone + 'static {}
|
||||||
impl<T: Send + Sync + Sized + MaybeDebug + Eq + PartialEq + Clone + 'static> Member for T {}
|
impl<T: Send + Sync + Sized + MaybeDebug + Eq + PartialEq + Clone + 'static> Member for T {}
|
||||||
|
|
||||||
|
/// Determine if a `MemberId` is a valid member.
|
||||||
|
pub trait IsMember<MemberId> {
|
||||||
|
/// Is the given `MemberId` a valid member?
|
||||||
|
fn is_member(member_id: &MemberId) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
/// Something which fulfills the abstract idea of a Substrate header. It has types for a `Number`,
|
/// Something which fulfills the abstract idea of a Substrate header. It has types for a `Number`,
|
||||||
/// a `Hash` and a `Digest`. It provides access to an `extrinsics_root`, `state_root` and
|
/// a `Hash` and a `Digest`. It provides access to an `extrinsics_root`, `state_root` and
|
||||||
/// `parent_hash`, as well as a `digest` and a block `number`.
|
/// `parent_hash`, as well as a `digest` and a block `number`.
|
||||||
@@ -702,10 +708,16 @@ pub trait Block: Clone + Send + Sync + Codec + Eq + MaybeSerializeDebugButNotDes
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Something that acts like an `Extrinsic`.
|
/// Something that acts like an `Extrinsic`.
|
||||||
pub trait Extrinsic {
|
pub trait Extrinsic: Sized {
|
||||||
|
/// The function call.
|
||||||
|
type Call;
|
||||||
|
|
||||||
/// Is this `Extrinsic` signed?
|
/// Is this `Extrinsic` signed?
|
||||||
/// If no information are available about signed/unsigned, `None` should be returned.
|
/// If no information are available about signed/unsigned, `None` should be returned.
|
||||||
fn is_signed(&self) -> Option<bool> { None }
|
fn is_signed(&self) -> Option<bool> { None }
|
||||||
|
|
||||||
|
/// New instance of an unsigned extrinsic aka "inherent".
|
||||||
|
fn new_unsigned(_call: Self::Call) -> Option<Self> { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the hashing type for a block.
|
/// Extract the hashing type for a block.
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ where
|
|||||||
changes_trie_transaction: Option<(MemoryDB<H>, H::Out)>,
|
changes_trie_transaction: Option<(MemoryDB<H>, H::Out)>,
|
||||||
/// Additional externalities for offchain workers.
|
/// Additional externalities for offchain workers.
|
||||||
///
|
///
|
||||||
/// If None, some methods from the trait might not supported.
|
/// If None, some methods from the trait might not be supported.
|
||||||
offchain_externalities: Option<&'a mut O>,
|
offchain_externalities: Option<&'a mut O>,
|
||||||
/// Dummy usage of N arg.
|
/// Dummy usage of N arg.
|
||||||
_phantom: ::std::marker::PhantomData<N>,
|
_phantom: ::std::marker::PhantomData<N>,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use log::warn;
|
|||||||
use hash_db::Hasher;
|
use hash_db::Hasher;
|
||||||
use parity_codec::{Decode, Encode};
|
use parity_codec::{Decode, Encode};
|
||||||
use primitives::{
|
use primitives::{
|
||||||
storage::well_known_keys, NativeOrEncoded, NeverNativeValue, offchain
|
storage::well_known_keys, NativeOrEncoded, NeverNativeValue, offchain,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
@@ -240,6 +240,19 @@ impl offchain::Externalities for NeverOffchainExt {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn network_state(
|
||||||
|
&self,
|
||||||
|
) -> Result<offchain::OpaqueNetworkState, ()> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authority_pubkey(
|
||||||
|
&self,
|
||||||
|
_crypto: offchain::CryptoKind,
|
||||||
|
) -> Result<Vec<u8>, ()> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
fn new_crypto_key(
|
fn new_crypto_key(
|
||||||
&mut self,
|
&mut self,
|
||||||
_crypto: offchain::CryptoKind,
|
_crypto: offchain::CryptoKind,
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ impl BlindCheckable for Extrinsic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExtrinsicT for Extrinsic {
|
impl ExtrinsicT for Extrinsic {
|
||||||
|
type Call = ();
|
||||||
|
|
||||||
fn is_signed(&self) -> Option<bool> {
|
fn is_signed(&self) -> Option<bool> {
|
||||||
if let Extrinsic::IncludeData(_) = *self {
|
if let Extrinsic::IncludeData(_) = *self {
|
||||||
Some(false)
|
Some(false)
|
||||||
@@ -147,6 +149,10 @@ impl ExtrinsicT for Extrinsic {
|
|||||||
Some(true)
|
Some(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_unsigned(_call: Self::Call) -> Option<Self> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Extrinsic {
|
impl Extrinsic {
|
||||||
|
|||||||
@@ -80,9 +80,13 @@ pub mod opaque {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl traits::Extrinsic for UncheckedExtrinsic {
|
impl traits::Extrinsic for UncheckedExtrinsic {
|
||||||
|
type Call = ();
|
||||||
fn is_signed(&self) -> Option<bool> {
|
fn is_signed(&self) -> Option<bool> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
fn new_unsigned(_call: Self::Call) -> Option<Self> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// Opaque block header type.
|
/// Opaque block header type.
|
||||||
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||||
|
|||||||
@@ -19,10 +19,10 @@
|
|||||||
use primitives::{ed25519, sr25519, Pair, crypto::UncheckedInto};
|
use primitives::{ed25519, sr25519, Pair, crypto::UncheckedInto};
|
||||||
use node_primitives::{AccountId, AuraId, Balance};
|
use node_primitives::{AccountId, AuraId, Balance};
|
||||||
use node_runtime::{
|
use node_runtime::{
|
||||||
GrandpaConfig, BalancesConfig, ContractsConfig, ElectionsConfig, DemocracyConfig, CouncilConfig,
|
GrandpaConfig, BalancesConfig, ContractsConfig, ElectionsConfig, DemocracyConfig,
|
||||||
AuraConfig, IndicesConfig, SessionConfig, StakingConfig, SudoConfig, TechnicalCommitteeConfig,
|
CouncilConfig, AuraConfig, ImOnlineConfig, IndicesConfig, SessionConfig, StakingConfig,
|
||||||
SystemConfig, WASM_BINARY, Perbill, SessionKeys, StakerStatus, DAYS, DOLLARS,
|
SudoConfig, TechnicalCommitteeConfig, SystemConfig, WASM_BINARY, Perbill, SessionKeys,
|
||||||
MILLICENTS,
|
StakerStatus, DAYS, DOLLARS, MILLICENTS,
|
||||||
};
|
};
|
||||||
pub use node_runtime::GenesisConfig;
|
pub use node_runtime::GenesisConfig;
|
||||||
use substrate_service;
|
use substrate_service;
|
||||||
@@ -154,6 +154,10 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
|
|||||||
aura: Some(AuraConfig {
|
aura: Some(AuraConfig {
|
||||||
authorities: initial_authorities.iter().map(|x| x.2.clone()).collect(),
|
authorities: initial_authorities.iter().map(|x| x.2.clone()).collect(),
|
||||||
}),
|
}),
|
||||||
|
im_online: Some(ImOnlineConfig {
|
||||||
|
gossip_at: 0,
|
||||||
|
last_new_era_start: 0,
|
||||||
|
}),
|
||||||
grandpa: Some(GrandpaConfig {
|
grandpa: Some(GrandpaConfig {
|
||||||
authorities: initial_authorities.iter().map(|x| (x.3.clone(), 1)).collect(),
|
authorities: initial_authorities.iter().map(|x| (x.3.clone(), 1)).collect(),
|
||||||
}),
|
}),
|
||||||
@@ -291,6 +295,10 @@ pub fn testnet_genesis(
|
|||||||
aura: Some(AuraConfig {
|
aura: Some(AuraConfig {
|
||||||
authorities: initial_authorities.iter().map(|x| x.2.clone()).collect(),
|
authorities: initial_authorities.iter().map(|x| x.2.clone()).collect(),
|
||||||
}),
|
}),
|
||||||
|
im_online: Some(ImOnlineConfig{
|
||||||
|
gossip_at: 0,
|
||||||
|
last_new_era_start: 0,
|
||||||
|
}),
|
||||||
grandpa: Some(GrandpaConfig {
|
grandpa: Some(GrandpaConfig {
|
||||||
authorities: initial_authorities.iter().map(|x| (x.3.clone(), 1)).collect(),
|
authorities: initial_authorities.iter().map(|x| (x.3.clone(), 1)).collect(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ mod tests {
|
|||||||
gas_price: 1 * MILLICENTS,
|
gas_price: 1 * MILLICENTS,
|
||||||
}),
|
}),
|
||||||
sudo: Some(Default::default()),
|
sudo: Some(Default::default()),
|
||||||
|
im_online: Some(Default::default()),
|
||||||
grandpa: Some(GrandpaConfig {
|
grandpa: Some(GrandpaConfig {
|
||||||
authorities: vec![],
|
authorities: vec![],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ system = { package = "srml-system", path = "../../srml/system", default-features
|
|||||||
timestamp = { package = "srml-timestamp", path = "../../srml/timestamp", default-features = false }
|
timestamp = { package = "srml-timestamp", path = "../../srml/timestamp", default-features = false }
|
||||||
treasury = { package = "srml-treasury", path = "../../srml/treasury", default-features = false }
|
treasury = { package = "srml-treasury", path = "../../srml/treasury", default-features = false }
|
||||||
sudo = { package = "srml-sudo", path = "../../srml/sudo", default-features = false }
|
sudo = { package = "srml-sudo", path = "../../srml/sudo", default-features = false }
|
||||||
|
im-online = { package = "srml-im-online", path = "../../srml/im-online", default-features = false }
|
||||||
node-primitives = { path = "../primitives", default-features = false }
|
node-primitives = { path = "../primitives", default-features = false }
|
||||||
consensus_aura = { package = "substrate-consensus-aura-primitives", path = "../../core/consensus/aura/primitives", default-features = false }
|
consensus_aura = { package = "substrate-consensus-aura-primitives", path = "../../core/consensus/aura/primitives", default-features = false }
|
||||||
rustc-hex = { version = "2.0", optional = true }
|
rustc-hex = { version = "2.0", optional = true }
|
||||||
@@ -79,4 +80,5 @@ std = [
|
|||||||
"rustc-hex",
|
"rustc-hex",
|
||||||
"substrate-keyring",
|
"substrate-keyring",
|
||||||
"offchain-primitives/std",
|
"offchain-primitives/std",
|
||||||
|
"im-online/std",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
|||||||
// and set impl_version to equal spec_version. If only runtime
|
// and set impl_version to equal spec_version. If only runtime
|
||||||
// implementation changes and behavior does not, then leave spec_version as
|
// implementation changes and behavior does not, then leave spec_version as
|
||||||
// is and increment impl_version.
|
// is and increment impl_version.
|
||||||
spec_version: 114,
|
spec_version: 115,
|
||||||
impl_version: 114,
|
impl_version: 115,
|
||||||
apis: RUNTIME_API_VERSIONS,
|
apis: RUNTIME_API_VERSIONS,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ parameter_types! {
|
|||||||
pub const Offset: BlockNumber = 0;
|
pub const Offset: BlockNumber = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionHandlers = (Grandpa, Aura);
|
type SessionHandlers = (Grandpa, Aura, ImOnline);
|
||||||
|
|
||||||
impl_opaque_keys! {
|
impl_opaque_keys! {
|
||||||
pub struct SessionKeys {
|
pub struct SessionKeys {
|
||||||
@@ -371,6 +371,15 @@ impl sudo::Trait for Runtime {
|
|||||||
type Proposal = Call;
|
type Proposal = Call;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl im_online::Trait for Runtime {
|
||||||
|
type AuthorityId = AuraId;
|
||||||
|
type Call = Call;
|
||||||
|
type Event = Event;
|
||||||
|
type SessionsPerEra = SessionsPerEra;
|
||||||
|
type UncheckedExtrinsic = UncheckedExtrinsic;
|
||||||
|
type IsValidAuthorityId = Aura;
|
||||||
|
}
|
||||||
|
|
||||||
impl grandpa::Trait for Runtime {
|
impl grandpa::Trait for Runtime {
|
||||||
type Event = Event;
|
type Event = Event;
|
||||||
}
|
}
|
||||||
@@ -409,6 +418,7 @@ construct_runtime!(
|
|||||||
Treasury: treasury::{Module, Call, Storage, Event<T>},
|
Treasury: treasury::{Module, Call, Storage, Event<T>},
|
||||||
Contracts: contracts,
|
Contracts: contracts,
|
||||||
Sudo: sudo,
|
Sudo: sudo,
|
||||||
|
ImOnline: im_online::{default, ValidateUnsigned},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ use rstd::{result, prelude::*};
|
|||||||
use parity_codec::Encode;
|
use parity_codec::Encode;
|
||||||
use srml_support::{decl_storage, decl_module, Parameter, storage::StorageValue, traits::Get};
|
use srml_support::{decl_storage, decl_module, Parameter, storage::StorageValue, traits::Get};
|
||||||
use primitives::{
|
use primitives::{
|
||||||
traits::{SaturatedConversion, Saturating, Zero, One, Member, TypedKey},
|
traits::{SaturatedConversion, Saturating, Zero, One, Member, IsMember, TypedKey},
|
||||||
generic::DigestItem,
|
generic::DigestItem,
|
||||||
};
|
};
|
||||||
use timestamp::OnTimestampSet;
|
use timestamp::OnTimestampSet;
|
||||||
@@ -210,6 +210,14 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Trait> IsMember<T::AuthorityId> for Module<T> {
|
||||||
|
fn is_member(authority_id: &T::AuthorityId) -> bool {
|
||||||
|
Self::authorities()
|
||||||
|
.iter()
|
||||||
|
.any(|id| id == authority_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A report of skipped authorities in Aura.
|
/// A report of skipped authorities in Aura.
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "std", derive(Debug))]
|
#[cfg_attr(feature = "std", derive(Debug))]
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ pub use timestamp;
|
|||||||
use rstd::{result, prelude::*};
|
use rstd::{result, prelude::*};
|
||||||
use srml_support::{decl_storage, decl_module, StorageValue, traits::FindAuthor, traits::Get};
|
use srml_support::{decl_storage, decl_module, StorageValue, traits::FindAuthor, traits::Get};
|
||||||
use timestamp::{OnTimestampSet, Trait};
|
use timestamp::{OnTimestampSet, Trait};
|
||||||
use primitives::{generic::DigestItem, traits::{SaturatedConversion, Saturating, RandomnessBeacon}};
|
use primitives::{
|
||||||
|
generic::DigestItem,
|
||||||
|
traits::{IsMember, SaturatedConversion, Saturating, RandomnessBeacon}
|
||||||
|
};
|
||||||
use primitives::ConsensusEngineId;
|
use primitives::ConsensusEngineId;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use timestamp::TimestampInherentData;
|
use timestamp::TimestampInherentData;
|
||||||
@@ -188,6 +191,14 @@ impl<T: Trait> FindAuthor<u64> for Module<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: timestamp::Trait> IsMember<AuthorityId> for Module<T> {
|
||||||
|
fn is_member(authority_id: &AuthorityId) -> bool {
|
||||||
|
<Module<T>>::authorities()
|
||||||
|
.iter()
|
||||||
|
.any(|id| id == authority_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Trait> Module<T> {
|
impl<T: Trait> Module<T> {
|
||||||
/// Determine the BABE slot duration based on the Timestamp module configuration.
|
/// Determine the BABE slot duration based on the Timestamp module configuration.
|
||||||
pub fn slot_duration() -> T::Moment {
|
pub fn slot_duration() -> T::Moment {
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "srml-im-online"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
parity-codec = { version = "4.1.1", default-features = false, features = ["derive"] }
|
||||||
|
primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false }
|
||||||
|
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
|
||||||
|
serde = { version = "1.0", optional = true }
|
||||||
|
session = { package = "srml-session", path = "../session", default-features = false }
|
||||||
|
srml-support = { path = "../support", default-features = false }
|
||||||
|
sr-io = { package = "sr-io", path = "../../core/sr-io", default-features = false }
|
||||||
|
substrate_primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false }
|
||||||
|
system = { package = "srml-system", path = "../system", default-features = false }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = [
|
||||||
|
"parity-codec/std",
|
||||||
|
"primitives/std",
|
||||||
|
"rstd/std",
|
||||||
|
"serde",
|
||||||
|
"session/std",
|
||||||
|
"srml-support/std",
|
||||||
|
"sr-io/std",
|
||||||
|
"system/std",
|
||||||
|
]
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
// Copyright 2019 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/>.
|
||||||
|
|
||||||
|
//! # I'm online Module
|
||||||
|
//!
|
||||||
|
//! If the local node is a validator (i.e. contains an authority key), this module
|
||||||
|
//! gossips a heartbeat transaction with each new session. The heartbeat functions
|
||||||
|
//! as a simple mechanism to signal that the node is online in the current era.
|
||||||
|
//!
|
||||||
|
//! Received heartbeats are tracked for one era and reset with each new era. The
|
||||||
|
//! module exposes two public functions to query if a heartbeat has been received
|
||||||
|
//! in the current era or session.
|
||||||
|
//!
|
||||||
|
//! The heartbeat is a signed transaction, which was signed using the session key
|
||||||
|
//! and includes the recent best block number of the local validators chain as well
|
||||||
|
//! as the [NetworkState](../../core/offchain/struct.NetworkState.html).
|
||||||
|
//! It is submitted as an Unsigned Transaction via off-chain workers.
|
||||||
|
//!
|
||||||
|
//! - [`im_online::Trait`](./trait.Trait.html)
|
||||||
|
//! - [`Call`](./enum.Call.html)
|
||||||
|
//! - [`Module`](./struct.Module.html)
|
||||||
|
//!
|
||||||
|
//! ## Interface
|
||||||
|
//!
|
||||||
|
//! ### Public Functions
|
||||||
|
//!
|
||||||
|
//! - `is_online_in_current_era` - True if the validator sent a heartbeat in the current era.
|
||||||
|
//! - `is_online_in_current_session` - True if the validator sent a heartbeat in the current session.
|
||||||
|
//!
|
||||||
|
//! ## Usage
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use srml_support::{decl_module, dispatch::Result};
|
||||||
|
//! use system::ensure_signed;
|
||||||
|
//! use srml_im_online::{self as im_online};
|
||||||
|
//!
|
||||||
|
//! pub trait Trait: im_online::Trait {}
|
||||||
|
//!
|
||||||
|
//! decl_module! {
|
||||||
|
//! pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||||
|
//! pub fn is_online(origin, authority_id: T::AuthorityId) -> Result {
|
||||||
|
//! let _sender = ensure_signed(origin)?;
|
||||||
|
//! let _is_online = <im_online::Module<T>>::is_online_in_current_era(&authority_id);
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! # fn main() { }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Dependencies
|
||||||
|
//!
|
||||||
|
//! This module depends on the [Session module](../srml_session/index.html).
|
||||||
|
|
||||||
|
// Ensure we're `no_std` when compiling for Wasm.
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
|
use substrate_primitives::{
|
||||||
|
crypto::TypedKey, offchain::CryptoKind,
|
||||||
|
crypto::key_types,
|
||||||
|
offchain::OpaqueNetworkState,
|
||||||
|
offchain::StorageKind,
|
||||||
|
sr25519, ed25519,
|
||||||
|
};
|
||||||
|
use parity_codec::{Encode, Decode};
|
||||||
|
use primitives::{
|
||||||
|
ApplyError, traits::{Member, IsMember, Extrinsic as ExtrinsicT},
|
||||||
|
transaction_validity::{TransactionValidity, TransactionLongevity},
|
||||||
|
};
|
||||||
|
use rstd::prelude::*;
|
||||||
|
use session::SessionIndex;
|
||||||
|
use sr_io::Printable;
|
||||||
|
use srml_support::{
|
||||||
|
Parameter, StorageValue, decl_module, decl_event, decl_storage,
|
||||||
|
traits::Get, StorageDoubleMap, print,
|
||||||
|
};
|
||||||
|
use system::ensure_none;
|
||||||
|
|
||||||
|
// The local storage database key under which the worker progress status
|
||||||
|
// is tracked.
|
||||||
|
const DB_KEY: &[u8] = b"srml/im-online-worker-status";
|
||||||
|
|
||||||
|
// It's important to persist the worker state, since e.g. the
|
||||||
|
// server could be restarted while starting the gossip process, but before
|
||||||
|
// finishing it. With every execution of the off-chain worker we check
|
||||||
|
// if we need to recover and resume gossipping or if there is already
|
||||||
|
// another off-chain worker in the process of gossipping.
|
||||||
|
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Debug))]
|
||||||
|
struct WorkerStatus<BlockNumber> {
|
||||||
|
done: bool,
|
||||||
|
gossipping_at: BlockNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error which may occur while executing the off-chain code.
|
||||||
|
enum OffchainErr {
|
||||||
|
DecodeAuthorityId,
|
||||||
|
DecodeWorkerStatus,
|
||||||
|
ExtrinsicCreation,
|
||||||
|
FailedSigning,
|
||||||
|
NetworkState,
|
||||||
|
SubmitTransaction,
|
||||||
|
UnknownCryptoKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Printable for OffchainErr {
|
||||||
|
fn print(self) {
|
||||||
|
match self {
|
||||||
|
OffchainErr::DecodeAuthorityId => print("Offchain error: decoding AuthorityId failed!"),
|
||||||
|
OffchainErr::DecodeWorkerStatus => print("Offchain error: decoding WorkerStatus failed!"),
|
||||||
|
OffchainErr::ExtrinsicCreation => print("Offchain error: extrinsic creation failed!"),
|
||||||
|
OffchainErr::FailedSigning => print("Offchain error: signing failed!"),
|
||||||
|
OffchainErr::NetworkState => print("Offchain error: fetching network state failed!"),
|
||||||
|
OffchainErr::SubmitTransaction => print("Offchain error: submitting transaction failed!"),
|
||||||
|
OffchainErr::UnknownCryptoKind => print("Offchain error: the CryptoKind is unknown!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Heartbeat which is send/received.
|
||||||
|
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Debug))]
|
||||||
|
pub struct Heartbeat<BlockNumber, AuthorityId>
|
||||||
|
where BlockNumber: PartialEq + Eq + Decode + Encode,
|
||||||
|
{
|
||||||
|
block_number: BlockNumber,
|
||||||
|
network_state: OpaqueNetworkState,
|
||||||
|
session_index: session::SessionIndex,
|
||||||
|
authority_id: AuthorityId,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Trait: system::Trait + session::Trait {
|
||||||
|
/// The overarching event type.
|
||||||
|
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
||||||
|
|
||||||
|
/// The function call.
|
||||||
|
type Call: From<Call<Self>>;
|
||||||
|
|
||||||
|
/// A extrinsic right from the external world. This is unchecked and so
|
||||||
|
/// can contain a signature.
|
||||||
|
type UncheckedExtrinsic: ExtrinsicT<Call=Self::Call> + Encode + Decode;
|
||||||
|
|
||||||
|
/// The identifier type for an authority.
|
||||||
|
type AuthorityId: Member + Parameter + Default + TypedKey + Decode + Encode + AsRef<[u8]>;
|
||||||
|
|
||||||
|
/// Number of sessions per era.
|
||||||
|
type SessionsPerEra: Get<SessionIndex>;
|
||||||
|
|
||||||
|
/// Determine if an `AuthorityId` is a valid authority.
|
||||||
|
type IsValidAuthorityId: IsMember<Self::AuthorityId>;
|
||||||
|
}
|
||||||
|
|
||||||
|
decl_event!(
|
||||||
|
pub enum Event<T> where
|
||||||
|
<T as system::Trait>::BlockNumber,
|
||||||
|
<T as Trait>::AuthorityId
|
||||||
|
{
|
||||||
|
/// A new heartbeat was received at this `BlockNumber` from `AuthorityId`
|
||||||
|
HeartbeatReceived(BlockNumber, AuthorityId),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
decl_storage! {
|
||||||
|
trait Store for Module<T: Trait> as ImOnline {
|
||||||
|
// The block number when we should gossip.
|
||||||
|
GossipAt get(gossip_at) config(): T::BlockNumber;
|
||||||
|
|
||||||
|
// The session index when the last new era started.
|
||||||
|
LastNewEraStart get(last_new_era_start) config(): Option<session::SessionIndex>;
|
||||||
|
|
||||||
|
// For each session index we keep a mapping of `AuthorityId` to
|
||||||
|
// `offchain::OpaqueNetworkState`.
|
||||||
|
ReceivedHeartbeats get(received_heartbeats): double_map session::SessionIndex,
|
||||||
|
blake2_256(T::AuthorityId) => Vec<u8>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decl_module! {
|
||||||
|
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||||
|
/// Number of sessions per era.
|
||||||
|
const SessionsPerEra: session::SessionIndex = T::SessionsPerEra::get();
|
||||||
|
|
||||||
|
fn deposit_event<T>() = default;
|
||||||
|
|
||||||
|
fn heartbeat(
|
||||||
|
origin,
|
||||||
|
heartbeat: Heartbeat<T::BlockNumber, T::AuthorityId>,
|
||||||
|
_signature: Vec<u8>
|
||||||
|
) {
|
||||||
|
ensure_none(origin)?;
|
||||||
|
|
||||||
|
let current_session = <session::Module<T>>::current_index();
|
||||||
|
let exists = <ReceivedHeartbeats<T>>::exists(current_session, &heartbeat.authority_id);
|
||||||
|
if !exists {
|
||||||
|
let now = <system::Module<T>>::block_number();
|
||||||
|
Self::deposit_event(RawEvent::HeartbeatReceived(now, heartbeat.authority_id.clone()));
|
||||||
|
|
||||||
|
let network_state = heartbeat.network_state.encode();
|
||||||
|
<ReceivedHeartbeats<T>>::insert(current_session, &heartbeat.authority_id, network_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs after every block.
|
||||||
|
fn offchain_worker(now: T::BlockNumber) {
|
||||||
|
fn gossip_at<T: Trait>(block_number: T::BlockNumber) -> Result<(), OffchainErr> {
|
||||||
|
let kind = match <T::AuthorityId as TypedKey>::KEY_TYPE {
|
||||||
|
key_types::SR25519 => CryptoKind::Sr25519,
|
||||||
|
key_types::ED25519 => CryptoKind::Ed25519,
|
||||||
|
_ => return Err(OffchainErr::UnknownCryptoKind),
|
||||||
|
};
|
||||||
|
|
||||||
|
// we run only when a local authority key is configured
|
||||||
|
if let Ok(key) = sr_io::authority_pubkey(kind) {
|
||||||
|
let authority_id = <T as Trait>::AuthorityId::decode(&mut &key[..])
|
||||||
|
.ok_or(OffchainErr::DecodeAuthorityId)?;
|
||||||
|
let network_state =
|
||||||
|
sr_io::network_state().map_err(|_| OffchainErr::NetworkState)?;
|
||||||
|
let heartbeat_data = Heartbeat {
|
||||||
|
block_number,
|
||||||
|
network_state,
|
||||||
|
session_index: <session::Module<T>>::current_index(),
|
||||||
|
authority_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let signature = sr_io::sign(None, kind, &heartbeat_data.encode())
|
||||||
|
.map_err(|_| OffchainErr::FailedSigning)?;
|
||||||
|
let call = Call::heartbeat(heartbeat_data, signature);
|
||||||
|
let ex = T::UncheckedExtrinsic::new_unsigned(call.into())
|
||||||
|
.ok_or(OffchainErr::ExtrinsicCreation)?;
|
||||||
|
sr_io::submit_transaction(&ex)
|
||||||
|
.map_err(|_| OffchainErr::SubmitTransaction)?;
|
||||||
|
set_worker_status::<T>(block_number, true);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_worker_status<T: Trait>(gossipping_at: T::BlockNumber, done: bool) {
|
||||||
|
let enc = WorkerStatus {
|
||||||
|
done,
|
||||||
|
gossipping_at,
|
||||||
|
};
|
||||||
|
sr_io::local_storage_set(StorageKind::PERSISTENT, DB_KEY, &enc.encode());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn was_not_yet_gossipped<T: Trait>(
|
||||||
|
now: T::BlockNumber,
|
||||||
|
next_gossip: T::BlockNumber,
|
||||||
|
) -> Result<bool, OffchainErr> {
|
||||||
|
let last_gossip = sr_io::local_storage_get(StorageKind::PERSISTENT, DB_KEY);
|
||||||
|
match last_gossip {
|
||||||
|
Some(l) => {
|
||||||
|
let worker_status: WorkerStatus<T::BlockNumber> = Decode::decode(&mut &l[..])
|
||||||
|
.ok_or(OffchainErr::DecodeWorkerStatus)?;
|
||||||
|
|
||||||
|
let was_aborted = !worker_status.done && worker_status.gossipping_at < now;
|
||||||
|
|
||||||
|
// another off-chain worker is currently in the process of submitting
|
||||||
|
let already_submitting =
|
||||||
|
!worker_status.done && worker_status.gossipping_at == now;
|
||||||
|
|
||||||
|
let not_yet_gossipped =
|
||||||
|
worker_status.done && worker_status.gossipping_at < next_gossip;
|
||||||
|
|
||||||
|
let ret = (was_aborted && !already_submitting) || not_yet_gossipped;
|
||||||
|
Ok(ret)
|
||||||
|
},
|
||||||
|
None => Ok(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_gossip = <GossipAt<T>>::get();
|
||||||
|
let not_yet_gossipped = match was_not_yet_gossipped::<T>(now, next_gossip) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => {
|
||||||
|
print(err);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if next_gossip < now && not_yet_gossipped {
|
||||||
|
set_worker_status::<T>(now, false);
|
||||||
|
|
||||||
|
match gossip_at::<T>(now) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(err) => print(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Trait> Module<T> {
|
||||||
|
/// Returns `true` if a heartbeat has been received for `AuthorityId`
|
||||||
|
/// during the current era. Otherwise `false`.
|
||||||
|
pub fn is_online_in_current_era(authority_id: &T::AuthorityId) -> bool {
|
||||||
|
let curr = <session::Module<T>>::current_index();
|
||||||
|
match LastNewEraStart::get() {
|
||||||
|
Some(start) => {
|
||||||
|
// iterate over every session
|
||||||
|
for index in start..curr {
|
||||||
|
if <ReceivedHeartbeats<T>>::exists(index, authority_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
None => <ReceivedHeartbeats<T>>::exists(curr, authority_id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if a heartbeat has been received for `AuthorityId`
|
||||||
|
/// during the current session. Otherwise `false`.
|
||||||
|
pub fn is_online_in_current_session(authority_id: &T::AuthorityId) -> bool {
|
||||||
|
let current_session = <session::Module<T>>::current_index();
|
||||||
|
<ReceivedHeartbeats<T>>::exists(current_session, authority_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Session has just changed.
|
||||||
|
fn new_session() {
|
||||||
|
let now = <system::Module<T>>::block_number();
|
||||||
|
<GossipAt<T>>::put(now);
|
||||||
|
|
||||||
|
let current_session = <session::Module<T>>::current_index();
|
||||||
|
|
||||||
|
match LastNewEraStart::get() {
|
||||||
|
Some(last_new_era_start) => {
|
||||||
|
let sessions_per_era = T::SessionsPerEra::get();
|
||||||
|
|
||||||
|
let new_era = current_session - last_new_era_start > sessions_per_era;
|
||||||
|
if new_era {
|
||||||
|
LastNewEraStart::put(current_session);
|
||||||
|
Self::remove_heartbeats();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => LastNewEraStart::put(current_session),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all stored heartbeats.
|
||||||
|
fn remove_heartbeats() {
|
||||||
|
let curr = <session::Module<T>>::current_index();
|
||||||
|
match LastNewEraStart::get() {
|
||||||
|
Some(start) => {
|
||||||
|
for index in start..curr {
|
||||||
|
<ReceivedHeartbeats<T>>::remove_prefix(index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => <ReceivedHeartbeats<T>>::remove_prefix(curr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||||
|
type Key = <T as Trait>::AuthorityId;
|
||||||
|
|
||||||
|
fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I) {
|
||||||
|
Self::new_session();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_disabled(_i: usize) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Trait> srml_support::unsigned::ValidateUnsigned for Module<T> {
|
||||||
|
type Call = Call<T>;
|
||||||
|
|
||||||
|
fn validate_unsigned(call: &Self::Call) -> srml_support::unsigned::TransactionValidity {
|
||||||
|
if let Call::heartbeat(heartbeat, signature) = call {
|
||||||
|
// verify that the incoming (unverified) pubkey is actually an authority id
|
||||||
|
let is_authority = T::IsValidAuthorityId::is_member(&heartbeat.authority_id);
|
||||||
|
if !is_authority {
|
||||||
|
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if <Module<T>>::is_online_in_current_session(&heartbeat.authority_id) {
|
||||||
|
// we already received a heartbeat for this authority
|
||||||
|
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if signature.len() != 64 {
|
||||||
|
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||||
|
}
|
||||||
|
|
||||||
|
let signature = {
|
||||||
|
let mut array = [0; 64];
|
||||||
|
array.copy_from_slice(&signature); // panics if not enough, hence the check above
|
||||||
|
array
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_heartbeat = heartbeat.encode();
|
||||||
|
|
||||||
|
let signature_valid = match <T::AuthorityId as TypedKey>::KEY_TYPE {
|
||||||
|
ed25519::Public::KEY_TYPE =>
|
||||||
|
sr_io::ed25519_verify(&signature, &encoded_heartbeat, &heartbeat.authority_id),
|
||||||
|
sr25519::Public::KEY_TYPE =>
|
||||||
|
sr_io::sr25519_verify(&signature, &encoded_heartbeat, &heartbeat.authority_id),
|
||||||
|
_ => return TransactionValidity::Invalid(ApplyError::BadSignature as i8),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !signature_valid {
|
||||||
|
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if session index from heartbeat is recent
|
||||||
|
let current_session = <session::Module<T>>::current_index();
|
||||||
|
if heartbeat.session_index < current_session {
|
||||||
|
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return srml_support::unsigned::TransactionValidity::Valid {
|
||||||
|
priority: 0,
|
||||||
|
requires: vec![],
|
||||||
|
provides: vec![encoded_heartbeat],
|
||||||
|
longevity: TransactionLongevity::max_value(),
|
||||||
|
propagate: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TransactionValidity::Invalid(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user