use serde::{Deserialize, Serialize};
use exonum::{
blockchain::{ConsensusConfig, ValidatorKeys},
crypto::{self, Hash, KeyPair, PublicKey},
helpers::{Height, Round, ValidatorId},
keys::Keys,
messages::{Precommit, Verified},
};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TestNetwork {
us: TestNode,
nodes: Vec<TestNode>,
}
impl TestNetwork {
pub fn new(validator_count: u16) -> Self {
Self::with_our_role(Some(ValidatorId(0)), validator_count)
}
pub fn with_our_role(us: Option<ValidatorId>, validator_count: u16) -> Self {
let keys = (0..validator_count).map(|_| Keys::random());
Self::with_our_role_from_keys(us, keys)
}
pub(crate) fn with_our_role_from_keys(
us: Option<ValidatorId>,
keys: impl IntoIterator<Item = Keys>,
) -> Self {
let mut nodes = keys
.into_iter()
.enumerate()
.map(|(n, keys)| TestNode {
keys,
validator_id: Some(ValidatorId(n as u16)),
})
.collect::<Vec<_>>();
assert!(
!nodes.is_empty(),
"At least one validator should be present in the network."
);
let us = if let Some(ValidatorId(id)) = us {
nodes[id as usize].clone()
} else {
let us = TestNode::new_auditor();
nodes.push(us.clone());
us
};
Self { nodes, us }
}
pub fn add_node(&mut self) -> &TestNode {
self.nodes.push(TestNode::new_auditor());
self.nodes.last().unwrap()
}
pub fn us(&self) -> &TestNode {
&self.us
}
pub fn validators(&self) -> Vec<TestNode> {
let mut validators = self
.nodes
.iter()
.filter(|x| x.validator_id.is_some())
.cloned()
.collect::<Vec<_>>();
validators.sort_by(|a, b| a.validator_id.cmp(&b.validator_id));
validators
}
pub fn nodes(&self) -> &[TestNode] {
&self.nodes
}
pub fn consensus_config(&self) -> ConsensusConfig {
let validator_keys = self
.validators()
.iter()
.map(TestNode::public_keys)
.collect();
ConsensusConfig::default().with_validator_keys(validator_keys)
}
pub fn update<I: IntoIterator<Item = TestNode>>(&mut self, mut us: TestNode, validators: I) {
let validators = validators
.into_iter()
.enumerate()
.map(|(id, mut validator)| {
let validator_id = ValidatorId(id as u16);
validator.change_role(Some(validator_id));
if us.public_keys().consensus_key == validator.public_keys().consensus_key {
us.change_role(Some(validator_id));
}
validator
})
.collect::<Vec<_>>();
self.nodes = validators;
self.us.clone_from(&us);
}
pub fn update_consensus_config(&mut self, config: &ConsensusConfig) {
for node in &mut self.nodes {
node.validator_id = config
.find_validator(|keys| keys.consensus_key == node.consensus_keypair().public_key());
}
let validators_count = self
.nodes
.iter()
.filter(|x| x.validator_id.is_some())
.count();
assert_eq!(validators_count, config.validator_keys.len());
let our_key = self.us.consensus_keypair().public_key();
self.us.validator_id = config.find_validator(|keys| keys.consensus_key == our_key);
}
pub fn service_public_key_of(&self, id: ValidatorId) -> Option<PublicKey> {
self.validators()
.get(id.0 as usize)
.map(|x| x.keys.service_pk())
}
pub fn consensus_public_key_of(&self, id: ValidatorId) -> Option<PublicKey> {
self.validators()
.get(id.0 as usize)
.map(|x| x.keys.consensus_pk())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TestNode {
keys: Keys,
#[serde(default, skip_serializing_if = "Option::is_none")]
validator_id: Option<ValidatorId>,
}
impl TestNode {
pub fn new_auditor() -> Self {
Self {
keys: Keys::random(),
validator_id: None,
}
}
pub fn new_validator(validator_id: ValidatorId) -> Self {
Self {
keys: Keys::random(),
validator_id: Some(validator_id),
}
}
pub fn from_parts(
consensus_keys: impl Into<KeyPair>,
service_keys: impl Into<KeyPair>,
validator_id: Option<ValidatorId>,
) -> Self {
Self {
keys: Keys::from_keys(consensus_keys, service_keys),
validator_id,
}
}
pub fn create_precommit(
&self,
height: Height,
block_hash: crypto::Hash,
) -> Verified<Precommit> {
use std::time::SystemTime;
Verified::from_value(
Precommit::new(
self.validator_id
.expect("An attempt to create propose from a non-validator node."),
height,
Round::first(),
Hash::zero(),
block_hash,
SystemTime::now().into(),
),
self.keys.consensus_pk(),
self.keys.consensus_sk(),
)
}
pub fn public_keys(&self) -> ValidatorKeys {
ValidatorKeys::new(self.keys.consensus_pk(), self.keys.service_pk())
}
pub fn validator_id(&self) -> Option<ValidatorId> {
self.validator_id
}
pub fn change_role(&mut self, role: Option<ValidatorId>) {
self.validator_id = role;
}
pub fn service_keypair(&self) -> KeyPair {
self.keys.service.clone()
}
pub fn consensus_keypair(&self) -> KeyPair {
self.keys.consensus.clone()
}
}
impl From<TestNode> for ValidatorKeys {
fn from(node: TestNode) -> Self {
node.public_keys()
}
}