feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,559 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// Pezcumulus 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.
|
||||
|
||||
// Pezcumulus 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 Pezcumulus. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Teyrchain bootnode advertisement.
|
||||
|
||||
use crate::config::MAX_ADDRESSES;
|
||||
use codec::{Compact, CompactRef, Decode, Encode};
|
||||
use cumulus_primitives_core::{
|
||||
relay_chain::{Hash as RelayHash, Header as RelayHeader},
|
||||
ParaId,
|
||||
};
|
||||
use cumulus_relay_chain_interface::{RelayChainInterface, RelayChainResult};
|
||||
use futures::{future::Fuse, pin_mut, FutureExt, StreamExt};
|
||||
use ip_network::IpNetwork;
|
||||
use log::{debug, error, trace, warn};
|
||||
use prost::Message;
|
||||
use pezsc_network::{
|
||||
config::OutgoingResponse,
|
||||
event::{DhtEvent, Event},
|
||||
multiaddr::Protocol,
|
||||
request_responses::IncomingRequest,
|
||||
service::traits::NetworkService,
|
||||
KademliaKey, Multiaddr,
|
||||
};
|
||||
use pezsp_consensus_babe::{digests::CompatibleDigestItem, Epoch, Randomness};
|
||||
use pezsp_runtime::traits::Header as _;
|
||||
use std::{collections::HashSet, pin::Pin, sync::Arc};
|
||||
use tokio::time::Sleep;
|
||||
|
||||
/// Log target for this file.
|
||||
const LOG_TARGET: &str = "bootnodes::advertisement";
|
||||
|
||||
/// Delay before retrying the DHT content provider publish operation.
|
||||
const RETRY_DELAY: std::time::Duration = std::time::Duration::from_secs(30);
|
||||
|
||||
/// Teyrchain bootnode advertisement parameters.
|
||||
pub struct BootnodeAdvertisementParams {
|
||||
/// Teyrchain ID.
|
||||
pub para_id: ParaId,
|
||||
/// Relay chain interface.
|
||||
pub relay_chain_interface: Arc<dyn RelayChainInterface>,
|
||||
/// Relay chain node network service.
|
||||
pub relay_chain_network: Arc<dyn NetworkService>,
|
||||
/// Bootnode request-response protocol request receiver.
|
||||
pub request_receiver: async_channel::Receiver<IncomingRequest>,
|
||||
/// Teyrchain node network service.
|
||||
pub teyrchain_network: Arc<dyn NetworkService>,
|
||||
/// Whether to advertise non-global IPs.
|
||||
pub advertise_non_global_ips: bool,
|
||||
/// Teyrchain genesis hash.
|
||||
pub teyrchain_genesis_hash: Vec<u8>,
|
||||
/// Teyrchain fork ID.
|
||||
pub teyrchain_fork_id: Option<String>,
|
||||
/// Teyrchain side public addresses.
|
||||
pub public_addresses: Vec<Multiaddr>,
|
||||
}
|
||||
|
||||
/// Teyrchain bootnode advertisement service.
|
||||
pub struct BootnodeAdvertisement {
|
||||
para_id: ParaId,
|
||||
para_id_scale_compact: Vec<u8>,
|
||||
relay_chain_interface: Arc<dyn RelayChainInterface>,
|
||||
relay_chain_network: Arc<dyn NetworkService>,
|
||||
current_epoch_key: Option<KademliaKey>,
|
||||
next_epoch_key: Option<KademliaKey>,
|
||||
current_epoch_publish_retry: Pin<Box<Fuse<Sleep>>>,
|
||||
next_epoch_publish_retry: Pin<Box<Fuse<Sleep>>>,
|
||||
request_receiver: async_channel::Receiver<IncomingRequest>,
|
||||
teyrchain_network: Arc<dyn NetworkService>,
|
||||
advertise_non_global_ips: bool,
|
||||
teyrchain_genesis_hash: Vec<u8>,
|
||||
teyrchain_fork_id: Option<String>,
|
||||
public_addresses: Vec<Multiaddr>,
|
||||
}
|
||||
|
||||
impl BootnodeAdvertisement {
|
||||
/// Create a new bootnode advertisement service.
|
||||
pub fn new(
|
||||
BootnodeAdvertisementParams {
|
||||
para_id,
|
||||
relay_chain_interface,
|
||||
relay_chain_network,
|
||||
request_receiver,
|
||||
teyrchain_network,
|
||||
advertise_non_global_ips,
|
||||
teyrchain_genesis_hash,
|
||||
teyrchain_fork_id,
|
||||
public_addresses,
|
||||
}: BootnodeAdvertisementParams,
|
||||
) -> Self {
|
||||
// Discard `/p2p/<peer_id>` from public addresses on initialization to not generate warnings
|
||||
// on every request for what is an operator mistake.
|
||||
let local_peer_id = teyrchain_network.local_peer_id();
|
||||
let public_addresses = public_addresses
|
||||
.into_iter()
|
||||
.filter_map(|mut addr| match addr.iter().last() {
|
||||
Some(Protocol::P2p(peer_id)) if &peer_id == local_peer_id.as_ref() => {
|
||||
addr.pop();
|
||||
Some(addr)
|
||||
},
|
||||
Some(Protocol::P2p(_)) => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Discarding public address containing not our peer ID: {addr}",
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => Some(addr),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
para_id,
|
||||
para_id_scale_compact: CompactRef(¶_id).encode(),
|
||||
relay_chain_interface,
|
||||
relay_chain_network,
|
||||
current_epoch_key: None,
|
||||
next_epoch_key: None,
|
||||
current_epoch_publish_retry: Box::pin(Fuse::terminated()),
|
||||
next_epoch_publish_retry: Box::pin(Fuse::terminated()),
|
||||
request_receiver,
|
||||
teyrchain_network,
|
||||
advertise_non_global_ips,
|
||||
teyrchain_genesis_hash,
|
||||
teyrchain_fork_id,
|
||||
public_addresses,
|
||||
}
|
||||
}
|
||||
|
||||
async fn current_epoch(&self, hash: RelayHash) -> RelayChainResult<Epoch> {
|
||||
let res = self
|
||||
.relay_chain_interface
|
||||
.call_runtime_api("BabeApi_current_epoch", hash, &[])
|
||||
.await?;
|
||||
Decode::decode(&mut &*res).map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn next_epoch(&self, hash: RelayHash) -> RelayChainResult<Epoch> {
|
||||
let res = self
|
||||
.relay_chain_interface
|
||||
.call_runtime_api("BabeApi_next_epoch", hash, &[])
|
||||
.await?;
|
||||
Decode::decode(&mut &*res).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn epoch_key(&self, randomness: Randomness) -> KademliaKey {
|
||||
self.para_id_scale_compact
|
||||
.clone()
|
||||
.into_iter()
|
||||
.chain(randomness.into_iter())
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
}
|
||||
|
||||
async fn current_and_next_epoch_keys(
|
||||
&self,
|
||||
header: RelayHeader,
|
||||
) -> (Option<KademliaKey>, Option<KademliaKey>) {
|
||||
let hash = header.hash();
|
||||
let number = header.number();
|
||||
|
||||
let current_epoch = match self.current_epoch(hash).await {
|
||||
Ok(epoch) => Some(epoch),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to query current epoch for #{number} {hash:?}: {e}",
|
||||
);
|
||||
|
||||
None
|
||||
},
|
||||
};
|
||||
|
||||
let next_epoch = match self.next_epoch(hash).await {
|
||||
Ok(epoch) => Some(epoch),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to query next epoch for #{number} {hash:?}: {e}",
|
||||
);
|
||||
|
||||
None
|
||||
},
|
||||
};
|
||||
|
||||
(
|
||||
current_epoch.map(|epoch| self.epoch_key(epoch.randomness)),
|
||||
next_epoch.map(|epoch| self.epoch_key(epoch.randomness)),
|
||||
)
|
||||
}
|
||||
|
||||
async fn handle_import_notification(&mut self, header: RelayHeader) {
|
||||
if let Some(ref old_current_epoch_key) = self.current_epoch_key {
|
||||
// Readvertise on start of new epoch only.
|
||||
let Some(next_epoch_descriptor) =
|
||||
header.digest().convert_first(|v| v.as_next_epoch_descriptor())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let next_epoch_key = self.epoch_key(next_epoch_descriptor.randomness);
|
||||
|
||||
if Some(&next_epoch_key) == self.next_epoch_key.as_ref() {
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
"Next epoch descriptor contains the same randomness as the previous one, \
|
||||
not considering this as epoch change (switched fork?)",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Epoch changed, cancel retry attempts.
|
||||
self.current_epoch_publish_retry = Box::pin(Fuse::terminated());
|
||||
self.next_epoch_publish_retry = Box::pin(Fuse::terminated());
|
||||
|
||||
debug!(target: LOG_TARGET, "New epoch started, readvertising teyrchain bootnode.");
|
||||
|
||||
// Stop advertisement of the obsolete key.
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Stopping advertisement of bootnode for old current epoch key {}",
|
||||
hex::encode(old_current_epoch_key.as_ref()),
|
||||
);
|
||||
self.relay_chain_network.stop_providing(old_current_epoch_key.clone());
|
||||
|
||||
// Advertise current keys.
|
||||
self.current_epoch_key = self.next_epoch_key.clone();
|
||||
self.next_epoch_key = Some(next_epoch_key);
|
||||
|
||||
if let Some(ref current_epoch_key) = self.current_epoch_key {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Advertising bootnode for current (old next) epoch key {}",
|
||||
hex::encode(current_epoch_key.as_ref()),
|
||||
);
|
||||
self.relay_chain_network.start_providing(current_epoch_key.clone());
|
||||
}
|
||||
|
||||
if let Some(ref next_epoch_key) = self.next_epoch_key {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Advertising bootnode for next epoch key {}",
|
||||
hex::encode(next_epoch_key.as_ref()),
|
||||
);
|
||||
self.relay_chain_network.start_providing(next_epoch_key.clone());
|
||||
}
|
||||
} else {
|
||||
// First advertisement on startup.
|
||||
let (current_epoch_key, next_epoch_key) =
|
||||
self.current_and_next_epoch_keys(header).await;
|
||||
self.current_epoch_key = current_epoch_key.clone();
|
||||
self.next_epoch_key = next_epoch_key.clone();
|
||||
|
||||
if let Some(current_epoch_key) = current_epoch_key {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Initial advertisement of bootnode for current epoch key {}",
|
||||
hex::encode(current_epoch_key.as_ref()),
|
||||
);
|
||||
|
||||
self.relay_chain_network.start_providing(current_epoch_key);
|
||||
} else {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Initial advertisement of bootnode for current epoch failed: no key."
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(next_epoch_key) = next_epoch_key {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Initial advertisement of bootnode for next epoch key {}",
|
||||
hex::encode(next_epoch_key.as_ref()),
|
||||
);
|
||||
|
||||
self.relay_chain_network.start_providing(next_epoch_key);
|
||||
} else {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Initial advertisement of bootnode for next epoch failed: no key."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The list of teyrchain side addresses.
|
||||
///
|
||||
/// The addresses are sorted as follows:
|
||||
/// 1) public addresses provided by the operator
|
||||
/// 2) global listen addresses
|
||||
/// 3) discovered external addresses
|
||||
/// 4) non-global listen addresses
|
||||
/// 5) loopback listen addresses
|
||||
fn paranode_addresses(&self) -> Vec<Multiaddr> {
|
||||
let local_peer_id = self.teyrchain_network.local_peer_id();
|
||||
|
||||
// Discard `/p2p/<peer_id>` part. `None` if the address contains foreign peer ID.
|
||||
let without_p2p = |mut addr: Multiaddr| match addr.iter().last() {
|
||||
Some(Protocol::P2p(peer_id)) if &peer_id == local_peer_id.as_ref() => {
|
||||
addr.pop();
|
||||
Some(addr)
|
||||
},
|
||||
Some(Protocol::P2p(_)) => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Ignoring teyrchain side address containing not our peer ID: {addr}",
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => Some(addr),
|
||||
};
|
||||
|
||||
// Check if the address is global.
|
||||
let is_global = |address: &Multiaddr| {
|
||||
address.iter().all(|protocol| match protocol {
|
||||
// The `ip_network` library is used because its `is_global()` method is stable,
|
||||
// while `is_global()` in the standard library currently isn't.
|
||||
Protocol::Ip4(ip) => IpNetwork::from(ip).is_global(),
|
||||
Protocol::Ip6(ip) => IpNetwork::from(ip).is_global(),
|
||||
_ => true,
|
||||
})
|
||||
};
|
||||
|
||||
// Check if the address is a loopback address.
|
||||
let is_loopback = |address: &Multiaddr| {
|
||||
address.iter().any(|protocol| match protocol {
|
||||
Protocol::Ip4(ip) => IpNetwork::from(ip).is_loopback(),
|
||||
Protocol::Ip6(ip) => IpNetwork::from(ip).is_loopback(),
|
||||
_ => false,
|
||||
})
|
||||
};
|
||||
|
||||
// 1) public addresses provided by the operator
|
||||
let public_addresses = self.public_addresses.clone().into_iter();
|
||||
|
||||
// 2) global listen addresses
|
||||
let global_listen_addresses =
|
||||
self.teyrchain_network.listen_addresses().into_iter().filter(is_global);
|
||||
|
||||
// 3a) discovered external addresses (global)
|
||||
let global_external_addresses =
|
||||
self.teyrchain_network.external_addresses().into_iter().filter(is_global);
|
||||
|
||||
// 3b) discovered external addresses (non-global)
|
||||
let non_global_external_addresses = self
|
||||
.teyrchain_network
|
||||
.external_addresses()
|
||||
.into_iter()
|
||||
.filter(|addr| !is_global(addr));
|
||||
|
||||
// 4) non-global listen addresses
|
||||
let non_global_listen_addresses = self
|
||||
.teyrchain_network
|
||||
.listen_addresses()
|
||||
.into_iter()
|
||||
.filter(|addr| !is_global(addr) && !is_loopback(addr));
|
||||
|
||||
// 5) loopback listen addresses
|
||||
let loopback_listen_addresses =
|
||||
self.teyrchain_network.listen_addresses().into_iter().filter(is_loopback);
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
public_addresses
|
||||
.chain(global_listen_addresses)
|
||||
.chain(global_external_addresses)
|
||||
.chain(
|
||||
self.advertise_non_global_ips
|
||||
.then_some(
|
||||
non_global_external_addresses
|
||||
.chain(non_global_listen_addresses)
|
||||
.chain(loopback_listen_addresses),
|
||||
)
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.filter_map(without_p2p)
|
||||
// Deduplicate addresses.
|
||||
.filter(|addr| seen.insert(addr.clone()))
|
||||
.take(MAX_ADDRESSES)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn handle_request(&mut self, req: IncomingRequest) {
|
||||
if req.payload == self.para_id_scale_compact {
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
"Serving paranode addresses request from {:?} for teyrchain ID {}",
|
||||
req.peer,
|
||||
self.para_id,
|
||||
);
|
||||
|
||||
let response = crate::schema::Response {
|
||||
peer_id: self.teyrchain_network.local_peer_id().to_bytes(),
|
||||
addrs: self.paranode_addresses().iter().map(|a| a.to_vec()).collect(),
|
||||
genesis_hash: self.teyrchain_genesis_hash.clone(),
|
||||
fork_id: self.teyrchain_fork_id.clone(),
|
||||
};
|
||||
|
||||
let _ = req.pending_response.send(OutgoingResponse {
|
||||
result: Ok(response.encode_to_vec()),
|
||||
reputation_changes: Vec::new(),
|
||||
sent_feedback: None,
|
||||
});
|
||||
} else {
|
||||
let payload = req.payload;
|
||||
match Compact::<ParaId>::decode(&mut &payload[..]) {
|
||||
Ok(para_id) => {
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
"Ignoring request for teyrchain ID {} != self teyrchain ID {} from {:?}",
|
||||
para_id.0,
|
||||
self.para_id,
|
||||
req.peer,
|
||||
);
|
||||
},
|
||||
Err(e) => {
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
"Cannot decode teyrchain ID in a request from {:?}: {e}",
|
||||
req.peer,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_dht_event(&mut self, event: DhtEvent) {
|
||||
match event {
|
||||
DhtEvent::StartedProviding(key) =>
|
||||
if Some(&key) == self.current_epoch_key.as_ref() {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Successfully published provider for current epoch key {}",
|
||||
hex::encode(key.as_ref()),
|
||||
);
|
||||
} else if Some(&key) == self.next_epoch_key.as_ref() {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Successfully published provider for next epoch key {}",
|
||||
hex::encode(key.as_ref()),
|
||||
);
|
||||
},
|
||||
DhtEvent::StartProvidingFailed(key) => {
|
||||
if Some(&key) == self.current_epoch_key.as_ref() {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to publish provider for current epoch key {}. Retrying in {RETRY_DELAY:?}",
|
||||
hex::encode(key.as_ref()),
|
||||
);
|
||||
self.current_epoch_publish_retry =
|
||||
Box::pin(tokio::time::sleep(RETRY_DELAY).fuse());
|
||||
} else if Some(&key) == self.next_epoch_key.as_ref() {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to publish provider for next epoch key {}. Retrying in {RETRY_DELAY:?}",
|
||||
hex::encode(key.as_ref()),
|
||||
);
|
||||
self.next_epoch_publish_retry =
|
||||
Box::pin(tokio::time::sleep(RETRY_DELAY).fuse());
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn retry_for_current_epoch(&mut self) {
|
||||
if let Some(current_epoch_key) = self.current_epoch_key.clone() {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Retrying advertising bootnode for current epoch key {}",
|
||||
hex::encode(current_epoch_key.as_ref()),
|
||||
);
|
||||
self.relay_chain_network.start_providing(current_epoch_key);
|
||||
} else {
|
||||
error!(
|
||||
target: LOG_TARGET,
|
||||
"Retrying advertising bootnode for current epoch failed: no key. This is a bug."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn retry_for_next_epoch(&mut self) {
|
||||
if let Some(next_epoch_key) = self.next_epoch_key.clone() {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Retrying advertising bootnode for next epoch key {}",
|
||||
hex::encode(next_epoch_key.as_ref()),
|
||||
);
|
||||
self.relay_chain_network.start_providing(next_epoch_key);
|
||||
} else {
|
||||
error!(
|
||||
target: LOG_TARGET,
|
||||
"Retrying advertising bootnode for next epoch failed: no key. This is a bug."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the bootnode advertisement service.
|
||||
pub async fn run(mut self) -> RelayChainResult<()> {
|
||||
let mut import_notification_stream =
|
||||
self.relay_chain_interface.import_notification_stream().await?;
|
||||
let dht_event_stream = self
|
||||
.relay_chain_network
|
||||
.event_stream("teyrchain-bootnode-discovery")
|
||||
.filter_map(|e| async move {
|
||||
match e {
|
||||
Event::Dht(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.fuse();
|
||||
pin_mut!(dht_event_stream);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
header = import_notification_stream.next() => match header {
|
||||
Some(header) => self.handle_import_notification(header).await,
|
||||
None => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Import notification stream terminated, terminating bootnode advertisement."
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
req = self.request_receiver.recv() => match req {
|
||||
Ok(req) => {
|
||||
self.handle_request(req);
|
||||
},
|
||||
Err(_) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Paranode request receiver terminated, terminating bootnode advertisement."
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
event = dht_event_stream.select_next_some() => self.handle_dht_event(event),
|
||||
() = &mut self.current_epoch_publish_retry => self.retry_for_current_epoch(),
|
||||
() = &mut self.next_epoch_publish_retry => self.retry_for_next_epoch(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// Pezcumulus 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.
|
||||
|
||||
// Pezcumulus 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 Pezcumulus. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Teyrchain bootnode request-response protocol configuration.
|
||||
|
||||
use pezsc_network::{
|
||||
request_responses::IncomingRequest, service::traits::NetworkBackend, ProtocolName,
|
||||
};
|
||||
use pezsp_runtime::traits::Block as BlockT;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Maximum number of addresses allowed in the response.
|
||||
pub const MAX_ADDRESSES: usize = 32;
|
||||
/// Expected maximum number of simultaneous requests from remote peers.
|
||||
/// Should be enough for a testnet with a plenty of nodes starting at the same time.
|
||||
const INBOUND_CHANNEL_SIZE: usize = 1000;
|
||||
/// Maximum request size. Should be enough to fit SCALE-compact-encoded `para_id`.
|
||||
const MAX_REQUEST_SIZE: u64 = 128;
|
||||
/// Maximum response size as per RFC.
|
||||
const MAX_RESPONSE_SIZE: u64 = 16 * 1024;
|
||||
/// Request-response protocol timeout.
|
||||
const TIMEOUT: Duration = Duration::from_secs(20);
|
||||
|
||||
/// Bootnode request-response protocol name given a genesis hash and fork id.
|
||||
pub fn paranode_protocol_name<Hash: AsRef<[u8]>>(
|
||||
genesis_hash: Hash,
|
||||
fork_id: Option<&str>,
|
||||
) -> ProtocolName {
|
||||
let genesis_hash = genesis_hash.as_ref();
|
||||
if let Some(fork_id) = fork_id {
|
||||
// This is not stated in RFC-0008, but other pezkuwi protocol names are based on `fork_id`
|
||||
// if it is present, so we also use it here.
|
||||
format!("/{}/{}/paranode", array_bytes::bytes2hex("", genesis_hash), fork_id)
|
||||
} else {
|
||||
format!("/{}/paranode", array_bytes::bytes2hex("", genesis_hash))
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Bootnode request-response protocol config.
|
||||
pub fn bootnode_request_response_config<
|
||||
Hash: AsRef<[u8]>,
|
||||
B: BlockT,
|
||||
N: NetworkBackend<B, <B as BlockT>::Hash>,
|
||||
>(
|
||||
genesis_hash: Hash,
|
||||
fork_id: Option<&str>,
|
||||
) -> (N::RequestResponseProtocolConfig, async_channel::Receiver<IncomingRequest>) {
|
||||
let (inbound_tx, inbound_rx) = async_channel::bounded(INBOUND_CHANNEL_SIZE);
|
||||
|
||||
let config = N::request_response_config(
|
||||
paranode_protocol_name(genesis_hash, fork_id),
|
||||
Vec::new(),
|
||||
MAX_REQUEST_SIZE,
|
||||
MAX_RESPONSE_SIZE,
|
||||
TIMEOUT,
|
||||
Some(inbound_tx),
|
||||
);
|
||||
|
||||
(config, inbound_rx)
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// Pezcumulus 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.
|
||||
|
||||
// Pezcumulus 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 Pezcumulus. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Teyrchain bootnode discovery.
|
||||
//!
|
||||
//! The discovery works as follows:
|
||||
//! 1. We start teyrchain bootnode content provider discovery on the relay chain DHT in
|
||||
//! [`BootnodeDiscovery::start_discovery`].
|
||||
//! 2. We handle every provider discovered in [`BootnodeDiscovery::handle_providers`] and try to
|
||||
//! request the bootnodes from the provider over a `/paranode` request-response protocol.
|
||||
//! 3. The request result is handled in [`BootnodeDiscovery::handle_response`]. If the request
|
||||
//! fails this is a sign of the provider addresses not being cached by the remote / dropped by
|
||||
//! the networking library (the case with libp2p). In this case we perform a `FIND_NODE` query
|
||||
//! to get the provider addresses first and repeat the request once we know them.
|
||||
//! 4. When the request over the `/paranode` protocol succeeds, we add the bootnode addresses as
|
||||
//! known addresses to the teyrchain networking.
|
||||
//! 5. If the content provider discovery had completed, all `FIND_NODE` queries finished, and all
|
||||
//! requests over the `/paranode` protocol succeded or failed, but we have not found any
|
||||
//! bootnode addresses, we repeat the discovery process after a cooldown period.
|
||||
|
||||
use crate::{config::MAX_ADDRESSES, schema::Response};
|
||||
use codec::{CompactRef, Decode, Encode};
|
||||
use cumulus_primitives_core::{relay_chain::Hash as RelayHash, ParaId};
|
||||
use cumulus_relay_chain_interface::{RelayChainError, RelayChainInterface, RelayChainResult};
|
||||
use futures::{
|
||||
channel::oneshot,
|
||||
future::{BoxFuture, Fuse, FusedFuture},
|
||||
pin_mut,
|
||||
stream::FuturesUnordered,
|
||||
FutureExt, StreamExt,
|
||||
};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use prost::Message;
|
||||
use pezsc_network::{
|
||||
event::{DhtEvent, Event},
|
||||
request_responses::{IfDisconnected, RequestFailure},
|
||||
service::traits::NetworkService,
|
||||
KademliaKey, Multiaddr, PeerId, ProtocolName,
|
||||
};
|
||||
use pezsp_consensus_babe::{Epoch, Randomness};
|
||||
use std::{collections::HashSet, pin::Pin, sync::Arc, time::Duration};
|
||||
use tokio::time::{sleep, Sleep};
|
||||
|
||||
/// Log target for this file.
|
||||
const LOG_TARGET: &str = "bootnodes::discovery";
|
||||
|
||||
/// Delay before retrying discovery in case of failure. Needed to rate-limit the attempts,
|
||||
/// especially in small testnets where a discovery attempt can be almost instant.
|
||||
const RETRY_DELAY: Duration = Duration::from_secs(30);
|
||||
|
||||
/// Teyrchain bootnode discovery parameters.
|
||||
pub struct BootnodeDiscoveryParams {
|
||||
/// Teyrchain ID.
|
||||
pub para_id: ParaId,
|
||||
/// Teyrchain node network service.
|
||||
pub teyrchain_network: Arc<dyn NetworkService>,
|
||||
/// Teyrchain genesis hash.
|
||||
pub teyrchain_genesis_hash: Vec<u8>,
|
||||
/// Teyrchain fork ID.
|
||||
pub teyrchain_fork_id: Option<String>,
|
||||
/// Relay chain interface.
|
||||
pub relay_chain_interface: Arc<dyn RelayChainInterface>,
|
||||
/// Relay chain network service.
|
||||
pub relay_chain_network: Arc<dyn NetworkService>,
|
||||
/// `/paranode` protocol name.
|
||||
pub paranode_protocol_name: ProtocolName,
|
||||
}
|
||||
|
||||
/// Teyrchain bootnode discovery service.
|
||||
pub struct BootnodeDiscovery {
|
||||
para_id_scale_compact: Vec<u8>,
|
||||
teyrchain_network: Arc<dyn NetworkService>,
|
||||
teyrchain_genesis_hash: Vec<u8>,
|
||||
teyrchain_fork_id: Option<String>,
|
||||
relay_chain_interface: Arc<dyn RelayChainInterface>,
|
||||
relay_chain_network: Arc<dyn NetworkService>,
|
||||
latest_relay_chain_hash: Option<RelayHash>,
|
||||
key_being_discovered: Option<KademliaKey>,
|
||||
paranode_protocol_name: ProtocolName,
|
||||
pending_responses: FuturesUnordered<
|
||||
BoxFuture<
|
||||
'static,
|
||||
(PeerId, Result<Result<(Vec<u8>, ProtocolName), RequestFailure>, oneshot::Canceled>),
|
||||
>,
|
||||
>,
|
||||
direct_requests: HashSet<PeerId>,
|
||||
find_node_queries: HashSet<PeerId>,
|
||||
pending_start_discovery: Pin<Box<Fuse<Sleep>>>,
|
||||
succeeded: bool,
|
||||
}
|
||||
|
||||
impl BootnodeDiscovery {
|
||||
/// Create a new bootnode discovery service.
|
||||
pub fn new(
|
||||
BootnodeDiscoveryParams {
|
||||
para_id,
|
||||
teyrchain_network,
|
||||
teyrchain_genesis_hash,
|
||||
teyrchain_fork_id,
|
||||
relay_chain_interface,
|
||||
relay_chain_network,
|
||||
paranode_protocol_name,
|
||||
}: BootnodeDiscoveryParams,
|
||||
) -> Self {
|
||||
Self {
|
||||
para_id_scale_compact: CompactRef(¶_id).encode(),
|
||||
teyrchain_network,
|
||||
teyrchain_genesis_hash,
|
||||
teyrchain_fork_id,
|
||||
relay_chain_interface,
|
||||
relay_chain_network,
|
||||
latest_relay_chain_hash: None,
|
||||
key_being_discovered: None,
|
||||
paranode_protocol_name,
|
||||
pending_responses: FuturesUnordered::default(),
|
||||
direct_requests: HashSet::new(),
|
||||
find_node_queries: HashSet::new(),
|
||||
// Trigger the discovery immediately on startup.
|
||||
pending_start_discovery: Box::pin(sleep(Duration::ZERO).fuse()),
|
||||
succeeded: false,
|
||||
}
|
||||
}
|
||||
|
||||
async fn current_epoch(&mut self, hash: RelayHash) -> RelayChainResult<Epoch> {
|
||||
let res = self
|
||||
.relay_chain_interface
|
||||
.call_runtime_api("BabeApi_current_epoch", hash, &[])
|
||||
.await?;
|
||||
Decode::decode(&mut &*res).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn epoch_key(&self, randomness: Randomness) -> KademliaKey {
|
||||
self.para_id_scale_compact
|
||||
.clone()
|
||||
.into_iter()
|
||||
.chain(randomness.into_iter())
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Start bootnode discovery.
|
||||
async fn start_discovery(&mut self) -> RelayChainResult<()> {
|
||||
let Some(hash) = self.latest_relay_chain_hash else {
|
||||
error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to start bootnode discovery: no relay chain hash available. This is a bug.",
|
||||
);
|
||||
// This is a graceful panic via the failure of essential task.
|
||||
return Err(RelayChainError::GenericError("no relay chain hash available".to_string()));
|
||||
};
|
||||
|
||||
let current_epoch = self.current_epoch(hash).await?;
|
||||
let current_epoch_key = self.epoch_key(current_epoch.randomness);
|
||||
self.key_being_discovered = Some(current_epoch_key.clone());
|
||||
self.relay_chain_network.get_providers(current_epoch_key.clone());
|
||||
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Started discovery of teyrchain bootnode providers for current epoch key {}",
|
||||
hex::encode(current_epoch_key),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Schedule bootnode discovery if needed. Returns `false` if the discovery event loop should be
|
||||
/// terminated.
|
||||
fn maybe_retry_discovery(&mut self) -> bool {
|
||||
let discovery_in_progress = self.key_being_discovered.is_some() ||
|
||||
!self.pending_responses.is_empty() ||
|
||||
!self.find_node_queries.is_empty();
|
||||
let discovery_scheduled = !self.pending_start_discovery.is_terminated();
|
||||
|
||||
if discovery_in_progress || discovery_scheduled {
|
||||
// Discovery is already in progress or scheduled, just continue the event loop.
|
||||
true
|
||||
} else {
|
||||
if self.succeeded {
|
||||
// No need to start discovery again if the previous attempt succeeded.
|
||||
info!(
|
||||
target: LOG_TARGET,
|
||||
"Teyrchain bootnode discovery on the relay chain DHT succeeded",
|
||||
);
|
||||
|
||||
false
|
||||
} else {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Retrying teyrchain bootnode discovery on the relay chain DHT in {RETRY_DELAY:?}",
|
||||
);
|
||||
self.pending_start_discovery = Box::pin(sleep(RETRY_DELAY).fuse());
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn request_bootnode(&mut self, peer_id: PeerId) {
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
"Requesting teyrchain bootnode from the relay chain {peer_id:?}",
|
||||
);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
self.relay_chain_network.start_request(
|
||||
peer_id,
|
||||
self.paranode_protocol_name.clone(),
|
||||
self.para_id_scale_compact.clone(),
|
||||
None,
|
||||
tx,
|
||||
IfDisconnected::TryConnect,
|
||||
);
|
||||
|
||||
self.pending_responses.push(async move { (peer_id, rx.await) }.boxed());
|
||||
}
|
||||
|
||||
fn handle_providers(&mut self, providers: Vec<PeerId>) {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Found teyrchain bootnode providers on the relay chain: {providers:?}",
|
||||
);
|
||||
|
||||
for peer_id in providers {
|
||||
if peer_id == self.relay_chain_network.local_peer_id() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// libp2p may yield the same provider multiple times; skip if we alredy queried it.
|
||||
if self.direct_requests.contains(&peer_id) || self.find_node_queries.contains(&peer_id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Directly request a bootnode from the peer without performing a `FIND_NODE` query
|
||||
// first. With litep2p backend this will likely succeed, because cached provider
|
||||
// addresses are automatically added to the transport manager known addresses list.
|
||||
//
|
||||
// With libp2p backend, or if the remote did not return the cached addresses of the
|
||||
// provider, the request will fail and we will perform a `FIND_NODE` query.
|
||||
self.direct_requests.insert(peer_id);
|
||||
self.request_bootnode(peer_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
res: Result<Result<(Vec<u8>, ProtocolName), RequestFailure>, oneshot::Canceled>,
|
||||
) {
|
||||
let direct_request = self.direct_requests.remove(&peer_id);
|
||||
|
||||
let response = match res {
|
||||
Ok(Ok((payload, _))) => match Response::decode(payload.as_slice()) {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to decode teyrchain bootnode response from {peer_id:?}: {e}",
|
||||
);
|
||||
return;
|
||||
},
|
||||
},
|
||||
Ok(Err(e)) => {
|
||||
if direct_request {
|
||||
// It only makes sense to try to find the node on the DHT in case of "address
|
||||
// not available" error. Unfortunately, libp2p and litep2p backends report such
|
||||
// errors differently, and also some network library could break the error
|
||||
// reporting in the future. So, to be on the safe side and avoid subtle bugs,
|
||||
// we always try to find the node on the DHT in case of the request failure.
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to directly query teyrchain bootnode from {peer_id:?}: {e}. \
|
||||
Starting FIND_NODE query on the DHT",
|
||||
);
|
||||
self.find_node_queries.insert(peer_id);
|
||||
self.relay_chain_network.find_closest_peers(peer_id);
|
||||
} else {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to query teyrchain bootnode from {peer_id:?} after finding
|
||||
the node addresses on the DHT: {e}",
|
||||
);
|
||||
}
|
||||
return;
|
||||
},
|
||||
Err(_) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Teyrchain bootnode request to {peer_id:?} canceled. \
|
||||
The node is likely terminating.",
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
match (response.genesis_hash, response.fork_id) {
|
||||
(genesis_hash, fork_id)
|
||||
if genesis_hash == self.teyrchain_genesis_hash &&
|
||||
fork_id == self.teyrchain_fork_id => {},
|
||||
(genesis_hash, fork_id) => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Received invalid teyrchain bootnode response from {peer_id:?}: \
|
||||
genesis hash {}, fork ID {:?} don't match expected genesis hash {}, fork ID {:?}",
|
||||
hex::encode(genesis_hash),
|
||||
fork_id,
|
||||
hex::encode(&self.teyrchain_genesis_hash),
|
||||
self.teyrchain_fork_id,
|
||||
);
|
||||
return;
|
||||
},
|
||||
}
|
||||
|
||||
let paranode_peer_id = match PeerId::from_bytes(response.peer_id.as_slice()) {
|
||||
Ok(peer_id) => peer_id,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to decode teyrchain peer ID in response from {peer_id:?}: {e}",
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
if paranode_peer_id == self.teyrchain_network.local_peer_id() {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Received own teyrchain node peer ID in bootnode response from {peer_id:?}. \
|
||||
This should not happen as we don't request teyrchain bootnodes from self.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let paranode_addresses = response
|
||||
.addrs
|
||||
.into_iter()
|
||||
.map(Multiaddr::try_from)
|
||||
.take(MAX_ADDRESSES)
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
let paranode_addresses = match paranode_addresses {
|
||||
Ok(paranode_addresses) => paranode_addresses,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to decode teyrchain node addresses in response from {peer_id:?}: {e}",
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Discovered teyrchain bootnode {paranode_peer_id:?} with addresses {paranode_addresses:?}",
|
||||
);
|
||||
|
||||
paranode_addresses.into_iter().for_each(|addr| {
|
||||
self.teyrchain_network.add_known_address(paranode_peer_id, addr);
|
||||
self.succeeded = true;
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_dht_event(&mut self, event: DhtEvent) {
|
||||
match event {
|
||||
DhtEvent::ProvidersFound(key, providers)
|
||||
// libp2p generates empty events, so also check if `providers` are not empty.
|
||||
if Some(key.clone()) == self.key_being_discovered && !providers.is_empty() =>
|
||||
self.handle_providers(providers),
|
||||
DhtEvent::NoMoreProviders(key) if Some(key.clone()) == self.key_being_discovered => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Teyrchain bootnode providers discovery finished for key {}",
|
||||
hex::encode(key),
|
||||
);
|
||||
self.key_being_discovered = None;
|
||||
},
|
||||
DhtEvent::ProvidersNotFound(key) if Some(key.clone()) == self.key_being_discovered => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Teyrchain bootnode providers not found for key {}",
|
||||
hex::encode(key),
|
||||
);
|
||||
self.key_being_discovered = None;
|
||||
},
|
||||
DhtEvent::ClosestPeersFound(peer_id, peers) if self.find_node_queries.remove(&peer_id) => {
|
||||
if let Some((_, addrs)) = peers
|
||||
.into_iter()
|
||||
.find(|(peer, addrs)| peer == &peer_id && !addrs.is_empty())
|
||||
{
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
"Found addresses on the DHT for teyrchain bootnode provider {peer_id:?}: {addrs:?}",
|
||||
);
|
||||
for address in addrs {
|
||||
self.relay_chain_network.add_known_address(peer_id, address);
|
||||
}
|
||||
self.request_bootnode(peer_id);
|
||||
} else {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to find addresses on the DHT for teyrchain bootnode provider {peer_id:?}",
|
||||
);
|
||||
}
|
||||
},
|
||||
DhtEvent::ClosestPeersNotFound(peer_id) if self.find_node_queries.remove(&peer_id) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to find addresses on the DHT for teyrchain bootnode provider {peer_id:?}",
|
||||
);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the bootnode discovery service.
|
||||
pub async fn run(mut self) -> RelayChainResult<()> {
|
||||
let mut import_notification_stream =
|
||||
self.relay_chain_interface.import_notification_stream().await?.fuse();
|
||||
let dht_event_stream = self
|
||||
.relay_chain_network
|
||||
.event_stream("teyrchain-bootnode-discovery")
|
||||
.filter_map(|e| async move {
|
||||
match e {
|
||||
Event::Dht(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.fuse();
|
||||
pin_mut!(dht_event_stream);
|
||||
|
||||
// Make sure the relay chain hash is always available before starting the discovery.
|
||||
let header = import_notification_stream.select_next_some().await;
|
||||
self.latest_relay_chain_hash = Some(header.hash());
|
||||
|
||||
loop {
|
||||
if !self.maybe_retry_discovery() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
_ = &mut self.pending_start_discovery => {
|
||||
self.start_discovery().await?;
|
||||
},
|
||||
header = import_notification_stream.select_next_some() => {
|
||||
self.latest_relay_chain_hash = Some(header.hash());
|
||||
},
|
||||
event = dht_event_stream.select_next_some() => self.handle_dht_event(event),
|
||||
(peer_id, res) = self.pending_responses.select_next_some(),
|
||||
if !self.pending_responses.is_empty() =>
|
||||
self.handle_response(peer_id, res),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// Pezcumulus 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.
|
||||
|
||||
// Pezcumulus 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 Pezcumulus. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Teyrchain bootnodes advertisement and discovery service.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod advertisement;
|
||||
mod config;
|
||||
mod discovery;
|
||||
mod task;
|
||||
|
||||
mod schema {
|
||||
include!(concat!(env!("OUT_DIR"), "/response.rs"));
|
||||
}
|
||||
|
||||
pub use config::bootnode_request_response_config;
|
||||
pub use task::{start_bootnode_tasks, StartBootnodeTasksParams};
|
||||
@@ -0,0 +1,20 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package response;
|
||||
|
||||
message Response {
|
||||
// Peer ID of the node on the parachain side.
|
||||
required bytes peer_id = 1;
|
||||
|
||||
// Multiaddresses of the parachain side of the node. The list and format are the same as for
|
||||
// the `listenAddrs` field of the `identify` protocol.
|
||||
repeated bytes addrs = 2;
|
||||
|
||||
// Genesis hash of the parachain. Used to determine the name of the networking protocol
|
||||
// to connect to the parachain. Untrusted.
|
||||
required bytes genesis_hash = 3;
|
||||
|
||||
// So-called "fork ID" of the parachain. Used to determine the name of the networking protocol
|
||||
// to connect to the parachain. Untrusted.
|
||||
optional string fork_id = 4;
|
||||
};
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// Pezcumulus 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.
|
||||
|
||||
// Pezcumulus 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 Pezcumulus. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Teyrchain bootnodes advertisement and discovery service.
|
||||
|
||||
use crate::{
|
||||
advertisement::{BootnodeAdvertisement, BootnodeAdvertisementParams},
|
||||
config::paranode_protocol_name,
|
||||
discovery::{BootnodeDiscovery, BootnodeDiscoveryParams},
|
||||
};
|
||||
use cumulus_primitives_core::{relay_chain::BlockId, ParaId};
|
||||
use cumulus_relay_chain_interface::RelayChainInterface;
|
||||
use log::{debug, error};
|
||||
use num_traits::Zero;
|
||||
use pezsc_network::{request_responses::IncomingRequest, service::traits::NetworkService, Multiaddr};
|
||||
use pezsc_service::TaskManager;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Log target for this crate.
|
||||
const LOG_TARGET: &str = "bootnodes";
|
||||
|
||||
/// Bootnode advertisement task params.
|
||||
pub struct StartBootnodeTasksParams<'a> {
|
||||
/// Enable embedded DHT bootnode.
|
||||
pub embedded_dht_bootnode: bool,
|
||||
/// Enable DHT bootnode discovery.
|
||||
pub dht_bootnode_discovery: bool,
|
||||
/// Teyrchain ID.
|
||||
pub para_id: ParaId,
|
||||
/// Task manager.
|
||||
pub task_manager: &'a mut TaskManager,
|
||||
/// Relay chain interface.
|
||||
pub relay_chain_interface: Arc<dyn RelayChainInterface>,
|
||||
/// Relay chain fork ID.
|
||||
pub relay_chain_fork_id: Option<String>,
|
||||
/// Relay chain network service.
|
||||
pub relay_chain_network: Arc<dyn NetworkService>,
|
||||
/// `/paranode` protocol request receiver.
|
||||
pub request_receiver: async_channel::Receiver<IncomingRequest>,
|
||||
/// Teyrchain node network service.
|
||||
pub teyrchain_network: Arc<dyn NetworkService>,
|
||||
/// Whether to advertise non-global IP addresses.
|
||||
pub advertise_non_global_ips: bool,
|
||||
/// Teyrchain genesis hash.
|
||||
pub teyrchain_genesis_hash: Vec<u8>,
|
||||
/// Teyrchain fork ID.
|
||||
pub teyrchain_fork_id: Option<String>,
|
||||
/// Teyrchain public addresses provided by the operator.
|
||||
pub teyrchain_public_addresses: Vec<Multiaddr>,
|
||||
}
|
||||
|
||||
async fn bootnode_advertisement(
|
||||
para_id: ParaId,
|
||||
relay_chain_interface: Arc<dyn RelayChainInterface>,
|
||||
relay_chain_network: Arc<dyn NetworkService>,
|
||||
request_receiver: async_channel::Receiver<IncomingRequest>,
|
||||
teyrchain_network: Arc<dyn NetworkService>,
|
||||
advertise_non_global_ips: bool,
|
||||
teyrchain_genesis_hash: Vec<u8>,
|
||||
teyrchain_fork_id: Option<String>,
|
||||
public_addresses: Vec<Multiaddr>,
|
||||
) {
|
||||
let bootnode_advertisement = BootnodeAdvertisement::new(BootnodeAdvertisementParams {
|
||||
para_id,
|
||||
relay_chain_interface,
|
||||
relay_chain_network,
|
||||
request_receiver,
|
||||
teyrchain_network,
|
||||
advertise_non_global_ips,
|
||||
teyrchain_genesis_hash,
|
||||
teyrchain_fork_id,
|
||||
public_addresses,
|
||||
});
|
||||
|
||||
if let Err(e) = bootnode_advertisement.run().await {
|
||||
error!(target: LOG_TARGET, "Bootnode advertisement terminated with error: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
async fn bootnode_discovery(
|
||||
para_id: ParaId,
|
||||
teyrchain_network: Arc<dyn NetworkService>,
|
||||
teyrchain_genesis_hash: Vec<u8>,
|
||||
teyrchain_fork_id: Option<String>,
|
||||
relay_chain_interface: Arc<dyn RelayChainInterface>,
|
||||
relay_chain_fork_id: Option<String>,
|
||||
relay_chain_network: Arc<dyn NetworkService>,
|
||||
) {
|
||||
let relay_chain_genesis_hash =
|
||||
match relay_chain_interface.header(BlockId::Number(Zero::zero())).await {
|
||||
Ok(Some(header)) => header.hash().as_bytes().to_vec(),
|
||||
Ok(None) => {
|
||||
error!(
|
||||
target: LOG_TARGET,
|
||||
"Bootnode discovery: relay chain genesis hash does not exist",
|
||||
);
|
||||
// Make essential task fail.
|
||||
return;
|
||||
},
|
||||
Err(e) => {
|
||||
error!(
|
||||
target: LOG_TARGET,
|
||||
"Bootnode discovery: failed to obtain relay chain genesis hash: {e}",
|
||||
);
|
||||
// Make essential task fail.
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let paranode_protocol_name =
|
||||
paranode_protocol_name(relay_chain_genesis_hash, relay_chain_fork_id.as_deref());
|
||||
|
||||
let bootnode_discovery = BootnodeDiscovery::new(BootnodeDiscoveryParams {
|
||||
para_id,
|
||||
teyrchain_network,
|
||||
teyrchain_genesis_hash,
|
||||
teyrchain_fork_id,
|
||||
relay_chain_interface,
|
||||
relay_chain_network,
|
||||
paranode_protocol_name,
|
||||
});
|
||||
|
||||
match bootnode_discovery.run().await {
|
||||
// Do not terminate the essentil task if bootnode discovery succeeded.
|
||||
Ok(()) => std::future::pending().await,
|
||||
Err(e) => error!(target: LOG_TARGET, "Bootnode discovery terminated with error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start teyrchain bootnode advertisement and discovery tasks.
|
||||
pub fn start_bootnode_tasks(
|
||||
StartBootnodeTasksParams {
|
||||
embedded_dht_bootnode,
|
||||
dht_bootnode_discovery,
|
||||
para_id,
|
||||
task_manager,
|
||||
relay_chain_interface,
|
||||
relay_chain_fork_id,
|
||||
relay_chain_network,
|
||||
request_receiver,
|
||||
teyrchain_network,
|
||||
advertise_non_global_ips,
|
||||
teyrchain_genesis_hash,
|
||||
teyrchain_fork_id,
|
||||
teyrchain_public_addresses,
|
||||
}: StartBootnodeTasksParams,
|
||||
) {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Embedded DHT bootnode enabled: {embedded_dht_bootnode}; \
|
||||
DHT bootnode discovery enabled: {dht_bootnode_discovery}",
|
||||
);
|
||||
|
||||
if embedded_dht_bootnode {
|
||||
task_manager.spawn_essential_handle().spawn(
|
||||
"pezcumulus-dht-bootnode-advertisement",
|
||||
None,
|
||||
bootnode_advertisement(
|
||||
para_id,
|
||||
relay_chain_interface.clone(),
|
||||
relay_chain_network.clone(),
|
||||
request_receiver,
|
||||
teyrchain_network.clone(),
|
||||
advertise_non_global_ips,
|
||||
teyrchain_genesis_hash.clone(),
|
||||
teyrchain_fork_id.clone(),
|
||||
teyrchain_public_addresses,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if dht_bootnode_discovery {
|
||||
task_manager.spawn_essential_handle().spawn(
|
||||
"pezcumulus-dht-bootnode-discovery",
|
||||
None,
|
||||
bootnode_discovery(
|
||||
para_id,
|
||||
teyrchain_network,
|
||||
teyrchain_genesis_hash,
|
||||
teyrchain_fork_id,
|
||||
relay_chain_interface,
|
||||
relay_chain_fork_id,
|
||||
relay_chain_network,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user