Skip to main content

rings_core/dht/
stabilization.rs

1//! Stabilization run daemons to maintain dht.
2
3use std::sync::Arc;
4
5use rings_transport::core::transport::WebrtcConnectionState;
6
7use crate::dht::entry::PlacedEntry;
8use crate::dht::successor::SuccessorReader;
9use crate::dht::types::ChordStorageRepair;
10use crate::dht::types::CorrectChord;
11use crate::dht::Chord;
12use crate::dht::Did;
13use crate::dht::PeerRing;
14use crate::dht::PeerRingAction;
15use crate::dht::PeerRingRemoteAction;
16use crate::error::Error;
17use crate::error::Result;
18use crate::message::FindSuccessorReportHandler;
19use crate::message::FindSuccessorSend;
20use crate::message::FindSuccessorThen;
21use crate::message::Message;
22use crate::message::MessagePayload;
23use crate::message::NotifyPredecessorSend;
24use crate::message::PayloadSender;
25use crate::message::QueryForTopoInfoSend;
26use crate::message::SyncEntriesWithSuccessor;
27use crate::swarm::transport::SwarmTransport;
28
29/// The stabilization runner.
30#[derive(Clone)]
31pub struct Stabilizer {
32    transport: Arc<SwarmTransport>,
33    dht: Arc<PeerRing>,
34}
35
36impl Stabilizer {
37    /// Create a new stabilization runner.
38    pub fn new(transport: Arc<SwarmTransport>) -> Self {
39        let dht = transport.dht.clone();
40        Self { transport, dht }
41    }
42
43    /// Run stabilization once.
44    pub async fn stabilize(&self) -> Result<()> {
45        tracing::debug!("STABILIZATION notify_predecessor start");
46        if let Err(e) = self.notify_predecessor().await {
47            tracing::error!("[stabilize] Failed on notify predecessor {:?}", e);
48        }
49        tracing::debug!("STABILIZATION notify_predecessor end");
50        tracing::debug!("STABILIZATION fix_fingers start");
51        if let Err(e) = self.fix_fingers().await {
52            tracing::error!("[stabilize] Failed on fix_finger {:?}", e);
53        }
54        tracing::debug!("STABILIZATION fix_fingers end");
55        tracing::debug!("STABILIZATION clean_unavailable_connections start");
56        if let Err(e) = self.clean_unavailable_connections().await {
57            tracing::error!(
58                "[stabilize] Failed on clean unavailable connections {:?}",
59                e
60            );
61        }
62        tracing::debug!("STABILIZATION clean_unavailable_connections end");
63        // Default HMCC/Zave stabilization path. The pure operation is specified
64        // as `CorrectStabilize` in tests/default/dht_convergence.rs.
65        tracing::debug!("STABILIZATION correct_stabilize start");
66        if let Err(e) = self.correct_stabilize().await {
67            tracing::error!("[stabilize] Failed on call correct stabilize {:?}", e);
68        }
69        tracing::debug!("STABILIZATION correct_stabilize end");
70        tracing::debug!("STABILIZATION repair_storage start");
71        if let Err(e) = self.repair_storage().await {
72            tracing::error!("[stabilize] Failed on repair storage {:?}", e);
73        }
74        tracing::debug!("STABILIZATION repair_storage end");
75        Ok(())
76    }
77
78    fn collect_storage_repair_actions(
79        act: PeerRingAction,
80        out: &mut Vec<(Did, Vec<PlacedEntry>)>,
81    ) -> Result<()> {
82        match act {
83            PeerRingAction::None => Ok(()),
84            PeerRingAction::RemoteAction(
85                destination,
86                PeerRingRemoteAction::SyncEntriesWithSuccessor(data),
87            ) => {
88                out.push((destination, data));
89                Ok(())
90            }
91            PeerRingAction::MultiActions(actions) => {
92                for action in actions {
93                    Self::collect_storage_repair_actions(action, out)?;
94                }
95                Ok(())
96            }
97            action => {
98                tracing::error!("Invalid storage repair action: {action:?}");
99                Err(crate::error::Error::unexpected_peer_ring_action(action))
100            }
101        }
102    }
103
104    async fn handle_storage_repair_action(&self, act: PeerRingAction) -> Result<()> {
105        let mut messages = Vec::new();
106        Self::collect_storage_repair_actions(act, &mut messages)?;
107        for (destination, data) in messages {
108            self.transport
109                .send_message(
110                    Message::SyncEntriesWithSuccessor(SyncEntriesWithSuccessor { data }),
111                    destination,
112                )
113                .await?;
114        }
115        Ok(())
116    }
117
118    /// Republish locally-held entries to their current affine owners.
119    pub async fn repair_storage(&self) -> Result<()> {
120        let action = self
121            .dht
122            .republish_local_entries(self.transport.storage_redundancy())
123            .await?;
124        self.handle_storage_repair_action(action).await
125    }
126
127    /// Clean unavailable connections in transport.
128    pub async fn clean_unavailable_connections(&self) -> Result<()> {
129        let conns = self.transport.get_connections();
130
131        for (did, conn) in conns.into_iter() {
132            // Only terminal states are cleaned. `Disconnected` is transient: ICE
133            // can recover from it, so tearing it down here (the stabilizer runs
134            // every few seconds) would kill connections during a brief blip
135            // before WebRTC self-heals. This mirrors the swarm callback, which
136            // also only leaves the DHT on `Failed`/`Closed`.
137            if matches!(
138                conn.webrtc_connection_state(),
139                WebrtcConnectionState::Failed | WebrtcConnectionState::Closed
140            ) {
141                tracing::info!("STABILIZATION clean_unavailable_transports: {:?}", did);
142                let should_repair = self
143                    .dht
144                    .peer_may_share_storage_responsibility(did, self.transport.storage_redundancy())
145                    .await?;
146                self.transport.disconnect(did).await?;
147                if should_repair {
148                    self.repair_storage().await?;
149                }
150            }
151        }
152
153        Ok(())
154    }
155
156    /// Notify predecessor, this is a DHT operation.
157    pub async fn notify_predecessor(&self) -> Result<()> {
158        let (successor_min, successor_list) = {
159            let successor = self.dht.successors();
160            (successor.min()?, successor.list()?)
161        };
162
163        let msg = Message::NotifyPredecessorSend(NotifyPredecessorSend { did: self.dht.did });
164        if self.dht.did != successor_min {
165            for s in successor_list {
166                tracing::debug!("STABILIZATION notify_predecessor: {:?}", s);
167                let payload =
168                    MessagePayload::new_send(msg.clone(), self.transport.session_sk(), s, s)?;
169                self.transport.send_payload(payload).await?;
170            }
171            Ok(())
172        } else {
173            Ok(())
174        }
175    }
176
177    /// Fix fingers from finger table, this is a DHT operation.
178    async fn fix_fingers(&self) -> Result<()> {
179        match self.dht.fix_fingers() {
180            Ok(action) => match action {
181                PeerRingAction::None => Ok(()),
182                PeerRingAction::RemoteAction(
183                    closest_predecessor,
184                    PeerRingRemoteAction::FindSuccessorForFix {
185                        did: finger_did,
186                        index,
187                    },
188                ) => {
189                    tracing::debug!("STABILIZATION fix_fingers: {:?}", finger_did);
190                    let msg = Message::FindSuccessorSend(FindSuccessorSend {
191                        did: finger_did,
192                        then: FindSuccessorThen::Report(
193                            FindSuccessorReportHandler::FixFingerTable { index },
194                        ),
195                        strict: false,
196                    });
197                    let payload = MessagePayload::new_send(
198                        msg.clone(),
199                        self.transport.session_sk(),
200                        closest_predecessor,
201                        closest_predecessor,
202                    )?;
203                    self.transport.send_payload(payload).await?;
204                    Ok(())
205                }
206                _ => {
207                    tracing::error!("Invalid PeerRing Action");
208                    Err(Error::PeerRingInvalidAction)
209                }
210            },
211            Err(e) => {
212                tracing::error!("{:?}", e);
213                Err(e)
214            }
215        }
216    }
217
218    /// Call stabilization from correct chord implementation
219    pub async fn correct_stabilize(&self) -> Result<()> {
220        if let PeerRingAction::RemoteAction(
221            next,
222            PeerRingRemoteAction::QueryForSuccessorListAndPred,
223        ) = self.dht.pre_stabilize()?
224        {
225            self.transport
226                .send_direct_message(
227                    Message::QueryForTopoInfoSend(QueryForTopoInfoSend::new_for_stab(next)),
228                    next,
229                )
230                .await?;
231        }
232        Ok(())
233    }
234}
235
236#[cfg(not(feature = "wasm"))]
237mod stabilizer {
238    use std::sync::Arc;
239    use std::time::Duration;
240
241    use futures::future::FutureExt;
242    use futures::pin_mut;
243    use futures::select;
244    use futures_timer::Delay;
245
246    use super::*;
247
248    impl Stabilizer {
249        /// Run stabilization in a loop.
250        pub async fn wait(self: Arc<Self>, interval: Duration) {
251            loop {
252                let timeout = Delay::new(interval).fuse();
253                pin_mut!(timeout);
254                select! {
255                    _ = timeout => self
256                        .stabilize()
257                        .await
258                        .unwrap_or_else(|e| tracing::error!("failed to stabilize {:?}", e)),
259                }
260            }
261        }
262    }
263}
264
265#[cfg(feature = "wasm")]
266mod stabilizer {
267    use std::sync::Arc;
268    use std::time::Duration;
269
270    use super::*;
271    use crate::poll;
272
273    impl Stabilizer {
274        /// Run stabilization in a loop.
275        pub async fn wait(self: Arc<Self>, interval: Duration) {
276            let millis = i32::try_from(interval.as_millis()).unwrap_or(i32::MAX);
277            let stabilizer = self;
278            poll!(
279                {
280                    let stabilizer = Arc::clone(&stabilizer);
281                    async move {
282                        stabilizer
283                            .stabilize()
284                            .await
285                            .unwrap_or_else(|e| tracing::error!("failed to stabilize {:?}", e));
286                    }
287                },
288                millis
289            );
290        }
291    }
292}