use std::str::FromStr;
use num_bigint::BigUint;
use crate::dht::successor::SuccessorReader;
use crate::dht::successor::SuccessorWriter;
use crate::dht::Chord;
use crate::dht::CorrectChord;
use crate::dht::Did;
use crate::dht::PeerRing;
use crate::dht::PeerRingAction;
use crate::dht::PeerRingRemoteAction;
use crate::dht::TopoInfo;
use crate::error::Result;
use crate::storage::MemStorage;
pub(super) const K: usize = 3;
const BITS: usize = 160;
pub(super) mod spec {
use super::*;
pub use crate::dht::topology::dist;
pub fn successors(all: &[Did], n: Did) -> Vec<Did> {
crate::dht::topology::successors(all, n, K)
}
pub fn predecessor(all: &[Did], n: Did) -> Option<Did> {
crate::dht::topology::predecessor(all, n)
}
pub fn correct_rectify_predecessor(me: Did, current: Option<Did>, pred: Did) -> Option<Did> {
crate::dht::topology::rectify_predecessor(me, current, pred)
}
pub fn finger_table(all: &[Did], n: Did) -> Vec<Option<Did>> {
crate::dht::topology::finger_table(all, n)
}
pub fn correct_stabilize_successors(
me: Did,
current: &[Did],
topo_successors: &[Did],
topo_predecessor: Option<Did>,
) -> Vec<Did> {
crate::dht::topology::stabilize_successors(
me,
current,
topo_successors,
topo_predecessor,
K,
)
}
pub fn correct_stabilize_query(
me: Did,
current: &[Did],
topo_predecessor: Option<Did>,
) -> Option<Did> {
crate::dht::topology::stabilize_query(me, current, topo_predecessor)
}
pub fn correct_stabilize_notify(me: Did, next_successors: &[Did]) -> Option<Did> {
crate::dht::topology::stabilize_notify(me, next_successors)
}
}
fn ring() -> BigUint {
BigUint::from(1u8) << BITS
}
fn did_frac(num: u64, den: u64) -> Did {
Did::from(ring() * BigUint::from(num) / BigUint::from(den))
}
fn did_pow(bit: usize) -> Did {
Did::from(BigUint::from(1u8) << bit)
}
pub(super) enum Layout {
Even(usize),
Pow2(usize),
Clustered,
DyadicBoundary,
}
impl Layout {
pub(super) fn dids(&self) -> Vec<Did> {
match *self {
Layout::Even(n) => (0..n as u64).map(|i| did_frac(i, n as u64)).collect(),
Layout::Pow2(n) => (1..=n).map(|i| did_pow(BITS - i)).collect(),
Layout::Clustered => [
"0xcc13321381c4be4d3264588d4573c9529c0167a0",
"0xdbf2d77c3a8bb59379009ec2ec423b8b58d60dbe",
"0xd9863aad3267eaadca60adf51464e16d6f79465b",
"0x8a5f987d1c2cc0fd6e0083df22ba9bd802706348",
"0x2b5d1f769f346a08cee37f7382495b01126d480a",
"0xca82ac762999ef4438d09223b01f9bf194cea94e",
]
.iter()
.map(|s| Did::from_str(s).unwrap())
.collect(),
Layout::DyadicBoundary => {
vec![Did::from(0u32), did_pow(100), did_pow(159)]
}
}
}
}
fn converged_dht(node: Did, all: &[Did]) -> PeerRing {
let dht = PeerRing::new_with_storage(node, K as u8, Box::new(MemStorage::new()));
for &other in all {
if other != node {
dht.join(other).unwrap();
dht.notify(other).unwrap();
}
}
dht
}
fn assert_converged_matches_spec(layout: &Layout) {
let dids = layout.dids();
assert!(dids.len() >= 2, "need at least two nodes");
for &n in &dids {
let dht = converged_dht(n, &dids);
assert_eq!(
dht.successors().list().unwrap(),
spec::successors(&dids, n),
"successor list mismatch at node {n}"
);
assert_eq!(
*dht.lock_predecessor().unwrap(),
spec::predecessor(&dids, n),
"predecessor mismatch at node {n}"
);
assert_eq!(
dht.lock_finger().unwrap().list().clone(),
spec::finger_table(&dids, n),
"finger table mismatch at node {n}"
);
}
}
fn dht_with_successors(me: Did, successors: &[Did]) -> PeerRing {
let dht = PeerRing::new_with_storage(me, K as u8, Box::new(MemStorage::new()));
for &successor in successors {
dht.successors().update(successor).unwrap();
}
dht
}
fn assert_correct_stabilize_matches_spec(
me: Did,
current_successors: &[Did],
topo_successors: &[Did],
topo_predecessor: Option<Did>,
) {
let dht = dht_with_successors(me, current_successors);
let action = dht
.stabilize(TopoInfo {
successors: topo_successors.to_vec(),
predecessor: topo_predecessor,
})
.unwrap();
let expected_successors = spec::correct_stabilize_successors(
me,
current_successors,
topo_successors,
topo_predecessor,
);
let mut expected_actions = vec![];
if let Some(query) = spec::correct_stabilize_query(me, current_successors, topo_predecessor) {
expected_actions.push(PeerRingAction::RemoteAction(
query,
PeerRingRemoteAction::QueryForSuccessorList,
));
}
if let Some(notify) = spec::correct_stabilize_notify(me, &expected_successors) {
expected_actions.push(PeerRingAction::RemoteAction(
notify,
PeerRingRemoteAction::Notify(me),
));
}
assert_eq!(
dht.successors().list().unwrap(),
expected_successors,
"CorrectStabilize successor list mismatch"
);
assert_eq!(
action,
PeerRingAction::MultiActions(expected_actions),
"CorrectStabilize action mismatch"
);
}
fn assert_correct_rectify_matches_spec(layout: &Layout) -> Result<()> {
let dids = layout.dids();
for &me in &dids {
let current_predecessors = std::iter::once(None)
.chain(dids.iter().copied().filter(|&did| did != me).map(Some))
.collect::<Vec<_>>();
for current in current_predecessors {
for pred in dids.iter().copied().filter(|&did| did != me) {
let dht = PeerRing::new_with_storage(me, K as u8, Box::new(MemStorage::new()));
for other in dids.iter().copied().filter(|&did| did != me) {
let _ = dht.join(other)?;
}
{
let mut predecessor = dht.lock_predecessor()?;
*predecessor = current;
}
let expected = spec::correct_rectify_predecessor(me, current, pred);
let successors_before = dht.successors().list()?;
let fingers_before = dht.lock_finger()?.list().clone();
dht.rectify(pred)?;
assert_eq!(
*dht.lock_predecessor()?,
expected,
"CorrectRectify predecessor mismatch (me={me}, current={current:?}, pred={pred})"
);
assert_eq!(
dht.successors().list()?,
successors_before,
"CorrectRectify changed successors (me={me}, pred={pred})"
);
assert_eq!(
dht.lock_finger()?.list().clone(),
fingers_before,
"CorrectRectify changed fingers (me={me}, pred={pred})"
);
}
}
}
Ok(())
}
#[test]
fn correct_rectify_matches_predecessor_spec() -> Result<()> {
for layout in [
Layout::Even(3),
Layout::Even(6),
Layout::Pow2(8),
Layout::Clustered,
Layout::DyadicBoundary,
] {
assert_correct_rectify_matches_spec(&layout)?;
}
Ok(())
}
#[test]
fn convergence_inductive_ladder_even() {
for n in 2..=8 {
assert_converged_matches_spec(&Layout::Even(n));
}
}
#[test]
fn convergence_pow2_full_finger_n8() {
assert_converged_matches_spec(&Layout::Pow2(8));
}
#[test]
fn convergence_representative_non_uniform_layouts() {
for layout in [Layout::Clustered, Layout::DyadicBoundary] {
assert_converged_matches_spec(&layout);
}
}
#[test]
fn correct_stabilize_improved_predecessor_matches_spec() {
let dids = Layout::Even(5).dids();
assert_correct_stabilize_matches_spec(
dids[0],
&[dids[2]],
&[dids[3], dids[4], dids[0]],
Some(dids[1]),
);
}
#[test]
fn correct_stabilize_without_predecessor_still_notifies_successor() {
let dids = Layout::Even(4).dids();
assert_correct_stabilize_matches_spec(dids[0], &[dids[1]], &[dids[2], dids[3]], None);
}
#[test]
fn correct_stabilize_self_predecessor_does_not_query_self() {
let dids = Layout::Even(3).dids();
assert_correct_stabilize_matches_spec(dids[0], &[dids[1]], &[dids[2]], Some(dids[0]));
}
#[test]
fn correct_stabilize_unsorted_current_successors_matches_spec() {
let dids = Layout::Even(6).dids();
assert_correct_stabilize_matches_spec(
dids[0],
&[dids[3], dids[1]],
&[dids[4], dids[5], dids[0]],
Some(dids[2]),
);
}
#[test]
fn correct_stabilize_farther_predecessor_does_not_query() {
let dids = Layout::Even(6).dids();
assert_correct_stabilize_matches_spec(
dids[0],
&[dids[3], dids[1]],
&[dids[4], dids[5]],
Some(dids[2]),
);
}
#[test]
fn correct_stabilize_deduplicates_self_and_truncates_candidates() {
let dids = Layout::Even(8).dids();
assert_correct_stabilize_matches_spec(
dids[0],
&[dids[4], dids[0], dids[2], dids[2]],
&[dids[1], dids[3], dids[5], dids[0]],
Some(dids[2]),
);
}
#[test]
fn correct_stabilize_ignores_last_topo_successor() {
let dids = Layout::Even(6).dids();
assert_correct_stabilize_matches_spec(dids[0], &[dids[4]], &[dids[5], dids[1]], None);
}
#[test]
fn correct_stabilize_empty_topo_without_successor_is_noop() {
let dids = Layout::Even(2).dids();
assert_correct_stabilize_matches_spec(dids[0], &[], &[], None);
}