-
Notifications
You must be signed in to change notification settings - Fork 0
Implement Libp2p P2P Sync with DHT Support #732
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,36 +5,115 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::sync::Arc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use synapse_core::{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error::{Error, Result}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ports::{network_port::NetworkPort, peer_port::PeerStoragePort}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ports::{network_port::NetworkPort, peer_port::PeerStoragePort, memory_port::MemoryPort}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use tracing::{info, warn}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// The prefix for peer records in the DHT. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use tracing::{info, warn, error}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use tokio::time::{self, Duration}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use synapse_core::ports::peer_port::PeerInfo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const PEER_DHT_PREFIX: &str = "synapse/peer/"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const MEMORY_SYNC_PREFIX: &str = "synapse/memory/sync/"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn dht_key(peer_id: &str) -> Vec<u8> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| format!("{}{}", PEER_DHT_PREFIX, peer_id).into_bytes() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn memory_sync_key(peer_id: &str) -> Vec<u8> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| format!("{}{}", MEMORY_SYNC_PREFIX, peer_id).into_bytes() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub struct Libp2pSyncAdapter { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| network_port: Arc<dyn NetworkPort>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| peer_storage: Arc<dyn PeerStoragePort>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| memory_storage: Arc<dyn MemoryPort>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shutdown_tx: tokio::sync::watch::Sender<bool>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| impl Libp2pSyncAdapter { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Creates a new `Libp2pSyncAdapter`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn new( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| network_port: Arc<dyn NetworkPort>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| peer_storage: Arc<dyn PeerStoragePort>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| memory_storage: Arc<dyn MemoryPort>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> Self { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let (shutdown_tx, _) = tokio::sync::watch::channel(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Self { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| network_port, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| peer_storage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| memory_storage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shutdown_tx, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Starts the periodic synchronization process in a background task. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn start(self: Arc<Self>) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let adapter = self.clone(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut shutdown_rx = self.shutdown_tx.subscribe(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokio::spawn(async move { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut interval = time::interval(Duration::from_secs(60)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loop { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokio::select! { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ = interval.tick() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Err(e) = adapter.sync_peers().await { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error!("Peer sync failed: {}", e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Err(e) = adapter.sync_memories().await { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error!("Memory sync failed: {}", e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ = shutdown_rx.changed() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if *shutdown_rx.borrow() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| info!("Shutting down sync adapter background task"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn shutdown(&self) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let _ = self.shutdown_tx.send(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+49
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find all call sites for Libp2pSyncAdapter creation, start, and shutdown
rg -n -C3 'Libp2pSyncAdapter::new|\.start\(|\.shutdown\(' cratesRepository: iberi22/synapse-protocol Length of output: 3726 🌐 Web query:
💡 Result: In
So: late subscribers don’t get a “change notification” for the current value, but they can still read it immediately. (docs.rs) Citations:
🏁 Script executed: #!/bin/bash
# Check the complete Libp2pSyncAdapter struct definition and instantiation
rg -n -A20 'impl Libp2pSyncAdapter' crates/synapse-infra/src/adapters/libp2p_sync_adapter.rs | head -80Repository: iberi22/synapse-protocol Length of output: 811 🏁 Script executed: #!/bin/bash
# Check if start() is actually called anywhere in production (not tests)
rg -n '\.start\(' crates --glob='!*test*' --glob='!**/tests/**'Repository: iberi22/synapse-protocol Length of output: 144 Guard The Either add a flag to track whether 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Synchronizes memory metadata over the DHT. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub async fn sync_memories(&self) -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| info!("Starting memory sync process"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 1. Publish our latest memory update timestamp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let local_peer_id = self.network_port.local_peer_id().await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let last_node = self.memory_storage.fetch_last_node().await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let last_update = last_node.map(|n| n.updated_at).unwrap_or(0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let key = memory_sync_key(&local_peer_id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let value = last_update.to_be_bytes().to_vec(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.network_port.dht_put(key, value).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 2. Check connected peers' memory timestamps | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let connected_peers = self.network_port.connected_peers().await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for peer_id in connected_peers { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if peer_id == local_peer_id { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let key = memory_sync_key(&peer_id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Ok(Some(value)) = self.network_port.dht_get(key).await { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if value.len() == 8 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut bytes = [0u8; 8]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bytes.copy_from_slice(&value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let peer_last_update = i64::from_be_bytes(bytes); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if peer_last_update > last_update { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| info!("Peer {} has newer memories ({} > {}). In a full implementation, we would trigger a reconciliation.", peer_id, peer_last_update, last_update); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+101
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic to convert the if let Ok(bytes) = <[u8; 8]>::try_from(value.as_slice()) {
let peer_last_update = i64::from_be_bytes(bytes);
if peer_last_update > last_update {
info!("Peer {} has newer memories ({} > {}). In a full implementation, we would trigger a reconciliation.", peer_id, peer_last_update, last_update);
}
} |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+99
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t silently drop DHT read failures in memory sync. The current Suggested patch- let key = memory_sync_key(&peer_id);
- if let Ok(Some(value)) = self.network_port.dht_get(key).await {
- if value.len() == 8 {
- let mut bytes = [0u8; 8];
- bytes.copy_from_slice(&value);
- let peer_last_update = i64::from_be_bytes(bytes);
-
- if peer_last_update > last_update {
- info!("Peer {} has newer memories ({} > {}). In a full implementation, we would trigger a reconciliation.", peer_id, peer_last_update, last_update);
- }
- }
- }
+ let key = memory_sync_key(&peer_id);
+ match self.network_port.dht_get(key).await {
+ Ok(Some(value)) if value.len() == 8 => {
+ let mut bytes = [0u8; 8];
+ bytes.copy_from_slice(&value);
+ let peer_last_update = i64::from_be_bytes(bytes);
+
+ if peer_last_update > last_update {
+ info!("Peer {} has newer memories ({} > {}). In a full implementation, we would trigger a reconciliation.", peer_id, peer_last_update, last_update);
+ }
+ }
+ Ok(Some(value)) => {
+ warn!("Invalid memory sync payload size from {}: {}", peer_id, value.len());
+ }
+ Ok(None) => {}
+ Err(e) => {
+ warn!("Failed to read memory sync marker for {}: {}", peer_id, e);
+ }
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| info!("Memory sync process completed"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Synchronizes peer information between local storage and the DHT. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub async fn sync_peers(&self) -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| info!("Starting peer sync process"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for handling Kademlia query results is quite nested and repeats the
if let Some(sender) = ...check. This can be refactored to be more linear and easier to read by handlingGetRecordandPutRecordresults in a more consolidated way. This improves readability and maintainability.