Skip to content

Commit

Permalink
Update submodues and formatting improvements (#19)
Browse files Browse the repository at this point in the history
* chore: Formatting improvements

* chore: Update submodules
  • Loading branch information
EmilIvanichkovv committed Jan 12, 2024
1 parent 6aa2861 commit c138792
Show file tree
Hide file tree
Showing 22 changed files with 119 additions and 117 deletions.
54 changes: 27 additions & 27 deletions dnsdisc/builder.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ export tree
## A collection of utilities for constructing a Merkle Tree
## encoding a list of ENR and link entries.
## The tree consists of DNS TXT records.
##
##
## Discovery via DNS is based on https://eips.ethereum.org/EIPS/eip-1459
##
##
## 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:
Expand Down Expand Up @@ -87,7 +87,7 @@ proc toTXTRecord*(subtreeEntry: SubtreeEntry): BuilderResult[string] =
txtRecord = LinkPrefix & subtreeEntry.linkEntry.str
of Branch:
txtRecord = BranchPrefix & subtreeEntry.branchEntry.children.join(",")

return ok(txtRecord)

proc subdomain*(subtreeEntry: SubtreeEntry): BuilderResult[string] =
Expand All @@ -96,16 +96,16 @@ proc subdomain*(subtreeEntry: SubtreeEntry): BuilderResult[string] =
## encoding of the (abbreviated) keccak256 hash of
## its TXT record content.
var txtRecord: string

try:
txtRecord = subtreeEntry.toTXTRecord().tryGet()
except ValueError:
return err("Failed to format subtree entry")

let
keccakHash = keccak256.digest(txtRecord.toBytes()).data[0..15]
subdomain = Base32.encode(keccakHash)

return ok(subdomain)

###############
Expand All @@ -132,15 +132,15 @@ proc buildTXT*(tree: Tree, domain: string): BuilderResult[Table[string, string]]
let
subdomainRes = subtreeEntry.subdomain()
txtRecordRes = subtreeEntry.toTXTRecord()

if subdomainRes.isErr:
return err("Failed to build: " & subdomainRes.error)

if txtRecordRes.isErr:
return err("Failed to build: " & txtRecordRes.error)

treeRecords[subdomainRes.get() & "." & domain] = txtRecordRes.get()

return ok(treeRecords)

proc buildSubtree*(entries: seq[SubtreeEntry]): BuilderResult[Subtree] =
Expand All @@ -152,26 +152,26 @@ proc buildSubtree*(entries: seq[SubtreeEntry]): BuilderResult[Subtree] =
# Single entry is its own root
subtree.subtreeRoot = entries[0]
return ok(subtree)

if entries.len() <= MaxChildren:
# Entries will fit in single branch
# Determine subdomain hashes
var children: seq[string]

for entry in entries:
let subdomainRes = entry.subdomain()

if subdomainRes.isErr:
return err("Failed to build subtree: " & subdomainRes.error)

children.add(subdomainRes.get())

# Return branch as subtree
subtree.subtreeRoot = SubtreeEntry(kind: Branch,
branchEntry: BranchEntry(children: children))
subtree.subtreeEntries = entries
return ok(subtree)

## Several branches required. The algorithm is now:
## 1. Create a branch subtree for each slice of entries that fits within MaxChildren
## 2. Create a subtree consisting of the subtree root entries of all branches in (1)
Expand Down Expand Up @@ -202,9 +202,9 @@ proc buildSubtree*(entries: seq[SubtreeEntry]): BuilderResult[Subtree] =
let rootsSubtreeRes = buildSubtree(combinedRoots)
if rootsSubtreeRes.isErr:
return err(rootsSubtreeRes.error)

let rootsSubtree = rootsSubtreeRes.get()

# Return combined subtree
subtree.subtreeRoot = rootsSubtree.subtreeRoot
subtree.subtreeEntries = concat(rootsSubtree.subtreeEntries, combinedEntries)
Expand All @@ -215,7 +215,7 @@ proc signTree*(tree: var Tree, privateKey: PrivateKey): BuilderResult[void] =
var
sig: Signature
rootEntry = tree.rootEntry

try:
sig = sign(privateKey, hashableContent(rootEntry))
except ValueError:
Expand All @@ -228,21 +228,21 @@ proc buildTree*(seqNo: uint32,
enrRecords: seq[Record],
links: seq[LinkEntry]): BuilderResult[Tree] =
## Builds a tree from given lists of ENR and links.

var tree: Tree

# @TODO verify ENR here - should be signed

# Convert ENR and links to subtree sequences
var
enrEntries: seq[SubtreeEntry]
linkEntries: seq[SubtreeEntry]

enrEntries = enrRecords.mapIt(SubtreeEntry(kind: Enr, enrEntry: EnrEntry(record: it)))
linkEntries = links.mapIt(SubtreeEntry(kind: Link, linkEntry: it))

# Build ENR and link subtrees

let enrSubtreeRes = buildSubtree(enrEntries)

if enrSubtreeRes.isErr:
Expand All @@ -257,21 +257,21 @@ proc buildTree*(seqNo: uint32,
let
erootRes = enrSubtreeRes.get().subtreeRoot.subdomain()
lrootRes = linkSubtreeRes.get().subtreeRoot.subdomain()

if erootRes.isErr or lrootRes.isErr:
return err("Failed to determine subtree root subdomain")

let rootEntry = RootEntry(eroot: erootRes.get(),
lroot: lrootRes.get(),
seqNo: seqNo)

# Combine subtrees
let entries = concat(@[enrSubtreeRes.get().subtreeRoot,
linkSubtreeRes.get().subtreeRoot],
enrSubtreeRes.get().subtreeEntries,
linkSubtreeRes.get().subtreeEntries)

tree = Tree(rootEntry: rootEntry,
entries: entries)

return ok(tree)
50 changes: 25 additions & 25 deletions dnsdisc/client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export

## Implementation of DNS-based discovery client protocol, as specified
## in https://eips.ethereum.org/EIPS/eip-1459
##
##
## This implementation is loosely based on the Go implementation of EIP-1459
## at https://github.com/ethereum/go-ethereum/blob/master/p2p/dnsdisc

Expand All @@ -26,7 +26,7 @@ type
## For now a client contains only a single tree in a single location
loc*: LinkEntry
tree*: Tree

## A Resolver proc takes a DNS domain as argument and
## returns the TXT record at that domain
Resolver* = proc(domain: string): Future[string] {.async.}
Expand All @@ -42,7 +42,7 @@ const

proc parseAndVerifySubtreeEntry(txtRecord: string, hashStr: string): EntryParseResult[SubtreeEntry] {.raises: [Defect, ValueError, Base32Error].} =
## Parses subtree TXT entry and verifies that it matches the hash

let res = parseSubtreeEntry(txtRecord)

if res.isErr():
Expand All @@ -54,7 +54,7 @@ proc parseAndVerifySubtreeEntry(txtRecord: string, hashStr: string): EntryParseR
subtreeEntry = res[]
checkHash = Base32.decode(hashStr)
entryHash = keccak256.digest(txtRecord.toBytes()).data

trace "Verifying parsed subtree entry", subtreeEntry=subtreeEntry

if entryHash[0..checkHash.len - 1] != checkHash:
Expand All @@ -73,25 +73,25 @@ proc resolveSubtreeEntry*(resolver: Resolver, loc: LinkEntry, subdomain: string)
if not await withTimeout(lookupFut, ResolverTimeout):
error "Failed to resolve DNS record", domain=subdomain
return err("Resolution failure: timeout")

let txtRecord = lookupFut.read()

trace "Resolving entry record", domain=subdomain, record=txtRecord

let res = parseAndVerifySubtreeEntry(txtRecord, subdomain)

if res.isErr():
error "Failed to parse and verify subtree entry", domain=loc.domain, record=txtRecord
return err("Resolution failure: " & res.error())

return ok(res[])

proc resolveAllEntries*(resolver: Resolver, loc: LinkEntry, rootEntry: RootEntry): Future[seq[SubtreeEntry]] {.async.} =
## Resolves all subtree entries at given root
## Follows EIP-1459 client protocol

var subtreeEntries: seq[SubtreeEntry]

var
# Initialise a hash set with the root hashes of ENR and link subtrees
hashes = toHashSet([rootEntry.eroot, rootEntry.lroot])
Expand All @@ -106,12 +106,12 @@ proc resolveAllEntries*(resolver: Resolver, loc: LinkEntry, rootEntry: RootEntry
# Resolve and remove random entry from subdomain hashes
nextHash = hashes.pop()
nextEntry = await resolveSubtreeEntry(resolver, loc, nextHash)

if nextEntry.isErr():
# @TODO metrics to track missing/failed entries
trace "Could not resolve next entry. Continuing.", subdomain=nextHash
continue

case nextEntry[].kind:
of Enr:
# Add to return list
Expand All @@ -122,7 +122,7 @@ proc resolveAllEntries*(resolver: Resolver, loc: LinkEntry, rootEntry: RootEntry
of Branch:
# Add branch children to hashes, and continue resolving
hashes.incl(nextEntry[].branchEntry.children.toHashSet())

return subtreeEntries

proc verifySignature(rootEntry: RootEntry, pubKey: PublicKey): bool =
Expand All @@ -143,7 +143,7 @@ proc verifySignature(rootEntry: RootEntry, pubKey: PublicKey): bool =

proc parseAndVerifyRoot(txtRecord: string, loc: LinkEntry): EntryParseResult[RootEntry] =
## Parses root TXT record and verifies signature

let res = parseRootEntry(txtRecord)

if res.isErr():
Expand All @@ -152,7 +152,7 @@ proc parseAndVerifyRoot(txtRecord: string, loc: LinkEntry): EntryParseResult[Roo
return res

let rootEntry = res[]

trace "Verifying parsed root entry", rootEntry=rootEntry

if not verifySignature(rootEntry, loc.pubKey):
Expand All @@ -165,28 +165,28 @@ proc resolveRoot*(resolver: Resolver, loc: LinkEntry): Future[ResolveResult[Root
## Resolves root entry at given location (LinkEntry)
## Also verifies the root signature and checks seq no
## Follows EIP-1459 client protocol

let lookupFut = resolver(loc.domain)

if not await withTimeout(lookupFut, ResolverTimeout):
error "Failed to resolve DNS record", domain=loc.domain
return err("Resolution failure: timeout")

let txtRecord = lookupFut.read()

info "Updating DNS discovery root", domain=loc.domain, record=txtRecord

let res = parseAndVerifyRoot(txtRecord, loc)

if res.isErr():
error "Failed to parse and verify root entry", domain=loc.domain, record=txtRecord
return err("Resolution failure: " & res.error())

return ok(res[])

proc syncTree(resolver: Resolver, rootLocation: LinkEntry): Future[Result[Tree, cstring]] {.async.} =
## Synchronises the client tree according to EIP-1459

let rootEntry = await resolveRoot(resolver, rootLocation)

if rootEntry.isErr:
Expand Down Expand Up @@ -227,13 +227,13 @@ proc getTree*(c: var Client, resolver: Resolver): Tree {.raises: [Defect, Catcha
## Main entry point into the client
## Returns a synchronised copy of the tree
## at the configured client domain
##
##
## For now the client tree is (only) synchronised whenever accessed.
## Note that this is a blocking operation to maintain memory safety
## on var Client
##
##
## @TODO periodically sync client tree and return only locally cached version

c.tree = (waitFor syncTree(resolver, c.loc)).tryGet()

return c.tree

0 comments on commit c138792

Please sign in to comment.