<div align="center">
<h2>Ligh𝞽ning</h2>
<p><strong>Rust QUIC transport layer for Bittensor</strong></p>
<p>Persistent QUIC connections with sr25519 handshake authentication for validator-miner communication.</p>
<p>
<a href="https://crates.io/crates/btlightning"><img src="https://img.shields.io/crates/v/btlightning" alt="crates.io"></a>
<a href="https://docs.rs/btlightning/latest/btlightning/"><img src="https://img.shields.io/docsrs/btlightning" alt="docs.rs"></a>
<a href="https://pypi.org/project/btlightning/"><img src="https://img.shields.io/pypi/v/btlightning" alt="PyPI"></a>
</p>
</div>
## Python
```bash
pip install btlightning
```
```python
from btlightning import Lightning
client = Lightning(wallet_hotkey="5GrwvaEF...")
client.set_python_signer(my_signer_callback)
client.initialize_connections([
{"hotkey": "5FHneW46...", "ip": "192.168.1.1", "port": 8443}
])
response = client.query_axon(
{"hotkey": "5FHneW46...", "ip": "192.168.1.1", "port": 8443},
{"synapse_type": "MyQuery", "data": {"key": "value"}}
)
```
## Rust
```toml
[dependencies]
btlightning = { version = "0.1", features = ["subtensor"] }
```
With the `subtensor` feature, the client discovers miners from the metagraph and keeps connections in sync automatically:
```rust,ignore
use btlightning::{LightningClient, LightningClientConfig, Sr25519Signer, MetagraphMonitorConfig};
let config = LightningClientConfig {
metagraph: Some(MetagraphMonitorConfig::finney(YOUR_NETUID)),
..Default::default()
};
let mut client = LightningClient::with_config("5GrwvaEF...".into(), config)?;
client.set_signer(Box::new(Sr25519Signer::from_seed(seed)));
client.initialize_connections(vec![]).await?;
```
Without `subtensor`, pass miner addresses directly:
```rust,ignore
use btlightning::{LightningClient, Sr25519Signer, QuicAxonInfo};
let mut client = LightningClient::new("5GrwvaEF...".into());
client.set_signer(Box::new(Sr25519Signer::from_seed(seed)));
client.initialize_connections(vec![
QuicAxonInfo::new("5FHneW46...".into(), "192.168.1.1".into(), 8443, 4)
]).await?;
```
Query a single miner by axon info and deserialize the response:
```rust,ignore
use btlightning::QuicRequest;
use serde::{Serialize, Deserialize};
#[derive(Serialize)]
struct MyQuery { prompt: String }
#[derive(Deserialize)]
struct MyResult { answer: String }
let request = QuicRequest::from_typed("MyQuery", &MyQuery { prompt: "hello".into() })?;
let response = client.query_axon(axon_info, request).await?.into_result()?;
let result: MyResult = response.deserialize_data()?;
```
Fan out to every QUIC miner on the subnet concurrently:
```rust,ignore
use std::sync::Arc;
use tokio::task::JoinSet;
let client = Arc::new(client);
let miners = metagraph.quic_miners();
let mut tasks = JoinSet::new();
for miner in miners {
let req = QuicRequest::from_typed("MyQuery", &MyQuery { prompt: "hello".into() })?;
let client = Arc::clone(&client);
tasks.spawn(async move {
(miner.hotkey.clone(), client.query_axon(miner, req).await)
});
}
while let Some(Ok((hotkey, result))) = tasks.join_next().await {
match result {
Ok(resp) => { /* handle response from hotkey */ }
Err(e) => { /* miner failed */ }
}
}
```
## Build from source
```bash
cargo build -p btlightning
maturin develop --manifest-path crates/btlightning-py/Cargo.toml
```
## Performance
Benchmarked on Apple Silicon (M-series), macOS, loopback networking. Echo handler returns input unchanged. Connection setup includes first request-response round trip. Lightning authenticates once at connection time and amortizes over all subsequent requests; bittensor signs and verifies every request. Bittensor payloads are base64-encoded (JSON has no binary type), adding ~33% wire overhead beyond the nominal payload size. Source: [`benchmarks/`](benchmarks/).
| Protocol | HTTP/1.1 | QUIC |
| Serialization | JSON | MessagePack |
| Transport encryption | None | TLS 1.3 |
| Auth model | Per-request | Per-connection |
| Connection setup (p50) | 114.46 ms | 2.68 ms | 0.71 ms |
| Latency p50 (1KB) | 24.01 ms | 0.08 ms | 0.05 ms |
| Latency p99 (1KB) | 28.80 ms | 0.09 ms | 0.07 ms |
| Throughput (1KB) | 41 req/s | 26,539 req/s | 68,650 req/s |
| Wire size (1KB payload) | 2,032 bytes | 1,052 bytes | 1,052 bytes |
|  | bittensor |
|  | lightning (Python) |
|  | lightning (Rust) |
> [!NOTE]
> Charts use log10 scale. Refer to the tables below for raw values.
### Latency p50
```mermaid
---
config:
xyChart:
plotReservedSpacePercent: 60
themeVariables:
xyChart:
backgroundColor: "#000000"
plotColorPalette: "#808080, #FF8C00, #FFE000"
titleColor: "#ffffff"
xAxisLabelColor: "#aaaaaa"
xAxisTitleColor: "#cccccc"
xAxisTickColor: "#666666"
xAxisLineColor: "#444444"
yAxisLabelColor: "#aaaaaa"
yAxisTitleColor: "#cccccc"
yAxisTickColor: "#666666"
yAxisLineColor: "#444444"
---
xychart-beta
x-axis ["256B", "1KB", "10KB", "100KB", "1MB"]
y-axis "log10(us)" 0 --> 5
bar "bittensor" [4.39, 4.38, 4.39, 4.42, 4.61]
bar "lightning (Python)" [1.90, 1.90, 2.36, 3.27, 4.31]
bar "lightning (Rust)" [1.70, 1.70, 2.04, 2.84, 3.87]
```
### Throughput (req/s)
```mermaid
---
config:
xyChart:
plotReservedSpacePercent: 60
themeVariables:
xyChart:
backgroundColor: "#000000"
plotColorPalette: "#FFE000, #FF8C00, #808080"
titleColor: "#ffffff"
xAxisLabelColor: "#aaaaaa"
xAxisTitleColor: "#cccccc"
xAxisTickColor: "#666666"
xAxisLineColor: "#444444"
yAxisLabelColor: "#aaaaaa"
yAxisTitleColor: "#cccccc"
yAxisTickColor: "#666666"
yAxisLineColor: "#444444"
---
xychart-beta
x-axis ["256B", "1KB", "10KB", "100KB", "1MB"]
y-axis "log10(req/s)" 0 --> 5
bar "lightning (Rust)" [4.92, 4.84, 4.27, 3.26, 2.26]
bar "lightning (Python)" [4.55, 4.42, 3.83, 2.94, 1.94]
bar "bittensor" [1.61, 1.61, 1.60, 1.57, 1.40]
```
<details>
<summary>Full results (all payload sizes)</summary>
#### Latency (ms)
| 256B | bittensor | 24.50 | 28.04 | 35.05 |
| | lightning (py) | 0.08 | 0.19 | 0.23 |
| | lightning (rust) | 0.05 | 0.06 | 0.07 |
| 1KB | bittensor | 24.01 | 25.57 | 28.80 |
| | lightning (py) | 0.08 | 0.09 | 0.09 |
| | lightning (rust) | 0.05 | 0.06 | 0.07 |
| 10KB | bittensor | 24.70 | 25.70 | 26.59 |
| | lightning (py) | 0.23 | 0.24 | 0.26 |
| | lightning (rust) | 0.11 | 0.12 | 0.13 |
| 100KB | bittensor | 26.13 | 27.31 | 29.42 |
| | lightning (py) | 1.88 | 2.25 | 2.37 |
| | lightning (rust) | 0.69 | 0.73 | 0.75 |
| 1MB | bittensor | 40.72 | 43.11 | 48.00 |
| | lightning (py) | 20.55 | 22.48 | 23.31 |
| | lightning (rust) | 7.40 | 12.36 | 17.02 |
#### Throughput (req/s)
| 256B | 41 | 35,181 | 83,460 |
| 1KB | 41 | 26,539 | 68,650 |
| 10KB | 40 | 6,729 | 18,686 |
| 100KB | 38 | 875 | 1,819 |
| 1MB | 25 | 88 | 181 |
#### Wire overhead (bytes)
| 256B | 1,008 | 284 |
| 1KB | 2,032 | 1,052 |
| 10KB | 14,321 | 10,268 |
| 100KB | 137,202 | 102,430 |
| 1MB | 1,398,771 | 1,048,606 |
</details>