mirror of https://github.com/status-im/nim-eth.git
379 lines
9.8 KiB
Nim
379 lines
9.8 KiB
Nim
import
|
|
unittest, sequtils, os,
|
|
stew/ranges/typedranges, eth/trie/[hexary, db, trie_defs], nimcrypto/utils,
|
|
./testutils, algorithm, eth/rlp/types as rlpTypes, random
|
|
|
|
from strutils import split
|
|
|
|
template put(t: HexaryTrie|SecureHexaryTrie, key, val: string) =
|
|
t.put(key.toBytesRange, val.toBytesRange)
|
|
|
|
template del(t: HexaryTrie|SecureHexaryTrie, key) =
|
|
t.del(key.toBytesRange)
|
|
|
|
template get(t: HexaryTrie|SecureHexaryTrie, key): auto =
|
|
t.get(key.toBytesRange)
|
|
|
|
suite "hexary trie":
|
|
setup:
|
|
var
|
|
db = newMemoryDB()
|
|
tr = initHexaryTrie(db)
|
|
|
|
test "ref-counted keys crash":
|
|
proc addKey(intKey: int) =
|
|
var key = newSeqWith(20, 0.byte)
|
|
key[19] = byte(intKey)
|
|
var data = newSeqWith(29, 1.byte)
|
|
|
|
var k = key.toRange
|
|
|
|
let v = tr.get(k)
|
|
doAssert(v.len == 0)
|
|
|
|
tr.put(k, toRange(data))
|
|
|
|
addKey(166)
|
|
addKey(193)
|
|
addKey(7)
|
|
addKey(101)
|
|
addKey(159)
|
|
addKey(187)
|
|
addKey(206)
|
|
addKey(242)
|
|
addKey(94)
|
|
addKey(171)
|
|
addKey(14)
|
|
addKey(143)
|
|
addKey(237)
|
|
addKey(148)
|
|
addKey(181)
|
|
addKey(147)
|
|
addKey(45)
|
|
addKey(81)
|
|
addKey(77)
|
|
addKey(123)
|
|
addKey(35)
|
|
addKey(24)
|
|
addKey(188)
|
|
addKey(136)
|
|
|
|
|
|
const genesisAccounts = "tests/cases/mainnet-genesis-accounts.txt"
|
|
if fileExists(genesisAccounts):
|
|
# This test is optional because it takes a while to run and the
|
|
# verification is already being part of Nimbus (see genesis.nim).
|
|
#
|
|
# On the other hand, it's useful to be able to debug just the trie
|
|
# code if problems arise. You can download the genesis-accounts file
|
|
# using the the following command at the root at the repo:
|
|
#
|
|
# wget https://gist.github.com/zah/f3a7d325a71d35df3c2606af05d30910/raw/d8bf8fed3d2760f0054cebf4de254a0564a87322/mainnet-genesis-accounts.txt -P tests/cases
|
|
test "genesis hash":
|
|
for line in lines(genesisAccounts):
|
|
var parts = line.split(" ")
|
|
var
|
|
key = fromHex(parts[0])
|
|
val = fromHex(parts[1])
|
|
|
|
SecureHexaryTrie(tr).put(key.toRange, val.toRange)
|
|
|
|
check tr.rootHashHex == "D7F8974FB5AC78D9AC099B9AD5018BEDC2CE0A72DAD1827A1709DA30580F0544"
|
|
|
|
# lexicographic comparison
|
|
proc lexComp(a, b: BytesRange): bool =
|
|
var
|
|
x = 0
|
|
y = 0
|
|
xlen = a.len
|
|
ylen = b.len
|
|
|
|
while x != xlen:
|
|
if y == ylen or b[y] < a[x]: return false
|
|
elif a[x] < b[y]: return true
|
|
inc x
|
|
inc y
|
|
|
|
result = y != ylen
|
|
|
|
proc cmp(a, b: BytesRange): int =
|
|
if a == b: return 0
|
|
if a.lexComp(b): return 1
|
|
return -1
|
|
|
|
test "get values and keys":
|
|
randomize()
|
|
var
|
|
memdb = newMemoryDB()
|
|
trie = initHexaryTrie(memdb)
|
|
keys = [
|
|
"key".toBytesRange,
|
|
"abc".toBytesRange,
|
|
"hola".toBytesRange,
|
|
"bubble".toBytesRange
|
|
]
|
|
|
|
vals = [
|
|
"hello".toBytesRange,
|
|
"world".toBytesRange,
|
|
"block".toBytesRange,
|
|
"chain".toBytesRange
|
|
]
|
|
|
|
for i in 0 ..< keys.len:
|
|
trie.put(keys[i], vals[i])
|
|
|
|
var values = trie.getValues()
|
|
values.sort(cmp)
|
|
vals.sort(cmp)
|
|
check values == vals
|
|
|
|
var paths = trie.getKeys()
|
|
paths.sort(cmp)
|
|
keys.sort(cmp)
|
|
check paths == keys
|
|
|
|
paths.setLen(0)
|
|
for k in trie.keys:
|
|
paths.add(k)
|
|
paths.sort(cmp)
|
|
keys.sort(cmp)
|
|
check paths == keys
|
|
|
|
values.setLen(0)
|
|
paths.setLen(0)
|
|
for k, v in trie:
|
|
paths.add k
|
|
values.add v
|
|
|
|
paths.sort(cmp)
|
|
values.sort(cmp)
|
|
check paths == keys
|
|
check values == vals
|
|
|
|
test "get values and keys with random data":
|
|
var
|
|
memdb = newMemoryDB()
|
|
trie = initHexaryTrie(memdb)
|
|
keys = randList(BytesRange, randGen(5, 32), randGen(10))
|
|
vals = randList(BytesRange, randGen(5, 7), randGen(10))
|
|
|
|
keys2 = randList(BytesRange, randGen(5, 30), randGen(15))
|
|
vals2 = randList(BytesRange, randGen(5, 7), randGen(15))
|
|
|
|
for i in 0 ..< keys.len:
|
|
trie.put(keys[i], vals[i])
|
|
|
|
for i in 0 ..< keys.len:
|
|
check trie.get(keys[i]) == vals[i]
|
|
|
|
var values = trie.getValues()
|
|
values.sort(cmp)
|
|
vals.sort(cmp)
|
|
check values == vals
|
|
|
|
let rootHash = trie.rootHash
|
|
for i in 0 ..< keys2.len:
|
|
trie.put(keys2[i], vals2[i])
|
|
var trie2 = initHexaryTrie(memdb, rootHash)
|
|
|
|
# because of pruning, equality become uncertain
|
|
values = trie2.getValues()
|
|
values.sort(cmp)
|
|
let
|
|
cmpResultA = values != vals
|
|
cmpResultB = values == vals
|
|
check cmpResultB or cmpResultA
|
|
|
|
var values2 = trie.getValues()
|
|
vals2.add vals
|
|
values2.sort(cmp)
|
|
vals2.sort(cmp)
|
|
check values2 == vals2
|
|
|
|
values2.setLen(0)
|
|
for k in trie.values:
|
|
values2.add(k)
|
|
values2.sort(cmp)
|
|
check values2 == vals2
|
|
|
|
var paths = trie.getKeys()
|
|
paths.sort(cmp)
|
|
keys2.add keys
|
|
keys2.sort(cmp)
|
|
check paths == keys2
|
|
|
|
paths.setLen(0)
|
|
for k in trie.keys:
|
|
paths.add(k)
|
|
paths.sort(cmp)
|
|
check paths == keys2
|
|
|
|
values.setLen(0)
|
|
paths.setLen(0)
|
|
for k, v in trie:
|
|
paths.add k
|
|
values.add v
|
|
|
|
paths.sort(cmp)
|
|
values.sort(cmp)
|
|
check paths == keys2
|
|
check values == vals2
|
|
|
|
test "non-pruning mode":
|
|
var
|
|
memdb = newMemoryDB()
|
|
nonPruningTrie = initHexaryTrie(memdb, false)
|
|
keys = randList(BytesRange, randGen(5, 77), randGen(30))
|
|
vals = randList(BytesRange, randGen(1, 57), randGen(30))
|
|
|
|
moreKeys = randList(BytesRange, randGen(5, 33), randGen(45))
|
|
moreVals = randList(BytesRange, randGen(1, 47), randGen(45))
|
|
|
|
for i in 0 ..< keys.len:
|
|
nonPruningTrie.put(keys[i], vals[i])
|
|
|
|
let rootHash = nonPruningTrie.rootHash
|
|
for i in 0 ..< moreKeys.len:
|
|
nonPruningTrie.put(moreKeys[i], moreVals[i])
|
|
|
|
var
|
|
readOnlyTrie = initHexaryTrie(memdb, rootHash)
|
|
secondaryTrie = initHexaryTrie(memdb, rootHash, false)
|
|
|
|
keys.sort(cmp)
|
|
vals.sort(cmp)
|
|
|
|
var
|
|
roKeys = readOnlyTrie.getKeys()
|
|
roValues = readOnlyTrie.getValues()
|
|
scKeys = secondaryTrie.getKeys()
|
|
scValues = secondaryTrie.getValues()
|
|
|
|
roKeys.sort(cmp)
|
|
roValues.sort(cmp)
|
|
scKeys.sort(cmp)
|
|
scValues.sort(cmp)
|
|
|
|
check keys == roKeys
|
|
check vals == roValues
|
|
check keys == scKeys
|
|
check vals == scValues
|
|
|
|
test "elaborate non-pruning test":
|
|
type
|
|
History = object
|
|
keys: seq[BytesRange]
|
|
values: seq[BytesRange]
|
|
rootHash: KeccakHash
|
|
|
|
const
|
|
listLength = 30
|
|
numLoop = 100
|
|
|
|
for iteration in 0 ..< numLoop:
|
|
var
|
|
memdb = newMemoryDB()
|
|
nonPruningTrie = initHexaryTrie(memdb, false)
|
|
keys = randList(BytesRange, randGen(3, 33), randGen(listLength))
|
|
values = randList(BytesRange, randGen(5, 77), randGen(listLength))
|
|
historyList = newSeq[History](listLength)
|
|
ok = true
|
|
|
|
for i, k in keys:
|
|
historyList[i].keys = newSeq[BytesRange](i + 1)
|
|
historyList[i].values = newSeq[BytesRange](i + 1)
|
|
for x in 0 ..< i + 1:
|
|
historyList[i].keys[x] = keys[x]
|
|
historyList[i].values[x] = values[x]
|
|
nonPruningTrie.put(keys[i], values[i])
|
|
historyList[i].rootHash = nonPruningTrie.rootHash
|
|
historyList[i].keys.sort(cmp)
|
|
historyList[i].values.sort(cmp)
|
|
|
|
for h in historyList:
|
|
var
|
|
trie = initHexaryTrie(memdb, h.rootHash)
|
|
pKeys: seq[BytesRange] = @[]
|
|
pValues = trie.getValues()
|
|
|
|
for k in trie.keys:
|
|
pKeys.add k
|
|
pKeys.sort(cmp)
|
|
pValues.sort(cmp)
|
|
check pKeys.len == h.keys.len
|
|
check pValues.len == h.values.len
|
|
check pKeys == h.keys
|
|
check pValues == h.values
|
|
|
|
ok = ok and pKeys.len == h.keys.len
|
|
ok = ok and pValues.len == h.values.len
|
|
ok = ok and pKeys == h.keys
|
|
ok = ok and pValues == h.values
|
|
if not ok: break
|
|
|
|
if not ok:
|
|
echo "ITERATION: ", iteration
|
|
break
|
|
|
|
proc isValidBranch(branch: seq[BytesRange], rootHash: KeccakHash, key, value: BytesRange): bool =
|
|
# branch must not be empty
|
|
doAssert(branch.len != 0)
|
|
|
|
var db = newMemoryDB()
|
|
for node in branch:
|
|
doAssert(node.len != 0)
|
|
let nodeHash = hexary.keccak(node)
|
|
db.put(nodeHash.data, node.toOpenArray)
|
|
|
|
var trie = initHexaryTrie(db, rootHash)
|
|
result = trie.get(key) == toRange(value)
|
|
|
|
test "get branch with pruning trie":
|
|
var
|
|
memdb = newMemoryDB()
|
|
trie = initHexaryTrie(memdb)
|
|
keys = randList(BytesRange, randGen(5, 77), randGen(30))
|
|
vals = randList(BytesRange, randGen(1, 57), randGen(30))
|
|
|
|
for i in 0 ..< keys.len:
|
|
trie.put(keys[i], vals[i])
|
|
|
|
for i in 0 ..< keys.len:
|
|
var branch = trie.getBranch(keys[i])
|
|
check isValidBranch(branch, trie.rootHash, keys[i], vals[i])
|
|
|
|
test "get branch with non pruning trie":
|
|
const
|
|
numKeyVal = 30
|
|
|
|
var
|
|
memdb = newMemoryDB()
|
|
nonPruningTrie = initHexaryTrie(memdb, false)
|
|
keys = randList(BytesRange, randGen(5, 77), randGen(numKeyVal))
|
|
vals = randList(BytesRange, randGen(1, 57), randGen(numKeyVal))
|
|
roots = newSeq[KeccakHash](numKeyVal)
|
|
|
|
for i in 0 ..< keys.len:
|
|
nonPruningTrie.put(keys[i], vals[i])
|
|
roots[i] = nonPruningTrie.rootHash
|
|
|
|
for i in 0 ..< keys.len:
|
|
var trie = initHexaryTrie(memdb, roots[i], false)
|
|
for x in 0 ..< i+1:
|
|
var branch = trie.getBranch(keys[x])
|
|
check isValidBranch(branch, trie.rootHash, keys[x], vals[x])
|
|
|
|
test "isPruning getter":
|
|
var
|
|
memdb = newMemoryDB()
|
|
nonPruningTrie = initHexaryTrie(memdb, false)
|
|
pruningTrie = initHexaryTrie(memdb, true)
|
|
nonPruningSecureTrie = initSecureHexaryTrie(memdb, false)
|
|
pruningSecureTrie = initSecureHexaryTrie(memdb, true)
|
|
|
|
check nonPruningTrie.isPruning == false
|
|
check pruningTrie.isPruning == true
|
|
check nonPruningSecureTRie.isPruning == false
|
|
check pruningSecureTRie.isPruning == true
|