mirror of https://github.com/status-im/nim-eth.git
parent
c482b4c5b6
commit
0addffcc6c
|
@ -1,6 +1,5 @@
|
|||
import
|
||||
./trie/[hexary, sparse_binary]
|
||||
./trie/[hexary, trie_defs]
|
||||
|
||||
export
|
||||
hexary, sparse_binary
|
||||
|
||||
hexary, trie_defs
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
import
|
||||
"."/[trie_bitseq, trie_defs, trie_utils, db, sparse_proofs]
|
||||
|
||||
export
|
||||
trie_utils, trie_bitseq,
|
||||
sparse_proofs.verifyProof
|
||||
|
||||
type
|
||||
DB = TrieDatabaseRef
|
||||
|
||||
SparseBinaryTrie* = object
|
||||
db: DB
|
||||
rootHash: seq[byte]
|
||||
|
||||
type
|
||||
# 256 * 2 div 8
|
||||
DoubleHash = array[64, byte]
|
||||
|
||||
proc initDoubleHash(a, b: openArray[byte]): DoubleHash =
|
||||
doAssert(a.len == 32, $a.len)
|
||||
doAssert(b.len == 32, $b.len)
|
||||
result[0..31] = a
|
||||
result[32..^1] = b
|
||||
|
||||
proc initDoubleHash(x: openArray[byte]): DoubleHash =
|
||||
initDoubleHash(x, x)
|
||||
|
||||
proc init*(x: typedesc[SparseBinaryTrie], db: DB): SparseBinaryTrie =
|
||||
result.db = db
|
||||
# Initialize an empty tree with one branch
|
||||
var value = initDoubleHash(emptyNodeHashes[0].data)
|
||||
result.rootHash = @(keccakHash(value).data)
|
||||
result.db.put(result.rootHash, value)
|
||||
|
||||
for i in 0..<treeHeight - 1:
|
||||
value = initDoubleHash(emptyNodeHashes[i+1].data)
|
||||
result.db.put(emptyNodeHashes[i].data, value)
|
||||
|
||||
result.db.put(emptyLeafNodeHash.data, [])
|
||||
|
||||
proc initSparseBinaryTrie*(db: DB): SparseBinaryTrie =
|
||||
init(SparseBinaryTrie, db)
|
||||
|
||||
proc init*(x: typedesc[SparseBinaryTrie], db: DB,
|
||||
rootHash: openArray[byte]): SparseBinaryTrie =
|
||||
checkValidHashZ(rootHash)
|
||||
result.db = db
|
||||
result.rootHash = @rootHash
|
||||
|
||||
proc initSparseBinaryTrie*(db: DB, rootHash: openArray[byte]): SparseBinaryTrie =
|
||||
init(SparseBinaryTrie, db, rootHash)
|
||||
|
||||
proc getDB*(t: SparseBinaryTrie): auto = t.db
|
||||
|
||||
proc getRootHash*(self: SparseBinaryTrie): seq[byte] {.inline.} =
|
||||
self.rootHash
|
||||
|
||||
proc getAux(self: SparseBinaryTrie, path: TrieBitSeq, rootHash: openArray[byte]): seq[byte] =
|
||||
var nodeHash = @rootHash
|
||||
for targetBit in path:
|
||||
let value = self.db.get(nodeHash)
|
||||
if value.len == 0: return
|
||||
if targetBit: nodeHash = value[32..^1]
|
||||
else: nodeHash = value[0..31]
|
||||
|
||||
if nodeHash == emptyLeafNodeHash.data:
|
||||
result = @[]
|
||||
else:
|
||||
result = self.db.get(nodeHash)
|
||||
|
||||
proc get*(self: SparseBinaryTrie, key: openArray[byte]): seq[byte] =
|
||||
## gets a key from the tree.
|
||||
doAssert(key.len == pathByteLen)
|
||||
let path = bits key
|
||||
self.getAux(path, self.rootHash)
|
||||
|
||||
proc get*(self: SparseBinaryTrie, key, rootHash: openArray[byte]): seq[byte] =
|
||||
## gets a key from the tree at a specific root.
|
||||
doAssert(key.len == pathByteLen)
|
||||
let path = bits key
|
||||
self.getAux(path, rootHash)
|
||||
|
||||
proc hashAndSave*(self: SparseBinaryTrie, node: openArray[byte]): seq[byte] =
|
||||
result = @(keccakHash(node).data)
|
||||
self.db.put(result, node)
|
||||
|
||||
proc hashAndSave*(self: SparseBinaryTrie, a, b: openArray[byte]): seq[byte] =
|
||||
let value = initDoubleHash(a, b)
|
||||
result = @(keccakHash(value).data)
|
||||
self.db.put(result, value)
|
||||
|
||||
proc setAux(self: var SparseBinaryTrie, value: openArray[byte],
|
||||
path: TrieBitSeq, depth: int, nodeHash: openArray[byte]): seq[byte] =
|
||||
if depth == treeHeight:
|
||||
result = self.hashAndSave(value)
|
||||
else:
|
||||
let
|
||||
node = self.db.get(nodeHash)
|
||||
leftNode = node[0..31]
|
||||
rightNode = node[32..^1]
|
||||
if path[depth]:
|
||||
result = self.hashAndSave(leftNode, self.setAux(value, path, depth+1, rightNode))
|
||||
else:
|
||||
result = self.hashAndSave(self.setAux(value, path, depth+1, leftNode), rightNode)
|
||||
|
||||
proc set*(self: var SparseBinaryTrie, key, value: openArray[byte]) =
|
||||
## sets a new value for a key in the tree, returns the new root,
|
||||
## and sets the new current root of the tree.
|
||||
doAssert(key.len == pathByteLen)
|
||||
let path = bits key
|
||||
self.rootHash = self.setAux(value, path, 0, self.rootHash)
|
||||
|
||||
proc set*(self: var SparseBinaryTrie, key, value, rootHash: openArray[byte]): seq[byte] =
|
||||
## sets a new value for a key in the tree at a specific root,
|
||||
## and returns the new root.
|
||||
doAssert(key.len == pathByteLen)
|
||||
let path = bits key
|
||||
self.setAux(value, path, 0, rootHash)
|
||||
|
||||
template exists*(self: SparseBinaryTrie, key: openArray[byte]): bool =
|
||||
self.get(key) != []
|
||||
|
||||
proc del*(self: var SparseBinaryTrie, key: openArray[byte]) =
|
||||
## Equals to setting the value to zeroBytesRange
|
||||
doAssert(key.len == pathByteLen)
|
||||
self.set(key, [])
|
||||
|
||||
# Dictionary API
|
||||
template `[]`*(self: SparseBinaryTrie, key: openArray[byte]): seq[byte] =
|
||||
self.get(key)
|
||||
|
||||
template `[]=`*(self: var SparseBinaryTrie, key, value: openArray[byte]) =
|
||||
self.set(key, value)
|
||||
|
||||
template contains*(self: SparseBinaryTrie, key: openArray[byte]): bool =
|
||||
self.exists(key)
|
||||
|
||||
proc proveAux(self: SparseBinaryTrie, key, rootHash: openArray[byte], output: var seq[seq[byte]]): bool =
|
||||
doAssert(key.len == pathByteLen)
|
||||
var currVal = self.db.get(rootHash)
|
||||
if currVal.len == 0: return false
|
||||
|
||||
let path = bits key
|
||||
for i, bit in path:
|
||||
if bit:
|
||||
# right side
|
||||
output[i] = currVal[0..31]
|
||||
currVal = self.db.get(currVal[32..^1])
|
||||
if currVal.len == 0: return false
|
||||
else:
|
||||
output[i] = currVal[32..^1]
|
||||
currVal = self.db.get(currVal[0..31])
|
||||
if currVal.len == 0: return false
|
||||
|
||||
result = true
|
||||
|
||||
# prove generates a Merkle proof for a key.
|
||||
proc prove*(self: SparseBinaryTrie, key: openArray[byte]): seq[seq[byte]] =
|
||||
result = newSeq[seq[byte]](treeHeight)
|
||||
if not self.proveAux(key, self.rootHash, result):
|
||||
result = @[]
|
||||
|
||||
# prove generates a Merkle proof for a key, at a specific root.
|
||||
proc prove*(self: SparseBinaryTrie, key, rootHash: openArray[byte]): seq[seq[byte]] =
|
||||
result = newSeq[seq[byte]](treeHeight)
|
||||
if not self.proveAux(key, rootHash, result):
|
||||
result = @[]
|
||||
|
||||
# proveCompact generates a compacted Merkle proof for a key.
|
||||
proc proveCompact*(self: SparseBinaryTrie, key: openArray[byte]): seq[seq[byte]] =
|
||||
var temp = self.prove(key)
|
||||
temp.compactProof
|
||||
|
||||
# proveCompact generates a compacted Merkle proof for a key, at a specific root.
|
||||
proc proveCompact*(self: SparseBinaryTrie, key, rootHash: openArray[byte]): seq[seq[byte]] =
|
||||
var temp = self.prove(key, rootHash)
|
||||
temp.compactProof
|
|
@ -1,86 +0,0 @@
|
|||
import
|
||||
"."/[trie_bitseq, trie_defs, trie_utils]
|
||||
|
||||
const
|
||||
treeHeight* = 160
|
||||
pathByteLen* = treeHeight div 8
|
||||
emptyLeafNodeHash* = blankStringHash
|
||||
|
||||
proc makeInitialEmptyTreeHash(H: static[int]): array[H, KeccakHash] =
|
||||
result[^1] = emptyLeafNodeHash
|
||||
for i in countdown(H-1, 1):
|
||||
result[i - 1] = keccakHash(result[i].data, result[i].data)
|
||||
|
||||
# cannot yet turn this into compile time constant
|
||||
let emptyNodeHashes* = makeInitialEmptyTreeHash(treeHeight)
|
||||
|
||||
# VerifyProof verifies a Merkle proof.
|
||||
proc verifyProofAux*(proof: seq[seq[byte]], root, key, value: openArray[byte]): bool =
|
||||
doAssert(root.len == 32)
|
||||
doAssert(key.len == pathByteLen)
|
||||
var
|
||||
path = bits key
|
||||
curHash = keccakHash(value)
|
||||
|
||||
if proof.len != treeHeight: return false
|
||||
|
||||
for i in countdown(treeHeight - 1, 0):
|
||||
var node = proof[i]
|
||||
if node.len != 32: return false
|
||||
if path[i]: # right
|
||||
# reuse curHash without more alloc
|
||||
curHash = keccakHash(node, curHash.data)
|
||||
else:
|
||||
curHash = keccakHash(curHash.data, node)
|
||||
|
||||
result = curHash.data == root
|
||||
|
||||
template verifyProof*(proof: seq[seq[byte]], root, key, value: openArray[byte]): bool =
|
||||
verifyProofAux(proof, root, key, value)
|
||||
|
||||
proc count(b: TrieBitSeq, val: bool): int =
|
||||
for c in b:
|
||||
if c == val: inc result
|
||||
|
||||
# CompactProof compacts a proof, to reduce its size.
|
||||
proc compactProof*(proof: seq[seq[byte]]): seq[seq[byte]] =
|
||||
if proof.len != treeHeight: return
|
||||
|
||||
var
|
||||
data = newSeq[byte](pathByteLen)
|
||||
bits = bits data
|
||||
|
||||
result = @[]
|
||||
result.add @[]
|
||||
for i in 0 ..< treeHeight:
|
||||
var node = proof[i]
|
||||
if node == emptyNodeHashes[i].data:
|
||||
bits[i] = true
|
||||
else:
|
||||
result.add node
|
||||
result[0] = bits.toBytes
|
||||
|
||||
# decompactProof decompacts a proof, so that it can be used for VerifyProof.
|
||||
proc decompactProof*(proof: seq[seq[byte]]): seq[seq[byte]] =
|
||||
if proof.len == 0: return
|
||||
if proof[0].len != pathByteLen: return
|
||||
let bits = bits proof[0]
|
||||
if proof.len != bits.count(false) + 1: return
|
||||
result = newSeq[seq[byte]](treeHeight)
|
||||
|
||||
var pos = 1 # skip bits
|
||||
for i in 0 ..< treeHeight:
|
||||
if bits[i]:
|
||||
result[i] = @(emptyNodeHashes[i].data)
|
||||
else:
|
||||
result[i] = proof[pos]
|
||||
inc pos
|
||||
|
||||
# verifyCompactProof verifies a compacted Merkle proof.
|
||||
proc verifyCompactProofAux*(proof: seq[seq[byte]], root, key, value: openArray[byte]): bool =
|
||||
var decompactedProof = decompactProof(proof)
|
||||
if decompactedProof.len == 0: return false
|
||||
verifyProofAux(decompactedProof, root, key, value)
|
||||
|
||||
template verifyCompactProof*(proof: seq[seq[byte]], root, key, value: openArray[byte]): bool =
|
||||
verifyCompactProofAux(proof, root, key, value)
|
|
@ -5,7 +5,6 @@ import
|
|||
./test_examples,
|
||||
./test_hexary_trie,
|
||||
./test_json_suite,
|
||||
./test_sparse_binary_trie,
|
||||
./test_transaction_db,
|
||||
./test_trie_bitseq,
|
||||
./test_hexary_proof
|
||||
|
|
|
@ -1,230 +0,0 @@
|
|||
{.used.}
|
||||
|
||||
import
|
||||
std/random,
|
||||
unittest2,
|
||||
stew/byteutils,
|
||||
../../eth/trie/[db, sparse_binary, sparse_proofs],
|
||||
./testutils
|
||||
|
||||
suite "sparse binary trie":
|
||||
randomize()
|
||||
var kv_pairs = randKVPair(20)
|
||||
var numbers = randList(int, randGen(1, 99), randGen(50, 100))
|
||||
var db = newMemoryDB()
|
||||
var trie = initSparseBinaryTrie(db)
|
||||
|
||||
test "basic set":
|
||||
for c in kv_pairs:
|
||||
check trie.exists(c.key) == false
|
||||
trie.set(c.key, c.value)
|
||||
|
||||
let prevRoot = trie.getRootHash()
|
||||
test "basic get":
|
||||
for c in kv_pairs:
|
||||
let x = trie.get(c.key)
|
||||
let y = c.value
|
||||
check x == y
|
||||
trie.del(c.key)
|
||||
|
||||
for c in kv_pairs:
|
||||
check trie.exists(c.key) == false
|
||||
|
||||
check trie.getRootHash() == keccakHash(emptyNodeHashes[0].data, emptyNodeHashes[0].data).data
|
||||
|
||||
test "single update set":
|
||||
random.shuffle(kv_pairs)
|
||||
for c in kv_pairs:
|
||||
trie.set(c.key, c.value)
|
||||
|
||||
# Check trie root remains the same even in different insert order
|
||||
check trie.getRootHash() == prevRoot
|
||||
|
||||
let prior_to_update_root = trie.getRootHash()
|
||||
test "single update get":
|
||||
for i in numbers:
|
||||
# If new value is the same as current value, skip the update
|
||||
if toBytes($i) == trie.get(kv_pairs[i].key):
|
||||
continue
|
||||
# Update
|
||||
trie.set(kv_pairs[i].key, toBytes($i))
|
||||
check trie.get(kv_pairs[i].key) == toBytes($i)
|
||||
check trie.getRootHash() != prior_to_update_root
|
||||
|
||||
# Un-update
|
||||
trie.set(kv_pairs[i].key, kv_pairs[i].value)
|
||||
check trie.getRootHash == prior_to_update_root
|
||||
|
||||
test "batch update with different update order":
|
||||
# First batch update
|
||||
for i in numbers:
|
||||
trie.set(kv_pairs[i].key, toBytes($i))
|
||||
|
||||
let batch_updated_root = trie.getRootHash()
|
||||
|
||||
# Un-update
|
||||
random.shuffle(numbers)
|
||||
for i in numbers:
|
||||
trie.set(kv_pairs[i].key, kv_pairs[i].value)
|
||||
|
||||
check trie.getRootHash() == prior_to_update_root
|
||||
|
||||
# Second batch update
|
||||
random.shuffle(numbers)
|
||||
for i in numbers:
|
||||
trie.set(kv_pairs[i].key, toBytes($i))
|
||||
|
||||
check trie.getRootHash() == batch_updated_root
|
||||
|
||||
test "dictionary API":
|
||||
trie[kv_pairs[0].key] = kv_pairs[0].value
|
||||
let x = trie[kv_pairs[0].key]
|
||||
let y = kv_pairs[0].value
|
||||
check x == y
|
||||
check kv_pairs[0].key in trie
|
||||
|
||||
test "get/set for specific root":
|
||||
db = newMemoryDB()
|
||||
trie = initSparseBinaryTrie(db)
|
||||
let
|
||||
testKey = kv_pairs[0].key
|
||||
testValue = kv_pairs[0].value
|
||||
testKey2 = kv_pairs[1].key
|
||||
testValue2 = kv_pairs[1].value
|
||||
|
||||
trie.set(testKey, testValue)
|
||||
var root = trie.getRootHash()
|
||||
var value = trie.get(testKey, root)
|
||||
check value == testValue
|
||||
|
||||
root = trie.set(testKey2, testValue2, root)
|
||||
value = trie.get(testKey2, root)
|
||||
check value == testValue2
|
||||
|
||||
value = trie.get(testKey, root)
|
||||
check value == testValue
|
||||
|
||||
proc makeBadProof(size: int, width = 32): seq[seq[byte]] =
|
||||
let badProofStr = randList(seq[byte], randGen(width, width), randGen(size, size))
|
||||
result = newSeq[seq[byte]](size)
|
||||
for i in 0 ..< result.len:
|
||||
result[i] = badProofStr[i]
|
||||
|
||||
test "proofs":
|
||||
const
|
||||
MaxBadProof = 32 * 8
|
||||
|
||||
let
|
||||
testKey = kv_pairs[0].key
|
||||
badKey = kv_pairs[1].key
|
||||
testValue = "testValue".toBytes
|
||||
testValue2 = "testValue2".toBytes
|
||||
badValue = "badValue".toBytes
|
||||
badProof = makeBadProof(MaxBadProof)
|
||||
|
||||
trie[testKey] = testValue
|
||||
var proof = trie.prove(testKey)
|
||||
check proof.len == treeHeight
|
||||
check verifyProof(proof, trie.getRootHash(), testKey, testValue) == true
|
||||
check verifyProof(proof, trie.getRootHash(), testKey, badValue) == false
|
||||
check verifyProof(proof, trie.getRootHash(), badKey, testValue) == false
|
||||
check verifyProof(badProof, trie.getRootHash(), testKey, testValue) == false
|
||||
|
||||
let
|
||||
testKey2 = kv_pairs[2].key
|
||||
testKey3 = kv_pairs[3].key
|
||||
defaultValue = default(seq[byte])
|
||||
|
||||
trie.set(testKey2, testValue)
|
||||
proof = trie.prove(testKey)
|
||||
check verifyProof(proof, trie.getRootHash(), testKey, testValue) == true
|
||||
check verifyProof(proof, trie.getRootHash(), testKey, badValue) == false
|
||||
check verifyProof(proof, trie.getRootHash(), testKey2, testValue) == false
|
||||
check verifyProof(badProof, trie.getRootHash(), testKey, testValue) == false
|
||||
|
||||
proof = trie.prove(testKey2)
|
||||
check verifyProof(proof, trie.getRootHash(), testKey2, testValue) == true
|
||||
check verifyProof(proof, trie.getRootHash(), testKey2, badValue) == false
|
||||
check verifyProof(proof, trie.getRootHash(), testKey3, testValue) == false
|
||||
check verifyProof(badProof, trie.getRootHash(), testKey, testValue) == false
|
||||
|
||||
var compactProof = compactProof(proof)
|
||||
var decompactedProof = decompactProof(compactProof)
|
||||
|
||||
check decompactedProof.len == proof.len
|
||||
for i, c in proof:
|
||||
check decompactedProof[i] == c
|
||||
|
||||
let
|
||||
badProof2 = makeBadProof(MaxBadProof + 1)
|
||||
badProof3 = makeBadProof(MaxBadProof - 1)
|
||||
badProof4 = makeBadProof(MaxBadProof, 31)
|
||||
badProof5 = makeBadProof(MaxBadProof, 33)
|
||||
badProof6 = makeBadProof(MaxBadProof, 1)
|
||||
|
||||
check verifyProof(badProof2, trie.getRootHash(), testKey3, defaultValue) == false
|
||||
check verifyProof(badProof3, trie.getRootHash(), testKey3, defaultValue) == false
|
||||
check verifyProof(badProof4, trie.getRootHash(), testKey3, defaultValue) == false
|
||||
check verifyProof(badProof5, trie.getRootHash(), testKey3, defaultValue) == false
|
||||
check verifyProof(badProof6, trie.getRootHash(), testKey3, defaultValue) == false
|
||||
|
||||
check compactProof(badProof2).len == 0
|
||||
check compactProof(badProof3).len == 0
|
||||
check decompactProof(badProof3).len == 0
|
||||
var zeroProof: seq[seq[byte]]
|
||||
check decompactProof(zeroProof).len == 0
|
||||
|
||||
proof = trie.proveCompact(testKey2)
|
||||
check verifyCompactProof(proof, trie.getRootHash(), testKey2, testValue) == true
|
||||
check verifyCompactProof(proof, trie.getRootHash(), testKey2, badValue) == false
|
||||
check verifyCompactProof(proof, trie.getRootHash(), testKey3, testValue) == false
|
||||
check verifyCompactProof(badProof, trie.getRootHash(), testKey, testValue) == false
|
||||
|
||||
var root = trie.getRootHash()
|
||||
trie.set(testKey2, testValue2)
|
||||
|
||||
proof = trie.proveCompact(testKey2, root)
|
||||
check verifyCompactProof(proof, root, testKey2, testValue) == true
|
||||
check verifyCompactProof(proof, root, testKey2, badValue) == false
|
||||
check verifyCompactProof(proof, root, testKey3, testValue) == false
|
||||
check verifyCompactProof(badProof, root, testKey, testValue) == false
|
||||
|
||||
proof = trie.prove(testKey2, root)
|
||||
check verifyProof(proof, root, testKey2, testValue) == true
|
||||
check verifyProof(proof, root, testKey2, badValue) == false
|
||||
check verifyProof(proof, root, testKey3, testValue) == false
|
||||
check verifyProof(badProof, root, testKey, testValue) == false
|
||||
|
||||
proof = trie.prove(testKey3)
|
||||
check proof.len == 0
|
||||
check verifyProof(proof, trie.getRootHash(), testKey3, defaultValue) == false
|
||||
check verifyProof(proof, trie.getRootHash(), testKey3, badValue) == false
|
||||
check verifyProof(proof, trie.getRootHash(), testKey2, defaultValue) == false
|
||||
check verifyProof(badProof, trie.getRootHash(), testKey, testValue) == false
|
||||
|
||||
test "examples":
|
||||
let
|
||||
key1 = "01234567890123456789".toBytes
|
||||
key2 = "abcdefghijklmnopqrst".toBytes
|
||||
|
||||
trie.set(key1, "value1".toBytes)
|
||||
trie.set(key2, "value2".toBytes)
|
||||
check trie.get(key1) == "value1".toBytes
|
||||
check trie.get(key2) == "value2".toBytes
|
||||
|
||||
trie.del(key1)
|
||||
check trie.get(key1) == []
|
||||
|
||||
trie.del(key2)
|
||||
check trie[key2] == []
|
||||
|
||||
let
|
||||
value1 = "hello world".toBytes
|
||||
badValue = "bad value".toBytes
|
||||
|
||||
trie[key1] = value1
|
||||
var proof = trie.prove(key1)
|
||||
|
||||
check verifyProof(proof, trie.getRootHash(), key1, value1) == true
|
||||
check verifyProof(proof, trie.getRootHash(), key1, badValue) == false
|
||||
check verifyProof(proof, trie.getRootHash(), key2, value1) == false
|
Loading…
Reference in New Issue