Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

p2p/dnsdisc: update to latest EIP-1459 spec #20168

Merged
merged 1 commit into from
Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions p2p/dnsdisc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down
34 changes: 22 additions & 12 deletions p2p/dnsdisc/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions p2p/dnsdisc/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
91 changes: 46 additions & 45 deletions p2p/dnsdisc/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -202,7 +202,7 @@ type (
seq uint
sig []byte
}
subtreeEntry struct {
branchEntry struct {
children []string
}
enrEntry struct {
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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}
Expand All @@ -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}
Expand Down Expand Up @@ -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
}
38 changes: 19 additions & 19 deletions p2p/dnsdisc/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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:
Expand Down