go-lns/lns_test.go

2175 lines
58 KiB
Go
Raw Permalink Normal View History

// SPDX-License-Identifier: EUPL-1.2
package lns
import (
"bytes"
2026-04-04 07:19:52 +00:00
"context"
"crypto/sha3"
"encoding/binary"
"strings"
"testing"
core "dappco.re/go/core"
"dappco.re/go/lns/pkg/covenant"
"dappco.re/go/lns/pkg/primitives"
"golang.org/x/crypto/blake2b"
)
2026-04-02 14:10:08 +00:00
func TestServiceLifecycleHooks(t *testing.T) {
svc := &Service{}
if got := svc.OnStartup(nil); !got.OK {
t.Fatalf("OnStartup returned %#v, want OK", got)
}
2026-04-04 07:19:52 +00:00
if got := svc.GetOnStartup(context.Background()); !got.OK {
t.Fatalf("GetOnStartup returned %#v, want OK", got)
}
2026-04-02 14:10:08 +00:00
if got := svc.OnShutdown(nil); !got.OK {
t.Fatalf("OnShutdown returned %#v, want OK", got)
}
2026-04-04 07:19:52 +00:00
if got := svc.GetOnShutdown(context.Background()); !got.OK {
t.Fatalf("GetOnShutdown returned %#v, want OK", got)
}
var nilSvc *Service
if got := nilSvc.OnStartup(nil); !got.OK {
t.Fatalf("nil OnStartup returned %#v, want OK", got)
}
2026-04-04 07:19:52 +00:00
if got := nilSvc.GetOnStartup(context.Background()); !got.OK {
t.Fatalf("nil GetOnStartup returned %#v, want OK", got)
}
if got := nilSvc.OnShutdown(nil); !got.OK {
t.Fatalf("nil OnShutdown returned %#v, want OK", got)
}
2026-04-04 07:19:52 +00:00
if got := nilSvc.GetOnShutdown(context.Background()); !got.OK {
t.Fatalf("nil GetOnShutdown returned %#v, want OK", got)
}
2026-04-02 14:10:08 +00:00
}
func TestServiceHandleIPCEvents(t *testing.T) {
svc := &Service{}
if got := svc.HandleIPCEvents(nil, nil); !got.OK {
t.Fatalf("HandleIPCEvents returned %#v, want OK", got)
}
2026-04-04 07:19:52 +00:00
if got := svc.GetHandleIPCEvents(nil, nil); !got.OK {
t.Fatalf("GetHandleIPCEvents returned %#v, want OK", got)
}
var nilSvc *Service
if got := nilSvc.HandleIPCEvents(nil, nil); !got.OK {
t.Fatalf("nil HandleIPCEvents returned %#v, want OK", got)
}
2026-04-04 07:19:52 +00:00
if got := nilSvc.GetHandleIPCEvents(nil, nil); !got.OK {
t.Fatalf("nil GetHandleIPCEvents returned %#v, want OK", got)
}
}
func TestServiceResolve(t *testing.T) {
svc := &Service{}
got, err := svc.Resolve("Foo-Bar.lthn")
if err != nil {
t.Fatalf("Resolve returned error: %v", err)
}
want := sha3.Sum256([]byte("foo-bar"))
if got != want {
t.Fatalf("Resolve returned %x, want %x", got, want)
}
got, err = svc.Resolve([]byte("Foo-Bar.lthn"))
if err != nil {
t.Fatalf("Resolve([]byte) returned error: %v", err)
}
if got != want {
t.Fatalf("Resolve([]byte) returned %x, want %x", got, want)
}
got, err = svc.GetResolve("Foo-Bar.lthn")
if err != nil {
t.Fatalf("GetResolve returned error: %v", err)
}
if got != want {
t.Fatalf("GetResolve returned %x, want %x", got, want)
}
}
func TestServiceResolveStringAndBinary(t *testing.T) {
svc := &Service{}
got, err := svc.ResolveString("Foo-Bar.lthn")
if err != nil {
t.Fatalf("ResolveString returned error: %v", err)
}
want := primitives.Hash(sha3.Sum256([]byte("foo-bar")))
if got != want {
t.Fatalf("ResolveString returned %x, want %x", got, want)
}
got, err = svc.ResolveBinary([]byte("Foo-Bar.lthn"))
if err != nil {
t.Fatalf("ResolveBinary returned error: %v", err)
}
if got != want {
t.Fatalf("ResolveBinary returned %x, want %x", got, want)
}
}
func TestServiceGetResolverAliases(t *testing.T) {
svc := &Service{}
want := primitives.Hash(sha3.Sum256([]byte("foo-bar")))
resolveCases := []struct {
name string
fn func(any) (primitives.Hash, error)
}{
{name: "GetResolveString", fn: func(name any) (primitives.Hash, error) { return svc.GetResolveString(name.(string)) }},
{name: "GetResolveBinary", fn: func(name any) (primitives.Hash, error) { return svc.GetResolveBinary(name.([]byte)) }},
{name: "GetResolveName", fn: svc.GetResolveName},
{name: "GetResolveByName", fn: svc.GetResolveByName},
{name: "GetResolveByString", fn: func(name any) (primitives.Hash, error) { return svc.GetResolveByString(name.(string)) }},
{name: "GetResolveByBinary", fn: func(name any) (primitives.Hash, error) { return svc.GetResolveByBinary(name.([]byte)) }},
{name: "GetHashString", fn: func(name any) (primitives.Hash, error) { return svc.GetHashString(name.(string)) }},
{name: "GetHashBinary", fn: func(name any) (primitives.Hash, error) { return svc.GetHashBinary(name.([]byte)) }},
{name: "GetHashName", fn: svc.GetHashName},
{name: "GetHashByName", fn: svc.GetHashByName},
{name: "GetHashByString", fn: func(name any) (primitives.Hash, error) { return svc.GetHashByString(name.(string)) }},
{name: "GetHashByBinary", fn: func(name any) (primitives.Hash, error) { return svc.GetHashByBinary(name.([]byte)) }},
}
for _, tc := range resolveCases {
var input any = "Foo-Bar.lthn"
switch tc.name {
case "GetResolveBinary", "GetResolveByBinary", "GetHashBinary", "GetHashByBinary":
input = []byte("Foo-Bar.lthn")
}
got, err := tc.fn(input)
if err != nil {
t.Fatalf("%s returned error: %v", tc.name, err)
}
if got != want {
t.Fatalf("%s returned %x, want %x", tc.name, got, want)
}
}
}
func TestServiceResolveRejectsInvalidNames(t *testing.T) {
svc := &Service{}
cases := []string{
"",
" foo-bar.lthn",
"foo-bar.lthn ",
"foo.bar.lthn",
2026-04-02 03:29:42 +00:00
"foo..lthn",
"foo.lthn..",
"foo-",
"test.lthn",
"foo.lthn",
}
for _, name := range cases {
if _, err := svc.Resolve(name); err == nil {
t.Fatalf("Resolve(%q) should reject the name", name)
}
}
if _, err := svc.Resolve(123); err == nil || !strings.Contains(err.Error(), "invalid name type") {
t.Fatalf("Resolve should reject unsupported input types with an invalid name type error, got %v", err)
}
}
2026-04-02 14:43:17 +00:00
func TestHashOperationLabels(t *testing.T) {
if _, err := Hash(123); err == nil {
t.Fatal("Hash should reject unsupported input types")
} else if got := core.Operation(err); got != "lns.Hash" {
t.Fatalf("Hash error operation = %q, want %q", got, "lns.Hash")
}
if _, err := HashString("foo-"); err == nil {
t.Fatal("HashString should reject malformed names")
} else if got := core.Operation(err); got != "lns.HashString" {
t.Fatalf("HashString error operation = %q, want %q", got, "lns.HashString")
}
if _, err := HashBinary([]byte("foo-")); err == nil {
t.Fatal("HashBinary should reject malformed names")
} else if got := core.Operation(err); got != "lns.HashBinary" {
t.Fatalf("HashBinary error operation = %q, want %q", got, "lns.HashBinary")
}
svc := &Service{}
if _, err := svc.Hash(123); err == nil {
t.Fatal("Service.Hash should reject unsupported input types")
} else if got := core.Operation(err); got != "lns.Service.Hash" {
t.Fatalf("Service.Hash error operation = %q, want %q", got, "lns.Service.Hash")
}
if _, err := svc.HashString("foo-"); err == nil {
t.Fatal("Service.HashString should reject malformed names")
} else if got := core.Operation(err); got != "lns.Service.HashString" {
t.Fatalf("Service.HashString error operation = %q, want %q", got, "lns.Service.HashString")
}
if _, err := svc.HashBinary([]byte("foo-")); err == nil {
t.Fatal("Service.HashBinary should reject malformed names")
} else if got := core.Operation(err); got != "lns.Service.HashBinary" {
t.Fatalf("Service.HashBinary error operation = %q, want %q", got, "lns.Service.HashBinary")
}
}
func TestServiceVerify(t *testing.T) {
svc := &Service{}
cases := []struct {
name any
ok bool
}{
{name: "Foo-Bar.lthn", ok: true},
{name: []byte("Foo-Bar.lthn"), ok: true},
{name: "foo.bar.lthn", ok: false},
{name: "foo-", ok: false},
{name: "foo.lthn", ok: false},
{name: 123, ok: false},
}
for _, tc := range cases {
if got := svc.Verify(tc.name); got != tc.ok {
t.Fatalf("Verify(%v) = %v, want %v", tc.name, got, tc.ok)
}
}
}
func TestServiceVerifyStringAndBinary(t *testing.T) {
svc := &Service{}
if !svc.VerifyString("Foo-Bar.lthn") {
t.Fatal("VerifyString should accept canonical names")
}
if svc.VerifyString("foo.bar.lthn") {
t.Fatal("VerifyString should reject malformed names")
}
if !svc.VerifyBinary([]byte("Foo-Bar.lthn")) {
t.Fatal("VerifyBinary should accept canonical names")
}
if svc.VerifyBinary([]byte("foo.bar.lthn")) {
t.Fatal("VerifyBinary should reject malformed names")
}
if svc.VerifyString("foo.lthn") {
t.Fatal("VerifyString should reject non-ASCII names")
}
if svc.VerifyBinary([]byte("foo.lthn")) {
t.Fatal("VerifyBinary should reject non-ASCII names")
}
}
2026-04-04 05:10:29 +00:00
func TestServiceHasAliasMirrors(t *testing.T) {
svc := &Service{}
if !svc.GetHasReserved("RESERVED.lthn") {
t.Fatal("GetHasReserved should report reserved canonical names")
}
if !svc.GetHasReservedString("reserved") {
t.Fatal("GetHasReservedString should report reserved catalog labels")
}
if !svc.GetHasReservedHash(primitives.Hash(sha3.Sum256([]byte("reserved")))) {
t.Fatal("GetHasReservedHash should report reserved canonical hashes")
}
if !svc.GetHasLocked("NEC.lthn") {
t.Fatal("GetHasLocked should report locked canonical names")
}
if !svc.GetHasLockedString("nec") {
t.Fatal("GetHasLockedString should report locked catalog labels")
}
if !svc.GetHasLockedHash(primitives.Hash(sha3.Sum256([]byte("nec")))) {
t.Fatal("GetHasLockedHash should report locked canonical hashes")
}
}
func TestServiceRolloutAlias(t *testing.T) {
svc := &Service{}
var hash primitives.Hash
rules := NameRules{
AuctionStart: 100,
RolloutInterval: 7,
}
if !svc.HasRollout(hash, 100, rules) {
t.Fatal("HasRollout should accept the rollout start height")
}
if !svc.GetHasRollout(hash, 100, rules) {
t.Fatal("GetHasRollout should alias HasRollout")
}
}
func TestServiceGetVerifyAliases(t *testing.T) {
svc := &Service{}
verifyCases := []struct {
name string
input any
fn func(any) bool
}{
{name: "GetVerifyName", input: "Foo-Bar.lthn", fn: svc.GetVerifyName},
{name: "GetVerifyByName", input: "Foo-Bar.lthn", fn: svc.GetVerifyByName},
{name: "GetVerifyString", input: "Foo-Bar.lthn", fn: func(name any) bool { return svc.GetVerifyString(name.(string)) }},
{name: "GetVerifyBinary", input: []byte("Foo-Bar.lthn"), fn: func(name any) bool { return svc.GetVerifyBinary(name.([]byte)) }},
{name: "GetVerifyByString", input: "Foo-Bar.lthn", fn: func(name any) bool { return svc.GetVerifyByString(name.(string)) }},
{name: "GetVerifyByBinary", input: []byte("Foo-Bar.lthn"), fn: func(name any) bool { return svc.GetVerifyByBinary(name.([]byte)) }},
}
for _, tc := range verifyCases {
if !tc.fn(tc.input) {
t.Fatalf("%s should accept canonical names", tc.name)
}
}
}
func TestServiceResolveNameAliases(t *testing.T) {
svc := &Service{}
got, err := svc.ResolveName("Foo-Bar.lthn")
if err != nil {
t.Fatalf("ResolveName returned error: %v", err)
}
want := primitives.Hash(sha3.Sum256([]byte("foo-bar")))
if got != want {
t.Fatalf("ResolveName returned %x, want %x", got, want)
}
if _, err := svc.ResolveName(123); err == nil {
t.Fatal("ResolveName should reject unsupported input types")
}
}
2026-04-02 05:21:54 +00:00
func TestServiceHashAliases(t *testing.T) {
svc := &Service{}
got, err := svc.Hash("Foo-Bar.lthn")
if err != nil {
t.Fatalf("Hash returned error: %v", err)
}
want := primitives.Hash(sha3.Sum256([]byte("foo-bar")))
if got != want {
t.Fatalf("Hash returned %x, want %x", got, want)
}
got, err = svc.HashString("Foo-Bar.lthn")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
if got != want {
t.Fatalf("HashString returned %x, want %x", got, want)
}
got, err = svc.HashBinary([]byte("Foo-Bar.lthn"))
if err != nil {
t.Fatalf("HashBinary returned error: %v", err)
}
if got != want {
t.Fatalf("HashBinary returned %x, want %x", got, want)
}
got, err = svc.HashName("Foo-Bar.lthn")
if err != nil {
t.Fatalf("HashName returned error: %v", err)
}
if got != want {
t.Fatalf("HashName returned %x, want %x", got, want)
}
got, err = svc.GetHash("Foo-Bar.lthn")
if err != nil {
t.Fatalf("GetHash returned error: %v", err)
}
if got != want {
t.Fatalf("GetHash returned %x, want %x", got, want)
}
2026-04-02 05:21:54 +00:00
got, err = svc.HashByName([]byte("Foo-Bar.lthn"))
if err != nil {
t.Fatalf("HashByName returned error: %v", err)
}
if got != want {
t.Fatalf("HashByName returned %x, want %x", got, want)
}
if _, err := svc.Hash(123); err == nil || !strings.Contains(err.Error(), "invalid name type") {
t.Fatalf("Hash should reject unsupported input types with an invalid name type error, got %v", err)
2026-04-02 05:21:54 +00:00
}
}
func TestServiceGetHashAndVerifyAliases(t *testing.T) {
svc := &Service{}
want := primitives.Hash(sha3.Sum256([]byte("foo-bar")))
got, err := svc.GetHash("Foo-Bar.lthn")
if err != nil {
t.Fatalf("GetHash returned error: %v", err)
}
if got != want {
t.Fatalf("GetHash returned %x, want %x", got, want)
}
if !svc.GetVerify("Foo-Bar.lthn") {
t.Fatal("GetVerify should accept canonical names")
}
if svc.GetVerify("foo.bar.lthn") {
t.Fatal("GetVerify should reject malformed names")
}
if svc.GetVerify(123) {
t.Fatal("GetVerify should reject unsupported input types")
}
}
func TestServiceRuleTables(t *testing.T) {
svc := &Service{}
if len(svc.Types()) == 0 || len(svc.TypesByVal()) == 0 {
t.Fatal("service rule tables should not be empty")
}
if len(svc.GetTypes()) != len(svc.Types()) {
t.Fatal("GetTypes should alias Types")
}
if len(svc.GetTypesByVal()) != len(svc.TypesByVal()) {
t.Fatal("GetTypesByVal should alias TypesByVal")
}
if _, ok := svc.Types()["BID"]; !ok {
t.Fatal("Types should expose BID")
}
if got, ok := svc.TypesByVal()[covenant.TypeBid]; !ok || got != "BID" {
t.Fatalf("TypesByVal[TypeBid] = %q, want %q", got, "BID")
}
if len(svc.Blacklist()) == 0 {
t.Fatal("Blacklist should not be empty")
}
if len(svc.GetBlacklist()) != len(svc.Blacklist()) {
t.Fatal("GetBlacklist should alias Blacklist")
}
if _, ok := svc.Blacklist()["test"]; !ok {
t.Fatal("Blacklist should expose test")
}
if svc.DefaultReservedCatalog() != svc.ReservedCatalog() {
t.Fatal("DefaultReservedCatalog should alias ReservedCatalog")
}
if svc.GetDefaultReservedCatalog() != svc.ReservedCatalog() {
t.Fatal("GetDefaultReservedCatalog should alias ReservedCatalog")
}
if svc.DefaultLockedCatalog() != svc.LockedCatalog() {
t.Fatal("DefaultLockedCatalog should alias LockedCatalog")
}
if svc.GetDefaultLockedCatalog() != svc.LockedCatalog() {
t.Fatal("GetDefaultLockedCatalog should alias LockedCatalog")
}
}
func TestServiceVerificationFlagTables(t *testing.T) {
svc := &Service{}
if len(svc.VerificationFlags()) != 3 {
t.Fatalf("VerificationFlags has %d entries, want 3", len(svc.VerificationFlags()))
}
if len(svc.GetVerificationFlags()) != len(svc.VerificationFlags()) {
t.Fatal("GetVerificationFlags should alias VerificationFlags")
}
if len(svc.VerificationFlagsByVal()) != len(svc.VerificationFlags()) {
t.Fatalf("VerificationFlagsByVal has %d entries, want %d", len(svc.VerificationFlagsByVal()), len(svc.VerificationFlags()))
}
if len(svc.GetVerificationFlagsByVal()) != len(svc.VerificationFlagsByVal()) {
t.Fatal("GetVerificationFlagsByVal should alias VerificationFlagsByVal")
}
if got, ok := svc.VerificationFlags()["VERIFY_COVENANTS_HARDENED"]; !ok || got != covenant.VerifyCovenantsHardened {
t.Fatalf("VerificationFlags[HARDENED] = %d, want %d", got, covenant.VerifyCovenantsHardened)
}
if got, ok := svc.VerificationFlagsByVal()[covenant.VerifyCovenantsLockup]; !ok || got != "VERIFY_COVENANTS_LOCKUP" {
t.Fatalf("VerificationFlagsByVal[LOCKUP] = %q, want %q", got, "VERIFY_COVENANTS_LOCKUP")
}
}
func TestServiceConstantGetters(t *testing.T) {
svc := &Service{}
if svc.MaxNameSize() != MaxNameSize || svc.GetMaxNameSize() != MaxNameSize {
t.Fatalf("MaxNameSize getters = %d/%d, want %d", svc.MaxNameSize(), svc.GetMaxNameSize(), MaxNameSize)
}
if svc.MaxResourceSize() != MaxResourceSize || svc.GetMaxResourceSize() != MaxResourceSize {
t.Fatalf("MaxResourceSize getters = %d/%d, want %d", svc.MaxResourceSize(), svc.GetMaxResourceSize(), MaxResourceSize)
}
if svc.VerifyCovenantsNone() != VerifyCovenantsNone || svc.GetVerifyCovenantsNone() != VerifyCovenantsNone {
t.Fatalf("VerifyCovenantsNone getters = %d/%d, want %d", svc.VerifyCovenantsNone(), svc.GetVerifyCovenantsNone(), VerifyCovenantsNone)
}
if svc.VerifyCovenantsHardened() != VerifyCovenantsHardened || svc.GetVerifyCovenantsHardened() != VerifyCovenantsHardened {
t.Fatalf("VerifyCovenantsHardened getters = %d/%d, want %d", svc.VerifyCovenantsHardened(), svc.GetVerifyCovenantsHardened(), VerifyCovenantsHardened)
}
if svc.VerifyCovenantsLockup() != VerifyCovenantsLockup || svc.GetVerifyCovenantsLockup() != VerifyCovenantsLockup {
t.Fatalf("VerifyCovenantsLockup getters = %d/%d, want %d", svc.VerifyCovenantsLockup(), svc.GetVerifyCovenantsLockup(), VerifyCovenantsLockup)
}
if svc.MandatoryVerifyCovenantFlags() != MandatoryVerifyCovenantFlags || svc.GetMandatoryVerifyCovenantFlags() != MandatoryVerifyCovenantFlags {
t.Fatalf("MandatoryVerifyCovenantFlags getters = %d/%d, want %d", svc.MandatoryVerifyCovenantFlags(), svc.GetMandatoryVerifyCovenantFlags(), MandatoryVerifyCovenantFlags)
}
if svc.MaxCovenantSize() != MaxCovenantSize || svc.GetMaxCovenantSize() != MaxCovenantSize {
t.Fatalf("MaxCovenantSize getters = %d/%d, want %d", svc.MaxCovenantSize(), svc.GetMaxCovenantSize(), MaxCovenantSize)
}
if svc.CovenantMaxSize() != CovenantMaxSize || svc.GetCovenantMaxSize() != CovenantMaxSize {
t.Fatalf("CovenantMaxSize getters = %d/%d, want %d", svc.CovenantMaxSize(), svc.GetCovenantMaxSize(), CovenantMaxSize)
}
}
func TestServiceHashByStringAndBinaryAliases(t *testing.T) {
svc := &Service{}
want := primitives.Hash(sha3.Sum256([]byte("foo-bar")))
got, err := svc.HashByString("Foo-Bar.lthn")
if err != nil {
t.Fatalf("HashByString returned error: %v", err)
}
if got != want {
t.Fatalf("HashByString returned %x, want %x", got, want)
}
got, err = svc.HashByBinary([]byte("Foo-Bar.lthn"))
if err != nil {
t.Fatalf("HashByBinary returned error: %v", err)
}
if got != want {
t.Fatalf("HashByBinary returned %x, want %x", got, want)
}
}
func TestServiceResolveByNameAliases(t *testing.T) {
svc := &Service{}
got, err := svc.ResolveByName("Foo-Bar.lthn")
if err != nil {
t.Fatalf("ResolveByName returned error: %v", err)
}
want := primitives.Hash(sha3.Sum256([]byte("foo-bar")))
if got != want {
t.Fatalf("ResolveByName returned %x, want %x", got, want)
}
if _, err := svc.ResolveByName(123); err == nil {
t.Fatal("ResolveByName should reject unsupported input types")
}
}
func TestServiceVerifyNameAliases(t *testing.T) {
svc := &Service{}
if !svc.VerifyName("Foo-Bar.lthn") {
t.Fatal("VerifyName should accept canonical names")
}
if svc.VerifyName("foo.bar.lthn") {
t.Fatal("VerifyName should reject malformed names")
}
if svc.VerifyName(123) {
t.Fatal("VerifyName should reject unsupported input types")
}
}
func TestServiceResolveByStringAndBinaryAliases(t *testing.T) {
svc := &Service{}
want := primitives.Hash(sha3.Sum256([]byte("foo-bar")))
got, err := svc.ResolveByString("Foo-Bar.lthn")
if err != nil {
t.Fatalf("ResolveByString returned error: %v", err)
}
if got != want {
t.Fatalf("ResolveByString returned %x, want %x", got, want)
}
got, err = svc.ResolveByBinary([]byte("Foo-Bar.lthn"))
if err != nil {
t.Fatalf("ResolveByBinary returned error: %v", err)
}
if got != want {
t.Fatalf("ResolveByBinary returned %x, want %x", got, want)
}
}
func TestServiceVerifyByStringAndBinaryAliases(t *testing.T) {
svc := &Service{}
if !svc.VerifyByString("Foo-Bar.lthn") {
t.Fatal("VerifyByString should accept canonical names")
}
if svc.VerifyByString("foo.bar.lthn") {
t.Fatal("VerifyByString should reject malformed names")
}
if !svc.VerifyByBinary([]byte("Foo-Bar.lthn")) {
t.Fatal("VerifyByBinary should accept canonical names")
}
if svc.VerifyByBinary([]byte("foo.bar.lthn")) {
t.Fatal("VerifyByBinary should reject malformed names")
}
}
func TestServiceVerifyByNameAliases(t *testing.T) {
svc := &Service{}
if !svc.VerifyByName("Foo-Bar.lthn") {
t.Fatal("VerifyByName should accept canonical names")
}
if svc.VerifyByName("foo.bar.lthn") {
t.Fatal("VerifyByName should reject malformed names")
}
if svc.VerifyByName(123) {
t.Fatal("VerifyByName should reject unsupported input types")
}
}
func TestServiceBlind(t *testing.T) {
svc := &Service{}
var nonce primitives.Hash
for i := range nonce {
nonce[i] = byte(i)
}
got, err := svc.Blind(0x1122334455667788, nonce)
if err != nil {
t.Fatalf("Blind returned error: %v", err)
}
var input [40]byte
binary.LittleEndian.PutUint64(input[:8], 0x1122334455667788)
copy(input[8:], nonce[:])
want := blake2b.Sum256(input[:])
if got != want {
t.Fatalf("Blind returned %x, want %x", got, want)
}
}
func TestServiceTypeName(t *testing.T) {
svc := &Service{}
if got := svc.TypeName(covenant.TypeBid); got != "BID" {
t.Fatalf("TypeName(TypeBid) = %q, want %q", got, "BID")
}
if got := svc.TypeName(covenant.CovenantType(99)); got != "UNKNOWN" {
t.Fatalf("TypeName(99) = %q, want %q", got, "UNKNOWN")
}
}
func TestServiceCovenantTypePredicates(t *testing.T) {
svc := &Service{}
if !svc.IsName(covenant.TypeOpen) || !svc.GetIsName(covenant.TypeOpen) {
t.Fatal("IsName should report open covenants")
}
if svc.IsName(covenant.TypeNone) || svc.GetIsName(covenant.TypeNone) {
t.Fatal("IsName should reject non-name covenants")
}
if !svc.IsKnown(covenant.TypeBid) || !svc.GetIsKnown(covenant.TypeBid) {
t.Fatal("IsKnown should report recognized covenants")
}
if svc.IsKnown(covenant.CovenantType(99)) || svc.GetIsKnown(covenant.CovenantType(99)) {
t.Fatal("IsKnown should reject unknown covenants")
}
if !svc.IsLinked(covenant.TypeReveal) || !svc.GetIsLinked(covenant.TypeReveal) {
t.Fatal("IsLinked should report linked covenants")
}
if svc.IsLinked(covenant.TypeOpen) || svc.GetIsLinked(covenant.TypeOpen) {
t.Fatal("IsLinked should reject unlinked covenants")
}
}
func TestServiceRolloutHelpers(t *testing.T) {
svc := &Service{}
var hash primitives.Hash
rules := NameRules{
AuctionStart: 100,
RolloutInterval: 7,
ClaimPeriod: 100,
AlexaLockupPeriod: 250,
}
start, week := svc.GetRollout(hash, rules)
if start != 100 || week != 0 {
t.Fatalf("GetRollout(zero) = (%d, %d), want (100, 0)", start, week)
}
if !svc.HasRollout(hash, 100, rules) {
t.Fatal("HasRollout should accept the rollout start height")
}
reservedHash, err := covenant.HashString("reserved")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
if !svc.IsReserved(reservedHash, 99, rules) {
t.Fatal("IsReserved should report reserved hashes before the claim period")
}
2026-04-04 05:07:09 +00:00
if !svc.GetIsReserved(reservedHash, 99, rules) {
t.Fatal("GetIsReserved should alias IsReserved")
}
rootHash, err := covenant.HashString("nec")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
if !svc.IsLockedUp(rootHash, 100, rules) {
t.Fatal("IsLockedUp should keep root hashes locked after the claim period")
}
2026-04-04 05:07:09 +00:00
if !svc.GetIsLockedUp(rootHash, 100, rules) {
t.Fatal("GetIsLockedUp should alias IsLockedUp")
}
}
func TestServiceGrindAndCountHelpers(t *testing.T) {
svc := &Service{}
tx := primitives.Transaction{
Outputs: []primitives.Output{
{Covenant: primitives.Covenant{Type: uint8(covenant.TypeOpen)}},
{Covenant: primitives.Covenant{Type: uint8(covenant.TypeClaim)}},
{Covenant: primitives.Covenant{Type: uint8(covenant.TypeUpdate)}},
{Covenant: primitives.Covenant{Type: uint8(covenant.TypeTransfer)}},
{Covenant: primitives.Covenant{Type: uint8(covenant.TypeRevoke)}},
{Covenant: primitives.Covenant{Type: uint8(covenant.TypeRegister)}},
{Covenant: primitives.Covenant{Type: uint8(covenant.TypeRenew)}},
{Covenant: primitives.Covenant{Type: uint8(covenant.TypeFinalize)}},
},
}
if got := svc.CountOpens(tx); got != 1 {
t.Fatalf("CountOpens() = %d, want 1", got)
}
2026-04-02 14:48:33 +00:00
if got := svc.GetCountOpens(tx); got != 1 {
t.Fatalf("GetCountOpens() = %d, want 1", got)
}
if got := svc.CountUpdates(tx); got != 5 {
t.Fatalf("CountUpdates() = %d, want 5", got)
}
2026-04-02 14:48:33 +00:00
if got := svc.GetCountUpdates(tx); got != 5 {
t.Fatalf("GetCountUpdates() = %d, want 5", got)
}
if got := svc.CountRenewals(tx); got != 3 {
t.Fatalf("CountRenewals() = %d, want 3", got)
}
2026-04-02 14:48:33 +00:00
if got := svc.GetCountRenewals(tx); got != 3 {
t.Fatalf("GetCountRenewals() = %d, want 3", got)
}
rules := NameRules{
AuctionStart: 100,
RolloutInterval: 7,
}
name, err := svc.GrindName(8, 100, rules)
if err != nil {
t.Fatalf("GrindName returned error: %v", err)
}
alias, err := svc.GetGrindName(8, 100, rules)
if err != nil {
t.Fatalf("GetGrindName returned error: %v", err)
}
if len(name) != 8 {
t.Fatalf("GrindName returned %q with length %d, want 8", name, len(name))
}
if len(alias) != 8 {
t.Fatalf("GetGrindName returned %q with length %d, want 8", alias, len(alias))
}
hash, err := covenant.HashString(name)
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
if !svc.HasRollout(hash, 100, rules) {
t.Fatalf("GrindName returned %q that does not satisfy rollout", name)
}
}
func TestServiceGetBlindAndGetTypeNameAliases(t *testing.T) {
svc := &Service{}
var nonce primitives.Hash
got, err := svc.GetBlind(1, nonce)
if err != nil {
t.Fatalf("GetBlind returned error: %v", err)
}
want, err := svc.Blind(1, nonce)
if err != nil {
t.Fatalf("Blind returned error: %v", err)
}
if got != want {
t.Fatalf("GetBlind returned %x, want %x", got, want)
}
if got := svc.GetTypeName(covenant.TypeBid); got != "BID" {
t.Fatalf("GetTypeName(TypeBid) = %q, want %q", got, "BID")
}
}
func TestServiceUtilityAliases(t *testing.T) {
svc := &Service{}
if svc.NewClaim() == nil {
t.Fatal("NewClaim should return a claim wrapper")
}
if svc.GetNewClaim() == nil {
t.Fatal("GetNewClaim should alias NewClaim")
}
2026-04-04 07:00:10 +00:00
if view := svc.NewNameView(); view == nil {
t.Fatal("NewNameView should return a cached name-state view")
} else if view.ToNameUndo().Names != nil {
t.Fatal("NewNameView should start with an empty undo payload")
}
if svc.GetNewNameView() == nil {
t.Fatal("GetNewNameView should alias NewNameView")
}
if got := svc.NextName("ExAmPle."); got != "example\x00." {
t.Fatalf("NextName returned %q", got)
}
if got := svc.GetNextName("ExAmPle."); got != "example\x00." {
t.Fatalf("GetNextName returned %q", got)
}
if got := svc.PrevName("ExAmPle."); got != "exampld\xff." {
t.Fatalf("PrevName returned %q", got)
}
if got := svc.GetPrevName("ExAmPle."); got != "exampld\xff." {
t.Fatalf("GetPrevName returned %q", got)
}
if svc.DefaultTTL() != DEFAULT_TTL || svc.GetDefaultTTL() != DEFAULT_TTL {
t.Fatalf("DefaultTTL getters = %d/%d, want %d", svc.DefaultTTL(), svc.GetDefaultTTL(), DEFAULT_TTL)
}
if len(svc.Dummy()) != 0 || len(svc.GetDummy()) != 0 {
t.Fatal("Dummy getters should return the zero-length DNS reference buffer")
}
if !bytes.Equal(svc.TypeMapRoot(), TYPE_MAP_ROOT) || !bytes.Equal(svc.GetTypeMapRoot(), TYPE_MAP_ROOT) {
t.Fatal("TypeMapRoot getters should alias the reference bitmap")
}
if !bytes.Equal(svc.TypeMapEmpty(), TYPE_MAP_EMPTY) || !bytes.Equal(svc.GetTypeMapEmpty(), TYPE_MAP_EMPTY) {
t.Fatal("TypeMapEmpty getters should alias the reference bitmap")
}
if !bytes.Equal(svc.TypeMapNS(), TYPE_MAP_NS) || !bytes.Equal(svc.GetTypeMapNS(), TYPE_MAP_NS) {
t.Fatal("TypeMapNS getters should alias the reference bitmap")
}
if !bytes.Equal(svc.TypeMapTXT(), TYPE_MAP_TXT) || !bytes.Equal(svc.GetTypeMapTXT(), TYPE_MAP_TXT) {
t.Fatal("TypeMapTXT getters should alias the reference bitmap")
}
if !bytes.Equal(svc.TypeMapA(), TYPE_MAP_A) || !bytes.Equal(svc.GetTypeMapA(), TYPE_MAP_A) {
t.Fatal("TypeMapA getters should alias the reference bitmap")
}
if !bytes.Equal(svc.TypeMapAAAA(), TYPE_MAP_AAAA) || !bytes.Equal(svc.GetTypeMapAAAA(), TYPE_MAP_AAAA) {
t.Fatal("TypeMapAAAA getters should alias the reference bitmap")
}
if len(svc.HSTypes()) != len(HSTypes) || len(svc.GetHSTypes()) != len(HSTypes) {
t.Fatal("HSTypes getters should alias the reference table")
}
if len(svc.HSTypesByVal()) != len(HSTypesByVal) || len(svc.GetHSTypesByVal()) != len(HSTypesByVal) {
t.Fatal("HSTypesByVal getters should alias the reference table")
}
record := svc.Create(".", svc.NextName("."), TYPE_MAP_ROOT)
if record.Name != "." {
t.Fatalf("Create Name = %q, want %q", record.Name, ".")
}
if record.NextDomain != "\x00." {
t.Fatalf("Create NextDomain = %q, want %q", record.NextDomain, "\x00.")
}
if record.TTL != DEFAULT_TTL {
t.Fatalf("Create TTL = %d, want %d", record.TTL, DEFAULT_TTL)
}
if !bytes.Equal(record.TypeBitmap, TYPE_MAP_ROOT) {
t.Fatalf("Create TypeBitmap = %x, want %x", record.TypeBitmap, TYPE_MAP_ROOT)
}
if svc.GetCreate("foo", "bar", TYPE_MAP_NS).TTL != DEFAULT_TTL {
t.Fatal("GetCreate should alias Create")
}
resource := svc.NewResource()
if resource == nil {
t.Fatal("NewResource should return a resource")
}
if resource.TTL != DEFAULT_TTL {
t.Fatalf("NewResource TTL = %d, want %d", resource.TTL, DEFAULT_TTL)
}
if svc.GetNewResource() == nil {
t.Fatal("GetNewResource should return a resource")
}
resource.Records = []ResourceRecord{TXTRecord{Entries: []string{"hello"}}}
encoded, err := resource.Encode()
if err != nil {
t.Fatalf("Resource.Encode returned error: %v", err)
}
decoded, err := svc.DecodeResource(encoded)
if err != nil {
t.Fatalf("DecodeResource returned error: %v", err)
}
if len(decoded.Records) != len(resource.Records) {
t.Fatalf("DecodeResource records = %d, want %d", len(decoded.Records), len(resource.Records))
}
if got, err := svc.GetDecodeResource(encoded); err != nil {
t.Fatalf("GetDecodeResource returned error: %v", err)
} else if got.TTL != decoded.TTL || len(got.Records) != len(decoded.Records) {
t.Fatal("GetDecodeResource should alias DecodeResource")
}
}
func TestServiceCoreAccessors(t *testing.T) {
runtime := core.NewServiceRuntime(nil, serviceOptions{})
svc := &Service{ServiceRuntime: runtime}
if svc.Core() != nil {
t.Fatal("Core should return nil when the service has no Core")
}
if svc.GetCore() != nil {
t.Fatal("GetCore should alias Core")
}
if got := svc.ServiceName(); got != ServiceName {
t.Fatalf("ServiceName() = %q, want %q", got, ServiceName)
}
if got := svc.GetServiceName(); got != ServiceName {
t.Fatalf("GetServiceName() = %q, want %q", got, ServiceName)
}
}
func TestServiceCoreNilSafety(t *testing.T) {
var svc *Service
if svc.Core() != nil {
t.Fatal("Core should return nil for a nil receiver")
}
if svc.GetCore() != nil {
t.Fatal("GetCore should return nil for a nil receiver")
}
if got := (&Service{}).Core(); got != nil {
t.Fatal("Core should return nil for a zero-value service")
}
}
func TestServiceNilReceiverCatalogAccessors(t *testing.T) {
var svc *Service
if svc.ReservedCatalog() != Reserved {
t.Fatal("ReservedCatalog should fall back to the package catalog on a nil receiver")
}
if svc.GetReservedCatalog() != Reserved {
t.Fatal("GetReservedCatalog should fall back to the package catalog on a nil receiver")
}
if svc.LockedCatalog() != Locked {
t.Fatal("LockedCatalog should fall back to the package catalog on a nil receiver")
}
if svc.GetLockedCatalog() != Locked {
t.Fatal("GetLockedCatalog should fall back to the package catalog on a nil receiver")
}
if svc.ReservedSize() != Reserved.Size() {
t.Fatal("ReservedSize should be nil-safe")
}
if svc.LockedSize() != Locked.Size() {
t.Fatal("LockedSize should be nil-safe")
}
if len(svc.ReservedEntries()) != len(Reserved.Entries()) {
t.Fatal("ReservedEntries should be nil-safe")
}
if len(svc.LockedEntries()) != len(Locked.Entries()) {
t.Fatal("LockedEntries should be nil-safe")
}
}
2026-04-02 07:23:05 +00:00
func TestNewService(t *testing.T) {
svc := NewService(nil)
if svc == nil {
t.Fatal("NewService should return a service instance")
}
if svc.Core() != nil {
t.Fatal("NewService(nil) should preserve a nil Core")
}
if got := svc.ServiceName(); got != ServiceName {
t.Fatalf("NewService ServiceName() = %q, want %q", got, ServiceName)
}
2026-04-04 06:27:33 +00:00
if got := GetNewService(nil); got == nil {
t.Fatal("GetNewService should return a service instance")
}
2026-04-02 07:23:05 +00:00
}
func TestNewServiceWithOptions(t *testing.T) {
c := core.New()
svc := NewServiceWithOptions(WithCore(c))
if svc == nil {
t.Fatal("NewServiceWithOptions should return a service instance")
}
if svc.Core() != c {
t.Fatal("WithCore should attach the provided Core instance")
}
if got := svc.ServiceName(); got != ServiceName {
t.Fatalf("NewServiceWithOptions ServiceName() = %q, want %q", got, ServiceName)
}
2026-04-04 06:27:33 +00:00
if got := GetNewServiceWithOptions(WithCore(c)); got == nil {
t.Fatal("GetNewServiceWithOptions should return a service instance")
} else if got.Core() != c {
t.Fatal("GetNewServiceWithOptions should attach the provided Core instance")
}
}
func TestNewServiceWithNilCoreOption(t *testing.T) {
svc := NewServiceWithOptions(WithCore(nil))
if svc == nil {
t.Fatal("NewServiceWithOptions should return a service instance")
}
if svc.Core() != nil {
t.Fatal("WithCore(nil) should preserve a nil Core")
}
}
func TestNewServiceWithCatalogOverrides(t *testing.T) {
reserved := covenant.DefaultReservedCatalog()
locked := covenant.DefaultLockedCatalog()
svc := NewServiceWithOptions(
WithReservedCatalog(reserved),
WithLockedCatalog(locked),
)
if svc.ReservedCatalog() != reserved {
t.Fatal("WithReservedCatalog should install the provided reserved catalog")
}
if svc.LockedCatalog() != locked {
t.Fatal("WithLockedCatalog should install the provided locked catalog")
}
}
func TestServiceDefaultCatalogIgnoresOverrides(t *testing.T) {
reserved := &covenant.ReservedCatalog{}
locked := &covenant.LockedCatalog{}
svc := NewServiceWithOptions(
WithReservedCatalog(reserved),
WithLockedCatalog(locked),
)
if svc.ReservedCatalog() != reserved {
t.Fatal("ReservedCatalog should return the service override")
}
if svc.DefaultReservedCatalog() != DefaultReservedCatalog() {
t.Fatal("DefaultReservedCatalog should return the package default catalog")
}
if svc.DefaultReservedCatalog() == svc.ReservedCatalog() {
t.Fatal("DefaultReservedCatalog should ignore the service override")
}
if svc.LockedCatalog() != locked {
t.Fatal("LockedCatalog should return the service override")
}
if svc.DefaultLockedCatalog() != DefaultLockedCatalog() {
t.Fatal("DefaultLockedCatalog should return the package default catalog")
}
if svc.DefaultLockedCatalog() == svc.LockedCatalog() {
t.Fatal("DefaultLockedCatalog should ignore the service override")
}
}
func TestCatalogOptionIgnoresNilCatalogs(t *testing.T) {
svc := NewServiceWithOptions(
WithReservedCatalog(nil),
WithLockedCatalog(nil),
)
if svc.ReservedCatalog() != ReservedCatalog() {
t.Fatal("WithReservedCatalog(nil) should not replace the default reserved catalog")
}
if svc.LockedCatalog() != LockedCatalog() {
t.Fatal("WithLockedCatalog(nil) should not replace the default locked catalog")
}
}
func TestRegisterRejectsNilCore(t *testing.T) {
res := Register(nil)
if res.OK {
t.Fatal("Register(nil) should fail")
}
if _, ok := res.Value.(error); !ok {
t.Fatalf("Register(nil) should return an error result, got %T", res.Value)
}
}
func TestRegisterWithOptions(t *testing.T) {
reserved := &covenant.ReservedCatalog{}
locked := &covenant.LockedCatalog{}
app := core.New(
core.WithService(RegisterWithOptions(
WithReservedCatalog(reserved),
WithLockedCatalog(locked),
)),
)
svc, ok := core.ServiceFor[*Service](app, ServiceName)
if !ok {
t.Fatal("RegisterWithOptions should register the LNS service")
}
if svc == nil {
t.Fatal("RegisterWithOptions should return a service instance")
}
if svc.ReservedCatalog() != reserved {
t.Fatal("RegisterWithOptions should apply the reserved catalog override")
}
if svc.LockedCatalog() != locked {
t.Fatal("RegisterWithOptions should apply the locked catalog override")
}
if svc.Core() != app {
t.Fatal("RegisterWithOptions should attach the Core instance")
}
}
func TestServiceGetReserved(t *testing.T) {
svc := &Service{}
item, ok := svc.GetReserved("RESERVED.lthn")
if !ok {
t.Fatal("GetReserved should find the reserved reference entry")
}
if item.Name != "reserved" {
t.Fatalf("item.Name = %q, want %q", item.Name, "reserved")
}
if item.Target != "reserved.com." {
t.Fatalf("item.Target = %q, want %q", item.Target, "reserved.com.")
}
hash, err := covenant.HashString("reserved")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
if item.Hash != hash {
t.Fatalf("item.Hash = %x, want %x", item.Hash, hash)
}
if _, ok := svc.GetReserved([]byte("reserved.lthn")); !ok {
t.Fatal("GetReserved([]byte) should find the reserved reference entry")
}
if _, ok := svc.GetReserved(123); ok {
t.Fatal("GetReserved should reject unsupported input types")
}
}
func TestServiceHasReserved(t *testing.T) {
svc := &Service{}
if !svc.HasReserved("RESERVED.lthn") {
t.Fatal("HasReserved should report canonical reserved names")
}
if !svc.HasReserved([]byte("reserved.lthn")) {
t.Fatal("HasReserved([]byte) should report canonical reserved names")
}
if svc.HasReserved("not-reserved.lthn") {
t.Fatal("HasReserved should return false for unknown names")
}
if svc.HasReserved(123) {
t.Fatal("HasReserved should reject unsupported input types")
}
}
2026-04-02 04:16:00 +00:00
func TestServiceGetReservedByName(t *testing.T) {
svc := &Service{}
item, ok := svc.GetReservedByName("RESERVED")
if !ok {
t.Fatal("GetReservedByName should find the reserved reference entry")
}
if item.Name != "reserved" {
t.Fatalf("item.Name = %q, want %q", item.Name, "reserved")
}
if _, ok := svc.GetReservedByName([]byte("reserved")); !ok {
t.Fatal("GetReservedByName([]byte) should find the reserved reference entry")
}
if _, ok := svc.GetReservedByName("RESERVED.lthn"); !ok {
t.Fatal("GetReservedByName should also accept canonical reserved names")
}
2026-04-02 04:16:00 +00:00
if _, ok := svc.GetReservedByName(123); ok {
t.Fatal("GetReservedByName should reject unsupported input types")
}
}
func TestServiceGetReservedStringAndBinary(t *testing.T) {
svc := &Service{}
item, ok := svc.GetReservedString("RESERVED")
if !ok {
t.Fatal("GetReservedString should find the reserved catalog label")
}
if item.Name != "reserved" {
t.Fatalf("item.Name = %q, want %q", item.Name, "reserved")
}
item, ok = svc.GetReservedBinary([]byte("reserved"))
if !ok {
t.Fatal("GetReservedBinary should find the reserved catalog label")
}
if item.Name != "reserved" {
t.Fatalf("item.Name = %q, want %q", item.Name, "reserved")
}
if !svc.HasReservedString("reserved") {
t.Fatal("HasReservedString should report reserved catalog labels")
}
if !svc.HasReservedBinary([]byte("RESERVED")) {
t.Fatal("HasReservedBinary should report reserved catalog labels")
}
if _, ok := svc.GetReservedString("RESERVED.lthn"); !ok {
t.Fatal("GetReservedString should also accept canonical reserved names")
}
if _, ok := svc.GetReservedBinary([]byte("reserved.lthn")); !ok {
t.Fatal("GetReservedBinary should also accept canonical reserved names")
}
if !svc.HasReservedString("reserved.lthn") {
t.Fatal("HasReservedString should report canonical reserved names")
}
if !svc.HasReservedBinary([]byte("RESERVED.lthn")) {
t.Fatal("HasReservedBinary should report canonical reserved names")
}
}
func TestServiceGetReservedByStringAndBinary(t *testing.T) {
svc := &Service{}
item, ok := svc.GetReservedByString("RESERVED")
if !ok {
t.Fatal("GetReservedByString should find the reserved catalog label")
}
if item.Name != "reserved" {
t.Fatalf("item.Name = %q, want %q", item.Name, "reserved")
}
item, ok = svc.GetReservedByBinary([]byte("reserved"))
if !ok {
t.Fatal("GetReservedByBinary should find the reserved catalog label")
}
if item.Name != "reserved" {
t.Fatalf("item.Name = %q, want %q", item.Name, "reserved")
}
if !svc.HasReservedByString("reserved") {
t.Fatal("HasReservedByString should report reserved catalog labels")
}
if !svc.HasReservedByBinary([]byte("RESERVED")) {
t.Fatal("HasReservedByBinary should report reserved catalog labels")
}
}
2026-04-02 04:16:00 +00:00
func TestServiceHasReservedByName(t *testing.T) {
svc := &Service{}
if !svc.HasReservedByName("RESERVED") {
t.Fatal("HasReservedByName should report reserved catalog labels")
}
if !svc.HasReservedByName([]byte("reserved")) {
t.Fatal("HasReservedByName([]byte) should report reserved catalog labels")
}
if !svc.HasReservedByName("reserved.lthn") {
t.Fatal("HasReservedByName should report canonical reserved names")
}
2026-04-02 04:16:00 +00:00
if svc.HasReservedByName("not-reserved") {
t.Fatal("HasReservedByName should return false for unknown labels")
}
if svc.HasReservedByName(123) {
t.Fatal("HasReservedByName should reject unsupported input types")
}
}
func TestServiceReservedNameAliases(t *testing.T) {
svc := &Service{}
item, ok := svc.GetReservedName("RESERVED")
if !ok {
t.Fatal("GetReservedName should find the reserved catalog label")
}
if item.Name != "reserved" {
t.Fatalf("item.Name = %q, want %q", item.Name, "reserved")
}
item, ok = svc.GetReservedName("RESERVED.lthn")
if !ok {
t.Fatal("GetReservedName should also accept canonical reserved names")
}
if item.Name != "reserved" {
t.Fatalf("item.Name = %q, want %q", item.Name, "reserved")
}
if !svc.HasReservedName("reserved") {
t.Fatal("HasReservedName should report reserved catalog labels")
}
if !svc.HasReservedName("reserved.lthn") {
t.Fatal("HasReservedName should report canonical reserved names")
}
}
func TestServiceGetReservedHash(t *testing.T) {
svc := &Service{}
hash, err := covenant.HashString("reserved")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
item, ok := svc.GetReservedHash(hash)
if !ok {
t.Fatal("GetReservedHash should find the reserved reference entry")
}
if item.Name != "reserved" {
t.Fatalf("item.Name = %q, want %q", item.Name, "reserved")
}
if !svc.HasReservedHash(hash) {
t.Fatal("HasReservedHash should report canonical reserved hashes")
}
if svc.HasReservedHash(primitives.Hash{}) {
t.Fatal("HasReservedHash should return false for unknown hashes")
}
}
func TestServiceGetReservedByHash(t *testing.T) {
svc := &Service{}
hash, err := covenant.HashString("reserved")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
item, ok := svc.GetReservedByHash(hash)
if !ok {
t.Fatal("GetReservedByHash should find the reserved reference entry")
}
if item.Name != "reserved" {
t.Fatalf("item.Name = %q, want %q", item.Name, "reserved")
}
if !svc.HasReservedByHash(hash) {
t.Fatal("HasReservedByHash should report canonical reserved hashes")
}
if svc.HasReservedByHash(primitives.Hash{}) {
t.Fatal("HasReservedByHash should return false for unknown hashes")
}
}
func TestServiceGetLocked(t *testing.T) {
svc := &Service{}
item, ok := svc.GetLocked("NEC.lthn")
if !ok {
t.Fatal("GetLocked should find the locked reference entry")
}
if item.Name != "nec" {
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
}
if item.Target != "nec." {
t.Fatalf("item.Target = %q, want %q", item.Target, "nec.")
}
if !item.Root || item.Custom {
t.Fatalf("unexpected flags on nec: %#v", item)
}
if _, ok := svc.GetLocked([]byte("nec.lthn")); !ok {
t.Fatal("GetLocked([]byte) should find the locked reference entry")
}
if _, ok := svc.GetLocked(123); ok {
t.Fatal("GetLocked should reject unsupported input types")
}
}
2026-04-02 03:46:19 +00:00
func TestServiceHasLocked(t *testing.T) {
svc := &Service{}
if !svc.HasLocked("NEC.lthn") {
t.Fatal("HasLocked should report canonical locked names")
}
if !svc.HasLocked([]byte("nec.lthn")) {
t.Fatal("HasLocked([]byte) should report canonical locked names")
}
if svc.HasLocked("not-locked.lthn") {
t.Fatal("HasLocked should return false for unknown names")
}
if svc.HasLocked(123) {
t.Fatal("HasLocked should reject unsupported input types")
}
}
2026-04-02 04:16:00 +00:00
func TestServiceGetLockedByName(t *testing.T) {
svc := &Service{}
item, ok := svc.GetLockedByName("NEC")
if !ok {
t.Fatal("GetLockedByName should find the locked reference entry")
}
if item.Name != "nec" {
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
}
if _, ok := svc.GetLockedByName([]byte("nec")); !ok {
t.Fatal("GetLockedByName([]byte) should find the locked reference entry")
}
if _, ok := svc.GetLockedByName("NEC.lthn"); !ok {
t.Fatal("GetLockedByName should also accept canonical locked names")
}
2026-04-02 04:16:00 +00:00
if _, ok := svc.GetLockedByName(123); ok {
t.Fatal("GetLockedByName should reject unsupported input types")
}
}
func TestServiceGetLockedStringAndBinary(t *testing.T) {
svc := &Service{}
item, ok := svc.GetLockedString("NEC")
if !ok {
t.Fatal("GetLockedString should find the locked catalog label")
}
if item.Name != "nec" {
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
}
item, ok = svc.GetLockedBinary([]byte("nec"))
if !ok {
t.Fatal("GetLockedBinary should find the locked catalog label")
}
if item.Name != "nec" {
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
}
if !svc.HasLockedString("nec") {
t.Fatal("HasLockedString should report locked catalog labels")
}
if !svc.HasLockedBinary([]byte("NEC")) {
t.Fatal("HasLockedBinary should report locked catalog labels")
}
if _, ok := svc.GetLockedString("NEC.lthn"); !ok {
t.Fatal("GetLockedString should also accept canonical locked names")
}
if _, ok := svc.GetLockedBinary([]byte("nec.lthn")); !ok {
t.Fatal("GetLockedBinary should also accept canonical locked names")
}
if !svc.HasLockedString("nec.lthn") {
t.Fatal("HasLockedString should report canonical locked names")
}
if !svc.HasLockedBinary([]byte("NEC.lthn")) {
t.Fatal("HasLockedBinary should report canonical locked names")
}
}
func TestServiceGetLockedByStringAndBinary(t *testing.T) {
svc := &Service{}
item, ok := svc.GetLockedByString("NEC")
if !ok {
t.Fatal("GetLockedByString should find the locked catalog label")
}
if item.Name != "nec" {
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
}
item, ok = svc.GetLockedByBinary([]byte("nec"))
if !ok {
t.Fatal("GetLockedByBinary should find the locked catalog label")
}
if item.Name != "nec" {
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
}
if !svc.HasLockedByString("nec") {
t.Fatal("HasLockedByString should report locked catalog labels")
}
if !svc.HasLockedByBinary([]byte("NEC")) {
t.Fatal("HasLockedByBinary should report locked catalog labels")
}
}
2026-04-02 04:16:00 +00:00
func TestServiceHasLockedByName(t *testing.T) {
svc := &Service{}
if !svc.HasLockedByName("NEC") {
t.Fatal("HasLockedByName should report locked catalog labels")
}
if !svc.HasLockedByName([]byte("nec")) {
t.Fatal("HasLockedByName([]byte) should report locked catalog labels")
}
if !svc.HasLockedByName("nec.lthn") {
t.Fatal("HasLockedByName should report canonical locked names")
}
2026-04-02 04:16:00 +00:00
if svc.HasLockedByName("not-locked") {
t.Fatal("HasLockedByName should return false for unknown labels")
}
if svc.HasLockedByName(123) {
t.Fatal("HasLockedByName should reject unsupported input types")
}
}
func TestServiceLockedNameAliases(t *testing.T) {
svc := &Service{}
item, ok := svc.GetLockedName("NEC")
if !ok {
t.Fatal("GetLockedName should find the locked catalog label")
}
if item.Name != "nec" {
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
}
item, ok = svc.GetLockedName("NEC.lthn")
if !ok {
t.Fatal("GetLockedName should also accept canonical locked names")
}
if item.Name != "nec" {
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
}
if !svc.HasLockedName("nec") {
t.Fatal("HasLockedName should report locked catalog labels")
}
if !svc.HasLockedName("nec.lthn") {
t.Fatal("HasLockedName should report canonical locked names")
}
}
func TestServiceGetLockedHash(t *testing.T) {
svc := &Service{}
hash, err := covenant.HashString("nec")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
item, ok := svc.GetLockedHash(hash)
if !ok {
t.Fatal("GetLockedHash should find the locked reference entry")
}
if item.Name != "nec" {
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
}
if !svc.HasLockedHash(hash) {
t.Fatal("HasLockedHash should report canonical locked hashes")
}
if svc.HasLockedHash(primitives.Hash{}) {
t.Fatal("HasLockedHash should return false for unknown hashes")
}
}
func TestServiceGetLockedByHash(t *testing.T) {
svc := &Service{}
hash, err := covenant.HashString("nec")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
item, ok := svc.GetLockedByHash(hash)
if !ok {
t.Fatal("GetLockedByHash should find the locked reference entry")
}
if item.Name != "nec" {
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
}
if !svc.HasLockedByHash(hash) {
t.Fatal("HasLockedByHash should report canonical locked hashes")
}
if svc.HasLockedByHash(primitives.Hash{}) {
t.Fatal("HasLockedByHash should return false for unknown hashes")
}
}
2026-04-02 03:46:19 +00:00
func TestServiceReservedCatalog(t *testing.T) {
svc := &Service{}
catalog := svc.ReservedCatalog()
getCatalog := svc.GetReservedCatalog()
defaultCatalog := DefaultReservedCatalog()
2026-04-02 06:52:10 +00:00
packageGetCatalog := GetReservedCatalog()
2026-04-02 03:46:19 +00:00
if catalog == nil {
t.Fatal("ReservedCatalog should return a catalog")
}
if getCatalog == nil {
t.Fatal("GetReservedCatalog should return a catalog")
}
if catalog != Reserved {
t.Fatal("ReservedCatalog should return the exported Reserved instance")
}
if getCatalog != catalog {
t.Fatal("GetReservedCatalog should alias ReservedCatalog")
}
if defaultCatalog != catalog {
t.Fatal("DefaultReservedCatalog should alias the covenant default catalog")
}
2026-04-02 06:52:10 +00:00
if packageGetCatalog != catalog {
t.Fatal("GetReservedCatalog should alias DefaultReservedCatalog")
}
if !svc.HasReservedCatalog() || !svc.GetHasReservedCatalog() {
t.Fatal("HasReservedCatalog aliases should report the service reserved catalog")
}
if got := svc.ReservedSize(); got != catalog.Size() {
t.Fatalf("ReservedSize() = %d, want %d", got, catalog.Size())
}
2026-04-02 05:41:13 +00:00
if got := svc.GetReservedSize(); got != catalog.Size() {
t.Fatalf("GetReservedSize() = %d, want %d", got, catalog.Size())
}
if got := svc.ReservedNameValue(); got != catalog.NameValue() {
t.Fatalf("ReservedNameValue() = %d, want %d", got, catalog.NameValue())
}
2026-04-02 05:41:13 +00:00
if got := svc.GetReservedNameValue(); got != catalog.NameValue() {
t.Fatalf("GetReservedNameValue() = %d, want %d", got, catalog.NameValue())
}
if got := svc.ReservedRootValue(); got != catalog.RootValue() {
t.Fatalf("ReservedRootValue() = %d, want %d", got, catalog.RootValue())
}
2026-04-02 05:41:13 +00:00
if got := svc.GetReservedRootValue(); got != catalog.RootValue() {
t.Fatalf("GetReservedRootValue() = %d, want %d", got, catalog.RootValue())
}
if got := svc.ReservedTopValue(); got != catalog.TopValue() {
t.Fatalf("ReservedTopValue() = %d, want %d", got, catalog.TopValue())
}
2026-04-02 05:41:13 +00:00
if got := svc.GetReservedTopValue(); got != catalog.TopValue() {
t.Fatalf("GetReservedTopValue() = %d, want %d", got, catalog.TopValue())
}
if got := svc.NameValue(); got != catalog.NameValue() {
t.Fatalf("NameValue() = %d, want %d", got, catalog.NameValue())
}
if got := svc.GetNameValue(); got != catalog.NameValue() {
t.Fatalf("GetNameValue() = %d, want %d", got, catalog.NameValue())
}
if got := svc.RootValue(); got != catalog.RootValue() {
t.Fatalf("RootValue() = %d, want %d", got, catalog.RootValue())
}
if got := svc.GetRootValue(); got != catalog.RootValue() {
t.Fatalf("GetRootValue() = %d, want %d", got, catalog.RootValue())
}
if got := svc.TopValue(); got != catalog.TopValue() {
t.Fatalf("TopValue() = %d, want %d", got, catalog.TopValue())
}
if got := svc.GetTopValue(); got != catalog.TopValue() {
t.Fatalf("GetTopValue() = %d, want %d", got, catalog.TopValue())
}
if got := svc.PrefixSize(); got != catalog.PrefixSize() {
t.Fatalf("PrefixSize() = %d, want %d", got, catalog.PrefixSize())
}
if got := svc.GetPrefixSize(); got != catalog.PrefixSize() {
t.Fatalf("GetPrefixSize() = %d, want %d", got, catalog.PrefixSize())
}
if got := svc.ReservedPrefixSize(); got != catalog.PrefixSize() {
t.Fatalf("ReservedPrefixSize() = %d, want %d", got, catalog.PrefixSize())
}
2026-04-02 05:41:13 +00:00
if got := svc.GetReservedPrefixSize(); got != catalog.PrefixSize() {
t.Fatalf("GetReservedPrefixSize() = %d, want %d", got, catalog.PrefixSize())
}
2026-04-02 03:46:19 +00:00
if !catalog.HasByName("RESERVED") {
t.Fatal("ReservedCatalog should expose the reserved catalog helpers")
}
if len(catalog.Keys()) == 0 {
t.Fatal("ReservedCatalog should expose hash lookup aliases")
}
if !catalog.HasByHash(catalog.Keys()[0]) {
t.Fatal("ReservedCatalog should expose hash lookup aliases")
}
if _, ok := catalog.GetByHash(catalog.Keys()[0]); !ok {
t.Fatal("ReservedCatalog should expose hash lookup aliases")
}
2026-04-02 03:46:19 +00:00
if len(catalog.Entries()) == 0 {
t.Fatal("ReservedCatalog should expose catalog entries")
}
if len(svc.ReservedEntries()) == 0 {
t.Fatal("ReservedEntries should expose catalog entries")
}
2026-04-02 05:41:13 +00:00
if len(svc.GetReservedEntries()) == 0 {
t.Fatal("GetReservedEntries should expose catalog entries")
}
if len(svc.ReservedKeys()) == 0 {
t.Fatal("ReservedKeys should expose catalog keys")
}
2026-04-02 05:41:13 +00:00
if len(svc.GetReservedKeys()) == 0 {
t.Fatal("GetReservedKeys should expose catalog keys")
}
if len(svc.ReservedValues()) == 0 {
t.Fatal("ReservedValues should expose catalog values")
}
2026-04-02 05:41:13 +00:00
if len(svc.GetReservedValues()) == 0 {
t.Fatal("GetReservedValues should expose catalog values")
}
2026-04-02 03:46:19 +00:00
}
func TestServiceLockedCatalog(t *testing.T) {
svc := &Service{}
catalog := svc.LockedCatalog()
getCatalog := svc.GetLockedCatalog()
defaultCatalog := DefaultLockedCatalog()
2026-04-02 06:52:10 +00:00
packageGetCatalog := GetLockedCatalog()
2026-04-02 03:46:19 +00:00
if catalog == nil {
t.Fatal("LockedCatalog should return a catalog")
}
if getCatalog == nil {
t.Fatal("GetLockedCatalog should return a catalog")
}
if catalog != Locked {
t.Fatal("LockedCatalog should return the exported Locked instance")
}
if getCatalog != catalog {
t.Fatal("GetLockedCatalog should alias LockedCatalog")
}
if defaultCatalog != catalog {
t.Fatal("DefaultLockedCatalog should alias the covenant default catalog")
}
2026-04-02 06:52:10 +00:00
if packageGetCatalog != catalog {
t.Fatal("GetLockedCatalog should alias DefaultLockedCatalog")
}
if !svc.HasLockedCatalog() || !svc.GetHasLockedCatalog() {
t.Fatal("HasLockedCatalog aliases should report the service locked catalog")
}
if got := svc.LockedSize(); got != catalog.Size() {
t.Fatalf("LockedSize() = %d, want %d", got, catalog.Size())
}
2026-04-02 05:41:13 +00:00
if got := svc.GetLockedSize(); got != catalog.Size() {
t.Fatalf("GetLockedSize() = %d, want %d", got, catalog.Size())
}
if got := svc.LockedPrefixSize(); got != catalog.PrefixSize() {
t.Fatalf("LockedPrefixSize() = %d, want %d", got, catalog.PrefixSize())
}
2026-04-02 05:41:13 +00:00
if got := svc.GetLockedPrefixSize(); got != catalog.PrefixSize() {
t.Fatalf("GetLockedPrefixSize() = %d, want %d", got, catalog.PrefixSize())
}
2026-04-02 03:46:19 +00:00
if !catalog.HasByName("NEC") {
t.Fatal("LockedCatalog should expose the locked catalog helpers")
}
if len(catalog.Keys()) == 0 {
t.Fatal("LockedCatalog should expose hash lookup aliases")
}
if !catalog.HasByHash(catalog.Keys()[0]) {
t.Fatal("LockedCatalog should expose hash lookup aliases")
}
if _, ok := catalog.GetByHash(catalog.Keys()[0]); !ok {
t.Fatal("LockedCatalog should expose hash lookup aliases")
}
2026-04-02 03:46:19 +00:00
if len(catalog.Entries()) == 0 {
t.Fatal("LockedCatalog should expose catalog entries")
}
if len(svc.LockedEntries()) == 0 {
t.Fatal("LockedEntries should expose catalog entries")
}
2026-04-02 05:41:13 +00:00
if len(svc.GetLockedEntries()) == 0 {
t.Fatal("GetLockedEntries should expose catalog entries")
}
if len(svc.LockedKeys()) == 0 {
t.Fatal("LockedKeys should expose catalog keys")
}
2026-04-02 05:41:13 +00:00
if len(svc.GetLockedKeys()) == 0 {
t.Fatal("GetLockedKeys should expose catalog keys")
}
if len(svc.LockedValues()) == 0 {
t.Fatal("LockedValues should expose catalog values")
}
2026-04-02 05:41:13 +00:00
if len(svc.GetLockedValues()) == 0 {
t.Fatal("GetLockedValues should expose catalog values")
}
2026-04-02 03:46:19 +00:00
}
func TestServiceNameSetHelpers(t *testing.T) {
svc := &Service{}
hash, err := svc.HashString("example-name")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
tx := primitives.Transaction{
Inputs: []primitives.Input{
{Prevout: primitives.Outpoint{TxHash: primitives.Hash{1}, Index: 0}},
},
Outputs: []primitives.Output{
{Covenant: primitives.Covenant{Type: uint8(covenant.TypeOpen), Items: [][]byte{hash[:], []byte{0, 0, 0, 0}, []byte("example-name")}}},
},
}
set := map[primitives.Hash]struct{}{
hash: {},
}
if !svc.HasNames(tx, set) {
t.Fatal("HasNames should report a matching name hash")
}
2026-04-02 14:48:33 +00:00
if !svc.GetHasNames(tx, set) {
t.Fatal("GetHasNames should report a matching name hash")
}
svc.RemoveNames(tx, set)
2026-04-02 14:48:33 +00:00
svc.GetRemoveNames(tx, set)
if len(set) != 0 {
2026-04-02 14:48:33 +00:00
t.Fatalf("RemoveNames/GetRemoveNames left %d entries, want 0", len(set))
}
svc.AddNames(tx, set)
2026-04-02 14:48:33 +00:00
svc.GetAddNames(tx, set)
if len(set) != 1 {
2026-04-02 14:48:33 +00:00
t.Fatalf("AddNames/GetAddNames left %d entries, want 1", len(set))
}
}
func TestServiceVerifyCovenants(t *testing.T) {
svc := &Service{}
hash, err := covenant.HashString("example-name")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
var prevHash primitives.Hash
prevHash[0] = 5
var finalHash primitives.Hash
finalHash[0] = 6
committedAddress := append([]byte(nil), finalHash[:]...)
outputAddress := append([]byte(nil), finalHash[:]...)
transferCovenant := primitives.Covenant{
Type: uint8(covenant.TypeTransfer),
Items: [][]byte{
hash[:],
make([]byte, 4),
[]byte{0},
committedAddress,
},
}
binary.LittleEndian.PutUint32(transferCovenant.Items[1], 100)
finalizeCovenant := primitives.Covenant{
Type: uint8(covenant.TypeFinalize),
Items: [][]byte{
hash[:],
make([]byte, 4),
[]byte("example-name"),
[]byte{0},
make([]byte, 4),
make([]byte, 4),
make([]byte, 32),
},
}
binary.LittleEndian.PutUint32(finalizeCovenant.Items[1], 100)
binary.LittleEndian.PutUint32(finalizeCovenant.Items[4], 1)
binary.LittleEndian.PutUint32(finalizeCovenant.Items[5], 2)
tx := primitives.Transaction{
Inputs: []primitives.Input{
{Prevout: primitives.Outpoint{TxHash: prevHash, Index: 0}},
},
Outputs: []primitives.Output{
{
Value: 1000,
Address: primitives.Address{
Version: 0,
Hash: outputAddress,
},
Covenant: finalizeCovenant,
},
},
}
2026-04-02 14:48:33 +00:00
if !svc.HasSaneCovenants(tx) {
t.Fatal("HasSaneCovenants should accept structurally valid covenants")
}
if !svc.GetHasSaneCovenants(tx) {
t.Fatal("GetHasSaneCovenants should accept structurally valid covenants")
}
view := packageTestCoinView{
coins: map[primitives.Outpoint]primitives.Output{
primitives.Outpoint{TxHash: prevHash, Index: 0}: primitives.Output{
Value: 1000,
Address: primitives.Address{
Version: 0,
Hash: []byte{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9},
},
Covenant: transferCovenant,
},
},
}
if got := svc.VerifyCovenants(tx, view, 100, Network{}); got != 0 {
t.Fatalf("VerifyCovenants returned %d, want 0", got)
}
tx.Outputs[0].Address.Hash[0] ^= 1
if got := svc.GetVerifyCovenants(tx, view, 100, Network{}); got != -1 {
t.Fatalf("GetVerifyCovenants returned %d for an invalid finalize address, want -1", got)
}
}