Skip to content

Commit e644664

Browse files
committed
feat: add scorer-inspect crate with text/csv/json output
scorer-inspect is a small standalone CLI for offline diagnostics of LDK serialized scorer files - both the `latest.bin` style payloads served at URLs like api.blocktank.to/scorer-prod or scores.zeusln.com/latest.bin and the bytes returned by Node::export_pathfinding_scores. Both wire formats are identical in current LDK (ProbabilisticScorer::write just delegates to ChannelLiquidities::write), so the parser is a single ChannelLiquidities::read; the --source flag is metadata only. Output is configurable as plain text (default), CSV, or pretty-printed JSON, optionally written to disk via --save. The summary section reports entry count, history-populated percentage, and offset / bucket weight distributions; the per-channel section can be limited via --top or fully dumped via --all, and sorted by narrowest offset window, recency, or historical bucket weight. Depends on the diagnostics() accessors added to ChannelLiquidities and ProbabilisticScorer in the synonymdev/rust-lightning fork. The ldk-node [patch.crates-io] block is unchanged here; the rust-lightning PR must land first and the rev pin must be bumped before this branch can merge. For local development of this crate, point [patch.crates-io] at a checkout of synonymdev/rust-lightning that has the diagnostics accessors applied.
1 parent afcebdf commit e644664

4 files changed

Lines changed: 418 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = [".", "crates/bdk-wallet-aggregate"]
2+
members = [".", "crates/bdk-wallet-aggregate", "crates/scorer-inspect"]
33
exclude = ["bindings/uniffi-bindgen"]
44

55
[package]

crates/scorer-inspect/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "scorer-inspect"
3+
version = "0.1.0"
4+
edition = "2021"
5+
rust-version = "1.85"
6+
description = "Offline diagnostics CLI for LDK ProbabilisticScorer / ChannelLiquidities files."
7+
license = "MIT OR Apache-2.0"
8+
9+
[dependencies]
10+
anyhow = "1"
11+
clap = { version = "4", features = ["derive"] }
12+
csv = "1"
13+
humansize = "2"
14+
lightning = { version = "0.2.0", features = ["std"] }
15+
serde = { version = "1", features = ["derive"] }
16+
serde_json = "1"

crates/scorer-inspect/README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# scorer-inspect
2+
3+
Offline diagnostics CLI for LDK serialized scorer files.
4+
5+
Reads a binary file produced by either:
6+
- LDK's external scorer / `ProbabilisticScorer::write` (e.g. `https://api.blocktank.to/scorer-prod`, `https://scores.zeusln.com/latest.bin`), or
7+
- ldk-node's `Node::export_pathfinding_scores`.
8+
9+
Both wire formats are identical in current LDK — `ProbabilisticScorer::write` just calls `self.channel_liquidities.write(w)`. The tool always reads via `ChannelLiquidities::read` and the `--source` flag is purely metadata for the report.
10+
11+
## Build
12+
13+
```
14+
cargo build -p scorer-inspect --release
15+
```
16+
17+
## Usage
18+
19+
```
20+
scorer-inspect <FILE>
21+
[--source served|exported]
22+
[--output text|csv|json]
23+
[--save PATH]
24+
[--top N]
25+
[--all]
26+
[--sort narrow|recent|history]
27+
```
28+
29+
- `--source` — annotates the report; doesn't affect parsing.
30+
- `--output text` (default) — short summary + top-N channel rows for human review.
31+
- `--output csv` — summary row, blank line, then one row per channel. Editor-friendly.
32+
- `--output json``{ summary: {...}, channels: [...] }`. Machine-readable.
33+
- `--save PATH` — write to file instead of stdout.
34+
- `--top N` (default 20) — limit channel rows.
35+
- `--all` — dump every entry; overrides `--top`.
36+
- `--sort``narrow` (smallest offset window first; highest information density), `recent` (most recently updated first), `history` (largest historical-bucket weight first; most probe-derived signal).
37+
38+
## Examples
39+
40+
```
41+
# Quick eyeball of Zeus's served file
42+
curl -o /tmp/zeus.bin https://scores.zeusln.com/latest.bin
43+
scorer-inspect /tmp/zeus.bin --source served
44+
45+
# Full per-channel CSV diff between Zeus and Bitkit
46+
curl -o /tmp/blocktank.bin https://api.blocktank.to/scorer-prod
47+
scorer-inspect /tmp/zeus.bin --source served --all --output csv --save /tmp/zeus.csv
48+
scorer-inspect /tmp/blocktank.bin --source served --all --output csv --save /tmp/blocktank.csv
49+
```
50+
51+
## What the columns mean
52+
53+
- `min_liquidity_offset_msat` / `max_liquidity_offset_msat` — non-directional offsets relative to the channel's node ordering. Resolving them into directional `min_liquidity_sat` / `max_liquidity_sat` requires a `NetworkGraph` (capacity + node-id ordering), which this tool doesn't yet take.
54+
- `has_history` — whether either historical-bucket array is non-zero. The single best signal for distinguishing probe-derived data from synthetic graph seeding.
55+
- `total_valid_points_tracked` (CSV/JSON), `history_weight` (text) — LDK-internal scalar weight summarizing the historical bucket distribution. Stored as `f64`; not an integer payment count.
56+
- `last_updated_secs` — seconds since the unix epoch when either liquidity bound was last modified.
57+
58+
## Distinguishing rich probe data from synthetic seeding
59+
60+
A scorer file can be large for two very different reasons:
61+
62+
- **Probe-rich**: many entries with `has_history=true`, narrow `[min_offset, max_offset]` windows, sizable `total_valid_points_tracked`. This is what real probing produces.
63+
- **Synthetic-coverage**: many entries with `has_history=false`, `min_offset=0`, `max_offset` close to channel capacity, zero bucket weights. This is what gossip-graph seeding produces — big file, low pathfinding signal.
64+
65+
Compare the two by running the tool against any scorer file and checking the `history populated` percentage and the offset-window distribution.
66+
67+
## Limits
68+
69+
- v1 is graph-free: directional output and capacity-resolved sats are not available without a `NetworkGraph`. Adding `--graph PATH` is tracked as future work.

0 commit comments

Comments
 (0)