mirror of
https://github.com/luxfi/staking.git
synced 2026-10-30 15:16:06 +00:00
203 lines
6.3 KiB
Go
203 lines
6.3 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package kmsboot
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// fakeFetcher returns canned bytes per (path,name). Fresh per test.
|
|
type fakeFetcher struct {
|
|
store map[string][]byte // key = path+name
|
|
closed bool
|
|
}
|
|
|
|
func (f *fakeFetcher) Get(_ context.Context, path, name string) ([]byte, error) {
|
|
b, ok := f.store[path+name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("fake: not found %s%s", path, name)
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
func (f *fakeFetcher) Close() { f.closed = true }
|
|
|
|
// TestInject_PassThroughWhenNoAddr asserts kmsboot is a no-op when
|
|
// the trigger env KMS_ADDR is unset. Any Lux-derived binary that
|
|
// drops kmsboot.Inject() into main() without configuring KMS keeps
|
|
// working unchanged.
|
|
func TestInject_PassThroughWhenNoAddr(t *testing.T) {
|
|
t.Setenv(EnvKMSAddr, "")
|
|
in := []string{"--network-id=1", "--data-dir=/data"}
|
|
got, err := Inject(context.Background(), in)
|
|
if err != nil {
|
|
t.Fatalf("Inject: %v", err)
|
|
}
|
|
if len(got) != len(in) {
|
|
t.Fatalf("expected pass-through, got %v", got)
|
|
}
|
|
}
|
|
|
|
// TestInjectWithFetcher_RoundTrip is the happy path. The fetcher
|
|
// returns three blobs; InjectWithFetcher prepends the three canonical
|
|
// content flags with base64-envelope of the bytes. Original argv tail
|
|
// is preserved verbatim.
|
|
func TestInjectWithFetcher_RoundTrip(t *testing.T) {
|
|
t.Setenv(EnvPathTemplate, "/staking/{ord}/")
|
|
t.Setenv(EnvPodName, "lqd-2")
|
|
|
|
path := "/staking/2/"
|
|
fetcher := &fakeFetcher{
|
|
store: map[string][]byte{
|
|
path + BlobMLDSAKey: []byte("mldsa-key-bytes"),
|
|
path + BlobMLDSAPubKey: []byte("mldsa-pub-bytes"),
|
|
path + BlobSignerKey: []byte("signer-bytes-32"),
|
|
},
|
|
}
|
|
got, err := InjectWithFetcher(context.Background(), fetcher,
|
|
[]string{"--network-id=2"})
|
|
if err != nil {
|
|
t.Fatalf("inject: %v", err)
|
|
}
|
|
if len(got) != 4 || got[3] != "--network-id=2" {
|
|
t.Fatalf("argv shape: %v", got)
|
|
}
|
|
want := []struct {
|
|
prefix string
|
|
blob []byte
|
|
}{
|
|
{FlagMLDSAKeyContent + "=", []byte("mldsa-key-bytes")},
|
|
{FlagMLDSAPubKeyContent + "=", []byte("mldsa-pub-bytes")},
|
|
{FlagSignerKeyContent + "=", []byte("signer-bytes-32")},
|
|
}
|
|
for i, w := range want {
|
|
a := got[i]
|
|
if !strings.HasPrefix(a, w.prefix) {
|
|
t.Errorf("argv[%d] does not start with %q: %q", i, w.prefix, a)
|
|
continue
|
|
}
|
|
raw, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(a, w.prefix))
|
|
if err != nil {
|
|
t.Errorf("argv[%d] base64 decode: %v", i, err)
|
|
continue
|
|
}
|
|
if string(raw) != string(w.blob) {
|
|
t.Errorf("argv[%d] payload: got %q want %q", i, raw, w.blob)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestInjectWithFetcher_PathSubstitution checks `{ord}` is replaced
|
|
// with the ordinal parsed from POD_NAME. Different pod naming
|
|
// schemes (lqd-N, hanzo-N, zoo-validator-N) all work — the suffix
|
|
// after the last `-` is what matters.
|
|
func TestInjectWithFetcher_PathSubstitution(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name, podName, wantPath string
|
|
}{
|
|
{"lqd-style", "lqd-3", "/staking/3/"},
|
|
{"hanzo-style", "hanzo-7", "/staking/7/"},
|
|
{"multi-dash", "zoo-validator-12", "/staking/12/"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Setenv(EnvPathTemplate, "/staking/{ord}/")
|
|
t.Setenv(EnvPodName, tc.podName)
|
|
fetcher := &fakeFetcher{
|
|
store: map[string][]byte{
|
|
tc.wantPath + BlobMLDSAKey: []byte("k"),
|
|
tc.wantPath + BlobMLDSAPubKey: []byte("p"),
|
|
tc.wantPath + BlobSignerKey: []byte("s"),
|
|
},
|
|
}
|
|
if _, err := InjectWithFetcher(context.Background(), fetcher, nil); err != nil {
|
|
t.Fatalf("inject %s: %v", tc.name, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestInjectWithFetcher_PassesEmptyBytesThrough — kmsboot has zero
|
|
// opinions about what KMS returns. If the blob is empty, the flag
|
|
// gets `=` followed by base64("") = empty string. lqd's config
|
|
// layer will reject it downstream as "not PEM" or similar — that's
|
|
// the right place for that error, not here.
|
|
func TestInjectWithFetcher_PassesEmptyBytesThrough(t *testing.T) {
|
|
t.Setenv(EnvPathTemplate, "/staking/{ord}/")
|
|
t.Setenv(EnvPodName, "lqd-0")
|
|
fetcher := &fakeFetcher{
|
|
store: map[string][]byte{
|
|
"/staking/0/" + BlobMLDSAKey: {},
|
|
"/staking/0/" + BlobMLDSAPubKey: []byte("ok"),
|
|
"/staking/0/" + BlobSignerKey: []byte("ok"),
|
|
},
|
|
}
|
|
got, err := InjectWithFetcher(context.Background(), fetcher, nil)
|
|
if err != nil {
|
|
t.Fatalf("empty blob must not fail at the kmsboot layer: %v", err)
|
|
}
|
|
if got[0] != FlagMLDSAKeyContent+"=" {
|
|
t.Errorf("expected empty payload after =, got %q", got[0])
|
|
}
|
|
}
|
|
|
|
// TestInjectWithFetcher_FetchErrorPropagates surfaces a Fetcher.Get
|
|
// error verbatim. Bad path, transport reset, missing blob — all
|
|
// percolate up as-is.
|
|
func TestInjectWithFetcher_FetchErrorPropagates(t *testing.T) {
|
|
t.Setenv(EnvPathTemplate, "/staking/{ord}/")
|
|
t.Setenv(EnvPodName, "lqd-0")
|
|
want := errors.New("transport reset")
|
|
fetcher := &errFetcher{err: want}
|
|
_, err := InjectWithFetcher(context.Background(), fetcher, nil)
|
|
if err == nil || !strings.Contains(err.Error(), "transport reset") {
|
|
t.Fatalf("expected propagated fetcher error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
type errFetcher struct{ err error }
|
|
|
|
func (e *errFetcher) Get(_ context.Context, _, _ string) ([]byte, error) { return nil, e.err }
|
|
func (e *errFetcher) Close() {}
|
|
|
|
// TestPodOrdinal — keep the pod-name parser table since various Lux
|
|
// downstream binaries will name their pods differently.
|
|
func TestPodOrdinal(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
in string
|
|
wantOrd int
|
|
wantErr string
|
|
}{
|
|
{"valid-lqd-0", "lqd-0", 0, ""},
|
|
{"valid-lqd-12", "lqd-12", 12, ""},
|
|
{"valid-hanzo-7", "hanzo-7", 7, ""},
|
|
{"valid-multi-dash", "zoo-validator-3", 3, ""},
|
|
{"empty-returns-zero", "", 0, ""}, // empty pod name = ord 0, no error
|
|
{"no-dash", "lqd0", 0, "no `-<ordinal>` suffix"},
|
|
{"trailing-dash", "lqd-", 0, "no `-<ordinal>` suffix"},
|
|
{"non-numeric", "lqd-abc", 0, "not numeric"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, err := PodOrdinal(tc.in)
|
|
if tc.wantErr != "" {
|
|
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
|
|
t.Fatalf("expected error containing %q, got: %v", tc.wantErr, err)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got != tc.wantOrd {
|
|
t.Errorf("ord: got %d, want %d", got, tc.wantOrd)
|
|
}
|
|
})
|
|
}
|
|
}
|