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:
parent
dcb9290d00
commit
2d448241fd
|
@ -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
|
||||
|
|
4
Makefile
4
Makefile
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
|
@ -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 @[]
|
|
@ -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
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
|
@ -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:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit ab4ba1cbfdccdb8c0398894ffc25169bc61faeed
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 689da19e9e9cfff4ced85e2b25c6b2b5598ed079
|
|
@ -0,0 +1 @@
|
|||
Subproject commit b2417fc0719a6d5069437a3097645d1fae6954d6
|
|
@ -0,0 +1 @@
|
|||
Subproject commit f354dfebe9f45ba33e6ad3b6f3163d516c9d0727
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 6bbc67d09e624d69052d62b2353878f491186942
|
Loading…
Reference in New Issue