Skip to main content

rings_core/message/handlers/
connection.rs

1use async_trait::async_trait;
2
3use crate::dht::types::Chord;
4use crate::dht::types::CorrectChord;
5use crate::dht::PeerRingAction;
6use crate::dht::TopoInfo;
7use crate::error::Error;
8use crate::error::Result;
9use crate::message::effects::PayloadRelayFunctor;
10use crate::message::types::ConnectNodeReport;
11use crate::message::types::ConnectNodeSend;
12use crate::message::types::FindSuccessorReport;
13use crate::message::types::FindSuccessorSend;
14use crate::message::types::Message;
15use crate::message::types::QueryForTopoInfoReport;
16use crate::message::types::QueryForTopoInfoSend;
17use crate::message::types::Then;
18use crate::message::FindSuccessorReportHandler;
19use crate::message::FindSuccessorThen;
20use crate::message::HandleMsg;
21use crate::message::MessageHandler;
22use crate::message::MessagePayload;
23
24/// QueryForTopoInfoSend is direct message
25#[cfg_attr(feature = "wasm", async_trait(?Send))]
26#[cfg_attr(not(feature = "wasm"), async_trait)]
27impl HandleMsg<QueryForTopoInfoSend> for MessageHandler {
28    async fn handle(&self, ctx: &MessagePayload, msg: &QueryForTopoInfoSend) -> Result<()> {
29        let info: TopoInfo = TopoInfo::try_from(self.dht.as_ref())?;
30        if msg.targets(self.dht.did) {
31            self.run_effects([PayloadRelayFunctor::send_report_message(
32                ctx,
33                Message::QueryForTopoInfoReport(msg.resp(info)),
34            )
35            .into()])
36                .await?
37        }
38        Ok(())
39    }
40}
41
42/// Try join received node into DHT after received from TopoInfo.
43#[cfg_attr(feature = "wasm", async_trait(?Send))]
44#[cfg_attr(not(feature = "wasm"), async_trait)]
45impl HandleMsg<QueryForTopoInfoReport> for MessageHandler {
46    async fn handle(&self, _ctx: &MessagePayload, msg: &QueryForTopoInfoReport) -> Result<()> {
47        match msg.then {
48            <QueryForTopoInfoReport as Then>::Then::SyncSuccessor => {
49                for peer in msg.info.successors.iter() {
50                    if self.transport.get_connection(*peer).is_some() {
51                        self.join_dht(*peer).await?;
52                    }
53                }
54            }
55            <QueryForTopoInfoReport as Then>::Then::Stabilization => {
56                // Establish stabilization-learned candidates first so the
57                // resulting Notify/Query actions can usually send immediately.
58                let candidates = msg
59                    .info
60                    .predecessor
61                    .into_iter()
62                    .chain(msg.info.successors.iter().copied());
63                self.connect_dht_peers(candidates).await?;
64                let ev = self.dht.stabilize(msg.info.clone())?;
65                self.handle_dht_events(&ev).await?;
66            }
67        }
68        Ok(())
69    }
70}
71
72#[cfg_attr(feature = "wasm", async_trait(?Send))]
73#[cfg_attr(not(feature = "wasm"), async_trait)]
74impl HandleMsg<ConnectNodeSend> for MessageHandler {
75    async fn handle(&self, ctx: &MessagePayload, msg: &ConnectNodeSend) -> Result<()> {
76        if msg.network_id != self.transport.network_id {
77            return Ok(());
78        }
79
80        if ctx.should_forward_from(self.dht.did) {
81            self.run_effects([PayloadRelayFunctor::forward_payload(ctx, None).into()])
82                .await
83        } else {
84            let answer = self
85                .transport
86                .answer_remote_connection(
87                    ctx.relay.try_origin_sender()?,
88                    self.inner_callback(),
89                    msg,
90                )
91                .await?;
92            self.run_effects([PayloadRelayFunctor::send_report_message(
93                ctx,
94                Message::ConnectNodeReport(answer),
95            )
96            .into()])
97                .await
98        }
99    }
100}
101
102#[cfg_attr(feature = "wasm", async_trait(?Send))]
103#[cfg_attr(not(feature = "wasm"), async_trait)]
104impl HandleMsg<ConnectNodeReport> for MessageHandler {
105    async fn handle(&self, ctx: &MessagePayload, msg: &ConnectNodeReport) -> Result<()> {
106        if ctx.should_forward_from(self.dht.did) {
107            self.run_effects([PayloadRelayFunctor::forward_payload(ctx, None).into()])
108                .await
109        } else {
110            self.transport
111                .accept_remote_connection(ctx.relay.try_origin_sender()?, msg)
112                .await
113        }
114    }
115}
116
117#[cfg_attr(feature = "wasm", async_trait(?Send))]
118#[cfg_attr(not(feature = "wasm"), async_trait)]
119impl HandleMsg<FindSuccessorSend> for MessageHandler {
120    async fn handle(&self, ctx: &MessagePayload, msg: &FindSuccessorSend) -> Result<()> {
121        match self.dht.find_successor(msg.did)? {
122            PeerRingAction::Some(did) => {
123                if msg.accepts_local_successor(self.dht.did) {
124                    match &msg.then {
125                        FindSuccessorThen::Report(handler) => {
126                            self.run_effects([PayloadRelayFunctor::send_report_message(
127                                ctx,
128                                Message::FindSuccessorReport(FindSuccessorReport {
129                                    did,
130                                    handler: handler.clone(),
131                                }),
132                            )
133                            .into()])
134                                .await
135                        }
136                    }
137                } else {
138                    self.run_effects([PayloadRelayFunctor::forward_payload(ctx, Some(did)).into()])
139                        .await
140                }
141            }
142            PeerRingAction::RemoteAction(next, _) => {
143                self.run_effects([PayloadRelayFunctor::reset_destination(ctx, next).into()])
144                    .await
145            }
146            act => Err(Error::unexpected_peer_ring_action(act)),
147        }
148    }
149}
150
151#[cfg_attr(feature = "wasm", async_trait(?Send))]
152#[cfg_attr(not(feature = "wasm"), async_trait)]
153impl HandleMsg<FindSuccessorReport> for MessageHandler {
154    async fn handle(&self, ctx: &MessagePayload, msg: &FindSuccessorReport) -> Result<()> {
155        if ctx.should_forward_from(self.dht.did) {
156            return self
157                .run_effects([PayloadRelayFunctor::forward_payload(ctx, None).into()])
158                .await;
159        }
160
161        match &msg.handler {
162            FindSuccessorReportHandler::FixFingerTable { index } => {
163                self.dht.apply_fixed_finger(*index, msg.did)?;
164                if msg.reports_remote_successor(self.dht.did) {
165                    self.connect_dht_peer(msg.did).await?;
166                }
167            }
168            FindSuccessorReportHandler::Connect => {
169                if msg.reports_remote_successor(self.dht.did) {
170                    self.connect_dht_peer(msg.did).await?;
171                }
172            }
173            _ => {}
174        }
175
176        Ok(())
177    }
178}
179
180#[cfg(not(feature = "wasm"))]
181#[cfg(test)]
182pub mod tests {
183    //! tests
184    use tokio::time::sleep;
185    use tokio::time::Duration;
186
187    use super::*;
188    use crate::dht::successor::SuccessorReader;
189    use crate::ecc::tests::gen_ordered_keys;
190    use crate::ecc::SecretKey;
191    use crate::tests::default::assert_no_more_msg;
192    use crate::tests::default::prepare_node;
193    use crate::tests::default::wait_for_msgs;
194    use crate::tests::default::Node;
195    use crate::tests::manually_establish_connection;
196
197    // node1.key < node2.key < node3.key
198    //
199    // Firstly, we connect node1 to node2, node2 to node3.
200    // Then, we connect node1 to node3 via DHT.
201    //
202    // After full connected, the topological structure should be:
203    //
204    // Node1 ------------ Node2 ------------ Node3
205    //   |-------------------------------------|
206    //
207    // --------- Connect node1 and node2
208    // 0. Node1 and node2 will set each other as their successor in DHTJoin handler.
209    //
210    // 1. Node1 send FindSuccessorSend(node1) to node2.
211    //    Meanwhile, node2 send FindSuccessorSend(node2) to node1.
212    //
213    // 2. Node1 respond by sending FindSuccessorReport(node2) to node2.
214    //    Meanwhile, node2 respond by sending FindSuccessorReport(node1) to node1.
215    //    But no node should update local successor by those reports.
216    //
217    // --------- Join node3 to node2
218    // 0. Node2 and node3 will set each other as their successor in DHTJoin handler.
219    //
220    // 1. Node3 send FindSuccessorSend(node3) to node2.
221    //    Meanwhile, node2 send FindSuccessorSend(node2) to node3.
222    //
223    // 2. Node3 respond by sending FindSuccessorReport(node2) to node2.
224    //    Meanwhile, node2 respond by sending FindSuccessorReport(node3) to node3.
225    //    But no node should update local successor by those reports.
226    //
227    // --------- Connect node1 to node3 via DHT
228    // 0. After checking finger table locally, node1 pick node2 to send ConnectNodeSend(node3).
229    //
230    // 1. Node2 relay ConnectNodeSend(node3) to node3.
231    //
232    // 2. Node3 respond by sending ConnectNodeReport(node1) to node2.
233    //
234    // 3. Node2 relay ConnectNodeReport(node1) to node1.
235    //
236    // --------- Communications after successful connection
237    //
238    #[tokio::test]
239    async fn test_triple_nodes_connection_1_2_3() -> Result<()> {
240        let keys = gen_ordered_keys(3);
241        let (key1, key2, key3) = (keys[0], keys[1], keys[2]);
242        test_triple_ordered_nodes_connection(key1, key2, key3).await?;
243        Ok(())
244    }
245
246    // The 2_3_1 should have same behavior as 1_2_3 since they are all clockwise.
247    #[tokio::test]
248    async fn test_triple_nodes_connection_2_3_1() -> Result<()> {
249        let keys = gen_ordered_keys(3);
250        let (key1, key2, key3) = (keys[0], keys[1], keys[2]);
251        test_triple_ordered_nodes_connection(key2, key3, key1).await?;
252        Ok(())
253    }
254
255    // The 3_1_2 should have same behavior as 1_2_3 since they are all clockwise.
256    #[tokio::test]
257    async fn test_triple_nodes_connection_3_1_2() -> Result<()> {
258        let keys = gen_ordered_keys(3);
259        let (key1, key2, key3) = (keys[0], keys[1], keys[2]);
260        test_triple_ordered_nodes_connection(key3, key1, key2).await?;
261        Ok(())
262    }
263
264    // node1.key > node2.key > node3.key
265    //
266    // All the processes are the same as test_triple_nodes_1_2_3. Except the following:
267    //
268    // --------- Join node3 to node2
269    // 0. Node3 will set node2 as successor in DHTJoin handler.
270    //
271    //    Node2 will not set node3 as successor in DHTJoin handler.
272    //    Because node2.processor.max() is node1, and node1.bias(node1) < node1.bias(node3).
273    //    That means node1 is closer to node2 than node3 on the clock circle.
274    //
275    // 1. Node3 send FindSuccessorSend(node3) to node2. Node2 relay it to Node1.
276    //    Meanwhile, node2 send FindSuccessorSend(node2) to node3.
277    //
278    // 2. Node3 respond by sending FindSuccessorReport(node2) to node2.
279    //    Meanwhile, node1 respond by sending FindSuccessorReport(node2) to node3 through node2.
280    //
281    // --------- Communications after successful connection
282    //
283    #[tokio::test]
284    async fn test_triple_nodes_connection_3_2_1() -> Result<()> {
285        let keys = gen_ordered_keys(3);
286        let (key1, key2, key3) = (keys[0], keys[1], keys[2]);
287        test_triple_desc_ordered_nodes_connection(key3, key2, key1).await?;
288        Ok(())
289    }
290
291    // The 2_1_3 should have same behavior as 3_2_1 since they are all anti-clockwise.
292    #[tokio::test]
293    async fn test_triple_nodes_connection_2_1_3() -> Result<()> {
294        let keys = gen_ordered_keys(3);
295        let (key1, key2, key3) = (keys[0], keys[1], keys[2]);
296        test_triple_desc_ordered_nodes_connection(key2, key1, key3).await?;
297        Ok(())
298    }
299
300    // The 1_3_2 should have same behavior as 3_2_1 since they are all anti-clockwise.
301    #[tokio::test]
302    async fn test_triple_nodes_connection_1_3_2() -> Result<()> {
303        let keys = gen_ordered_keys(3);
304        let (key1, key2, key3) = (keys[0], keys[1], keys[2]);
305        test_triple_desc_ordered_nodes_connection(key1, key3, key2).await?;
306        Ok(())
307    }
308
309    async fn test_triple_ordered_nodes_connection(
310        key1: SecretKey,
311        key2: SecretKey,
312        key3: SecretKey,
313    ) -> Result<(Node, Node, Node)> {
314        let node1 = prepare_node(key1).await;
315        let node2 = prepare_node(key2).await;
316        let node3 = prepare_node(key3).await;
317
318        println!("========================================");
319        println!("||  now we connect node1 and node2    ||");
320        println!("========================================");
321
322        manually_establish_connection(&node1.swarm, &node2.swarm).await;
323        wait_for_msgs([&node1, &node2, &node3]).await;
324        assert_no_more_msg([&node1, &node2, &node3]).await;
325
326        node1.assert_transports(vec![node2.did()]);
327        node2.assert_transports(vec![node1.did()]);
328        node3.assert_transports(vec![]);
329        assert_eq!(node1.dht().successors().list()?, vec![node2.did()]);
330        assert_eq!(node2.dht().successors().list()?, vec![node1.did()]);
331        assert_eq!(node3.dht().successors().list()?, vec![]);
332
333        println!("========================================");
334        println!("||  now we start join node3 to node2  ||");
335        println!("========================================");
336
337        manually_establish_connection(&node3.swarm, &node2.swarm).await;
338        wait_for_msgs([&node1, &node2, &node3]).await;
339        assert_no_more_msg([&node1, &node2, &node3]).await;
340
341        println!("=== Check state before connect via DHT ===");
342        node1.assert_transports(vec![node2.did()]);
343        node2.assert_transports(vec![node1.did(), node3.did()]);
344        node3.assert_transports(vec![node2.did()]);
345        assert_eq!(node1.dht().successors().list()?, vec![node2.did(),]);
346        assert_eq!(node2.dht().successors().list()?, vec![
347            node3.did(),
348            node1.did()
349        ]);
350        assert_eq!(node3.dht().successors().list()?, vec![node2.did()]);
351
352        println!("=============================================");
353        println!("||  now we connect node1 to node3 via DHT  ||");
354        println!("=============================================");
355
356        // check node1 and node3 is not connected to each other
357        assert!(node1.swarm.transport.get_connection(node3.did()).is_none());
358        // node1's successor should be node2 now
359        assert_eq!(node1.dht().successors().max()?, node2.did());
360
361        node1.swarm.connect(node3.did()).await?;
362        wait_for_msgs([&node1, &node2, &node3]).await;
363        assert_no_more_msg([&node1, &node2, &node3]).await;
364
365        println!("=== Check state after connect via DHT ===");
366        node1.assert_transports(vec![node2.did(), node3.did()]);
367        node2.assert_transports(vec![node1.did(), node3.did()]);
368        node3.assert_transports(vec![node1.did(), node2.did()]);
369        assert_eq!(node1.dht().successors().list()?, vec![
370            node2.did(),
371            node3.did()
372        ]);
373        assert_eq!(node2.dht().successors().list()?, vec![
374            node3.did(),
375            node1.did()
376        ]);
377        assert_eq!(node3.dht().successors().list()?, vec![
378            node1.did(),
379            node2.did()
380        ]);
381
382        Ok((node1, node2, node3))
383    }
384
385    async fn test_triple_desc_ordered_nodes_connection(
386        key1: SecretKey,
387        key2: SecretKey,
388        key3: SecretKey,
389    ) -> Result<(Node, Node, Node)> {
390        let node1 = prepare_node(key1).await;
391        let node2 = prepare_node(key2).await;
392        let node3 = prepare_node(key3).await;
393
394        println!("========================================");
395        println!("||  now we connect node1 and node2    ||");
396        println!("========================================");
397
398        manually_establish_connection(&node1.swarm, &node2.swarm).await;
399        wait_for_msgs([&node1, &node2, &node3]).await;
400        assert_no_more_msg([&node1, &node2, &node3]).await;
401
402        assert_eq!(node1.dht().successors().list()?, vec![node2.did()]);
403        assert_eq!(node2.dht().successors().list()?, vec![node1.did()]);
404        assert_eq!(node3.dht().successors().list()?, vec![]);
405
406        println!("========================================");
407        println!("||  now we start join node3 to node2  ||");
408        println!("========================================");
409
410        manually_establish_connection(&node3.swarm, &node2.swarm).await;
411        wait_for_msgs([&node1, &node2, &node3]).await;
412        assert_no_more_msg([&node1, &node2, &node3]).await;
413
414        println!("=== Check state before connect via DHT ===");
415        node1.assert_transports(vec![node2.did()]);
416        node2.assert_transports(vec![node1.did(), node3.did()]);
417        node3.assert_transports(vec![node2.did()]);
418        assert_eq!(node1.dht().successors().list()?, vec![node2.did()]);
419        assert_eq!(node2.dht().successors().list()?, vec![
420            node1.did(),
421            node3.did()
422        ]);
423        assert_eq!(node3.dht().successors().list()?, vec![node2.did()]);
424
425        println!("=============================================");
426        println!("||  now we connect node1 to node3 via DHT  ||");
427        println!("=============================================");
428
429        // check node1 and node3 is not connected to each other
430        assert!(node1.swarm.transport.get_connection(node3.did()).is_none());
431        // node1's successor should be node2 now
432        assert_eq!(node1.dht().successors().max()?, node2.did());
433
434        node1.swarm.connect(node3.did()).await?;
435        wait_for_msgs([&node1, &node2, &node3]).await;
436        assert_no_more_msg([&node1, &node2, &node3]).await;
437
438        println!("=== Check state after connect via DHT ===");
439        node1.assert_transports(vec![node2.did(), node3.did()]);
440        node2.assert_transports(vec![node1.did(), node3.did()]);
441        node3.assert_transports(vec![node1.did(), node2.did()]);
442        assert_eq!(node1.dht().successors().list()?, vec![
443            node3.did(),
444            node2.did()
445        ]);
446        assert_eq!(node2.dht().successors().list()?, vec![
447            node1.did(),
448            node3.did()
449        ]);
450        assert_eq!(node3.dht().successors().list()?, vec![
451            node2.did(),
452            node1.did()
453        ]);
454
455        Ok((node1, node2, node3))
456    }
457
458    #[tokio::test]
459    async fn test_fourth_node_connection() -> Result<()> {
460        let keys = gen_ordered_keys(4);
461        let (key1, key2, key3, key4) = (keys[0], keys[1], keys[2], keys[3]);
462        let (node1, node2, node3) = test_triple_ordered_nodes_connection(key1, key2, key3).await?;
463        // we now have three connected nodes
464        // node1 -> node2 -> node3
465        //  |-<-----<---------<--|
466
467        let node4 = prepare_node(key4).await;
468
469        // Unless we use a fixed did value, we cannot fully predict the communication order between node4 and the nodes,
470        // because we do not know the distance between node4 and each node.
471        //
472        // Therefore, here we only guarantee that messages can be processed correctly without checking the specific message order.
473        //
474        // In addition, we check the final state to ensure the entire process meets expectations.
475
476        // connect node4 to node2
477        manually_establish_connection(&node4.swarm, &node2.swarm).await;
478        // Poll for convergence rather than sleeping a fixed amount: under the
479        // release-LTO CI run with native WebRTC, 6s is not always enough and the
480        // assertions below would flake. The expected final state is unchanged.
481        wait_until("node4 joined: DHT successors converged", || {
482            Ok(
483                node1.dht().successors().list()? == vec![node2.did(), node3.did(), node4.did()]
484                    && node2.dht().successors().list()?
485                        == vec![node3.did(), node4.did(), node1.did()]
486                    && node3.dht().successors().list()? == vec![node1.did(), node2.did()]
487                    && node4.dht().successors().list()? == vec![node1.did(), node2.did()],
488            )
489        })
490        .await?;
491
492        println!("=== Check state before connect via DHT ===");
493        node1.assert_transports(vec![node2.did(), node3.did(), node4.did()]);
494        node2.assert_transports(vec![node3.did(), node4.did(), node1.did()]);
495        node3.assert_transports(vec![node1.did(), node2.did()]);
496        // node4 will connect node1 after connecting node2, because node2 notified node4 that node1 is its predecessor.
497        node4.assert_transports(vec![node1.did(), node2.did()]);
498        assert_eq!(node1.dht().successors().list()?, vec![
499            node2.did(),
500            node3.did(),
501            node4.did(),
502        ]);
503        assert_eq!(node2.dht().successors().list()?, vec![
504            node3.did(),
505            node4.did(),
506            node1.did(),
507        ]);
508        assert_eq!(node3.dht().successors().list()?, vec![
509            node1.did(),
510            node2.did(),
511        ]);
512        assert_eq!(node4.dht().successors().list()?, vec![
513            node1.did(),
514            node2.did(),
515        ]);
516
517        println!("========================================");
518        println!("| test node4 connect node3 via dht     |");
519        println!("========================================");
520        println!(
521            "node1.did(): {:?}, node2.did(): {:?}, node3.did(): {:?}, node4.did(): {:?}",
522            node1.did(),
523            node2.did(),
524            node3.did(),
525            node4.did(),
526        );
527        println!("==================================================");
528
529        node4.swarm.connect(node3.did()).await?;
530        // Same as above: poll for the post-connect converged state instead of a
531        // fixed 6s sleep so the test is robust under CI contention.
532        wait_until("node4 connected node3: DHT successors converged", || {
533            Ok(
534                node1.dht().successors().list()? == vec![node2.did(), node3.did(), node4.did()]
535                    && node2.dht().successors().list()?
536                        == vec![node3.did(), node4.did(), node1.did()]
537                    && node3.dht().successors().list()?
538                        == vec![node4.did(), node1.did(), node2.did()]
539                    && node4.dht().successors().list()?
540                        == vec![node1.did(), node2.did(), node3.did()],
541            )
542        })
543        .await?;
544
545        println!("=== Check state after connect via DHT ===");
546        node1.assert_transports(vec![node2.did(), node3.did(), node4.did()]);
547        node2.assert_transports(vec![node3.did(), node4.did(), node1.did()]);
548        node3.assert_transports(vec![node4.did(), node1.did(), node2.did()]);
549        node4.assert_transports(vec![node1.did(), node2.did(), node3.did()]);
550        assert_eq!(node1.dht().successors().list()?, vec![
551            node2.did(),
552            node3.did(),
553            node4.did()
554        ]);
555        assert_eq!(node2.dht().successors().list()?, vec![
556            node3.did(),
557            node4.did(),
558            node1.did(),
559        ]);
560        assert_eq!(node3.dht().successors().list()?, vec![
561            node4.did(),
562            node1.did(),
563            node2.did(),
564        ]);
565        assert_eq!(node4.dht().successors().list()?, vec![
566            node1.did(),
567            node2.did(),
568            node3.did(),
569        ]);
570
571        Ok(())
572    }
573
574    /// Poll `cond` every 200ms until it returns true, failing after ~60s.
575    /// Used instead of fixed sleeps so the test is deterministic regardless of
576    /// how long the WebRTC handshake/teardown takes on a given machine.
577    ///
578    /// The window is generous on purpose: ICE paces connectivity checks at
579    /// ~200ms each, so on a host with many network interfaces (lots of
580    /// candidate pairs) establishing the connection can legitimately take ~20s.
581    async fn wait_until(msg: &str, mut cond: impl FnMut() -> Result<bool>) -> Result<()> {
582        for _ in 0..300 {
583            if cond()? {
584                return Ok(());
585            }
586            sleep(Duration::from_millis(200)).await;
587        }
588        Err(Error::InvalidMessage(format!("timeout waiting for: {msg}")))
589    }
590
591    #[tokio::test]
592    async fn test_finger_when_disconnect() -> Result<()> {
593        let key1 = SecretKey::random();
594        let key2 = SecretKey::random();
595
596        let node1 = prepare_node(key1).await;
597        let node2 = prepare_node(key2).await;
598
599        {
600            assert!(node1.dht().lock_finger()?.is_empty());
601            assert!(node1.dht().lock_finger()?.is_empty());
602        }
603
604        manually_establish_connection(&node1.swarm, &node2.swarm).await;
605
606        // The data channels open and `on_data_channel_open -> join_dht` runs
607        // asynchronously, so poll until both sides have joined each other rather
608        // than asserting after a fixed wait.
609        wait_until("node1 and node2 to join each other's DHT", || {
610            let finger1 = node1.dht().lock_finger()?.clone().clone_finger();
611            let finger2 = node2.dht().lock_finger()?.clone().clone_finger();
612            Ok(finger1.into_iter().any(|x| x == Some(node2.did()))
613                && finger2.into_iter().any(|x| x == Some(node1.did())))
614        })
615        .await?;
616
617        node1.assert_transports(vec![node2.did()]);
618        node2.assert_transports(vec![node1.did()]);
619
620        println!("===================================");
621        println!("| test disconnect node1 and node2 |");
622        println!("===================================");
623        node1.swarm.disconnect(node2.did()).await?;
624
625        // node1 closes locally; node2 learns via the data channel closing and
626        // tears its side down promptly (without waiting for the ICE `Failed`
627        // timeout). Poll until both sides have removed the connection.
628        wait_until("both sides to drop the connection", || {
629            Ok(node1.swarm.transport.get_connection(node2.did()).is_none()
630                && node2.swarm.transport.get_connection(node1.did()).is_none())
631        })
632        .await?;
633
634        node1.assert_transports(vec![]);
635        node2.assert_transports(vec![]);
636        {
637            let finger1 = node1.dht().lock_finger()?.clone().clone_finger();
638            let finger2 = node2.dht().lock_finger()?.clone().clone_finger();
639            assert!(finger1.into_iter().all(|x| x.is_none()));
640            assert!(finger2.into_iter().all(|x| x.is_none()));
641        }
642
643        Ok(())
644    }
645}