/ tests / test_builder.nim
test_builder.nim
  1  {.used.}
  2  
  3  import
  4    chronos,
  5    std/[sequtils, strformat, tables],
  6    testutils/unittests,
  7    ../dnsdisc/builder,
  8    ./test_utils
  9  
 10  procSuite "Test DNS Discovery: Merkle Tree builder":
 11    setup:
 12      let
 13        exampleRecords = initExampleRecords()
 14        exampleRoot = parseRootEntry(RootTxt).get()
 15        exampleLink = parseSubtreeEntry(LinkTxt).get()
 16        exampleBranch = parseSubtreeEntry(BranchTxt).get()
 17        exampleEnr1 = parseSubtreeEntry(Enr1Txt).get()
 18        exampleEnr2 = parseSubtreeEntry(Enr2Txt).get()
 19        exampleEnr3 = parseSubtreeEntry(Enr3Txt).get()
 20        exampleTree = Tree(rootEntry: exampleRoot,
 21                            entries: @[exampleLink,
 22                                       exampleBranch,
 23                                       exampleEnr1,
 24                                       exampleEnr2,
 25                                       exampleEnr3])
 26  
 27    # Test tree entries:
 28  
 29    asyncTest "Create TXT records":
 30      check:
 31        # Can convert (back) to TXT record
 32        exampleRoot.toTXTRecord().get() == RootTxt
 33        exampleEnr1.toTXTRecord().get() == Enr1Txt
 34        exampleLink.toTXTRecord().get() == LinkTxt
 35        exampleBranch.toTXTRecord().get() == BranchTxt
 36  
 37    asyncTest "Determine subdomain":
 38      check:
 39        # Successfully compute subdomain hash for each entry
 40        exampleEnr1.subdomain().get() == Enr1Subdomain
 41        exampleLink.subdomain().get() == LinkSubdomain
 42        exampleBranch.subdomain().get() == BranchSubdomain
 43  
 44    asyncTest "Build TXT records":
 45      check:
 46        # Successfully build all TXT records from example tree
 47        exampleTree.buildTXT("nodes.example.org").get() == exampleRecords
 48  
 49    asyncTest "Build subtree entries":
 50      # Test subtree from single leaf entry
 51      let subtreeLeaf = buildSubtree(@[parseSubtreeEntry(Enr1Txt).get()])
 52      check:
 53        subtreeLeaf.isOk()
 54        subtreeLeaf[].subtreeRoot.kind == Enr
 55        subtreeLeaf[].subtreeEntries.len == 0
 56  
 57      # Test subtree with single branch and leafs
 58      let subtreeSingle = buildSubtree(@[parseSubtreeEntry(Enr1Txt).get(),
 59                                         parseSubtreeEntry(Enr2Txt).get(),
 60                                         parseSubtreeEntry(Enr3Txt).get()])
 61      check:
 62        # Successfully build ENR subtree
 63        subtreeSingle.isOk()
 64        subtreeSingle[].subtreeRoot.kind == Branch
 65        subtreeSingle[].subtreeRoot.branchEntry.children == @[Enr1Subdomain, Enr2Subdomain, Enr3Subdomain]
 66        subtreeSingle[].subtreeEntries.len == 3
 67  
 68      # Test subtree with multiple branches-of-branches
 69      var entries: seq[SubtreeEntry]
 70      for i in 1..(MaxChildren*MaxChildren):
 71        # We need at least MaxChildren branch entries holding MaxChildren leaf nodes
 72        entries.add(parseSubtreeEntry(Enr1Txt).get())
 73  
 74      let
 75        subtreeMultiple = buildSubtree(entries)
 76        expectedLeafCount = MaxChildren*MaxChildren
 77        expectedBranchCount = MaxChildren # Excluding subtree root branch
 78      check:
 79        subtreeMultiple.isOk()
 80        subtreeMultiple[].subtreeRoot.kind == Branch
 81        subtreeMultiple[].subtreeEntries.len == expectedLeafCount + expectedBranchCount
 82        subtreeMultiple[].subtreeEntries.filterIt(it.kind == Enr).len == expectedLeafCount
 83        subtreeMultiple[].subtreeEntries.filterIt(it.kind == Branch).len == expectedBranchCount
 84  
 85    asyncTest "Build complete tree":
 86      var
 87        enr1: Record
 88        enr2: Record
 89        enr3: Record
 90        link = parseLinkEntry("enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org").get()
 91        seqNo = 1.uint32
 92  
 93      check:
 94        enr1.fromBase64("-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA")
 95        enr2.fromBase64("-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI")
 96        enr3.fromBase64("-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o")
 97  
 98      let tree = buildTree(seqNo, @[enr1, enr2, enr3], @[link])
 99  
100      check:
101        # Successfully build example tree
102        tree.isOk()
103        # Root entry
104        tree[].rootEntry.seqNo == 1
105        tree[].rootEntry.eroot == exampleRoot.eroot
106        tree[].rootEntry.lroot == exampleRoot.lroot
107        # Subtree entries
108        tree[].entries.len == 5
109        tree[].entries.filterIt(it.kind == Branch).len == 1
110        tree[].getNodes().len == 3
111        tree[].getLinks().len == 1
112  
113    asyncTest "Sign tree":
114      let
115        secKeyHex = "58d23b55bc9cdce1f18c2500f40ff4ab7245df9a89505e9b1fa4851f623d241d"
116        expectedSig = "8BGq3_ZasQUPvDyU7cqBpsHVjn4CP5GsFf7Xf9M1bJARCXII3SrD7e_I8Q6qw9oLItHapevgFlfPfwhqPXFRrAA"
117        expectedTXT = fmt"{RootPrefix} e={exampleTree.rootEntry.eroot} l={exampleTree.rootEntry.lroot} seq={exampleTree.rootEntry.seqNo} sig={expectedSig}"
118        testSecKey = PrivateKey.fromHex(secKeyHex).get()
119        testPubKey = testSecKey.toPublicKey()
120  
121      var tree = exampleTree
122  
123      let
124        signRes = signTree(tree, testSecKey)
125      check:
126        signRes.isOk()
127        tree.rootEntry.signature.len == SkRawRecoverableSignatureSize # 65 bytes
128  
129      # Verify signature
130      let
131        sigHash = hashableContent(tree.rootEntry)
132        sig = SignatureNR.fromRaw(tree.rootEntry.signature)
133  
134      check:
135        sig.isOk()
136        verify(sig = sig[],
137               msg = sigHash,
138               key = testPubKey)
139  
140      # Check TXT record
141      check:
142        tree.rootEntry.toTXTRecord().get() == expectedTXT