mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
174 lines
5.5 KiB
Go
174 lines
5.5 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
//go:build metrics
|
|
|
|
package security
|
|
|
|
import (
|
|
"strconv"
|
|
"testing"
|
|
|
|
consensusconfig "github.com/luxfi/consensus/config"
|
|
"github.com/luxfi/metric"
|
|
)
|
|
|
|
// TestMetrics_SecurityProfileGauge_Reflects_ActiveProfile proves the
|
|
// chain-wide profile gauges carry the active profile's posture after
|
|
// SetActiveProfile is called.
|
|
//
|
|
// Closes the "operator can't tell what posture the chain is on"
|
|
// audit gap: a 1 → 0 transition on profile_post_quantum_end_to_end
|
|
// is the alert primitive an operator wires to PagerDuty.
|
|
func TestMetrics_SecurityProfileGauge_Reflects_ActiveProfile(t *testing.T) {
|
|
reg := metric.NewRegistry()
|
|
metricsInstance := metric.NewWithRegistry("lux_security", reg)
|
|
m := NewMetrics(metricsInstance)
|
|
|
|
strictPQ := consensusconfig.StrictPQ()
|
|
m.SetActiveProfile(strictPQ)
|
|
|
|
// Gather and assert the gauge family for each expected metric
|
|
// carries the strict-PQ posture (1 on each headline boolean).
|
|
families, err := reg.Gather()
|
|
if err != nil {
|
|
t.Fatalf("Gather: %v", err)
|
|
}
|
|
values := flattenMetricValues(families)
|
|
|
|
expect := map[string]float64{
|
|
"lux_security_profile_post_quantum_end_to_end:profile_id=" +
|
|
strconv.Itoa(int(consensusconfig.ProfileStrictPQ)) +
|
|
",profile_name=STRICT_PQ": 1,
|
|
"lux_security_profile_nist_friendly:profile_id=" +
|
|
strconv.Itoa(int(consensusconfig.ProfileStrictPQ)): 1,
|
|
"lux_security_profile_lux_canonical:profile_id=" +
|
|
strconv.Itoa(int(consensusconfig.ProfileStrictPQ)): 1,
|
|
"lux_security_profile_unsafe_mode_enabled:profile_id=" +
|
|
strconv.Itoa(int(consensusconfig.ProfileStrictPQ)): 0,
|
|
}
|
|
for key, want := range expect {
|
|
got, ok := values[key]
|
|
if !ok {
|
|
t.Errorf("missing metric %s; got families: %v", key, values)
|
|
continue
|
|
}
|
|
if got != want {
|
|
t.Errorf("metric %s = %v; want %v", key, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMetrics_SecurityProfileGauge_Reflects_UnsafeFork proves a
|
|
// classical-compat fork sets unsafe_mode_enabled=1, post-quantum=0,
|
|
// nist_friendly=1 (the unsafe fork keeps SHA3 but loses every
|
|
// classical-primitive Forbid bit), and lux_canonical=0.
|
|
func TestMetrics_SecurityProfileGauge_Reflects_UnsafeFork(t *testing.T) {
|
|
reg := metric.NewRegistry()
|
|
metricsInstance := metric.NewWithRegistry("lux_security", reg)
|
|
m := NewMetrics(metricsInstance)
|
|
|
|
pCopy := consensusconfig.ForkClassicalCompatUnsafeProfile
|
|
hash, err := pCopy.ComputeHash()
|
|
if err != nil {
|
|
t.Fatalf("ComputeHash: %v", err)
|
|
}
|
|
pCopy.ProfileHash = hash
|
|
m.SetActiveProfile(&pCopy)
|
|
|
|
families, err := reg.Gather()
|
|
if err != nil {
|
|
t.Fatalf("Gather: %v", err)
|
|
}
|
|
values := flattenMetricValues(families)
|
|
|
|
idLabel := strconv.Itoa(int(consensusconfig.ForkClassicalCompatUnsafeProfileID))
|
|
cases := map[string]float64{
|
|
"lux_security_profile_post_quantum_end_to_end:profile_id=" + idLabel +
|
|
",profile_name=FORK_CLASSICAL_COMPAT_UNSAFE": 0,
|
|
"lux_security_profile_lux_canonical:profile_id=" + idLabel: 0,
|
|
"lux_security_profile_unsafe_mode_enabled:profile_id=" + idLabel: 1,
|
|
"lux_security_profile_nist_friendly:profile_id=" + idLabel: 1,
|
|
}
|
|
for key, want := range cases {
|
|
got, ok := values[key]
|
|
if !ok {
|
|
t.Errorf("missing metric %s; values=%v", key, values)
|
|
continue
|
|
}
|
|
if got != want {
|
|
t.Errorf("metric %s = %v; want %v", key, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMetrics_ObserveMempoolClassicalCredential_IncrementsCounter
|
|
// proves the per-chain classical-credentials counter wires through
|
|
// the call-site shim correctly.
|
|
func TestMetrics_ObserveMempoolClassicalCredential_IncrementsCounter(t *testing.T) {
|
|
reg := metric.NewRegistry()
|
|
metricsInstance := metric.NewWithRegistry("lux_security", reg)
|
|
m := NewMetrics(metricsInstance)
|
|
|
|
m.ObserveMempoolClassicalCredential("P")
|
|
m.ObserveMempoolClassicalCredential("P")
|
|
m.ObserveMempoolClassicalCredential("X")
|
|
|
|
families, err := reg.Gather()
|
|
if err != nil {
|
|
t.Fatalf("Gather: %v", err)
|
|
}
|
|
values := flattenMetricValues(families)
|
|
|
|
if got := values["lux_security_mempool_classical_credentials_total:chain=P"]; got != 2 {
|
|
t.Errorf("P counter = %v; want 2", got)
|
|
}
|
|
if got := values["lux_security_mempool_classical_credentials_total:chain=X"]; got != 1 {
|
|
t.Errorf("X counter = %v; want 1", got)
|
|
}
|
|
}
|
|
|
|
// TestMetrics_SetZchainProofLagEpochs_StoresValue proves the
|
|
// zchain_proof_lag_epochs gauge reflects the supplied epoch count.
|
|
func TestMetrics_SetZchainProofLagEpochs_StoresValue(t *testing.T) {
|
|
reg := metric.NewRegistry()
|
|
metricsInstance := metric.NewWithRegistry("lux_security", reg)
|
|
m := NewMetrics(metricsInstance)
|
|
|
|
m.SetZchainProofLagEpochs(7)
|
|
|
|
families, err := reg.Gather()
|
|
if err != nil {
|
|
t.Fatalf("Gather: %v", err)
|
|
}
|
|
values := flattenMetricValues(families)
|
|
got, ok := values["lux_security_zchain_proof_lag_epochs:"]
|
|
if !ok {
|
|
t.Fatalf("missing zchain_proof_lag_epochs; values=%v", values)
|
|
}
|
|
if got != 7 {
|
|
t.Errorf("zchain_proof_lag_epochs = %v; want 7", got)
|
|
}
|
|
}
|
|
|
|
// flattenMetricValues converts a Gather() result into a map keyed by
|
|
// "<metric_name>:<sorted_label_pairs>" → value. The flattening lets
|
|
// tests assert on a single canonical key per (metric, label set)
|
|
// without writing nested loops.
|
|
func flattenMetricValues(families []*metric.MetricFamily) map[string]float64 {
|
|
out := make(map[string]float64)
|
|
for _, mf := range families {
|
|
for _, m := range mf.Metrics {
|
|
labels := ""
|
|
for i, lp := range m.Labels {
|
|
if i > 0 {
|
|
labels += ","
|
|
}
|
|
labels += lp.Name + "=" + lp.Value
|
|
}
|
|
out[mf.Name+":"+labels] = m.Value.Value
|
|
}
|
|
}
|
|
return out
|
|
}
|