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#[cfg_attr(feature = "wasm", async_trait(?Send))]
38#[cfg_attr(not(feature = "wasm"), async_trait)]
39pub trait ChordStorageInterface<const REDUNDANT: u16> {
40 async fn storage_fetch(&self, entry_key: Did) -> Result<()>;
42 async fn storage_store(&self, entry: Entry) -> Result<()>;
44 async fn storage_append_data(&self, topic: &str, data: Encoded) -> Result<()>;
46 async fn storage_touch_data(&self, topic: &str, data: Encoded) -> Result<()>;
48}
49
50#[cfg_attr(feature = "wasm", async_trait(?Send))]
52#[cfg_attr(not(feature = "wasm"), async_trait)]
53pub trait ChordStorageInterfaceCacheChecker {
54 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#[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#[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#[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#[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#[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 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 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 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 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 async fn handle(&self, ctx: &MessagePayload, msg: &SearchEntry) -> Result<()> {
393 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 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 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 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 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 println!("entry_key is on node2 {:?}", node2.did());
1108 <Swarm as ChordStorageInterface<1>>::storage_fetch(&node1.swarm, entry_key).await?;
1109
1110 let ev = next_payload(&node2).await?;
1112 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 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 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 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 <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 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}