// SPDX-License-Identifier: EUPL-1.2 package lns import ( "bytes" "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" ) func TestServiceLifecycleHooks(t *testing.T) { svc := &Service{} if got := svc.OnStartup(nil); !got.OK { t.Fatalf("OnStartup returned %#v, want OK", got) } if got := svc.GetOnStartup(context.Background()); !got.OK { t.Fatalf("GetOnStartup returned %#v, want OK", got) } if got := svc.OnShutdown(nil); !got.OK { t.Fatalf("OnShutdown returned %#v, want OK", got) } 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) } 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) } if got := nilSvc.GetOnShutdown(context.Background()); !got.OK { t.Fatalf("nil GetOnShutdown returned %#v, want OK", got) } } func TestServiceHandleIPCEvents(t *testing.T) { svc := &Service{} if got := svc.HandleIPCEvents(nil, nil); !got.OK { t.Fatalf("HandleIPCEvents returned %#v, want OK", got) } 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) } 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", "foo..lthn", "foo.lthn..", "foo-", "test.lthn", "Kfoo.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) } } 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: "Kfoo.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("Kfoo.lthn") { t.Fatal("VerifyString should reject non-ASCII names") } if svc.VerifyBinary([]byte("Kfoo.lthn")) { t.Fatal("VerifyBinary should reject non-ASCII names") } } 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") } } 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) } 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) } } 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") } 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") } 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) } 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) } 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) } 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") } 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") } } 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) } if got := GetNewService(nil); got == nil { t.Fatal("GetNewService should return a service instance") } } 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) } 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") } } 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") } 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") } } 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") } 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") } } 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") } } 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") } 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") } } 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") } 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") } } func TestServiceReservedCatalog(t *testing.T) { svc := &Service{} catalog := svc.ReservedCatalog() getCatalog := svc.GetReservedCatalog() defaultCatalog := DefaultReservedCatalog() packageGetCatalog := GetReservedCatalog() 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") } 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()) } 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()) } 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()) } 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()) } 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()) } if got := svc.GetReservedPrefixSize(); got != catalog.PrefixSize() { t.Fatalf("GetReservedPrefixSize() = %d, want %d", got, catalog.PrefixSize()) } 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") } if len(catalog.Entries()) == 0 { t.Fatal("ReservedCatalog should expose catalog entries") } if len(svc.ReservedEntries()) == 0 { t.Fatal("ReservedEntries should expose catalog entries") } if len(svc.GetReservedEntries()) == 0 { t.Fatal("GetReservedEntries should expose catalog entries") } if len(svc.ReservedKeys()) == 0 { t.Fatal("ReservedKeys should expose catalog keys") } if len(svc.GetReservedKeys()) == 0 { t.Fatal("GetReservedKeys should expose catalog keys") } if len(svc.ReservedValues()) == 0 { t.Fatal("ReservedValues should expose catalog values") } if len(svc.GetReservedValues()) == 0 { t.Fatal("GetReservedValues should expose catalog values") } } func TestServiceLockedCatalog(t *testing.T) { svc := &Service{} catalog := svc.LockedCatalog() getCatalog := svc.GetLockedCatalog() defaultCatalog := DefaultLockedCatalog() packageGetCatalog := GetLockedCatalog() 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") } 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()) } 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()) } if got := svc.GetLockedPrefixSize(); got != catalog.PrefixSize() { t.Fatalf("GetLockedPrefixSize() = %d, want %d", got, catalog.PrefixSize()) } 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") } if len(catalog.Entries()) == 0 { t.Fatal("LockedCatalog should expose catalog entries") } if len(svc.LockedEntries()) == 0 { t.Fatal("LockedEntries should expose catalog entries") } if len(svc.GetLockedEntries()) == 0 { t.Fatal("GetLockedEntries should expose catalog entries") } if len(svc.LockedKeys()) == 0 { t.Fatal("LockedKeys should expose catalog keys") } if len(svc.GetLockedKeys()) == 0 { t.Fatal("GetLockedKeys should expose catalog keys") } if len(svc.LockedValues()) == 0 { t.Fatal("LockedValues should expose catalog values") } if len(svc.GetLockedValues()) == 0 { t.Fatal("GetLockedValues should expose catalog values") } } 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") } if !svc.GetHasNames(tx, set) { t.Fatal("GetHasNames should report a matching name hash") } svc.RemoveNames(tx, set) svc.GetRemoveNames(tx, set) if len(set) != 0 { t.Fatalf("RemoveNames/GetRemoveNames left %d entries, want 0", len(set)) } svc.AddNames(tx, set) svc.GetAddNames(tx, set) if len(set) != 1 { 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, }, }, } 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) } }