We were reading two fields of eleven — the name and the peer id — and dropping the rest, including the hardware every node already announces. `NodeMetadata` now carries the slow-moving half: implementation, version, the target triple, CPU, cores, memory, kernel, distro, whether it is virtualised, and the process start time. The volatile half stays out on purpose. Peer count, transaction queue, bandwidth and state cache change every few seconds, are already a rolling series in the feed, and would be a lot of rows for something nobody would page back through. `startup_time` is kept rather than derived, so node uptime becomes a fact about the node rather than about how long this observer has been watching it. **Location is resolved to a country and no finer.** The feed sends `[latitude, longitude, city]` and no country, so `blackbeard_core::geo` derives one offline from a 508 KiB boundary set — no network call, and no third party told where these nodes are. The city is read and discarded: those coordinates come from an IP geolocation database and at city resolution are wrong often enough that publishing one would assert something we do not know. Nothing downstream can render a city because nothing downstream is given one. The lookup returns subdivisions before countries, and `GB-ENG` is exactly the extra precision this is avoiding, so only the two-letter code survives. `LocatedNode` is a separate frame because the telemetry server geolocates asynchronously — `AddedNode` almost always carries a null location and the country follows a moment later. Which means a reconnect, which re-sends every node with a null location again, must not erase a country already resolved. There is a test for that, because it would present as countries silently vanishing hours after a restart. The mapping is pinned by tests built from frames captured off the live feed rather than from a spec: the format is positional and differs between telemetry releases, so bytes the feed actually sent are the only honest fixture. If a future release renumbers a field, those tests are what says so. Refs #15 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
73 lines
3.3 KiB
TOML
73 lines
3.3 KiB
TOML
[workspace]
|
|
resolver = "3"
|
|
members = ["crates/*"]
|
|
|
|
[workspace.package]
|
|
version = "0.1.0"
|
|
edition = "2024"
|
|
rust-version = "1.85"
|
|
license = "GPL-3.0-or-later"
|
|
authors = ["Rob Thijssen <rob@lair.cafe>"]
|
|
repository = "https://git.lair.cafe/Quantus-Network/blackbeard.observer"
|
|
|
|
[workspace.dependencies]
|
|
blackbeard-entities = { path = "crates/blackbeard-entities", version = "=0.1.0" }
|
|
blackbeard-core = { path = "crates/blackbeard-core", version = "=0.1.0" }
|
|
blackbeard-data = { path = "crates/blackbeard-data", version = "=0.1.0" }
|
|
|
|
anyhow = "1"
|
|
axum = { version = "0.8", features = ["ws", "macros"] }
|
|
chrono = { version = "0.4", default-features = false, features = ["clock", "serde", "std"] }
|
|
clap = { version = "4", features = ["derive", "env"] }
|
|
figment = { version = "0.10", features = ["toml", "env"] }
|
|
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
|
hex = "0.4"
|
|
# The chain's own description of itself, and a decoder driven by it. This is the
|
|
# alternative to hand-written SCALE readers that rot on every runtime upgrade,
|
|
# and to pulling in subxt or the runtime crate to avoid writing them.
|
|
frame-metadata = { version = "23", default-features = false, features = ["current", "decode"] }
|
|
parity-scale-codec = { version = "3", default-features = false, features = ["derive"] }
|
|
scale-info = { version = "2", default-features = false }
|
|
scale-value = { version = "0.18", default-features = false }
|
|
# Only for `IgnoreVisitor`: the pre-flight walk in `Runtime::decode_checked`
|
|
# that keeps a bogus length prefix from sizing an allocation. Pinned to the
|
|
# version `scale-value` itself resolves, so both see one type registry.
|
|
scale-decode = { version = "0.16", default-features = false }
|
|
# Offline reverse geocoding, country level only. The embedded 180x90 dataset is
|
|
# 508 KiB and needs no network at all — see `blackbeard_core::geo`.
|
|
country-boundaries = "1"
|
|
rust_iso3166 = "0.2"
|
|
# The chain's own reward-address derivation, so the observer computes the same
|
|
# account `pallets/mining-rewards` pays rather than guessing or asking.
|
|
qp-poseidon-core = { version = "3.1.0", default-features = false }
|
|
# SS58 checksums only. Base58 is hand-rolled; this is not.
|
|
blake2 = { version = "0.10", default-features = false }
|
|
twox-hash = { version = "2", default-features = false, features = ["xxhash64"] }
|
|
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-native-roots"] }
|
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
sqlx = { version = "0.8", default-features = false, features = [
|
|
"postgres",
|
|
"runtime-tokio-rustls",
|
|
"macros", "json",
|
|
"migrate",
|
|
"chrono",
|
|
"bigdecimal",
|
|
] }
|
|
bigdecimal = "0.4"
|
|
primitive-types = { version = "0.13", default-features = false }
|
|
thiserror = "2"
|
|
tokio = { version = "1", features = ["full"] }
|
|
tokio-tungstenite = { version = "0.24", default-features = false, features = ["connect", "rustls-tls-native-roots"] }
|
|
tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip"] }
|
|
tracing = "0.1"
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
|
ts-rs = { version = "10", features = ["serde-compat", "chrono-impl", "no-serde-warnings"] }
|
|
url = "2"
|
|
|
|
[profile.release]
|
|
lto = "thin"
|
|
codegen-units = 1
|
|
strip = "debuginfo"
|