Skip to content

Commit 98318ba

Browse files
authored
feat(lattica): infra reconnect and beetswap not request all peers (#26)
2 parents b538e5e + d8e82af commit 98318ba

File tree

16 files changed

+1771
-458
lines changed

16 files changed

+1771
-458
lines changed

bindings/python/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings/python/examples/bitswap.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ def main():
1414
.with_bootstraps(bootstrap_nodes) \
1515
.build()
1616

17+
lattica.configure_bitswap_peer_selection(
18+
top_n=3,
19+
enabled=True,
20+
min_peers=2,
21+
enable_randomness=True,
22+
have_wait_window_ms=100,
23+
min_candidate_ratio=0.3
24+
)
25+
1726
# wait for connection
1827
time.sleep(1)
1928

@@ -26,6 +35,8 @@ def main():
2635
data = lattica.get_block(request_cid)
2736
print(f"data: {data}")
2837

38+
lattica.print_bitswap_stats()
39+
2940
else:
3041
# put block
3142
cid = lattica.put_block(b'hello')

bindings/python/py_src/lattica/client.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,103 @@ def is_symmetric_nat(self):
253253
except Exception as e:
254254
raise RuntimeError(f"Failed to check is_symmetric_nat error: {e}")
255255

256+
def configure_bitswap_peer_selection(
257+
self,
258+
top_n: int = 3,
259+
enabled: bool = True,
260+
min_peers: int = 2,
261+
enable_randomness: bool = True,
262+
have_wait_window_ms: int = 100,
263+
min_candidate_ratio: float = 0.3
264+
) -> None:
265+
"""Configure Bitswap peer selection strategy.
266+
267+
Args:
268+
top_n: Number of top peers to select
269+
enabled: Enable smart selection
270+
min_peers: Minimum peers threshold
271+
enable_randomness: Enable randomness in selection
272+
have_wait_window_ms: Wait window in ms after first Have response before selecting peers.
273+
This allows more peers to respond, ensuring better selection. Default: 100ms
274+
min_candidate_ratio: Minimum candidate ratio (0.0-1.0) before starting selection.
275+
Selection starts when candidates >= total_peers * min_candidate_ratio. Default: 0.3
276+
"""
277+
try:
278+
self._lattica_instance.configure_bitswap_peer_selection(
279+
top_n, enabled, min_peers, enable_randomness,
280+
have_wait_window_ms, min_candidate_ratio
281+
)
282+
except Exception as e:
283+
raise RuntimeError(f"Failed to configure bitswap peer selection: {e}")
284+
285+
def get_bitswap_global_stats(self) -> dict:
286+
"""Get Bitswap global statistics.
287+
288+
Returns:
289+
dict with total_requests, successful_requests, failed_requests, total_bytes_received
290+
"""
291+
try:
292+
return self._lattica_instance.get_bitswap_global_stats()
293+
except Exception as e:
294+
raise RuntimeError(f"Failed to get bitswap global stats: {e}")
295+
296+
def get_bitswap_peer_rankings(self) -> List[dict]:
297+
"""Get Bitswap peer rankings with detailed metrics.
298+
299+
Returns:
300+
List of dicts sorted by score descending, each containing:
301+
- peer_id: Peer ID string
302+
- score: Composite score (0-100)
303+
- blocks_received: Number of successfully received blocks
304+
- failures: Number of failures
305+
- success_rate: Success rate (0.0-1.0)
306+
- avg_speed: Average speed in bytes/sec
307+
"""
308+
try:
309+
return self._lattica_instance.get_bitswap_peer_rankings()
310+
except Exception as e:
311+
raise RuntimeError(f"Failed to get bitswap peer rankings: {e}")
312+
313+
def print_bitswap_stats(self) -> None:
314+
"""Print Bitswap stats report to stdout.
315+
316+
This method combines get_bitswap_global_stats() and get_bitswap_peer_rankings()
317+
to display a formatted statistics report in Python.
318+
"""
319+
try:
320+
# Get global stats
321+
stats = self.get_bitswap_global_stats()
322+
total_requests = stats.get('successful_requests', 0) + stats.get('failed_requests', 0)
323+
success_rate = (stats.get('successful_requests', 0) / total_requests * 100) if total_requests > 0 else 0.0
324+
bytes_mb = stats.get('total_bytes_received', 0) / (1024 * 1024)
325+
326+
print(f"\n{'='*100}")
327+
print("Bitswap Statistics Report")
328+
print(f"{'='*100}")
329+
print(f" Total Requests: {total_requests}")
330+
print(f" Successful: {stats.get('successful_requests', 0)}")
331+
print(f" Failed: {stats.get('failed_requests', 0)}")
332+
print(f" Success Rate: {success_rate:.2f}%")
333+
print(f" Total Received: {bytes_mb:.2f} MB")
334+
335+
# Get peer rankings with details
336+
rankings = self.get_bitswap_peer_rankings()
337+
if rankings:
338+
print(f"\nTop {min(len(rankings), 10)} Peers:")
339+
print(f" {'#':<3} {'Peer ID':<54} {'Score':>7} {'Success':>8} {'Fail':>5} {'Rate':>7} {'Speed':>12}")
340+
print(f" {'-'*3} {'-'*54} {'-'*7} {'-'*8} {'-'*5} {'-'*7} {'-'*12}")
341+
for i, peer in enumerate(rankings[:10], 1):
342+
peer_id = peer['peer_id']
343+
display_id = peer_id[:52] + ".." if len(peer_id) > 54 else peer_id
344+
speed_mb = peer['avg_speed'] / (1024 * 1024)
345+
rate_pct = peer['success_rate'] * 100
346+
print(f" {i:<3} {display_id:<54} {peer['score']:>7.2f} {peer['blocks_received']:>8} {peer['failures']:>5} {rate_pct:>6.1f}% {speed_mb:>9.2f}MB/s")
347+
else:
348+
print("\n No peer data available.")
349+
print(f"{'='*100}\n")
350+
except Exception as e:
351+
raise RuntimeError(f"Failed to print bitswap stats: {e}")
352+
256353
def __enter__(self):
257354
self._ensure_initialized()
258355
return self

bindings/python/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "lattica"
7-
version = "1.0.18"
7+
version = "1.0.19"
88
description = "A unified Python SDK for P2P networking with integrated DHT and NAT capabilities"
99
readme = "README.md"
1010
license = {text = "MIT"}
@@ -127,4 +127,4 @@ exclude_lines = [
127127
"if __name__ == .__main__.:",
128128
"class .*\\bProtocol\\):",
129129
"@(abc\\.)?abstractmethod",
130-
]
130+
]

bindings/python/src/core.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use lattica::{network, rpc, common};
22
use std::sync::{Arc};
33
use tokio::sync::{Mutex};
4-
use pyo3::{prelude::*, types::PyDict, IntoPyObjectExt};
4+
use pyo3::{prelude::*, types::{PyDict, PyList}, IntoPyObjectExt};
55
use tokio::runtime::Runtime;
66
use libp2p::{Multiaddr, PeerId};
77
use async_trait::async_trait;
@@ -557,6 +557,87 @@ impl LatticaSDK {
557557
fn is_symmetric_nat(&self) -> PyResult<Option<bool>> {
558558
self.lattica.is_symmetric_nat().map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("{:?}", e)))
559559
}
560+
561+
/// Configure Bitswap peer selection strategy
562+
///
563+
/// Args:
564+
/// top_n: Number of top peers to select (default: 3)
565+
/// enabled: Enable smart selection (default: true)
566+
/// min_peers: Minimum peers threshold (default: 2)
567+
/// enable_randomness: Enable randomness in selection (default: true)
568+
/// have_wait_window_ms: Wait window in ms after first Have response (default: 100)
569+
/// min_candidate_ratio: Minimum candidate ratio before selection (default: 0.3)
570+
#[pyo3(signature = (top_n = 3, enabled = true, min_peers = 2, enable_randomness = true, have_wait_window_ms = 100, min_candidate_ratio = 0.3))]
571+
fn configure_bitswap_peer_selection(
572+
&self,
573+
top_n: usize,
574+
enabled: bool,
575+
min_peers: usize,
576+
enable_randomness: bool,
577+
have_wait_window_ms: u64,
578+
min_candidate_ratio: f64,
579+
) -> PyResult<()> {
580+
Python::with_gil(|py| {
581+
py.allow_threads(|| {
582+
self.runtime.block_on(async move {
583+
let config = lattica::PeerSelectionConfig {
584+
top_n,
585+
enabled,
586+
min_peers,
587+
enable_randomness,
588+
have_wait_window_ms,
589+
min_candidate_ratio,
590+
};
591+
self.lattica.configure_bitswap_peer_selection(config).await
592+
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("{:?}", e)))
593+
})
594+
})
595+
})
596+
}
597+
598+
/// Get Bitswap global statistics
599+
fn get_bitswap_global_stats(&self, py: Python) -> PyResult<PyObject> {
600+
py.allow_threads(|| {
601+
self.runtime.block_on(async move {
602+
let stats = self.lattica.get_bitswap_global_stats().await
603+
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("{:?}", e)))?;
604+
605+
Python::with_gil(|py| {
606+
let dict = PyDict::new(py);
607+
dict.set_item("total_requests", stats.total_requests)?;
608+
dict.set_item("successful_requests", stats.successful_requests)?;
609+
dict.set_item("failed_requests", stats.failed_requests)?;
610+
dict.set_item("total_bytes_received", stats.total_bytes_received)?;
611+
Ok(dict.into_py_any(py)?)
612+
})
613+
})
614+
})
615+
}
616+
617+
/// Get Bitswap peer rankings with detailed metrics
618+
fn get_bitswap_peer_rankings(&self, py: Python) -> PyResult<PyObject> {
619+
py.allow_threads(|| {
620+
self.runtime.block_on(async move {
621+
let details = self.lattica.get_bitswap_peer_rankings().await
622+
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("{:?}", e)))?;
623+
624+
Python::with_gil(|py| {
625+
let list = PyList::empty(py);
626+
for detail in details {
627+
let dict = PyDict::new(py);
628+
dict.set_item("peer_id", detail.peer_id)?;
629+
dict.set_item("score", detail.score)?;
630+
dict.set_item("blocks_received", detail.blocks_received)?;
631+
dict.set_item("failures", detail.failures)?;
632+
dict.set_item("success_rate", detail.success_rate)?;
633+
dict.set_item("avg_speed", detail.avg_speed)?;
634+
list.append(dict)?;
635+
}
636+
Ok(list.into_py_any(py)?)
637+
})
638+
})
639+
})
640+
}
560641
}
561642

562643
#[pymethods]

lattica/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lattica/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
pub mod common;
22
pub mod network;
33
pub mod rpc;
4-
pub mod fs_blockstore;
4+
pub mod fs_blockstore;
5+
6+
// Re-export beetswap types for external use
7+
pub use beetswap::PeerSelectionConfig;

lattica/src/network/behaviour.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,4 +271,26 @@ impl LatticaBehaviour{
271271
tx.send(Err(anyhow!("Kad is not enabled"))).ok();
272272
}
273273
}
274+
275+
/// Configure Bitswap peer selection strategy
276+
pub fn configure_bitswap_peer_selection(&mut self, config: beetswap::PeerSelectionConfig) {
277+
if let Some(bitswap) = self.bitswap.as_mut() {
278+
bitswap.set_peer_selection_config(config);
279+
}
280+
}
281+
282+
/// Get Bitswap peer selection config
283+
pub fn get_bitswap_peer_selection_config(&self) -> Option<beetswap::PeerSelectionConfig> {
284+
self.bitswap.as_ref().map(|b| b.get_peer_selection_config().clone())
285+
}
286+
287+
/// Get Bitswap global stats
288+
pub fn get_bitswap_global_stats(&self) -> Option<beetswap::GlobalStats> {
289+
self.bitswap.as_ref().map(|b| b.get_global_stats().clone())
290+
}
291+
292+
/// Get Bitswap peer rankings with detailed metrics
293+
pub fn get_bitswap_peer_rankings(&self) -> Vec<beetswap::PeerDetail> {
294+
self.bitswap.as_ref().map(|b| b.get_peer_rankings()).unwrap_or_default()
295+
}
274296
}

0 commit comments

Comments
 (0)