mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
A lane that runs no block is said once at start, at info
The start-up report warned about every CPU lane on every start: cevm's Go entry passes no host, so a CPU lane declines the health battery, and a build with no library linked runs nothing at all. Those lanes are what they are, not something that went wrong, so they are named together in one info line; a GPU lane that does not run the battery is still a warning of its own. A loaded library of another ABI is one warning, and no lane is probed. The report tests move out of backend_cgo_test.go, which the native build failed: it expected ErrNotLinked from a build that links the library. cevmreport_test.go runs in every build and holds the start-up report to warn only about a GPU lane. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -1,116 +0,0 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cgo
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/chains/evm/cevm"
|
||||
"github.com/luxfi/log"
|
||||
)
|
||||
|
||||
// A backend that answered the health battery is reported at info; one that did
|
||||
// not is reported at warn, carrying the probe that failed and the reason.
|
||||
//
|
||||
// The level is the whole point: an operator's alerting reads it, and a backend
|
||||
// that cannot execute a block reported at info is a chain that will not run
|
||||
// with nothing raised about it.
|
||||
func TestAnUnhealthyBackendIsReportedAtWarnWithItsReason(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
logger := log.NewWriter(&buf)
|
||||
|
||||
report(logger, cevm.HealthReport{
|
||||
Backend: cevm.GPUCUDA,
|
||||
Name: "gpu-cuda",
|
||||
OK: false,
|
||||
Probe: "storage",
|
||||
Err: errors.New("kernel did not launch"),
|
||||
})
|
||||
|
||||
got := lines(t, &buf)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("one report produced %d log lines", len(got))
|
||||
}
|
||||
if lvl := strings.ToLower(str(got[0]["level"])); lvl != "warn" {
|
||||
t.Errorf("an unhealthy backend was logged at %q, want warn", lvl)
|
||||
}
|
||||
for k, want := range map[string]string{"backend": "gpu-cuda", "probe": "storage"} {
|
||||
if str(got[0][k]) != want {
|
||||
t.Errorf("log field %q = %q, want %q", k, str(got[0][k]), want)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(str(got[0]["err"]), "kernel did not launch") {
|
||||
t.Errorf("log does not carry the reason: %v", got[0]["err"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAHealthyBackendIsReportedAtInfo(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
logger := log.NewWriter(&buf)
|
||||
|
||||
report(logger, cevm.HealthReport{
|
||||
Backend: cevm.CPUParallel,
|
||||
Name: "cpu-parallel",
|
||||
OK: true,
|
||||
ProbesRun: 5,
|
||||
GasUsed: 105000,
|
||||
})
|
||||
|
||||
got := lines(t, &buf)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("one report produced %d log lines", len(got))
|
||||
}
|
||||
if lvl := strings.ToLower(str(got[0]["level"])); lvl != "info" {
|
||||
t.Errorf("a healthy backend was logged at %q, want info", lvl)
|
||||
}
|
||||
if str(got[0]["backend"]) != "cpu-parallel" {
|
||||
t.Errorf("log names backend %q", got[0]["backend"])
|
||||
}
|
||||
}
|
||||
|
||||
// This build links no native library, so the one report it has says so — and
|
||||
// says the right thing. It used to blame CGo, which is enabled here; an
|
||||
// operator who followed that checked CGO_ENABLED and found it already 1.
|
||||
func TestThisBuildReportsWhyItCannotExecute(t *testing.T) {
|
||||
reports := cevm.Health()
|
||||
if len(reports) == 0 {
|
||||
t.Fatal("Health() said nothing at all")
|
||||
}
|
||||
for _, h := range reports {
|
||||
if h.OK {
|
||||
continue
|
||||
}
|
||||
if !errors.Is(h.Err, cevm.ErrNotLinked) {
|
||||
t.Errorf("backend %q is not healthy and blames %v, want cevm.ErrNotLinked",
|
||||
h.Name, h.Err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lines(t *testing.T, buf *bytes.Buffer) []map[string]any {
|
||||
t.Helper()
|
||||
var out []map[string]any
|
||||
for _, l := range strings.Split(strings.TrimSpace(buf.String()), "\n") {
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal([]byte(l), &m); err != nil {
|
||||
t.Fatalf("log line %q is not JSON: %v", l, err)
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func str(v any) string {
|
||||
s, _ := v.(string)
|
||||
return s
|
||||
}
|
||||
+47
-11
@@ -8,26 +8,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/chains/evm/cevm"
|
||||
"github.com/luxfi/log"
|
||||
)
|
||||
|
||||
func reportCevm(logger log.Logger) {
|
||||
logger.Info("cevm linked",
|
||||
"abi", cevm.LibraryABIVersion(),
|
||||
"backends", cevm.AvailableBackends(),
|
||||
)
|
||||
for _, h := range cevm.Health() {
|
||||
abi := cevm.LibraryABIVersion()
|
||||
logger.Info("cevm linked", "abi", abi, "backends", cevm.AvailableBackends())
|
||||
if abi != cevm.ABIVersion {
|
||||
// Every block is declined, so no lane has anything to report.
|
||||
logger.Warn("cevm library speaks another ABI; the Go EVM runs every block",
|
||||
"library", abi, "reads", cevm.ABIVersion)
|
||||
return
|
||||
}
|
||||
reportHealth(logger, cevm.Health())
|
||||
}
|
||||
|
||||
// reportHealth says what the backends answered to the health battery.
|
||||
//
|
||||
// A lane that runs no block in any build of this kind is said once, at info:
|
||||
// a CPU lane, which cevm's Go entry passes no host and so runs nothing on,
|
||||
// and the one lane of a build with no library. That is what the lane is, not
|
||||
// something that went wrong, and a warning every start would bury the ones
|
||||
// that did. Every other lane is reported on its own.
|
||||
func reportHealth(logger log.Logger, reports []cevm.HealthReport) {
|
||||
var idle []string
|
||||
for _, h := range reports {
|
||||
if runsNothing(h) {
|
||||
idle = append(idle, h.Name)
|
||||
continue
|
||||
}
|
||||
report(logger, h)
|
||||
}
|
||||
if len(idle) > 0 {
|
||||
logger.Info("cevm runs no block on these backends; the Go EVM does", "backends", idle)
|
||||
}
|
||||
}
|
||||
|
||||
// report says what one cevm backend answered to the health battery.
|
||||
//
|
||||
// It is separate from the report above because it is the only part that
|
||||
// depends on what the health check found: a build with no library linked has
|
||||
// exactly one report and it is never healthy, so a check folded into the loop
|
||||
// could only ever be read one way.
|
||||
// runsNothing reports whether h is a lane that runs no block by what it is: a
|
||||
// CPU lane that declined, or a lane with no library behind it.
|
||||
func runsNothing(h cevm.HealthReport) bool {
|
||||
if h.OK {
|
||||
return false
|
||||
}
|
||||
if errors.Is(h.Err, cevm.ErrNotLinked) {
|
||||
return true
|
||||
}
|
||||
cpu := h.Backend == cevm.CPUSequential || h.Backend == cevm.CPUParallel
|
||||
return cpu && errors.Is(h.Err, cevm.ErrDeclined)
|
||||
}
|
||||
|
||||
// report says what one cevm backend answered to the health battery: at info
|
||||
// when it ran it, and at warn, with the probe and the reason, when it did not.
|
||||
// A GPU lane that declines the battery's funded transfer has no device, or a
|
||||
// device that does not work, and the Go EVM is running every block it would.
|
||||
func report(logger log.Logger, h cevm.HealthReport) {
|
||||
if h.OK {
|
||||
logger.Info("cevm backend healthy",
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/chains/evm/cevm"
|
||||
"github.com/luxfi/log"
|
||||
)
|
||||
|
||||
// A backend that should have run the health battery and did not is reported
|
||||
// at warn, carrying the probe that failed and the reason; one that ran it is
|
||||
// reported at info.
|
||||
//
|
||||
// The level is the whole point: an operator's alerting reads it, and a GPU
|
||||
// lane that cannot run a block reported at info is a device doing nothing with
|
||||
// nothing raised about it.
|
||||
func TestAnUnhealthyBackendIsReportedAtWarnWithItsReason(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
logger := log.NewWriter(&buf)
|
||||
|
||||
report(logger, cevm.HealthReport{
|
||||
Backend: cevm.GPUCUDA,
|
||||
Name: "gpu-cuda",
|
||||
OK: false,
|
||||
Probe: "storage",
|
||||
Err: errors.New("kernel did not launch"),
|
||||
})
|
||||
|
||||
got := lines(t, &buf)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("one report produced %d log lines", len(got))
|
||||
}
|
||||
if lvl := level(got[0]); lvl != "warn" {
|
||||
t.Errorf("an unhealthy backend was logged at %q, want warn", lvl)
|
||||
}
|
||||
for k, want := range map[string]string{"backend": "gpu-cuda", "probe": "storage"} {
|
||||
if str(got[0][k]) != want {
|
||||
t.Errorf("log field %q = %q, want %q", k, str(got[0][k]), want)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(str(got[0]["err"]), "kernel did not launch") {
|
||||
t.Errorf("log does not carry the reason: %v", got[0]["err"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAHealthyBackendIsReportedAtInfo(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
logger := log.NewWriter(&buf)
|
||||
|
||||
report(logger, cevm.HealthReport{
|
||||
Backend: cevm.GPUMetal,
|
||||
Name: "gpu-metal",
|
||||
OK: true,
|
||||
ProbesRun: 1,
|
||||
GasUsed: 21000,
|
||||
})
|
||||
|
||||
got := lines(t, &buf)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("one report produced %d log lines", len(got))
|
||||
}
|
||||
if lvl := level(got[0]); lvl != "info" {
|
||||
t.Errorf("a healthy backend was logged at %q, want info", lvl)
|
||||
}
|
||||
if str(got[0]["backend"]) != "gpu-metal" {
|
||||
t.Errorf("log names backend %q", got[0]["backend"])
|
||||
}
|
||||
}
|
||||
|
||||
// A CPU lane declines every block through cevm's Go entry, and a build with
|
||||
// no library runs none: that is what those lanes are on every start, so they
|
||||
// are said once, together, at info — never a warning per lane per start.
|
||||
func TestALaneThatRunsNothingIsSaidOnceAtInfo(t *testing.T) {
|
||||
declined := fmt.Errorf("probe %q: %w", "transfer", cevm.ErrDeclined)
|
||||
for name, reports := range map[string][]cevm.HealthReport{
|
||||
"cpu lanes": {
|
||||
{Backend: cevm.CPUSequential, Name: "cpu-sequential", Err: declined},
|
||||
{Backend: cevm.CPUParallel, Name: "cpu-parallel (Block-STM)", Err: declined},
|
||||
},
|
||||
"no library": {
|
||||
{Backend: cevm.CPUSequential, Name: "cpu-sequential", Err: cevm.ErrNotLinked},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
reportHealth(log.NewWriter(&buf), reports)
|
||||
got := lines(t, &buf)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("%d lanes that run nothing produced %d log lines, want 1", len(reports), len(got))
|
||||
}
|
||||
if lvl := level(got[0]); lvl != "info" {
|
||||
t.Errorf("lanes that run nothing were logged at %q, want info", lvl)
|
||||
}
|
||||
named, _ := got[0]["backends"].([]any)
|
||||
if len(named) != len(reports) {
|
||||
t.Errorf("the line names %v, want every one of %d lanes", got[0]["backends"], len(reports))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A GPU lane that declines the funded transfer is not what a GPU lane is: it
|
||||
// is warned about on its own, beside the CPU lanes' one info line and the
|
||||
// healthy lane's.
|
||||
func TestAGPULaneThatDeclinesIsWarnedAboutAlone(t *testing.T) {
|
||||
declined := fmt.Errorf("probe %q: %w", "transfer", cevm.ErrDeclined)
|
||||
var buf bytes.Buffer
|
||||
reportHealth(log.NewWriter(&buf), []cevm.HealthReport{
|
||||
{Backend: cevm.CPUSequential, Name: "cpu-sequential", Err: declined},
|
||||
{Backend: cevm.CPUParallel, Name: "cpu-parallel (Block-STM)", Err: declined},
|
||||
{Backend: cevm.GPUMetal, Name: "gpu-metal", OK: true, ProbesRun: 1, GasUsed: 21000},
|
||||
{Backend: cevm.GPUCUDA, Name: "gpu-cuda", Probe: "transfer", Err: declined},
|
||||
})
|
||||
var warns []string
|
||||
for _, l := range lines(t, &buf) {
|
||||
if level(l) == "warn" {
|
||||
warns = append(warns, str(l["backend"]))
|
||||
}
|
||||
}
|
||||
if len(warns) != 1 || warns[0] != "gpu-cuda" {
|
||||
t.Fatalf("warned about %v, want the declining GPU lane alone", warns)
|
||||
}
|
||||
}
|
||||
|
||||
// What this build says at start: a lane that is not healthy says why, and
|
||||
// nothing it is by construction — a CPU lane, or a build with no library — is
|
||||
// a warning. Only a GPU lane that did not run the battery may warn.
|
||||
func TestThisBuildWarnsOnlyAboutAGPULane(t *testing.T) {
|
||||
for _, h := range cevm.Health() {
|
||||
if !h.OK && h.Err == nil {
|
||||
t.Errorf("backend %q is not healthy and does not say why", h.Name)
|
||||
}
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
reportCevm(log.NewWriter(&buf))
|
||||
for _, l := range lines(t, &buf) {
|
||||
if level(l) != "warn" {
|
||||
continue
|
||||
}
|
||||
if b := str(l["backend"]); !strings.HasPrefix(b, "gpu-") {
|
||||
t.Errorf("start warned about %q: %v", b, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lines(t *testing.T, buf *bytes.Buffer) []map[string]any {
|
||||
t.Helper()
|
||||
var out []map[string]any
|
||||
for _, l := range strings.Split(strings.TrimSpace(buf.String()), "\n") {
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal([]byte(l), &m); err != nil {
|
||||
t.Fatalf("log line %q is not JSON: %v", l, err)
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func level(m map[string]any) string { return strings.ToLower(str(m["level"])) }
|
||||
|
||||
func str(v any) string {
|
||||
s, _ := v.(string)
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user