Add tree creation utility (#9)

* Improve client error handling and API

* Basic creator API

* Add tree creator utility

* Add tree creation basic tutorial

* Minor improvements
This commit is contained in:
Hanno Cornelius 2021-09-06 11:24:47 +02:00 committed by GitHub
parent dcb9290d00
commit 2d448241fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 727 additions and 11 deletions

25
.gitmodules vendored
View File

@ -63,3 +63,28 @@
url = https://github.com/status-im/nim-serialization.git
ignore = untracked
branch = master
[submodule "vendor/nim-confutils"]
path = vendor/nim-confutils
url = https://github.com/status-im/nim-confutils.git
ignore = untracked
branch = master
[submodule "vendor/nim-json-rpc"]
path = vendor/nim-json-rpc
url = https://github.com/status-im/nim-json-rpc.git
ignore = untracked
branch = master
[submodule "vendor/nim-http-utils"]
path = vendor/nim-http-utils
url = https://github.com/status-im/nim-http-utils.git
ignore = untracked
branch = master
[submodule "vendor/nim-websock"]
path = vendor/nim-websock
url = https://github.com/status-im/nim-websock.git
ignore = untracked
branch = master
[submodule "vendor/nim-zlib"]
path = vendor/nim-zlib
url = https://github.com/status-im/nim-zlib.git
ignore = untracked
branch = master

View File

@ -40,6 +40,10 @@ update: | update-common
rm -rf dnsdisc.nims && \
$(MAKE) dnsdisc.nims $(HANDLE_OUTPUT)
creator: | build deps
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim creator $(NIM_PARAMS) dnsdisc.nims
test: | deps
echo -e "Running: $@" && \
$(ENV_SCRIPT) nim test $(NIM_PARAMS) dnsdisc.nims

View File

@ -17,9 +17,39 @@ export tree
## This implementation is based on the Go implementation of EIP-1459
## at https://github.com/ethereum/go-ethereum/blob/master/p2p/dnsdisc
## How we determine MaxChildren: (Adapted from go-ethereum)
## https://github.com/ethereum/go-ethereum/blob/4d88974864c3ee84a24b1088064162b8dbab8ad5/p2p/dnsdisc/tree.go#L116-L146
##
## We want to keep the UDP size below 512 bytes. The UDP size is roughly:
## UDP length = 8 + UDP payload length ( 229 )
## UPD Payload length:
## - dns.id 2
## - dns.flags 2
## - dns.count.queries 2
## - dns.count.answers 2
## - dns.count.auth_rr 2
## - dns.count.add_rr 2
## - queries (query-size + 6)
## - answers :
## - dns.resp.name 2
## - dns.resp.type 2
## - dns.resp.class 2
## - dns.resp.ttl 4
## - dns.resp.len 2
## - dns.txt.length 1
## - dns.txt resp_data_size
## So the total size is roughly a fixed overhead of `39`, and the size of the
## query (domain name) and response.
## The query size is, for example, FVY6INQ6LZ33WLCHO3BPR3FH6Y.snap.mainnet.ethdisco.net (52)
## We also have some static data in the response, such as `enrtree-branch:`, and potentially
## splitting the response up with `" "`, leaving us with a size of roughly `400` that we need
## to stay below.
## The number `370` is used to have some margin for extra overhead (for example, the dns query
## may be larger - more subdomains).
const
HashAbbrevSize = 1 + (16*13)/8 # Size of an encoded hash (plus comma)
MaxChildren* = 370 div toInt(HashAbbrevSize) # 13 children
MaxChildren* = 370 div toInt(HashAbbrevSize) # 13 children. See comment above for explanation.
type
BuilderResult*[T] = Result[T, string]
@ -196,7 +226,7 @@ proc signTree*(tree: var Tree, privateKey: PrivateKey): BuilderResult[void] =
proc buildTree*(seqNo: uint32,
enrRecords: seq[Record],
links: seq[string]): BuilderResult[Tree] =
links: seq[LinkEntry]): BuilderResult[Tree] =
## Builds a tree from given lists of ENR and links.
var tree: Tree
@ -209,14 +239,7 @@ proc buildTree*(seqNo: uint32,
linkEntries: seq[SubtreeEntry]
enrEntries = enrRecords.mapIt(SubtreeEntry(kind: Enr, enrEntry: EnrEntry(record: it)))
for link in links:
let parsedLinkRes = parseLinkEntry(link)
if parsedLinkRes.isErr:
return err("Failed to parse link entry: " & parsedLinkRes.error)
linkEntries.add(SubtreeEntry(kind: Link, linkEntry: parsedLinkRes.get()))
linkEntries = links.mapIt(SubtreeEntry(kind: Link, linkEntry: it))
# Build ENR and link subtrees

View File

@ -0,0 +1,211 @@
{.push raises: [Defect]}
## A utility module to create a Merkle tree encoding
## a list of ENR and link entries. This is a standalone
## module that can be run as a CLI application. It exposes
## a JSON-RPC API.
import
std/[options, tables],
chronos,
chronicles,
stew/base32,
stew/results,
../builder
logScope:
topics = "tree.creator"
type
TreeCreator* = object
privateKey: PrivateKey
domain: Option[string]
seqNo: uint32
enrRecords: seq[Record]
links: seq[LinkEntry]
tree: Option[Tree]
isUpdated: bool
CreatorResult*[T] = Result[T, string]
##############
# Public API #
##############
proc setDomain*(tc: var TreeCreator, domain: string) =
tc.domain = some(domain)
proc getDomain*(tc: TreeCreator): Option[string] =
tc.domain
proc addEnrEntries*(tc: var TreeCreator, enrRecords: seq[string]): bool =
debug "adding enr entries"
var isSuccess = true
for enrRecord in enrRecords:
# Attempt to create ENR from strings
var enr: Record
if enr.fromURI(enrRecord):
tc.enrRecords.add(enr)
tc.isUpdated = true
else:
debug "Failed to parse ENR entry", enrRecord=enrRecord
isSuccess = false
return isSuccess
proc getEnrEntries*(tc: TreeCreator): seq[Record] =
tc.enrRecords
proc addLinkEntries*(tc: var TreeCreator, links: seq[string]): bool =
debug "adding link entries"
var isSuccess = true
for link in links:
# Attempt to parse link entries
let parsedLinkRes = parseLinkEntry(link)
if parsedLinkRes.isOk:
tc.links.add(parsedLinkRes.get())
tc.isUpdated = true
else:
debug "Failed to parse link entry", link=link
isSuccess = false
return isSuccess
proc getLinkEntries*(tc: TreeCreator): seq[LinkEntry] =
tc.links
proc buildTree*(tc: var TreeCreator): CreatorResult[Tree] =
## Attempts to build the tree, if it has not been built yet
## or if it has since been updated
debug "attempting to build tree"
if tc.tree.isSome() and not tc.isUpdated:
# We've already built a tree and there has been no update since.
debug "no update. returning tree."
return ok(tc.tree.get())
var tree: Tree
if tc.enrRecords.len == 0 and tc.links.len == 0:
# No entries to encode
return err("no enr or link entries configured")
# Build tree from existing entries. Increase seq no as per EIP-1459.
tc.seqNo = tc.seqNo + 1
let treeRes = buildTree(tc.seqNo, tc.enrRecords, tc.links)
if treeRes.isErr():
return err(treeRes.error)
# Sign tree
tree = treeRes[]
let signRes = tree.signTree(tc.privateKey)
if signRes.isErr():
return err(signRes.error)
# Cache signed tree on creator and reset isUpdated state
tc.tree = some(tree)
tc.isUpdated = false
return ok(tree)
proc getTXTs*(tc: var TreeCreator): CreatorResult[Table[string, string]] =
debug "getting TXT records"
if tc.domain.isNone():
return err("Failed to create: no domain configured")
# Attempt to build tree, if necessary
let buildRes = tc.buildTree()
if buildRes.isErr():
return err("Failed to create: " & buildRes.error)
# Extract TXT records
let txtRes = tc.tree.get().buildTXT(tc.domain.get())
if txtRes.isErr():
return err("Failed to create: " & txtRes.error)
return ok(txtRes[])
proc getPublicKey*(tc: TreeCreator): string =
## Returns the compressed 32 byte public key
## in base32 encoding. This forms the "username"
## part of the tree location URL as per
## https://eips.ethereum.org/EIPS/eip-1459
Base32.encode(tc.privateKey.toPublicKey().toRawCompressed())
proc getURL*(tc: TreeCreator): CreatorResult[string] =
## Returns the tree URL in the format
## 'enrtree://<public_key>@<domain>' as per
## https://eips.ethereum.org/EIPS/eip-1459
if tc.domain.isNone():
return err("Failed to create: no domain configured")
return ok(LinkPrefix & tc.getPublicKey & "@" & tc.domain.get())
##########################
# Creator initialization #
##########################
proc init*(T: type TreeCreator,
privateKey: PrivateKey,
domain = none(string),
enrRecords: seq[Record] = @[],
links: seq[LinkEntry] = @[]): T =
let treeCreator = TreeCreator(
privateKey: privateKey,
domain: domain,
seqNo: 0, # No sequence no yet, as tree has not been built
enrRecords: enrRecords,
links: links,
isUpdated: true # Indicates that tree requires a build
)
return treeCreator
{.pop.} # @TODO confutils.nim(775, 17) Error: can raise an unlisted exception: ref IOError
when isMainModule:
import
confutils,
stew/shims/net as stewNet,
./tree_creator_conf,
./tree_creator_rpc
logScope:
topics = "tree.creator.setup"
let
conf = TreeCreatorConf.load()
# 1/2 Initialise TreeCreator
debug "1/2 initialising"
let domain = if conf.domain == "": none(string)
else: some(conf.domain)
var treeCreator = TreeCreator.init(conf.privateKey,
domain,
conf.enrRecords,
conf.links)
# 2/2 Install JSON-RPC API handlers
debug "2/2 starting RPC API"
treeCreator.startRpc(conf.rpcAddress, Port(conf.rpcPort))
debug "setup complete"
runForever()

View File

@ -0,0 +1,85 @@
import
confutils, confutils/defs, confutils/std/net,
eth/p2p/discoveryv5/enr,
eth/keys,
stew/results,
../tree
type
TreeCreatorConf* = object
# General config
privateKey* {.
desc: "Tree creator private key as 64 char hex string.",
defaultValue: PrivateKey.random(newRng()[])
name: "private-key" }: PrivateKey
domain* {.
desc: "Fully qualified domain name for the tree root entry",
defaultValue: ""
name: "domain" }: string
enrRecords* {.
desc: "Discoverable ENR entry in ENR text encoded format. Argument may be repeated."
name: "enr-record" }: seq[Record]
links* {.
desc: "Discoverable link entry in link entry format. Argument may be repeated."
name: "link" }: seq[LinkEntry]
## JSON-RPC config
rpcAddress* {.
desc: "Listening address of the JSON-RPC server.",
defaultValue: ValidIpAddress.init("127.0.0.1")
name: "rpc-address" }: ValidIpAddress
rpcPort* {.
desc: "Listening port of the JSON-RPC server.",
defaultValue: 8545
name: "rpc-port" }: uint16
##################
# Config parsing #
##################
proc parseCmdArg*(T: type PrivateKey, p: TaintedString): T =
try:
let pk = PrivateKey.fromHex(string(p)).tryGet()
return pk
except CatchableError:
raise newException(ConfigurationError, "Invalid private key")
proc completeCmdArg*(T: type PrivateKey, val: TaintedString): seq[string] =
return @[]
proc parseCmdArg*(T: type enr.Record, p: TaintedString): T =
var enr: enr.Record
if not fromURI(enr, p):
raise newException(ConfigurationError, "Invalid ENR")
return enr
proc completeCmdArg*(T: type enr.Record, val: TaintedString): seq[string] =
return @[]
proc parseCmdArg*(T: type LinkEntry, p: TaintedString): T =
try:
let linkEntry = parseLinkEntry(string(p)).tryGet()
return linkEntry
except CatchableError:
raise newException(ConfigurationError, "Invalid link entry")
proc completeCmdArg*(T: type LinkEntry, val: TaintedString): seq[string] =
return @[]
proc parseCmdArg*(T: type ValidIpAddress, p: TaintedString): T =
try:
let ipAddr = ValidIpAddress.init(p)
return ipAddr
except CatchableError as e:
raise newException(ConfigurationError, "Invalid IP address")
proc completeCmdArg*(T: type ValidIpAddress, val: TaintedString): seq[string] =
return @[]

View File

@ -0,0 +1,57 @@
{.push raises: [Defect, CatchableError].}
import
std/tables,
chronicles,
json_rpc/rpcserver,
stew/shims/net,
stew/results,
./tree_creator
logScope:
topics = "tree.creator.rpc"
proc installRpcApiHandlers(initTc: TreeCreator, rpcsrv: RpcServer) {.gcsafe.} =
var tc = initTc # Create a mutable copy, to maintain memory safety
rpcsrv.rpc("post_domain") do(domain: string) -> bool:
debug "post_domain"
tc.setDomain(domain)
return true
rpcsrv.rpc("get_domain") do() -> Option[string]:
debug "get_domain"
return tc.getDomain()
rpcsrv.rpc("post_enr_entries") do(enrRecords: seq[string]) -> bool:
debug "post_enr_entries"
return tc.addEnrEntries(enrRecords)
rpcsrv.rpc("post_link_entries") do(links: seq[string]) -> bool:
debug "post_link_entries"
return tc.addLinkEntries(links)
rpcsrv.rpc("get_txt_records") do() -> Table[string, string]:
debug "get_txt_records"
let txts = tc.getTXTs().tryGet()
return txts
rpcsrv.rpc("get_public_key") do() -> string:
debug "get_public_key"
return tc.getPublicKey()
rpcsrv.rpc("get_url") do() -> string:
debug "get_url"
let url = tc.getURL().tryGet()
return url
proc startRpc*(tc: var TreeCreator, rpcIp: ValidIpAddress, rpcPort: Port) =
info "Starting RPC server"
let
ta = initTAddress(rpcIp, rpcPort)
rpcServer = newRpcHttpServer([ta])
installRpcApiHandlers(tc, rpcServer)
rpcServer.start()
info "RPC Server started", ta

View File

@ -20,6 +20,14 @@ requires "nim >= 1.2.0",
"nimcrypto"
# Helper functions
proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") =
if not dirExists "build":
mkDir "build"
# allow something like "nim nimbus --verbosity:0 --hints:off nimbus.nims"
var extra_params = params
for i in 2..<paramCount():
extra_params &= " " & paramStr(i)
exec "nim " & lang & " --out:build/" & name & " " & extra_params & " " & srcDir & name & ".nim"
proc test(name: string, params = "-d:chronicles_log_level=DEBUG", lang = "c") =
# XXX: When running `> NIM_PARAMS="-d:chronicles_log_level=INFO" make test2`
@ -27,5 +35,8 @@ proc test(name: string, params = "-d:chronicles_log_level=DEBUG", lang = "c") =
# specified here.
exec "nim " & lang & " -r " & params & " tests/" & name & ".nim"
task creator, "Build DNS discovery tree creator":
buildBinary "tree_creator", "discovery/dnsdisc/creator/", "-d:chronicles_log_level=DEBUG -d:chronosStrictException"
task test, "Build & run all DNS discovery tests":
test "all_tests"

295
docs/tutorial.md Normal file
View File

@ -0,0 +1,295 @@
# DNS-based discovery Tree Creation: Basic Tutorial
## Background
The `tree_creator` is a command line utility used to create and update [EIP-1459](https://eips.ethereum.org/EIPS/eip-1459) compliant Merkle trees.
It takes as input a list of node records (in ENR text encoding) and link entries pointing to other trees.
The `tree_creator` utility will keep track of the tree sequence number and increase it whenever the encoded links or ENR entries are updated.
The root domain is configurable.
The most useful output is a map of subdomains to TXT records that can easily be converted to a zone file and deployed to a DNS name server,
from where the encoded lists of links and ENR can be retrieved by clients.
## How to build and run
To build and run `tree_creator` using its default configuration:
```bash
# Build `tree_creator` utility
make creator
# Run tree creator utility with default configuration
./build/tree_creator
```
To initialise the tree with some entries when running the utility,
specify `--enr-record` or `--link` command line options.
Both arguments may be repeated as many times as necessary.
```bash
./build/tree_creator \
--link=enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@never.gonna.give.you.up \
--enr-record=enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA \
--enr-record=enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI
```
The domain can be specified when running the utility with the `--domain` option.
This can later be modified using the JSON-RPC API.
```bash
./build/tree_creator --domain=mydomain.example.org
```
For a full list of available command line options,
run the utility with the `--help` option:
```bash
./build/tree_creator --help
```
## Using the JSON-RPC API
The JSON-RPC API is exposed by default on the localhost (`127.0.0.1`) at port `8545`.
It is possible to change this configuration by setting the `rpc-address` and `rpc-port` options when running the node:
```bash
./build/tree_creator --rpc-address:127.0.1.1 --rpc-port:8546
```
The following JSON-RPC API methods are defined for the `tree_creator`:
---
### 1. `post_domain`
The `post_domain` method sets the fully qualified root domain name for the tree.
#### Parameters
| Field | Type | Inclusion | Description |
| ----: | :---: | :---: |----------- |
| `domain` | `String` | mandatory | The fully qualified domain name to set for the tree root entry |
#### Response
- **`Bool`** - `true` on success or an [error](https://www.jsonrpc.org/specification#error_object) on failure.
---
### 2. `get_domain`
The `get_domain` method returns the domain currently configured for this tree.
#### Parameters
none
#### Response
- The domain or an [error](https://www.jsonrpc.org/specification#error_object) on failure.
---
### 3. `post_enr_entries`
The `post_enr_entries` method adds a sequence of node records to the `tree_creator` to be encoded.
The node records must be ENR text encoded as per [EIP-778](https://eips.ethereum.org/EIPS/eip-778#text-encoding)
#### Parameters
| Field | Type | Inclusion | Description |
| ----: | :---: | :---: |----------- |
| `enrRecords` | `Array`[`String`] | mandatory | A list of ENR records to add to the tree |
#### Response
- **`Bool`** - `true` on success or an [error](https://www.jsonrpc.org/specification#error_object) on failure.
---
### 4. `post_link_entries`
The `post_link_entries` method adds a sequence of links referencing other trees to the `tree_creator` to be encoded.
The links must formatted according to the the `enrtree` scheme defined in [EIP-1459](https://eips.ethereum.org/EIPS/eip-1459#dns-record-structure)
#### Parameters
| Field | Type | Inclusion | Description |
| ----: | :---: | :---: |----------- |
| `links` | `Array`[`String`] | mandatory | A list of links to other trees to add to the tree |
#### Response
- **`Bool`** - `true` on success or an [error](https://www.jsonrpc.org/specification#error_object) on failure.
---
### 5. `get_txt_records`
The `get_txt_records` method returns a map of subdomains to TXT records for the encoded Merkle tree.
This can easily be converted to a zone file and deployed to a DNS name server.
#### Parameters
none
#### Response
- A map of subdomain to TXT record or an [error](https://www.jsonrpc.org/specification#error_object) on failure.
---
### 6. `get_public_key`
The `get_public_key` method returns the compressed 32 byte public key in base32 encoding.
This forms the "username" part of the tree location URL as per [EIP-1459](https://eips.ethereum.org/EIPS/eip-1459)
#### Parameters
none
#### Response
- The public key or an [error](https://www.jsonrpc.org/specification#error_object) on failure.
---
### 7. `get_url`
The `get_url` method returns the tree URL in the format `enrtree://<public_key>@<domain>` as per [EIP-1459](https://eips.ethereum.org/EIPS/eip-1459)
#### Parameters
none
#### Response
- The tree URL or an [error](https://www.jsonrpc.org/specification#error_object) on failure.
## JSON-RPC API example
One way to access JSON-RPC methods is by using the `cURL` command line tool.
For example:
```bash
curl -d '{"jsonrpc":"2.0","id":"id","method":"<method-name>", "params":[<params>]}' --header "Content-Type: application/json" http://localhost:8545
```
where `<method-name>` is the name of the JSON-RPC method to call and `<params>` is a comma-separated `Array` of parameters to pass as arguments to the selected method.
This example assumes that the `tree_creator` is running and the API is exposed on the `localhost` at port `8545` (the default configuration).
### Setting the domain
#### Example request:
```json
{
"jsonrpc": "2.0",
"id": "id",
"method": "post_domain",
"params": [
"mynodes.example.org"
]
}
```
#### Example response:
```json
{
"jsonrpc": "2.0",
"id": "id",
"result": true
}
```
### Adding ENR and link entries
#### Example request - adding ENR:
```json
{
"jsonrpc": "2.0",
"id": "id",
"method": "post_enr_entries",
"params": [
[
"enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA",
"enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o"
]
]
}
```
#### Example request - adding links:
```json
{
"jsonrpc": "2.0",
"id": "id",
"method": "post_link_entries",
"params": [
[
"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@never.gonna.let.you.down"
]
]
}
```
### Retrieving encoded TXT records
#### Example request:
```json
{
"jsonrpc": "2.0",
"id": "id",
"method": "get_txt_records",
"params": []
}
```
#### Example response:
```json
{
"jsonrpc": "2.0",
"id": "id",
"result": {
"mynodes.example.org": "enrtree-root:v1 e=DHTTG472H5RVLIHOIQSPLVMGGA l=T7J7RURX6U73I7N4DKNIJYUOUU seq=1 sig=9e4E1Yw2cdPjuLvwjhfmBvjDKepAFow0x5BfVy8JzG56RDSTErOFxOz8eUzBO5l_acE-VHQLc9TFB8muSbZH6QE",
"T7J7RURX6U73I7N4DKNIJYUOUU.mynodes.example.org": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@never.gonna.let.you.down",
"DHTTG472H5RVLIHOIQSPLVMGGA.mynodes.example.org": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,MHTDO6TMUBRIA2XWG5LUDACK24",
"MHTDO6TMUBRIA2XWG5LUDACK24.mynodes.example.org": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o",
"2XS2367YHAXJFGLZHVAWLQD4ZY.mynodes.example.org": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA"
}
}
```
### Retrieving the tree URL
#### Example request:
```json
{
"jsonrpc": "2.0",
"id": "id",
"method": "get_url",
"params": []
}
```
#### Example response:
```json
{
"jsonrpc": "2.0",
"id": "id",
"result": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@mynodes.example.org"
}
```
## References
1. [EIP-778](https://eips.ethereum.org/EIPS/eip-778)
2. [EIP-1459](https://eips.ethereum.org/EIPS/eip-1459)

View File

@ -71,7 +71,7 @@ procSuite "Test DNS Discovery: Merkle Tree builder":
enr1: Record
enr2: Record
enr3: Record
link = "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"
link = parseLinkEntry("enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org").get()
seqNo = 1.uint32
check:

1
vendor/nim-confutils vendored Submodule

@ -0,0 +1 @@
Subproject commit ab4ba1cbfdccdb8c0398894ffc25169bc61faeed

1
vendor/nim-http-utils vendored Submodule

@ -0,0 +1 @@
Subproject commit 689da19e9e9cfff4ced85e2b25c6b2b5598ed079

1
vendor/nim-json-rpc vendored Submodule

@ -0,0 +1 @@
Subproject commit b2417fc0719a6d5069437a3097645d1fae6954d6

1
vendor/nim-websock vendored Submodule

@ -0,0 +1 @@
Subproject commit f354dfebe9f45ba33e6ad3b6f3163d516c9d0727

1
vendor/nim-zlib vendored Submodule

@ -0,0 +1 @@
Subproject commit 6bbc67d09e624d69052d62b2353878f491186942