Skip to main content

rings_core/message/handlers/
storage.rs

1#![warn(missing_docs)]
2
3use std::sync::Arc;
4
5use async_recursion::async_recursion;
6use async_trait::async_trait;
7
8use crate::dht::entry::Entry;
9use crate::dht::entry::SyncedEntryAck;
10use crate::dht::Chord;
11use crate::dht::ChordStorage;
12use crate::dht::ChordStorageCache;
13use crate::dht::ChordStorageRepair;
14use crate::dht::ChordStorageSync;
15use crate::dht::Did;
16use crate::dht::PeerRing;
17use crate::dht::PeerRingAction;
18use crate::dht::PeerRingRemoteAction;
19use crate::error::Error;
20use crate::error::Result;
21use crate::message::effects::PayloadRelayFunctor;
22use crate::message::types::FoundEntry;
23use crate::message::types::Message;
24use crate::message::types::SearchEntry;
25use crate::message::types::SyncEntriesWithSuccessor;
26use crate::message::types::SyncEntriesWithSuccessorReport;
27use crate::message::Encoded;
28use crate::message::HandleMsg;
29use crate::message::MessageHandler;
30use crate::message::MessagePayload;
31use crate::message::PayloadSender;
32use crate::prelude::entry::EntryOperation;
33use crate::swarm::transport::SwarmTransport;
34use crate::swarm::Swarm;
35
36/// ChordStorageInterface should imply necessary method for DHT storage
37#[cfg_attr(feature = "wasm", async_trait(?Send))]
38#[cfg_attr(not(feature = "wasm"), async_trait)]
39pub trait ChordStorageInterface<const REDUNDANT: u16> {
40    /// Fetch an entry from DHT storage.
41    async fn storage_fetch(&self, entry_key: Did) -> Result<()>;
42    /// Store an entry on DHT storage.
43    async fn storage_store(&self, entry: Entry) -> Result<()>;
44    /// Append data to a Data kind entry.
45    async fn storage_append_data(&self, topic: &str, data: Encoded) -> Result<()>;
46    /// Append data to a Data kind entry uniquely.
47    async fn storage_touch_data(&self, topic: &str, data: Encoded) -> Result<()>;
48}
49
50/// ChordStorageInterfaceCacheChecker defines the interface for checking the local cache of the DHT.
51#[cfg_attr(feature = "wasm", async_trait(?Send))]
52#[cfg_attr(not(feature = "wasm"), async_trait)]
53pub trait ChordStorageInterfaceCacheChecker {
54    /// Check the local cache of the DHT for a specific entry key.
55    ///
56    /// Returns an optional `Entry` representing the cached data, or `None` if it is not found.
57    async fn storage_check_cache(&self, entry_key: Did) -> Option<Entry>;
58}
59
60fn finish_storage_action(act: PeerRingAction) -> Result<()> {
61    match act {
62        PeerRingAction::None => Ok(()),
63        act => Err(Error::unexpected_peer_ring_action(act)),
64    }
65}
66
67fn finish_storage_action_ref(act: &PeerRingAction) -> Result<()> {
68    match act {
69        PeerRingAction::None => Ok(()),
70        act => Err(Error::unexpected_peer_ring_action(act.clone())),
71    }
72}
73
74async fn reset_storage_relay_destination(
75    handler: &MessageHandler,
76    ctx: &MessagePayload,
77    next: Did,
78) -> Result<()> {
79    handler
80        .run_effects([PayloadRelayFunctor::reset_destination(ctx, next).into()])
81        .await
82}
83
84async fn repair_observed_storage_misses(
85    transport: Arc<SwarmTransport>,
86    entry: Entry,
87    redundancy: u16,
88) -> Result<()> {
89    let misses = transport.take_storage_misses(entry.did, redundancy)?;
90    let repair = transport.dht.read_repair_entry(entry, &misses).await?;
91    handle_storage_repair_act(transport, repair).await
92}
93
94/// Execute storage fetch actions for the Swarm-facing storage API.
95#[cfg_attr(feature = "wasm", async_recursion(?Send))]
96#[cfg_attr(not(feature = "wasm"), async_recursion)]
97async fn handle_storage_fetch_act<const REDUNDANT: u16>(
98    transport: Arc<SwarmTransport>,
99    resource: Did,
100    act: PeerRingAction,
101) -> Result<()> {
102    match act {
103        PeerRingAction::SomeEntry(evidence) => {
104            transport
105                .dht
106                .local_cache_put(evidence.entry.clone())
107                .await?;
108            let misses = evidence.misses;
109            let repair = transport
110                .dht
111                .read_repair_entry(evidence.entry, &misses)
112                .await?;
113            handle_storage_repair_act(transport.clone(), repair).await?;
114        }
115        PeerRingAction::RemoteAction(next, dht_act) => {
116            if let PeerRingRemoteAction::FindEntry(query) = dht_act {
117                tracing::debug!(
118                    "storage_fetch send_message: SearchEntry({:?}) to {:?}",
119                    query,
120                    next
121                );
122                transport
123                    .send_message(
124                        Message::SearchEntry(SearchEntry {
125                            resource: query.resource,
126                            placement: query.placement,
127                            redundancy: REDUNDANT,
128                        }),
129                        next,
130                    )
131                    .await?;
132            }
133        }
134        PeerRingAction::MultiActions(acts) => {
135            for act in acts {
136                handle_storage_fetch_act::<REDUNDANT>(transport.clone(), resource, act).await?;
137            }
138        }
139        PeerRingAction::EntryMisses(misses) => {
140            transport.observe_storage_misses(resource, REDUNDANT, misses)?;
141        }
142        act => finish_storage_action(act)?,
143    }
144    Ok(())
145}
146
147/// Execute storage store actions for the Swarm-facing storage API.
148#[cfg_attr(feature = "wasm", async_recursion(?Send))]
149#[cfg_attr(not(feature = "wasm"), async_recursion)]
150pub(super) async fn handle_storage_store_act(
151    transport: Arc<SwarmTransport>,
152    act: PeerRingAction,
153) -> Result<()> {
154    match act {
155        PeerRingAction::RemoteAction(target, PeerRingRemoteAction::FindEntryForOperate(op)) => {
156            transport
157                .send_message(Message::OperateEntry(op), target)
158                .await?;
159        }
160        PeerRingAction::MultiActions(acts) => {
161            for act in acts {
162                handle_storage_store_act(transport.clone(), act).await?;
163            }
164        }
165        act => finish_storage_action(act)?,
166    }
167    Ok(())
168}
169
170/// Execute copy-only storage repair actions.
171#[cfg_attr(feature = "wasm", async_recursion(?Send))]
172#[cfg_attr(not(feature = "wasm"), async_recursion)]
173pub(super) async fn handle_storage_repair_act(
174    transport: Arc<SwarmTransport>,
175    act: PeerRingAction,
176) -> Result<()> {
177    match act {
178        PeerRingAction::RemoteAction(
179            destination,
180            PeerRingRemoteAction::SyncEntriesWithSuccessor(data),
181        ) => {
182            transport
183                .send_message(
184                    Message::SyncEntriesWithSuccessor(SyncEntriesWithSuccessor { data }),
185                    destination,
186                )
187                .await?;
188        }
189        PeerRingAction::MultiActions(acts) => {
190            for act in acts {
191                handle_storage_repair_act(transport.clone(), act).await?;
192            }
193        }
194        act => finish_storage_action(act)?,
195    }
196    Ok(())
197}
198
199/// Execute storage search actions emitted by inbound message handlers.
200#[cfg_attr(feature = "wasm", async_recursion(?Send))]
201#[cfg_attr(not(feature = "wasm"), async_recursion)]
202async fn handle_storage_search_act(
203    handler: &MessageHandler,
204    ctx: &MessagePayload,
205    act: PeerRingAction,
206    resource: Did,
207    redundancy: u16,
208) -> Result<()> {
209    match act {
210        PeerRingAction::SomeEntry(evidence) => {
211            handler
212                .run_effects([PayloadRelayFunctor::send_report_message(
213                    ctx,
214                    Message::FoundEntry(FoundEntry {
215                        data: vec![evidence.entry],
216                        misses: evidence.misses,
217                        resource,
218                        redundancy,
219                    }),
220                )
221                .into()])
222                .await
223        }
224        PeerRingAction::EntryMisses(misses) => {
225            handler
226                .run_effects([PayloadRelayFunctor::send_report_message(
227                    ctx,
228                    Message::FoundEntry(FoundEntry {
229                        data: vec![],
230                        misses,
231                        resource,
232                        redundancy,
233                    }),
234                )
235                .into()])
236                .await
237        }
238        PeerRingAction::RemoteAction(next, _) => {
239            reset_storage_relay_destination(handler, ctx, next).await
240        }
241        PeerRingAction::MultiActions(acts) => {
242            let jobs = acts.iter().map(|act| async move {
243                handle_storage_search_act(handler, ctx, act.clone(), resource, redundancy).await
244            });
245
246            for res in futures::future::join_all(jobs).await {
247                if res.is_err() {
248                    tracing::error!("Failed on handle multi actions: {:#?}", res)
249                }
250            }
251
252            Ok(())
253        }
254        act => finish_storage_action(act),
255    }
256}
257
258/// Execute storage operation actions emitted by inbound message handlers.
259#[cfg_attr(feature = "wasm", async_recursion(?Send))]
260#[cfg_attr(not(feature = "wasm"), async_recursion)]
261async fn handle_storage_operate_act(
262    handler: &MessageHandler,
263    ctx: &MessagePayload,
264    act: &PeerRingAction,
265) -> Result<()> {
266    match act {
267        PeerRingAction::RemoteAction(next, _) => {
268            reset_storage_relay_destination(handler, ctx, *next).await
269        }
270        PeerRingAction::MultiActions(acts) => {
271            let jobs = acts
272                .iter()
273                .map(|act| async move { handle_storage_operate_act(handler, ctx, act).await });
274
275            for res in futures::future::join_all(jobs).await {
276                if res.is_err() {
277                    tracing::error!("Failed on handle multi actions: {:#?}", res)
278                }
279            }
280
281            Ok(())
282        }
283        act => finish_storage_action_ref(act),
284    }
285}
286
287async fn persist_synced_entries(
288    handler: &MessageHandler,
289    msg: &SyncEntriesWithSuccessor,
290) -> Result<Vec<SyncedEntryAck>> {
291    let mut acks = Vec::with_capacity(msg.data.len());
292    for placed in msg.data.iter() {
293        let entry = placed.entry.clone().try_into_storage_entry()?;
294        handler
295            .dht
296            .join_storage_entry(placed.key, entry.clone())
297            .await?;
298        acks.push(SyncedEntryAck::new(placed.key, entry));
299    }
300    Ok(acks)
301}
302
303fn next_hop_for_sync_entries(
304    handler: &MessageHandler,
305    ctx: &MessagePayload,
306) -> Result<Option<Did>> {
307    if ctx.is_relay_destination_for(handler.dht.did) {
308        return Ok(None);
309    }
310
311    match handler.dht.find_successor(ctx.relay.destination)? {
312        PeerRingAction::Some(owner) if owner == handler.dht.did => Ok(None),
313        PeerRingAction::Some(next) => Ok(Some(next)),
314        PeerRingAction::RemoteAction(next, PeerRingRemoteAction::FindSuccessor(_)) => {
315            Ok(Some(next))
316        }
317        action => Err(Error::unexpected_peer_ring_action(action)),
318    }
319}
320
321async fn report_synced_entries(
322    handler: &MessageHandler,
323    ctx: &MessagePayload,
324    acks: Vec<SyncedEntryAck>,
325) -> Result<()> {
326    handler
327        .run_effects([PayloadRelayFunctor::send_report_message(
328            ctx,
329            Message::SyncEntriesWithSuccessorReport(SyncEntriesWithSuccessorReport { acks }),
330        )
331        .into()])
332        .await
333}
334
335#[cfg_attr(feature = "wasm", async_trait(?Send))]
336#[cfg_attr(not(feature = "wasm"), async_trait)]
337impl ChordStorageInterfaceCacheChecker for Swarm {
338    /// Check local cache
339    async fn storage_check_cache(&self, entry_key: Did) -> Option<Entry> {
340        self.dht.local_cache_get(entry_key).await.ok().flatten()
341    }
342}
343
344#[cfg_attr(feature = "wasm", async_trait(?Send))]
345#[cfg_attr(not(feature = "wasm"), async_trait)]
346impl<const REDUNDANT: u16> ChordStorageInterface<REDUNDANT> for Swarm {
347    /// Fetch an entry. If it exists in local storage, copy it to the cache;
348    /// otherwise query the responsible remote node.
349    async fn storage_fetch(&self, entry_key: Did) -> Result<()> {
350        self.transport.ensure_storage_redundancy::<REDUNDANT>()?;
351        self.transport.start_storage_lookup(entry_key, REDUNDANT)?;
352        // If peer found that data is on it's localstore, copy it to the cache
353        let act =
354            <PeerRing as ChordStorage<_, REDUNDANT>>::entry_lookup(&self.dht, entry_key).await?;
355        handle_storage_fetch_act::<REDUNDANT>(self.transport.clone(), entry_key, act).await?;
356        Ok(())
357    }
358
359    /// Store Entry, `TryInto<Entry>` is implemented for alot of types
360    async fn storage_store(&self, entry: Entry) -> Result<()> {
361        self.transport.ensure_storage_redundancy::<REDUNDANT>()?;
362        let op = EntryOperation::Overwrite(entry);
363        let act = <PeerRing as ChordStorage<_, REDUNDANT>>::entry_operate(&self.dht, op).await?;
364        handle_storage_store_act(self.transport.clone(), act).await?;
365        Ok(())
366    }
367
368    async fn storage_append_data(&self, topic: &str, data: Encoded) -> Result<()> {
369        self.transport.ensure_storage_redundancy::<REDUNDANT>()?;
370        let entry: Entry = (topic.to_string(), data).try_into()?;
371        let op = EntryOperation::Extend(entry);
372        let act = <PeerRing as ChordStorage<_, REDUNDANT>>::entry_operate(&self.dht, op).await?;
373        handle_storage_store_act(self.transport.clone(), act).await?;
374        Ok(())
375    }
376
377    async fn storage_touch_data(&self, topic: &str, data: Encoded) -> Result<()> {
378        self.transport.ensure_storage_redundancy::<REDUNDANT>()?;
379        let entry: Entry = (topic.to_string(), data).try_into()?;
380        let op = EntryOperation::Touch(entry);
381        let act = <PeerRing as ChordStorage<_, REDUNDANT>>::entry_operate(&self.dht, op).await?;
382        handle_storage_store_act(self.transport.clone(), act).await?;
383        Ok(())
384    }
385}
386
387#[cfg_attr(feature = "wasm", async_trait(?Send))]
388#[cfg_attr(not(feature = "wasm"), async_trait)]
389impl HandleMsg<SearchEntry> for MessageHandler {
390    /// Search Entry via successor
391    /// If a Entry is storead local, it will response immediately.(See Chordstorageinterface::storage_fetch)
392    async fn handle(&self, ctx: &MessagePayload, msg: &SearchEntry) -> Result<()> {
393        // For relay message, set redundant to 1
394        match <PeerRing as ChordStorage<_, 1>>::entry_lookup(&self.dht, msg.placement).await {
395            Ok(action) => {
396                handle_storage_search_act(self, ctx, action, msg.resource, msg.redundancy).await
397            }
398            Err(e) => Err(e),
399        }
400    }
401}
402
403#[cfg_attr(feature = "wasm", async_trait(?Send))]
404#[cfg_attr(not(feature = "wasm"), async_trait)]
405impl HandleMsg<FoundEntry> for MessageHandler {
406    async fn handle(&self, ctx: &MessagePayload, msg: &FoundEntry) -> Result<()> {
407        if ctx.should_forward_from(self.dht.did) {
408            return self
409                .run_effects([PayloadRelayFunctor::forward_payload(ctx, None).into()])
410                .await;
411        }
412        let found_entry = msg.single_entry()?;
413        self.transport.observe_storage_misses(
414            msg.resource,
415            msg.redundancy,
416            msg.misses.iter().copied(),
417        )?;
418        if let Some(data) = found_entry {
419            self.dht.local_cache_put(data.clone()).await?;
420            repair_observed_storage_misses(self.transport.clone(), data.clone(), msg.redundancy)
421                .await?;
422        } else if !msg.misses.is_empty() {
423            if let Some(entry) = self.dht.local_cache_get(msg.resource).await? {
424                repair_observed_storage_misses(self.transport.clone(), entry, msg.redundancy)
425                    .await?;
426            }
427        }
428        Ok(())
429    }
430}
431
432#[cfg_attr(feature = "wasm", async_trait(?Send))]
433#[cfg_attr(not(feature = "wasm"), async_trait)]
434impl HandleMsg<EntryOperation> for MessageHandler {
435    async fn handle(&self, ctx: &MessagePayload, msg: &EntryOperation) -> Result<()> {
436        // For relay message, set redundant to 1
437        let action =
438            <PeerRing as ChordStorage<_, 1>>::entry_operate(&self.dht, msg.clone()).await?;
439        handle_storage_operate_act(self, ctx, &action).await
440    }
441}
442
443#[cfg_attr(feature = "wasm", async_trait(?Send))]
444#[cfg_attr(not(feature = "wasm"), async_trait)]
445impl HandleMsg<SyncEntriesWithSuccessor> for MessageHandler {
446    // received remote sync entry request
447    async fn handle(&self, ctx: &MessagePayload, msg: &SyncEntriesWithSuccessor) -> Result<()> {
448        if let Some(next) = next_hop_for_sync_entries(self, ctx)? {
449            return self
450                .run_effects([PayloadRelayFunctor::forward_payload(ctx, Some(next)).into()])
451                .await;
452        }
453
454        let acks = persist_synced_entries(self, msg).await?;
455        if let Err(e) = report_synced_entries(self, ctx, acks).await {
456            tracing::warn!("Failed to report synced entries: {e:?}");
457        }
458        Ok(())
459    }
460}
461
462#[cfg_attr(feature = "wasm", async_trait(?Send))]
463#[cfg_attr(not(feature = "wasm"), async_trait)]
464impl HandleMsg<SyncEntriesWithSuccessorReport> for MessageHandler {
465    async fn handle(
466        &self,
467        _ctx: &MessagePayload,
468        msg: &SyncEntriesWithSuccessorReport,
469    ) -> Result<()> {
470        let action = self.dht.acknowledge_synced_entries(&msg.acks).await?;
471        finish_storage_action(action)
472    }
473}
474
475#[cfg(not(feature = "wasm"))]
476#[cfg(test)]
477mod test {
478    use std::sync::Arc;
479
480    use super::*;
481    use crate::consts::ENTRY_DATA_MAX_LEN;
482    use crate::dht::entry::PlacedEntry;
483    use crate::dht::entry::PlacementMiss;
484    use crate::dht::successor::SuccessorReader;
485    use crate::dht::successor::SuccessorWriter;
486    use crate::ecc::tests::gen_ordered_keys;
487    use crate::ecc::SecretKey;
488    use crate::message::Encoder;
489    use crate::prelude::entry::EntryKind;
490    use crate::session::SessionSk;
491    use crate::storage::MemStorage;
492    use crate::swarm::callback::SwarmCallback;
493    use crate::swarm::transport::STORAGE_LOOKUP_OBSERVATION_CAPACITY;
494    use crate::swarm::SwarmBuilder;
495    use crate::tests::default::assert_no_more_msg;
496    use crate::tests::default::prepare_node;
497    use crate::tests::default::wait_for_msgs;
498    use crate::tests::default::Node;
499    use crate::tests::manually_establish_connection;
500
501    struct NoopCallback;
502
503    impl SwarmCallback for NoopCallback {}
504
505    async fn next_payload(node: &Node) -> Result<MessagePayload> {
506        node.listen_once()
507            .await
508            .ok_or_else(|| Error::InvalidMessage("expected message payload".to_string()))
509    }
510
511    fn next_generated_key(keys: &mut impl Iterator<Item = SecretKey>) -> Result<SecretKey> {
512        keys.next()
513            .ok_or_else(|| Error::InvalidMessage("expected generated key".to_string()))
514    }
515
516    async fn assert_cached_data_values(
517        node: &Node,
518        entry_key: Did,
519        expected: &[&str],
520    ) -> Result<()> {
521        let entry = node
522            .swarm
523            .storage_check_cache(entry_key)
524            .await
525            .ok_or_else(|| Error::InvalidMessage("expected cached entry".to_string()))?;
526        let expected_data = expected
527            .iter()
528            .map(|value| value.to_string().encode())
529            .collect::<Result<Vec<_>>>()?;
530
531        assert_eq!(entry.did, entry_key);
532        assert_eq!(entry.kind, EntryKind::Data);
533        assert_eq!(entry.data, expected_data);
534        assert_eq!(entry.crdt.dots.len(), entry.data.len());
535        Ok(())
536    }
537
538    #[test]
539    fn finish_storage_action_accepts_empty_action() -> Result<()> {
540        finish_storage_action(PeerRingAction::None)?;
541        finish_storage_action_ref(&PeerRingAction::None)?;
542        Ok(())
543    }
544
545    #[test]
546    fn finish_storage_action_rejects_unhandled_action() -> Result<()> {
547        let did = SecretKey::random().address().into();
548        match finish_storage_action(PeerRingAction::Some(did)) {
549            Err(Error::PeerRingUnexpectedAction(action)) => {
550                assert_eq!(*action, PeerRingAction::Some(did));
551                Ok(())
552            }
553            res => Err(Error::InvalidMessage(format!(
554                "expected unexpected storage action, got {res:?}"
555            ))),
556        }
557    }
558
559    #[tokio::test]
560    async fn sync_entries_handler_stores_entry_at_placement_key() -> Result<()> {
561        let node = prepare_node(SecretKey::random()).await;
562        let handler = MessageHandler::new(node.swarm.transport.clone(), Arc::new(NoopCallback));
563        let resource_id = Did::from(10u32);
564        let placement_key = Did::from(100u32);
565        let entry = Entry::new(
566            resource_id,
567            vec!["placed".to_string().encode()?],
568            EntryKind::Data,
569        );
570        let stored_entry = entry.clone().try_into_storage_entry()?;
571        let context_key = SecretKey::random();
572        let context_session = SessionSk::new_with_seckey(&context_key)?;
573        let context = MessagePayload::new_send(
574            Message::custom(b"sync context")?,
575            &context_session,
576            node.did(),
577            node.did(),
578        )?;
579
580        handler
581            .handle(&context, &SyncEntriesWithSuccessor {
582                data: vec![PlacedEntry::new(placement_key, entry.clone())],
583            })
584            .await?;
585
586        assert_eq!(
587            node.dht().storage.get(&placement_key.to_string()).await?,
588            Some(stored_entry)
589        );
590        assert_eq!(
591            node.dht().storage.get(&resource_id.to_string()).await?,
592            None
593        );
594        Ok(())
595    }
596
597    #[tokio::test]
598    async fn sync_entries_handler_caps_inbound_entry_payloads() -> Result<()> {
599        let node = prepare_node(SecretKey::random()).await;
600        let handler = MessageHandler::new(node.swarm.transport.clone(), Arc::new(NoopCallback));
601        let placement_key = Did::from(100u32);
602        let entry = Entry::new(
603            Did::from(10u32),
604            (0..ENTRY_DATA_MAX_LEN + 3)
605                .map(|i| format!("payload{i}").encode())
606                .collect::<Result<Vec<_>>>()?,
607            EntryKind::Data,
608        );
609        let context_key = SecretKey::random();
610        let context_session = SessionSk::new_with_seckey(&context_key)?;
611        let context = MessagePayload::new_send(
612            Message::custom(b"sync context")?,
613            &context_session,
614            node.did(),
615            node.did(),
616        )?;
617
618        handler
619            .handle(&context, &SyncEntriesWithSuccessor {
620                data: vec![PlacedEntry::new(placement_key, entry)],
621            })
622            .await?;
623
624        let stored = node
625            .dht()
626            .storage
627            .get(&placement_key.to_string())
628            .await?
629            .ok_or_else(|| Error::InvalidMessage("expected stored sync entry".to_string()))?;
630
631        assert_eq!(stored.data.len(), ENTRY_DATA_MAX_LEN);
632        let first_payload: String = stored
633            .data
634            .first()
635            .ok_or_else(|| Error::InvalidMessage("expected capped payload".to_string()))?
636            .decode()?;
637        assert_eq!(first_payload, String::from("payload3"));
638        Ok(())
639    }
640
641    #[tokio::test]
642    async fn sync_entries_handler_routes_repair_by_placement_destination() -> Result<()> {
643        let mut keys = gen_ordered_keys(2).into_iter();
644        let node1 = prepare_node(next_generated_key(&mut keys)?).await;
645        let node2 = prepare_node(next_generated_key(&mut keys)?).await;
646        manually_establish_connection(&node1.swarm, &node2.swarm).await;
647        wait_for_msgs([&node1, &node2]).await;
648        assert_no_more_msg([&node1, &node2]).await;
649
650        let handler = MessageHandler::new(node1.swarm.transport.clone(), Arc::new(NoopCallback));
651        let placement_key = node2.did();
652        let entry = Entry::new(
653            Did::from(10u32),
654            vec!["routed repair".to_string().encode()?],
655            EntryKind::Data,
656        );
657        let stored_entry = entry.clone().try_into_storage_entry()?;
658        let msg = SyncEntriesWithSuccessor {
659            data: vec![PlacedEntry::new(placement_key, entry.clone())],
660        };
661        let context_key = SecretKey::random();
662        let context_session = SessionSk::new_with_seckey(&context_key)?;
663        let context = MessagePayload::new_send(
664            Message::SyncEntriesWithSuccessor(msg.clone()),
665            &context_session,
666            node1.did(),
667            placement_key,
668        )?;
669
670        handler.handle(&context, &msg).await?;
671
672        let forwarded = next_payload(&node2).await?;
673        assert!(matches!(
674            forwarded.transaction.data()?,
675            Message::SyncEntriesWithSuccessor(SyncEntriesWithSuccessor { data })
676                if data == vec![PlacedEntry::new(placement_key, entry.clone())]
677        ));
678        let ack = next_payload(&node1).await?;
679        assert!(matches!(
680            ack.transaction.data()?,
681            Message::SyncEntriesWithSuccessorReport(SyncEntriesWithSuccessorReport { acks })
682                if acks == vec![SyncedEntryAck::new(placement_key, stored_entry.clone())]
683        ));
684        assert_eq!(
685            node1.dht().storage.get(&placement_key.to_string()).await?,
686            None
687        );
688        assert_eq!(
689            node2.dht().storage.get(&placement_key.to_string()).await?,
690            Some(stored_entry)
691        );
692        Ok(())
693    }
694
695    #[tokio::test]
696    async fn leave_dht_republishes_after_responsibility_peer_departure() -> Result<()> {
697        let key = SecretKey::random();
698        let session = SessionSk::new_with_seckey(&key)?;
699        let swarm = Arc::new(
700            SwarmBuilder::new(
701                0,
702                "stun://stun.l.google.com:19302",
703                Box::new(MemStorage::new()),
704                session,
705            )
706            .dht_storage_redundancy(2)
707            .build(),
708        );
709        let node = Node::new(swarm);
710        let departed = Did::from(100u32);
711        node.dht().successors().update(departed)?;
712        let entry = Entry::new(key.address().into(), vec![], EntryKind::Data);
713        let placement_keys = entry.did.rotate_affine(2)?;
714        node.dht()
715            .storage
716            .put(&placement_keys[0].to_string(), &entry)
717            .await?;
718        let handler = MessageHandler::new(node.swarm.transport.clone(), Arc::new(NoopCallback));
719
720        handler.leave_dht(departed).await?;
721
722        assert!(!node.dht().successors().contains(&departed)?);
723        assert_eq!(
724            node.dht()
725                .storage
726                .get(&placement_keys[1].to_string())
727                .await?,
728            Some(entry)
729        );
730        Ok(())
731    }
732
733    #[tokio::test]
734    async fn storage_api_rejects_redundancy_mismatch() -> Result<()> {
735        let node = prepare_node(SecretKey::random()).await;
736
737        let result =
738            <Swarm as ChordStorageInterface<2>>::storage_fetch(&node.swarm, node.did()).await;
739
740        assert!(matches!(
741            result,
742            Err(Error::StorageRedundancyMismatch {
743                configured: 1,
744                requested: 2
745            })
746        ));
747        Ok(())
748    }
749
750    #[tokio::test]
751    async fn local_hit_read_repair_sends_no_search_for_unknown_replicas() -> Result<()> {
752        let key = SecretKey::random();
753        let session = SessionSk::new_with_seckey(&key)?;
754        let swarm = Arc::new(
755            SwarmBuilder::new(
756                0,
757                "stun://stun.l.google.com:19302",
758                Box::new(MemStorage::new()),
759                session,
760            )
761            .dht_storage_redundancy(2)
762            .build(),
763        );
764        let node = Node::new(swarm);
765        let entry = Entry::new(
766            key.address().into(),
767            vec!["local".to_string().encode()?],
768            EntryKind::Data,
769        );
770        let first_key = entry
771            .did
772            .rotate_affine(2)?
773            .into_iter()
774            .next()
775            .ok_or_else(|| Error::InvalidMessage("expected first placement".to_string()))?;
776        node.dht()
777            .storage
778            .put(&first_key.to_string(), &entry)
779            .await?;
780
781        <Swarm as ChordStorageInterface<2>>::storage_fetch(&node.swarm, entry.did).await?;
782
783        assert_eq!(node.swarm.storage_check_cache(entry.did).await, Some(entry));
784        assert_no_more_msg([&node]).await;
785        Ok(())
786    }
787
788    #[tokio::test]
789    async fn found_entry_repairs_buffered_misses_only() -> Result<()> {
790        let node = prepare_node(SecretKey::random()).await;
791        let handler = MessageHandler::new(node.swarm.transport.clone(), Arc::new(NoopCallback));
792        let entry = Entry::new(
793            Did::from(10u32),
794            vec!["repair".to_string().encode()?],
795            EntryKind::Data,
796        );
797        let stored_entry = entry.clone().try_into_storage_entry()?;
798        let placement_key = Did::from(100u32);
799        let unknown_key = Did::from(120u32);
800        let context_key = SecretKey::random();
801        let context_session = SessionSk::new_with_seckey(&context_key)?;
802        let context = MessagePayload::new_send(
803            Message::FoundEntry(FoundEntry {
804                data: vec![],
805                misses: vec![PlacementMiss::new(placement_key, node.did())],
806                resource: entry.did,
807                redundancy: 2,
808            }),
809            &context_session,
810            node.did(),
811            node.did(),
812        )?;
813
814        handler
815            .handle(&context, &FoundEntry {
816                data: vec![],
817                misses: vec![PlacementMiss::new(placement_key, node.did())],
818                resource: entry.did,
819                redundancy: 2,
820            })
821            .await?;
822        handler
823            .handle(&context, &FoundEntry {
824                data: vec![entry.clone()],
825                misses: vec![],
826                resource: entry.did,
827                redundancy: 2,
828            })
829            .await?;
830
831        assert_eq!(
832            node.dht().storage.get(&placement_key.to_string()).await?,
833            Some(stored_entry)
834        );
835        assert_eq!(
836            node.dht().storage.get(&unknown_key.to_string()).await?,
837            None
838        );
839        Ok(())
840    }
841
842    #[tokio::test]
843    async fn found_entry_rejects_multiple_entries() -> Result<()> {
844        let node = prepare_node(SecretKey::random()).await;
845        let handler = MessageHandler::new(node.swarm.transport.clone(), Arc::new(NoopCallback));
846        let resource = Did::from(10u32);
847        let first = Entry::new(
848            resource,
849            vec!["first".to_string().encode()?],
850            EntryKind::Data,
851        );
852        let second = Entry::new(
853            resource,
854            vec!["second".to_string().encode()?],
855            EntryKind::Data,
856        );
857        let context_key = SecretKey::random();
858        let context_session = SessionSk::new_with_seckey(&context_key)?;
859        let context = MessagePayload::new_send(
860            Message::FoundEntry(FoundEntry {
861                data: vec![first.clone(), second.clone()],
862                misses: vec![],
863                resource,
864                redundancy: 2,
865            }),
866            &context_session,
867            node.did(),
868            node.did(),
869        )?;
870
871        let result = handler
872            .handle(&context, &FoundEntry {
873                data: vec![first, second],
874                misses: vec![],
875                resource,
876                redundancy: 2,
877            })
878            .await;
879
880        assert!(
881            matches!(result, Err(Error::InvalidMessage(message)) if message.contains("more than one"))
882        );
883        assert_eq!(node.swarm.storage_check_cache(resource).await, None);
884        assert_eq!(node.swarm.transport.storage_lookup_observation_count()?, 0);
885        Ok(())
886    }
887
888    #[tokio::test]
889    async fn storage_miss_observation_buffer_is_bounded() -> Result<()> {
890        let node = prepare_node(SecretKey::random()).await;
891        for index in 0..(STORAGE_LOOKUP_OBSERVATION_CAPACITY + 8) {
892            let resource = Did::from((index + 1) as u32);
893            let placement = Did::from((index + 10_000) as u32);
894            node.swarm
895                .transport
896                .observe_storage_misses(resource, 2, [PlacementMiss::new(placement, node.did())])?;
897        }
898
899        assert!(
900            node.swarm.transport.storage_lookup_observation_count()?
901                <= STORAGE_LOOKUP_OBSERVATION_CAPACITY
902        );
903        Ok(())
904    }
905
906    #[tokio::test]
907    async fn storage_fetch_starts_fresh_observation_round() -> Result<()> {
908        let node = prepare_node(SecretKey::random()).await;
909        let resource = Did::from(10u32);
910        let placement = Did::from(100u32);
911        node.swarm
912            .transport
913            .observe_storage_misses(resource, 1, [PlacementMiss::new(placement, node.did())])?;
914
915        node.swarm.transport.start_storage_lookup(resource, 1)?;
916        let misses = node.swarm.transport.take_storage_misses(resource, 1)?;
917
918        assert!(misses.is_empty());
919        assert_eq!(node.swarm.transport.storage_lookup_observation_count()?, 0);
920        Ok(())
921    }
922
923    #[tokio::test]
924    async fn expired_storage_misses_do_not_trigger_late_repair() -> Result<()> {
925        let node = prepare_node(SecretKey::random()).await;
926        let handler = MessageHandler::new(node.swarm.transport.clone(), Arc::new(NoopCallback));
927        let entry = Entry::new(
928            Did::from(10u32),
929            vec!["fresh".to_string().encode()?],
930            EntryKind::Data,
931        );
932        let placement_key = Did::from(100u32);
933        let context_key = SecretKey::random();
934        let context_session = SessionSk::new_with_seckey(&context_key)?;
935        let context = MessagePayload::new_send(
936            Message::FoundEntry(FoundEntry {
937                data: vec![],
938                misses: vec![PlacementMiss::new(placement_key, node.did())],
939                resource: entry.did,
940                redundancy: 2,
941            }),
942            &context_session,
943            node.did(),
944            node.did(),
945        )?;
946
947        handler
948            .handle(&context, &FoundEntry {
949                data: vec![],
950                misses: vec![PlacementMiss::new(placement_key, node.did())],
951                resource: entry.did,
952                redundancy: 2,
953            })
954            .await?;
955        node.swarm
956            .transport
957            .expire_storage_lookup_observation(entry.did, 2)?;
958        handler
959            .handle(&context, &FoundEntry {
960                data: vec![entry.clone()],
961                misses: vec![],
962                resource: entry.did,
963                redundancy: 2,
964            })
965            .await?;
966
967        assert_eq!(node.swarm.storage_check_cache(entry.did).await, Some(entry));
968        assert_eq!(
969            node.dht().storage.get(&placement_key.to_string()).await?,
970            None
971        );
972        Ok(())
973    }
974
975    #[tokio::test]
976    async fn sync_entries_handler_reports_persisted_entries() -> Result<()> {
977        let sender = prepare_node(SecretKey::random()).await;
978        let receiver = prepare_node(SecretKey::random()).await;
979        manually_establish_connection(&sender.swarm, &receiver.swarm).await;
980        wait_for_msgs([&sender, &receiver]).await;
981
982        let handler = MessageHandler::new(receiver.swarm.transport.clone(), Arc::new(NoopCallback));
983        let placement_key = Did::from(100u32);
984        let entry = Entry::new(
985            Did::from(10u32),
986            vec!["acked".to_string().encode()?],
987            EntryKind::Data,
988        );
989        let stored_entry = entry.clone().try_into_storage_entry()?;
990        let sync_msg = SyncEntriesWithSuccessor {
991            data: vec![PlacedEntry::new(placement_key, entry.clone())],
992        };
993        let context = MessagePayload::new_send(
994            Message::SyncEntriesWithSuccessor(sync_msg.clone()),
995            sender.swarm.transport.session_sk(),
996            receiver.did(),
997            receiver.did(),
998        )?;
999
1000        handler.handle(&context, &sync_msg).await?;
1001
1002        let payload = next_payload(&sender).await?;
1003        match payload.transaction.data::<Message>()? {
1004            Message::SyncEntriesWithSuccessorReport(report) => {
1005                assert_eq!(report.acks, vec![SyncedEntryAck::new(
1006                    placement_key,
1007                    stored_entry.clone()
1008                )]);
1009            }
1010            message => {
1011                return Err(Error::InvalidMessage(format!(
1012                    "expected SyncEntriesWithSuccessorReport, got {message:?}"
1013                )))
1014            }
1015        }
1016        assert_eq!(
1017            receiver
1018                .dht()
1019                .storage
1020                .get(&placement_key.to_string())
1021                .await?,
1022            Some(stored_entry)
1023        );
1024        Ok(())
1025    }
1026
1027    #[tokio::test]
1028    async fn sync_entries_report_handler_deletes_only_acked_keys() -> Result<()> {
1029        let node = prepare_node(SecretKey::random()).await;
1030        let handler = MessageHandler::new(node.swarm.transport.clone(), Arc::new(NoopCallback));
1031        let acked_key = Did::from(100u32);
1032        let pending_key = Did::from(120u32);
1033        let acked_entry = Entry::new(Did::from(10u32), vec![], EntryKind::Data);
1034        let pending_entry = Entry::new(Did::from(20u32), vec![], EntryKind::Data);
1035        let context = MessagePayload::new_send(
1036            Message::custom(b"sync ack context")?,
1037            node.swarm.transport.session_sk(),
1038            node.did(),
1039            node.did(),
1040        )?;
1041        node.dht()
1042            .storage
1043            .put(&acked_key.to_string(), &acked_entry)
1044            .await?;
1045        node.dht()
1046            .storage
1047            .put(&pending_key.to_string(), &pending_entry)
1048            .await?;
1049
1050        handler
1051            .handle(&context, &SyncEntriesWithSuccessorReport {
1052                acks: vec![SyncedEntryAck::new(acked_key, acked_entry)],
1053            })
1054            .await?;
1055
1056        assert_eq!(node.dht().storage.get(&acked_key.to_string()).await?, None);
1057        assert_eq!(
1058            node.dht().storage.get(&pending_key.to_string()).await?,
1059            Some(pending_entry)
1060        );
1061        Ok(())
1062    }
1063
1064    #[tokio::test]
1065    async fn storage_store_fetches_remote_entry_into_cache() -> Result<()> {
1066        let mut keys = gen_ordered_keys(2).into_iter();
1067        let key1 = next_generated_key(&mut keys)?;
1068        let key2 = next_generated_key(&mut keys)?;
1069        let node1 = prepare_node(key1).await;
1070        let node2 = prepare_node(key2).await;
1071
1072        manually_establish_connection(&node1.swarm, &node2.swarm).await;
1073        wait_for_msgs([&node1, &node2]).await;
1074        assert_no_more_msg([&node1, &node2]).await;
1075
1076        // Now, node1 is the successor of node2, and node2 is the successor of node1.
1077        // Following tests storing data on node2 and query it from node1.
1078        let data = "Across the Great Wall we can reach every corner in the world.".to_string();
1079        let entry: Entry = data.clone().try_into()?;
1080        let entry_key = entry.did;
1081
1082        // Make sure the data is stored on node2.
1083        let (node1, node2) = if entry_key.in_range(node2.did(), node2.did(), node1.did()) {
1084            (node1, node2)
1085        } else {
1086            (node2, node1)
1087        };
1088
1089        assert_eq!(node1.dht().cache.count().await?, 0);
1090        assert_eq!(node2.dht().cache.count().await?, 0);
1091        assert!(node1.swarm.storage_check_cache(entry_key).await.is_none());
1092        assert!(node2.swarm.storage_check_cache(entry_key).await.is_none());
1093
1094        <Swarm as ChordStorageInterface<1>>::storage_store(&node1.swarm, entry.clone()).await?;
1095        let ev = next_payload(&node2).await?;
1096        assert!(matches!(
1097            ev.transaction.data()?,
1098            Message::OperateEntry(EntryOperation::Overwrite(x)) if x.did == entry_key
1099        ));
1100
1101        assert!(node1.swarm.storage_check_cache(entry_key).await.is_none());
1102        assert!(node2.swarm.storage_check_cache(entry_key).await.is_none());
1103        assert!(node1.dht().storage.count().await? == 0);
1104        assert!(node2.dht().storage.count().await? != 0);
1105
1106        // test remote query
1107        println!("entry_key is on node2 {:?}", node2.did());
1108        <Swarm as ChordStorageInterface<1>>::storage_fetch(&node1.swarm, entry_key).await?;
1109
1110        // it will send request to node2
1111        let ev = next_payload(&node2).await?;
1112        // node2 received search entry request
1113        assert!(matches!(
1114            ev.transaction.data()?,
1115            Message::SearchEntry(x) if x.resource == entry_key && x.placement == entry_key
1116        ));
1117
1118        let ev = next_payload(&node1).await?;
1119        assert!(matches!(
1120            ev.transaction.data()?,
1121            Message::FoundEntry(x)
1122                if x.resource == entry_key
1123                    && x.misses.is_empty()
1124                    && x.data.first().is_some_and(|entry| entry.did == entry_key)
1125        ));
1126
1127        assert_cached_data_values(&node1, entry_key, &[data.as_str()]).await?;
1128
1129        Ok(())
1130    }
1131
1132    #[tokio::test]
1133    async fn storage_append_data_preserves_entry_payload_order() -> Result<()> {
1134        let mut keys = gen_ordered_keys(2).into_iter();
1135        let key1 = next_generated_key(&mut keys)?;
1136        let key2 = next_generated_key(&mut keys)?;
1137        let node1 = prepare_node(key1).await;
1138        let node2 = prepare_node(key2).await;
1139
1140        manually_establish_connection(&node1.swarm, &node2.swarm).await;
1141        wait_for_msgs([&node1, &node2]).await;
1142        assert_no_more_msg([&node1, &node2]).await;
1143
1144        // Now, node1 is the successor of node2, and node2 is the successor of node1.
1145        // Following tests storing data on node2 and query it from node1.
1146        let topic = "Across the Great Wall we can reach every corner in the world.".to_string();
1147        let entry: Entry = topic.clone().try_into()?;
1148        let entry_key = entry.did;
1149
1150        // Make sure the data is stored on node2.
1151        let (node1, node2) = if entry_key.in_range(node2.did(), node2.did(), node1.did()) {
1152            (node1, node2)
1153        } else {
1154            (node2, node1)
1155        };
1156
1157        assert_eq!(node1.dht().cache.count().await?, 0);
1158        assert_eq!(node2.dht().cache.count().await?, 0);
1159        assert!(node1.swarm.storage_check_cache(entry_key).await.is_none());
1160        assert!(node2.swarm.storage_check_cache(entry_key).await.is_none());
1161
1162        <Swarm as ChordStorageInterface<1>>::storage_append_data(
1163            &node1.swarm,
1164            &topic,
1165            "111".to_string().encode()?,
1166        )
1167        .await?;
1168        wait_for_msgs([&node1, &node2]).await;
1169        assert_no_more_msg([&node1, &node2]).await;
1170
1171        <Swarm as ChordStorageInterface<1>>::storage_append_data(
1172            &node1.swarm,
1173            &topic,
1174            "222".to_string().encode()?,
1175        )
1176        .await?;
1177        wait_for_msgs([&node1, &node2]).await;
1178        assert_no_more_msg([&node1, &node2]).await;
1179
1180        assert!(node1.swarm.storage_check_cache(entry_key).await.is_none());
1181        assert!(node2.swarm.storage_check_cache(entry_key).await.is_none());
1182        assert!(node1.dht().storage.count().await? == 0);
1183        assert!(node2.dht().storage.count().await? != 0);
1184
1185        // test remote query
1186        println!("entry_key is on node2 {:?}", node2.did());
1187        <Swarm as ChordStorageInterface<1>>::storage_fetch(&node1.swarm, entry_key).await?;
1188        wait_for_msgs([&node1, &node2]).await;
1189        assert_no_more_msg([&node1, &node2]).await;
1190
1191        assert_cached_data_values(&node1, entry_key, &["111", "222"]).await?;
1192
1193        // Append more data
1194        <Swarm as ChordStorageInterface<1>>::storage_append_data(
1195            &node1.swarm,
1196            &topic,
1197            "333".to_string().encode()?,
1198        )
1199        .await?;
1200        wait_for_msgs([&node1, &node2]).await;
1201        assert_no_more_msg([&node1, &node2]).await;
1202
1203        // test remote query agagin
1204        println!("entry_key is on node2 {:?}", node2.did());
1205        <Swarm as ChordStorageInterface<1>>::storage_fetch(&node1.swarm, entry_key).await?;
1206        wait_for_msgs([&node1, &node2]).await;
1207        assert_no_more_msg([&node1, &node2]).await;
1208
1209        assert_cached_data_values(&node1, entry_key, &["111", "222", "333"]).await?;
1210
1211        Ok(())
1212    }
1213
1214    #[tokio::test]
1215    async fn storage_touch_data_moves_existing_entry_payload_to_end_once() -> Result<()> {
1216        let mut keys = gen_ordered_keys(2).into_iter();
1217        let key1 = next_generated_key(&mut keys)?;
1218        let key2 = next_generated_key(&mut keys)?;
1219        let node1 = prepare_node(key1).await;
1220        let node2 = prepare_node(key2).await;
1221
1222        manually_establish_connection(&node1.swarm, &node2.swarm).await;
1223        wait_for_msgs([&node1, &node2]).await;
1224        assert_no_more_msg([&node1, &node2]).await;
1225
1226        let topic = "touch keeps unique entry payloads ordered by recency".to_string();
1227        let entry: Entry = topic.clone().try_into()?;
1228        let entry_key = entry.did;
1229
1230        let (node1, node2) = if entry_key.in_range(node2.did(), node2.did(), node1.did()) {
1231            (node1, node2)
1232        } else {
1233            (node2, node1)
1234        };
1235
1236        for value in ["111", "222", "333", "222"] {
1237            <Swarm as ChordStorageInterface<1>>::storage_touch_data(
1238                &node1.swarm,
1239                &topic,
1240                value.to_string().encode()?,
1241            )
1242            .await?;
1243            wait_for_msgs([&node1, &node2]).await;
1244            assert_no_more_msg([&node1, &node2]).await;
1245        }
1246
1247        assert!(node1.swarm.storage_check_cache(entry_key).await.is_none());
1248        assert!(node2.swarm.storage_check_cache(entry_key).await.is_none());
1249        assert_eq!(node1.dht().storage.count().await?, 0);
1250        assert_ne!(node2.dht().storage.count().await?, 0);
1251
1252        <Swarm as ChordStorageInterface<1>>::storage_fetch(&node1.swarm, entry_key).await?;
1253        wait_for_msgs([&node1, &node2]).await;
1254        assert_no_more_msg([&node1, &node2]).await;
1255
1256        assert_cached_data_values(&node1, entry_key, &["111", "333", "222"]).await?;
1257
1258        Ok(())
1259    }
1260}