Expand description
Ligh𝞽ning
Rust QUIC transport layer for Bittensor
Persistent QUIC connections with sr25519 handshake authentication for validator-miner communication.
§Python
pip install btlightningfrom 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
[dependencies]
btlightning = { version = "0.1", features = ["subtensor"] }With the subtensor feature, the client discovers miners from the metagraph and keeps connections in sync automatically:
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:
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:
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:
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
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/.
| bittensor (dendrite/axon) | lightning | |
|---|---|---|
| Protocol | HTTP/1.1 | QUIC |
| Serialization | JSON | MessagePack |
| Transport encryption | None | TLS 1.3 |
| Auth model | Per-request | Per-connection |
| Metric | bittensor | lightning (Python) | lightning (Rust) |
|---|---|---|---|
| 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 |
| Color | System |
|---|---|
| bittensor | |
| lightning (Python) | |
| lightning (Rust) |
[!NOTE] Charts use log10 scale. Refer to the tables below for raw values.
§Latency p50
---
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)
---
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]Full results (all payload sizes)
§Latency (ms)
| Payload | System | p50 | p95 | p99 |
|---|---|---|---|---|
| 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)
| Payload | bittensor | lightning (Python) | lightning (Rust) |
|---|---|---|---|
| 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)
| Payload | bittensor | lightning |
|---|---|---|
| 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 |
Re-exports§
pub use client::LightningClient;pub use client::LightningClientConfig;pub use client::LightningClientConfigBuilder;pub use client::StreamingResponse;pub use error::LightningError;pub use error::Result;pub use metagraph::is_valid_ip;pub use metagraph::Metagraph;pub use metagraph::MetagraphMonitorConfig;pub use metagraph::NeuronInfo;pub use metagraph::FINNEY_ENDPOINT;pub use metagraph::TESTNET_ENDPOINT;pub use server::typed_async_handler;pub use server::typed_handler;pub use server::AsyncSynapseHandler;pub use server::LightningServer;pub use server::LightningServerConfig;pub use server::LightningServerConfigBuilder;pub use server::StreamingSynapseHandler;pub use server::SynapseHandler;pub use server::ValidatorPermitResolver;pub use signing::BtWalletSigner;pub use signing::CallbackSigner;pub use signing::Signer;pub use signing::Sr25519Signer;pub use types::parse_frame_header;pub use types::HandshakeRequest;pub use types::HandshakeResponse;pub use types::MessageType;pub use types::PeerAddr;pub use types::QuicAxonInfo;pub use types::QuicRequest;pub use types::QuicResponse;pub use types::SynapsePacket;pub use types::SynapseResponse;pub use types::DEFAULT_MAX_FRAME_PAYLOAD;