go-lns/lns_package_test.go
2026-04-04 05:10:29 +00:00

1149 lines
36 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package lns
import (
"crypto/sha3"
"encoding/binary"
"strings"
"testing"
"golang.org/x/crypto/blake2b"
"dappco.re/go/lns/pkg/covenant"
"dappco.re/go/lns/pkg/primitives"
)
func TestPackageResolveAndVerifyAliases(t *testing.T) {
want := primitives.Hash(sha3.Sum256([]byte("foo-bar")))
resolveCases := []struct {
name string
input any
fn func(any) (primitives.Hash, error)
}{
{name: "Resolve", input: "Foo-Bar.lthn", fn: Resolve},
{name: "GetResolve", input: "Foo-Bar.lthn", fn: GetResolve},
{name: "Hash", input: "Foo-Bar.lthn", fn: Hash},
{name: "GetHash", input: "Foo-Bar.lthn", fn: GetHash},
{name: "GetResolveString", input: "Foo-Bar.lthn", fn: func(name any) (primitives.Hash, error) { return GetResolveString(name.(string)) }},
{name: "GetResolveBinary", input: []byte("Foo-Bar.lthn"), fn: func(name any) (primitives.Hash, error) { return GetResolveBinary(name.([]byte)) }},
{name: "ResolveName", input: "Foo-Bar.lthn", fn: ResolveName},
{name: "GetResolveName", input: "Foo-Bar.lthn", fn: GetResolveName},
{name: "HashName", input: "Foo-Bar.lthn", fn: HashName},
{name: "GetHashName", input: "Foo-Bar.lthn", fn: GetHashName},
{name: "ResolveByName", input: "Foo-Bar.lthn", fn: ResolveByName},
{name: "GetResolveByName", input: "Foo-Bar.lthn", fn: GetResolveByName},
{name: "HashByName", input: "Foo-Bar.lthn", fn: HashByName},
{name: "GetHashByName", input: "Foo-Bar.lthn", fn: GetHashByName},
{name: "ResolveByString", input: "Foo-Bar.lthn", fn: func(name any) (primitives.Hash, error) { return ResolveByString(name.(string)) }},
{name: "GetResolveByString", input: "Foo-Bar.lthn", fn: func(name any) (primitives.Hash, error) { return GetResolveByString(name.(string)) }},
{name: "ResolveByBinary", input: []byte("Foo-Bar.lthn"), fn: func(name any) (primitives.Hash, error) { return ResolveByBinary(name.([]byte)) }},
{name: "GetResolveByBinary", input: []byte("Foo-Bar.lthn"), fn: func(name any) (primitives.Hash, error) { return GetResolveByBinary(name.([]byte)) }},
{name: "HashString", input: "Foo-Bar.lthn", fn: func(name any) (primitives.Hash, error) { return HashString(name.(string)) }},
{name: "GetHashString", input: "Foo-Bar.lthn", fn: func(name any) (primitives.Hash, error) { return GetHashString(name.(string)) }},
{name: "HashBinary", input: []byte("Foo-Bar.lthn"), fn: func(name any) (primitives.Hash, error) { return HashBinary(name.([]byte)) }},
{name: "GetHashBinary", input: []byte("Foo-Bar.lthn"), fn: func(name any) (primitives.Hash, error) { return GetHashBinary(name.([]byte)) }},
{name: "HashByString", input: "Foo-Bar.lthn", fn: func(name any) (primitives.Hash, error) { return HashByString(name.(string)) }},
{name: "GetHashByString", input: "Foo-Bar.lthn", fn: func(name any) (primitives.Hash, error) { return GetHashByString(name.(string)) }},
{name: "HashByBinary", input: []byte("Foo-Bar.lthn"), fn: func(name any) (primitives.Hash, error) { return HashByBinary(name.([]byte)) }},
{name: "GetHashByBinary", input: []byte("Foo-Bar.lthn"), fn: func(name any) (primitives.Hash, error) { return GetHashByBinary(name.([]byte)) }},
}
for _, tc := range resolveCases {
got, err := tc.fn(tc.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)
}
}
if got, err := ResolveString("Foo-Bar.lthn"); err != nil {
t.Fatalf("ResolveString returned error: %v", err)
} else if got != want {
t.Fatalf("ResolveString returned %x, want %x", got, want)
}
if got, err := ResolveBinary([]byte("Foo-Bar.lthn")); err != nil {
t.Fatalf("ResolveBinary returned error: %v", err)
} else if got != want {
t.Fatalf("ResolveBinary returned %x, want %x", got, want)
}
if _, err := 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)
}
if _, err := 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)
}
for _, tc := range []struct {
name string
fn func(any) (primitives.Hash, error)
}{
{name: "ResolveString", fn: func(name any) (primitives.Hash, error) { return ResolveString(name.(string)) }},
{name: "ResolveBinary", fn: func(name any) (primitives.Hash, error) { return ResolveBinary(name.([]byte)) }},
{name: "HashString", fn: func(name any) (primitives.Hash, error) { return HashString(name.(string)) }},
{name: "HashBinary", fn: func(name any) (primitives.Hash, error) { return HashBinary(name.([]byte)) }},
} {
input := any("foo.lthn..")
if tc.name == "ResolveBinary" || tc.name == "HashBinary" {
input = []byte("foo.lthn..")
}
if _, err := tc.fn(input); err == nil {
t.Fatalf("%s should reject malformed names", tc.name)
}
}
if got, err := HashString("Foo-Bar.lthn"); err != nil {
t.Fatalf("HashString returned error: %v", err)
} else if got != want {
t.Fatalf("HashString returned %x, want %x", got, want)
}
if got, err := HashBinary([]byte("Foo-Bar.lthn")); err != nil {
t.Fatalf("HashBinary returned error: %v", err)
} else if got != want {
t.Fatalf("HashBinary returned %x, want %x", got, want)
}
verifyCases := []struct {
name string
input any
badInput any
fn func(any) bool
}{
{name: "Verify", input: "Foo-Bar.lthn", badInput: "foo.bar.lthn", fn: Verify},
{name: "GetVerify", input: "Foo-Bar.lthn", badInput: "foo.bar.lthn", fn: GetVerify},
{name: "VerifyName", input: "Foo-Bar.lthn", badInput: "foo.bar.lthn", fn: VerifyName},
{name: "GetVerifyName", input: "Foo-Bar.lthn", badInput: "foo.bar.lthn", fn: GetVerifyName},
{name: "VerifyByName", input: "Foo-Bar.lthn", badInput: "foo.bar.lthn", fn: VerifyByName},
{name: "GetVerifyByName", input: "Foo-Bar.lthn", badInput: "foo.bar.lthn", fn: GetVerifyByName},
{name: "VerifyString", input: "Foo-Bar.lthn", badInput: "foo.bar.lthn", fn: func(name any) bool { return VerifyString(name.(string)) }},
{name: "GetVerifyString", input: "Foo-Bar.lthn", badInput: "foo.bar.lthn", fn: func(name any) bool { return GetVerifyString(name.(string)) }},
{name: "VerifyBinary", input: []byte("Foo-Bar.lthn"), badInput: []byte("foo.bar.lthn"), fn: func(name any) bool { return VerifyBinary(name.([]byte)) }},
{name: "GetVerifyBinary", input: []byte("Foo-Bar.lthn"), badInput: []byte("foo.bar.lthn"), fn: func(name any) bool { return GetVerifyBinary(name.([]byte)) }},
{name: "VerifyByString", input: "Foo-Bar.lthn", badInput: "foo.bar.lthn", fn: func(name any) bool { return VerifyByString(name.(string)) }},
{name: "GetVerifyByString", input: "Foo-Bar.lthn", badInput: "foo.bar.lthn", fn: func(name any) bool { return GetVerifyByString(name.(string)) }},
{name: "VerifyByBinary", input: []byte("Foo-Bar.lthn"), badInput: []byte("foo.bar.lthn"), fn: func(name any) bool { return VerifyByBinary(name.([]byte)) }},
{name: "GetVerifyByBinary", input: []byte("Foo-Bar.lthn"), badInput: []byte("foo.bar.lthn"), fn: func(name any) bool { return GetVerifyByBinary(name.([]byte)) }},
}
for _, tc := range verifyCases {
if !tc.fn(tc.input) {
t.Fatalf("%s should accept canonical names", tc.name)
}
if tc.fn(tc.badInput) {
t.Fatalf("%s should reject malformed names", tc.name)
}
}
if !VerifyString("Foo-Bar.lthn") {
t.Fatal("VerifyString should accept canonical names")
}
if VerifyString("foo.bar.lthn") {
t.Fatal("VerifyString should reject malformed names")
}
if !VerifyBinary([]byte("Foo-Bar.lthn")) {
t.Fatal("VerifyBinary should accept canonical names")
}
if VerifyBinary([]byte("foo.bar.lthn")) {
t.Fatal("VerifyBinary should reject malformed names")
}
if !VerifyByString("Foo-Bar.lthn") {
t.Fatal("VerifyByString should accept canonical names")
}
if !VerifyByBinary([]byte("Foo-Bar.lthn")) {
t.Fatal("VerifyByBinary should accept canonical names")
}
if !GetHasReserved("RESERVED.lthn") {
t.Fatal("GetHasReserved should report reserved canonical names")
}
if !GetHasReservedString("reserved") {
t.Fatal("GetHasReservedString should report reserved catalog labels")
}
if !GetHasReservedBinary([]byte("reserved")) {
t.Fatal("GetHasReservedBinary should report reserved catalog labels")
}
if !GetHasLocked("NEC.lthn") {
t.Fatal("GetHasLocked should report locked canonical names")
}
if !GetHasLockedString("nec") {
t.Fatal("GetHasLockedString should report locked catalog labels")
}
if !GetHasLockedBinary([]byte("nec")) {
t.Fatal("GetHasLockedBinary should report locked catalog labels")
}
reservedHash, err := covenant.HashString("reserved")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
if !GetHasReservedHash(reservedHash) {
t.Fatal("GetHasReservedHash should report reserved canonical hashes")
}
lockedHash, err := covenant.HashString("nec")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
if !GetHasLockedHash(lockedHash) {
t.Fatal("GetHasLockedHash should report locked canonical hashes")
}
}
func TestPackageBlindAndTypeAliases(t *testing.T) {
var nonce primitives.Hash
for i := range nonce {
nonce[i] = byte(i)
}
got, err := 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)
}
got, err = GetBlind(0x1122334455667788, nonce)
if err != nil {
t.Fatalf("GetBlind returned error: %v", err)
}
if got != want {
t.Fatalf("GetBlind returned %x, want %x", got, want)
}
if got := TypeName(covenant.TypeBid); got != "BID" {
t.Fatalf("TypeName(TypeBid) = %q, want %q", got, "BID")
}
if got := GetTypeName(covenant.CovenantType(99)); got != "UNKNOWN" {
t.Fatalf("GetTypeName(99) = %q, want %q", got, "UNKNOWN")
}
}
func TestPackageRolloutAlias(t *testing.T) {
var hash primitives.Hash
rules := NameRules{
AuctionStart: 100,
RolloutInterval: 7,
}
if !HasRollout(hash, 100, rules) {
t.Fatal("HasRollout should accept the rollout start height")
}
if !GetHasRollout(hash, 100, rules) {
t.Fatal("GetHasRollout should alias HasRollout")
}
}
func TestPackageServiceNameAlias(t *testing.T) {
if got := GetServiceName(); got != ServiceName {
t.Fatalf("GetServiceName() = %q, want %q", got, ServiceName)
}
}
func TestPackageDefaultCatalogAliases(t *testing.T) {
if DefaultReservedCatalog() != ReservedCatalog() {
t.Fatal("DefaultReservedCatalog should alias ReservedCatalog")
}
if GetReservedCatalog() != ReservedCatalog() {
t.Fatal("GetReservedCatalog should alias ReservedCatalog")
}
if GetDefaultReservedCatalog() != DefaultReservedCatalog() {
t.Fatal("GetDefaultReservedCatalog should alias DefaultReservedCatalog")
}
if DefaultLockedCatalog() != LockedCatalog() {
t.Fatal("DefaultLockedCatalog should alias LockedCatalog")
}
if GetLockedCatalog() != LockedCatalog() {
t.Fatal("GetLockedCatalog should alias LockedCatalog")
}
if GetDefaultLockedCatalog() != DefaultLockedCatalog() {
t.Fatal("GetDefaultLockedCatalog should alias DefaultLockedCatalog")
}
}
func TestLookupCatalogNamePreservesDottedLabels(t *testing.T) {
hash := primitives.Hash(sha3.Sum256([]byte("foo.bar")))
byLabel := map[string]string{
"foo.bar": "label",
}
byHash := map[primitives.Hash]string{
hash: "hash",
}
got, ok := lookupCatalogName(
"foo.bar",
func(name string) (string, bool) {
item, ok := byLabel[name]
return item, ok
},
func(name primitives.Hash) (string, bool) {
item, ok := byHash[name]
return item, ok
},
)
if !ok || got != "label" {
t.Fatalf("lookupCatalogName should resolve dotted raw labels, got %q ok=%v", got, ok)
}
got, ok = lookupCatalogName(
"FOO.BAR.LTHN",
func(name string) (string, bool) {
item, ok := byLabel[name]
return item, ok
},
func(name primitives.Hash) (string, bool) {
item, ok := byHash[name]
return item, ok
},
)
if !ok || got != "hash" {
t.Fatalf("lookupCatalogName should fall back to hash lookup for canonical dotted names, got %q ok=%v", got, ok)
}
got, ok = LookupCatalogName(
"foo.bar",
func(name string) (string, bool) {
item, ok := byLabel[name]
return item, ok
},
func(name primitives.Hash) (string, bool) {
item, ok := byHash[name]
return item, ok
},
)
if !ok || got != "label" {
t.Fatalf("LookupCatalogName should preserve dotted labels, got %q ok=%v", got, ok)
}
got, ok = GetLookupCatalogName(
"FOO.BAR.LTHN",
func(name string) (string, bool) {
item, ok := byLabel[name]
return item, ok
},
func(name primitives.Hash) (string, bool) {
item, ok := byHash[name]
return item, ok
},
)
if !ok || got != "hash" {
t.Fatalf("GetLookupCatalogName should alias LookupCatalogName, got %q ok=%v", got, ok)
}
svc := NewService(nil)
gotAny, ok := svc.LookupCatalogName(
"foo.bar",
func(name string) (any, bool) {
item, ok := byLabel[name]
return any(item), ok
},
func(name primitives.Hash) (any, bool) {
item, ok := byHash[name]
return any(item), ok
},
)
if !ok {
t.Fatalf("svc.LookupCatalogName should resolve dotted raw labels, got ok=%v", ok)
}
gotStr, ok := gotAny.(string)
if !ok || gotStr != "label" {
t.Fatalf("svc.LookupCatalogName should resolve dotted raw labels, got %v ok=%v", gotAny, ok)
}
gotAny, ok = svc.GetLookupCatalogName(
"FOO.BAR.LTHN",
func(name string) (any, bool) {
item, ok := byLabel[name]
return any(item), ok
},
func(name primitives.Hash) (any, bool) {
item, ok := byHash[name]
return any(item), ok
},
)
if !ok {
t.Fatalf("svc.GetLookupCatalogName should alias svc.LookupCatalogName, got ok=%v", ok)
}
gotStr, ok = gotAny.(string)
if !ok || gotStr != "hash" {
t.Fatalf("svc.GetLookupCatalogName should alias svc.LookupCatalogName, got %v ok=%v", gotAny, ok)
}
}
func TestLookupCatalogNameNilCallbacks(t *testing.T) {
if got, ok := LookupCatalogName[string]("foo", nil, nil); ok || got != "" {
t.Fatalf("LookupCatalogName with nil callbacks = (%q, %v), want (\"\", false)", got, ok)
}
if got, ok := GetLookupCatalogName[string]("foo", nil, nil); ok || got != "" {
t.Fatalf("GetLookupCatalogName with nil callbacks = (%q, %v), want (\"\", false)", got, ok)
}
svc := NewService(nil)
if got, ok := svc.LookupCatalogName("foo", nil, nil); ok || got != nil {
t.Fatalf("svc.LookupCatalogName with nil callbacks = (%v, %v), want (nil, false)", got, ok)
}
if got, ok := svc.GetLookupCatalogName("foo", nil, nil); ok || got != nil {
t.Fatalf("svc.GetLookupCatalogName with nil callbacks = (%v, %v), want (nil, false)", got, ok)
}
}
func TestPackageTypeTables(t *testing.T) {
if len(Types) == 0 || len(TypesByVal) == 0 {
t.Fatal("type lookup tables should not be empty")
}
if len(GetTypes()) != len(Types) {
t.Fatal("GetTypes should alias Types")
}
if len(GetTypesByVal()) != len(TypesByVal) {
t.Fatal("GetTypesByVal should alias TypesByVal")
}
if got, ok := Types["BID"]; !ok || got != covenant.TypeBid {
t.Fatalf("Types[\"BID\"] = %d, want %d", got, covenant.TypeBid)
}
if got, ok := TypesByVal[covenant.TypeBid]; !ok || got != "BID" {
t.Fatalf("TypesByVal[TypeBid] = %q, want %q", got, "BID")
}
if got, ok := GetTypes()["BID"]; !ok || got != covenant.TypeBid {
t.Fatalf("GetTypes()[\"BID\"] = %d, want %d", got, covenant.TypeBid)
}
if got, ok := GetTypesByVal()[covenant.TypeBid]; !ok || got != "BID" {
t.Fatalf("GetTypesByVal()[TypeBid] = %q, want %q", got, "BID")
}
if CovenantType(TypeBid) != covenant.TypeBid {
t.Fatalf("CovenantType(TypeBid) = %d, want %d", CovenantType(TypeBid), covenant.TypeBid)
}
}
func TestPackageBlacklistAlias(t *testing.T) {
if len(Blacklist) == 0 {
t.Fatal("Blacklist should not be empty")
}
if len(GetBlacklist()) != len(Blacklist) {
t.Fatal("GetBlacklist should alias Blacklist")
}
if _, ok := GetBlacklist()["test"]; !ok {
t.Fatal("GetBlacklist should expose the covenant verifier blacklist")
}
}
func TestPackageVerificationFlagTables(t *testing.T) {
if len(VerificationFlags) != 3 {
t.Fatalf("VerificationFlags has %d entries, want 3", len(VerificationFlags))
}
if len(GetVerificationFlags()) != len(VerificationFlags) {
t.Fatal("GetVerificationFlags should alias VerificationFlags")
}
if len(VerificationFlagsByVal) != len(VerificationFlags) {
t.Fatalf("VerificationFlagsByVal has %d entries, want %d", len(VerificationFlagsByVal), len(VerificationFlags))
}
if len(GetVerificationFlagsByVal()) != len(VerificationFlagsByVal) {
t.Fatal("GetVerificationFlagsByVal should alias VerificationFlagsByVal")
}
if got, ok := VerificationFlags["VERIFY_COVENANTS_LOCKUP"]; !ok || got != covenant.VerifyCovenantsLockup {
t.Fatalf("VerificationFlags[LOCKUP] = %d, want %d", got, covenant.VerifyCovenantsLockup)
}
if got, ok := VerificationFlagsByVal[covenant.VerifyCovenantsNone]; !ok || got != "VERIFY_COVENANTS_NONE" {
t.Fatalf("VerificationFlagsByVal[None] = %q, want %q", got, "VERIFY_COVENANTS_NONE")
}
}
func TestPackageConstantGetters(t *testing.T) {
if GetMaxNameSize() != MaxNameSize {
t.Fatalf("GetMaxNameSize() = %d, want %d", GetMaxNameSize(), MaxNameSize)
}
if GetMaxResourceSize() != MaxResourceSize {
t.Fatalf("GetMaxResourceSize() = %d, want %d", GetMaxResourceSize(), MaxResourceSize)
}
if GetVerifyCovenantsNone() != VerifyCovenantsNone {
t.Fatalf("GetVerifyCovenantsNone() = %d, want %d", GetVerifyCovenantsNone(), VerifyCovenantsNone)
}
if GetVerifyCovenantsHardened() != VerifyCovenantsHardened {
t.Fatalf("GetVerifyCovenantsHardened() = %d, want %d", GetVerifyCovenantsHardened(), VerifyCovenantsHardened)
}
if GetVerifyCovenantsLockup() != VerifyCovenantsLockup {
t.Fatalf("GetVerifyCovenantsLockup() = %d, want %d", GetVerifyCovenantsLockup(), VerifyCovenantsLockup)
}
if GetMandatoryVerifyCovenantFlags() != MandatoryVerifyCovenantFlags {
t.Fatalf("GetMandatoryVerifyCovenantFlags() = %d, want %d", GetMandatoryVerifyCovenantFlags(), MandatoryVerifyCovenantFlags)
}
if GetMaxCovenantSize() != MaxCovenantSize {
t.Fatalf("GetMaxCovenantSize() = %d, want %d", GetMaxCovenantSize(), MaxCovenantSize)
}
if GetCovenantMaxSize() != CovenantMaxSize {
t.Fatalf("GetCovenantMaxSize() = %d, want %d", GetCovenantMaxSize(), CovenantMaxSize)
}
}
func TestPackageNameStateAliases(t *testing.T) {
var state NameState
var delta NameDelta
var rules NameStateRules
if state.Name != nil {
t.Fatal("NameState alias should expose the primitives name state type")
}
if !delta.IsNull() {
t.Fatal("NameDelta alias should expose the primitives name delta type")
}
if NameStateOpening != primitives.NameStateOpening ||
NameStateLocked != primitives.NameStateLocked ||
NameStateBidding != primitives.NameStateBidding ||
NameStateReveal != primitives.NameStateReveal ||
NameStateClosed != primitives.NameStateClosed ||
NameStateRevoked != primitives.NameStateRevoked {
t.Fatal("name state constants should mirror pkg/primitives")
}
if got := NameStateStatus(NameStateClosed).String(); got != "CLOSED" {
t.Fatalf("NameStateStatus.String() = %q, want %q", got, "CLOSED")
}
if rules.TreeInterval != 0 || rules.LockupPeriod != 0 || rules.BiddingPeriod != 0 || rules.RevealPeriod != 0 {
t.Fatal("NameStateRules alias should expose the primitives name state rules type")
}
}
func TestPackageCatalogTypeAliases(t *testing.T) {
var reserved ReservedName
var locked LockedName
var reservedEntry ReservedEntry
var lockedEntry LockedEntry
if reserved.Name != "" || locked.Name != "" {
t.Fatal("catalog name aliases should expose the covenant catalog entry types")
}
if reservedEntry.Value.Name != "" || lockedEntry.Value.Name != "" {
t.Fatal("catalog entry aliases should expose the covenant catalog entry pair types")
}
}
func TestPackageNameSetHelpers(t *testing.T) {
hash, err := HashString("example-name")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
tx := primitives.Transaction{
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 !HasNames(tx, set) {
t.Fatal("HasNames should report a matching name hash")
}
if !GetHasNames(tx, set) {
t.Fatal("GetHasNames should report a matching name hash")
}
RemoveNames(tx, set)
GetRemoveNames(tx, set)
if len(set) != 0 {
t.Fatalf("RemoveNames/GetRemoveNames left %d entries, want 0", len(set))
}
AddNames(tx, set)
GetAddNames(tx, set)
if len(set) != 1 {
t.Fatalf("AddNames/GetAddNames left %d entries, want 1", len(set))
}
}
func TestPackageHasSaneCovenants(t *testing.T) {
hash, err := HashString("example-name")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
valid := 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")}}},
},
}
if !HasSaneCovenants(valid) {
t.Fatal("HasSaneCovenants should accept structurally valid covenants")
}
if !GetHasSaneCovenants(valid) {
t.Fatal("GetHasSaneCovenants should accept structurally valid covenants")
}
}
func TestPackageGrindAndCountHelpers(t *testing.T) {
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 := CountOpens(tx); got != 1 {
t.Fatalf("CountOpens() = %d, want 1", got)
}
if got := GetCountOpens(tx); got != 1 {
t.Fatalf("GetCountOpens() = %d, want 1", got)
}
if got := CountUpdates(tx); got != 5 {
t.Fatalf("CountUpdates() = %d, want 5", got)
}
if got := GetCountUpdates(tx); got != 5 {
t.Fatalf("GetCountUpdates() = %d, want 5", got)
}
if got := CountRenewals(tx); got != 3 {
t.Fatalf("CountRenewals() = %d, want 3", got)
}
if got := GetCountRenewals(tx); got != 3 {
t.Fatalf("GetCountRenewals() = %d, want 3", got)
}
rules := NameRules{
AuctionStart: 100,
RolloutInterval: 7,
}
name, err := GrindName(8, 100, rules)
if err != nil {
t.Fatalf("GrindName returned error: %v", err)
}
alias, err := 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 !HasRollout(hash, 100, rules) {
t.Fatalf("GrindName returned %q that does not satisfy rollout", name)
}
}
func TestPackageRolloutHelpers(t *testing.T) {
var hash primitives.Hash
rules := NameRules{
AuctionStart: 100,
RolloutInterval: 7,
ClaimPeriod: 100,
AlexaLockupPeriod: 250,
}
start, week := GetRollout(hash, rules)
if start != 100 || week != 0 {
t.Fatalf("GetRollout(zero) = (%d, %d), want (100, 0)", start, week)
}
if !HasRollout(hash, 100, rules) {
t.Fatal("HasRollout should accept the rollout start height")
}
hash, err := covenant.HashString("reserved")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
if !IsReserved(hash, 99, rules) {
t.Fatal("IsReserved should report reserved hashes before the claim period")
}
if !GetIsReserved(hash, 99, rules) {
t.Fatal("GetIsReserved should alias IsReserved")
}
rootHash, err := covenant.HashString("nec")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
if !IsLockedUp(rootHash, 100, rules) {
t.Fatal("IsLockedUp should keep root hashes locked after the claim period")
}
if !GetIsLockedUp(rootHash, 100, rules) {
t.Fatal("GetIsLockedUp should alias IsLockedUp")
}
}
func TestPackageCatalogLookupNormalization(t *testing.T) {
reservedCases := []struct {
name string
fn func(any) (covenant.ReservedName, bool)
}{
{name: "GetReserved", fn: GetReserved},
{name: "GetReservedName", fn: GetReservedName},
{name: "GetReservedString", fn: func(name any) (covenant.ReservedName, bool) { return GetReservedString(name.(string)) }},
{name: "GetReservedBinary", fn: func(name any) (covenant.ReservedName, bool) { return GetReservedBinary(name.([]byte)) }},
{name: "GetReservedByString", fn: func(name any) (covenant.ReservedName, bool) { return GetReservedByString(name.(string)) }},
{name: "GetReservedByBinary", fn: func(name any) (covenant.ReservedName, bool) { return GetReservedByBinary(name.([]byte)) }},
}
for _, tc := range reservedCases {
input := any("reserved.lthn.")
if tc.name == "GetReservedBinary" || tc.name == "GetReservedByBinary" {
input = []byte("reserved.lthn.")
}
item, ok := tc.fn(input)
if !ok {
t.Fatalf("%s should resolve canonicalized reserved names", tc.name)
}
if item.Name != "reserved" {
t.Fatalf("%s returned %q, want %q", tc.name, item.Name, "reserved")
}
}
lockedCases := []struct {
name string
fn func(any) (covenant.LockedName, bool)
}{
{name: "GetLocked", fn: GetLocked},
{name: "GetLockedName", fn: GetLockedName},
{name: "GetLockedString", fn: func(name any) (covenant.LockedName, bool) { return GetLockedString(name.(string)) }},
{name: "GetLockedBinary", fn: func(name any) (covenant.LockedName, bool) { return GetLockedBinary(name.([]byte)) }},
{name: "GetLockedByString", fn: func(name any) (covenant.LockedName, bool) { return GetLockedByString(name.(string)) }},
{name: "GetLockedByBinary", fn: func(name any) (covenant.LockedName, bool) { return GetLockedByBinary(name.([]byte)) }},
}
for _, tc := range lockedCases {
input := any("nec.lthn.")
if tc.name == "GetLockedBinary" || tc.name == "GetLockedByBinary" {
input = []byte("nec.lthn.")
}
item, ok := tc.fn(input)
if !ok {
t.Fatalf("%s should resolve canonicalized locked names", tc.name)
}
if item.Name != "nec" {
t.Fatalf("%s returned %q, want %q", tc.name, item.Name, "nec")
}
}
}
type packageTestCoinView struct {
coins map[primitives.Outpoint]primitives.Output
}
func (v packageTestCoinView) GetOutput(prevout primitives.Outpoint) (primitives.Output, bool) {
coin, ok := v.coins[prevout]
return coin, ok
}
func TestPackageVerifyCovenants(t *testing.T) {
hash, err := covenant.HashString("example-name")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
var prevHash primitives.Hash
prevHash[0] = 3
var finalHash primitives.Hash
finalHash[0] = 4
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,
},
},
}
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 := VerifyCovenants(tx, view, 100, covenant.Network{}); got != 0 {
t.Fatalf("VerifyCovenants returned %d, want 0", got)
}
tx.Outputs[0].Address.Hash[0] ^= 1
if got := GetVerifyCovenants(tx, view, 100, covenant.Network{}); got != -1 {
t.Fatalf("GetVerifyCovenants returned %d for an invalid finalize address, want -1", got)
}
}
func TestPackageTypeConstants(t *testing.T) {
cases := []struct {
name string
got covenant.CovenantType
want covenant.CovenantType
}{
{name: "TypeNone", got: TypeNone, want: covenant.TypeNone},
{name: "TypeClaim", got: TypeClaim, want: covenant.TypeClaim},
{name: "TypeOpen", got: TypeOpen, want: covenant.TypeOpen},
{name: "TypeBid", got: TypeBid, want: covenant.TypeBid},
{name: "TypeReveal", got: TypeReveal, want: covenant.TypeReveal},
{name: "TypeRedeem", got: TypeRedeem, want: covenant.TypeRedeem},
{name: "TypeRegister", got: TypeRegister, want: covenant.TypeRegister},
{name: "TypeUpdate", got: TypeUpdate, want: covenant.TypeUpdate},
{name: "TypeRenew", got: TypeRenew, want: covenant.TypeRenew},
{name: "TypeTransfer", got: TypeTransfer, want: covenant.TypeTransfer},
{name: "TypeFinalize", got: TypeFinalize, want: covenant.TypeFinalize},
{name: "TypeRevoke", got: TypeRevoke, want: covenant.TypeRevoke},
}
for _, tc := range cases {
if tc.got != tc.want {
t.Fatalf("%s = %d, want %d", tc.name, tc.got, tc.want)
}
}
if _, ok := Blacklist["test"]; !ok {
t.Fatal("Blacklist should expose the covenant verifier blacklist")
}
if MaxNameSize != 63 {
t.Fatalf("MaxNameSize = %d, want 63", MaxNameSize)
}
if MaxResourceSize != 512 {
t.Fatalf("MaxResourceSize = %d, want 512", MaxResourceSize)
}
if VerifyCovenantsNone != 0 {
t.Fatalf("VerifyCovenantsNone = %d, want 0", VerifyCovenantsNone)
}
if VerifyCovenantsHardened != 1<<0 {
t.Fatalf("VerifyCovenantsHardened = %d, want %d", VerifyCovenantsHardened, 1<<0)
}
if VerifyCovenantsLockup != 1<<1 {
t.Fatalf("VerifyCovenantsLockup = %d, want %d", VerifyCovenantsLockup, 1<<1)
}
if MandatoryVerifyCovenantFlags != VerifyCovenantsNone {
t.Fatalf("MandatoryVerifyCovenantFlags = %d, want %d", MandatoryVerifyCovenantFlags, VerifyCovenantsNone)
}
if MaxCovenantSize != 585 {
t.Fatalf("MaxCovenantSize = %d, want 585", MaxCovenantSize)
}
if CovenantMaxSize != MaxCovenantSize {
t.Fatalf("CovenantMaxSize = %d, want %d", CovenantMaxSize, MaxCovenantSize)
}
}
func TestPackageCovenantTypePredicates(t *testing.T) {
if !IsName(TypeOpen) || !GetIsName(TypeOpen) {
t.Fatal("IsName should report open covenants")
}
if IsName(TypeNone) || GetIsName(TypeNone) {
t.Fatal("IsName should reject non-name covenants")
}
if !IsKnown(TypeBid) || !GetIsKnown(TypeBid) {
t.Fatal("IsKnown should report recognized covenants")
}
if IsKnown(covenant.CovenantType(99)) || GetIsKnown(covenant.CovenantType(99)) {
t.Fatal("IsKnown should reject unknown covenants")
}
if !IsLinked(TypeReveal) || !GetIsLinked(TypeReveal) {
t.Fatal("IsLinked should report linked covenants")
}
if IsLinked(TypeOpen) || GetIsLinked(TypeOpen) {
t.Fatal("IsLinked should reject unlinked covenants")
}
}
func TestPackageCatalogAliases(t *testing.T) {
reservedCatalog := ReservedCatalog()
lockedCatalog := LockedCatalog()
if reservedCatalog == nil {
t.Fatal("ReservedCatalog should return a catalog")
}
if lockedCatalog == nil {
t.Fatal("LockedCatalog should return a catalog")
}
if Reserved != reservedCatalog {
t.Fatal("Reserved should alias ReservedCatalog")
}
if Locked != lockedCatalog {
t.Fatal("Locked should alias LockedCatalog")
}
if GetReservedCatalog() != reservedCatalog {
t.Fatal("GetReservedCatalog should alias ReservedCatalog")
}
if GetLockedCatalog() != lockedCatalog {
t.Fatal("GetLockedCatalog should alias LockedCatalog")
}
if DefaultReservedCatalog() != reservedCatalog {
t.Fatal("DefaultReservedCatalog should match ReservedCatalog")
}
if DefaultLockedCatalog() != lockedCatalog {
t.Fatal("DefaultLockedCatalog should match LockedCatalog")
}
if GetDefaultReservedCatalog() != reservedCatalog {
t.Fatal("GetDefaultReservedCatalog should match ReservedCatalog")
}
if GetDefaultLockedCatalog() != lockedCatalog {
t.Fatal("GetDefaultLockedCatalog should match LockedCatalog")
}
if ReservedSize() != reservedCatalog.Size() || GetReservedSize() != reservedCatalog.Size() {
t.Fatal("ReservedSize aliases should match the catalog size")
}
if LockedSize() != lockedCatalog.Size() || GetLockedSize() != lockedCatalog.Size() {
t.Fatal("LockedSize aliases should match the catalog size")
}
if ReservedPrefixSize() != reservedCatalog.PrefixSize() || GetReservedPrefixSize() != reservedCatalog.PrefixSize() {
t.Fatal("ReservedPrefixSize aliases should match the catalog prefix")
}
if NameValue() != reservedCatalog.NameValue() || GetNameValue() != reservedCatalog.NameValue() {
t.Fatal("NameValue aliases should match the catalog base value")
}
if RootValue() != reservedCatalog.RootValue() || GetRootValue() != reservedCatalog.RootValue() {
t.Fatal("RootValue aliases should match the catalog root value")
}
if TopValue() != reservedCatalog.TopValue() || GetTopValue() != reservedCatalog.TopValue() {
t.Fatal("TopValue aliases should match the catalog top value")
}
if PrefixSize() != reservedCatalog.PrefixSize() || GetPrefixSize() != reservedCatalog.PrefixSize() {
t.Fatal("PrefixSize aliases should match the reserved catalog prefix")
}
if LockedPrefixSize() != lockedCatalog.PrefixSize() || GetLockedPrefixSize() != lockedCatalog.PrefixSize() {
t.Fatal("LockedPrefixSize aliases should match the catalog prefix")
}
if len(ReservedEntries()) == 0 || len(GetReservedEntries()) == 0 {
t.Fatal("ReservedEntries aliases should expose catalog entries")
}
if len(ReservedKeys()) == 0 || len(GetReservedKeys()) == 0 {
t.Fatal("ReservedKeys aliases should expose catalog keys")
}
if len(ReservedValues()) == 0 || len(GetReservedValues()) == 0 {
t.Fatal("ReservedValues aliases should expose catalog values")
}
if len(LockedEntries()) == 0 || len(GetLockedEntries()) == 0 {
t.Fatal("LockedEntries aliases should expose catalog entries")
}
if len(LockedKeys()) == 0 || len(GetLockedKeys()) == 0 {
t.Fatal("LockedKeys aliases should expose catalog keys")
}
if len(LockedValues()) == 0 || len(GetLockedValues()) == 0 {
t.Fatal("LockedValues aliases should expose catalog values")
}
if _, ok := GetReserved("RESERVED.lthn"); !ok {
t.Fatal("GetReserved should find the reserved reference entry")
}
if !HasReserved("reserved.lthn") || !HasReservedByName("reserved") || !HasReservedName("reserved") {
t.Fatal("reserved lookup aliases should report reserved names")
}
if _, ok := GetReservedByString("RESERVED"); !ok {
t.Fatal("GetReservedByString should find the reserved catalog label")
}
if _, ok := GetReservedByBinary([]byte("reserved")); !ok {
t.Fatal("GetReservedByBinary should find the reserved catalog label")
}
if _, ok := GetLocked("NEC.lthn"); !ok {
t.Fatal("GetLocked should find the locked reference entry")
}
if !HasLocked("nec.lthn") || !HasLockedByName("nec") || !HasLockedName("nec") {
t.Fatal("locked lookup aliases should report locked names")
}
if _, ok := GetLockedByString("NEC"); !ok {
t.Fatal("GetLockedByString should find the locked catalog label")
}
if _, ok := GetLockedByBinary([]byte("nec")); !ok {
t.Fatal("GetLockedByBinary should find the locked catalog label")
}
if _, ok := GetReservedByHash(reservedCatalog.Keys()[0]); !ok {
t.Fatal("GetReservedByHash should return a reserved entry")
}
if !HasReservedHash(reservedCatalog.Keys()[0]) || !HasReservedByHash(reservedCatalog.Keys()[0]) {
t.Fatal("HasReservedHash aliases should report reserved entries")
}
if _, ok := GetLockedByHash(lockedCatalog.Keys()[0]); !ok {
t.Fatal("GetLockedByHash should return a locked entry")
}
if !HasLockedHash(lockedCatalog.Keys()[0]) || !HasLockedByHash(lockedCatalog.Keys()[0]) {
t.Fatal("HasLockedHash aliases should report locked entries")
}
}