From 73003659561d83eb3123e008157134eaab6e4b03 Mon Sep 17 00:00:00 2001
From: Felix Lange <fjl@twurst.com>
Date: Wed, 16 Oct 2019 13:35:24 +0200
Subject: [PATCH] p2p/dnsdisc: update to latest EIP-1459 spec (#20168)

This updates the DNS TXT record format to the latest
changes in ethereum/EIPs#2313.
---
 p2p/dnsdisc/client.go      |  4 +-
 p2p/dnsdisc/client_test.go | 34 +++++++++-----
 p2p/dnsdisc/sync.go        |  4 +-
 p2p/dnsdisc/tree.go        | 91 +++++++++++++++++++-------------------
 p2p/dnsdisc/tree_test.go   | 38 ++++++++--------
 5 files changed, 91 insertions(+), 80 deletions(-)

diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go
index d4ed08ecb..677c0aa92 100644
--- a/p2p/dnsdisc/client.go
+++ b/p2p/dnsdisc/client.go
@@ -107,7 +107,7 @@ func NewClient(cfg Config, urls ...string) (*Client, error) {
 // SyncTree downloads the entire node tree at the given URL. This doesn't add the tree for
 // later use, but any previously-synced entries are reused.
 func (c *Client) SyncTree(url string) (*Tree, error) {
-	le, err := parseURL(url)
+	le, err := parseLink(url)
 	if err != nil {
 		return nil, fmt.Errorf("invalid enrtree URL: %v", err)
 	}
@@ -122,7 +122,7 @@ func (c *Client) SyncTree(url string) (*Tree, error) {
 
 // AddTree adds a enrtree:// URL to crawl.
 func (c *Client) AddTree(url string) error {
-	le, err := parseURL(url)
+	le, err := parseLink(url)
 	if err != nil {
 		return fmt.Errorf("invalid enrtree URL: %v", err)
 	}
diff --git a/p2p/dnsdisc/client_test.go b/p2p/dnsdisc/client_test.go
index 7e3a0f482..d8e3ecee3 100644
--- a/p2p/dnsdisc/client_test.go
+++ b/p2p/dnsdisc/client_test.go
@@ -41,12 +41,12 @@ const (
 
 func TestClientSyncTree(t *testing.T) {
 	r := mapResolver{
-		"3CA2MBMUQ55ZCT74YEEQLANJDI.n": "enr=-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI=",
-		"53HBTPGGZ4I76UEPCNQGZWIPTQ.n": "enr=-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA=",
-		"BG7SVUBUAJ3UAWD2ATEBLMRNEE.n": "enrtree=53HBTPGGZ4I76UEPCNQGZWIPTQ,3CA2MBMUQ55ZCT74YEEQLANJDI,HNHR6UTVZF5TJKK3FV27ZI76P4",
-		"HNHR6UTVZF5TJKK3FV27ZI76P4.n": "enr=-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o=",
-		"JGUFMSAGI7KZYB3P7IZW4S5Y3A.n": "enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
-		"n":                            "enrtree-root=v1 e=BG7SVUBUAJ3UAWD2ATEBLMRNEE l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=1 sig=gacuU0nTy9duIdu1IFDyF5Lv9CFHqHiNcj91n0frw70tZo3tZZsCVkE3j1ILYyVOHRLWGBmawo_SEkThZ9PgcQE=",
+		"n":                            "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA",
+		"C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
+		"JWXYDBPXYWG6FX3GMDIBFA6CJ4.n": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24",
+		"2XS2367YHAXJFGLZHVAWLQD4ZY.n": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA",
+		"H4FHT4B454P6UXFD7JCYQ5PWDY.n": "enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI",
+		"MHTDO6TMUBRIA2XWG5LUDACK24.n": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o",
 	}
 	var (
 		wantNodes = testNodes(0x29452, 3)
@@ -75,15 +75,25 @@ func TestClientSyncTree(t *testing.T) {
 
 // In this test, syncing the tree fails because it contains an invalid ENR entry.
 func TestClientSyncTreeBadNode(t *testing.T) {
+	// var b strings.Builder
+	// b.WriteString(enrPrefix)
+	// b.WriteString("-----")
+	// badHash := subdomain(&b)
+	// tree, _ := MakeTree(3, nil, []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"})
+	// tree.entries[badHash] = &b
+	// tree.root.eroot = badHash
+	// url, _ := tree.Sign(testKey(signingKeySeed), "n")
+	// fmt.Println(url)
+	// fmt.Printf("%#v\n", tree.ToTXT("n"))
+
 	r := mapResolver{
-		"n":                            "enrtree-root=v1 e=ZFJZDQKSOMJRYYQSZKJZC54HCF l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=3 sig=WEy8JTZ2dHmXM2qeBZ7D2ECK7SGbnurl1ge_S_5GQBAqnADk0gLTcg8Lm5QNqLHZjJKGAb443p996idlMcBqEQA=",
-		"JGUFMSAGI7KZYB3P7IZW4S5Y3A.n": "enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
-		"ZFJZDQKSOMJRYYQSZKJZC54HCF.n": "enr=gggggggggggggg=",
+		"n":                            "enrtree-root:v1 e=INDMVBZEEQ4ESVYAKGIYU74EAA l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=3 sig=Vl3AmunLur0JZ3sIyJPSH6A3Vvdp4F40jWQeCmkIhmcgwE4VC5U9wpK8C_uL_CMY29fd6FAhspRvq2z_VysTLAA",
+		"C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
+		"INDMVBZEEQ4ESVYAKGIYU74EAA.n": "enr:-----",
 	}
-
 	c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
-	_, err := c.SyncTree("enrtree://APFGGTFOBVE2ZNAB3CSMNNX6RRK3ODIRLP2AA5U4YFAA6MSYZUYTQ@n")
-	wantErr := nameError{name: "ZFJZDQKSOMJRYYQSZKJZC54HCF.n", err: entryError{typ: "enr", err: errInvalidENR}}
+	_, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n")
+	wantErr := nameError{name: "INDMVBZEEQ4ESVYAKGIYU74EAA.n", err: entryError{typ: "enr", err: errInvalidENR}}
 	if err != wantErr {
 		t.Fatalf("expected sync error %q, got %q", wantErr, err)
 	}
diff --git a/p2p/dnsdisc/sync.go b/p2p/dnsdisc/sync.go
index bd5c8d023..533dacc65 100644
--- a/p2p/dnsdisc/sync.go
+++ b/p2p/dnsdisc/sync.go
@@ -120,7 +120,7 @@ func (ct *clientTree) syncNextRandomENR(ctx context.Context) (*enode.Node, error
 }
 
 func (ct *clientTree) String() string {
-	return ct.loc.url()
+	return ct.loc.String()
 }
 
 // removeHash removes the element at index from h.
@@ -209,7 +209,7 @@ func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, err
 		if !ts.link {
 			return nil, errLinkInENRTree
 		}
-	case *subtreeEntry:
+	case *branchEntry:
 		ts.missing = append(ts.missing, e.children...)
 	}
 	return e, nil
diff --git a/p2p/dnsdisc/tree.go b/p2p/dnsdisc/tree.go
index 855d4968c..eba2ff9c0 100644
--- a/p2p/dnsdisc/tree.go
+++ b/p2p/dnsdisc/tree.go
@@ -49,7 +49,7 @@ func (t *Tree) Sign(key *ecdsa.PrivateKey, domain string) (url string, err error
 	root.sig = sig
 	t.root = &root
 	link := &linkEntry{domain, &key.PublicKey}
-	return link.url(), nil
+	return link.String(), nil
 }
 
 // SetSignature verifies the given signature and assigns it as the tree's current
@@ -96,7 +96,7 @@ func (t *Tree) Links() []string {
 	var links []string
 	for _, e := range t.entries {
 		if le, ok := e.(*linkEntry); ok {
-			links = append(links, le.url())
+			links = append(links, le.String())
 		}
 	}
 	return links
@@ -115,15 +115,15 @@ func (t *Tree) Nodes() []*enode.Node {
 
 const (
 	hashAbbrev    = 16
-	maxChildren   = 300 / (hashAbbrev * (13 / 8))
+	maxChildren   = 300 / hashAbbrev * (13 / 8)
 	minHashLength = 12
-	rootPrefix    = "enrtree-root=v1"
 )
 
 // MakeTree creates a tree containing the given nodes and links.
 func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) {
 	// Sort records by ID and ensure all nodes have a valid record.
 	records := make([]*enode.Node, len(nodes))
+
 	copy(records, nodes)
 	sortByID(records)
 	for _, n := range records {
@@ -139,7 +139,7 @@ func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) {
 	}
 	linkEntries := make([]entry, len(links))
 	for i, l := range links {
-		le, err := parseURL(l)
+		le, err := parseLink(l)
 		if err != nil {
 			return nil, err
 		}
@@ -166,7 +166,7 @@ func (t *Tree) build(entries []entry) entry {
 			hashes[i] = subdomain(e)
 			t.entries[hashes[i]] = e
 		}
-		return &subtreeEntry{hashes}
+		return &branchEntry{hashes}
 	}
 	var subtrees []entry
 	for len(entries) > 0 {
@@ -202,7 +202,7 @@ type (
 		seq   uint
 		sig   []byte
 	}
-	subtreeEntry struct {
+	branchEntry struct {
 		children []string
 	}
 	enrEntry struct {
@@ -218,7 +218,14 @@ type (
 
 var (
 	b32format = base32.StdEncoding.WithPadding(base32.NoPadding)
-	b64format = base64.URLEncoding
+	b64format = base64.RawURLEncoding
+)
+
+const (
+	rootPrefix   = "enrtree-root:v1"
+	linkPrefix   = "enrtree://"
+	branchPrefix = "enrtree-branch:"
+	enrPrefix    = "enr:"
 )
 
 func subdomain(e entry) string {
@@ -242,37 +249,29 @@ func (e *rootEntry) verifySignature(pubkey *ecdsa.PublicKey) bool {
 	return crypto.VerifySignature(crypto.FromECDSAPub(pubkey), e.sigHash(), sig)
 }
 
-func (e *subtreeEntry) String() string {
-	return "enrtree=" + strings.Join(e.children, ",")
+func (e *branchEntry) String() string {
+	return branchPrefix + strings.Join(e.children, ",")
 }
 
 func (e *enrEntry) String() string {
-	enc, _ := rlp.EncodeToBytes(e.node.Record())
-	return "enr=" + b64format.EncodeToString(enc)
+	return e.node.String()
 }
 
 func (e *linkEntry) String() string {
-	return "enrtree-link=" + e.link()
-}
-
-func (e *linkEntry) url() string {
-	return "enrtree://" + e.link()
-}
-
-func (e *linkEntry) link() string {
-	return fmt.Sprintf("%s@%s", b32format.EncodeToString(crypto.CompressPubkey(e.pubkey)), e.domain)
+	pubkey := b32format.EncodeToString(crypto.CompressPubkey(e.pubkey))
+	return fmt.Sprintf("%s%s@%s", linkPrefix, pubkey, e.domain)
 }
 
 // Entry Parsing
 
 func parseEntry(e string, validSchemes enr.IdentityScheme) (entry, error) {
 	switch {
-	case strings.HasPrefix(e, "enrtree-link="):
-		return parseLink(e[13:])
-	case strings.HasPrefix(e, "enrtree="):
-		return parseSubtree(e[8:])
-	case strings.HasPrefix(e, "enr="):
-		return parseENR(e[4:], validSchemes)
+	case strings.HasPrefix(e, linkPrefix):
+		return parseLinkEntry(e)
+	case strings.HasPrefix(e, branchPrefix):
+		return parseBranch(e)
+	case strings.HasPrefix(e, enrPrefix):
+		return parseENR(e, validSchemes)
 	default:
 		return nil, errUnknownEntry
 	}
@@ -294,7 +293,19 @@ func parseRoot(e string) (rootEntry, error) {
 	return rootEntry{eroot, lroot, seq, sigb}, nil
 }
 
-func parseLink(e string) (entry, error) {
+func parseLinkEntry(e string) (entry, error) {
+	le, err := parseLink(e)
+	if err != nil {
+		return nil, err
+	}
+	return le, nil
+}
+
+func parseLink(e string) (*linkEntry, error) {
+	if !strings.HasPrefix(e, linkPrefix) {
+		return nil, fmt.Errorf("wrong/missing scheme 'enrtree' in URL")
+	}
+	e = e[len(linkPrefix):]
 	pos := strings.IndexByte(e, '@')
 	if pos == -1 {
 		return nil, entryError{"link", errNoPubkey}
@@ -311,21 +322,23 @@ func parseLink(e string) (entry, error) {
 	return &linkEntry{domain, key}, nil
 }
 
-func parseSubtree(e string) (entry, error) {
+func parseBranch(e string) (entry, error) {
+	e = e[len(branchPrefix):]
 	if e == "" {
-		return &subtreeEntry{}, nil // empty entry is OK
+		return &branchEntry{}, nil // empty entry is OK
 	}
 	hashes := make([]string, 0, strings.Count(e, ","))
 	for _, c := range strings.Split(e, ",") {
 		if !isValidHash(c) {
-			return nil, entryError{"subtree", errInvalidChild}
+			return nil, entryError{"branch", errInvalidChild}
 		}
 		hashes = append(hashes, c)
 	}
-	return &subtreeEntry{hashes}, nil
+	return &branchEntry{hashes}, nil
 }
 
 func parseENR(e string, validSchemes enr.IdentityScheme) (entry, error) {
+	e = e[len(enrPrefix):]
 	enc, err := b64format.DecodeString(e)
 	if err != nil {
 		return nil, entryError{"enr", errInvalidENR}
@@ -364,21 +377,9 @@ func truncateHash(hash string) string {
 
 // ParseURL parses an enrtree:// URL and returns its components.
 func ParseURL(url string) (domain string, pubkey *ecdsa.PublicKey, err error) {
-	le, err := parseURL(url)
+	le, err := parseLink(url)
 	if err != nil {
 		return "", nil, err
 	}
 	return le.domain, le.pubkey, nil
 }
-
-func parseURL(url string) (*linkEntry, error) {
-	const scheme = "enrtree://"
-	if !strings.HasPrefix(url, scheme) {
-		return nil, fmt.Errorf("wrong/missing scheme 'enrtree' in URL")
-	}
-	le, err := parseLink(url[len(scheme):])
-	if err != nil {
-		return nil, err.(entryError).err
-	}
-	return le.(*linkEntry), nil
-}
diff --git a/p2p/dnsdisc/tree_test.go b/p2p/dnsdisc/tree_test.go
index e2fe96245..b6d0a8433 100644
--- a/p2p/dnsdisc/tree_test.go
+++ b/p2p/dnsdisc/tree_test.go
@@ -32,15 +32,15 @@ func TestParseRoot(t *testing.T) {
 		err   error
 	}{
 		{
-			input: "enrtree-root=v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtw=",
+			input: "enrtree-root:v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtw",
 			err:   entryError{"root", errSyntax},
 		},
 		{
-			input: "enrtree-root=v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM l=TO4Q75OQ2N7DX4EOOR7X66A6OM seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtw=",
+			input: "enrtree-root:v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM l=TO4Q75OQ2N7DX4EOOR7X66A6OM seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtw",
 			err:   entryError{"root", errInvalidSig},
 		},
 		{
-			input: "enrtree-root=v1 e=QFT4PBCRX4XQCV3VUYJ6BTCEPU l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=3 sig=3FmXuVwpa8Y7OstZTx9PIb1mt8FrW7VpDOFv4AaGCsZ2EIHmhraWhe4NxYhQDlw5MjeFXYMbJjsPeKlHzmJREQE=",
+			input: "enrtree-root:v1 e=QFT4PBCRX4XQCV3VUYJ6BTCEPU l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=3 sig=3FmXuVwpa8Y7OstZTx9PIb1mt8FrW7VpDOFv4AaGCsZ2EIHmhraWhe4NxYhQDlw5MjeFXYMbJjsPeKlHzmJREQE",
 			e: rootEntry{
 				eroot: "QFT4PBCRX4XQCV3VUYJ6BTCEPU",
 				lroot: "JGUFMSAGI7KZYB3P7IZW4S5Y3A",
@@ -69,49 +69,49 @@ func TestParseEntry(t *testing.T) {
 	}{
 		// Subtrees:
 		{
-			input: "enrtree=1,2",
-			err:   entryError{"subtree", errInvalidChild},
+			input: "enrtree-branch:1,2",
+			err:   entryError{"branch", errInvalidChild},
 		},
 		{
-			input: "enrtree=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
-			err:   entryError{"subtree", errInvalidChild},
+			input: "enrtree-branch:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+			err:   entryError{"branch", errInvalidChild},
 		},
 		{
-			input: "enrtree=",
-			e:     &subtreeEntry{},
+			input: "enrtree-branch:",
+			e:     &branchEntry{},
 		},
 		{
-			input: "enrtree=AAAAAAAAAAAAAAAAAAAA",
-			e:     &subtreeEntry{[]string{"AAAAAAAAAAAAAAAAAAAA"}},
+			input: "enrtree-branch:AAAAAAAAAAAAAAAAAAAA",
+			e:     &branchEntry{[]string{"AAAAAAAAAAAAAAAAAAAA"}},
 		},
 		{
-			input: "enrtree=AAAAAAAAAAAAAAAAAAAA,BBBBBBBBBBBBBBBBBBBB",
-			e:     &subtreeEntry{[]string{"AAAAAAAAAAAAAAAAAAAA", "BBBBBBBBBBBBBBBBBBBB"}},
+			input: "enrtree-branch:AAAAAAAAAAAAAAAAAAAA,BBBBBBBBBBBBBBBBBBBB",
+			e:     &branchEntry{[]string{"AAAAAAAAAAAAAAAAAAAA", "BBBBBBBBBBBBBBBBBBBB"}},
 		},
 		// Links
 		{
-			input: "enrtree-link=AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@nodes.example.org",
+			input: "enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@nodes.example.org",
 			e:     &linkEntry{"nodes.example.org", &testkey.PublicKey},
 		},
 		{
-			input: "enrtree-link=nodes.example.org",
+			input: "enrtree://nodes.example.org",
 			err:   entryError{"link", errNoPubkey},
 		},
 		{
-			input: "enrtree-link=AP62DT7WOTEQZGQZOU474PP3KMEGVTTE7A7NPRXKX3DUD57@nodes.example.org",
+			input: "enrtree://AP62DT7WOTEQZGQZOU474PP3KMEGVTTE7A7NPRXKX3DUD57@nodes.example.org",
 			err:   entryError{"link", errBadPubkey},
 		},
 		{
-			input: "enrtree-link=AP62DT7WONEQZGQZOU474PP3KMEGVTTE7A7NPRXKX3DUD57TQHGIA@nodes.example.org",
+			input: "enrtree://AP62DT7WONEQZGQZOU474PP3KMEGVTTE7A7NPRXKX3DUD57TQHGIA@nodes.example.org",
 			err:   entryError{"link", errBadPubkey},
 		},
 		// ENRs
 		{
-			input: "enr=-HW4QES8QIeXTYlDzbfr1WEzE-XKY4f8gJFJzjJL-9D7TC9lJb4Z3JPRRz1lP4pL_N_QpT6rGQjAU9Apnc-C1iMP36OAgmlkgnY0iXNlY3AyNTZrMaED5IdwfMxdmR8W37HqSFdQLjDkIwBd4Q_MjxgZifgKSdM=",
+			input: "enr:-HW4QES8QIeXTYlDzbfr1WEzE-XKY4f8gJFJzjJL-9D7TC9lJb4Z3JPRRz1lP4pL_N_QpT6rGQjAU9Apnc-C1iMP36OAgmlkgnY0iXNlY3AyNTZrMaED5IdwfMxdmR8W37HqSFdQLjDkIwBd4Q_MjxgZifgKSdM",
 			e:     &enrEntry{node: testNode(nodesSeed1)},
 		},
 		{
-			input: "enr=-HW4QLZHjM4vZXkbp-5xJoHsKSbE7W39FPC8283X-y8oHcHPTnDDlIlzL5ArvDUlHZVDPgmFASrh7cWgLOLxj4wprRkHgmlkgnY0iXNlY3AyNTZrMaEC3t2jLMhDpCDX5mbSEwDn4L3iUfyXzoO8G28XvjGRkrAg=",
+			input: "enr:-HW4QLZHjM4vZXkbp-5xJoHsKSbE7W39FPC8283X-y8oHcHPTnDDlIlzL5ArvDUlHZVDPgmFASrh7cWgLOLxj4wprRkHgmlkgnY0iXNlY3AyNTZrMaEC3t2jLMhDpCDX5mbSEwDn4L3iUfyXzoO8G28XvjGRkrAg=",
 			err:   entryError{"enr", errInvalidENR},
 		},
 		// Invalid:
-- 
GitLab