Now pruning tree after deletions
This commit is contained in:
parent
c5cdeac8db
commit
e9b7d794cc
|
@ -1 +1 @@
|
|||
Subproject commit f9834381b33d1b6d2b0b34bc7d05ddfb767f8b34
|
||||
Subproject commit 72906be185fbb62c5fe746a5354ff54ea0f56cbd
|
|
@ -207,7 +207,9 @@ proc updateAllCommitments*(tree: BranchesNode) =
|
|||
for node in nodes:
|
||||
for index, commitment in node.commitmentsSnapshot:
|
||||
points.add(commitment)
|
||||
points.add(node.branches[index].commitment)
|
||||
if node.branches[index] != nil:
|
||||
points.add(node.branches[index].commitment)
|
||||
else: points.add(IdentityPoint)
|
||||
childIndexes.add(index)
|
||||
|
||||
var frs = newSeq[Field](points.len)
|
||||
|
|
|
@ -13,7 +13,7 @@ import
|
|||
./tree,
|
||||
./commitment
|
||||
|
||||
when TraceLogs: import std/strformat
|
||||
when TraceLogs: import std/[strformat, strutils]
|
||||
|
||||
|
||||
proc newValuesNode*(key, value: Bytes32) : ValuesNode =
|
||||
|
@ -40,13 +40,6 @@ proc setValue(node: ValuesNode, index: byte, value: Bytes32) =
|
|||
node.values[index] = heapValue
|
||||
|
||||
|
||||
proc deleteValue(node: ValuesNode, index: byte) =
|
||||
## Deletes the value at the given `index`, if any
|
||||
node.updateCommitment(index, nil)
|
||||
node.values[index] = nil
|
||||
|
||||
|
||||
# TODO: prevent setting a value from a non-root node
|
||||
proc setValue*(node: BranchesNode, key: Bytes32, value: Bytes32) =
|
||||
## Stores the given `value` in the tree at the given `key`
|
||||
var current = node
|
||||
|
@ -102,37 +95,85 @@ proc setValue*(node: BranchesNode, key: Bytes32, value: Bytes32) =
|
|||
|
||||
|
||||
|
||||
proc deleteValue(node: BranchesNode, key: Bytes32, depth: int = 0):
|
||||
tuple[found: bool, empty: bool, values: ValuesNode] =
|
||||
## Deletes the value associated with the given `key` from the tree, and prunes
|
||||
## the tree as needed
|
||||
|
||||
#[
|
||||
Algorithm:
|
||||
- We walk down the tree and try to find the ValuesNodes that contains the
|
||||
value.
|
||||
- If we can't find it, or can't find the value within it, we return found=false
|
||||
- If we find it and the ValuesNodes contains more than one value, we set
|
||||
the target value to nil and update the ValuesNodes commitment.
|
||||
We return found=true, empty=false
|
||||
- If the ValuesNode contains just the target value, we simply detach it
|
||||
from its parent and discard it. We don't bother with removing the value
|
||||
or updating its commitment.
|
||||
- After detaching it, if the parent is left empty, we signal its own parent
|
||||
that it should be detached too, by returning empty=true
|
||||
- If the parent is left with one other ValuesNode (and no other BranchesNode),
|
||||
we signal its own parent that it should be detached, but that the other
|
||||
ValuesNode should be attached in its stead. We return it in the `values`
|
||||
tuple field.
|
||||
- When an ancestor obtains that ValuesNode, it will attach it in case it
|
||||
has at least one other brach (be it a ValuesNode or BranchesNode).
|
||||
Otherwise, it will notify its own parent it should be disconnected as
|
||||
well and pass the ValuesNode along.
|
||||
- Meaning, the ValuesNode (sibling to the ValuesNode from which the value
|
||||
was removed) starts travelling up the tree till it lands in a BranchesNode
|
||||
that contains one other branch, or reaches the root.
|
||||
- In any case of the tree being modified, we snapshot the commitments of
|
||||
nodes whose children were modified, so they can be bulk-updated later on.
|
||||
Leaves node commitments are updated on the spot though.
|
||||
]#
|
||||
|
||||
var child = node.branches[key[depth]]
|
||||
when TraceLogs: echo " ".repeat(depth) & &"At branch {cast[uint64](node)}, depth {depth}, child index {key[depth].toHex}"
|
||||
|
||||
if child of ValuesNode:
|
||||
var vn = child.ValuesNode
|
||||
var target = vn.values[key[^1]]
|
||||
when TraceLogs: echo " ".repeat(depth+1) & &"At ValuesNode {cast[uint64](vn)}, depth {depth+1}"
|
||||
if target == nil:
|
||||
when TraceLogs: echo " ".repeat(depth+1) & &"Value not found at index {key[^1].toHex}"
|
||||
return (found: false, empty: false, values: nil)
|
||||
node.snapshotChildCommitment(key[depth])
|
||||
var hasOtherValues = vn.values.any(v => v != nil and v != target)
|
||||
if hasOtherValues:
|
||||
when TraceLogs: echo " ".repeat(depth+1) & &"ValuesNode has multiple values; removing value at index {key[^1].toHex}"
|
||||
vn.updateCommitment(key[^1], nil)
|
||||
vn.values[key[^1]] = nil
|
||||
return (found: true, empty: false, values: nil)
|
||||
when TraceLogs: echo " ".repeat(depth+1) & &"ValuesNode contains only the target value at index {key[^1].toHex}; detaching from tree"
|
||||
node.branches[key[depth]] = nil
|
||||
|
||||
elif child of BranchesNode:
|
||||
var bn = child.BranchesNode
|
||||
var (found, empty, values) = deleteValue(bn, key, depth + 1)
|
||||
if not found:
|
||||
return (found, empty, values)
|
||||
node.snapshotChildCommitment(key[depth])
|
||||
if not empty:
|
||||
return (found, empty, values)
|
||||
if values == nil:
|
||||
when TraceLogs: echo " ".repeat(depth) & &"At branch {cast[uint64](node)}, depth {depth}. Detached child from tree."
|
||||
node.branches[key[depth]] = nil
|
||||
else:
|
||||
when TraceLogs: echo " ".repeat(depth) & &"At branch {cast[uint64](node)}, depth {depth}. Replaced child with inner ValuesNode."
|
||||
node.branches[key[depth]] = values # propagate ValuesNode up the tree
|
||||
|
||||
if node.branches.all(b => b == nil):
|
||||
return (found: true, empty: true, values: nil)
|
||||
elif node.branches.any(b => b of BranchesNode) or
|
||||
node.branches.foldl(if b of ValuesNode: a+1 else: a, 0) >= 2:
|
||||
return (found: true, empty: false, values: nil)
|
||||
else:
|
||||
let vn = node.branches.filter(b => b != nil and b != child)[0].ValuesNode
|
||||
return (found: true, empty: true, values: vn)
|
||||
|
||||
|
||||
|
||||
proc deleteValue*(node: BranchesNode, key: Bytes32): bool =
|
||||
## Deletes the value associated with the given `key` from the tree.
|
||||
var current = node
|
||||
var depth = 0
|
||||
when TraceLogs: echo &"Deleting value for key {key.toHex}"
|
||||
|
||||
# Walk down the tree until the branch closest to the key
|
||||
while current.branches[key[depth]] of BranchesNode:
|
||||
when TraceLogs: echo &"At node {cast[uint64](current)}. Going down to branch '{key[depth].toHex}' at depth {depth}"
|
||||
current.snapshotChildCommitment(key[depth])
|
||||
current = current.branches[key[depth]].BranchesNode
|
||||
inc(depth)
|
||||
|
||||
# If we reached a ValuesNode...
|
||||
var vn = current.branches[key[depth]].ValuesNode
|
||||
if vn != nil:
|
||||
when TraceLogs: echo &"At node {cast[uint64](current)}. Found ValuesNode at branch '{key[depth].toHex}', depth {depth}, addr {cast[uint64](vn)}"
|
||||
when TraceLogs: echo &" Stem: {vn.stem.toHex}"
|
||||
|
||||
# If the stem differs from the key, we can't use that ValuesNode.
|
||||
# This means the value doesn't exist for the given key, so we return false.
|
||||
var divergence = vn.stem.zip(key).firstMatchAt(tup => tup[0] != tup[1])
|
||||
if divergence.found:
|
||||
return false
|
||||
|
||||
# If the stem matches the key, we found the ValuesNode for the key.
|
||||
# We remove it by setting the branch to nil.
|
||||
current.snapshotChildCommitment(key[depth])
|
||||
vn.deleteValue(key[^1])
|
||||
|
||||
return true
|
||||
|
||||
# If no ValuesNode was found for the key, it means the value doesn't exist.
|
||||
return false
|
||||
return deleteValue(node, key, 0).found
|
||||
|
|
|
@ -54,6 +54,11 @@ func hexToBits*(c: char): byte =
|
|||
else: raise newException(ValueError, "Character must be hexadecimal (a-f | A-F | 0-9)")
|
||||
|
||||
|
||||
func toHex*(b: byte): string =
|
||||
result.add bitsToHex(b shr 4)
|
||||
result.add bitsToHex(b and 0x0f)
|
||||
|
||||
|
||||
proc writeAsHex*(stream: Stream, b: byte) =
|
||||
## Writes a byte to the stream as two hex characters
|
||||
stream.write(bitsToHex(b shr 4))
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
## The main module. Provides some tests.
|
||||
|
||||
import
|
||||
std/[random, streams, os],
|
||||
std/[random, streams, os, strformat],
|
||||
unittest2,
|
||||
../eth_verkle/[utils, math],
|
||||
../eth_verkle/[config, utils, math],
|
||||
../eth_verkle/tree/[tree, operations, commitment]
|
||||
|
||||
suite "main":
|
||||
|
@ -45,11 +45,26 @@ suite "main":
|
|||
|
||||
let deleteKvps = @[
|
||||
"1100000000000000000000000000000000000000000000000000000000010000",
|
||||
"2211000000000000000000000000000000000000000000000000000000000000",
|
||||
"5500000000000000000000000000000000000000000000000000000000001100"
|
||||
"3300000000000000000000000000000000000000000000000000000000000001",
|
||||
"5500000000000000000000000000000000000000000000000000000000000000",
|
||||
"5500000000000000000000000000000000000000000000000000000000001100",
|
||||
]
|
||||
const expectedRootCommitment3 = "1b0b20e55d30cbd3538f98a194d955aa74b77342196046e70d68f458a7f6d084"
|
||||
## Matches go-verkle commitment
|
||||
const expectedRootCommitment3 = "4145d957eb624cb56af3861ebe0db2e9fee2de523b19aad55acf9085eb7cd158"
|
||||
|
||||
|
||||
const finalKvps = @[
|
||||
("0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000011"),
|
||||
("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", "0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
("1100000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000003"),
|
||||
("2200000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000004"),
|
||||
("2211000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000005"),
|
||||
("3300000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000006"),
|
||||
("33000000000000000000000000000000000000000000000000000000000000ff", "0000000000000000000000000000000000000000000000000000000000000008"),
|
||||
("4400000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000009"),
|
||||
("4400000011000000000000000000000000000000000000000000000000000000", "000000000000000000000000000000000000000000000000000000000000000a"),
|
||||
("4400000011000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000013"),
|
||||
]
|
||||
|
||||
|
||||
|
||||
iterator hexKvpsToBytes32(kvps: openArray[tuple[key: string, value: string]]):
|
||||
|
@ -61,6 +76,7 @@ suite "main":
|
|||
test "sanity":
|
||||
|
||||
# Populate tree and check root commitment
|
||||
when TraceLogs: echo "\n\n\nPopulating tree\n"
|
||||
var tree = newBranchesNode()
|
||||
for (key, value) in sampleKvps.hexKvpsToBytes32():
|
||||
tree.setValue(key, value)
|
||||
|
@ -69,6 +85,7 @@ suite "main":
|
|||
check tree.serializeCommitment.toHex == expectedRootCommitment1
|
||||
|
||||
# Update some nodes in the tree and check updated root commitment
|
||||
when TraceLogs: echo "\n\n\nAdding and modfying some key/values in the tree\n"
|
||||
for (key, value) in updateKvps.hexKvpsToBytes32():
|
||||
tree.setValue(key, value)
|
||||
tree.updateAllCommitments()
|
||||
|
@ -76,13 +93,25 @@ suite "main":
|
|||
check tree.serializeCommitment.toHex == expectedRootCommitment2
|
||||
|
||||
# Delete some nodes in the tree and check updated root commitment
|
||||
when TraceLogs: echo "\n\n\nDeleting some key/values in the tree\n"
|
||||
for hexKey in deleteKvps:
|
||||
when TraceLogs: echo &"Deleting key {hexKey}"
|
||||
let key = hexToBytesArray[32](hexKey)
|
||||
check tree.deleteValue(key) == true
|
||||
tree.updateAllCommitments()
|
||||
#tree.printTree(newFileStream(stdout))
|
||||
#check tree.serializeCommitment.toHex == expectedRootCommitment3
|
||||
# Note: currently fails since we don't deep-delete values like go-verkle does
|
||||
check tree.serializeCommitment.toHex == expectedRootCommitment3
|
||||
|
||||
# Populate a new tree with just the values remaining in the step above;
|
||||
# we expect the same commitment
|
||||
when TraceLogs: echo "\n\n\nCreating new tree with final key/values from steps above, structure and commitments should match previous step\n"
|
||||
var tree2 = newBranchesNode()
|
||||
for (key, value) in finalKvps.hexKvpsToBytes32():
|
||||
when TraceLogs: echo &"Adding {key.toHex} = {value.toHex}"
|
||||
tree2.setValue(key, value)
|
||||
tree2.updateAllCommitments()
|
||||
#tree2.printTree(newFileStream(stdout))
|
||||
check tree2.serializeCommitment.toHex == expectedRootCommitment3
|
||||
|
||||
|
||||
# test "testDelNonExistingValues":
|
||||
|
|
Loading…
Reference in New Issue