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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -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