[agent/codex:gpt-5.4-mini] Read the JS reference source at docs/js-covenants/ and docs/... #1
69 changed files with 2229 additions and 16 deletions
13
internal/nameutil/name_example_test.go
Normal file
13
internal/nameutil/name_example_test.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package nameutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ExampleCanonicalize() {
|
||||
got, _ := Canonicalize("Example.LTHN.")
|
||||
fmt.Println(got)
|
||||
// Output: example
|
||||
}
|
||||
57
internal/nameutil/name_test.go
Normal file
57
internal/nameutil/name_test.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package nameutil
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNameUtil_Function_Good(t *testing.T) {
|
||||
got, ok := Canonicalize("Example.LTHN.")
|
||||
if !ok {
|
||||
t.Fatal("Canonicalize should accept canonical names")
|
||||
}
|
||||
|
||||
if got != "example" {
|
||||
t.Fatalf("Canonicalize() = %q, want %q", got, "example")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameUtil_Function_Bad(t *testing.T) {
|
||||
if got, ok := Canonicalize(42); ok || got != "" {
|
||||
t.Fatalf("Canonicalize() = %q, %v, want empty false", got, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameUtil_Function_Ugly(t *testing.T) {
|
||||
got, ok := CatalogLabel([]byte("MiXeD"))
|
||||
if !ok {
|
||||
t.Fatal("CatalogLabel should accept raw byte labels")
|
||||
}
|
||||
|
||||
if got != "MiXeD" {
|
||||
t.Fatalf("CatalogLabel() = %q, want %q", got, "MiXeD")
|
||||
}
|
||||
}
|
||||
|
||||
func TestName_Function_Good(t *testing.T) {
|
||||
got, ok := Canonicalize("Example.lthn.")
|
||||
if !ok || got != "example" {
|
||||
t.Fatalf("Canonicalize() = %q, %v, want %q, true", got, ok, "example")
|
||||
}
|
||||
}
|
||||
|
||||
func TestName_Function_Bad(t *testing.T) {
|
||||
if got, ok := Canonicalize(42); ok || got != "" {
|
||||
t.Fatalf("Canonicalize() = %q, %v, want empty false", got, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestName_Function_Ugly(t *testing.T) {
|
||||
got, ok := CatalogLabel([]byte("MiXeD"))
|
||||
if !ok {
|
||||
t.Fatal("CatalogLabel should accept raw byte labels")
|
||||
}
|
||||
|
||||
if got != "MiXeD" {
|
||||
t.Fatalf("CatalogLabel() = %q, want %q", got, "MiXeD")
|
||||
}
|
||||
}
|
||||
27
lns.go
27
lns.go
|
|
@ -1219,6 +1219,12 @@ type NameUndoEntry = primitives.NameUndoEntry
|
|||
// Claim mirrors the raw ownership-proof claim wrapper from pkg/primitives.
|
||||
type Claim = primitives.Claim
|
||||
|
||||
// InvItem mirrors the inventory item wrapper from pkg/primitives.
|
||||
type InvItem = primitives.InvItem
|
||||
|
||||
// InvType mirrors the inventory item type tag from pkg/primitives.
|
||||
type InvType = primitives.InvType
|
||||
|
||||
// NameStateJSON mirrors the primitive JSON representation for a name state.
|
||||
type NameStateJSON = primitives.NameStateJSON
|
||||
|
||||
|
|
@ -1254,6 +1260,17 @@ const (
|
|||
NameStateRevoked = primitives.NameStateRevoked
|
||||
)
|
||||
|
||||
// Inventory item constants mirror pkg/primitives so callers can keep using the
|
||||
// top-level lns package for common network wrappers.
|
||||
const (
|
||||
InvTypeTX = primitives.InvTypeTX
|
||||
InvTypeBlock = primitives.InvTypeBlock
|
||||
InvTypeFilteredBlock = primitives.InvTypeFilteredBlock
|
||||
InvTypeCompactBlock = primitives.InvTypeCompactBlock
|
||||
InvTypeClaim = primitives.InvTypeClaim
|
||||
InvTypeAirdrop = primitives.InvTypeAirdrop
|
||||
)
|
||||
|
||||
// NewClaim constructs an empty claim wrapper.
|
||||
func NewClaim() *Claim {
|
||||
return primitives.NewClaim()
|
||||
|
|
@ -1264,6 +1281,16 @@ func GetNewClaim() *Claim {
|
|||
return NewClaim()
|
||||
}
|
||||
|
||||
// NewInvItem constructs an inventory item wrapper.
|
||||
func NewInvItem(t InvType, hash primitives.Hash) *InvItem {
|
||||
return primitives.NewInvItem(t, hash)
|
||||
}
|
||||
|
||||
// GetNewInvItem is an alias for NewInvItem.
|
||||
func GetNewInvItem(t InvType, hash primitives.Hash) *InvItem {
|
||||
return NewInvItem(t, hash)
|
||||
}
|
||||
|
||||
// NewOutpoint returns the null outpoint used by coinbase inputs and empty
|
||||
// name-state owner references.
|
||||
func NewOutpoint() Outpoint {
|
||||
|
|
|
|||
10
lns_example_test.go
Normal file
10
lns_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package lns
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleGetServiceName() {
|
||||
fmt.Println(GetServiceName())
|
||||
// Output: lns
|
||||
}
|
||||
35
lns_test.go
35
lns_test.go
|
|
@ -2172,3 +2172,38 @@ func TestServiceVerifyCovenants(t *testing.T) {
|
|||
t.Fatalf("GetVerifyCovenants returned %d for an invalid finalize address, want -1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLns_Function_Good(t *testing.T) {
|
||||
if got := GetServiceName(); got != ServiceName {
|
||||
t.Fatalf("GetServiceName() = %q, want %q", got, ServiceName)
|
||||
}
|
||||
|
||||
if svc := GetNewService(nil); svc == nil {
|
||||
t.Fatal("GetNewService should return a service")
|
||||
}
|
||||
|
||||
if svc := GetNewServiceWithOptions(nil); svc == nil {
|
||||
t.Fatal("GetNewServiceWithOptions should return a service")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLns_Function_Bad(t *testing.T) {
|
||||
if got := Register(nil); got.OK {
|
||||
t.Fatalf("Register(nil) = %#v, want failure", got)
|
||||
}
|
||||
|
||||
if got := GetRegister(nil); got.OK {
|
||||
t.Fatalf("GetRegister(nil) = %#v, want failure", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLns_Function_Ugly(t *testing.T) {
|
||||
svc := NewServiceWithOptions(nil)
|
||||
if svc == nil {
|
||||
t.Fatal("NewServiceWithOptions(nil) should return a service")
|
||||
}
|
||||
|
||||
if svc.reservedCatalogOverride != nil || svc.lockedCatalogOverride != nil {
|
||||
t.Fatalf("nil options should not populate catalog overrides: %#v", svc)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
pkg/covenant/airdrop_proof_example_test.go
Normal file
12
pkg/covenant/airdrop_proof_example_test.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampletestAirdropProof() {
|
||||
proof := testAirdropProof()
|
||||
fmt.Println(proof.isSane())
|
||||
|
||||
// Output: true
|
||||
}
|
||||
65
pkg/covenant/airdrop_proof_test.go
Normal file
65
pkg/covenant/airdrop_proof_test.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testAirdropProof() *airdropProof {
|
||||
key := make([]byte, 1+1+1+2+8+1)
|
||||
key[0] = airdropKeyAddress
|
||||
key[1] = 1
|
||||
key[2] = 2
|
||||
key[3] = 0xaa
|
||||
key[4] = 0xbb
|
||||
binary.LittleEndian.PutUint64(key[5:13], 10)
|
||||
key[13] = 0xff
|
||||
|
||||
return &airdropProof{
|
||||
index: 0,
|
||||
proof: nil,
|
||||
subindex: 0,
|
||||
subproof: nil,
|
||||
key: key,
|
||||
version: 1,
|
||||
address: []byte{0x01, 0x02},
|
||||
fee: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAirdropProof_Function_Good(t *testing.T) {
|
||||
proof := testAirdropProof()
|
||||
if !proof.isSane() {
|
||||
t.Fatal("expected proof to be sane")
|
||||
}
|
||||
|
||||
key, ok := proof.getKey()
|
||||
if !ok {
|
||||
t.Fatal("expected proof key to decode")
|
||||
}
|
||||
|
||||
if key.value != 10 || key.version != 1 {
|
||||
t.Fatalf("unexpected key decode: %#v", key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAirdropProof_Function_Bad(t *testing.T) {
|
||||
proof := testAirdropProof()
|
||||
proof.version = 32
|
||||
|
||||
if proof.isSane() {
|
||||
t.Fatal("expected invalid version to fail sanity checks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAirdropProof_Function_Ugly(t *testing.T) {
|
||||
proof := testAirdropProof()
|
||||
proof.key = proof.key[:len(proof.key)-1]
|
||||
|
||||
if proof.isSane() {
|
||||
t.Fatal("expected truncated key to fail sanity checks")
|
||||
}
|
||||
}
|
||||
|
||||
15
pkg/covenant/blind_example_test.go
Normal file
15
pkg/covenant/blind_example_test.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"dappco.re/go/lns/pkg/primitives"
|
||||
)
|
||||
|
||||
func ExampleGetBlind() {
|
||||
_, err := GetBlind(1000, primitives.Hash{})
|
||||
fmt.Println(err == nil)
|
||||
// Output: true
|
||||
}
|
||||
|
|
@ -52,3 +52,50 @@ func TestGetBlind(t *testing.T) {
|
|||
t.Fatalf("GetBlind returned %x, want %x", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlind_Function_Good(t *testing.T) {
|
||||
var nonce primitives.Hash
|
||||
for i := range nonce {
|
||||
nonce[i] = byte(i)
|
||||
}
|
||||
|
||||
got, err := Blind(0, nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("Blind returned error: %v", err)
|
||||
}
|
||||
|
||||
want, err := GetBlind(0, nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlind returned error: %v", err)
|
||||
}
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("Blind() = %x, want %x", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlind_Function_Bad(t *testing.T) {
|
||||
var nonce primitives.Hash
|
||||
got, err := Blind(1, nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("Blind returned error: %v", err)
|
||||
}
|
||||
|
||||
if got == (primitives.Hash{}) {
|
||||
t.Fatal("Blind should return a non-zero commitment for non-zero value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlind_Function_Ugly(t *testing.T) {
|
||||
var nonce primitives.Hash
|
||||
nonce[0] = 1
|
||||
|
||||
got, err := Blind(0, nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("Blind returned error: %v", err)
|
||||
}
|
||||
|
||||
if got == (primitives.Hash{}) {
|
||||
t.Fatal("Blind should produce a digest for arbitrary nonces")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
pkg/covenant/covenant_example_test.go
Normal file
10
pkg/covenant/covenant_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleTypeName() {
|
||||
fmt.Println(TypeName(TypeBid))
|
||||
// Output: BID
|
||||
}
|
||||
|
|
@ -42,3 +42,36 @@ func TestCovenantTypePredicates(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCovenant_Function_Good(t *testing.T) {
|
||||
if got := GetTypeName(TypeRegister); got != "REGISTER" {
|
||||
t.Fatalf("GetTypeName(TypeRegister) = %q, want %q", got, "REGISTER")
|
||||
}
|
||||
|
||||
if got := GetTypes()["BID"]; got != TypeBid {
|
||||
t.Fatalf("GetTypes()[BID] = %d, want %d", got, TypeBid)
|
||||
}
|
||||
|
||||
if got := TypeFinalize.String(); got != "FINALIZE" {
|
||||
t.Fatalf("String() = %q, want %q", got, "FINALIZE")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovenant_Function_Bad(t *testing.T) {
|
||||
if got := TypeName(CovenantType(99)); got != "UNKNOWN" {
|
||||
t.Fatalf("TypeName(99) = %q, want %q", got, "UNKNOWN")
|
||||
}
|
||||
|
||||
if TypeNone.IsName() || TypeNone.IsLinked() || !TypeNone.IsKnown() {
|
||||
t.Fatal("TypeNone predicate invariants failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovenant_Function_Ugly(t *testing.T) {
|
||||
if got := GetTypesByVal()[TypeRevoke]; got != "REVOKE" {
|
||||
t.Fatalf("GetTypesByVal()[TypeRevoke] = %q, want %q", got, "REVOKE")
|
||||
}
|
||||
|
||||
if !TypeReveal.IsLinked() || !TypeRevoke.IsLinked() {
|
||||
t.Fatal("linked covenant boundary checks failed")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
pkg/covenant/locked_lookup_example_test.go
Normal file
11
pkg/covenant/locked_lookup_example_test.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleGetLockedName() {
|
||||
item, _ := GetLockedName("NEC")
|
||||
fmt.Println(item.Name)
|
||||
// Output: nec
|
||||
}
|
||||
|
|
@ -353,3 +353,38 @@ func TestLockedCatalogGetByNameRejectsNonASCII(t *testing.T) {
|
|||
t.Fatal("GetByName should reject non-ASCII labels")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLockedLookup_Function_Good(t *testing.T) {
|
||||
item, ok := GetLockedName("NEC")
|
||||
if !ok {
|
||||
t.Fatal("GetLockedName should find the locked reference entry")
|
||||
}
|
||||
|
||||
if item.Name != "nec" {
|
||||
t.Fatalf("item.Name = %q, want %q", item.Name, "nec")
|
||||
}
|
||||
|
||||
if !HasLockedHash(item.Hash) {
|
||||
t.Fatal("HasLockedHash should report the locked reference entry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLockedLookup_Function_Bad(t *testing.T) {
|
||||
if HasLockedName("does-not-exist") {
|
||||
t.Fatal("unknown names should not be reported as locked")
|
||||
}
|
||||
|
||||
if _, ok := GetLockedName("does-not-exist"); ok {
|
||||
t.Fatal("GetLockedName should return false for unknown names")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLockedLookup_Function_Ugly(t *testing.T) {
|
||||
if !GetHasLockedName("nec.lthn") {
|
||||
t.Fatal("GetHasLockedName should accept canonical .lthn names")
|
||||
}
|
||||
|
||||
if _, ok := GetLockedByBinary([]byte("NEC")); !ok {
|
||||
t.Fatal("GetLockedByBinary should find the locked reference entry")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
pkg/covenant/name_example_test.go
Normal file
10
pkg/covenant/name_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleVerifyString() {
|
||||
fmt.Println(VerifyString("foo"))
|
||||
// Output: true
|
||||
}
|
||||
11
pkg/covenant/name_lookup_example_test.go
Normal file
11
pkg/covenant/name_lookup_example_test.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleasciiLowerName() {
|
||||
got, _ := asciiLowerName("NEC.lthn.")
|
||||
fmt.Println(got)
|
||||
// Output: nec
|
||||
}
|
||||
34
pkg/covenant/name_lookup_test.go
Normal file
34
pkg/covenant/name_lookup_test.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNameLookup_Function_Good(t *testing.T) {
|
||||
got, ok := asciiLowerName("NEC.lthn.")
|
||||
if !ok {
|
||||
t.Fatal("asciiLowerName should accept canonical labels")
|
||||
}
|
||||
|
||||
if got != "nec" {
|
||||
t.Fatalf("asciiLowerName() = %q, want %q", got, "nec")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameLookup_Function_Bad(t *testing.T) {
|
||||
if got, ok := asciiLowerName(""); ok || got != "" {
|
||||
t.Fatalf("asciiLowerName() = %q, %v, want empty false", got, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameLookup_Function_Ugly(t *testing.T) {
|
||||
long := make([]byte, maxNameSize+1)
|
||||
for i := range long {
|
||||
long[i] = 'a'
|
||||
}
|
||||
|
||||
if got, ok := asciiLowerName(string(long)); ok || got != "" {
|
||||
t.Fatalf("asciiLowerName() = %q, %v, want empty false", got, ok)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -363,3 +363,41 @@ func TestHashRejectsInvalidName(t *testing.T) {
|
|||
t.Fatal("HashName should reject unsupported input types")
|
||||
}
|
||||
}
|
||||
|
||||
func TestName_Function_Good(t *testing.T) {
|
||||
if !VerifyString("example-1") {
|
||||
t.Fatal("VerifyString should accept a valid non-blacklisted name")
|
||||
}
|
||||
|
||||
if _, err := HashString("example-1"); err != nil {
|
||||
t.Fatalf("HashString returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestName_Function_Bad(t *testing.T) {
|
||||
if VerifyString("Example") {
|
||||
t.Fatal("VerifyString should reject uppercase labels")
|
||||
}
|
||||
|
||||
if _, err := HashString("Example"); err == nil {
|
||||
t.Fatal("HashString should reject invalid names")
|
||||
}
|
||||
|
||||
if VerifyName(123) {
|
||||
t.Fatal("VerifyName should reject unsupported input types")
|
||||
}
|
||||
}
|
||||
|
||||
func TestName_Function_Ugly(t *testing.T) {
|
||||
if VerifyBinary([]byte("abc-")) {
|
||||
t.Fatal("VerifyBinary should reject trailing hyphens")
|
||||
}
|
||||
|
||||
if VerifyBinary([]byte("ab\x80")) {
|
||||
t.Fatal("VerifyBinary should reject non-ASCII input")
|
||||
}
|
||||
|
||||
if _, ok := GetBlacklist()["example"]; !ok {
|
||||
t.Fatal("GetBlacklist should expose the blacklist map")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
pkg/covenant/names_example_test.go
Normal file
16
pkg/covenant/names_example_test.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"dappco.re/go/lns/pkg/primitives"
|
||||
)
|
||||
|
||||
func ExampleHasNames() {
|
||||
hash := testNameHash()
|
||||
tx := testNameTx(TypeOpen, hash)
|
||||
fmt.Println(HasNames(tx, map[primitives.Hash]struct{}{hash: {}}))
|
||||
// Output: true
|
||||
}
|
||||
63
pkg/covenant/names_test.go
Normal file
63
pkg/covenant/names_test.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/lns/pkg/primitives"
|
||||
)
|
||||
|
||||
func testNameHash() primitives.Hash {
|
||||
var hash primitives.Hash
|
||||
hash[0] = 0x42
|
||||
return hash
|
||||
}
|
||||
|
||||
func testNameTx(covType CovenantType, hash primitives.Hash) primitives.Transaction {
|
||||
return primitives.Transaction{
|
||||
Outputs: []primitives.Output{{
|
||||
Covenant: primitives.Covenant{
|
||||
Type: uint8(covType),
|
||||
Items: [][]byte{hash[:]},
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func TestNames_Function_Good(t *testing.T) {
|
||||
hash := testNameHash()
|
||||
set := map[primitives.Hash]struct{}{hash: {}}
|
||||
tx := testNameTx(TypeOpen, hash)
|
||||
|
||||
if !HasNames(tx, set) {
|
||||
t.Fatal("expected transaction to match the set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNames_Function_Bad(t *testing.T) {
|
||||
hash := testNameHash()
|
||||
set := map[primitives.Hash]struct{}{hash: {}}
|
||||
tx := testNameTx(TypeNone, hash)
|
||||
|
||||
if HasNames(tx, set) {
|
||||
t.Fatal("expected plain transfers to be ignored")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNames_Function_Ugly(t *testing.T) {
|
||||
hash := testNameHash()
|
||||
tx := testNameTx(TypeRenew, hash)
|
||||
set := map[primitives.Hash]struct{}{}
|
||||
|
||||
AddNames(tx, set)
|
||||
if _, ok := set[hash]; !ok {
|
||||
t.Fatal("AddNames should record name hashes")
|
||||
}
|
||||
|
||||
RemoveNames(tx, set)
|
||||
if _, ok := set[hash]; ok {
|
||||
t.Fatal("RemoveNames should delete name hashes")
|
||||
}
|
||||
}
|
||||
|
||||
11
pkg/covenant/reserved_lookup_example_test.go
Normal file
11
pkg/covenant/reserved_lookup_example_test.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleGetReservedName() {
|
||||
item, _ := GetReservedName("RESERVED")
|
||||
fmt.Println(item.Name)
|
||||
// Output: reserved
|
||||
}
|
||||
15
pkg/covenant/rules_example_test.go
Normal file
15
pkg/covenant/rules_example_test.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"dappco.re/go/lns/pkg/primitives"
|
||||
)
|
||||
|
||||
func ExampleGetRollout() {
|
||||
start, _ := GetRollout(primitives.Hash{}, NameRules{NoRollout: true})
|
||||
fmt.Println(start)
|
||||
// Output: 0
|
||||
}
|
||||
14
pkg/covenant/rules_extra_example_test.go
Normal file
14
pkg/covenant/rules_extra_example_test.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"dappco.re/go/lns/pkg/primitives"
|
||||
)
|
||||
|
||||
func ExampleCountOpens() {
|
||||
fmt.Println(CountOpens(primitives.Transaction{}))
|
||||
// Output: 0
|
||||
}
|
||||
|
|
@ -781,3 +781,44 @@ func TestCoinbaseClaimConjureOverflow(t *testing.T) {
|
|||
t.Fatalf("VerifyCovenants returned %d for overflowing coinbase claim outputs, want -1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRulesExtra_Function_Good(t *testing.T) {
|
||||
tx := primitives.Transaction{
|
||||
Outputs: []primitives.Output{
|
||||
{Covenant: primitives.Covenant{Type: uint8(TypeOpen)}},
|
||||
{Covenant: primitives.Covenant{Type: uint8(TypeUpdate)}},
|
||||
},
|
||||
}
|
||||
|
||||
if got := CountOpens(tx); got != 1 {
|
||||
t.Fatalf("CountOpens() = %d, want 1", got)
|
||||
}
|
||||
|
||||
if got := GetCountUpdates(tx); got != 2 {
|
||||
t.Fatalf("GetCountUpdates() = %d, want 2", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRulesExtra_Function_Bad(t *testing.T) {
|
||||
if _, err := GrindName(0, 0, NameRules{}); err == nil {
|
||||
t.Fatal("GrindName should reject zero-sized names")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRulesExtra_Function_Ugly(t *testing.T) {
|
||||
tx := primitives.Transaction{
|
||||
Outputs: []primitives.Output{
|
||||
{Covenant: primitives.Covenant{Type: uint8(TypeClaim)}},
|
||||
{Covenant: primitives.Covenant{Type: uint8(TypeRenew)}},
|
||||
{Covenant: primitives.Covenant{Type: uint8(TypeFinalize)}},
|
||||
},
|
||||
}
|
||||
|
||||
if got := CountRenewals(tx); got != 2 {
|
||||
t.Fatalf("CountRenewals() = %d, want 2", got)
|
||||
}
|
||||
|
||||
if got := GetCountRenewals(tx); got != 2 {
|
||||
t.Fatalf("GetCountRenewals() = %d, want 2", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
pkg/covenant/rules_test.go
Normal file
42
pkg/covenant/rules_test.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/lns/pkg/primitives"
|
||||
)
|
||||
|
||||
func TestRules_Function_Good(t *testing.T) {
|
||||
rules := NameRules{NoRollout: true}
|
||||
start, week := GetRollout(primitives.Hash{}, rules)
|
||||
if start != 0 || week != 0 {
|
||||
t.Fatalf("GetRollout() = (%d, %d), want zeros", start, week)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRules_Function_Bad(t *testing.T) {
|
||||
rules := NameRules{NoReserved: true, ClaimPeriod: 100}
|
||||
hash, err := HashString("reserved")
|
||||
if err != nil {
|
||||
t.Fatalf("HashString returned error: %v", err)
|
||||
}
|
||||
|
||||
if IsReserved(hash, 1, rules) {
|
||||
t.Fatal("expected NoReserved to disable reserved checks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRules_Function_Ugly(t *testing.T) {
|
||||
hash, err := HashString("nec")
|
||||
if err != nil {
|
||||
t.Fatalf("HashString returned error: %v", err)
|
||||
}
|
||||
|
||||
rules := NameRules{ClaimPeriod: 1, AlexaLockupPeriod: 100}
|
||||
if !IsLockedUp(hash, 1, rules) {
|
||||
t.Fatal("expected locked catalog names to remain locked")
|
||||
}
|
||||
}
|
||||
|
||||
23
pkg/covenant/verify_example_test.go
Normal file
23
pkg/covenant/verify_example_test.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"dappco.re/go/lns/pkg/primitives"
|
||||
)
|
||||
|
||||
func ExampleVerifyCovenants() {
|
||||
var prev primitives.Hash
|
||||
prev[0] = 1
|
||||
|
||||
tx := primitives.Transaction{
|
||||
Inputs: []primitives.Input{{Prevout: primitives.Outpoint{TxHash: prev, Index: 0}}},
|
||||
Outputs: []primitives.Output{{Covenant: primitives.Covenant{Type: uint8(TypeNone)}}},
|
||||
}
|
||||
|
||||
view := verifyCoinView{output: primitives.Output{Covenant: primitives.Covenant{Type: uint8(TypeNone)}}}
|
||||
fmt.Println(VerifyCovenants(tx, view, 0, Network{}))
|
||||
// Output: 0
|
||||
}
|
||||
70
pkg/covenant/verify_test.go
Normal file
70
pkg/covenant/verify_test.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package covenant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/lns/pkg/primitives"
|
||||
)
|
||||
|
||||
type verifyCoinView struct {
|
||||
output primitives.Output
|
||||
}
|
||||
|
||||
func (v verifyCoinView) GetOutput(primitives.Outpoint) (primitives.Output, bool) {
|
||||
return v.output, true
|
||||
}
|
||||
|
||||
func TestVerify_Function_Good(t *testing.T) {
|
||||
var prev primitives.Hash
|
||||
prev[0] = 1
|
||||
prevout := primitives.Outpoint{TxHash: prev, Index: 0}
|
||||
|
||||
tx := primitives.Transaction{
|
||||
Inputs: []primitives.Input{{
|
||||
Prevout: prevout,
|
||||
}},
|
||||
Outputs: []primitives.Output{{
|
||||
Covenant: primitives.Covenant{Type: uint8(TypeNone)},
|
||||
}},
|
||||
}
|
||||
|
||||
view := verifyCoinView{output: primitives.Output{
|
||||
Covenant: primitives.Covenant{Type: uint8(TypeNone)},
|
||||
}}
|
||||
|
||||
if got := VerifyCovenants(tx, view, 0, Network{}); got != 0 {
|
||||
t.Fatalf("VerifyCovenants() = %d, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify_Function_Bad(t *testing.T) {
|
||||
tx := primitives.Transaction{}
|
||||
if got := VerifyCovenants(tx, nil, 0, Network{}); got != -1 {
|
||||
t.Fatalf("VerifyCovenants() = %d, want -1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify_Function_Ugly(t *testing.T) {
|
||||
var prev primitives.Hash
|
||||
prev[0] = 1
|
||||
prevout := primitives.Outpoint{TxHash: prev, Index: 0}
|
||||
|
||||
tx := primitives.Transaction{
|
||||
Inputs: []primitives.Input{{
|
||||
Prevout: prevout,
|
||||
}},
|
||||
Outputs: []primitives.Output{{
|
||||
Covenant: primitives.Covenant{Type: uint8(TypeNone)},
|
||||
}},
|
||||
}
|
||||
|
||||
view := verifyCoinView{output: primitives.Output{
|
||||
Covenant: primitives.Covenant{Type: uint8(TypeBid)},
|
||||
}}
|
||||
|
||||
if got := VerifyCovenants(tx, view, 0, Network{}); got != -1 {
|
||||
t.Fatalf("VerifyCovenants() = %d, want -1", got)
|
||||
}
|
||||
}
|
||||
|
|
@ -90,6 +90,13 @@ var HSTypes = map[string]HSType{
|
|||
"TXT": HSTypeTXT,
|
||||
}
|
||||
|
||||
// hsTypes mirrors the JS export name used by the DNS reference helpers.
|
||||
//
|
||||
// The Go package keeps the canonical HSTypes identifier as well, but the
|
||||
// lowercase alias makes the reference shape available to same-package callers
|
||||
// and test coverage.
|
||||
var hsTypes = HSTypes
|
||||
|
||||
// HSTypesByVal mirrors the JS hsTypesByVal reverse lookup table.
|
||||
var HSTypesByVal = map[HSType]string{
|
||||
HSTypeDS: "DS",
|
||||
|
|
@ -101,6 +108,9 @@ var HSTypesByVal = map[HSType]string{
|
|||
HSTypeTXT: "TXT",
|
||||
}
|
||||
|
||||
// hsTypesByVal mirrors the JS export name used by the DNS reference helpers.
|
||||
var hsTypesByVal = HSTypesByVal
|
||||
|
||||
// GetHSTypes returns the DNS record-type lookup table.
|
||||
func GetHSTypes() map[string]HSType {
|
||||
return HSTypes
|
||||
|
|
|
|||
10
pkg/dns/common_example_test.go
Normal file
10
pkg/dns/common_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package dns
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleGetDefaultTTL() {
|
||||
fmt.Println(GetDefaultTTL())
|
||||
// Output: 21600
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ package dns
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -77,6 +78,14 @@ func TestHSTypesTables(t *testing.T) {
|
|||
t.Fatal("GetHSTypesByVal should alias HSTypesByVal")
|
||||
}
|
||||
|
||||
if reflect.ValueOf(hsTypes).Pointer() != reflect.ValueOf(HSTypes).Pointer() {
|
||||
t.Fatal("hsTypes should alias HSTypes")
|
||||
}
|
||||
|
||||
if reflect.ValueOf(hsTypesByVal).Pointer() != reflect.ValueOf(HSTypesByVal).Pointer() {
|
||||
t.Fatal("hsTypesByVal should alias HSTypesByVal")
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
got HSType
|
||||
|
|
@ -109,3 +118,33 @@ func TestHSTypesTables(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommon_Function_Good(t *testing.T) {
|
||||
if got := GetDefaultTTL(); got != DEFAULT_TTL {
|
||||
t.Fatalf("GetDefaultTTL() = %d, want %d", got, DEFAULT_TTL)
|
||||
}
|
||||
|
||||
if len(GetDummy()) != 0 {
|
||||
t.Fatalf("GetDummy() length = %d, want 0", len(GetDummy()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommon_Function_Bad(t *testing.T) {
|
||||
if _, ok := GetHSTypes()["NOPE"]; ok {
|
||||
t.Fatal("GetHSTypes should not contain unknown keys")
|
||||
}
|
||||
|
||||
if _, ok := GetHSTypesByVal()[HSType(99)]; ok {
|
||||
t.Fatal("GetHSTypesByVal should not contain unknown values")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommon_Function_Ugly(t *testing.T) {
|
||||
if !bytes.Equal(GetTypeMapAAAA(), TYPE_MAP_AAAA) {
|
||||
t.Fatal("GetTypeMapAAAA should alias TYPE_MAP_AAAA")
|
||||
}
|
||||
|
||||
if reflect.ValueOf(GetHSTypes()).Pointer() != reflect.ValueOf(HSTypes).Pointer() {
|
||||
t.Fatal("GetHSTypes should alias HSTypes")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,26 @@ type NSECRecord struct {
|
|||
TTL int
|
||||
}
|
||||
|
||||
// GetName returns the owner name.
|
||||
func (r NSECRecord) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
// GetNextDomain returns the canonical successor name.
|
||||
func (r NSECRecord) GetNextDomain() string {
|
||||
return r.NextDomain
|
||||
}
|
||||
|
||||
// GetTypeBitmap returns a copy of the NSEC type bitmap.
|
||||
func (r NSECRecord) GetTypeBitmap() []byte {
|
||||
return append([]byte(nil), r.TypeBitmap...)
|
||||
}
|
||||
|
||||
// GetTTL returns the record TTL.
|
||||
func (r NSECRecord) GetTTL() int {
|
||||
return r.TTL
|
||||
}
|
||||
|
||||
// Create constructs a reference NSEC record container.
|
||||
//
|
||||
// name := Create(".", NextName("."), TYPE_MAP_ROOT)
|
||||
|
|
@ -56,11 +76,12 @@ func GetNextName(tld string) string {
|
|||
// PrevName returns the canonical predecessor for a top-level domain name.
|
||||
//
|
||||
// The helper lowercases and trims any trailing dot before applying the
|
||||
// reference ordering logic.
|
||||
// reference ordering logic. Empty trimmed names are invalid, matching the
|
||||
// reference helper's assertion behavior.
|
||||
func PrevName(tld string) string {
|
||||
tld = trimFQDN(strings.ToLower(tld))
|
||||
if len(tld) == 0 {
|
||||
return "."
|
||||
panic("dns.PrevName: invalid top-level domain")
|
||||
}
|
||||
|
||||
last := tld[len(tld)-1] - 1
|
||||
|
|
|
|||
10
pkg/dns/nsec_example_test.go
Normal file
10
pkg/dns/nsec_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package dns
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNextName() {
|
||||
fmt.Printf("%q\n", NextName("example"))
|
||||
// Output: "example\x00."
|
||||
}
|
||||
|
|
@ -33,10 +33,32 @@ func TestNSECRecordHelpers(t *testing.T) {
|
|||
t.Fatalf("Create bitmap = %x, want %x", rr.TypeBitmap, bitmap)
|
||||
}
|
||||
|
||||
if rr.GetName() != rr.Name {
|
||||
t.Fatalf("GetName() = %q, want %q", rr.GetName(), rr.Name)
|
||||
}
|
||||
|
||||
if rr.GetNextDomain() != rr.NextDomain {
|
||||
t.Fatalf("GetNextDomain() = %q, want %q", rr.GetNextDomain(), rr.NextDomain)
|
||||
}
|
||||
|
||||
if rr.GetTTL() != rr.TTL {
|
||||
t.Fatalf("GetTTL() = %d, want %d", rr.GetTTL(), rr.TTL)
|
||||
}
|
||||
|
||||
if !bytes.Equal(rr.GetTypeBitmap(), rr.TypeBitmap) {
|
||||
t.Fatalf("GetTypeBitmap() = %x, want %x", rr.GetTypeBitmap(), rr.TypeBitmap)
|
||||
}
|
||||
|
||||
bitmap[0] = 0xff
|
||||
if rr.TypeBitmap[0] != 0x01 {
|
||||
t.Fatal("Create should copy the type bitmap")
|
||||
}
|
||||
|
||||
clone := rr.GetTypeBitmap()
|
||||
clone[0] = 0xff
|
||||
if rr.TypeBitmap[0] != 0x01 {
|
||||
t.Fatal("GetTypeBitmap should return a copy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextName(t *testing.T) {
|
||||
|
|
@ -70,7 +92,6 @@ func TestPrevName(t *testing.T) {
|
|||
}{
|
||||
{name: "fqdn", in: "Foo-Bar.", want: "foo-baq\xff."},
|
||||
{name: "label", in: "example", want: "exampld\xff."},
|
||||
{name: "root", in: ".", want: "."},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
|
@ -83,3 +104,44 @@ func TestPrevName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrevNameRejectsEmptyName(t *testing.T) {
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
t.Fatal("PrevName should panic for an empty trimmed name")
|
||||
}
|
||||
}()
|
||||
|
||||
_ = PrevName(".")
|
||||
}
|
||||
|
||||
func TestNsec_Function_Good(t *testing.T) {
|
||||
rr := GetCreate("Example", NextName("Example"), []byte{0x01, 0x02})
|
||||
if rr.Name != "Example." {
|
||||
t.Fatalf("GetCreate name = %q, want %q", rr.Name, "Example.")
|
||||
}
|
||||
|
||||
if rr.TTL != DEFAULT_TTL {
|
||||
t.Fatalf("GetCreate TTL = %d, want %d", rr.TTL, DEFAULT_TTL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNsec_Function_Bad(t *testing.T) {
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
t.Fatal("PrevName should panic for an empty trimmed name")
|
||||
}
|
||||
}()
|
||||
|
||||
_ = PrevName(".")
|
||||
}
|
||||
|
||||
func TestNsec_Function_Ugly(t *testing.T) {
|
||||
if got := GetNextName("Example"); got != "example\x00." {
|
||||
t.Fatalf("GetNextName() = %q, want %q", got, "example\x00.")
|
||||
}
|
||||
|
||||
if got := GetPrevName("Example"); got != "exampld\xff." {
|
||||
t.Fatalf("GetPrevName() = %q, want %q", got, "exampld\xff.")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
pkg/dns/resolve_example_test.go
Normal file
10
pkg/dns/resolve_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package dns
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleVerifyString() {
|
||||
fmt.Println(VerifyString("Foo-Bar.lthn"))
|
||||
// Output: true
|
||||
}
|
||||
|
|
@ -560,3 +560,31 @@ func TestPackageLevelResolveAndVerifyAliases(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_Function_Good(t *testing.T) {
|
||||
got, err := 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() = %x, want %x", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_Function_Bad(t *testing.T) {
|
||||
if _, err := Resolve(123); err == nil {
|
||||
t.Fatal("Resolve should reject unsupported input types")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_Function_Ugly(t *testing.T) {
|
||||
if !VerifyByString("Foo-Bar.lthn") {
|
||||
t.Fatal("VerifyByString should accept canonical names after normalization")
|
||||
}
|
||||
|
||||
if !GetVerifyBinary([]byte("Foo-Bar.lthn")) {
|
||||
t.Fatal("GetVerifyBinary should accept canonical byte names")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,24 @@ type Resource struct {
|
|||
Records []ResourceRecord
|
||||
}
|
||||
|
||||
// GetTTL returns the resource TTL.
|
||||
func (r *Resource) GetTTL() int {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return r.TTL
|
||||
}
|
||||
|
||||
// GetRecords returns the resource records.
|
||||
func (r *Resource) GetRecords() []ResourceRecord {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return append([]ResourceRecord(nil), r.Records...)
|
||||
}
|
||||
|
||||
// ResourceJSON represents the JSON form of a DNS resource.
|
||||
type ResourceJSON struct {
|
||||
Records []json.RawMessage `json:"records"`
|
||||
|
|
@ -164,6 +182,20 @@ func (DSRecord) Type() HSType { return HSTypeDS }
|
|||
// GetType is an alias for Type.
|
||||
func (r DSRecord) GetType() HSType { return r.Type() }
|
||||
|
||||
// GetKeyTag returns the DS key tag.
|
||||
func (r DSRecord) GetKeyTag() uint16 { return r.KeyTag }
|
||||
|
||||
// GetAlgorithm returns the DS algorithm.
|
||||
func (r DSRecord) GetAlgorithm() uint8 { return r.Algorithm }
|
||||
|
||||
// GetDigestType returns the DS digest type.
|
||||
func (r DSRecord) GetDigestType() uint8 { return r.DigestType }
|
||||
|
||||
// GetDigest returns a copy of the DS digest.
|
||||
func (r DSRecord) GetDigest() []byte {
|
||||
return append([]byte(nil), r.Digest...)
|
||||
}
|
||||
|
||||
// Type returns the DNS record type.
|
||||
func (NSRecord) Type() HSType { return HSTypeNS }
|
||||
|
||||
|
|
@ -179,6 +211,9 @@ func (GLUE4Record) Type() HSType { return HSTypeGLUE4 }
|
|||
// GetType is an alias for Type.
|
||||
func (r GLUE4Record) GetType() HSType { return r.Type() }
|
||||
|
||||
// GetAddress returns the IPv4 glue address.
|
||||
func (r GLUE4Record) GetAddress() netip.Addr { return r.Address }
|
||||
|
||||
// GetNS is an alias for the NS field accessor.
|
||||
func (r GLUE4Record) GetNS() string { return r.NS }
|
||||
|
||||
|
|
@ -188,6 +223,9 @@ func (GLUE6Record) Type() HSType { return HSTypeGLUE6 }
|
|||
// GetType is an alias for Type.
|
||||
func (r GLUE6Record) GetType() HSType { return r.Type() }
|
||||
|
||||
// GetAddress returns the IPv6 glue address.
|
||||
func (r GLUE6Record) GetAddress() netip.Addr { return r.Address }
|
||||
|
||||
// GetNS is an alias for the NS field accessor.
|
||||
func (r GLUE6Record) GetNS() string { return r.NS }
|
||||
|
||||
|
|
@ -197,6 +235,9 @@ func (SYNTH4Record) Type() HSType { return HSTypeSYNTH4 }
|
|||
// GetType is an alias for Type.
|
||||
func (r SYNTH4Record) GetType() HSType { return r.Type() }
|
||||
|
||||
// GetAddress returns the synthesized IPv4 address.
|
||||
func (r SYNTH4Record) GetAddress() netip.Addr { return r.Address }
|
||||
|
||||
// GetNS is an alias for NS.
|
||||
func (r SYNTH4Record) GetNS() string { return r.NS() }
|
||||
|
||||
|
|
@ -206,6 +247,9 @@ func (SYNTH6Record) Type() HSType { return HSTypeSYNTH6 }
|
|||
// GetType is an alias for Type.
|
||||
func (r SYNTH6Record) GetType() HSType { return r.Type() }
|
||||
|
||||
// GetAddress returns the synthesized IPv6 address.
|
||||
func (r SYNTH6Record) GetAddress() netip.Addr { return r.Address }
|
||||
|
||||
// GetNS is an alias for NS.
|
||||
func (r SYNTH6Record) GetNS() string { return r.NS() }
|
||||
|
||||
|
|
@ -215,6 +259,11 @@ func (TXTRecord) Type() HSType { return HSTypeTXT }
|
|||
// GetType is an alias for Type.
|
||||
func (r TXTRecord) GetType() HSType { return r.Type() }
|
||||
|
||||
// GetEntries returns a copy of the TXT entries.
|
||||
func (r TXTRecord) GetEntries() []string {
|
||||
return append([]string(nil), r.Entries...)
|
||||
}
|
||||
|
||||
// NS returns the synthesized target host for a SYNTH4 record.
|
||||
func (r SYNTH4Record) NS() string {
|
||||
if !r.Address.Is4() {
|
||||
|
|
@ -443,9 +492,13 @@ func (r *Resource) GetToTXT(name string) []TXTRecord {
|
|||
|
||||
// ToZone projects the resource into zone-order records.
|
||||
//
|
||||
// Authority-like records are emitted first in original order, with duplicate NS
|
||||
// targets collapsed. Glue records for in-zone hosts are appended last.
|
||||
func (r *Resource) ToZone(name string) []ResourceRecord {
|
||||
// Records are emitted in their original order, with duplicate NS targets
|
||||
// collapsed. Glue records for in-zone hosts are appended last.
|
||||
//
|
||||
// The optional sign flag is accepted for API parity with the JS reference. The
|
||||
// simplified Go DNS layer does not emit RRSIG records, so the flag currently
|
||||
// preserves the unmodified RRset.
|
||||
func (r *Resource) ToZone(name string, sign ...bool) []ResourceRecord {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -526,8 +579,8 @@ func (r *Resource) ToZone(name string) []ResourceRecord {
|
|||
}
|
||||
|
||||
// GetToZone is an alias for ToZone.
|
||||
func (r *Resource) GetToZone(name string) []ResourceRecord {
|
||||
return r.ToZone(name)
|
||||
func (r *Resource) GetToZone(name string, sign ...bool) []ResourceRecord {
|
||||
return r.ToZone(name, sign...)
|
||||
}
|
||||
|
||||
// ToReferral builds the referral/negative-answer message shape used by the JS
|
||||
|
|
@ -574,8 +627,9 @@ func (r *Resource) GetToReferral(name string, typ HSType, isTLD bool) DNSMessage
|
|||
// ToDNS projects the resource into the JS reference DNS response shape.
|
||||
//
|
||||
// Subdomain lookups are routed to the appropriate TLD referral. TLD TXT
|
||||
// lookups answer directly when the resource has no NS records; DS lookups and
|
||||
// all other cases fall through to the referral/negative-answer logic.
|
||||
// lookups answer directly when the resource has no NS records; DS lookups
|
||||
// answer authoritatively when DS records are present. All other cases fall
|
||||
// through to the referral/negative-answer logic.
|
||||
func (r *Resource) ToDNS(name string, typ HSType) DNSMessage {
|
||||
name = fqdn(strings.ToLower(name))
|
||||
labels := strings.Split(strings.TrimSuffix(name, "."), ".")
|
||||
|
|
@ -593,8 +647,10 @@ func (r *Resource) ToDNS(name string, typ HSType) DNSMessage {
|
|||
}
|
||||
}
|
||||
case HSTypeDS:
|
||||
// DS TLD lookups intentionally fall through to the referral/negative
|
||||
// proof path to match the JS reference helper.
|
||||
msg.AA = true
|
||||
for _, record := range r.ToDS(name) {
|
||||
msg.Answer = append(msg.Answer, record)
|
||||
}
|
||||
}
|
||||
|
||||
if len(msg.Answer) == 0 && len(msg.Authority) == 0 {
|
||||
|
|
@ -977,6 +1033,21 @@ type resourceRecordProbe struct {
|
|||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func requireJSONFields(raw json.RawMessage, fields ...string) error {
|
||||
var obj map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &obj); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
if _, ok := obj[field]; !ok {
|
||||
return fmt.Errorf("missing required field %q", field)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceRecordFromJSON(raw json.RawMessage) (ResourceRecord, error) {
|
||||
var probe resourceRecordProbe
|
||||
if err := json.Unmarshal(raw, &probe); err != nil {
|
||||
|
|
@ -985,6 +1056,9 @@ func resourceRecordFromJSON(raw json.RawMessage) (ResourceRecord, error) {
|
|||
|
||||
switch probe.Type {
|
||||
case "DS":
|
||||
if err := requireJSONFields(raw, "keyTag", "algorithm", "digestType", "digest"); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid DS record", err)
|
||||
}
|
||||
var jsonRecord DSRecordJSON
|
||||
if err := json.Unmarshal(raw, &jsonRecord); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid DS record", err)
|
||||
|
|
@ -995,6 +1069,9 @@ func resourceRecordFromJSON(raw json.RawMessage) (ResourceRecord, error) {
|
|||
}
|
||||
return record, nil
|
||||
case "NS":
|
||||
if err := requireJSONFields(raw, "ns"); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid NS record", err)
|
||||
}
|
||||
var jsonRecord NSRecordJSON
|
||||
if err := json.Unmarshal(raw, &jsonRecord); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid NS record", err)
|
||||
|
|
@ -1005,6 +1082,9 @@ func resourceRecordFromJSON(raw json.RawMessage) (ResourceRecord, error) {
|
|||
}
|
||||
return record, nil
|
||||
case "GLUE4":
|
||||
if err := requireJSONFields(raw, "ns", "address"); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid GLUE4 record", err)
|
||||
}
|
||||
var jsonRecord GLUE4RecordJSON
|
||||
if err := json.Unmarshal(raw, &jsonRecord); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid GLUE4 record", err)
|
||||
|
|
@ -1015,6 +1095,9 @@ func resourceRecordFromJSON(raw json.RawMessage) (ResourceRecord, error) {
|
|||
}
|
||||
return record, nil
|
||||
case "GLUE6":
|
||||
if err := requireJSONFields(raw, "ns", "address"); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid GLUE6 record", err)
|
||||
}
|
||||
var jsonRecord GLUE6RecordJSON
|
||||
if err := json.Unmarshal(raw, &jsonRecord); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid GLUE6 record", err)
|
||||
|
|
@ -1025,6 +1108,9 @@ func resourceRecordFromJSON(raw json.RawMessage) (ResourceRecord, error) {
|
|||
}
|
||||
return record, nil
|
||||
case "SYNTH4":
|
||||
if err := requireJSONFields(raw, "address"); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid SYNTH4 record", err)
|
||||
}
|
||||
var jsonRecord SYNTH4RecordJSON
|
||||
if err := json.Unmarshal(raw, &jsonRecord); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid SYNTH4 record", err)
|
||||
|
|
@ -1035,6 +1121,9 @@ func resourceRecordFromJSON(raw json.RawMessage) (ResourceRecord, error) {
|
|||
}
|
||||
return record, nil
|
||||
case "SYNTH6":
|
||||
if err := requireJSONFields(raw, "address"); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid SYNTH6 record", err)
|
||||
}
|
||||
var jsonRecord SYNTH6RecordJSON
|
||||
if err := json.Unmarshal(raw, &jsonRecord); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid SYNTH6 record", err)
|
||||
|
|
@ -1045,6 +1134,9 @@ func resourceRecordFromJSON(raw json.RawMessage) (ResourceRecord, error) {
|
|||
}
|
||||
return record, nil
|
||||
case "TXT":
|
||||
if err := requireJSONFields(raw, "txt"); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid TXT record", err)
|
||||
}
|
||||
var jsonRecord TXTRecordJSON
|
||||
if err := json.Unmarshal(raw, &jsonRecord); err != nil {
|
||||
return nil, core.E("dns.Resource.FromJSON", "invalid TXT record", err)
|
||||
|
|
@ -1080,6 +1172,9 @@ func (r *DSRecord) FromJSON(jsonView DSRecordJSON) error {
|
|||
if err != nil {
|
||||
return core.E("dns.DSRecord.FromJSON", "invalid digest encoding", err)
|
||||
}
|
||||
if len(raw) > 255 {
|
||||
return core.E("dns.DSRecord.FromJSON", "digest exceeds maximum size", nil)
|
||||
}
|
||||
|
||||
r.KeyTag = jsonView.KeyTag
|
||||
r.Algorithm = jsonView.Algorithm
|
||||
|
|
@ -1101,6 +1196,9 @@ func (r *NSRecord) FromJSON(jsonView NSRecordJSON) error {
|
|||
if jsonView.Type != "NS" {
|
||||
return core.E("dns.NSRecord.FromJSON", "invalid NS record type", nil)
|
||||
}
|
||||
if err := validateDNSName(jsonView.NS); err != nil {
|
||||
return core.E("dns.NSRecord.FromJSON", "invalid ns name", err)
|
||||
}
|
||||
|
||||
r.NS = jsonView.NS
|
||||
return nil
|
||||
|
|
@ -1120,6 +1218,9 @@ func (r *GLUE4Record) FromJSON(jsonView GLUE4RecordJSON) error {
|
|||
if jsonView.Type != "GLUE4" {
|
||||
return core.E("dns.GLUE4Record.FromJSON", "invalid GLUE4 record type", nil)
|
||||
}
|
||||
if err := validateDNSName(jsonView.NS); err != nil {
|
||||
return core.E("dns.GLUE4Record.FromJSON", "invalid ns name", err)
|
||||
}
|
||||
|
||||
addr, err := netip.ParseAddr(jsonView.Address)
|
||||
if err != nil || !addr.Is4() {
|
||||
|
|
@ -1145,6 +1246,9 @@ func (r *GLUE6Record) FromJSON(jsonView GLUE6RecordJSON) error {
|
|||
if jsonView.Type != "GLUE6" {
|
||||
return core.E("dns.GLUE6Record.FromJSON", "invalid GLUE6 record type", nil)
|
||||
}
|
||||
if err := validateDNSName(jsonView.NS); err != nil {
|
||||
return core.E("dns.GLUE6Record.FromJSON", "invalid ns name", err)
|
||||
}
|
||||
|
||||
addr, err := netip.ParseAddr(jsonView.Address)
|
||||
if err != nil || !addr.Is6() {
|
||||
|
|
@ -1215,11 +1319,27 @@ func (r *TXTRecord) FromJSON(jsonView TXTRecordJSON) error {
|
|||
if jsonView.Type != "TXT" {
|
||||
return core.E("dns.TXTRecord.FromJSON", "invalid TXT record type", nil)
|
||||
}
|
||||
for _, entry := range jsonView.Entries {
|
||||
if len(entry) > 255 {
|
||||
return core.E("dns.TXTRecord.FromJSON", "txt entry exceeds maximum size", nil)
|
||||
}
|
||||
}
|
||||
|
||||
r.Entries = append(r.Entries[:0], jsonView.Entries...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDNSName(name string) error {
|
||||
var buf bytes.Buffer
|
||||
if err := writeName(&buf, name); err != nil {
|
||||
return err
|
||||
}
|
||||
if buf.Len() > 255 {
|
||||
return errors.New("dns name exceeds maximum size")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeRecord(buf *bytes.Buffer, record ResourceRecord) error {
|
||||
switch rr := record.(type) {
|
||||
case DSRecord:
|
||||
|
|
|
|||
10
pkg/dns/resource_example_test.go
Normal file
10
pkg/dns/resource_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package dns
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNewResource() {
|
||||
fmt.Println(NewResource().HasNS())
|
||||
// Output: false
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ package dns
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -11,6 +13,90 @@ import (
|
|||
"net/netip"
|
||||
)
|
||||
|
||||
func TestResourceFromJSONRejectsInvalidRecords(t *testing.T) {
|
||||
tooLongDigest := strings.Repeat("aa", 256)
|
||||
tooLongTXT := strings.Repeat("a", 256)
|
||||
tooLongLabel := strings.Repeat("a", covenant.MaxNameSize+1) + ".example."
|
||||
tooLongName := strings.Repeat("a.", 128)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
raw string
|
||||
}{
|
||||
{
|
||||
name: "oversized ds digest",
|
||||
raw: `{"type":"DS","keyTag":1,"algorithm":2,"digestType":3,"digest":"` + tooLongDigest + `"}`,
|
||||
},
|
||||
{
|
||||
name: "invalid ns label",
|
||||
raw: `{"type":"NS","ns":"` + tooLongLabel + `"}`,
|
||||
},
|
||||
{
|
||||
name: "oversized ns name",
|
||||
raw: `{"type":"GLUE4","ns":"` + tooLongName + `","address":"192.0.2.1"}`,
|
||||
},
|
||||
{
|
||||
name: "oversized txt entry",
|
||||
raw: `{"type":"TXT","txt":["` + tooLongTXT + `"]}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var resource Resource
|
||||
if err := resource.FromJSON(ResourceJSON{Records: []json.RawMessage{json.RawMessage(tc.raw)}}); err == nil {
|
||||
t.Fatal("FromJSON should reject invalid record JSON")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceFromJSONRejectsMissingRequiredFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
raw string
|
||||
}{
|
||||
{
|
||||
name: "ds missing digest",
|
||||
raw: `{"type":"DS","keyTag":1,"algorithm":2,"digestType":3}`,
|
||||
},
|
||||
{
|
||||
name: "ns missing host",
|
||||
raw: `{"type":"NS"}`,
|
||||
},
|
||||
{
|
||||
name: "glue4 missing host",
|
||||
raw: `{"type":"GLUE4","address":"192.0.2.1"}`,
|
||||
},
|
||||
{
|
||||
name: "glue6 missing address",
|
||||
raw: `{"type":"GLUE6","ns":"ns1.example."}`,
|
||||
},
|
||||
{
|
||||
name: "synth4 missing address",
|
||||
raw: `{"type":"SYNTH4"}`,
|
||||
},
|
||||
{
|
||||
name: "synth6 missing address",
|
||||
raw: `{"type":"SYNTH6"}`,
|
||||
},
|
||||
{
|
||||
name: "txt missing array",
|
||||
raw: `{"type":"TXT"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var resource Resource
|
||||
err := resource.FromJSON(ResourceJSON{Records: []json.RawMessage{json.RawMessage(tc.raw)}})
|
||||
if err == nil {
|
||||
t.Fatal("FromJSON should reject records with missing required fields")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceEncodeDecodeRoundTrip(t *testing.T) {
|
||||
resource := NewResource()
|
||||
resource.Records = []ResourceRecord{
|
||||
|
|
@ -248,6 +334,20 @@ func TestResourceTypeHelpers(t *testing.T) {
|
|||
if !resource.HasDS() || !resource.GetHasDS() {
|
||||
t.Fatal("HasDS should report DS records")
|
||||
}
|
||||
|
||||
if got := resource.GetTTL(); got != DEFAULT_TTL {
|
||||
t.Fatalf("GetTTL() = %d, want %d", got, DEFAULT_TTL)
|
||||
}
|
||||
|
||||
if got := resource.GetRecords(); len(got) != len(resource.Records) {
|
||||
t.Fatalf("GetRecords() = %d records, want %d", len(got), len(resource.Records))
|
||||
}
|
||||
|
||||
gotRecords := resource.GetRecords()
|
||||
gotRecords[0] = nil
|
||||
if resource.Records[0] == nil {
|
||||
t.Fatal("GetRecords should return a copy of the record slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceRecordTypeAliases(t *testing.T) {
|
||||
|
|
@ -342,6 +442,62 @@ func TestResourceRecordNSAliases(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceRecordFieldAccessors(t *testing.T) {
|
||||
ds := DSRecord{
|
||||
KeyTag: 11,
|
||||
Algorithm: 12,
|
||||
DigestType: 13,
|
||||
Digest: []byte{0xaa, 0xbb},
|
||||
}
|
||||
if ds.GetKeyTag() != ds.KeyTag {
|
||||
t.Fatalf("GetKeyTag() = %d, want %d", ds.GetKeyTag(), ds.KeyTag)
|
||||
}
|
||||
if ds.GetAlgorithm() != ds.Algorithm {
|
||||
t.Fatalf("GetAlgorithm() = %d, want %d", ds.GetAlgorithm(), ds.Algorithm)
|
||||
}
|
||||
if ds.GetDigestType() != ds.DigestType {
|
||||
t.Fatalf("GetDigestType() = %d, want %d", ds.GetDigestType(), ds.DigestType)
|
||||
}
|
||||
if got := ds.GetDigest(); !bytes.Equal(got, ds.Digest) {
|
||||
t.Fatalf("GetDigest() = %x, want %x", got, ds.Digest)
|
||||
}
|
||||
gotDigest := ds.GetDigest()
|
||||
gotDigest[0] = 0xff
|
||||
if ds.Digest[0] != 0xaa {
|
||||
t.Fatal("GetDigest should return a copy")
|
||||
}
|
||||
|
||||
glue4 := GLUE4Record{NS: "ns1.example.", Address: netip.MustParseAddr("192.0.2.1")}
|
||||
if glue4.GetAddress().String() != glue4.Address.String() {
|
||||
t.Fatalf("GLUE4 GetAddress() = %v, want %v", glue4.GetAddress(), glue4.Address)
|
||||
}
|
||||
|
||||
glue6 := GLUE6Record{NS: "ns2.example.", Address: netip.MustParseAddr("2001:db8::1")}
|
||||
if glue6.GetAddress().String() != glue6.Address.String() {
|
||||
t.Fatalf("GLUE6 GetAddress() = %v, want %v", glue6.GetAddress(), glue6.Address)
|
||||
}
|
||||
|
||||
synth4 := SYNTH4Record{Address: netip.MustParseAddr("198.51.100.9")}
|
||||
if synth4.GetAddress().String() != synth4.Address.String() {
|
||||
t.Fatalf("SYNTH4 GetAddress() = %v, want %v", synth4.GetAddress(), synth4.Address)
|
||||
}
|
||||
|
||||
synth6 := SYNTH6Record{Address: netip.MustParseAddr("2001:db8::9")}
|
||||
if synth6.GetAddress().String() != synth6.Address.String() {
|
||||
t.Fatalf("SYNTH6 GetAddress() = %v, want %v", synth6.GetAddress(), synth6.Address)
|
||||
}
|
||||
|
||||
txt := TXTRecord{Entries: []string{"hello", "world"}}
|
||||
if got := txt.GetEntries(); !reflect.DeepEqual(got, txt.Entries) {
|
||||
t.Fatalf("GetEntries() = %#v, want %#v", got, txt.Entries)
|
||||
}
|
||||
gotEntries := txt.GetEntries()
|
||||
gotEntries[0] = "changed"
|
||||
if txt.Entries[0] != "hello" {
|
||||
t.Fatal("GetEntries should return a copy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceToNSEC(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
|
|
@ -481,6 +637,16 @@ func TestResourceProjectionHelpers(t *testing.T) {
|
|||
t.Fatalf("GetToZone returned %d records, want %d", len(aliasZone), len(zone))
|
||||
}
|
||||
|
||||
signedZone := resource.ToZone("example.", true)
|
||||
if len(signedZone) != len(zone) {
|
||||
t.Fatalf("ToZone(..., true) returned %d records, want %d", len(signedZone), len(zone))
|
||||
}
|
||||
|
||||
signedAliasZone := resource.GetToZone("example.", true)
|
||||
if len(signedAliasZone) != len(zone) {
|
||||
t.Fatalf("GetToZone(..., true) returned %d records, want %d", len(signedAliasZone), len(zone))
|
||||
}
|
||||
|
||||
var nilResource *Resource
|
||||
if got := nilResource.ToNS("example."); got != nil {
|
||||
t.Fatalf("nil ToNS = %#v, want nil", got)
|
||||
|
|
@ -499,6 +665,37 @@ func TestResourceProjectionHelpers(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceToZonePreservesOriginalOrder(t *testing.T) {
|
||||
resource := NewResource()
|
||||
resource.Records = []ResourceRecord{
|
||||
TXTRecord{Entries: []string{"first"}},
|
||||
DSRecord{KeyTag: 7, Algorithm: 8, DigestType: 9, Digest: []byte{0xaa}},
|
||||
NSRecord{NS: "ns1.example."},
|
||||
GLUE4Record{NS: "ns1.example.", Address: netip.MustParseAddr("192.0.2.10")},
|
||||
}
|
||||
|
||||
zone := resource.ToZone("example.")
|
||||
if len(zone) != 4 {
|
||||
t.Fatalf("ToZone returned %d records, want 4", len(zone))
|
||||
}
|
||||
|
||||
if rr, ok := zone[0].(TXTRecord); !ok || len(rr.Entries) != 1 || rr.Entries[0] != "first" {
|
||||
t.Fatalf("ToZone[0] = %#v, want TXT first", zone[0])
|
||||
}
|
||||
|
||||
if rr, ok := zone[1].(DSRecord); !ok || rr.KeyTag != 7 {
|
||||
t.Fatalf("ToZone[1] = %#v, want DS record", zone[1])
|
||||
}
|
||||
|
||||
if rr, ok := zone[2].(NSRecord); !ok || rr.NS != "ns1.example." {
|
||||
t.Fatalf("ToZone[2] = %#v, want NS ns1.example.", zone[2])
|
||||
}
|
||||
|
||||
if rr, ok := zone[3].(GLUE4Record); !ok || rr.NS != "ns1.example." || rr.Address.String() != "192.0.2.10" {
|
||||
t.Fatalf("ToZone[3] = %#v, want GLUE4 ns1.example./192.0.2.10", zone[3])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceToDNSAndReferral(t *testing.T) {
|
||||
resource := NewResource()
|
||||
resource.Records = []ResourceRecord{
|
||||
|
|
@ -533,7 +730,10 @@ func TestResourceToDNSAndReferral(t *testing.T) {
|
|||
t.Fatal("GetToReferral should alias ToReferral")
|
||||
}
|
||||
|
||||
negative := resource.ToDNS("example.", HSTypeDS)
|
||||
txtResource := NewResource()
|
||||
txtResource.Records = []ResourceRecord{TXTRecord{Entries: []string{"hello"}}}
|
||||
|
||||
negative := txtResource.ToDNS("example.", HSTypeDS)
|
||||
if !negative.AA {
|
||||
t.Fatal("ToDNS should set AA for a TLD DS negative proof")
|
||||
}
|
||||
|
|
@ -547,9 +747,6 @@ func TestResourceToDNSAndReferral(t *testing.T) {
|
|||
t.Fatalf("ToDNS authority[0] = %#v, want NSEC example./example\\x00.", negative.Authority[0])
|
||||
}
|
||||
|
||||
txtResource := NewResource()
|
||||
txtResource.Records = []ResourceRecord{TXTRecord{Entries: []string{"hello"}}}
|
||||
|
||||
answer := txtResource.ToDNS("example.", HSTypeTXT)
|
||||
if !answer.AA {
|
||||
t.Fatal("ToDNS should set AA for an in-zone TXT answer")
|
||||
|
|
@ -568,6 +765,25 @@ func TestResourceToDNSAndReferral(t *testing.T) {
|
|||
if aliasAnswer.AA != answer.AA || len(aliasAnswer.Answer) != len(answer.Answer) || len(aliasAnswer.Authority) != len(answer.Authority) || len(aliasAnswer.Additional) != len(answer.Additional) {
|
||||
t.Fatal("GetToDNS should alias ToDNS")
|
||||
}
|
||||
|
||||
dsAnswer := resource.ToDNS("example.", HSTypeDS)
|
||||
if !dsAnswer.AA {
|
||||
t.Fatal("ToDNS should set AA for an in-zone DS answer")
|
||||
}
|
||||
if len(dsAnswer.Answer) != 1 {
|
||||
t.Fatalf("ToDNS DS answer = %d, want 1", len(dsAnswer.Answer))
|
||||
}
|
||||
if ds, ok := dsAnswer.Answer[0].(DSRecord); !ok || ds.KeyTag != 7 || ds.Algorithm != 8 || ds.DigestType != 9 || !bytes.Equal(ds.Digest, []byte{0xaa}) {
|
||||
t.Fatalf("ToDNS DS answer[0] = %#v, want DS keyTag=7", dsAnswer.Answer[0])
|
||||
}
|
||||
if len(dsAnswer.Authority) != 0 || len(dsAnswer.Additional) != 0 {
|
||||
t.Fatalf("ToDNS DS answer should not populate authority/additional, got %+v", dsAnswer)
|
||||
}
|
||||
|
||||
aliasDSAnswer := resource.GetToDNS("example.", HSTypeDS)
|
||||
if aliasDSAnswer.AA != dsAnswer.AA || len(aliasDSAnswer.Answer) != len(dsAnswer.Answer) || len(aliasDSAnswer.Authority) != len(dsAnswer.Authority) || len(aliasDSAnswer.Additional) != len(dsAnswer.Additional) {
|
||||
t.Fatal("GetToDNS should alias ToDNS for DS answers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceDecodeStopsAtUnknownType(t *testing.T) {
|
||||
|
|
@ -616,3 +832,42 @@ func TestResourceDecodeRejectsInvalidPayloads(t *testing.T) {
|
|||
t.Fatal("Decode should reject truncated TXT entries")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResource_Function_Good(t *testing.T) {
|
||||
resource := 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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResource_Function_Bad(t *testing.T) {
|
||||
if _, err := DecodeResource([]byte{1}); err == nil {
|
||||
t.Fatal("DecodeResource should reject unsupported versions")
|
||||
}
|
||||
|
||||
var resource *Resource
|
||||
if got := resource.GetSize(); got != 0 {
|
||||
t.Fatalf("nil resource GetSize() = %d, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResource_Function_Ugly(t *testing.T) {
|
||||
resource := &Resource{
|
||||
Records: []ResourceRecord{
|
||||
NSRecord{NS: "ns1.example."},
|
||||
TXTRecord{Entries: []string{"hello"}},
|
||||
},
|
||||
}
|
||||
|
||||
if !resource.HasNS() || !resource.GetHasNS() {
|
||||
t.Fatal("resource should report NS-capable records")
|
||||
}
|
||||
|
||||
if got := resource.ToNS("example"); len(got) != 1 || got[0].NS != "ns1.example." {
|
||||
t.Fatalf("ToNS() = %#v, want one NS record", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,19 @@ func (c Claim) GetVirtualSize() int {
|
|||
return c.VirtualSize()
|
||||
}
|
||||
|
||||
// ToInv converts the claim blob into a claim inventory item.
|
||||
func (c Claim) ToInv() InvItem {
|
||||
return InvItem{
|
||||
Type: InvTypeClaim,
|
||||
Hash: c.Hash(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetToInv is an alias for ToInv.
|
||||
func (c Claim) GetToInv() InvItem {
|
||||
return c.ToInv()
|
||||
}
|
||||
|
||||
// MarshalBinary serializes the claim wrapper.
|
||||
func (c Claim) MarshalBinary() ([]byte, error) {
|
||||
if len(c.Blob) > maxClaimSize {
|
||||
|
|
|
|||
10
pkg/primitives/claim_example_test.go
Normal file
10
pkg/primitives/claim_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNewClaim() {
|
||||
fmt.Println(NewClaim().GetSize())
|
||||
// Output: 2
|
||||
}
|
||||
|
|
@ -52,6 +52,19 @@ func TestClaimHelpers(t *testing.T) {
|
|||
t.Fatalf("GetVirtualSize() = %d, want %d", got, claim.VirtualSize())
|
||||
}
|
||||
|
||||
inv := claim.ToInv()
|
||||
if inv.Type != InvTypeClaim {
|
||||
t.Fatalf("ToInv().Type = %d, want %d", inv.Type, InvTypeClaim)
|
||||
}
|
||||
|
||||
if inv.Hash != wantHash {
|
||||
t.Fatalf("ToInv().Hash = %x, want %x", inv.Hash, wantHash)
|
||||
}
|
||||
|
||||
if got := claim.GetToInv(); got != inv {
|
||||
t.Fatalf("GetToInv() = %+v, want %+v", got, inv)
|
||||
}
|
||||
|
||||
if got := claim.ToBlob(); string(got) != string(blob) {
|
||||
t.Fatalf("ToBlob() = %q, want %q", got, blob)
|
||||
}
|
||||
|
|
@ -87,3 +100,37 @@ func TestClaimHelpers(t *testing.T) {
|
|||
t.Fatal("NewClaim helpers should return a claim wrapper")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaim_Function_Good(t *testing.T) {
|
||||
if NewClaim() == nil || GetNewClaim() == nil {
|
||||
t.Fatal("NewClaim helpers should return a claim wrapper")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaim_Function_Bad(t *testing.T) {
|
||||
var claim *Claim
|
||||
if claim.FromBlob([]byte("proof")) != nil {
|
||||
t.Fatal("nil claim receiver should stay nil")
|
||||
}
|
||||
|
||||
if _, err := (Claim{Blob: make([]byte, maxClaimSize + 1)}).MarshalBinary(); err == nil {
|
||||
t.Fatal("MarshalBinary should reject oversized claims")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaim_Function_Ugly(t *testing.T) {
|
||||
claim := Claim{Blob: []byte("ownership-proof")}
|
||||
raw, err := claim.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
var decoded Claim
|
||||
if err := decoded.UnmarshalBinary(raw); err != nil {
|
||||
t.Fatalf("UnmarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
if got := decoded.GetBlob(); string(got) != "ownership-proof" {
|
||||
t.Fatalf("decoded blob = %q, want %q", got, "ownership-proof")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
pkg/primitives/covenant_binary_example_test.go
Normal file
11
pkg/primitives/covenant_binary_example_test.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleCovenant_MarshalBinary() {
|
||||
raw, _ := Covenant{}.MarshalBinary()
|
||||
fmt.Println(len(raw))
|
||||
// Output: 2
|
||||
}
|
||||
|
|
@ -66,3 +66,49 @@ func TestCovenantBinaryZeroValue(t *testing.T) {
|
|||
t.Fatalf("decoded zero value = %+v, want zero value", decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovenantBinary_Function_Good(t *testing.T) {
|
||||
cov := Covenant{}
|
||||
cov.SetFinalize(
|
||||
Hash{43, 44, 45},
|
||||
188,
|
||||
[]byte("final"),
|
||||
12,
|
||||
21,
|
||||
34,
|
||||
Hash{46, 47, 48},
|
||||
)
|
||||
|
||||
raw, err := cov.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
var decoded Covenant
|
||||
if err := decoded.UnmarshalBinary(raw); err != nil {
|
||||
t.Fatalf("UnmarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
if decoded.Type != cov.Type || decoded.Len() != cov.Len() {
|
||||
t.Fatalf("decoded covenant = %+v, want %+v", decoded, cov)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovenantBinary_Function_Bad(t *testing.T) {
|
||||
var cov Covenant
|
||||
if err := cov.UnmarshalBinary(nil); err == nil {
|
||||
t.Fatal("UnmarshalBinary should reject short buffers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovenantBinary_Function_Ugly(t *testing.T) {
|
||||
var cov Covenant
|
||||
raw, err := cov.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
if len(raw) == 0 {
|
||||
t.Fatal("MarshalBinary should produce a compact encoding")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
pkg/primitives/covenant_items_example_test.go
Normal file
12
pkg/primitives/covenant_items_example_test.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleCovenant_SetOpen() {
|
||||
var cov Covenant
|
||||
cov.SetOpen(Hash{}, []byte("example"))
|
||||
fmt.Println(cov.Len())
|
||||
// Output: 3
|
||||
}
|
||||
|
|
@ -643,3 +643,39 @@ func TestCovenantJSONRejectsInvalidEncoding(t *testing.T) {
|
|||
t.Fatal("FromJSON should reject invalid hex data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovenantItems_Function_Good(t *testing.T) {
|
||||
var cov Covenant
|
||||
cov.SetOpen(Hash{1, 2, 3}, []byte("example"))
|
||||
|
||||
if cov.Len() != 3 {
|
||||
t.Fatalf("SetOpen should populate three covenant items, got %d", cov.Len())
|
||||
}
|
||||
|
||||
if got := cov.IndexOf([]byte("example")); got != 2 {
|
||||
t.Fatalf("IndexOf() = %d, want 2", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovenantItems_Function_Bad(t *testing.T) {
|
||||
var cov Covenant
|
||||
if _, err := cov.Get(0); err == nil {
|
||||
t.Fatal("Get should reject empty covenants")
|
||||
}
|
||||
|
||||
if err := cov.Set(1, []byte("x")); err == nil {
|
||||
t.Fatal("Set should reject out-of-range indexes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovenantItems_Function_Ugly(t *testing.T) {
|
||||
cov := Covenant{Items: [][]byte{[]byte("a"), []byte("b")}}
|
||||
|
||||
if item, err := cov.Get(-1); err != nil || string(item) != "b" {
|
||||
t.Fatalf("Get(-1) = %q, %v, want %q, nil", item, err, "b")
|
||||
}
|
||||
|
||||
if got := cov.Clone(); got.Len() != cov.Len() {
|
||||
t.Fatalf("Clone() length = %d, want %d", got.Len(), cov.Len())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
pkg/primitives/covenant_json_example_test.go
Normal file
11
pkg/primitives/covenant_json_example_test.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleCovenant_GetJSON() {
|
||||
json := Covenant{Type: covenantTypeOpen, Items: [][]byte{{0x01, 0x02}}}.GetJSON()
|
||||
fmt.Println(json.Action)
|
||||
// Output: OPEN
|
||||
}
|
||||
41
pkg/primitives/covenant_json_test.go
Normal file
41
pkg/primitives/covenant_json_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCovenantJSON_Function_Good(t *testing.T) {
|
||||
cov := Covenant{Type: covenantTypeOpen, Items: [][]byte{{0x01, 0x02}}}
|
||||
json := cov.GetJSON()
|
||||
if json.Action != "OPEN" || len(json.Items) != 1 || json.Items[0] != "0102" {
|
||||
t.Fatalf("unexpected JSON: %#v", json)
|
||||
}
|
||||
|
||||
var got Covenant
|
||||
if err := got.FromJSON(json); err != nil {
|
||||
t.Fatalf("FromJSON returned error: %v", err)
|
||||
}
|
||||
|
||||
if got.Type != cov.Type || got.GetVarSize() != cov.GetVarSize() {
|
||||
t.Fatalf("round trip mismatch: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovenantJSON_Function_Bad(t *testing.T) {
|
||||
var cov Covenant
|
||||
if err := cov.FromJSON(CovenantJSON{Items: []string{"zz"}}); err == nil {
|
||||
t.Fatal("expected invalid hex to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovenantJSON_Function_Ugly(t *testing.T) {
|
||||
var cov Covenant
|
||||
if err := cov.FromJSON(CovenantJSON{}); err != nil {
|
||||
t.Fatalf("FromJSON returned error for empty JSON: %v", err)
|
||||
}
|
||||
|
||||
if cov.Len() != 0 {
|
||||
t.Fatalf("expected empty covenant, got %#v", cov)
|
||||
}
|
||||
}
|
||||
|
||||
96
pkg/primitives/invitem.go
Normal file
96
pkg/primitives/invitem.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// InvType identifies a payload type in an inventory item.
|
||||
type InvType uint32
|
||||
|
||||
const (
|
||||
// InvTypeTX identifies a transaction inventory item.
|
||||
InvTypeTX InvType = 1
|
||||
|
||||
// InvTypeBlock identifies a block inventory item.
|
||||
InvTypeBlock InvType = 2
|
||||
|
||||
// InvTypeFilteredBlock identifies a filtered block inventory item.
|
||||
InvTypeFilteredBlock InvType = 3
|
||||
|
||||
// InvTypeCompactBlock identifies a compact block inventory item.
|
||||
InvTypeCompactBlock InvType = 4
|
||||
|
||||
// InvTypeClaim identifies a claim inventory item.
|
||||
InvTypeClaim InvType = 5
|
||||
|
||||
// InvTypeAirdrop identifies an airdrop proof inventory item.
|
||||
InvTypeAirdrop InvType = 6
|
||||
)
|
||||
|
||||
// InvItem mirrors the JS inventory wrapper used for network advertisements.
|
||||
type InvItem struct {
|
||||
Type InvType
|
||||
Hash Hash
|
||||
}
|
||||
|
||||
// NewInvItem constructs an inventory item with the provided type and hash.
|
||||
func NewInvItem(t InvType, hash Hash) *InvItem {
|
||||
return &InvItem{Type: t, Hash: hash}
|
||||
}
|
||||
|
||||
// GetNewInvItem is an alias for NewInvItem.
|
||||
func GetNewInvItem(t InvType, hash Hash) *InvItem {
|
||||
return NewInvItem(t, hash)
|
||||
}
|
||||
|
||||
// GetSize returns the serialized size of the inventory item.
|
||||
func (i InvItem) GetSize() int {
|
||||
return 36
|
||||
}
|
||||
|
||||
// MarshalBinary serializes the inventory item.
|
||||
func (i InvItem) MarshalBinary() ([]byte, error) {
|
||||
buf := make([]byte, i.GetSize())
|
||||
binary.LittleEndian.PutUint32(buf[:4], uint32(i.Type))
|
||||
copy(buf[4:], i.Hash[:])
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary decodes the inventory item.
|
||||
func (i *InvItem) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != i.GetSize() {
|
||||
return fmt.Errorf("primitives.InvItem.UnmarshalBinary: invalid length")
|
||||
}
|
||||
|
||||
i.Type = InvType(binary.LittleEndian.Uint32(data[:4]))
|
||||
copy(i.Hash[:], data[4:])
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsBlock reports whether the inventory item represents a block.
|
||||
func (i InvItem) IsBlock() bool {
|
||||
switch i.Type {
|
||||
case InvTypeBlock, InvTypeFilteredBlock, InvTypeCompactBlock:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsTX reports whether the inventory item represents a transaction.
|
||||
func (i InvItem) IsTX() bool {
|
||||
return i.Type == InvTypeTX
|
||||
}
|
||||
|
||||
// IsClaim reports whether the inventory item represents a claim.
|
||||
func (i InvItem) IsClaim() bool {
|
||||
return i.Type == InvTypeClaim
|
||||
}
|
||||
|
||||
// IsAirdrop reports whether the inventory item represents an airdrop proof.
|
||||
func (i InvItem) IsAirdrop() bool {
|
||||
return i.Type == InvTypeAirdrop
|
||||
}
|
||||
10
pkg/primitives/invitem_example_test.go
Normal file
10
pkg/primitives/invitem_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNewInvItem() {
|
||||
fmt.Println(NewInvItem(InvTypeClaim, Hash{}).IsClaim())
|
||||
// Output: true
|
||||
}
|
||||
79
pkg/primitives/invitem_test.go
Normal file
79
pkg/primitives/invitem_test.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInvItemHelpers(t *testing.T) {
|
||||
var hash Hash
|
||||
hash[0] = 0xaa
|
||||
hash[31] = 0x55
|
||||
|
||||
item := NewInvItem(InvTypeClaim, hash)
|
||||
if item == nil {
|
||||
t.Fatal("NewInvItem should return a value")
|
||||
}
|
||||
|
||||
if got := item.GetSize(); got != 36 {
|
||||
t.Fatalf("GetSize() = %d, want 36", got)
|
||||
}
|
||||
|
||||
if !item.IsClaim() || item.IsTX() || item.IsBlock() || item.IsAirdrop() {
|
||||
t.Fatal("inventory type predicates should match the claim type")
|
||||
}
|
||||
|
||||
raw, err := item.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
if got := binary.LittleEndian.Uint32(raw[:4]); got != uint32(InvTypeClaim) {
|
||||
t.Fatalf("MarshalBinary type = %d, want %d", got, InvTypeClaim)
|
||||
}
|
||||
|
||||
var decoded InvItem
|
||||
if err := decoded.UnmarshalBinary(raw); err != nil {
|
||||
t.Fatalf("UnmarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
if decoded.Type != item.Type || decoded.Hash != item.Hash {
|
||||
t.Fatalf("decoded inventory item = %+v, want %+v", decoded, item)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvItem_Function_Good(t *testing.T) {
|
||||
item := NewInvItem(InvTypeClaim, Hash{})
|
||||
if item == nil {
|
||||
t.Fatal("NewInvItem should return a value")
|
||||
}
|
||||
|
||||
if !item.IsClaim() || item.IsTX() || item.IsBlock() || item.IsAirdrop() {
|
||||
t.Fatal("inventory type predicates should match the claim type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvItem_Function_Bad(t *testing.T) {
|
||||
var item InvItem
|
||||
if err := item.UnmarshalBinary([]byte{1, 2}); err == nil {
|
||||
t.Fatal("UnmarshalBinary should reject invalid lengths")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvItem_Function_Ugly(t *testing.T) {
|
||||
item := InvItem{Type: InvTypeCompactBlock}
|
||||
if !item.IsBlock() {
|
||||
t.Fatal("compact block should count as a block")
|
||||
}
|
||||
|
||||
raw, err := item.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
if len(raw) != item.GetSize() {
|
||||
t.Fatalf("MarshalBinary length = %d, want %d", len(raw), item.GetSize())
|
||||
}
|
||||
}
|
||||
10
pkg/primitives/namedelta_example_test.go
Normal file
10
pkg/primitives/namedelta_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNameDelta_IsNull() {
|
||||
fmt.Println(NameDelta{}.IsNull())
|
||||
// Output: true
|
||||
}
|
||||
|
|
@ -172,3 +172,28 @@ func assertNameDeltaEqual(t *testing.T, got, want NameDelta) {
|
|||
assertBoolPtr("Expired", got.Expired, want.Expired)
|
||||
assertBoolPtr("Weak", got.Weak, want.Weak)
|
||||
}
|
||||
|
||||
func TestNameDelta_Function_Good(t *testing.T) {
|
||||
if got := (NameDelta{}).IsNull(); !got {
|
||||
t.Fatal("zero delta should be null")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameDelta_Function_Bad(t *testing.T) {
|
||||
var delta NameDelta
|
||||
if err := delta.UnmarshalBinary([]byte{1, 2, 3}); err == nil {
|
||||
t.Fatal("UnmarshalBinary should reject short buffers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameDelta_Function_Ugly(t *testing.T) {
|
||||
height := uint32(7)
|
||||
delta := NameDelta{Height: &height}
|
||||
if got := delta.GetField(); got != 1 {
|
||||
t.Fatalf("GetField() = %d, want 1", got)
|
||||
}
|
||||
|
||||
if got := delta.GetSize(); got <= 4 {
|
||||
t.Fatalf("GetSize() = %d, want greater than 4", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
pkg/primitives/namestate_binary_example_test.go
Normal file
13
pkg/primitives/namestate_binary_example_test.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ExampleNameState_MarshalBinary() {
|
||||
raw, _ := NameState{Name: []byte("example")}.MarshalBinary()
|
||||
fmt.Println(len(raw) > 0)
|
||||
// Output: true
|
||||
}
|
||||
55
pkg/primitives/namestate_binary_test.go
Normal file
55
pkg/primitives/namestate_binary_test.go
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNameStateBinary_Function_Good(t *testing.T) {
|
||||
ns := NameState{
|
||||
Name: []byte("example"),
|
||||
Height: 10,
|
||||
Renewal: 20,
|
||||
Value: 30,
|
||||
Highest: 40,
|
||||
Data: []byte{0x01, 0x02},
|
||||
Transfer: 50,
|
||||
Revoked: 60,
|
||||
Claimed: 70,
|
||||
Renewals: 80,
|
||||
Registered: true,
|
||||
Expired: true,
|
||||
Weak: true,
|
||||
}
|
||||
|
||||
raw, err := ns.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
var got NameState
|
||||
if err := got.UnmarshalBinary(raw); err != nil {
|
||||
t.Fatalf("UnmarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(got.Name, ns.Name) || !bytes.Equal(got.Data, ns.Data) {
|
||||
t.Fatalf("round trip mismatch: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameStateBinary_Function_Bad(t *testing.T) {
|
||||
ns := NameState{Name: bytes.Repeat([]byte("a"), 256)}
|
||||
if _, err := ns.MarshalBinary(); err == nil {
|
||||
t.Fatal("expected long name to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameStateBinary_Function_Ugly(t *testing.T) {
|
||||
var ns NameState
|
||||
if err := ns.UnmarshalBinary([]byte{0x01}); err == nil {
|
||||
t.Fatal("expected short buffer to fail")
|
||||
}
|
||||
}
|
||||
|
||||
11
pkg/primitives/namestate_example_test.go
Normal file
11
pkg/primitives/namestate_example_test.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNameState_Clone() {
|
||||
ns := NameState{Name: []byte("example")}
|
||||
fmt.Println(string(ns.Clone().Name))
|
||||
// Output: example
|
||||
}
|
||||
14
pkg/primitives/namestate_json_example_test.go
Normal file
14
pkg/primitives/namestate_json_example_test.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNameState_GetJSON() {
|
||||
json := NameState{
|
||||
Name: []byte("example"),
|
||||
NameHash: Hash{0x01},
|
||||
}.GetJSON(0, NameStateRules{})
|
||||
fmt.Println(json.Name)
|
||||
// Output: example
|
||||
}
|
||||
42
pkg/primitives/namestate_json_test.go
Normal file
42
pkg/primitives/namestate_json_test.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNameStateJSON_Function_Good(t *testing.T) {
|
||||
ns := NameState{
|
||||
Name: []byte("example"),
|
||||
NameHash: Hash{0x01},
|
||||
}
|
||||
|
||||
json := ns.GetJSON(0, NameStateRules{})
|
||||
if json.Name != "example" || json.NameHash == "" {
|
||||
t.Fatalf("unexpected JSON: %#v", json)
|
||||
}
|
||||
|
||||
var got NameState
|
||||
if err := got.FromJSON(json); err != nil {
|
||||
t.Fatalf("FromJSON returned error: %v", err)
|
||||
}
|
||||
|
||||
if string(got.Name) != "example" {
|
||||
t.Fatalf("round trip mismatch: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameStateJSON_Function_Bad(t *testing.T) {
|
||||
var ns NameState
|
||||
if err := ns.FromJSON(NameStateJSON{Name: "example", NameHash: "zz"}); err == nil {
|
||||
t.Fatal("expected invalid hash encoding to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameStateJSON_Function_Ugly(t *testing.T) {
|
||||
var ns NameState
|
||||
json := ns.GetFormat(0, NameStateRules{})
|
||||
if json.State == "" {
|
||||
t.Fatal("expected formatted JSON to include a state")
|
||||
}
|
||||
}
|
||||
|
||||
11
pkg/primitives/namestate_state_example_test.go
Normal file
11
pkg/primitives/namestate_state_example_test.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNameState_IsOpening() {
|
||||
ns := NameState{Height: 10}
|
||||
fmt.Println(ns.IsOpening(10, NameStateRules{TreeInterval: 5}))
|
||||
// Output: true
|
||||
}
|
||||
|
|
@ -151,3 +151,26 @@ func TestNameStateClaimableAndExpired(t *testing.T) {
|
|||
t.Fatal("closed names without an owner should expire once the renewal window ends")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameStateState_Function_Good(t *testing.T) {
|
||||
ns := NameState{Height: 10}
|
||||
rules := NameStateRules{TreeInterval: 1, BiddingPeriod: 1, RevealPeriod: 1}
|
||||
|
||||
if got := ns.State(10, rules); got != NameStateOpening {
|
||||
t.Fatalf("State() = %s, want %s", got, NameStateOpening)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameStateState_Function_Bad(t *testing.T) {
|
||||
var ns NameState
|
||||
if ns.IsClaimable(0, NameStateRules{}) {
|
||||
t.Fatal("zero state should not be claimable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameStateState_Function_Ugly(t *testing.T) {
|
||||
ns := NameState{Revoked: 12}
|
||||
if got := ns.State(13, NameStateRules{}); got != NameStateRevoked {
|
||||
t.Fatalf("State() = %s, want %s", got, NameStateRevoked)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
pkg/primitives/namestate_stats_example_test.go
Normal file
14
pkg/primitives/namestate_stats_example_test.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNameState_ToStats() {
|
||||
stats := NameState{
|
||||
Name: []byte("example"),
|
||||
Height: 10,
|
||||
}.ToStats(10, NameStateRules{TreeInterval: 5, BlocksPerDay: 24})
|
||||
fmt.Println(stats.BlocksUntilBidding)
|
||||
// Output: 6
|
||||
}
|
||||
35
pkg/primitives/namestate_stats_test.go
Normal file
35
pkg/primitives/namestate_stats_test.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNameStateStats_Function_Good(t *testing.T) {
|
||||
ns := NameState{Name: []byte("example"), Height: 10}
|
||||
stats := ns.ToStats(10, NameStateRules{TreeInterval: 5, BlocksPerDay: 24})
|
||||
if stats == nil || stats.BlocksUntilBidding != 6 || stats.OpenPeriodStart != 10 {
|
||||
t.Fatalf("unexpected stats: %#v", stats)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameStateStats_Function_Bad(t *testing.T) {
|
||||
ns := NameState{Renewal: 1}
|
||||
if stats := ns.ToStats(10, NameStateRules{TreeInterval: 0, RenewalWindow: 0}); stats != nil {
|
||||
t.Fatalf("expected nil stats for expired name without owner, got %#v", stats)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameStateStats_Function_Ugly(t *testing.T) {
|
||||
var owner Outpoint
|
||||
owner.TxHash[0] = 1
|
||||
|
||||
ns := NameState{
|
||||
Owner: owner,
|
||||
Revoked: 10,
|
||||
}
|
||||
|
||||
stats := ns.ToStats(10, NameStateRules{TreeInterval: 0, AuctionMaturity: 5, BlocksPerDay: 48})
|
||||
if stats == nil || stats.RevokePeriodStart != 10 || stats.BlocksUntilReopen != 5 {
|
||||
t.Fatalf("expected expired stats, got %#v", stats)
|
||||
}
|
||||
}
|
||||
|
|
@ -571,3 +571,25 @@ func TestNameStateBinaryZeroValue(t *testing.T) {
|
|||
t.Fatalf("decoded zero value = %+v, want zero value", decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameState_Function_Good(t *testing.T) {
|
||||
var ns NameState
|
||||
if ns.Delta() == nil || ns.GetDelta() == nil {
|
||||
t.Fatal("Delta helpers should allocate a sparse delta")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameState_Function_Bad(t *testing.T) {
|
||||
ns := NameState{Name: []byte("foo"), Height: 1}
|
||||
if got := ns.MaybeExpire(1, NameStateRules{}); got {
|
||||
t.Fatal("MaybeExpire should stay false when the name is not expired")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameState_Function_Ugly(t *testing.T) {
|
||||
ns := NameState{Name: []byte("foo"), Height: 10}
|
||||
clone := ns.Clone()
|
||||
if clone.NameHash != ns.NameHash || !bytes.Equal(clone.Name, ns.Name) {
|
||||
t.Fatalf("Clone() = %+v, want %+v", clone, ns)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
pkg/primitives/outpoint_example_test.go
Normal file
10
pkg/primitives/outpoint_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNewOutpoint() {
|
||||
fmt.Println(NewOutpoint().IsNull())
|
||||
// Output: true
|
||||
}
|
||||
10
pkg/primitives/outpoint_json_example_test.go
Normal file
10
pkg/primitives/outpoint_json_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleOutpoint_GetJSON() {
|
||||
fmt.Println(NewOutpoint().GetJSON().Index)
|
||||
// Output: 4294967295
|
||||
}
|
||||
35
pkg/primitives/outpoint_json_test.go
Normal file
35
pkg/primitives/outpoint_json_test.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOutpointJSON_Function_Good(t *testing.T) {
|
||||
op := Outpoint{Index: 7}
|
||||
op.TxHash[0] = 1
|
||||
|
||||
json := op.GetJSON()
|
||||
var got Outpoint
|
||||
if err := got.FromJSON(json); err != nil {
|
||||
t.Fatalf("FromJSON returned error: %v", err)
|
||||
}
|
||||
|
||||
if !got.Equals(op) {
|
||||
t.Fatalf("round trip mismatch: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutpointJSON_Function_Bad(t *testing.T) {
|
||||
var op Outpoint
|
||||
if err := op.FromJSON(OutpointJSON{Hash: "zz"}); err == nil {
|
||||
t.Fatal("expected invalid hash encoding to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutpointJSON_Function_Ugly(t *testing.T) {
|
||||
var op Outpoint
|
||||
if err := op.FromJSON(OutpointJSON{Hash: "00", Index: 1}); err == nil {
|
||||
t.Fatal("expected short hash to fail")
|
||||
}
|
||||
}
|
||||
|
||||
46
pkg/primitives/outpoint_test.go
Normal file
46
pkg/primitives/outpoint_test.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOutpoint_Function_Good(t *testing.T) {
|
||||
op := NewOutpoint()
|
||||
if !op.IsNull() {
|
||||
t.Fatal("NewOutpoint should return the null outpoint")
|
||||
}
|
||||
|
||||
raw, err := op.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
var got Outpoint
|
||||
if err := got.UnmarshalBinary(raw); err != nil {
|
||||
t.Fatalf("UnmarshalBinary returned error: %v", err)
|
||||
}
|
||||
|
||||
if !got.Equals(op) {
|
||||
t.Fatalf("round trip mismatch: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutpoint_Function_Bad(t *testing.T) {
|
||||
var op Outpoint
|
||||
if err := op.UnmarshalBinary([]byte{0x01}); err == nil {
|
||||
t.Fatal("expected invalid length to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutpoint_Function_Ugly(t *testing.T) {
|
||||
var op Outpoint
|
||||
op.TxHash[0] = 1
|
||||
clone := op.Clone()
|
||||
if !bytes.Equal(clone.TxHash[:], op.TxHash[:]) || clone.Index != op.Index {
|
||||
t.Fatalf("Clone() mismatch: %#v", clone)
|
||||
}
|
||||
}
|
||||
|
||||
10
pkg/primitives/types_example_test.go
Normal file
10
pkg/primitives/types_example_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleCovenant_IsName() {
|
||||
fmt.Println(Covenant{Type: covenantTypeOpen}.IsName())
|
||||
// Output: true
|
||||
}
|
||||
|
|
@ -367,3 +367,28 @@ func TestOutputIsUnspendable(t *testing.T) {
|
|||
t.Fatal("revoke covenant output should be unspendable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypes_Function_Good(t *testing.T) {
|
||||
addr := Address{Version: 0, Hash: bytes.Repeat([]byte{0x01}, 20)}
|
||||
if !addr.IsValid() || !addr.IsPubkeyHash() {
|
||||
t.Fatalf("valid pubkey hash address should be accepted: %#v", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypes_Function_Bad(t *testing.T) {
|
||||
addr := Address{Version: 32, Hash: bytes.Repeat([]byte{0x01}, 1)}
|
||||
if addr.IsValid() {
|
||||
t.Fatalf("invalid address should not be valid: %#v", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypes_Function_Ugly(t *testing.T) {
|
||||
cov := Covenant{Type: covenantTypeRevoke}
|
||||
if !cov.IsUnspendable() || !cov.IsRevoke() {
|
||||
t.Fatalf("revoke covenant should be unspendable and revoke: %#v", cov)
|
||||
}
|
||||
|
||||
if !(Covenant{Type: covenantTypeOpen}).IsName() {
|
||||
t.Fatal("open covenant should be a name covenant")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
pkg/primitives/view_example_test.go
Normal file
12
pkg/primitives/view_example_test.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package primitives
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNewNameView() {
|
||||
view := NewNameView()
|
||||
ns, _ := view.GetNameStateSync(nil, Hash{})
|
||||
fmt.Println(ns.NameHash == (Hash{}))
|
||||
// Output: true
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ package primitives
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -154,3 +155,47 @@ func (d NameDelta) MarshalMust(t *testing.T) []byte {
|
|||
|
||||
return data
|
||||
}
|
||||
|
||||
func TestView_Function_Good(t *testing.T) {
|
||||
view := NewNameView()
|
||||
if view == nil {
|
||||
t.Fatal("NewNameView should return a view")
|
||||
}
|
||||
|
||||
var hash Hash
|
||||
ns, err := view.GetNameStateSync(nil, hash)
|
||||
if err != nil {
|
||||
t.Fatalf("GetNameStateSync returned error: %v", err)
|
||||
}
|
||||
|
||||
if ns == nil || ns.NameHash != hash {
|
||||
t.Fatalf("GetNameStateSync returned %#v, want zero state keyed by hash", ns)
|
||||
}
|
||||
}
|
||||
|
||||
func TestView_Function_Bad(t *testing.T) {
|
||||
view := NewNameView()
|
||||
wantErr := errors.New("db failure")
|
||||
if _, err := view.GetNameStateSync(stubNameStateDB{err: wantErr}, Hash{}); err != wantErr {
|
||||
t.Fatalf("GetNameStateSync should propagate db errors, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestView_Function_Ugly(t *testing.T) {
|
||||
var view NameView
|
||||
firstHash := Hash{1}
|
||||
secondHash := Hash{2}
|
||||
|
||||
view.names = map[Hash]*NameState{
|
||||
firstHash: &NameState{NameHash: firstHash},
|
||||
secondHash: &NameState{NameHash: secondHash},
|
||||
}
|
||||
view.order = []Hash{secondHash, firstHash}
|
||||
view.names[firstHash].setHeight(1)
|
||||
view.names[secondHash].setHeight(2)
|
||||
|
||||
undo := view.ToNameUndo()
|
||||
if len(undo.Names) != 2 {
|
||||
t.Fatalf("ToNameUndo() = %d entries, want 2", len(undo.Names))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue