mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
go list ./... returned nothing, so no package here could be built or tested and the failure was invisible: one directory held a Go test beside fifteen .cpp files, which makes it a Go package the toolchain then refuses. The generator moves to the golden/ subdirectory the other two C++ chains already use. That silence was hiding a package that does not compile. host/ calls EnsureIdentity and identity.go defines LoadIdentity — a rename that happened on one side — and it has no importers, because the Go host is cmd/luxd now. Deleted rather than repaired: the thing it was a stand-in for arrived. The operator tools came across too (backup, blscheck, ceremony, config, genesishash, keytoaddr, pqkeygen, repair-proposervm, wire) plus benchmarks and examples, so the package set now differs from the retired repo by exactly one name: main/ is cmd/luxd. Two tests were right and are now satisfied rather than silenced. The purity test looked for ./main; it looks where the daemon lives. The chain-address test found conformance/dex spelling /v1/chain by hand — but that is its own module on purpose, since it links an EVM ABI and a signer the node must not carry, so it cannot call Chain. The rule is about this module, and the walk now says so.
201 lines
6.2 KiB
Go
201 lines
6.2 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"io/fs"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/zap-proto/zip"
|
|
|
|
"github.com/luxfi/constants"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
"github.com/luxfi/metric"
|
|
)
|
|
|
|
// gone is the segment chains used to answer on, written once so this file never
|
|
// spells the address it is asserting is unreachable.
|
|
const gone = "bc"
|
|
|
|
// newServer builds the shipped server and hands back both it and the handler
|
|
// its listener would serve: the router underneath CORS and the host filter, not
|
|
// the router alone. Routes are asserted through that whole chain.
|
|
func newServer(t *testing.T) (Server, http.Handler) {
|
|
t.Helper()
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = ln.Close() })
|
|
|
|
s, err := New(
|
|
log.NoLog{},
|
|
ln,
|
|
[]string{"*"},
|
|
time.Second,
|
|
ids.EmptyNodeID,
|
|
false,
|
|
nil,
|
|
metric.NewRegistry(),
|
|
HTTPConfig{},
|
|
[]string{"*"},
|
|
)
|
|
require.NoError(t, err)
|
|
return s, s.(*server).handler
|
|
}
|
|
|
|
// TestChainAnswersUnderOneSegment measures where a chain is reachable, on a
|
|
// real mounted app rather than on a claim about the route table: a typed op
|
|
// answers under [constants.ChainAliasPrefix], and the segment that prefix
|
|
// replaced answers nothing.
|
|
func TestChainAnswersUnderOneSegment(t *testing.T) {
|
|
require := require.New(t)
|
|
s, serve := newServer(t)
|
|
|
|
app := zip.New(zip.Config{AppName: "platform"})
|
|
|
|
// Height of the last accepted block.
|
|
zip.Get(app, "/height", func(context.Context, *struct{}) (*height, error) {
|
|
return &height{Height: 7}, nil
|
|
})
|
|
|
|
handler, err := Mount(app)
|
|
require.NoError(err)
|
|
t.Cleanup(func() { _ = app.Shutdown() })
|
|
|
|
// Mounted the one way the chain manager mounts a chain: the app at the
|
|
// chain's base, under Ops.
|
|
require.NoError(s.AddRoute(handler, constants.ChainAliasPrefix+"/P", Ops))
|
|
|
|
body, code := ask(t, serve, Chain("", "P")+Ops+"/height")
|
|
require.Equal(http.StatusOK, code, body)
|
|
require.JSONEq(`{"height":7}`, body)
|
|
|
|
for _, dead := range []string{
|
|
fmt.Sprintf("%s/%s/P%s/height", baseURL, gone, Ops),
|
|
fmt.Sprintf("%s/%s/P%s", baseURL, gone, Ops),
|
|
fmt.Sprintf("%s/%s/P/height", baseURL, gone),
|
|
fmt.Sprintf("%s/%s/P", baseURL, gone),
|
|
} {
|
|
body, code := ask(t, serve, dead)
|
|
require.Equal(http.StatusNotFound, code, "%s still answers: %s", dead, body)
|
|
}
|
|
}
|
|
|
|
// TestChainAddressBuiltOnlyHere fails if any Go source in this module writes a
|
|
// chain address out by hand instead of calling [Chain].
|
|
//
|
|
// This is what stops the segment drifting back. When it last changed, the
|
|
// router moved and three shipped clients did not — the P-Chain client the
|
|
// wallet is built on, the wallet's own X-Chain requester, and the heartbeat
|
|
// example had each spelled the segment themselves. Nothing failed to compile.
|
|
// They simply went on addressing a name that was no longer served.
|
|
//
|
|
// It reads string literals, so prose describing a route stays free to name it;
|
|
// only building one is the error. The patterns are assembled rather than
|
|
// written, so this file is not its own first offender.
|
|
func TestChainAddressBuiltOnlyHere(t *testing.T) {
|
|
forbidden := []struct{ frag, why string }{
|
|
{baseURL + "/" + gone, "addresses a chain under a segment that is not served"},
|
|
{baseURL + "/" + constants.ChainAliasPrefix, "spells the chain segment; call server.Chain(uri, alias)"},
|
|
{baseURL + "/%s", "interpolates the chain segment; call server.Chain(uri, alias)"},
|
|
}
|
|
|
|
var bad []string
|
|
fset := token.NewFileSet()
|
|
root := moduleRoot(t)
|
|
|
|
require.NoError(t, filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() {
|
|
switch d.Name() {
|
|
case ".git", "vendor", "testdata", "node_modules":
|
|
return fs.SkipDir
|
|
}
|
|
// A nested module is a different program with a different
|
|
// dependency set, and some of them exist precisely so they can
|
|
// carry what the node must not — conformance/dex links an EVM ABI
|
|
// and a signer. Such a module cannot import [Chain], so requiring
|
|
// it to call [Chain] would be asking it to depend on the node to
|
|
// avoid spelling a string. The rule this test enforces is about
|
|
// THIS module.
|
|
if p != root {
|
|
if _, err := os.Stat(filepath.Join(p, "go.mod")); err == nil {
|
|
return fs.SkipDir
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
if filepath.Ext(p) != ".go" {
|
|
return nil
|
|
}
|
|
// A file that will not parse is the build's problem, not this test's.
|
|
f, err := parser.ParseFile(fset, p, nil, 0)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
ast.Inspect(f, func(n ast.Node) bool {
|
|
lit, ok := n.(*ast.BasicLit)
|
|
if !ok || lit.Kind != token.STRING {
|
|
return true
|
|
}
|
|
text, err := strconv.Unquote(lit.Value)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
for _, f := range forbidden {
|
|
if strings.Contains(text, f.frag) {
|
|
at := fset.Position(lit.Pos())
|
|
rel, _ := filepath.Rel(root, at.Filename)
|
|
bad = append(bad, fmt.Sprintf("%s:%d: %s\n\t%s",
|
|
filepath.ToSlash(rel), at.Line, f.why, lit.Value))
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
return nil
|
|
}))
|
|
|
|
require.Empty(t, bad, "a chain address is built in exactly one place, [Chain]:\n%s", strings.Join(bad, "\n"))
|
|
}
|
|
|
|
// TestChainDerivesFromTheConstant pins Chain to the constant rather than to a
|
|
// copy of its current value, so renaming the segment renames the route.
|
|
func TestChainDerivesFromTheConstant(t *testing.T) {
|
|
require.Equal(t, baseURL+"/"+constants.ChainAliasPrefix+"/P", Chain("", "P"))
|
|
require.Equal(t, "http://n:9630"+baseURL+"/"+constants.ChainAliasPrefix+"/C", Chain("http://n:9630", "C"))
|
|
require.Contains(t, Chain("", "X"), constants.ChainAliasPrefix)
|
|
require.NotContains(t, Chain("", "X"), "/"+gone+"/")
|
|
}
|
|
|
|
// moduleRoot walks up from the package directory to the directory holding
|
|
// go.mod, so the scan covers the module rather than this package.
|
|
func moduleRoot(t *testing.T) string {
|
|
t.Helper()
|
|
dir, err := os.Getwd()
|
|
require.NoError(t, err)
|
|
for {
|
|
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
|
return dir
|
|
}
|
|
parent := filepath.Dir(dir)
|
|
require.NotEqual(t, parent, dir, "no go.mod above the package directory")
|
|
dir = parent
|
|
}
|
|
}
|