Skip to content
This repository has been archived by the owner on Jun 25, 2021. It is now read-only.

Commit

Permalink
feat: last byte of node's name represents its age
Browse files Browse the repository at this point in the history
  • Loading branch information
maqi committed Mar 29, 2021
1 parent 107ab67 commit 69cef7a
Show file tree
Hide file tree
Showing 16 changed files with 214 additions and 186 deletions.
28 changes: 7 additions & 21 deletions src/consensus/dkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,39 +654,25 @@ impl DkgCommands for Option<DkgCommand> {
mod tests {
use super::*;
use crate::{
node::test_utils::arbitrary_unique_nodes, section::test_utils::gen_addr, ELDER_SIZE,
MIN_AGE,
crypto, node::test_utils::arbitrary_unique_nodes, section::test_utils::gen_addr,
ELDER_SIZE, MIN_AGE,
};
use assert_matches::assert_matches;
use proptest::prelude::*;
use rand::{rngs::SmallRng, SeedableRng};
use std::{collections::HashMap, iter};
use xor_name::Prefix;

#[test]
fn dkg_key_is_affected_by_ages() {
let name = rand::random();
let addr = gen_addr();

let peer0 = Peer::new(name, addr, MIN_AGE);
let peer1 = Peer::new(name, addr, MIN_AGE + 1);

let elders_info0 = EldersInfo::new(iter::once(peer0), Prefix::default());
let elders_info1 = EldersInfo::new(iter::once(peer1), Prefix::default());

let key0 = DkgKey::new(&elders_info0, 0);
let key1 = DkgKey::new(&elders_info1, 0);

assert_ne!(key0, key1);
}

#[test]
fn single_participant() {
// If there is only one participant, the DKG should complete immediately.

let mut voter = DkgVoter::default();

let node = Node::new(crypto::gen_keypair(), gen_addr());
let node = Node::new(
crypto::gen_keypair(&Prefix::default().range_inclusive(), MIN_AGE + 1),
gen_addr(),
);
let elders_info = EldersInfo::new(iter::once(node.peer()), Prefix::default());
let dkg_key = DkgKey::new(&elders_info, 0);

Expand Down Expand Up @@ -802,6 +788,6 @@ mod tests {
}

fn arbitrary_elder_nodes() -> impl Strategy<Value = Vec<Node>> {
arbitrary_unique_nodes(2..=ELDER_SIZE, MIN_AGE..)
arbitrary_unique_nodes(2..=ELDER_SIZE)
}
}
20 changes: 14 additions & 6 deletions src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub use ed25519_dalek::{

use ed25519_dalek::ExpandedSecretKey;
use std::ops::RangeInclusive;
use xor_name::XorName;
use xor_name::{XorName, XOR_NAME_LEN};

/// SHA3-256 hash digest.
pub type Digest256 = [u8; 32];
Expand Down Expand Up @@ -43,18 +43,26 @@ pub fn name(public_key: &PublicKey) -> XorName {
XorName(public_key.to_bytes())
}

/// Construct a random `Keypair`
pub fn gen_keypair() -> Keypair {
Keypair::generate(&mut rand::thread_rng())
#[cfg(test)]
/// Construct a random `XorName` whose last byte represents the targeted age.
pub fn gen_name_with_age(age: u8) -> XorName {
loop {
let name = XorName::random();
if age == name[XOR_NAME_LEN - 1] {
return name;
}
}
}

/// Construct a `Keypair` whose name is in the interval [start, end] (both endpoints inclusive).
pub fn gen_keypair_within_range(range: &RangeInclusive<XorName>) -> Keypair {
/// And the last byte equals to the targeted age.
pub fn gen_keypair(range: &RangeInclusive<XorName>, age: u8) -> Keypair {
let mut rng = rand::thread_rng();

loop {
let keypair = Keypair::generate(&mut rng);
if range.contains(&name(&keypair.public)) {
let new_name = name(&keypair.public);
if range.contains(&new_name) && age == new_name[XOR_NAME_LEN - 1] {
return keypair;
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/delivery_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ mod tests {
use super::*;
use crate::{
consensus::test_utils::proven,
crypto,
section::{
test_utils::{gen_addr, gen_elders_info},
EldersInfo, MemberInfo, SectionChain, MIN_AGE,
Expand Down Expand Up @@ -217,8 +218,9 @@ mod tests {
fn delivery_targets_elder_to_our_adult() -> Result<()> {
let (our_name, mut section, network, sk) = setup_elder()?;

let dst_name = section.prefix().substituted_in(rand::random());
let peer = Peer::new(dst_name, gen_addr(), MIN_AGE + 1);
let name = crypto::gen_name_with_age(MIN_AGE + 1);
let dst_name = section.prefix().substituted_in(name);
let peer = Peer::new(dst_name, gen_addr());
let member_info = MemberInfo::joined(peer);
let member_info = proven(&sk, member_info)?;
assert!(section.update_member(member_info));
Expand Down
10 changes: 7 additions & 3 deletions src/messages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ impl Message {
let signature = crypto::sign(&serialized, &node.keypair);
let src = SrcAuthority::Node {
public_key: node.keypair.public,
age: node.age,
age: node.age(),
signature,
};

Expand Down Expand Up @@ -503,10 +503,14 @@ mod tests {
};
use anyhow::Result;
use std::iter;
use xor_name::Prefix;

#[test]
fn extend_proof_chain() -> Result<()> {
let node = Node::new(crypto::gen_keypair(), gen_addr());
let node = Node::new(
crypto::gen_keypair(&Prefix::default().range_inclusive(), MIN_AGE + 1),
gen_addr(),
);

let sk0 = bls::SecretKey::random();
let pk0 = sk0.public_key();
Expand All @@ -521,7 +525,7 @@ mod tests {
let (elders_info, _) = section::test_utils::gen_elders_info(Default::default(), 3);
let elders_info = consensus::test_utils::proven(&sk1, elders_info)?;

let peer = Peer::new(rand::random(), gen_addr(), MIN_AGE);
let peer = Peer::new(rand::random(), gen_addr());
let member_info = MemberInfo::joined(peer);
let member_info = consensus::test_utils::proven(&sk1, member_info)?;

Expand Down
4 changes: 1 addition & 3 deletions src/messages/src_authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ impl SrcAuthority {
// If this location is `Node`, returns the corresponding `Peer` with `addr`. Otherwise error.
pub(crate) fn peer(&self, addr: SocketAddr) -> Result<Peer> {
match self {
Self::Node {
public_key, age, ..
} => Ok(Peer::new(name(public_key), addr, *age)),
Self::Node { public_key, .. } => Ok(Peer::new(name(public_key), addr)),
Self::Section { .. } | Self::BlsShare { .. } => Err(Error::InvalidSrcLocation),
}
}
Expand Down
47 changes: 19 additions & 28 deletions src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use crate::{crypto, peer::Peer, MIN_AGE};
use crate::{crypto, peer::Peer};
use ed25519_dalek::Keypair;
use std::{
fmt::{self, Debug, Display, Formatter},
net::SocketAddr,
sync::Arc,
};
use xor_name::XorName;
use xor_name::{XorName, XOR_NAME_LEN};

/// Information and state of our node
#[derive(Clone)]
Expand All @@ -23,29 +23,28 @@ pub(crate) struct Node {
// TODO: find a way to not require `Clone`.
pub keypair: Arc<Keypair>,
pub addr: SocketAddr,
pub age: u8,
}

impl Node {
pub fn new(keypair: Keypair, addr: SocketAddr) -> Self {
Self {
keypair: Arc::new(keypair),
addr,
age: MIN_AGE,
}
}

pub fn with_age(self, age: u8) -> Self {
Self { age, ..self }
}

pub fn peer(&self) -> Peer {
Peer::new(self.name(), self.addr, self.age)
Peer::new(self.name(), self.addr)
}

pub fn name(&self) -> XorName {
crypto::name(&self.keypair.public)
}

// Last byte of the name represents the age.
pub fn age(&self) -> u8 {
self.name()[XOR_NAME_LEN - 1]
}
}

impl Display for Node {
Expand All @@ -59,7 +58,7 @@ impl Debug for Node {
f.debug_struct("Node")
.field("name", &self.name())
.field("addr", &self.addr)
.field("age", &self.age)
.field("age", &self.age())
.finish()
}
}
Expand All @@ -70,29 +69,21 @@ pub(crate) mod test_utils {
use itertools::Itertools;
use proptest::{collection::SizeRange, prelude::*};

pub(crate) fn arbitrary_node(age: impl Strategy<Value = u8>) -> impl Strategy<Value = Node> {
(
crypto::test_utils::arbitrary_keypair(),
any::<SocketAddr>(),
age,
)
.prop_map(|(keypair, addr, age)| Node::new(keypair, addr).with_age(age))
pub(crate) fn arbitrary_node() -> impl Strategy<Value = Node> {
(crypto::test_utils::arbitrary_keypair(), any::<SocketAddr>())
.prop_map(|(keypair, addr)| Node::new(keypair, addr))
}

// Generate Vec<Node> where no two nodes have the same name.
pub(crate) fn arbitrary_unique_nodes(
count: impl Into<SizeRange>,
age: impl Strategy<Value = u8>,
) -> impl Strategy<Value = Vec<Node>> {
proptest::collection::vec(arbitrary_node(age), count).prop_filter(
"non-unique keys",
|nodes| {
nodes
.iter()
.unique_by(|node| node.keypair.secret.as_bytes())
.count()
== nodes.len()
},
)
proptest::collection::vec(arbitrary_node(), count).prop_filter("non-unique keys", |nodes| {
nodes
.iter()
.unique_by(|node| node.keypair.secret.as_bytes())
.count()
== nodes.len()
})
}
}
34 changes: 10 additions & 24 deletions src/peer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{
hash::Hash,
net::SocketAddr,
};
use xor_name::XorName;
use xor_name::{XorName, XOR_NAME_LEN};

/// Network p2p peer identity.
/// When a node knows another p2p_node as a `Peer` it's implicitly connected to it. This is separate
Expand All @@ -21,13 +21,12 @@ use xor_name::XorName;
pub struct Peer {
name: XorName,
addr: SocketAddr,
age: u8,
}

impl Peer {
/// Creates a new `Peer` given `Name`, `SocketAddr` and `age`.
pub fn new(name: XorName, addr: SocketAddr, age: u8) -> Self {
Self { name, addr, age }
/// Creates a new `Peer` given `Name`, `SocketAddr`.
pub fn new(name: XorName, addr: SocketAddr) -> Self {
Self { name, addr }
}

/// Returns the `XorName` of the peer.
Expand All @@ -42,20 +41,7 @@ impl Peer {

/// Returns the age.
pub fn age(&self) -> u8 {
self.age
}

// Converts this info into one with the input age.
pub fn with_age(self, age: u8) -> Self {
Self { age, ..self }
}

// Converts this info into one with the age increased by one.
pub fn increment_age(self) -> Self {
Self {
age: self.age.saturating_add(1),
..self
}
self.name[XOR_NAME_LEN - 1]
}
}

Expand All @@ -78,14 +64,14 @@ pub(crate) mod test_utils {
// Generate Vec<Peer> where no two peers have the same name.
pub(crate) fn arbitrary_unique_peers(
count: impl Into<SizeRange>,
age: impl Strategy<Value = u8>,
) -> impl Strategy<Value = Vec<Peer>> {
proptest::collection::btree_map(arbitrary_xor_name(), (any::<SocketAddr>(), age), count)
.prop_map(|peers| {
proptest::collection::btree_map(arbitrary_xor_name(), any::<SocketAddr>(), count).prop_map(
|peers| {
peers
.into_iter()
.map(|(name, (addr, age))| Peer::new(name, addr, age))
.map(|(name, addr)| Peer::new(name, addr))
.collect()
})
},
)
}
}
2 changes: 1 addition & 1 deletion src/relocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ mod tests {
proptest! {
#[test]
fn proptest_actions(
peers in arbitrary_unique_peers(2..ELDER_SIZE + 1, MIN_AGE..MAX_AGE),
peers in arbitrary_unique_peers(2..ELDER_SIZE + 1),
signature_trailing_zeros in 0..MAX_AGE,
seed in any::<u64>().no_shrink())
{
Expand Down
11 changes: 10 additions & 1 deletion src/routing/approved.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,15 @@ impl Approved {
(MIN_AGE + 1, None, None)
};

// Requires the node name matches the age.
if age != peer.age() {
debug!(
"Ignoring JoinRequest from {} - required age {:?} not presented.",
peer, age,
);
return Ok(vec![]);
}

// Require resource proof only if joining as a new node.
if previous_name.is_none() {
if let Some(response) = join_request.resource_proof_response {
Expand All @@ -1268,7 +1277,7 @@ impl Approved {
}

self.vote(Vote::Online {
member_info: MemberInfo::joined(peer.with_age(age)),
member_info: MemberInfo::joined(peer),
previous_name,
their_knowledge,
})
Expand Down

0 comments on commit 69cef7a

Please sign in to comment.