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,65 @@
|
||||
[package]
|
||||
name = "pezsc-authority-discovery"
|
||||
version = "0.34.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
build = "build.rs"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Bizinikiwi authority discovery."
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
codec = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
futures-timer = { workspace = true }
|
||||
ip_network = { workspace = true }
|
||||
linked_hash_set = { workspace = true }
|
||||
log = { workspace = true, default-features = true }
|
||||
prometheus-endpoint = { workspace = true, default-features = true }
|
||||
prost = { workspace = true }
|
||||
rand = { workspace = true, default-features = true }
|
||||
pezsc-client-api = { workspace = true, default-features = true }
|
||||
pezsc-network = { workspace = true, default-features = true }
|
||||
pezsc-network-types = { workspace = true, default-features = true }
|
||||
pezsc-service.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
pezsp-api = { workspace = true, default-features = true }
|
||||
pezsp-authority-discovery = { workspace = true, default-features = true }
|
||||
pezsp-blockchain = { workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-keystore = { workspace = true, default-features = true }
|
||||
pezsp-runtime = { workspace = true, default-features = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
hex.workspace = true
|
||||
quickcheck = { workspace = true }
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
bizinikiwi-test-runtime-client = { workspace = true }
|
||||
tempfile.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = { workspace = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"pezsc-client-api/runtime-benchmarks",
|
||||
"pezsc-network/runtime-benchmarks",
|
||||
"pezsc-service/runtime-benchmarks",
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-authority-discovery/runtime-benchmarks",
|
||||
"pezsp-blockchain/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"bizinikiwi-test-runtime-client/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,9 @@
|
||||
# Bizinikiwi authority discovery
|
||||
|
||||
This crate enables Bizinikiwi authorities to discover and directly connect to
|
||||
other authorities. It is split into two components the [`Worker`] and the
|
||||
[`Service`].
|
||||
|
||||
See [`Worker`] and [`Service`] for more documentation.
|
||||
|
||||
License: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
@@ -0,0 +1,29 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
fn main() {
|
||||
prost_build::compile_protos(
|
||||
&[
|
||||
"src/worker/schema/dht-v1.proto",
|
||||
"src/worker/schema/dht-v2.proto",
|
||||
"src/worker/schema/dht-v3.proto",
|
||||
],
|
||||
&["src/worker/schema"],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Authority discovery errors.
|
||||
|
||||
/// AuthorityDiscovery Result.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Error type for the authority discovery module.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Error {
|
||||
#[error("Received dht value found event with records with different keys.")]
|
||||
ReceivingDhtValueFoundEventWithDifferentKeys,
|
||||
|
||||
#[error("Received dht value found event with no records.")]
|
||||
ReceivingDhtValueFoundEventWithNoRecords,
|
||||
|
||||
#[error("Failed to verify a dht payload with the given signature.")]
|
||||
VerifyingDhtPayload,
|
||||
|
||||
#[error("Failed to hash the authority id to be used as a dht key.")]
|
||||
HashingAuthorityId(#[from] pezsc_network_types::multihash::Error),
|
||||
|
||||
#[error("Failed calling into the Bizinikiwi runtime: {0}")]
|
||||
CallingRuntime(#[from] pezsp_blockchain::Error),
|
||||
|
||||
#[error("Received a dht record with a key that does not match any in-flight awaited keys.")]
|
||||
ReceivingUnexpectedRecord,
|
||||
|
||||
#[error("Failed to encode a protobuf payload.")]
|
||||
EncodingProto(#[from] prost::EncodeError),
|
||||
|
||||
#[error("Failed to decode a protobuf payload.")]
|
||||
DecodingProto(#[from] prost::DecodeError),
|
||||
|
||||
#[error("Failed to encode or decode scale payload.")]
|
||||
EncodingDecodingScale(#[from] codec::Error),
|
||||
|
||||
#[error("Failed to encode or decode AddrCache.")]
|
||||
EncodingDecodingAddrCache(String),
|
||||
|
||||
#[error("Failed to parse a libp2p multi address.")]
|
||||
ParsingMultiaddress(#[from] pezsc_network::multiaddr::ParseError),
|
||||
|
||||
#[error("Failed to parse a libp2p key: {0}")]
|
||||
ParsingLibp2pIdentity(String),
|
||||
|
||||
#[error("Failed to sign: {0}.")]
|
||||
CannotSign(String),
|
||||
|
||||
#[error("Failed to register Prometheus metric.")]
|
||||
Prometheus(#[from] prometheus_endpoint::PrometheusError),
|
||||
|
||||
#[error("Received authority record that contains addresses with multiple peer ids")]
|
||||
ReceivingDhtValueFoundEventWithDifferentPeerIds,
|
||||
|
||||
#[error("Received authority record without any addresses having a peer id")]
|
||||
ReceivingDhtValueFoundEventWithNoPeerIds,
|
||||
|
||||
#[error("Received authority record without a valid signature for the remote peer id.")]
|
||||
MissingPeerIdSignature,
|
||||
|
||||
#[error("Unable to fetch best block.")]
|
||||
BestBlockFetchingError,
|
||||
|
||||
#[error("Publisher not present.")]
|
||||
MissingPublisher,
|
||||
|
||||
#[error("Unknown authority.")]
|
||||
UnknownAuthority,
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use futures::{future::FutureExt, ready, stream::Stream};
|
||||
use futures_timer::Delay;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// Exponentially increasing interval
|
||||
///
|
||||
/// Doubles interval duration on each tick until the configured maximum is reached.
|
||||
pub struct ExpIncInterval {
|
||||
start: Duration,
|
||||
max: Duration,
|
||||
next: Duration,
|
||||
delay: Delay,
|
||||
}
|
||||
|
||||
impl ExpIncInterval {
|
||||
/// Create a new [`ExpIncInterval`].
|
||||
pub fn new(start: Duration, max: Duration) -> Self {
|
||||
let delay = Delay::new(start);
|
||||
Self { start, max, next: start * 2, delay }
|
||||
}
|
||||
|
||||
/// Fast forward the exponentially increasing interval to the configured maximum, if not already
|
||||
/// set.
|
||||
pub fn set_to_max(&mut self) {
|
||||
if self.next == self.max {
|
||||
return;
|
||||
}
|
||||
|
||||
self.next = self.max;
|
||||
self.delay = Delay::new(self.next);
|
||||
}
|
||||
|
||||
/// Rewind the exponentially increasing interval to the configured start, if not already set.
|
||||
pub fn set_to_start(&mut self) {
|
||||
if self.next == self.start * 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.next = self.start * 2;
|
||||
self.delay = Delay::new(self.start);
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for ExpIncInterval {
|
||||
type Item = ();
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
ready!(self.delay.poll_unpin(cx));
|
||||
self.delay = Delay::new(self.next);
|
||||
self.next = std::cmp::min(self.max, self.next * 2);
|
||||
|
||||
Poll::Ready(Some(()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
//! Bizinikiwi authority discovery.
|
||||
//!
|
||||
//! This crate enables Bizinikiwi authorities to discover and directly connect to
|
||||
//! other authorities. It is split into two components the [`Worker`] and the
|
||||
//! [`Service`].
|
||||
//!
|
||||
//! See [`Worker`] and [`Service`] for more documentation.
|
||||
|
||||
pub use crate::{
|
||||
error::Error,
|
||||
service::Service,
|
||||
worker::{AuthorityDiscovery, NetworkProvider, Role, Worker},
|
||||
};
|
||||
|
||||
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
Stream,
|
||||
};
|
||||
|
||||
use pezsc_network::{event::DhtEvent, Multiaddr};
|
||||
use pezsc_network_types::PeerId;
|
||||
use pezsp_authority_discovery::AuthorityId;
|
||||
use pezsp_blockchain::HeaderBackend;
|
||||
use pezsp_core::traits::SpawnNamed;
|
||||
use pezsp_runtime::traits::Block as BlockT;
|
||||
mod error;
|
||||
mod interval;
|
||||
mod service;
|
||||
mod worker;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Configuration of [`Worker`].
|
||||
pub struct WorkerConfig {
|
||||
/// The maximum interval in which the node will publish its own address on the DHT.
|
||||
///
|
||||
/// By default this is set to 1 hour.
|
||||
pub max_publish_interval: Duration,
|
||||
|
||||
/// Interval at which the keystore is queried. If the keys have changed, unconditionally
|
||||
/// re-publish its addresses on the DHT.
|
||||
///
|
||||
/// By default this is set to 1 minute.
|
||||
pub keystore_refresh_interval: Duration,
|
||||
|
||||
/// The maximum interval in which the node will query the DHT for new entries.
|
||||
///
|
||||
/// By default this is set to 10 minutes.
|
||||
pub max_query_interval: Duration,
|
||||
|
||||
/// If `false`, the node won't publish on the DHT multiaddresses that contain non-global
|
||||
/// IP addresses (such as 10.0.0.1).
|
||||
///
|
||||
/// Recommended: `false` for live chains, and `true` for local chains or for testing.
|
||||
///
|
||||
/// Defaults to `true` to avoid the surprise factor.
|
||||
pub publish_non_global_ips: bool,
|
||||
|
||||
/// Public addresses set by the node operator to always publish first in the authority
|
||||
/// discovery DHT record.
|
||||
pub public_addresses: Vec<Multiaddr>,
|
||||
|
||||
/// Reject authority discovery records that are not signed by their network identity (PeerId)
|
||||
///
|
||||
/// Defaults to `false` to provide compatibility with old versions
|
||||
pub strict_record_validation: bool,
|
||||
|
||||
/// The directory of where the persisted AddrCache file is located,
|
||||
/// optional since NetworkConfiguration's `net_config_path` field
|
||||
/// is optional. If None, we won't persist the AddrCache at all.
|
||||
pub persisted_cache_directory: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for WorkerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// Kademlia's default time-to-live for Dht records is 36h, republishing records every
|
||||
// 24h through libp2p-kad. Given that a node could restart at any point in time, one can
|
||||
// not depend on the republishing process, thus publishing own external addresses should
|
||||
// happen on an interval < 36h.
|
||||
max_publish_interval: Duration::from_secs(1 * 60 * 60),
|
||||
keystore_refresh_interval: Duration::from_secs(60),
|
||||
// External addresses of remote authorities can change at any given point in time. The
|
||||
// interval on which to trigger new queries for the current and next authorities is a
|
||||
// trade off between efficiency and performance.
|
||||
//
|
||||
// Querying 700 [`AuthorityId`]s takes ~8m on the Kusama DHT (16th Nov 2020) when
|
||||
// comparing `authority_discovery_authority_addresses_requested_total` and
|
||||
// `authority_discovery_dht_event_received`.
|
||||
max_query_interval: Duration::from_secs(10 * 60),
|
||||
publish_non_global_ips: true,
|
||||
public_addresses: Vec::new(),
|
||||
strict_record_validation: false,
|
||||
persisted_cache_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new authority discovery [`Worker`] and [`Service`].
|
||||
///
|
||||
/// See the struct documentation of each for more details.
|
||||
pub fn new_worker_and_service<Client, Block, DhtEventStream>(
|
||||
client: Arc<Client>,
|
||||
network: Arc<dyn NetworkProvider>,
|
||||
dht_event_rx: DhtEventStream,
|
||||
role: Role,
|
||||
prometheus_registry: Option<prometheus_endpoint::Registry>,
|
||||
spawner: impl SpawnNamed + 'static,
|
||||
) -> (Worker<Client, Block, DhtEventStream>, Service)
|
||||
where
|
||||
Block: BlockT + Unpin + 'static,
|
||||
Client: AuthorityDiscovery<Block> + Send + Sync + 'static + HeaderBackend<Block>,
|
||||
DhtEventStream: Stream<Item = DhtEvent> + Unpin,
|
||||
{
|
||||
new_worker_and_service_with_config(
|
||||
Default::default(),
|
||||
client,
|
||||
network,
|
||||
dht_event_rx,
|
||||
role,
|
||||
prometheus_registry,
|
||||
spawner,
|
||||
)
|
||||
}
|
||||
|
||||
/// Same as [`new_worker_and_service`] but with support for providing the `config`.
|
||||
///
|
||||
/// When in doubt use [`new_worker_and_service`] as it will use the default configuration.
|
||||
pub fn new_worker_and_service_with_config<Client, Block, DhtEventStream>(
|
||||
config: WorkerConfig,
|
||||
client: Arc<Client>,
|
||||
network: Arc<dyn NetworkProvider>,
|
||||
dht_event_rx: DhtEventStream,
|
||||
role: Role,
|
||||
prometheus_registry: Option<prometheus_endpoint::Registry>,
|
||||
spawner: impl SpawnNamed + 'static,
|
||||
) -> (Worker<Client, Block, DhtEventStream>, Service)
|
||||
where
|
||||
Block: BlockT + Unpin + 'static,
|
||||
Client: AuthorityDiscovery<Block> + 'static,
|
||||
DhtEventStream: Stream<Item = DhtEvent> + Unpin,
|
||||
{
|
||||
let (to_worker, from_service) = mpsc::channel(0);
|
||||
|
||||
let worker = Worker::new(
|
||||
from_service,
|
||||
client,
|
||||
network,
|
||||
dht_event_rx,
|
||||
role,
|
||||
prometheus_registry,
|
||||
config,
|
||||
spawner,
|
||||
);
|
||||
let service = Service::new(to_worker);
|
||||
|
||||
(worker, service)
|
||||
}
|
||||
|
||||
/// Message send from the [`Service`] to the [`Worker`].
|
||||
pub(crate) enum ServicetoWorkerMsg {
|
||||
/// See [`Service::get_addresses_by_authority_id`].
|
||||
GetAddressesByAuthorityId(AuthorityId, oneshot::Sender<Option<HashSet<Multiaddr>>>),
|
||||
/// See [`Service::get_authority_ids_by_peer_id`].
|
||||
GetAuthorityIdsByPeerId(PeerId, oneshot::Sender<Option<HashSet<AuthorityId>>>),
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{collections::HashSet, fmt::Debug};
|
||||
|
||||
use crate::ServicetoWorkerMsg;
|
||||
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
SinkExt,
|
||||
};
|
||||
|
||||
use pezsc_network::Multiaddr;
|
||||
use pezsc_network_types::PeerId;
|
||||
use pezsp_authority_discovery::AuthorityId;
|
||||
|
||||
/// Service to interact with the [`crate::Worker`].
|
||||
#[derive(Clone)]
|
||||
pub struct Service {
|
||||
to_worker: mpsc::Sender<ServicetoWorkerMsg>,
|
||||
}
|
||||
|
||||
impl Debug for Service {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("AuthorityDiscoveryService").finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Service`] allows to interact with a [`crate::Worker`], e.g. by querying the
|
||||
/// [`crate::Worker`]'s local address cache for a given [`AuthorityId`].
|
||||
impl Service {
|
||||
pub(crate) fn new(to_worker: mpsc::Sender<ServicetoWorkerMsg>) -> Self {
|
||||
Self { to_worker }
|
||||
}
|
||||
|
||||
/// Get the addresses for the given [`AuthorityId`] from the local address
|
||||
/// cache.
|
||||
///
|
||||
/// Returns `None` if no entry was present or connection to the
|
||||
/// [`crate::Worker`] failed.
|
||||
///
|
||||
/// Note: [`Multiaddr`]s returned always include a [`PeerId`] via a
|
||||
/// [`pezsc_network_types::multiaddr::Protocol::P2p`] component. Equality of
|
||||
/// [`PeerId`]s across [`Multiaddr`]s returned by a single call is not
|
||||
/// enforced today, given that there are still authorities out there
|
||||
/// publishing the addresses of their sentry nodes on the DHT. In the future
|
||||
/// this guarantee can be provided.
|
||||
pub async fn get_addresses_by_authority_id(
|
||||
&mut self,
|
||||
authority: AuthorityId,
|
||||
) -> Option<HashSet<Multiaddr>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
self.to_worker
|
||||
.send(ServicetoWorkerMsg::GetAddressesByAuthorityId(authority, tx))
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
rx.await.ok().flatten()
|
||||
}
|
||||
|
||||
/// Get the [`AuthorityId`] for the given [`PeerId`] from the local address
|
||||
/// cache.
|
||||
///
|
||||
/// Returns `None` if no entry was present or connection to the
|
||||
/// [`crate::Worker`] failed.
|
||||
pub async fn get_authority_ids_by_peer_id(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
) -> Option<HashSet<AuthorityId>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
self.to_worker
|
||||
.send(ServicetoWorkerMsg::GetAuthorityIdsByPeerId(peer_id, tx))
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
rx.await.ok().flatten()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
new_worker_and_service_with_config,
|
||||
worker::{
|
||||
tests::{TestApi, TestNetwork},
|
||||
AddrCache, Role,
|
||||
},
|
||||
WorkerConfig,
|
||||
};
|
||||
|
||||
use futures::{channel::mpsc::channel, executor::LocalPool, task::LocalSpawn};
|
||||
use pezsc_network_types::ed25519;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use pezsc_network::{multiaddr::Protocol, Multiaddr, PeerId};
|
||||
use pezsp_authority_discovery::AuthorityId;
|
||||
use pezsp_core::{crypto::key_types, testing::TaskExecutor, traits::SpawnNamed};
|
||||
use pezsp_keystore::{testing::MemoryKeystore, Keystore};
|
||||
|
||||
pub(super) fn create_spawner() -> Box<dyn SpawnNamed> {
|
||||
Box::new(TaskExecutor::new())
|
||||
}
|
||||
|
||||
pub(super) fn test_config(path_buf: Option<std::path::PathBuf>) -> WorkerConfig {
|
||||
WorkerConfig { persisted_cache_directory: path_buf, ..Default::default() }
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_addresses_and_authority_id() {
|
||||
let (_dht_event_tx, dht_event_rx) = channel(0);
|
||||
let network: Arc<TestNetwork> = Arc::new(Default::default());
|
||||
|
||||
let mut pool = LocalPool::new();
|
||||
|
||||
let key_store = MemoryKeystore::new();
|
||||
|
||||
let remote_authority_id: AuthorityId = pool.run_until(async {
|
||||
key_store
|
||||
.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)
|
||||
.unwrap()
|
||||
.into()
|
||||
});
|
||||
|
||||
let remote_peer_id = PeerId::random();
|
||||
let remote_addr = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333"
|
||||
.parse::<Multiaddr>()
|
||||
.unwrap()
|
||||
.with(Protocol::P2p(remote_peer_id.into()));
|
||||
|
||||
let test_api = Arc::new(TestApi { authorities: vec![] });
|
||||
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let path = tempdir.path().to_path_buf();
|
||||
let (mut worker, mut service) = new_worker_and_service_with_config(
|
||||
test_config(Some(path)),
|
||||
test_api,
|
||||
network.clone(),
|
||||
Box::pin(dht_event_rx),
|
||||
Role::PublishAndDiscover(key_store.into()),
|
||||
None,
|
||||
create_spawner(),
|
||||
);
|
||||
worker.inject_addresses(remote_authority_id.clone(), vec![remote_addr.clone()]);
|
||||
|
||||
pool.spawner().spawn_local_obj(Box::pin(worker.run()).into()).unwrap();
|
||||
|
||||
pool.run_until(async {
|
||||
assert_eq!(
|
||||
Some(HashSet::from([remote_addr])),
|
||||
service.get_addresses_by_authority_id(remote_authority_id.clone()).await,
|
||||
);
|
||||
assert_eq!(
|
||||
Some(HashSet::from([remote_authority_id])),
|
||||
service.get_authority_ids_by_peer_id(remote_peer_id.into()).await,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cryptos_are_compatible() {
|
||||
use pezsp_core::crypto::Pair;
|
||||
|
||||
let libp2p_keypair = ed25519::Keypair::generate();
|
||||
let libp2p_public = libp2p_keypair.public();
|
||||
|
||||
let pezsp_core_secret =
|
||||
{ pezsp_core::ed25519::Pair::from_seed_slice(&libp2p_keypair.secret().as_ref()).unwrap() };
|
||||
let pezsp_core_public = pezsp_core_secret.public();
|
||||
|
||||
let message = b"we are more powerful than not to be better";
|
||||
|
||||
let libp2p_signature = libp2p_keypair.sign(message);
|
||||
let pezsp_core_signature = pezsp_core_secret.sign(message); // no error expected...
|
||||
|
||||
assert!(pezsp_core::ed25519::Pair::verify(
|
||||
&pezsp_core::ed25519::Signature::try_from(libp2p_signature.as_slice()).unwrap(),
|
||||
message,
|
||||
&pezsp_core_public
|
||||
));
|
||||
assert!(libp2p_public.verify(message, pezsp_core_signature.as_ref()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn when_addr_cache_is_persisted_with_authority_ids_then_when_worker_is_created_it_loads_the_persisted_cache(
|
||||
) {
|
||||
// ARRANGE
|
||||
let (_dht_event_tx, dht_event_rx) = channel(0);
|
||||
let mut pool = LocalPool::new();
|
||||
let key_store = MemoryKeystore::new();
|
||||
|
||||
let remote_authority_id: AuthorityId = pool.run_until(async {
|
||||
key_store
|
||||
.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)
|
||||
.unwrap()
|
||||
.into()
|
||||
});
|
||||
let remote_peer_id = PeerId::random();
|
||||
let remote_addr = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333"
|
||||
.parse::<Multiaddr>()
|
||||
.unwrap()
|
||||
.with(Protocol::P2p(remote_peer_id.into()));
|
||||
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let cache_path = tempdir.path().to_path_buf();
|
||||
|
||||
// persist the remote_authority_id and remote_addr in the cache
|
||||
{
|
||||
let mut addr_cache = AddrCache::default();
|
||||
addr_cache.insert(remote_authority_id.clone(), vec![remote_addr.clone()]);
|
||||
let path_to_save = cache_path.join(crate::worker::ADDR_CACHE_FILE_NAME);
|
||||
addr_cache.serialize_and_persist(&path_to_save);
|
||||
}
|
||||
|
||||
let (_, from_service) = futures::channel::mpsc::channel(0);
|
||||
|
||||
// ACT
|
||||
// Create a worker with the persisted cache
|
||||
let worker = crate::worker::Worker::new(
|
||||
from_service,
|
||||
Arc::new(TestApi { authorities: vec![] }),
|
||||
Arc::new(TestNetwork::default()),
|
||||
Box::pin(dht_event_rx),
|
||||
Role::PublishAndDiscover(key_store.into()),
|
||||
None,
|
||||
test_config(Some(cache_path)),
|
||||
create_spawner(),
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
assert!(worker.contains_authority(&remote_authority_id));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,675 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::error::Error;
|
||||
use log::{info, warn};
|
||||
use pezsc_network::{multiaddr::Protocol, Multiaddr};
|
||||
use pezsc_network_types::PeerId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use pezsp_authority_discovery::AuthorityId;
|
||||
use pezsp_runtime::DeserializeOwned;
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap, HashSet},
|
||||
fs::File,
|
||||
io::{self, BufReader, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
/// Cache for [`AuthorityId`] -> [`HashSet<Multiaddr>`] and [`PeerId`] -> [`HashSet<AuthorityId>`]
|
||||
/// mappings.
|
||||
#[derive(Default, Clone, PartialEq, Debug)]
|
||||
pub(crate) struct AddrCache {
|
||||
/// The addresses found in `authority_id_to_addresses` are guaranteed to always match
|
||||
/// the peerids found in `peer_id_to_authority_ids`. In other words, these two hashmaps
|
||||
/// are similar to a bi-directional map.
|
||||
///
|
||||
/// Since we may store the mapping across several sessions, a single
|
||||
/// `PeerId` might correspond to multiple `AuthorityId`s. However,
|
||||
/// it's not expected that a single `AuthorityId` can have multiple `PeerId`s.
|
||||
authority_id_to_addresses: HashMap<AuthorityId, HashSet<Multiaddr>>,
|
||||
peer_id_to_authority_ids: HashMap<PeerId, HashSet<AuthorityId>>,
|
||||
}
|
||||
|
||||
impl Serialize for AddrCache {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
SerializeAddrCache::from(self.clone()).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AddrCache {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
SerializeAddrCache::deserialize(deserializer).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// A storage and serialization time optimized version of `AddrCache`
|
||||
/// which contains the bare minimum info to reconstruct the AddrCache. We
|
||||
/// rely on the fact that the `peer_id_to_authority_ids` can be reconstructed from
|
||||
/// the `authority_id_to_addresses` field.
|
||||
///
|
||||
/// Benchmarks show that this is about 2x faster to serialize and about 4x faster to deserialize
|
||||
/// compared to the full `AddrCache`.
|
||||
///
|
||||
/// Storage wise it is about half the size of the full `AddrCache`.
|
||||
///
|
||||
/// This is used to persist the `AddrCache` to disk and load it back.
|
||||
///
|
||||
/// AddrCache impl of Serialize and Deserialize "piggybacks" on this struct.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SerializeAddrCache {
|
||||
authority_id_to_addresses: HashMap<AuthorityId, HashSet<Multiaddr>>,
|
||||
}
|
||||
|
||||
impl From<SerializeAddrCache> for AddrCache {
|
||||
fn from(value: SerializeAddrCache) -> Self {
|
||||
let mut peer_id_to_authority_ids: HashMap<PeerId, HashSet<AuthorityId>> = HashMap::new();
|
||||
|
||||
for (authority_id, addresses) in &value.authority_id_to_addresses {
|
||||
for peer_id in addresses_to_peer_ids(addresses) {
|
||||
peer_id_to_authority_ids
|
||||
.entry(peer_id)
|
||||
.or_insert_with(HashSet::new)
|
||||
.insert(authority_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
AddrCache {
|
||||
authority_id_to_addresses: value.authority_id_to_addresses,
|
||||
peer_id_to_authority_ids,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<AddrCache> for SerializeAddrCache {
|
||||
fn from(value: AddrCache) -> Self {
|
||||
Self { authority_id_to_addresses: value.authority_id_to_addresses }
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_file(path: impl AsRef<Path>, contents: &str) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let mut file = File::create(path)?;
|
||||
file.write_all(contents.as_bytes())?;
|
||||
file.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl TryFrom<&Path> for AddrCache {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(path: &Path) -> Result<Self, Self::Error> {
|
||||
// Try to load from the cache file if it exists and is valid.
|
||||
load_from_file::<AddrCache>(&path).map_err(|e| {
|
||||
Error::EncodingDecodingAddrCache(format!(
|
||||
"Failed to load AddrCache from file: {}, error: {:?}",
|
||||
path.display(),
|
||||
e
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
impl AddrCache {
|
||||
pub fn new() -> Self {
|
||||
AddrCache::default()
|
||||
}
|
||||
|
||||
fn serialize(&self) -> Option<String> {
|
||||
serde_json::to_string_pretty(self).inspect_err(|e| {
|
||||
warn!(target: super::LOG_TARGET, "Failed to serialize AddrCache to JSON: {} => skip persisting it.", e);
|
||||
}).ok()
|
||||
}
|
||||
|
||||
fn persist(path: impl AsRef<Path>, serialized_cache: String) {
|
||||
match write_to_file(path.as_ref(), &serialized_cache) {
|
||||
Err(err) => {
|
||||
warn!(target: super::LOG_TARGET, "Failed to persist AddrCache on disk at path: {}, error: {}", path.as_ref().display(), err);
|
||||
},
|
||||
Ok(_) => {
|
||||
info!(target: super::LOG_TARGET, "Successfully persisted AddrCache on disk");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_and_persist(&self, path: impl AsRef<Path>) {
|
||||
let Some(serialized) = self.serialize() else { return };
|
||||
Self::persist(path, serialized);
|
||||
}
|
||||
|
||||
/// Inserts the given [`AuthorityId`] and [`Vec<Multiaddr>`] pair for future lookups by
|
||||
/// [`AuthorityId`] or [`PeerId`].
|
||||
pub fn insert(&mut self, authority_id: AuthorityId, addresses: Vec<Multiaddr>) {
|
||||
let addresses = addresses.into_iter().collect::<HashSet<_>>();
|
||||
let peer_ids = addresses_to_peer_ids(&addresses);
|
||||
|
||||
if peer_ids.is_empty() {
|
||||
log::debug!(
|
||||
target: super::LOG_TARGET,
|
||||
"Authority({:?}) provides no addresses or addresses without peer ids. Adresses: {:?}",
|
||||
authority_id,
|
||||
addresses,
|
||||
);
|
||||
return;
|
||||
} else if peer_ids.len() > 1 {
|
||||
log::warn!(
|
||||
target: super::LOG_TARGET,
|
||||
"Authority({:?}) can be reached through multiple peer ids: {:?}",
|
||||
authority_id,
|
||||
peer_ids
|
||||
);
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
target: super::LOG_TARGET,
|
||||
"Found addresses for authority {authority_id:?}: {addresses:?}",
|
||||
);
|
||||
|
||||
let old_addresses = self.authority_id_to_addresses.insert(authority_id.clone(), addresses);
|
||||
let old_peer_ids = addresses_to_peer_ids(&old_addresses.unwrap_or_default());
|
||||
|
||||
// Add the new peer ids
|
||||
peer_ids.difference(&old_peer_ids).for_each(|new_peer_id| {
|
||||
self.peer_id_to_authority_ids
|
||||
.entry(*new_peer_id)
|
||||
.or_default()
|
||||
.insert(authority_id.clone());
|
||||
});
|
||||
|
||||
// Remove the old peer ids
|
||||
self.remove_authority_id_from_peer_ids(&authority_id, old_peer_ids.difference(&peer_ids));
|
||||
}
|
||||
|
||||
/// Remove the given `authority_id` from the `peer_id` to `authority_ids` mapping.
|
||||
///
|
||||
/// If a `peer_id` doesn't have any `authority_id` assigned anymore, it is removed.
|
||||
fn remove_authority_id_from_peer_ids<'a>(
|
||||
&mut self,
|
||||
authority_id: &AuthorityId,
|
||||
peer_ids: impl Iterator<Item = &'a PeerId>,
|
||||
) {
|
||||
peer_ids.for_each(|peer_id| {
|
||||
if let Entry::Occupied(mut e) = self.peer_id_to_authority_ids.entry(*peer_id) {
|
||||
e.get_mut().remove(authority_id);
|
||||
|
||||
// If there are no more entries, remove the peer id.
|
||||
if e.get().is_empty() {
|
||||
e.remove();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the number of authority IDs in the cache.
|
||||
pub fn num_authority_ids(&self) -> usize {
|
||||
self.authority_id_to_addresses.len()
|
||||
}
|
||||
|
||||
/// Returns the addresses for the given [`AuthorityId`].
|
||||
pub fn get_addresses_by_authority_id(
|
||||
&self,
|
||||
authority_id: &AuthorityId,
|
||||
) -> Option<&HashSet<Multiaddr>> {
|
||||
self.authority_id_to_addresses.get(authority_id)
|
||||
}
|
||||
|
||||
/// Returns the [`AuthorityId`]s for the given [`PeerId`].
|
||||
///
|
||||
/// As the authority id can change between sessions, one [`PeerId`] can be mapped to
|
||||
/// multiple authority ids.
|
||||
pub fn get_authority_ids_by_peer_id(&self, peer_id: &PeerId) -> Option<&HashSet<AuthorityId>> {
|
||||
self.peer_id_to_authority_ids.get(peer_id)
|
||||
}
|
||||
|
||||
/// Removes all [`PeerId`]s and [`Multiaddr`]s from the cache that are not related to the given
|
||||
/// [`AuthorityId`]s.
|
||||
pub fn retain_ids(&mut self, authority_ids: &[AuthorityId]) {
|
||||
// The below logic could be replaced by `BtreeMap::drain_filter` once it stabilized.
|
||||
let authority_ids_to_remove = self
|
||||
.authority_id_to_addresses
|
||||
.iter()
|
||||
.filter(|(id, _addresses)| !authority_ids.contains(id))
|
||||
.map(|entry| entry.0)
|
||||
.cloned()
|
||||
.collect::<Vec<AuthorityId>>();
|
||||
|
||||
for authority_id_to_remove in authority_ids_to_remove {
|
||||
// Remove other entries from `self.authority_id_to_addresses`.
|
||||
let addresses = if let Some(addresses) =
|
||||
self.authority_id_to_addresses.remove(&authority_id_to_remove)
|
||||
{
|
||||
addresses
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.remove_authority_id_from_peer_ids(
|
||||
&authority_id_to_remove,
|
||||
addresses_to_peer_ids(&addresses).iter(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn peer_id_from_multiaddr(addr: &Multiaddr) -> Option<PeerId> {
|
||||
addr.iter().last().and_then(|protocol| {
|
||||
if let Protocol::P2p(multihash) = protocol {
|
||||
PeerId::from_multihash(multihash).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn addresses_to_peer_ids(addresses: &HashSet<Multiaddr>) -> HashSet<PeerId> {
|
||||
addresses.iter().filter_map(peer_id_from_multiaddr).collect::<HashSet<_>>()
|
||||
}
|
||||
|
||||
fn load_from_file<T: DeserializeOwned>(path: impl AsRef<Path>) -> io::Result<T> {
|
||||
let file = File::open(path)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
serde_json::from_reader(reader).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::{
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
use quickcheck::{Arbitrary, Gen, QuickCheck, TestResult};
|
||||
use pezsc_network_types::multihash::{Code, Multihash};
|
||||
|
||||
use pezsp_authority_discovery::{AuthorityId, AuthorityPair};
|
||||
use pezsp_core::crypto::Pair;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TestAuthorityId(AuthorityId);
|
||||
|
||||
impl Arbitrary for TestAuthorityId {
|
||||
fn arbitrary(g: &mut Gen) -> Self {
|
||||
let seed = (0..32).map(|_| u8::arbitrary(g)).collect::<Vec<_>>();
|
||||
TestAuthorityId(AuthorityPair::from_seed_slice(&seed).unwrap().public())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TestMultiaddr(Multiaddr);
|
||||
|
||||
impl Arbitrary for TestMultiaddr {
|
||||
fn arbitrary(g: &mut Gen) -> Self {
|
||||
let seed = (0..32).map(|_| u8::arbitrary(g)).collect::<Vec<_>>();
|
||||
let peer_id =
|
||||
PeerId::from_multihash(Multihash::wrap(Code::Sha2_256.into(), &seed).unwrap())
|
||||
.unwrap();
|
||||
let multiaddr = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333"
|
||||
.parse::<Multiaddr>()
|
||||
.unwrap()
|
||||
.with(Protocol::P2p(peer_id.into()));
|
||||
|
||||
TestMultiaddr(multiaddr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TestMultiaddrsSamePeerCombo(Multiaddr, Multiaddr);
|
||||
|
||||
impl Arbitrary for TestMultiaddrsSamePeerCombo {
|
||||
fn arbitrary(g: &mut Gen) -> Self {
|
||||
let seed = (0..32).map(|_| u8::arbitrary(g)).collect::<Vec<_>>();
|
||||
let peer_id =
|
||||
PeerId::from_multihash(Multihash::wrap(Code::Sha2_256.into(), &seed).unwrap())
|
||||
.unwrap();
|
||||
let multiaddr1 = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333"
|
||||
.parse::<Multiaddr>()
|
||||
.unwrap()
|
||||
.with(Protocol::P2p(peer_id.into()));
|
||||
let multiaddr2 = "/ip6/2002:db8:0:0:0:0:0:2/tcp/30133"
|
||||
.parse::<Multiaddr>()
|
||||
.unwrap()
|
||||
.with(Protocol::P2p(peer_id.into()));
|
||||
TestMultiaddrsSamePeerCombo(multiaddr1, multiaddr2)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retains_only_entries_of_provided_authority_ids() {
|
||||
fn property(
|
||||
first: (TestAuthorityId, TestMultiaddr),
|
||||
second: (TestAuthorityId, TestMultiaddr),
|
||||
third: (TestAuthorityId, TestMultiaddr),
|
||||
) -> TestResult {
|
||||
let first: (AuthorityId, Multiaddr) = ((first.0).0, (first.1).0);
|
||||
let second: (AuthorityId, Multiaddr) = ((second.0).0, (second.1).0);
|
||||
let third: (AuthorityId, Multiaddr) = ((third.0).0, (third.1).0);
|
||||
|
||||
let mut cache = AddrCache::new();
|
||||
|
||||
cache.insert(first.0.clone(), vec![first.1.clone()]);
|
||||
cache.insert(second.0.clone(), vec![second.1.clone()]);
|
||||
cache.insert(third.0.clone(), vec![third.1.clone()]);
|
||||
|
||||
assert_eq!(
|
||||
Some(&HashSet::from([third.1.clone()])),
|
||||
cache.get_addresses_by_authority_id(&third.0),
|
||||
"Expect `get_addresses_by_authority_id` to return addresses of third authority.",
|
||||
);
|
||||
assert_eq!(
|
||||
Some(&HashSet::from([third.0.clone()])),
|
||||
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()),
|
||||
"Expect `get_authority_id_by_peer_id` to return `AuthorityId` of third authority.",
|
||||
);
|
||||
|
||||
cache.retain_ids(&vec![first.0.clone(), second.0]);
|
||||
|
||||
assert_eq!(
|
||||
None,
|
||||
cache.get_addresses_by_authority_id(&third.0),
|
||||
"Expect `get_addresses_by_authority_id` to not return `None` for third authority.",
|
||||
);
|
||||
assert_eq!(
|
||||
None,
|
||||
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()),
|
||||
"Expect `get_authority_id_by_peer_id` to return `None` for third authority.",
|
||||
);
|
||||
|
||||
TestResult::passed()
|
||||
}
|
||||
|
||||
QuickCheck::new()
|
||||
.max_tests(10)
|
||||
.quickcheck(property as fn(_, _, _) -> TestResult)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_to_serializable() {
|
||||
let serializable = SerializeAddrCache::from(AddrCache::sample());
|
||||
let roundtripped = AddrCache::from(serializable);
|
||||
assert_eq!(roundtripped, AddrCache::sample())
|
||||
}
|
||||
#[test]
|
||||
fn keeps_consistency_between_authority_id_and_peer_id() {
|
||||
fn property(
|
||||
authority1: TestAuthorityId,
|
||||
authority2: TestAuthorityId,
|
||||
multiaddr1: TestMultiaddr,
|
||||
multiaddr2: TestMultiaddr,
|
||||
multiaddr3: TestMultiaddrsSamePeerCombo,
|
||||
) -> TestResult {
|
||||
let authority1 = authority1.0;
|
||||
let authority2 = authority2.0;
|
||||
let multiaddr1 = multiaddr1.0;
|
||||
let multiaddr2 = multiaddr2.0;
|
||||
let TestMultiaddrsSamePeerCombo(multiaddr3, multiaddr4) = multiaddr3;
|
||||
|
||||
let mut cache = AddrCache::new();
|
||||
|
||||
cache.insert(authority1.clone(), vec![multiaddr1.clone()]);
|
||||
cache.insert(
|
||||
authority1.clone(),
|
||||
vec![multiaddr2.clone(), multiaddr3.clone(), multiaddr4.clone()],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
None,
|
||||
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr1).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
Some(&HashSet::from([authority1.clone()])),
|
||||
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
Some(&HashSet::from([authority1.clone()])),
|
||||
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
Some(&HashSet::from([authority1.clone()])),
|
||||
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr4).unwrap())
|
||||
);
|
||||
|
||||
cache.insert(authority2.clone(), vec![multiaddr2.clone()]);
|
||||
|
||||
assert_eq!(
|
||||
Some(&HashSet::from([authority2.clone(), authority1.clone()])),
|
||||
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
Some(&HashSet::from([authority1.clone()])),
|
||||
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
|
||||
);
|
||||
assert_eq!(cache.get_addresses_by_authority_id(&authority1).unwrap().len(), 3);
|
||||
|
||||
cache.insert(authority2.clone(), vec![multiaddr2.clone(), multiaddr3.clone()]);
|
||||
|
||||
assert_eq!(
|
||||
Some(&HashSet::from([authority2.clone(), authority1.clone()])),
|
||||
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
Some(&HashSet::from([authority2.clone(), authority1.clone()])),
|
||||
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
&HashSet::from([multiaddr2.clone(), multiaddr3.clone(), multiaddr4.clone()]),
|
||||
cache.get_addresses_by_authority_id(&authority1).unwrap(),
|
||||
);
|
||||
|
||||
TestResult::passed()
|
||||
}
|
||||
|
||||
QuickCheck::new()
|
||||
.max_tests(10)
|
||||
.quickcheck(property as fn(_, _, _, _, _) -> TestResult)
|
||||
}
|
||||
|
||||
/// As the runtime gives us the current + next authority ids, it can happen that some
|
||||
/// authority changed its session keys. Changing the sessions keys leads to having two
|
||||
/// authority ids that map to the same `PeerId` & addresses.
|
||||
#[test]
|
||||
fn adding_two_authority_ids_for_the_same_peer_id() {
|
||||
let mut addr_cache = AddrCache::new();
|
||||
|
||||
let peer_id = PeerId::random();
|
||||
let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into()));
|
||||
|
||||
let authority_id0 = AuthorityPair::generate().0.public();
|
||||
let authority_id1 = AuthorityPair::generate().0.public();
|
||||
|
||||
addr_cache.insert(authority_id0.clone(), vec![addr.clone()]);
|
||||
addr_cache.insert(authority_id1.clone(), vec![addr.clone()]);
|
||||
|
||||
assert_eq!(2, addr_cache.num_authority_ids());
|
||||
assert_eq!(
|
||||
&HashSet::from([addr.clone()]),
|
||||
addr_cache.get_addresses_by_authority_id(&authority_id0).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
&HashSet::from([addr]),
|
||||
addr_cache.get_addresses_by_authority_id(&authority_id1).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
impl AddrCache {
|
||||
pub fn sample() -> Self {
|
||||
let mut addr_cache = AddrCache::new();
|
||||
|
||||
let peer_id = PeerId::from_multihash(
|
||||
Multihash::wrap(Code::Sha2_256.into(), &[0xab; 32]).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into()));
|
||||
let authority_id0 = AuthorityPair::from_seed(&[0xaa; 32]).public();
|
||||
let authority_id1 = AuthorityPair::from_seed(&[0xbb; 32]).public();
|
||||
|
||||
addr_cache.insert(authority_id0.clone(), vec![addr.clone()]);
|
||||
addr_cache.insert(authority_id1.clone(), vec![addr.clone()]);
|
||||
addr_cache
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_json() {
|
||||
let sample = || AddrCache::sample();
|
||||
let serializable = AddrCache::from(sample());
|
||||
let json = serde_json::to_string(&serializable).expect("Serialization should not fail");
|
||||
let deserialized = serde_json::from_str::<AddrCache>(&json).unwrap();
|
||||
let from_serializable = AddrCache::try_from(deserialized).unwrap();
|
||||
assert_eq!(sample(), from_serializable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_from_json() {
|
||||
let json = r#"
|
||||
{
|
||||
"authority_id_to_addresses": {
|
||||
"5FjfMGrqw9ck5XZaPVTKm2RE5cbwoVUfXvSGZY7KCUEFtdr7": [
|
||||
"/p2p/QmZtnFaddFtzGNT8BxdHVbQrhSFdq1pWxud5z4fA4kxfDt"
|
||||
],
|
||||
"5DiQDBQvjFkmUF3C8a7ape5rpRPoajmMj44Q9CTGPfVBaa6U": [
|
||||
"/p2p/QmZtnFaddFtzGNT8BxdHVbQrhSFdq1pWxud5z4fA4kxfDt"
|
||||
]
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let deserialized = serde_json::from_str::<AddrCache>(json).unwrap();
|
||||
assert_eq!(deserialized, AddrCache::sample())
|
||||
}
|
||||
|
||||
fn serialize_and_write_to_file<T: Serialize>(
|
||||
path: impl AsRef<Path>,
|
||||
contents: &T,
|
||||
) -> io::Result<()> {
|
||||
let serialized = serde_json::to_string_pretty(contents).unwrap();
|
||||
write_to_file(path, &serialized)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_cache_from_disc() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("cache.json");
|
||||
let sample = AddrCache::sample();
|
||||
assert_eq!(sample.num_authority_ids(), 2);
|
||||
serialize_and_write_to_file(&path, &sample).unwrap();
|
||||
sleep(Duration::from_millis(10)); // Ensure file is written before loading
|
||||
let cache = AddrCache::try_from(path.as_path()).unwrap();
|
||||
assert_eq!(cache.num_authority_ids(), 2);
|
||||
}
|
||||
|
||||
fn create_cache(authority_id_count: u64, multiaddr_per_authority_count: u64) -> AddrCache {
|
||||
let mut addr_cache = AddrCache::new();
|
||||
|
||||
for i in 0..authority_id_count {
|
||||
let seed = &mut [0xab as u8; 32];
|
||||
let i_bytes = i.to_le_bytes();
|
||||
seed[0..8].copy_from_slice(&i_bytes);
|
||||
|
||||
let authority_id = AuthorityPair::from_seed(seed).public();
|
||||
let multi_addresses = (0..multiaddr_per_authority_count)
|
||||
.map(|j| {
|
||||
let mut digest = [0xab; 32];
|
||||
let j_bytes = j.to_le_bytes();
|
||||
digest[0..8].copy_from_slice(&j_bytes);
|
||||
let peer_id = PeerId::from_multihash(
|
||||
Multihash::wrap(Code::Sha2_256.into(), &digest).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
Multiaddr::empty().with(Protocol::P2p(peer_id.into()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(multi_addresses.len(), multiaddr_per_authority_count as usize);
|
||||
addr_cache.insert(authority_id.clone(), multi_addresses);
|
||||
}
|
||||
assert_eq!(addr_cache.authority_id_to_addresses.len(), authority_id_count as usize);
|
||||
|
||||
addr_cache
|
||||
}
|
||||
|
||||
/// This test is ignored by default as it takes a long time to run.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn addr_cache_measure_serde_performance() {
|
||||
let addr_cache = create_cache(1000, 5);
|
||||
|
||||
/// A replica of `AddrCache` that is serializable and deserializable
|
||||
/// without any optimizations.
|
||||
#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct NaiveSerdeAddrCache {
|
||||
authority_id_to_addresses: HashMap<AuthorityId, HashSet<Multiaddr>>,
|
||||
peer_id_to_authority_ids: HashMap<PeerId, HashSet<AuthorityId>>,
|
||||
}
|
||||
impl From<AddrCache> for NaiveSerdeAddrCache {
|
||||
fn from(value: AddrCache) -> Self {
|
||||
Self {
|
||||
authority_id_to_addresses: value.authority_id_to_addresses,
|
||||
peer_id_to_authority_ids: value.peer_id_to_authority_ids,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let naive = NaiveSerdeAddrCache::from(addr_cache.clone());
|
||||
let storage_optimized = addr_cache.clone();
|
||||
|
||||
fn measure_clone<T: Clone>(data: &T) -> Duration {
|
||||
let start = Instant::now();
|
||||
let _ = data.clone();
|
||||
start.elapsed()
|
||||
}
|
||||
fn measure_serialize<T: Serialize>(data: &T) -> (Duration, String) {
|
||||
let start = Instant::now();
|
||||
let json = serde_json::to_string_pretty(data).unwrap();
|
||||
(start.elapsed(), json)
|
||||
}
|
||||
fn measure_deserialize<T: DeserializeOwned>(json: String) -> (Duration, T) {
|
||||
let start = Instant::now();
|
||||
let value = serde_json::from_str(&json).unwrap();
|
||||
(start.elapsed(), value)
|
||||
}
|
||||
|
||||
let serialize_naive = measure_serialize(&naive);
|
||||
let serialize_storage_optimized = measure_serialize(&storage_optimized);
|
||||
println!("CLONE: Naive took: {} ms", measure_clone(&naive).as_millis());
|
||||
println!(
|
||||
"CLONE: Storage optimized took: {} ms",
|
||||
measure_clone(&storage_optimized).as_millis()
|
||||
);
|
||||
println!("SERIALIZE: Naive took: {} ms", serialize_naive.0.as_millis());
|
||||
println!(
|
||||
"SERIALIZE: Storage optimized took: {} ms",
|
||||
serialize_storage_optimized.0.as_millis()
|
||||
);
|
||||
let deserialize_naive = measure_deserialize::<NaiveSerdeAddrCache>(serialize_naive.1);
|
||||
let deserialize_storage_optimized =
|
||||
measure_deserialize::<AddrCache>(serialize_storage_optimized.1);
|
||||
println!("DESERIALIZE: Naive took: {} ms", deserialize_naive.0.as_millis());
|
||||
println!(
|
||||
"DESERIALIZE: Storage optimized took: {} ms",
|
||||
deserialize_storage_optimized.0.as_millis()
|
||||
);
|
||||
assert_eq!(deserialize_naive.1, naive);
|
||||
assert_eq!(deserialize_storage_optimized.1, storage_optimized);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package authority_discovery_v1;
|
||||
|
||||
// First we need to serialize the addresses in order to be able to sign them.
|
||||
message AuthorityAddresses {
|
||||
repeated bytes addresses = 1;
|
||||
}
|
||||
|
||||
// Then we need to serialize addresses and signature to send them over the wire.
|
||||
message SignedAuthorityAddresses {
|
||||
bytes addresses = 1;
|
||||
bytes signature = 2;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package authority_discovery_v2;
|
||||
|
||||
// First we need to serialize the addresses in order to be able to sign them.
|
||||
message AuthorityRecord {
|
||||
// Possibly multiple `MultiAddress`es through which the node can be
|
||||
repeated bytes addresses = 1;
|
||||
}
|
||||
|
||||
message PeerSignature {
|
||||
bytes signature = 1;
|
||||
bytes public_key = 2;
|
||||
}
|
||||
|
||||
// Then we need to serialize the authority record and signature to send them over the wire.
|
||||
message SignedAuthorityRecord {
|
||||
bytes record = 1;
|
||||
bytes auth_signature = 2;
|
||||
// Even if there are multiple `record.addresses`, all of them have the same peer id.
|
||||
// Old versions are missing this field. It is optional in order to provide compatibility both ways.
|
||||
PeerSignature peer_signature = 3;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package authority_discovery_v3;
|
||||
|
||||
// First we need to serialize the addresses in order to be able to sign them.
|
||||
message AuthorityRecord {
|
||||
// Possibly multiple `MultiAddress`es through which the node can be reached.
|
||||
repeated bytes addresses = 1;
|
||||
// Information about the creation time of the record
|
||||
TimestampInfo creation_time = 2;
|
||||
}
|
||||
|
||||
message PeerSignature {
|
||||
bytes signature = 1;
|
||||
bytes public_key = 2;
|
||||
}
|
||||
|
||||
// Information regarding the creation data of the record
|
||||
message TimestampInfo {
|
||||
// Time since UNIX_EPOCH in nanoseconds, scale encoded
|
||||
bytes timestamp = 1;
|
||||
}
|
||||
|
||||
// Then we need to serialize the authority record and signature to send them over the wire.
|
||||
message SignedAuthorityRecord {
|
||||
bytes record = 1;
|
||||
bytes auth_signature = 2;
|
||||
// Even if there are multiple `record.addresses`, all of them have the same peer id.
|
||||
// Old versions are missing this field. It is optional in order to provide compatibility both ways.
|
||||
PeerSignature peer_signature = 3;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
mod schema_v1 {
|
||||
include!(concat!(env!("OUT_DIR"), "/authority_discovery_v1.rs"));
|
||||
}
|
||||
|
||||
mod schema_v2 {
|
||||
include!(concat!(env!("OUT_DIR"), "/authority_discovery_v2.rs"));
|
||||
}
|
||||
|
||||
use super::*;
|
||||
use codec::Encode;
|
||||
use prost::Message;
|
||||
use pezsc_network::{Multiaddr, PeerId};
|
||||
use pezsc_network_types::ed25519::Keypair;
|
||||
|
||||
#[test]
|
||||
fn v2_decodes_v1() {
|
||||
let peer_id = PeerId::random();
|
||||
let multiaddress: Multiaddr =
|
||||
format!("/ip4/127.0.0.1/tcp/3003/p2p/{}", peer_id).parse().unwrap();
|
||||
let vec_addresses = vec![multiaddress.to_vec()];
|
||||
let vec_auth_signature = b"Totally valid signature, I promise!".to_vec();
|
||||
|
||||
let addresses_v1 = schema_v1::AuthorityAddresses { addresses: vec_addresses.clone() };
|
||||
let mut vec_addresses_v1 = vec![];
|
||||
addresses_v1.encode(&mut vec_addresses_v1).unwrap();
|
||||
let signed_addresses_v1 = schema_v1::SignedAuthorityAddresses {
|
||||
addresses: vec_addresses_v1.clone(),
|
||||
signature: vec_auth_signature.clone(),
|
||||
};
|
||||
let mut vec_signed_addresses_v1 = vec![];
|
||||
signed_addresses_v1.encode(&mut vec_signed_addresses_v1).unwrap();
|
||||
|
||||
let signed_record_v2_decoded =
|
||||
SignedAuthorityRecord::decode(vec_signed_addresses_v1.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(&signed_record_v2_decoded.record, &vec_addresses_v1);
|
||||
assert_eq!(&signed_record_v2_decoded.auth_signature, &vec_auth_signature);
|
||||
assert_eq!(&signed_record_v2_decoded.peer_signature, &None);
|
||||
|
||||
let record_v2_decoded = AuthorityRecord::decode(vec_addresses_v1.as_slice()).unwrap();
|
||||
assert_eq!(&record_v2_decoded.addresses, &vec_addresses);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v1_decodes_v2() {
|
||||
let peer_secret = Keypair::generate();
|
||||
let peer_public = peer_secret.public();
|
||||
let peer_id = peer_public.to_peer_id();
|
||||
let multiaddress: Multiaddr =
|
||||
format!("/ip4/127.0.0.1/tcp/3003/p2p/{}", peer_id).parse().unwrap();
|
||||
let vec_addresses = vec![multiaddress.to_vec()];
|
||||
let vec_auth_signature = b"Totally valid signature, I promise!".to_vec();
|
||||
let vec_peer_signature = b"Surprisingly hard to crack crypto".to_vec();
|
||||
|
||||
let record_v2 = schema_v2::AuthorityRecord { addresses: vec_addresses.clone() };
|
||||
let mut vec_record_v2 = vec![];
|
||||
record_v2.encode(&mut vec_record_v2).unwrap();
|
||||
let vec_peer_public = peer_public.to_bytes().to_vec();
|
||||
let peer_signature_v2 =
|
||||
PeerSignature { public_key: vec_peer_public, signature: vec_peer_signature };
|
||||
let signed_record_v2 = SignedAuthorityRecord {
|
||||
record: vec_record_v2.clone(),
|
||||
auth_signature: vec_auth_signature.clone(),
|
||||
peer_signature: Some(peer_signature_v2.clone()),
|
||||
};
|
||||
let mut vec_signed_record_v2 = vec![];
|
||||
signed_record_v2.encode(&mut vec_signed_record_v2).unwrap();
|
||||
|
||||
let signed_addresses_v1_decoded =
|
||||
schema_v1::SignedAuthorityAddresses::decode(vec_signed_record_v2.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(&signed_addresses_v1_decoded.addresses, &vec_record_v2);
|
||||
assert_eq!(&signed_addresses_v1_decoded.signature, &vec_auth_signature);
|
||||
|
||||
let addresses_v2_decoded =
|
||||
schema_v2::AuthorityRecord::decode(vec_record_v2.as_slice()).unwrap();
|
||||
assert_eq!(&addresses_v2_decoded.addresses, &vec_addresses);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v1_decodes_v3() {
|
||||
let peer_secret = Keypair::generate();
|
||||
let peer_public = peer_secret.public();
|
||||
let peer_id = peer_public.to_peer_id();
|
||||
let multiaddress: Multiaddr =
|
||||
format!("/ip4/127.0.0.1/tcp/3003/p2p/{}", peer_id).parse().unwrap();
|
||||
let vec_addresses = vec![multiaddress.to_vec()];
|
||||
let vec_auth_signature = b"Totally valid signature, I promise!".to_vec();
|
||||
let vec_peer_signature = b"Surprisingly hard to crack crypto".to_vec();
|
||||
|
||||
let record_v3 = AuthorityRecord {
|
||||
addresses: vec_addresses.clone(),
|
||||
creation_time: Some(TimestampInfo { timestamp: Encode::encode(&55) }),
|
||||
};
|
||||
let mut vec_record_v3 = vec![];
|
||||
record_v3.encode(&mut vec_record_v3).unwrap();
|
||||
let vec_peer_public = peer_public.to_bytes().to_vec();
|
||||
let peer_signature_v3 =
|
||||
PeerSignature { public_key: vec_peer_public, signature: vec_peer_signature };
|
||||
let signed_record_v3 = SignedAuthorityRecord {
|
||||
record: vec_record_v3.clone(),
|
||||
auth_signature: vec_auth_signature.clone(),
|
||||
peer_signature: Some(peer_signature_v3.clone()),
|
||||
};
|
||||
let mut vec_signed_record_v3 = vec![];
|
||||
signed_record_v3.encode(&mut vec_signed_record_v3).unwrap();
|
||||
|
||||
let signed_addresses_v1_decoded =
|
||||
schema_v1::SignedAuthorityAddresses::decode(vec_signed_record_v3.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(&signed_addresses_v1_decoded.addresses, &vec_record_v3);
|
||||
assert_eq!(&signed_addresses_v1_decoded.signature, &vec_auth_signature);
|
||||
|
||||
let addresses_v2_decoded =
|
||||
schema_v2::AuthorityRecord::decode(vec_record_v3.as_slice()).unwrap();
|
||||
assert_eq!(&addresses_v2_decoded.addresses, &vec_addresses);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v3_decodes_v2() {
|
||||
let peer_secret = Keypair::generate();
|
||||
let peer_public = peer_secret.public();
|
||||
let peer_id = peer_public.to_peer_id();
|
||||
let multiaddress: Multiaddr =
|
||||
format!("/ip4/127.0.0.1/tcp/3003/p2p/{}", peer_id).parse().unwrap();
|
||||
let vec_addresses = vec![multiaddress.to_vec()];
|
||||
let vec_auth_signature = b"Totally valid signature, I promise!".to_vec();
|
||||
let vec_peer_signature = b"Surprisingly hard to crack crypto".to_vec();
|
||||
|
||||
let record_v2 = schema_v2::AuthorityRecord { addresses: vec_addresses.clone() };
|
||||
let mut vec_record_v2 = vec![];
|
||||
record_v2.encode(&mut vec_record_v2).unwrap();
|
||||
let vec_peer_public = peer_public.to_bytes().to_vec();
|
||||
let peer_signature_v2 =
|
||||
schema_v2::PeerSignature { public_key: vec_peer_public, signature: vec_peer_signature };
|
||||
let signed_record_v2 = schema_v2::SignedAuthorityRecord {
|
||||
record: vec_record_v2.clone(),
|
||||
auth_signature: vec_auth_signature.clone(),
|
||||
peer_signature: Some(peer_signature_v2.clone()),
|
||||
};
|
||||
let mut vec_signed_record_v2 = vec![];
|
||||
signed_record_v2.encode(&mut vec_signed_record_v2).unwrap();
|
||||
|
||||
let signed_addresses_v3_decoded =
|
||||
SignedAuthorityRecord::decode(vec_signed_record_v2.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(&signed_addresses_v3_decoded.record, &vec_record_v2);
|
||||
assert_eq!(&signed_addresses_v3_decoded.auth_signature, &vec_auth_signature);
|
||||
|
||||
let addresses_v3_decoded = AuthorityRecord::decode(vec_record_v2.as_slice()).unwrap();
|
||||
assert_eq!(&addresses_v3_decoded.addresses, &vec_addresses);
|
||||
assert_eq!(&addresses_v3_decoded.creation_time, &None);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user