Skip to main content

rings_core/dht/
topology.rs

1#![deny(missing_docs)]
2//! Pure topology transition model for Chord.
3//!
4//! This module is the production home of the algebraic operators previously
5//! mirrored only in convergence tests. The mutable [`PeerRing`](crate::dht::PeerRing)
6//! shell interprets these pure transitions by writing successor/predecessor
7//! fields and by turning [`TopologyAction`](crate::dht::topology::TopologyAction)
8//! values into transport actions.
9//!
10//! State variables:
11//! - `R = Z / 2^160`, represented by [`Did`](crate::dht::Did).
12//! - `succ[n]` is the bounded successor sequence for node `n`.
13//! - `pred[n]` is the optional predecessor for node `n`.
14//! - `finger[n][i]` is the optional sparse/no-wrap finger-table entry at slot `i`.
15//!
16//! Law: join, remove, notify, stabilize, and finger maintenance are pure
17//! transitions over this state. Stabilize/notify/finger refinement are monotone
18//! over the finite known topology set; their least fixpoint is the converged
19//! Chord state plus a finger table derived from that topology.
20
21use num_bigint::BigUint;
22
23use super::Did;
24
25/// Ring bit-width; `Did` is `Z/2^160`.
26pub const RING_BITS: usize = 160;
27
28/// Default successor-list capacity used by the production builder and tests.
29pub const DEFAULT_SUCCESSOR_CAPACITY: usize = 3;
30
31/// Pure per-node topology state.
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct TopologyState {
34    /// Local node identifier.
35    pub local: Did,
36    /// Known successors, ordered by clockwise distance from `local`.
37    pub successors: Vec<Did>,
38    /// Known predecessor.
39    pub predecessor: Option<Did>,
40    /// Sparse/no-wrap finger table.
41    pub fingers: Vec<Option<Did>>,
42    /// Next finger index maintained by the periodic finger fixer.
43    pub fix_finger_index: usize,
44}
45
46impl TopologyState {
47    /// Construct a pure topology state.
48    pub fn new(
49        local: Did,
50        successors: Vec<Did>,
51        predecessor: Option<Did>,
52        fingers: Vec<Option<Did>>,
53        fix_finger_index: usize,
54    ) -> Self {
55        Self {
56            local,
57            successors,
58            predecessor,
59            fingers,
60            fix_finger_index,
61        }
62    }
63}
64
65/// Pure result of looking up the owner of a DID in local topology state.
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub enum FindSuccessorStep {
68    /// The local state can answer with this successor.
69    Local(Did),
70    /// The query must be forwarded to `next`.
71    Remote {
72        /// Next hop.
73        next: Did,
74        /// DID whose successor is being searched.
75        did: Did,
76    },
77}
78
79/// Pure topology input event.
80#[derive(Clone, Debug, PartialEq, Eq)]
81pub enum TopologyEvent {
82    /// A connected peer is introduced to the topology state.
83    Join {
84        /// Peer learned by the local node.
85        peer: Did,
86    },
87    /// Atomically admit a transport-validated peer and its pending finger continuations.
88    Admit {
89        /// Peer whose data channel is open.
90        peer: Did,
91        /// Finger slots whose lookup completed while the peer was handshaking.
92        fixed_fingers: Vec<ConditionalFingerUpdate>,
93    },
94    /// A peer is removed from successor, predecessor, and finger state.
95    Remove {
96        /// Peer that left or failed.
97        peer: Did,
98        /// Successor-list transition justified by the caller's evidence.
99        successor: SuccessorRemoval,
100    },
101    /// A successor candidate was accepted by the liveness/interpreter boundary.
102    UpdateSuccessor {
103        /// Candidate successor.
104        successor: Did,
105    },
106    /// HMCC/Zave notify input: one candidate predecessor notified this node.
107    Notify {
108        /// Candidate predecessor.
109        predecessor: Did,
110    },
111    /// HMCC/Zave stabilize input: topological information returned by the
112    /// current successor.
113    Stabilize {
114        /// Successor list reported by the successor.
115        successors: Vec<Did>,
116        /// Predecessor reported by the successor.
117        predecessor: Option<Did>,
118    },
119    /// Periodic finger-fix transition.
120    FixFinger,
121    /// Apply a reported successor to a fixed finger slot.
122    ApplyFinger {
123        /// Finger slot to update.
124        index: usize,
125        /// Successor reported for that slot.
126        successor: Did,
127    },
128}
129
130/// A deferred finger update that may commit only if its source slot has not
131/// changed since the lookup result was queued.
132#[derive(Clone, Copy, Debug, PartialEq, Eq)]
133pub struct ConditionalFingerUpdate {
134    /// Finger slot returned by the lookup.
135    pub index: usize,
136    /// Slot value observed when the update was deferred.
137    pub expected: Option<Did>,
138}
139
140/// Successor-list evidence attached to a peer-removal transition.
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub enum SuccessorRemoval {
143    /// Preserve surviving successor-list entries for an ordinary leave.
144    Preserve,
145    /// Replace an unavailable head with exactly these transport-validated peers.
146    ///
147    /// An empty list clears every successor claim. The transition normalizes
148    /// ordering, uniqueness, self references, and capacity.
149    ReplaceWith(Vec<Did>),
150}
151
152/// Pure topology side effect emitted by a transition.
153#[derive(Clone, Copy, Debug, PartialEq, Eq)]
154pub enum TopologyAction {
155    /// Ask `next` to find `did` and report with the connect handler.
156    FindSuccessorForConnect {
157        /// Next hop.
158        next: Did,
159        /// DID being searched.
160        did: Did,
161    },
162    /// Ask `next` to find `did` and report with the finger-fix handler.
163    FindSuccessorForFix {
164        /// Next hop.
165        next: Did,
166        /// DID being searched.
167        did: Did,
168        /// Finger slot to update when the report returns.
169        index: usize,
170    },
171    /// Query this improved successor for its successor list.
172    QuerySuccessorList(Did),
173    /// Notify this successor that `local` is its predecessor candidate.
174    Notify(Did),
175}
176
177/// Result of applying one pure topology transition.
178#[derive(Clone, Debug, PartialEq, Eq)]
179pub struct TopologyStep {
180    /// Next topology state.
181    pub state: TopologyState,
182    /// Actions to be interpreted by the effect layer.
183    pub actions: Vec<TopologyAction>,
184}
185
186/// `dist(a,b) == (b - a) mod 2^160`, the clockwise distance from `a` to `b`.
187pub fn dist(a: Did, b: Did) -> BigUint {
188    BigUint::from(b - a)
189}
190
191fn push_unique(xs: &mut Vec<Did>, x: Did) {
192    if !xs.contains(&x) {
193        xs.push(x);
194    }
195}
196
197fn sorted_successors(mut candidates: Vec<Did>, local: Did, capacity: usize) -> Vec<Did> {
198    candidates.retain(|&did| did != local);
199    candidates.sort_by_key(|&did| dist(local, did));
200    candidates.dedup();
201    candidates.truncate(capacity);
202    candidates
203}
204
205/// `Successors(n)`: the nearest forward nodes, ordered by clockwise distance.
206pub fn successors(all: &[Did], n: Did, capacity: usize) -> Vec<Did> {
207    sorted_successors(all.to_vec(), n, capacity)
208}
209
210/// `Predecessor(n)`: the nearest node behind `n`.
211pub fn predecessor(all: &[Did], n: Did) -> Option<Did> {
212    all.iter()
213        .copied()
214        .filter(|&did| did != n)
215        .max_by_key(|&did| dist(n, did))
216}
217
218/// `Finger(n, bit)`: nearest forward node at distance `>= 2^bit`, else `None`.
219///
220/// This mirrors Rings' sparse/no-wrap finger table, not the Chord paper's
221/// wrapping finger definition.
222pub fn finger(all: &[Did], n: Did, bit: usize) -> Option<Did> {
223    let threshold = BigUint::from(1u8) << bit;
224    all.iter()
225        .copied()
226        .filter(|&did| did != n && dist(n, did) >= threshold)
227        .min_by_key(|&did| dist(n, did))
228}
229
230/// Full sparse/no-wrap finger table predicted by the topology operator.
231pub fn finger_table(all: &[Did], n: Did) -> Vec<Option<Did>> {
232    (0..RING_BITS).map(|bit| finger(all, n, bit)).collect()
233}
234
235/// Correct successor list after introducing one candidate successor.
236pub fn update_successors(local: Did, current: &[Did], candidate: Did, capacity: usize) -> Vec<Did> {
237    let mut candidates = current.to_vec();
238    push_unique(&mut candidates, candidate);
239    sorted_successors(candidates, local, capacity)
240}
241
242fn finger_join(local: Did, current: &[Option<Did>], peer: Did) -> Vec<Option<Did>> {
243    let bias = dist(local, peer);
244    current
245        .iter()
246        .copied()
247        .enumerate()
248        .map(|(slot, old)| {
249            let pos = BigUint::from(Did::power_of_two(slot));
250            if bias < pos || peer == local {
251                old
252            } else {
253                match old {
254                    Some(existing) if dist(local, existing) < bias => old,
255                    _ => Some(peer),
256                }
257            }
258        })
259        .collect()
260}
261
262/// Remove `peer` from every finger slot without erasing valid slots between
263/// non-contiguous runs. Each removed run inherits its immediate following hint.
264pub(crate) fn remove_finger_peer(current: &[Option<Did>], peer: Did) -> Vec<Option<Did>> {
265    let mut next = current.to_vec();
266    let mut index = 0;
267    while index < next.len() {
268        if next.get(index).copied().flatten() != Some(peer) {
269            index = index.saturating_add(1);
270            continue;
271        }
272
273        let run_start = index;
274        while next.get(index).copied().flatten() == Some(peer) {
275            index = index.saturating_add(1);
276        }
277        let replacement = next.get(index).copied().flatten();
278        for slot in next.iter_mut().take(index).skip(run_start) {
279            *slot = replacement;
280        }
281    }
282    next
283}
284
285fn finger_set(
286    local: Did,
287    current: &[Option<Did>],
288    index: usize,
289    successor: Did,
290) -> Vec<Option<Did>> {
291    let mut next = current.to_vec();
292    if successor != local {
293        if let Some(slot) = next.get_mut(index) {
294            *slot = Some(successor);
295        }
296    }
297    next
298}
299
300/// Pure Chord successor lookup against one topology state.
301pub fn find_successor(state: &TopologyState, did: Did) -> FindSuccessorStep {
302    let head = state.successors.first().copied().unwrap_or(state.local);
303    if state.successors.is_empty() || dist(state.local, did) <= dist(state.local, head) {
304        FindSuccessorStep::Local(head)
305    } else {
306        let next = state
307            .fingers
308            .iter()
309            .rev()
310            .flatten()
311            .copied()
312            .find(|peer| dist(state.local, *peer) < dist(state.local, did))
313            .unwrap_or(state.local);
314        FindSuccessorStep::Remote { next, did }
315    }
316}
317
318/// Correct predecessor value after one HMCC/Zave rectify transition.
319pub fn rectify_predecessor(local: Did, current: Option<Did>, candidate: Did) -> Option<Did> {
320    match current {
321        Some(cur) if dist(local, cur) >= dist(local, candidate) => Some(cur),
322        _ => Some(candidate),
323    }
324}
325
326/// Correct successor list after one HMCC/Zave stabilize transition.
327pub fn stabilize_successors(
328    local: Did,
329    current: &[Did],
330    topo_successors: &[Did],
331    topo_predecessor: Option<Did>,
332    capacity: usize,
333) -> Vec<Did> {
334    let mut known = vec![local];
335    for &did in current {
336        push_unique(&mut known, did);
337    }
338    if let Some(pred) = topo_predecessor {
339        push_unique(&mut known, pred);
340    }
341    for &did in topo_successors
342        .iter()
343        .take(topo_successors.len().saturating_sub(1))
344    {
345        push_unique(&mut known, did);
346    }
347    successors(&known, local, capacity)
348}
349
350/// Improved-successor query emitted by one HMCC/Zave stabilize transition.
351pub fn stabilize_query(local: Did, current: &[Did], topo_predecessor: Option<Did>) -> Option<Did> {
352    let pred = topo_predecessor?;
353    if pred == local {
354        return None;
355    }
356    let old_head = current.iter().copied().min_by_key(|&did| dist(local, did));
357    match old_head {
358        Some(head) if dist(local, pred) >= dist(local, head) => None,
359        _ => Some(pred),
360    }
361}
362
363/// Notify action emitted after one HMCC/Zave stabilize transition.
364pub fn stabilize_notify(local: Did, next_successors: &[Did]) -> Option<Did> {
365    next_successors.first().copied().filter(|&did| did != local)
366}
367
368fn step_join(state: &TopologyState, peer: Did, capacity: usize) -> TopologyStep {
369    if peer == state.local {
370        return TopologyStep {
371            state: state.clone(),
372            actions: Vec::new(),
373        };
374    }
375    TopologyStep {
376        state: TopologyState {
377            successors: update_successors(state.local, &state.successors, peer, capacity),
378            fingers: finger_join(state.local, &state.fingers, peer),
379            ..state.clone()
380        },
381        actions: vec![TopologyAction::FindSuccessorForConnect {
382            next: peer,
383            did: state.local,
384        }],
385    }
386}
387
388fn step_admit(
389    state: &TopologyState,
390    peer: Did,
391    fixed_fingers: &[ConditionalFingerUpdate],
392    capacity: usize,
393) -> TopologyStep {
394    if peer == state.local {
395        return TopologyStep {
396            state: state.clone(),
397            actions: Vec::new(),
398        };
399    }
400
401    let successors = update_successors(state.local, &state.successors, peer, capacity);
402    let inserted = !state.successors.contains(&peer) && successors.contains(&peer);
403    let mut fingers = finger_join(state.local, &state.fingers, peer);
404    for update in fixed_fingers {
405        if state.fingers.get(update.index).copied().flatten() == update.expected {
406            fingers = finger_set(state.local, &fingers, update.index, peer);
407        }
408    }
409
410    let mut actions = Vec::new();
411    if inserted {
412        actions.push(TopologyAction::QuerySuccessorList(peer));
413    }
414    actions.push(TopologyAction::FindSuccessorForConnect {
415        next: peer,
416        did: state.local,
417    });
418    TopologyStep {
419        state: TopologyState {
420            successors,
421            fingers,
422            ..state.clone()
423        },
424        actions,
425    }
426}
427
428fn step_remove(
429    state: &TopologyState,
430    peer: Did,
431    successor: SuccessorRemoval,
432    capacity: usize,
433) -> TopologyStep {
434    let removed_head = state.successors.first().copied() == Some(peer);
435    let mut next_successors = state
436        .successors
437        .iter()
438        .copied()
439        .filter(|&did| did != peer)
440        .collect::<Vec<_>>();
441    let fingers = remove_finger_peer(&state.fingers, peer);
442    if removed_head {
443        match successor {
444            SuccessorRemoval::Preserve => {}
445            SuccessorRemoval::ReplaceWith(mut validated) => {
446                validated.retain(|candidate| *candidate != peer);
447                next_successors = sorted_successors(validated, state.local, capacity);
448            }
449        }
450    }
451    TopologyStep {
452        state: TopologyState {
453            successors: next_successors,
454            predecessor: state.predecessor.filter(|&did| did != peer),
455            fingers,
456            ..state.clone()
457        },
458        actions: Vec::new(),
459    }
460}
461
462fn step_update_successor(state: &TopologyState, successor: Did, capacity: usize) -> TopologyStep {
463    let next_successors = update_successors(state.local, &state.successors, successor, capacity);
464    let inserted = !state.successors.contains(&successor) && next_successors.contains(&successor);
465    TopologyStep {
466        state: TopologyState {
467            successors: next_successors,
468            ..state.clone()
469        },
470        actions: if inserted {
471            vec![TopologyAction::QuerySuccessorList(successor)]
472        } else {
473            Vec::new()
474        },
475    }
476}
477
478fn step_fix_finger(state: &TopologyState) -> TopologyStep {
479    if state.fingers.is_empty() {
480        return TopologyStep {
481            state: state.clone(),
482            actions: Vec::new(),
483        };
484    }
485    let index = (state.fix_finger_index + 1) % state.fingers.len();
486    let did = state.local + Did::power_of_two(index);
487    match find_successor(state, did) {
488        FindSuccessorStep::Local(successor) => TopologyStep {
489            state: TopologyState {
490                fingers: finger_set(state.local, &state.fingers, index, successor),
491                fix_finger_index: index,
492                ..state.clone()
493            },
494            actions: Vec::new(),
495        },
496        FindSuccessorStep::Remote { next, did } => TopologyStep {
497            state: TopologyState {
498                fix_finger_index: index,
499                ..state.clone()
500            },
501            actions: vec![TopologyAction::FindSuccessorForFix { next, did, index }],
502        },
503    }
504}
505
506/// Apply one pure topology transition.
507///
508/// Post: the returned state depends only on `state` and `event`; no locks,
509/// storage, clocks, randomness, or transport effects are read here.
510pub fn step(state: &TopologyState, event: TopologyEvent, capacity: usize) -> TopologyStep {
511    match event {
512        TopologyEvent::Join { peer } => step_join(state, peer, capacity),
513        TopologyEvent::Admit {
514            peer,
515            fixed_fingers,
516        } => step_admit(state, peer, &fixed_fingers, capacity),
517        TopologyEvent::Remove { peer, successor } => step_remove(state, peer, successor, capacity),
518        TopologyEvent::UpdateSuccessor { successor } => {
519            step_update_successor(state, successor, capacity)
520        }
521        TopologyEvent::Notify { predecessor } => TopologyStep {
522            state: TopologyState {
523                predecessor: rectify_predecessor(state.local, state.predecessor, predecessor),
524                ..state.clone()
525            },
526            actions: Vec::new(),
527        },
528        TopologyEvent::Stabilize {
529            successors: topo_successors,
530            predecessor: topo_predecessor,
531        } => {
532            let next_successors = stabilize_successors(
533                state.local,
534                &state.successors,
535                &topo_successors,
536                topo_predecessor,
537                capacity,
538            );
539            let mut actions = Vec::new();
540            if let Some(query) = stabilize_query(state.local, &state.successors, topo_predecessor) {
541                actions.push(TopologyAction::QuerySuccessorList(query));
542            }
543            if let Some(notify) = stabilize_notify(state.local, &next_successors) {
544                actions.push(TopologyAction::Notify(notify));
545            }
546            TopologyStep {
547                state: TopologyState {
548                    successors: next_successors,
549                    ..state.clone()
550                },
551                actions,
552            }
553        }
554        TopologyEvent::FixFinger => step_fix_finger(state),
555        TopologyEvent::ApplyFinger { index, successor } => TopologyStep {
556            state: TopologyState {
557                fingers: finger_set(state.local, &state.fingers, index, successor),
558                ..state.clone()
559            },
560            actions: Vec::new(),
561        },
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use num_bigint::BigUint;
568
569    use super::*;
570
571    fn did(value: u32) -> Did {
572        Did::from(value)
573    }
574
575    fn state(
576        local: Did,
577        successors: Vec<Did>,
578        predecessor: Option<Did>,
579        fingers: Vec<Option<Did>>,
580        fix_finger_index: usize,
581    ) -> TopologyState {
582        TopologyState::new(local, successors, predecessor, fingers, fix_finger_index)
583    }
584
585    fn successor_distances(local: Did, successors: &[Did], capacity: usize) -> Vec<BigUint> {
586        let infinity = BigUint::from(1u8) << RING_BITS;
587        (0..capacity)
588            .map(|index| {
589                successors
590                    .get(index)
591                    .map(|successor| dist(local, *successor))
592                    .unwrap_or_else(|| infinity.clone())
593            })
594            .collect()
595    }
596
597    fn refines_successor_distances(before: &TopologyState, after: &TopologyState) -> bool {
598        let before_distances =
599            successor_distances(before.local, &before.successors, DEFAULT_SUCCESSOR_CAPACITY);
600        let after_distances =
601            successor_distances(after.local, &after.successors, DEFAULT_SUCCESSOR_CAPACITY);
602        before_distances
603            .iter()
604            .zip(after_distances.iter())
605            .all(|(before, after)| after <= before)
606    }
607
608    #[test]
609    fn test_join_step_updates_successors_fingers_and_connect_action() {
610        let local = did(0);
611        let peer = did(8);
612        let next = step(
613            &state(local, vec![], None, vec![None; 5], 0),
614            TopologyEvent::Join { peer },
615            DEFAULT_SUCCESSOR_CAPACITY,
616        );
617
618        assert_eq!(next.state.successors, vec![peer]);
619        assert_eq!(next.state.fingers, vec![
620            Some(peer),
621            Some(peer),
622            Some(peer),
623            Some(peer),
624            None
625        ]);
626        assert_eq!(next.actions, vec![
627            TopologyAction::FindSuccessorForConnect {
628                next: peer,
629                did: local
630            }
631        ]);
632    }
633
634    #[test]
635    fn test_join_step_refines_successor_distance_vector() {
636        let local = did(0);
637        let current = state(local, vec![did(20), did(40)], None, vec![None; 5], 0);
638        let next = step(
639            &current,
640            TopologyEvent::Join { peer: did(10) },
641            DEFAULT_SUCCESSOR_CAPACITY,
642        );
643
644        assert!(refines_successor_distances(&current, &next.state));
645    }
646
647    #[test]
648    fn test_stabilize_step_refines_successor_distance_vector() {
649        let local = did(0);
650        let current = state(local, vec![did(40)], None, vec![None; 5], 0);
651        let next = step(
652            &current,
653            TopologyEvent::Stabilize {
654                successors: vec![did(50), did(60)],
655                predecessor: Some(did(10)),
656            },
657            DEFAULT_SUCCESSOR_CAPACITY,
658        );
659
660        assert!(refines_successor_distances(&current, &next.state));
661    }
662
663    #[test]
664    fn test_remove_step_removes_peer_from_every_topology_slot() {
665        let local = did(0);
666        let peer = did(8);
667        let next = step(
668            &state(
669                local,
670                vec![peer],
671                Some(peer),
672                vec![Some(peer), Some(peer)],
673                0,
674            ),
675            TopologyEvent::Remove {
676                peer,
677                successor: SuccessorRemoval::Preserve,
678            },
679            DEFAULT_SUCCESSOR_CAPACITY,
680        );
681
682        assert!(next.state.successors.is_empty());
683        assert_eq!(next.state.predecessor, None);
684        assert_eq!(next.state.fingers, vec![None, None]);
685        assert!(next.actions.is_empty());
686    }
687
688    #[test]
689    fn test_ordinary_remove_does_not_promote_an_unverified_finger() {
690        let local = did(0);
691        let removed = did(8);
692        let fallback = did(16);
693        let next = step(
694            &state(
695                local,
696                vec![removed],
697                None,
698                vec![Some(removed), None, Some(fallback)],
699                0,
700            ),
701            TopologyEvent::Remove {
702                peer: removed,
703                successor: SuccessorRemoval::Preserve,
704            },
705            DEFAULT_SUCCESSOR_CAPACITY,
706        );
707
708        assert!(next.state.successors.is_empty());
709        assert_eq!(next.state.fingers, vec![None, None, Some(fallback)]);
710        assert!(next.actions.is_empty());
711    }
712
713    #[test]
714    fn test_remove_step_preserves_valid_slots_between_noncontiguous_peer_runs() {
715        let local = did(0);
716        let removed = did(8);
717        let middle = did(16);
718        let tail = did(32);
719        let next = step(
720            &state(
721                local,
722                vec![removed],
723                None,
724                vec![Some(removed), Some(middle), Some(removed), Some(tail)],
725                0,
726            ),
727            TopologyEvent::Remove {
728                peer: removed,
729                successor: SuccessorRemoval::Preserve,
730            },
731            DEFAULT_SUCCESSOR_CAPACITY,
732        );
733
734        assert_eq!(next.state.fingers, vec![
735            Some(middle),
736            Some(middle),
737            Some(tail),
738            Some(tail)
739        ]);
740    }
741
742    #[test]
743    fn test_unavailable_head_without_live_fallback_clears_unverified_successor_tail() {
744        let local = did(0);
745        let removed = did(8);
746        let unverified = did(12);
747        let next = step(
748            &state(
749                local,
750                vec![removed, unverified],
751                None,
752                vec![Some(unverified)],
753                0,
754            ),
755            TopologyEvent::Remove {
756                peer: removed,
757                successor: SuccessorRemoval::ReplaceWith(Vec::new()),
758            },
759            DEFAULT_SUCCESSOR_CAPACITY,
760        );
761
762        assert!(next.state.successors.is_empty());
763        assert_eq!(next.state.fingers, vec![Some(unverified)]);
764        assert!(next.actions.is_empty());
765    }
766
767    #[test]
768    fn test_remove_step_replaces_unavailable_head_with_validated_successors_only() {
769        let local = did(0);
770        let removed = did(8);
771        let unverified = did(12);
772        let fallback = did(16);
773        let verified_tail = did(24);
774        let next = step(
775            &state(
776                local,
777                vec![removed, unverified, fallback, verified_tail],
778                None,
779                vec![Some(unverified), Some(fallback), Some(verified_tail)],
780                0,
781            ),
782            TopologyEvent::Remove {
783                peer: removed,
784                successor: SuccessorRemoval::ReplaceWith(vec![
785                    removed,
786                    verified_tail,
787                    fallback,
788                    fallback,
789                    local,
790                ]),
791            },
792            DEFAULT_SUCCESSOR_CAPACITY,
793        );
794
795        assert_eq!(next.state.successors, vec![fallback, verified_tail]);
796        assert_eq!(next.state.fingers, vec![
797            Some(unverified),
798            Some(fallback),
799            Some(verified_tail)
800        ]);
801        assert!(next.actions.is_empty());
802    }
803
804    #[test]
805    fn test_admit_step_commits_join_and_pending_fingers_in_one_state() {
806        let local = did(0);
807        let peer = did(16);
808        let next = step(
809            &state(local, Vec::new(), None, vec![None; 5], 0),
810            TopologyEvent::Admit {
811                peer,
812                fixed_fingers: vec![ConditionalFingerUpdate {
813                    index: 4,
814                    expected: None,
815                }],
816            },
817            DEFAULT_SUCCESSOR_CAPACITY,
818        );
819
820        assert_eq!(next.state.successors, vec![peer]);
821        assert_eq!(next.state.fingers, vec![
822            Some(peer),
823            Some(peer),
824            Some(peer),
825            Some(peer),
826            Some(peer)
827        ]);
828        assert_eq!(next.actions, vec![
829            TopologyAction::QuerySuccessorList(peer),
830            TopologyAction::FindSuccessorForConnect {
831                next: peer,
832                did: local
833            }
834        ]);
835    }
836
837    #[test]
838    fn test_admit_step_does_not_overwrite_finger_changed_after_update_was_deferred() {
839        let local = did(0);
840        let fresher = did(8);
841        let peer = did(16);
842        let next = step(
843            &state(local, vec![fresher], None, vec![Some(fresher); 5], 0),
844            TopologyEvent::Admit {
845                peer,
846                fixed_fingers: vec![ConditionalFingerUpdate {
847                    index: 4,
848                    expected: None,
849                }],
850            },
851            DEFAULT_SUCCESSOR_CAPACITY,
852        );
853
854        assert_eq!(next.state.fingers[4], Some(fresher));
855    }
856
857    #[test]
858    fn test_fix_finger_step_updates_local_successor_slot() {
859        let local = did(0);
860        let successor = did(8);
861        let next = step(
862            &state(local, vec![successor], None, vec![None; 4], 2),
863            TopologyEvent::FixFinger,
864            DEFAULT_SUCCESSOR_CAPACITY,
865        );
866
867        assert_eq!(next.state.fix_finger_index, 3);
868        assert_eq!(next.state.fingers, vec![None, None, None, Some(successor)]);
869        assert!(next.actions.is_empty());
870    }
871
872    #[test]
873    fn test_fix_finger_step_emits_indexed_remote_action() {
874        let local = did(0);
875        let successor = did(4);
876        let next_hop = did(6);
877        let next = step(
878            &state(
879                local,
880                vec![successor],
881                None,
882                vec![None, None, Some(next_hop), None],
883                2,
884            ),
885            TopologyEvent::FixFinger,
886            DEFAULT_SUCCESSOR_CAPACITY,
887        );
888
889        assert_eq!(next.state.fix_finger_index, 3);
890        assert_eq!(next.actions, vec![TopologyAction::FindSuccessorForFix {
891            next: next_hop,
892            did: Did::power_of_two(3),
893            index: 3
894        }]);
895    }
896
897    #[test]
898    fn test_fix_finger_step_queries_local_relative_probe() {
899        let local = did(100);
900        let successor = did(104);
901        let next_hop = did(106);
902        let next = step(
903            &state(
904                local,
905                vec![successor],
906                None,
907                vec![None, None, Some(next_hop), None],
908                2,
909            ),
910            TopologyEvent::FixFinger,
911            DEFAULT_SUCCESSOR_CAPACITY,
912        );
913
914        assert_eq!(next.state.fix_finger_index, 3);
915        assert_eq!(next.actions, vec![TopologyAction::FindSuccessorForFix {
916            next: next_hop,
917            did: local + Did::power_of_two(3),
918            index: 3
919        }]);
920    }
921
922    #[test]
923    fn test_apply_finger_step_updates_exact_slot() {
924        let local = did(0);
925        let successor = did(8);
926        let next = step(
927            &state(local, vec![], None, vec![None; 4], 0),
928            TopologyEvent::ApplyFinger {
929                index: 2,
930                successor,
931            },
932            DEFAULT_SUCCESSOR_CAPACITY,
933        );
934
935        assert_eq!(next.state.fingers, vec![None, None, Some(successor), None]);
936        assert!(next.actions.is_empty());
937    }
938
939    #[test]
940    fn test_apply_finger_step_ignores_self_and_out_of_range_slot() {
941        let local = did(0);
942        let current = state(local, vec![], None, vec![None; 2], 0);
943        let self_update = step(
944            &current,
945            TopologyEvent::ApplyFinger {
946                index: 1,
947                successor: local,
948            },
949            DEFAULT_SUCCESSOR_CAPACITY,
950        );
951        let out_of_range = step(
952            &current,
953            TopologyEvent::ApplyFinger {
954                index: 9,
955                successor: did(9),
956            },
957            DEFAULT_SUCCESSOR_CAPACITY,
958        );
959
960        assert_eq!(self_update.state, current);
961        assert_eq!(out_of_range.state, current);
962    }
963}