remove binary tries (#736)

Binary tries were an early research idea but are no longer part of any
plausible etheruem roadmap, hence we remove them.
This commit is contained in:
Jacek Sieka 2024-09-30 12:59:16 +02:00 committed by GitHub
parent 00f5fb1fe0
commit b49df0a71a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1 additions and 1715 deletions

View File

@ -4,332 +4,3 @@ Nim Implementation of the Ethereum Trie structure
## Hexary Trie
## Binary Trie
Binary-trie is a dictionary-like data structure to store key-value pair.
Much like it's sibling Hexary-trie, the key-value pair will be stored into key-value flat-db.
The primary difference with Hexary-trie is, each node of Binary-trie only consist of one or two child,
while Hexary-trie node can contains up to 16 or 17 child-nodes.
Unlike Hexary-trie, Binary-trie store it's data into flat-db without using rlp encoding.
Binary-trie store its value using simple **Node-Types** encoding.
The encoded-node will be hashed by keccak_256 and the hash value will be the key to flat-db.
Each entry in the flat-db will looks like:
| key | value |
|----------------------|--------------------------------------------|
| 32-bytes-keccak-hash | encoded-node(KV or BRANCH or LEAF encoded) |
### Node-Types
* KV = [0, encoded-key-path, 32 bytes hash of child]
* BRANCH = [1, 32 bytes hash of left child, 32 bytes hash of right child]
* LEAF = [2, value]
The KV node can have BRANCH node or LEAF node as it's child, but cannot a KV node.
The internal algorithm will merge a KV(parent)->KV(child) into one KV node.
Every KV node contains encoded keypath to reduce the number of blank nodes.
The BRANCH node can have KV, BRANCH, or LEAF node as it's children.
The LEAF node is the terminal node, it contains the value of a key.
### encoded-key-path
While Hexary-trie encode the path using Hex-Prefix encoding, Binary-trie
encode the path using binary encoding, the scheme looks like this table below.
```text
|--------- odd --------|
00mm yyyy xxxx xxxx xxxx xxxx
|------ even -----|
1000 00mm yyyy xxxx xxxx xxxx
```
| symbol | explanation |
|--------|--------------------------|
| xxxx | nibble of binary keypath in bits, 0 = left, 1 = right|
| yyyy | nibble contains 0-3 bits padding + binary keypath |
| mm | number of binary keypath bits modulo 4 (0-3) |
| 00 | zero zero prefix |
| 1000 | even numbered nibbles prefix |
if there is no padding, then yyyy bit sequence is absent, mm also zero.
yyyy = mm bits + padding bits must be 4 bits length.
### The API
The primary API for Binary-trie is `set` and `get`.
* set(key, value) --- _store a value associated with a key_
* get(key): value --- _get a value using a key_
Both `key` and `value` are of `seq[byte]` type. And they cannot have zero length.
Getting a non-existent key will return zero length seq[byte].
Binary-trie also provide dictionary syntax API for `set` and `get`.
* trie[key] = value -- same as `set`
* value = trie[key] -- same as `get`
* contains(key) a.k.a. `in` operator
Additional APIs are:
* exists(key) -- returns `bool`, to check key-value existence -- same as contains
* delete(key) -- remove a key-value from the trie
* deleteSubtrie(key) -- remove a key-value from the trie plus all of it's subtrie
that starts with the same key prefix
* rootNode() -- get root node
* rootNode(node) -- replace the root node
* getRootHash(): `Hash32` with `seq[byte]` type
* getDB(): `DB` -- get flat-db pointer
Constructor API:
* initBinaryTrie(DB, rootHash[optional]) -- rootHash has `seq[byte]` or Hash32 type
* init(BinaryTrie, DB, rootHash[optional])
Normally you would not set the rootHash when constructing an empty Binary-trie.
Setting the rootHash occurred in a scenario where you have a populated DB
with existing trie structure and you know the rootHash,
and then you want to continue/resume the trie operations.
## Examples
```Nim
import
eth/trie/[db, binary, utils]
var db = newMemoryDB()
var trie = initBinaryTrie(db)
trie.set("key1", "value1")
trie.set("key2", "value2")
doAssert trie.get("key1") == "value1".toBytes
doAssert trie.get("key2") == "value2".toBytes
# delete all subtrie with key prefixes "key"
trie.deleteSubtrie("key")
doAssert trie.get("key1") == []
doAssert trie.get("key2") == []]
trie["moon"] = "sun"
doAssert "moon" in trie
doAssert trie["moon"] == "sun".toBytes
```
Remember, `set` and `get` are trie operations. A single `set` operation may invoke
more than one store/lookup operation into the underlying DB. The same is also happened to `get` operation,
it could do more than one flat-db lookup before it return the requested value.
## The truth behind a lie
What kind of lie? actually, `delete` and `deleteSubtrie` doesn't remove the
'deleted' node from the underlying DB. It only make the node inaccessible
from the user of the trie. The same also happened if you update the value of a key,
the old value node is not removed from the underlying DB.
A more subtle lie also happened when you add new entries into the trie using `set` operation.
The previous hash of affected branch become obsolete and replaced by new hash,
the old hash become inaccessible to the user.
You may think that is a waste of storage space.
Luckily, we also provide some utilities to deal with this situation, the branch utils.
## The branch utils
The branch utils consist of these API:
* checkIfBranchExist(DB; rootHash; keyPrefix): bool
* getBranch(DB; rootHash; key): branch
* isValidBranch(branch, rootHash, key, value): bool
* getWitness(DB; nodeHash; key): branch
* getTrieNodes(DB; nodeHash): branch
`keyPrefix`, `key`, and `value` are bytes container with length greater than zero.
They can be openArray[byte].
`rootHash` and `nodeHash` also bytes container,
but they have constraint: must be 32 bytes in length, and it must be a keccak_256 hash value.
`branch` is a list of nodes, or in this case a `seq[seq[byte]]`.
A list? yes, the structure is stored along with the encoded node.
Therefore a list is enough to reconstruct the entire trie/branch.
```Nim
import
eth/trie/[db, binary, utils]
var db = newMemoryDB()
var trie = initBinaryTrie(db)
trie.set("key1", "value1")
trie.set("key2", "value2")
doAssert checkIfBranchExist(db, trie.getRootHash(), "key") == true
doAssert checkIfBranchExist(db, trie.getRootHash(), "key1") == true
doAssert checkIfBranchExist(db, trie.getRootHash(), "ken") == false
doAssert checkIfBranchExist(db, trie.getRootHash(), "key123") == false
```
The tree will looks like:
```text
root ---> A(kvnode, *common key prefix*)
|
|
|
B(branchnode)
/ \
/ \
/ \
C1(kvnode, *remain kepath*) C2(kvnode, *remain kepath*)
| |
| |
| |
D1(leafnode, b'value1') D2(leafnode, b'value2')
```
```Nim
var branchA = getBranch(db, trie.getRootHash(), "key1")
# ==> [A, B, C1, D1]
var branchB = getBranch(db, trie.getRootHash(), "key2")
# ==> [A, B, C2, D2]
doAssert isValidBranch(branchA, trie.getRootHash(), "key1", "value1") == true
# wrong key, return zero bytes
doAssert isValidBranch(branchA, trie.getRootHash(), "key5", "") == true
doAssert isValidBranch(branchB, trie.getRootHash(), "key1", "value1") # InvalidNode
var x = getBranch(db, trie.getRootHash(), "key")
# ==> [A]
x = getBranch(db, trie.getRootHash(), "key123") # InvalidKeyError
x = getBranch(db, trie.getRootHash(), "key5") # there is still branch for non-exist key
# ==> [A]
var branch = getWitness(db, trie.getRootHash(), "key1")
# equivalent to `getBranch(db, trie.getRootHash(), "key1")`
# ==> [A, B, C1, D1]
branch = getWitness(db, trie.getRootHash(), "key")
# this will include additional nodes of "key2"
# ==> [A, B, C1, D1, C2, D2]
var wholeTrie = getWitness(db, trie.getRootHash(), "")
# this will return the whole trie
# ==> [A, B, C1, D1, C2, D2]
var node = branch[1] # B
let nodeHash = keccak256.digest(node.baseAddr, uint(node.len))
var nodes = getTrieNodes(db, nodeHash)
doAssert nodes.len == wholeTrie.len - 1
# ==> [B, C1, D1, C2, D2]
```
## Remember the lie?
Because trie `delete`, `deleteSubtrie` and `set` operation create inaccessible nodes in the underlying DB,
we need to remove them if necessary. We already see that `wholeTrie = getWitness(db, trie.getRootHash(), "")`
will return the whole trie, a list of accessible nodes.
Then we can write the clean tree into a new DB instance to replace the old one.
## Sparse Merkle Trie
Sparse Merkle Trie(SMT) is a variant of Binary Trie which uses binary encoding to
represent path during trie traversal. When Binary Trie uses three types of node,
SMT only use one type of node without any additional special encoding to store it's key-path.
Actually, it doesn't even store it's key-path anywhere like Binary Trie,
the key-path is stored implicitly in the trie structure during key-value insertion.
Because the key-path is not encoded in any special ways, the bits can be extracted directly from
the key without any conversion.
However, the key restricted to a fixed length because the algorithm demand a fixed height trie
to works properly. In this case, the trie height is limited to 160 level,
or the key is of fixed length 20 bytes (8 bits x 20 = 160).
To be able to use variable length key, the algorithm can be adapted slightly using hashed key before
constructing the binary key-path. For example, if using keccak256 as the hashing function,
then the height of the tree will be 256, but the key itself can be any length.
### The API
The primary API for Binary-trie is `set` and `get`.
* set(key, value, rootHash[optional]) --- _store a value associated with a key_
* get(key, rootHash[optional]): value --- _get a value using a key_
Both `key` and `value` are of `BytesRange` type. And they cannot have zero length.
You can also use convenience API `get` and `set` which accepts
`Bytes` or `string` (a `string` is conceptually wrong in this context
and may costlier than a `BytesRange`, but it is good for testing purpose).
rootHash is an optional parameter. When used, `get` will get a key from specific root,
and `set` will also set a key at specific root.
Getting a non-existent key will return zero length BytesRange or a zeroBytesRange.
Sparse Merkle Trie also provide dictionary syntax API for `set` and `get`.
* trie[key] = value -- same as `set`
* value = trie[key] -- same as `get`
* contains(key) a.k.a. `in` operator
Additional APIs are:
* exists(key) -- returns `bool`, to check key-value existence -- same as contains
* delete(key) -- remove a key-value from the trie
* getRootHash(): `KeccakHash` with `BytesRange` type
* getDB(): `DB` -- get flat-db pointer
* prove(key, rootHash[optional]): proof -- useful for merkling
Constructor API:
* initSparseBinaryTrie(DB, rootHash[optional])
* init(SparseBinaryTrie, DB, rootHash[optional])
Normally you would not set the rootHash when constructing an empty Sparse Merkle Trie.
Setting the rootHash occurred in a scenario where you have a populated DB
with existing trie structure and you know the rootHash,
and then you want to continue/resume the trie operations.
## Examples
```Nim
import
eth/trie/[db, sparse_binary, utils]
var
db = newMemoryDB()
trie = initSparseMerkleTrie(db)
let
key1 = "01234567890123456789"
key2 = "abcdefghijklmnopqrst"
trie.set(key1, "value1")
trie.set(key2, "value2")
doAssert trie.get(key1) == "value1".toBytes
doAssert trie.get(key2) == "value2".toBytes
trie.delete(key1)
doAssert trie.get(key1) == []
trie.delete(key2)
doAssert trie[key2] == []
```
Remember, `set` and `get` are trie operations. A single `set` operation may invoke
more than one store/lookup operation into the underlying DB. The same is also happened to `get` operation,
it could do more than one flat-db lookup before it return the requested value.
While Binary Trie perform a variable numbers of lookup and store operations, Sparse Merkle Trie
will do constant numbers of lookup and store operations each `get` and `set` operation.
## Merkle Proofing
Using ``prove`` dan ``verifyProof`` API, we can do some merkling with SMT.
```Nim
let
value1 = "hello world"
badValue = "bad value"
trie[key1] = value1
var proof = trie.prove(key1)
doAssert verifyProof(proof, trie.getRootHash(), key1, value1) == true
doAssert verifyProof(proof, trie.getRootHash(), key1, badValue) == false
doAssert verifyProof(proof, trie.getRootHash(), key2, value1) == false
```

View File

@ -1,143 +0,0 @@
import
std/sequtils,
stew/ptrops,
"."/[trie_defs, trie_bitseq]
type
TrieNodeKind* = enum
KV_TYPE = 0
BRANCH_TYPE = 1
LEAF_TYPE = 2
TrieNodeKey* = seq[byte]
TrieNode* = object
case kind*: TrieNodeKind
of KV_TYPE:
keyPath*: TrieBitSeq
child*: TrieNodeKey
of BRANCH_TYPE:
leftChild*: TrieNodeKey
rightChild*: TrieNodeKey
of LEAF_TYPE:
value*: seq[byte]
InvalidNode* = object of CorruptedTrieDatabase
ValidationError* = object of CorruptedTrieDatabase
# ----------------------------------------------
template sliceToEnd*(r: TrieBitSeq, index: int): TrieBitSeq =
if r.len <= index: TrieBitSeq() else: r[index .. ^1]
proc decodeToBinKeypath*(path: seq[byte]): TrieBitSeq =
## Decodes bytes into a sequence of 0s and 1s
## Used in decoding key path of a KV-NODE
var path = path.bits
if path[0]:
path = path[4..^1]
doAssert path[0] == false
doAssert path[1] == false
var bits = path[2].int shl 1
bits = bits or path[3].int
if path.len > 4:
path[4+((4 - bits) mod 4)..^1]
else:
TrieBitSeq()
proc parseNode*(node: openArray[byte]): TrieNode =
# Input: a serialized node
if node.len == 0:
raise newException(InvalidNode, "Blank node is not a valid node type in Binary Trie")
if node[0].ord < low(TrieNodeKind).ord or node[0].ord > high(TrieNodeKind).ord:
raise newException(InvalidNode, "Invalid node type")
let nodeType = node[0].TrieNodeKind
case nodeType
of BRANCH_TYPE:
if node.len != 65:
raise newException(InvalidNode, "Invalid branch node, both child node should be 32 bytes long each")
# Output: node type, left child, right child
result = TrieNode(kind: BRANCH_TYPE, leftChild: node[1..<33], rightChild: node[33..^1])
doAssert(result.leftChild.len == 32)
doAssert(result.rightChild.len == 32)
return result
of KV_TYPE:
if node.len <= 33:
raise newException(InvalidNode, "Invalid kv node, short of key path or child node hash")
# Output: node type, keypath, child
return TrieNode(kind: KV_TYPE, keyPath: decodeToBinKeypath(node[1..^33]), child: node[^32..^1])
of LEAF_TYPE:
if node.len == 1:
raise newException(InvalidNode, "Invalid leaf node, can not contain empty value")
# Output: node type, value
return TrieNode(kind: LEAF_TYPE, value: node[1..^1])
proc encodeKVNode*(keyPath: TrieBitSeq, childHash: TrieNodeKey): seq[byte] =
## Serializes a key/value node
if keyPath.len == 0:
raise newException(ValidationError, "Key path can not be empty")
if childHash.len != 32:
raise newException(ValidationError, "Invalid hash len")
# Encodes a sequence of 0s and 1s into tightly packed bytes
# Used in encoding key path of a KV-NODE
# KV-NODE = KV-TYPE-PREFIX + encoded keypath + 32 bytes hash
let
len = keyPath.len
padding = ((not len) + 1) and 3 # modulo 4 padding
paddedBinLen = len + padding
prefix = len mod 4
result = newSeq[byte](((len + padding) div 8) + 34)
result[0] = KV_TYPE.byte
if paddedBinLen mod 8 == 4:
var nbits = 4 - padding
result[1] = byte(prefix shl 4) or byte.fromBits(keyPath, 0, nbits)
for i in 0..<(len div 8):
result[i+2] = byte.fromBits(keyPath, nbits, 8)
inc(nbits, 8)
else:
var nbits = 8 - padding
result[1] = byte(0b1000_0000) or byte(prefix)
result[2] = byte.fromBits(keyPath, 0, nbits)
for i in 0..<((len-1) div 8):
result[i+3] = byte.fromBits(keyPath, nbits, 8)
inc(nbits, 8)
copyMem(result[^32].addr, childHash.baseAddr, 32)
proc encodeKVNode*(keyPath: bool, childHash: TrieNodeKey): seq[byte] =
result = newSeq[byte](34)
result[0] = KV_TYPE.byte
result[1] = byte(16) or byte(keyPath)
copyMem(result[^32].addr, childHash.baseAddr, 32)
proc encodeBranchNode*(leftChildHash, rightChildHash: TrieNodeKey): seq[byte] =
## Serializes a branch node
const
BRANCH_TYPE_PREFIX = @[BRANCH_TYPE.byte]
if leftChildHash.len != 32 or rightChildHash.len != 32:
raise newException(ValidationError, "encodeBranchNode: Invalid hash len")
result = BRANCH_TYPE_PREFIX.concat(leftChildHash, rightChildHash)
proc encodeLeafNode*(value: openArray[byte]): seq[byte] =
## Serializes a leaf node
const
LEAF_TYPE_PREFIX = @[LEAF_TYPE.byte]
if value.len == 0:
raise newException(ValidationError, "Value of leaf node can not be empty")
result = LEAF_TYPE_PREFIX.concat(@value)
proc getCommonPrefixLength*(a, b: TrieBitSeq): int =
let len = min(a.len, b.len)
for i in 0..<len:
if a[i] != b[i]: return i
result = len

View File

@ -1,284 +0,0 @@
import
"."/[trie_bitseq, trie_defs, db, binaries, trie_utils]
export
trie_utils
type
DB = TrieDatabaseRef
BinaryTrie* = object
db: DB
rootHash: TrieNodeKey
NodeOverrideError* = object of CatchableError
const
zeroHash* = default(seq[byte])
proc init*(x: typedesc[BinaryTrie], db: DB,
rootHash: openArray[byte]): BinaryTrie =
checkValidHashZ(rootHash)
result.db = db
result.rootHash = @(rootHash)
proc getDB*(t: BinaryTrie): auto = t.db
proc initBinaryTrie*(db: DB, rootHash: openArray[byte]): BinaryTrie =
init(BinaryTrie, db, rootHash)
proc initBinaryTrie*(db: DB): BinaryTrie =
init(BinaryTrie, db, zeroHash)
proc getRootHash*(self: BinaryTrie): TrieNodeKey {.inline.} =
self.rootHash
template fetchNode(self: BinaryTrie, nodeHash: TrieNodeKey): TrieNode =
doAssert(nodeHash.len == 32)
parseNode self.db.get(nodeHash)
proc getAux(self: BinaryTrie, nodeHash: TrieNodeKey, keyPath: TrieBitSeq): seq[byte] =
# Empty trie
if isZeroHash(nodeHash):
return
let node = self.fetchNode(nodeHash)
# Key-value node descend
if node.kind == LEAF_TYPE:
if keyPath.len != 0: return
return node.value
elif node.kind == KV_TYPE:
# keyPath too short
if keyPath.len == 0: return
let sliceLen = min(node.keyPath.len, keyPath.len)
if keyPath[0..<sliceLen] == node.keyPath:
return self.getAux(node.child, keyPath.sliceToEnd(node.keyPath.len))
else:
return
# Branch node descend
elif node.kind == BRANCH_TYPE:
# keyPath too short
if keyPath.len == 0: return
if keyPath[0]: # first bit == 1
return self.getAux(node.rightChild, keyPath.sliceToEnd(1))
else:
return self.getAux(node.leftChild, keyPath.sliceToEnd(1))
proc get*(self: BinaryTrie, key: openArray[byte]): seq[byte] {.inline.} =
var keyBits = key.bits
return self.getAux(self.rootHash, keyBits)
proc hashAndSave*(self: BinaryTrie, node: openArray[byte]): TrieNodeKey =
result = @(keccak256(node).data)
self.db.put(result, node)
template saveKV(self: BinaryTrie, keyPath: TrieBitSeq | bool, child: openArray[byte]): untyped =
self.hashAndSave(encodeKVNode(keyPath, child))
template saveLeaf(self: BinaryTrie, value: openArray[byte]): untyped =
self.hashAndSave(encodeLeafNode(value))
template saveBranch(self: BinaryTrie, L, R: openArray[byte]): untyped =
self.hashAndSave(encodeBranchNode(L, R))
proc setBranchNode(self: BinaryTrie, keyPath: TrieBitSeq, node: TrieNode,
value: openArray[byte], deleteSubtrie = false): TrieNodeKey
proc setKVNode(self: BinaryTrie, keyPath: TrieBitSeq, nodeHash: TrieNodeKey,
node: TrieNode, value: openArray[byte], deleteSubtrie = false): TrieNodeKey
const
overrideErrorMsg =
"Fail to set the value because the prefix of it's key is the same as existing key"
proc setAux(self: BinaryTrie, nodeHash: TrieNodeKey, keyPath: TrieBitSeq,
value: openArray[byte], deleteSubtrie = false): TrieNodeKey =
## If deleteSubtrie is set to True, what it will do is that it take in a keyPath
## and traverse til the end of keyPath, then delete the whole subtrie of that node.
## Note: keyPath should be in binary array format, i.e., encoded by encode_to_bin()
template checkBadKeyPath(): untyped =
# keyPath too short
if keyPath.len == 0:
if deleteSubtrie: return zeroHash
else: raise newException(NodeOverrideError, overrideErrorMsg)
template ifGoodValue(body: untyped): untyped =
if value.len != 0: body
else: return zeroHash
# Empty trie
if isZeroHash(nodeHash):
ifGoodValue:
return self.saveKV(keyPath, self.saveLeaf(value))
let node = self.fetchNode(nodeHash)
case node.kind
of LEAF_TYPE: # Node is a leaf node
# keyPath must match, there should be no remaining keyPath
if keyPath.len != 0:
raise newException(NodeOverrideError, overrideErrorMsg)
if deleteSubtrie: return zeroHash
ifGoodValue:
return self.saveLeaf(value)
of KV_TYPE: # node is a key-value node
checkBadKeyPath()
return self.setKVNode(keyPath, nodeHash, node, value, deleteSubtrie)
of BRANCH_TYPE: # node is a branch node
checkBadKeyPath()
return self.setBranchNode(keyPath, node, value, deleteSubtrie)
proc set*(self: var BinaryTrie, key, value: openArray[byte]) {.inline.} =
## Sets the value at the given keyPath from the given node
## Key will be encoded into binary array format first.
var keyBits = key.bits
self.rootHash = self.setAux(self.rootHash, keyBits, value)
proc setBranchNode(self: BinaryTrie, keyPath: TrieBitSeq, node: TrieNode,
value: openArray[byte], deleteSubtrie = false): TrieNodeKey =
# Which child node to update? Depends on first bit in keyPath
var newLeftChild, newRightChild: TrieNodeKey
if keyPath[0]: # first bit == 1
newRightChild = self.setAux(node.rightChild, keyPath[1..^1], value, deleteSubtrie)
newLeftChild = node.leftChild
else:
newLeftChild = self.setAux(node.leftChild, keyPath[1..^1], value, deleteSubtrie)
newRightChild = node.rightChild
let blankLeft = isZeroHash(newLeftChild)
# Compress branch node into kv node
if blankLeft or isZeroHash(newRightChild):
let childNode = if blankLeft: newRightChild else: newLeftChild
var subNode = self.fetchNode(childNode)
# Compress (k1, (k2, NODE)) -> (k1 + k2, NODE)
if subNode.kind == KV_TYPE:
# exploit subNode.keyPath unused prefix bit
# to avoid bitVector concat
subNode.keyPath.pushFront(blankLeft)
result = self.saveKV(subNode.keyPath, subNode.child)
# kv node pointing to a branch node
elif subNode.kind in {BRANCH_TYPE, LEAF_TYPE}:
result = self.saveKV(blankLeft, childNode)
else:
result = self.saveBranch(newLeftChild, newRightChild)
proc setKVNode(self: BinaryTrie, keyPath: TrieBitSeq, nodeHash: TrieNodeKey,
node: TrieNode, value: openArray[byte], deleteSubtrie = false): TrieNodeKey =
# keyPath prefixes match
if deleteSubtrie:
if keyPath.len < node.keyPath.len and keyPath == node.keyPath[0..<keyPath.len]:
return zeroHash
let sliceLen = min(node.keyPath.len, keyPath.len)
if keyPath[0..<sliceLen] == node.keyPath:
# Recurse into child
let subNodeHash = self.setAux(node.child,
keyPath.sliceToEnd(node.keyPath.len), value, deleteSubtrie)
# If child is empty
if isZeroHash(subNodeHash):
return zeroHash
let subNode = self.fetchNode(subNodeHash)
# If the child is a key-value node, compress together the keyPaths
# into one node
if subNode.kind == KV_TYPE:
return self.saveKV(node.keyPath & subNode.keyPath, subNode.child)
else:
return self.saveKV(node.keyPath, subNodeHash)
# keyPath prefixes don't match. Here we will be converting a key-value node
# of the form (k, CHILD) into a structure of one of the following forms:
# 1. (k[:-1], (NEWCHILD, CHILD))
# 2. (k[:-1], ((k2, NEWCHILD), CHILD))
# 3. (k1, ((k2, CHILD), NEWCHILD))
# 4. (k1, ((k2, CHILD), (k2', NEWCHILD))
# 5. (CHILD, NEWCHILD)
# 6. ((k[1:], CHILD), (k', NEWCHILD))
# 7. ((k[1:], CHILD), NEWCHILD)
# 8. (CHILD, (k[1:], NEWCHILD))
else:
let
commonPrefixLen = getCommonPrefixLength(node.keyPath, keyPath[0..<sliceLen])
cplenPlusOne = commonPrefixLen + 1
# New key-value pair can not contain empty value
# Or one can not delete non-exist subtrie
if value.len == 0 or deleteSubtrie: return nodeHash
var valNode, oldNode, newSub: TrieNodeKey
# valnode: the child node that has the new value we are adding
# Case 1: keyPath prefixes almost match, so we are in case (1), (2), (5), (6)
if keyPath.len == cplenPlusOne:
valNode = self.saveLeaf(value)
# Case 2: keyPath prefixes mismatch in the middle, so we need to break
# the keyPath in half. We are in case (3), (4), (7), (8)
else:
if keyPath.len <= commonPrefixLen:
raise newException(NodeOverrideError, overrideErrorMsg)
valNode = self.saveKV(keyPath[cplenPlusOne..^1], self.saveLeaf(value))
# oldnode: the child node the has the old child value
# Case 1: (1), (3), (5), (6)
if node.keyPath.len == cplenPlusOne:
oldNode = node.child
# (2), (4), (6), (8)
else:
oldNode = self.saveKV(node.keyPath[cplenPlusOne..^1], node.child)
# Create the new branch node (because the key paths diverge, there has to
# be some "first bit" at which they diverge, so there must be a branch
# node somewhere)
if keyPath[commonPrefixLen]: # first bit == 1
newSub = self.saveBranch(oldNode, valNode)
else:
newSub = self.saveBranch(valNode, oldNode)
# Case 1: keyPath prefixes match in the first bit, so we still need
# a kv node at the top
# (1) (2) (3) (4)
if commonPrefixLen != 0:
return self.saveKV(node.keyPath[0..<commonPrefixLen], newSub)
# Case 2: keyPath prefixes diverge in the first bit, so we replace the
# kv node with a branch node
# (5) (6) (7) (8)
else:
return newSub
template exists*(self: BinaryTrie, key: openArray[byte]): bool =
self.get(key) != []
proc delete*(self: var BinaryTrie, key: openArray[byte]) {.inline.} =
## Equals to setting the value to zeroBytesRange
var keyBits = key.bits
self.rootHash = self.setAux(self.rootHash, keyBits, [])
proc deleteSubtrie*(self: var BinaryTrie, key: openArray[byte]) {.inline.} =
## Given a key prefix, delete the whole subtrie that starts with the key prefix.
## Key will be encoded into binary array format first.
## It will call `setAux` with `deleteSubtrie` set to true.
var keyBits = key.bits
self.rootHash = self.setAux(self.rootHash, keyBits, [], true)
# Convenience
proc rootNode*(self: BinaryTrie): seq[byte] {.inline.} =
self.db.get(self.rootHash)
proc rootNode*(self: var BinaryTrie, node: openArray[byte]) {.inline.} =
self.rootHash = self.hashAndSave(node)
# Dictionary API
template `[]`*(self: BinaryTrie, key: seq[byte]): seq[byte] =
self.get(key)
template `[]=`*(self: var BinaryTrie, key, value: seq[byte]) =
self.set(key, value)
template contains*(self: BinaryTrie, key: seq[byte]): bool =
self.exists(key)

View File

@ -1,168 +0,0 @@
import
"."/[trie_defs, binary, binaries, db, trie_utils, trie_bitseq]
type
DB = TrieDatabaseRef
# TODO: replace the usages of this with regular asserts
InvalidKeyError* = object of Defect
template query(db: DB, nodeHash: TrieNodeKey): seq[byte] =
db.get(nodeHash)
proc checkIfBranchExistImpl(db: DB; nodeHash: TrieNodeKey; keyPrefix: TrieBitSeq): bool =
if nodeHash == zeroHash:
return false
let node = parseNode(db.query(nodeHash))
case node.kind:
of LEAF_TYPE:
if keyPrefix.len != 0: return false
return true
of KV_TYPE:
if keyPrefix.len == 0: return true
if keyPrefix.len < node.keyPath.len:
if keyPrefix == node.keyPath[0..<keyPrefix.len]: return true
return false
else:
if keyPrefix[0..<node.keyPath.len] == node.keyPath:
return checkIfBranchExistImpl(db, node.child, keyPrefix.sliceToEnd(node.keyPath.len))
return false
of BRANCH_TYPE:
if keyPrefix.len == 0: return true
if keyPrefix[0] == false:
return checkIfBranchExistImpl(db, node.leftChild, keyPrefix.sliceToEnd(1))
else:
return checkIfBranchExistImpl(db, node.rightChild, keyPrefix.sliceToEnd(1))
proc checkIfBranchExist*(db: DB; rootHash: TrieNodeKey, keyPrefix: openArray[byte]): bool =
## Given a key prefix, return whether this prefix is
## the prefix of an existing key in the trie.
checkValidHashZ(rootHash)
var keyPrefixBits = bits keyPrefix
checkIfBranchExistImpl(db, rootHash, keyPrefixBits)
proc getBranchImpl(db: DB; nodeHash: TrieNodeKey, keyPath: TrieBitSeq, output: var seq[seq[byte]]) =
if nodeHash == zeroHash: return
let nodeVal = db.query(nodeHash)
let node = parseNode(nodeVal)
case node.kind
of LEAF_TYPE:
if keyPath.len == 0:
output.add nodeVal
else:
raise newException(InvalidKeyError, "Key too long")
of KV_TYPE:
if keyPath.len == 0:
raise newException(InvalidKeyError, "Key too short")
output.add nodeVal
let sliceLen = min(keyPath.len, node.keyPath.len)
if keyPath[0..<sliceLen] == node.keyPath:
getBranchImpl(db, node.child, keyPath.sliceToEnd(sliceLen), output)
of BRANCH_TYPE:
if keyPath.len == 0:
raise newException(InvalidKeyError, "Key too short")
output.add nodeVal
if keyPath[0] == false:
getBranchImpl(db, node.leftChild, keyPath.sliceToEnd(1), output)
else:
getBranchImpl(db, node.rightChild, keyPath.sliceToEnd(1), output)
proc getBranch*(db: DB; rootHash: seq[byte]; key: openArray[byte]): seq[seq[byte]] =
## Get a long-format Merkle branch
checkValidHashZ(rootHash)
result = @[]
var keyBits = bits key
getBranchImpl(db, rootHash, keyBits, result)
proc isValidBranch*(branch: seq[seq[byte]], rootHash: seq[byte], key, value: openArray[byte]): bool =
checkValidHashZ(rootHash)
# branch must not be empty
doAssert(branch.len != 0)
var db = newMemoryDB()
for node in branch:
doAssert(node.len != 0)
let nodeHash = keccak256(node)
db.put(nodeHash.data, node)
var trie = initBinaryTrie(db, rootHash)
result = trie.get(key) == value
proc getTrieNodesImpl(db: DB; nodeHash: TrieNodeKey, output: var seq[seq[byte]]): bool =
## Get full trie of a given root node
if nodeHash.isZeroHash(): return false
var nodeVal: seq[byte]
if nodeHash in db:
nodeVal = db.query(nodeHash)
else:
return false
let node = parseNode(nodeVal)
case node.kind
of KV_TYPE:
output.add nodeVal
result = getTrieNodesImpl(db, node.child, output)
of BRANCH_TYPE:
output.add nodeVal
result = getTrieNodesImpl(db, node.leftChild, output)
result = getTrieNodesImpl(db, node.rightChild, output)
of LEAF_TYPE:
output.add nodeVal
proc getTrieNodes*(db: DB; nodeHash: TrieNodeKey): seq[seq[byte]] =
checkValidHashZ(nodeHash)
result = @[]
discard getTrieNodesImpl(db, nodeHash, result)
proc getWitnessImpl*(db: DB; nodeHash: TrieNodeKey; keyPath: TrieBitSeq; output: var seq[seq[byte]]) =
if keyPath.len == 0:
if not getTrieNodesImpl(db, nodeHash, output): return
if nodeHash.isZeroHash(): return
var nodeVal: seq[byte]
if nodeHash in db:
nodeVal = db.query(nodeHash)
else:
return
let node = parseNode(nodeVal)
case node.kind
of LEAF_TYPE:
if keyPath.len != 0:
raise newException(InvalidKeyError, "Key too long")
of KV_TYPE:
output.add nodeVal
if keyPath.len < node.keyPath.len and node.keyPath[0..<keyPath.len] == keyPath:
if not getTrieNodesImpl(db, node.child, output): return
elif keyPath[0..<node.keyPath.len] == node.keyPath:
getWitnessImpl(db, node.child, keyPath.sliceToEnd(node.keyPath.len), output)
of BRANCH_TYPE:
output.add nodeVal
if keyPath[0] == false:
getWitnessImpl(db, node.leftChild, keyPath.sliceToEnd(1), output)
else:
getWitnessImpl(db, node.rightChild, keyPath.sliceToEnd(1), output)
proc getWitness*(db: DB; nodeHash: TrieNodeKey; key: openArray[byte]): seq[seq[byte]] =
## Get all witness given a keyPath prefix.
## Include
##
## 1. witness along the keyPath and
## 2. witness in the subtrie of the last node in keyPath
checkValidHashZ(nodeHash)
result = @[]
var keyBits = bits key
getWitnessImpl(db, nodeHash, keyBits, result)

View File

@ -1,129 +0,0 @@
import
stew/bitops2
type
TrieBitSeq* = object
## Bit sequence as used in ethereum tries
data: seq[byte]
start: int
mLen: int ## Length in bits
template `@`(s, idx: untyped): untyped =
(when idx is BackwardsIndex: s.len - int(idx) else: int(idx))
proc bits*(a: seq[byte], start, len: int): TrieBitSeq =
doAssert start <= len
doAssert len <= 8 * a.len
TrieBitSeq(data: a, start: start, mLen: len)
template bits*(a: seq[byte]): TrieBitSeq =
bits(a, 0, a.len * 8)
template bits*(a: seq[byte], len: int): TrieBitSeq =
bits(a, 0, len)
template bits*(a: openArray[byte], start, len: int): TrieBitSeq =
bits(@a, start, len)
template bits*(a: openArray[byte]): TrieBitSeq =
bits(@a, 0, a.len * 8)
template bits*(a: openArray[byte], len: int): TrieBitSeq =
bits(@a, 0, len)
template bits*(x: TrieBitSeq): TrieBitSeq = x
proc len*(r: TrieBitSeq): int = r.mLen
iterator enumerateBits(x: TrieBitSeq): (int, bool) =
var p = x.start
var i = 0
let e = x.len
while i != e:
yield (i, getBitBE(x.data, p))
inc p
inc i
iterator items*(x: TrieBitSeq): bool =
for _, v in enumerateBits(x): yield v
iterator pairs*(x: TrieBitSeq): (int, bool) =
for i, v in enumerateBits(x): yield (i, v)
proc `[]`*(x: TrieBitSeq, idx: int): bool =
doAssert idx < x.len
let p = x.start + idx
result = getBitBE(x.data, p)
proc sliceNormalized(x: TrieBitSeq, ibegin, iend: int): TrieBitSeq =
doAssert ibegin >= 0 and
ibegin < x.len and
iend < x.len and
iend + 1 >= ibegin # the +1 here allows the result to be
# an empty range
result.data = x.data
result.start = x.start + ibegin
result.mLen = iend - ibegin + 1
proc `[]`*(r: TrieBitSeq, s: HSlice): TrieBitSeq =
sliceNormalized(r, r @ s.a, r @ s.b)
proc `==`*(a, b: TrieBitSeq): bool =
if a.len != b.len: return false
for i in 0 ..< a.len:
if a[i] != b[i]: return false
true
proc `[]=`*(r: var TrieBitSeq, idx: Natural, val: bool) =
doAssert idx < r.len
let absIdx = r.start + idx
changeBitBE(r.data, absIdx, val)
proc pushFront*(x: var TrieBitSeq, val: bool) =
doAssert x.start > 0
dec x.start
x[0] = val
inc x.mLen
template neededBytes(nBits: int): int =
(nBits shr 3) + ord((nBits and 0b111) != 0)
static:
doAssert neededBytes(2) == 1
doAssert neededBytes(8) == 1
doAssert neededBytes(9) == 2
proc `&`*(a, b: TrieBitSeq): TrieBitSeq =
let totalLen = a.len + b.len
var bytes = newSeq[byte](totalLen.neededBytes)
result = bits(bytes, 0, totalLen)
for i in 0 ..< a.len: result.data.changeBitBE(i, a[i])
for i in 0 ..< b.len: result.data.changeBitBE(i + a.len, b[i])
proc `$`*(r: TrieBitSeq): string =
result = newStringOfCap(r.len)
for bit in r:
result.add(if bit: '1' else: '0')
proc fromBits*(T: type, r: TrieBitSeq, offset, num: Natural): T =
doAssert(num <= sizeof(T) * 8)
# XXX: Nim has a bug that a typedesc parameter cannot be used
# in a type coercion, so we must define an alias here:
type TT = T
for i in 0 ..< num:
result = (result shl 1) or TT(r[offset + i])
proc parse*(T: type TrieBitSeq, s: string): TrieBitSeq =
var bytes = newSeq[byte](s.len.neededBytes)
for i, c in s:
case c
of '0': discard
of '1': setBitBE(bytes, i)
else: doAssert false
result = bits(bytes, 0, s.len)
proc toBytes*(r: TrieBitSeq): seq[byte] =
r.data[(r.start div 8)..<((r.mLen - r.start + 7) div 8)]

View File

@ -1,10 +0,0 @@
import
./trie_defs
export trie_defs
template checkValidHashZ*(x: untyped) =
doAssert(x.len == 32 or x.len == 0)
template isZeroHash*(x: openArray[byte]): bool =
x.len == 0

View File

@ -1,10 +1,5 @@
import
./test_bin_trie,
./test_binaries_utils,
./test_branches_utils,
./test_examples,
./test_hexary_trie,
./test_json_suite,
./test_transaction_db,
./test_trie_bitseq,
./test_hexary_proof

View File

@ -1,128 +0,0 @@
{.used.}
import
std/random,
unittest2,
stew/byteutils,
../../eth/trie/[db, binary],
./testutils
suite "binary trie":
test "different order insert":
randomize()
var kv_pairs = randKVPair()
var res = zeroHash
for _ in 0..<1: # repeat 3 times
var db = newMemoryDB()
var trie = initBinaryTrie(db)
random.shuffle(kv_pairs)
for i, c in kv_pairs:
trie.set(c.key, c.value)
let x = trie.get(c.key)
let y = c.value
check y == x
check res == zeroHash or trie.getRootHash() == res
res = trie.getRootHash()
# insert already exist key/value
trie.set(kv_pairs[0].key, kv_pairs[0].value)
check trie.getRootHash() == res
# Delete all key/value
random.shuffle(kv_pairs)
for i, c in kv_pairs:
trie.delete(c.key)
check trie.getRootHash() == zeroHash
const delSubtrieData = [
(("\x12\x34\x56\x78", "78"), ("\x12\x34\x56\x79", "79"), "\x12\x34\x56", true, false),
(("\x12\x34\x56\x78", "78"), ("\x12\x34\x56\xff", "ff"), "\x12\x34\x56", true, false),
(("\x12\x34\x56\x78", "78"), ("\x12\x34\x56\x79", "79"), "\x12\x34\x57", false, false),
(("\x12\x34\x56\x78", "78"), ("\x12\x34\x56\x79", "79"), "\x12\x34\x56\x78\x9a", false, true)
]
test "delete subtrie":
for data in delSubtrieData:
var db = newMemoryDB()
var trie = initBinaryTrie(db)
let kv1 = data[0]
let kv2 = data[1]
let key_to_be_deleted = data[2]
let will_delete = data[3]
let will_raise_error = data[4]
# First test case, delete subtrie of a kv node
trie.set(kv1[0].toBytes, kv1[1].toBytes)
trie.set(kv2[0].toBytes, kv2[1].toBytes)
check trie.get(kv1[0].toBytes) == kv1[1].toBytes
check trie.get(kv2[0].toBytes) == kv2[1].toBytes
if will_delete:
trie.deleteSubtrie(key_to_be_deleted.toBytes)
check trie.get(kv1[0].toBytes) == []
check trie.get(kv2[0].toBytes) == []
check trie.getRootHash() == zeroHash
else:
if will_raise_error:
try:
trie.deleteSubtrie(key_to_be_deleted.toBytes)
except NodeOverrideError:
discard
else:
let root_hash_before_delete = trie.getRootHash()
trie.deleteSubtrie(key_to_be_deleted.toBytes)
check trie.get(kv1[0].toBytes) == toBytes(kv1[1])
check trie.get(kv2[0].toBytes) == toBytes(kv2[1])
check trie.getRootHash() == root_hash_before_delete
const invalidKeyData = [
("\x12\x34\x56", false),
("\x12\x34\x56\x77", false),
("\x12\x34\x56\x78\x9a", true),
("\x12\x34\x56\x79\xab", true),
("\xab\xcd\xef", false)
]
test "invalid key":
for data in invalidKeyData:
var db = newMemoryDB()
var trie = initBinaryTrie(db)
trie.set("\x12\x34\x56\x78".toBytes, "78".toBytes)
trie.set("\x12\x34\x56\x79".toBytes, "79".toBytes)
let invalidKey = data[0]
let if_error = data[1]
check trie.get(invalidKey.toBytes) == []
if if_error:
try:
trie.delete(invalidKey.toBytes)
except NodeOverrideError:
discard
else:
let previous_root_hash = trie.getRootHash()
trie.delete(invalidKey.toBytes)
check previous_root_hash == trie.getRootHash()
test "update value":
let keys = randList(string, randGen(32, 32), randGen(100, 100))
let vals = randList(int, randGen(0, 99), randGen(50, 50))
var db = newMemoryDB()
var trie = initBinaryTrie(db)
for key in keys:
trie.set(key.toBytes, "old".toBytes)
var current_root = trie.getRootHash()
for i in vals:
trie.set(keys[i].toBytes, "old".toBytes)
check current_root == trie.getRootHash()
trie.set(keys[i].toBytes, "new".toBytes)
check current_root != trie.getRootHash()
check trie.get(keys[i].toBytes) == toBytes("new")
current_root = trie.getRootHash()

View File

@ -1,179 +0,0 @@
{.used.}
import
std/strutils,
unittest2,
nimcrypto/[keccak, hash], stew/byteutils,
../../eth/trie/[binaries, trie_bitseq],
./testutils
proc parseBitVector(x: string): TrieBitSeq =
result = genBitVec(x.len)
for i, c in x:
result[i] = (c == '1')
const
commonPrefixData = [
(@[0b0000_0000.byte], @[0b0000_0000.byte], 8),
(@[0b0000_0000.byte], @[0b1000_0000.byte], 0),
(@[0b1000_0000.byte], @[0b1100_0000.byte], 1),
(@[0b0000_0000.byte], @[0b0100_0000.byte], 1),
(@[0b1110_0000.byte], @[0b1100_0000.byte], 2),
(@[0b0000_1111.byte], @[0b1111_1111.byte], 0)
]
suite "binaries utils":
test "get common prefix length":
for c in commonPrefixData:
var
c0 = c[0]
c1 = c[1]
let actual_a = getCommonPrefixLength(c0.bits, c1.bits)
let actual_b = getCommonPrefixLength(c1.bits, c0.bits)
let expected = c[2]
check actual_a == actual_b
check actual_a == expected
const
None = ""
parseNodeData = {
"\x00\x03\x04\x05\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p":
(0, "00110000010000000101", "\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", false),
"\x01\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p":
(1, "\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", "\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", false),
"\x02value": (2, None, "value", false),
"": (0, None, None, true),
"\x00\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p": (0, None, None, true),
"\x01\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p": (0, None, None, true),
"\x01\x02\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p":
(0, None, None, true),
"\x02": (0, None, None, true),
"\x03": (0, None, None, true)
}
test "node parsing":
for c in parseNodeData:
let input = toBytes(c[0])
let node = c[1]
let kind = TrieNodeKind(node[0])
let raiseError = node[3]
var res: TrieNode
if raiseError:
expect(InvalidNode):
res = parseNode(input)
else:
res = parseNode(input)
check(kind == res.kind)
case res.kind
of KV_TYPE:
check(res.keyPath == parseBitVector(node[1]))
check(res.child == toBytes(node[2]))
of BRANCH_TYPE:
check(res.leftChild == toBytes(node[2]))
check(res.rightChild == toBytes(node[2]))
of LEAF_TYPE:
check(res.value == toBytes(node[2]))
const
kvData = [
("0", "\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", "\x00\x10\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", false),
("" , "\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", None, true),
("0", "\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", None, true),
("1", "\x00\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", None, true),
("2", "", None, true)
]
test "kv node encoding":
for c in kvData:
let keyPath = parseBitVector(c[0])
let node = toBytes(c[1])
let output = toBytes(c[2])
let raiseError = c[3]
if raiseError:
expect(ValidationError):
check output == encodeKVNode(keyPath, node)
else:
check output == encodeKVNode(keyPath, node)
const
branchData = [
("\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6", "\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p",
"\x01\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", false),
("", "\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", None, true),
("\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", "\x01", None, true),
("\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", "12345", None, true),
(repeat('\x01', 33), repeat('\x01', 32), None, true),
]
test "branch node encode":
for c in branchData:
let left = toBytes(c[0])
let right = toBytes(c[1])
let output = toBytes(c[2])
let raiseError = c[3]
if raiseError:
expect(ValidationError):
check output == encodeBranchNode(left, right)
else:
check output == encodeBranchNode(left, right)
const
leafData = [
("\x03\x04\x05", "\x02\x03\x04\x05", false),
("", None, true)
]
test "leaf node encode":
for c in leafData:
let raiseError = c[2]
if raiseError:
expect(ValidationError):
check toBytes(c[1]) == encodeLeafNode(toBytes(c[0]))
else:
check toBytes(c[1]) == encodeLeafNode(toBytes(c[0]))
test "random kv encoding":
let lengths = randList(int, randGen(1, 999), randGen(100, 100), unique = false)
for len in lengths:
var k = len
var bitvec = genBitVec(len)
var nodeHash = keccak256.digest(cast[ptr byte](k.addr), uint(sizeof(int)))
var kvnode = encodeKVNode(bitvec, @(nodeHash.data))
# first byte if KV_TYPE
# in the middle are 1..n bits of binary-encoded-keypath
# last 32 bytes are hash
var keyPath = decodeToBinKeypath(kvnode[1..^33])
check kvnode[0].ord == KV_TYPE.ord
check keyPath == bitvec
check kvnode[^32..^1] == nodeHash.data
test "optimized single bit keypath kvnode encoding":
var k = 1
var nodeHash = keccak256.digest(cast[ptr byte](k.addr), uint(sizeof(int)))
var bitvec = genBitVec(1)
bitvec[0] = false
var kvnode = encodeKVNode(bitvec, @(nodeHash.data))
var kp = decodeToBinKeypath(kvnode[1..^33])
var okv = encodeKVNode(false, @(nodeHash.data))
check okv == kvnode
var okp = decodeToBinKeypath(kvnode[1..^33])
check okp == kp
check okp.len == 1
check okp == bitvec
bitvec[0] = true
kvnode = encodeKVNode(bitvec, @(nodeHash.data))
kp = decodeToBinKeypath(kvnode[1..^33])
okv = encodeKVNode(true, @(nodeHash.data))
check okv == kvnode
okp = decodeToBinKeypath(kvnode[1..^33])
check okp == kp
check okp.len == 1
check okp == bitvec

View File

@ -1,146 +0,0 @@
{.used.}
import
std/[sets, strutils],
unittest2,
stew/byteutils,
../../eth/trie/[db, binary, branches]
suite "branches utils":
proc testTrie(): BinaryTrie =
var db = newMemoryDB()
var trie = initBinaryTrie(db)
trie.set("\x12\x34\x56\x78\x9a".toBytes, "9a".toBytes)
trie.set("\x12\x34\x56\x78\x9b".toBytes, "9b".toBytes)
trie.set("\x12\x34\x56\xff".toBytes, "ff".toBytes)
trie
const branchExistData = [
("\x12\x34", true),
("\x12\x34\x56\x78\x9b", true),
("\x12\x56", false),
("\x12\x34\x56\xff\xff", false),
("\x12\x34\x56", true),
("\x12\x34\x56\x78", true)
]
test "branch exists":
var trie = testTrie()
var db = trie.getDB()
for c in branchExistData:
let keyPrefix = c[0].toBytes
let if_exist = c[1]
check checkIfBranchExist(db, trie.getRootHash(), keyPrefix) == if_exist
const branchData = [
("\x12\x34", true),
("\x12\x34\x56\xff", true),
("\x12\x34\x56\x78\x9b", true),
("\x12\x56", true),
("\x12\x34\x56\xff\xff", false),
("", false)
]
test "branch":
var trie = testTrie()
var db = trie.getDB()
for c in branchData:
let key = c[0].toBytes
let keyValid = c[1]
if keyValid:
let branch = getBranch(db, trie.getRootHash(), key)
check isValidBranch(branch, trie.getRootHash(), key, trie.get(key))
else:
try:
discard getBranch(db, trie.getRootHash(), key)
except InvalidKeyError:
check(true)
except CatchableError:
check(false)
const trieNodesData = [
("#\xf037,w\xb9()\x0e4\x92\xdf\x11\xca\xea\xa5\x13/\x10\x1bJ\xa7\x16\x07\x07G\xb1\x01_\x16\xca", @["\x029a"]),
("\x84\x97\xc1\xf7S\xf5\xa2\xbb>\xbd\xe9\xc3t\x0f\xac/\xad\xa8\x01\xff\x9aE\t\xc1\xab\x9e\xa3|\xc7Z\xb0v",
@["\x01#\xf037,w\xb9()\x0e4\x92\xdf\x11\xca\xea\xa5\x13/\x10\x1bJ\xa7\x16\x07\x07G\xb1\x01_\x16\xcaG\xe9\xb6\xa1\xfa\xd5\x82\xf4k\x04\x9c\x8e\xc8\x17\xb4G\xe1c*n\xf4o\x02\x85\xf1\x19\xa8\x83`\xfb\xf8\xa2",
"\x029a",
"\x029b"]),
("\x13\x07<\xa0w6\xd5O\x91\x93\xb1\xde,0}\xe7\xee\x82\xd7\xf6\xce\x1b^\xb7}\"\n\xe4&\xe2\xd7v",
@["\x00\x82<M\x84\x97\xc1\xf7S\xf5\xa2\xbb>\xbd\xe9\xc3t\x0f\xac/\xad\xa8\x01\xff\x9aE\t\xc1\xab\x9e\xa3|\xc7Z\xb0v",
"\x01#\xf037,w\xb9()\x0e4\x92\xdf\x11\xca\xea\xa5\x13/\x10\x1bJ\xa7\x16\x07\x07G\xb1\x01_\x16\xcaG\xe9\xb6\xa1\xfa\xd5\x82\xf4k\x04\x9c\x8e\xc8\x17\xb4G\xe1c*n\xf4o\x02\x85\xf1\x19\xa8\x83`\xfb\xf8\xa2",
"\x029a",
"\x029b"]),
("X\x99\x8f\x13\xeb\x9bF\x08\xec|\x8b\xd8}\xca\xed\xda\xbb4\tl\xc8\x9bJ;J\xed\x11\x86\xc2\xd7+\xca",
@["\x00\x80\x124V\xde\xb5\x8f\xdb\x98\xc0\xe8\xed\x10\xde\x84\x89\xe1\xc3\x90\xbeoi7y$sJ\x07\xa1h\xf5t\x1c\xac\r+",
"\x01\x13\x07<\xa0w6\xd5O\x91\x93\xb1\xde,0}\xe7\xee\x82\xd7\xf6\xce\x1b^\xb7}\"\n\xe4&\xe2\xd7v7\x94\x07\x18\xc9\x96E\xf1\x9bS1sv\xa2\x8b\x9a\x88\xfd/>5\xcb3\x9e\x03\x08\r\xe2\xe1\xd5\xaaq",
"\x00\x82<M\x84\x97\xc1\xf7S\xf5\xa2\xbb>\xbd\xe9\xc3t\x0f\xac/\xad\xa8\x01\xff\x9aE\t\xc1\xab\x9e\xa3|\xc7Z\xb0v",
"\x00\x83\x7fR\xce\xe1\xe1 +\x96\xde\xae\xcdV\x13\x9a \x90.7H\xb6\x80\t\x10\xe1(\x03\x15\xde\x94\x17X\xee\xe1",
"\x01#\xf037,w\xb9()\x0e4\x92\xdf\x11\xca\xea\xa5\x13/\x10\x1bJ\xa7\x16\x07\x07G\xb1\x01_\x16\xcaG\xe9\xb6\xa1\xfa\xd5\x82\xf4k\x04\x9c\x8e\xc8\x17\xb4G\xe1c*n\xf4o\x02\x85\xf1\x19\xa8\x83`\xfb\xf8\xa2",
"\x02ff",
"\x029a",
"\x029b"]),
("\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p", @[]),
(repeat('0', 32), @[])
]
proc toRanges(x: seq[string]): seq[seq[byte]] =
result = newSeq[seq[byte]](x.len)
for i, c in x: result[i] = toBytes(c)
test "get trie nodes":
var trie = testTrie()
var db = trie.getDB()
for c in trieNodesData:
let root = c[0].toBytes()
let nodes = toRanges(c[1])
check toHashSet(nodes) == toHashSet(getTrieNodes(db, root))
const witnessData = [
("\x12\x34\x56\x78\x9b",
@["\x00\x80\x124V\xde\xb5\x8f\xdb\x98\xc0\xe8\xed\x10\xde\x84\x89\xe1\xc3\x90\xbeoi7y$sJ\x07\xa1h\xf5t\x1c\xac\r+",
"\x01\x13\x07<\xa0w6\xd5O\x91\x93\xb1\xde,0}\xe7\xee\x82\xd7\xf6\xce\x1b^\xb7}\"\n\xe4&\xe2\xd7v7\x94\x07\x18\xc9\x96E\xf1\x9bS1sv\xa2\x8b\x9a\x88\xfd/>5\xcb3\x9e\x03\x08\r\xe2\xe1\xd5\xaaq",
"\x00\x82<M\x84\x97\xc1\xf7S\xf5\xa2\xbb>\xbd\xe9\xc3t\x0f\xac/\xad\xa8\x01\xff\x9aE\t\xc1\xab\x9e\xa3|\xc7Z\xb0v",
"\x01#\xf037,w\xb9()\x0e4\x92\xdf\x11\xca\xea\xa5\x13/\x10\x1bJ\xa7\x16\x07\x07G\xb1\x01_\x16\xcaG\xe9\xb6\xa1\xfa\xd5\x82\xf4k\x04\x9c\x8e\xc8\x17\xb4G\xe1c*n\xf4o\x02\x85\xf1\x19\xa8\x83`\xfb\xf8\xa2",
"\x029b"]),
("\x12\x34\x56\x78",
@["\x00\x80\x124V\xde\xb5\x8f\xdb\x98\xc0\xe8\xed\x10\xde\x84\x89\xe1\xc3\x90\xbeoi7y$sJ\x07\xa1h\xf5t\x1c\xac\r+",
"\x01\x13\x07<\xa0w6\xd5O\x91\x93\xb1\xde,0}\xe7\xee\x82\xd7\xf6\xce\x1b^\xb7}\"\n\xe4&\xe2\xd7v7\x94\x07\x18\xc9\x96E\xf1\x9bS1sv\xa2\x8b\x9a\x88\xfd/>5\xcb3\x9e\x03\x08\r\xe2\xe1\xd5\xaaq",
"\x00\x82<M\x84\x97\xc1\xf7S\xf5\xa2\xbb>\xbd\xe9\xc3t\x0f\xac/\xad\xa8\x01\xff\x9aE\t\xc1\xab\x9e\xa3|\xc7Z\xb0v",
"\x01#\xf037,w\xb9()\x0e4\x92\xdf\x11\xca\xea\xa5\x13/\x10\x1bJ\xa7\x16\x07\x07G\xb1\x01_\x16\xcaG\xe9\xb6\xa1\xfa\xd5\x82\xf4k\x04\x9c\x8e\xc8\x17\xb4G\xe1c*n\xf4o\x02\x85\xf1\x19\xa8\x83`\xfb\xf8\xa2",
"\x029a",
"\x029b"]),
("\x12\x34\x56",
@["\x00\x80\x124V\xde\xb5\x8f\xdb\x98\xc0\xe8\xed\x10\xde\x84\x89\xe1\xc3\x90\xbeoi7y$sJ\x07\xa1h\xf5t\x1c\xac\r+",
"\x01\x13\x07<\xa0w6\xd5O\x91\x93\xb1\xde,0}\xe7\xee\x82\xd7\xf6\xce\x1b^\xb7}\"\n\xe4&\xe2\xd7v7\x94\x07\x18\xc9\x96E\xf1\x9bS1sv\xa2\x8b\x9a\x88\xfd/>5\xcb3\x9e\x03\x08\r\xe2\xe1\xd5\xaaq",
"\x00\x82<M\x84\x97\xc1\xf7S\xf5\xa2\xbb>\xbd\xe9\xc3t\x0f\xac/\xad\xa8\x01\xff\x9aE\t\xc1\xab\x9e\xa3|\xc7Z\xb0v",
"\x00\x83\x7fR\xce\xe1\xe1 +\x96\xde\xae\xcdV\x13\x9a \x90.7H\xb6\x80\t\x10\xe1(\x03\x15\xde\x94\x17X\xee\xe1",
"\x01#\xf037,w\xb9()\x0e4\x92\xdf\x11\xca\xea\xa5\x13/\x10\x1bJ\xa7\x16\x07\x07G\xb1\x01_\x16\xcaG\xe9\xb6\xa1\xfa\xd5\x82\xf4k\x04\x9c\x8e\xc8\x17\xb4G\xe1c*n\xf4o\x02\x85\xf1\x19\xa8\x83`\xfb\xf8\xa2",
"\x02ff",
"\x029a",
"\x029b"]),
("\x12",
@["\x00\x80\x124V\xde\xb5\x8f\xdb\x98\xc0\xe8\xed\x10\xde\x84\x89\xe1\xc3\x90\xbeoi7y$sJ\x07\xa1h\xf5t\x1c\xac\r+",
"\x01\x13\x07<\xa0w6\xd5O\x91\x93\xb1\xde,0}\xe7\xee\x82\xd7\xf6\xce\x1b^\xb7}\"\n\xe4&\xe2\xd7v7\x94\x07\x18\xc9\x96E\xf1\x9bS1sv\xa2\x8b\x9a\x88\xfd/>5\xcb3\x9e\x03\x08\r\xe2\xe1\xd5\xaaq",
"\x00\x82<M\x84\x97\xc1\xf7S\xf5\xa2\xbb>\xbd\xe9\xc3t\x0f\xac/\xad\xa8\x01\xff\x9aE\t\xc1\xab\x9e\xa3|\xc7Z\xb0v",
"\x00\x83\x7fR\xce\xe1\xe1 +\x96\xde\xae\xcdV\x13\x9a \x90.7H\xb6\x80\t\x10\xe1(\x03\x15\xde\x94\x17X\xee\xe1",
"\x01#\xf037,w\xb9()\x0e4\x92\xdf\x11\xca\xea\xa5\x13/\x10\x1bJ\xa7\x16\x07\x07G\xb1\x01_\x16\xcaG\xe9\xb6\xa1\xfa\xd5\x82\xf4k\x04\x9c\x8e\xc8\x17\xb4G\xe1c*n\xf4o\x02\x85\xf1\x19\xa8\x83`\xfb\xf8\xa2",
"\x02ff",
"\x029a",
"\x029b"]),
(repeat('0', 32),
@["\x00\x80\x124V\xde\xb5\x8f\xdb\x98\xc0\xe8\xed\x10\xde\x84\x89\xe1\xc3\x90\xbeoi7y$sJ\x07\xa1h\xf5t\x1c\xac\r+"]),
]
test "get witness for key prefix":
var trie = testTrie()
var db = trie.getDB()
for c in witnessData:
let key = c[0].toBytes
let nodes = toRanges(c[1])
if nodes.len != 0:
let x = toHashSet(nodes)
let y = toHashSet(getWitness(db, trie.getRootHash(), key))
check x == y

View File

@ -1,100 +0,0 @@
# Copyright (c) 2019-2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.used.}
import
unittest2,
stew/byteutils,
../../eth/trie/[db, binary, binaries, branches]
suite "examples":
var db = newMemoryDB()
var trie = initBinaryTrie(db)
test "basic set/get":
trie.set("key1".toBytes(), "value1".toBytes())
trie.set("key2".toBytes(), "value2".toBytes())
check trie.get("key1".toBytes) == "value1".toBytes
check trie.get("key2".toBytes) == "value2".toBytes
test "check branch exists":
check checkIfBranchExist(db, trie.getRootHash(), "key".toBytes) == true
check checkIfBranchExist(db, trie.getRootHash(), "key1".toBytes) == true
check checkIfBranchExist(db, trie.getRootHash(), "ken".toBytes) == false
check checkIfBranchExist(db, trie.getRootHash(), "key123".toBytes) == false
test "branches utils":
var branchA = getBranch(db, trie.getRootHash(), "key1".toBytes)
# ==> [A, B, C1, D1]
check branchA.len == 4
var branchB = getBranch(db, trie.getRootHash(), "key2".toBytes)
# ==> [A, B, C2, D2]
check branchB.len == 4
check isValidBranch(branchA, trie.getRootHash(), "key1".toBytes, "value1".toBytes) == true
check isValidBranch(branchA, trie.getRootHash(), "key5".toBytes, "".toBytes) == true
expect InvalidNode:
check isValidBranch(branchB, trie.getRootHash(), "key1".toBytes, "value1".toBytes)
var x = getBranch(db, trie.getRootHash(), "key".toBytes)
# ==> [A]
check x.len == 1
expect InvalidKeyError:
x = getBranch(db, trie.getRootHash(), "key123".toBytes) # InvalidKeyError
x = getBranch(db, trie.getRootHash(), "key5".toBytes) # there is still branch for non-exist key
# ==> [A]
check x.len == 1
test "getWitness":
var branch = getWitness(db, trie.getRootHash(), "key1".toBytes)
# equivalent to `getBranch(db, trie.getRootHash(), "key1")`
# ==> [A, B, C1, D1]
check branch.len == 4
branch = getWitness(db, trie.getRootHash(), "key".toBytes)
# this will include additional nodes of "key2"
# ==> [A, B, C1, D1, C2, D2]
check branch.len == 6
branch = getWitness(db, trie.getRootHash(), "".toBytes)
# this will return the whole trie
# ==> [A, B, C1, D1, C2, D2]
check branch.len == 6
let beforeDeleteLen = db.totalRecordsInMemoryDB
test "verify intermediate entries existence":
var branchs = getWitness(db, trie.getRootHash, [])
# set operation create new intermediate entries
check branchs.len < beforeDeleteLen
var node = branchs[1]
let nodeHash = keccak256(node)
var nodes = getTrieNodes(db, @(nodeHash.data))
check nodes.len == branchs.len - 1
test "delete sub trie":
# delete all subtrie with key prefixes "key"
trie.deleteSubtrie("key".toBytes)
check trie.get("key1".toBytes) == []
check trie.get("key2".toBytes) == []
test "prove the lie":
# `delete` and `deleteSubtrie` not actually delete the nodes
check db.totalRecordsInMemoryDB == beforeDeleteLen
var branchs = getWitness(db, trie.getRootHash, [])
check branchs.len == 0
test "dictionary syntax API":
# dictionary syntax API
trie["moon".toBytes] = "sun".toBytes
check "moon".toBytes in trie
check trie["moon".toBytes] == "sun".toBytes

View File

@ -1,85 +0,0 @@
{.used.}
import
std/random,
unittest2,
../../eth/trie/trie_bitseq
proc randomBytes(n: int): seq[byte] =
result = newSeq[byte](n)
for i in 0 ..< result.len:
result[i] = byte(rand(256))
suite "trie bitseq":
test "basic":
var a = @[byte 0b10101010, 0b11110000, 0b00001111, 0b01010101]
var bSeq = @[byte 0b10101010, 0b00000000, 0b00000000, 0b11111111]
var b = bits(bSeq, 8)
var cSeq = @[byte 0b11110000, 0b00001111, 0b00000000, 0b00000000]
var c = bits(cSeq, 16)
var dSeq = @[byte 0b00001111, 0b00000000, 0b00000000, 0b00000000]
var d = bits(dSeq, 8)
var eSeq = @[byte 0b01010101, 0b00000000, 0b00000000, 0b00000000]
var e = bits(eSeq, 8)
var m = a.bits
var n = m[0..7]
check n == b
check n.len == 8
check b.len == 8
check c == m[8..23]
check $(d) == "00001111"
check $(e) == "01010101"
var f = int.fromBits(e, 0, 4)
check f == 0b0101
let k = n & d
check(k.len == n.len + d.len)
check($k == $n & $d)
var asciiSeq = @[byte('A'),byte('S'),byte('C'),byte('I'),byte('I')]
let asciiBits = bits(asciiSeq)
check $asciiBits == "0100000101010011010000110100100101001001"
test "concat operator":
randomize(5000)
for i in 0..<256:
var xSeq = randomBytes(rand(i))
var ySeq = randomBytes(rand(i))
let x = xSeq.bits
let y = ySeq.bits
var z = x & y
check z.len == x.len + y.len
check($z == $x & $y)
test "get set bits":
randomize(1000)
for i in 0..<256:
# produce random vector
var xSeq = randomBytes(i)
var ySeq = randomBytes(i)
var x = xSeq.bits
var y = ySeq.bits
for idx, bit in x:
y[idx] = bit
check x == y
test "constructor with start":
var a = @[byte 0b10101010, 0b11110000, 0b00001111, 0b01010101]
var b = a.bits(1, 8)
check b.len == 8
check b[0] == false
check $b == "01010101"
b[0] = true
check $b == "11010101"
check b[0] == true
b.pushFront(false)
check b[0] == false
check $b == "011010101"

View File

@ -1,7 +1,6 @@
import
std/[random, sets],
nimcrypto/[utils, sysrand],
../../eth/trie/trie_bitseq
nimcrypto/[utils, sysrand]
type
RandGen*[T] = object
@ -77,10 +76,3 @@ proc randKVPair*(keySize = 32): seq[KVPair] =
result = newSeq[KVPair](listLen)
for i in 0..<listLen:
result[i] = KVPair(key: keys[i], value: vals[i])
proc genBitVec*(len: int): TrieBitSeq =
let k = ((len + 7) and (not 7)) shr 3
var s = newSeq[byte](k)
result = bits(s, len)
for i in 0..<len:
result[i] = rand(2) == 1