mirror of https://github.com/status-im/nim-eth.git
Some changes to make hexary.nim better able to handle incomplete DBs (#602)
* Added maybeGet, for working with incomplete DBs. * Made trie.del throw an exception if it encounters a missing node.
This commit is contained in:
parent
917888356e
commit
f5dd26eac0
|
@ -31,11 +31,41 @@ proc expectHash(r: Rlp): seq[byte] =
|
||||||
raise newException(RlpTypeMismatch,
|
raise newException(RlpTypeMismatch,
|
||||||
"RLP expected to be a Keccak hash value, but has an incorrect length")
|
"RLP expected to be a Keccak hash value, but has an incorrect length")
|
||||||
|
|
||||||
|
type MissingNodeError* = ref object of Defect
|
||||||
|
path*: NibblesSeq
|
||||||
|
nodeHashBytes*: seq[byte]
|
||||||
|
|
||||||
|
proc dbGet(db: DB, data: openArray[byte]): seq[byte]
|
||||||
|
{.gcsafe, raises: [Defect].} =
|
||||||
|
# Useful for debugging:
|
||||||
|
# doAssert(db.contains(data), "dbGet, db must contain the data")
|
||||||
|
db.get(data)
|
||||||
|
|
||||||
|
proc dbGet(db: DB, key: Rlp): seq[byte] =
|
||||||
|
dbGet(db, key.expectHash)
|
||||||
|
|
||||||
proc dbPut(db: DB, data: openArray[byte]): TrieNodeKey
|
proc dbPut(db: DB, data: openArray[byte]): TrieNodeKey
|
||||||
{.gcsafe, raises: [Defect].}
|
{.gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
template get(db: DB, key: Rlp): seq[byte] =
|
# For stateless mode, it's possible for nodes to be missing from the DB,
|
||||||
db.get(key.expectHash)
|
# and we need the higher-level code to be able to find out the *path* to
|
||||||
|
# the missing node. So here we need the path to be passed in, and if the
|
||||||
|
# node is missing we'll raise an exception to get that information up to
|
||||||
|
# where it's needed.
|
||||||
|
proc getPossiblyMissingNode(db: DB, data: openArray[byte], fullPath: NibblesSeq, pathIndex: int): seq[byte]
|
||||||
|
{.gcsafe, raises: [Defect].} =
|
||||||
|
# FIXME-Adam: This causes some tests to fail in nimbus-eth1; I'm not
|
||||||
|
# sure why. I need to figure it out, though, because we need this
|
||||||
|
# behaviour.
|
||||||
|
#
|
||||||
|
# if db.contains(data):
|
||||||
|
# db.get(data)
|
||||||
|
# else:
|
||||||
|
# raise MissingNodeError(path: fullPath.slice(0, pathIndex), nodeHashBytes: @data)
|
||||||
|
db.get(data)
|
||||||
|
|
||||||
|
proc getPossiblyMissingNode(db: DB, key: Rlp, fullPath: NibblesSeq, pathIndex: int): seq[byte] =
|
||||||
|
getPossiblyMissingNode(db, key.expectHash, fullPath, pathIndex)
|
||||||
|
|
||||||
converter toTrieNodeKey(hash: KeccakHash): TrieNodeKey =
|
converter toTrieNodeKey(hash: KeccakHash): TrieNodeKey =
|
||||||
result.hash = hash
|
result.hash = hash
|
||||||
|
@ -78,27 +108,21 @@ proc getLocalBytes(x: TrieNodeKey): seq[byte] =
|
||||||
|
|
||||||
template keyToLocalBytes(db: DB, k: TrieNodeKey): seq[byte] =
|
template keyToLocalBytes(db: DB, k: TrieNodeKey): seq[byte] =
|
||||||
if k.len < 32: k.getLocalBytes
|
if k.len < 32: k.getLocalBytes
|
||||||
else: db.get(k.asDbKey)
|
else: dbGet(db, k.asDbKey)
|
||||||
|
|
||||||
template extensionNodeKey(r: Rlp): auto =
|
template extensionNodeKey(r: Rlp): auto =
|
||||||
hexPrefixDecode r.listElem(0).toBytes
|
hexPrefixDecode r.listElem(0).toBytes
|
||||||
|
|
||||||
proc getAux(db: DB, nodeRlp: Rlp, path: NibblesSeq): seq[byte]
|
proc getLookup(db: DB, elem: Rlp, fullPath: NibblesSeq, pathIndex: int): Rlp =
|
||||||
{.gcsafe, raises: [RlpError, Defect].}
|
|
||||||
|
|
||||||
proc getAuxByHash(db: DB, node: TrieNodeKey, path: NibblesSeq): seq[byte] =
|
|
||||||
var nodeRlp = rlpFromBytes keyToLocalBytes(db, node)
|
|
||||||
return getAux(db, nodeRlp, path)
|
|
||||||
|
|
||||||
template getLookup(elem: untyped): untyped =
|
|
||||||
if elem.isList: elem
|
if elem.isList: elem
|
||||||
else: rlpFromBytes(get(db, elem.expectHash))
|
else: rlpFromBytes(getPossiblyMissingNode(db, elem.expectHash, fullPath, pathIndex))
|
||||||
|
|
||||||
proc getAux(db: DB, nodeRlp: Rlp, path: NibblesSeq): seq[byte]
|
proc getAux(db: DB, nodeRlp: Rlp, fullPath: NibblesSeq, pathIndex: int): seq[byte]
|
||||||
{.gcsafe, raises: [RlpError, Defect].} =
|
{.gcsafe, raises: [RlpError, Defect].} =
|
||||||
if not nodeRlp.hasData or nodeRlp.isEmpty:
|
if not nodeRlp.hasData or nodeRlp.isEmpty:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
let path = fullPath.slice(pathIndex)
|
||||||
case nodeRlp.listLen
|
case nodeRlp.listLen
|
||||||
of 2:
|
of 2:
|
||||||
let (isLeaf, k) = nodeRlp.extensionNodeKey
|
let (isLeaf, k) = nodeRlp.extensionNodeKey
|
||||||
|
@ -109,8 +133,8 @@ proc getAux(db: DB, nodeRlp: Rlp, path: NibblesSeq): seq[byte]
|
||||||
if sharedNibbles == path.len and isLeaf:
|
if sharedNibbles == path.len and isLeaf:
|
||||||
return value.toBytes
|
return value.toBytes
|
||||||
elif not isLeaf:
|
elif not isLeaf:
|
||||||
let nextLookup = value.getLookup
|
let nextLookup = getLookup(db, value, fullPath, pathIndex + sharedNibbles)
|
||||||
return getAux(db, nextLookup, path.slice(sharedNibbles))
|
return getAux(db, nextLookup, fullPath, pathIndex + sharedNibbles)
|
||||||
|
|
||||||
return
|
return
|
||||||
of 17:
|
of 17:
|
||||||
|
@ -120,14 +144,18 @@ proc getAux(db: DB, nodeRlp: Rlp, path: NibblesSeq): seq[byte]
|
||||||
if branch.isEmpty:
|
if branch.isEmpty:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
let nextLookup = branch.getLookup
|
let nextLookup = getLookup(db, branch, fullPath, pathIndex + 1)
|
||||||
return getAux(db, nextLookup, path.slice(1))
|
return getAux(db, nextLookup, fullPath, pathIndex + 1)
|
||||||
else:
|
else:
|
||||||
raise newException(CorruptedTrieDatabase,
|
raise newException(CorruptedTrieDatabase,
|
||||||
"HexaryTrie node with an unexpected number of children")
|
"HexaryTrie node with an unexpected number of children")
|
||||||
|
|
||||||
|
proc getAuxByHash(db: DB, node: TrieNodeKey, fullPath: NibblesSeq, pathIndex: int): seq[byte] =
|
||||||
|
var nodeRlp = rlpFromBytes keyToLocalBytes(db, node)
|
||||||
|
return getAux(db, nodeRlp, fullPath, pathIndex)
|
||||||
|
|
||||||
proc get*(self: HexaryTrie; key: openArray[byte]): seq[byte] =
|
proc get*(self: HexaryTrie; key: openArray[byte]): seq[byte] =
|
||||||
return getAuxByHash(self.db, self.root, initNibbleRange(key))
|
return getAuxByHash(self.db, self.root, initNibbleRange(key), 0)
|
||||||
|
|
||||||
proc getKeysAux(db: DB, stack: var seq[tuple[nodeRlp: Rlp, path: NibblesSeq]]): seq[byte] =
|
proc getKeysAux(db: DB, stack: var seq[tuple[nodeRlp: Rlp, path: NibblesSeq]]): seq[byte] =
|
||||||
while stack.len > 0:
|
while stack.len > 0:
|
||||||
|
@ -147,15 +175,15 @@ proc getKeysAux(db: DB, stack: var seq[tuple[nodeRlp: Rlp, path: NibblesSeq]]):
|
||||||
else:
|
else:
|
||||||
let
|
let
|
||||||
value = nodeRlp.listElem(1)
|
value = nodeRlp.listElem(1)
|
||||||
nextLookup = value.getLookup
|
nextLookup = getLookup(db, value, key, key.len)
|
||||||
stack.add((nextLookup, key))
|
stack.add((nextLookup, key))
|
||||||
of 17:
|
of 17:
|
||||||
for i in 0 ..< 16:
|
for i in 0 ..< 16:
|
||||||
var branch = nodeRlp.listElem(i)
|
var branch = nodeRlp.listElem(i)
|
||||||
if not branch.isEmpty:
|
if not branch.isEmpty:
|
||||||
let nextLookup = branch.getLookup
|
|
||||||
var key = path.cloneAndReserveNibble()
|
var key = path.cloneAndReserveNibble()
|
||||||
key.replaceLastNibble(i.byte)
|
key.replaceLastNibble(i.byte)
|
||||||
|
let nextLookup = getLookup(db, branch, key, key.len)
|
||||||
stack.add((nextLookup, key))
|
stack.add((nextLookup, key))
|
||||||
|
|
||||||
var lastElem = nodeRlp.listElem(16)
|
var lastElem = nodeRlp.listElem(16)
|
||||||
|
@ -173,29 +201,33 @@ iterator keys*(self: HexaryTrie): seq[byte] =
|
||||||
while stack.len > 0:
|
while stack.len > 0:
|
||||||
yield getKeysAux(self.db, stack)
|
yield getKeysAux(self.db, stack)
|
||||||
|
|
||||||
proc getValuesAux(db: DB, stack: var seq[Rlp]): seq[byte] =
|
proc getValuesAux(db: DB, stack: var seq[tuple[nodeRlp: Rlp, path: NibblesSeq]]): seq[byte] =
|
||||||
while stack.len > 0:
|
while stack.len > 0:
|
||||||
let nodeRlp = stack.pop()
|
let (nodeRlp, path) = stack.pop()
|
||||||
if not nodeRlp.hasData or nodeRlp.isEmpty:
|
if not nodeRlp.hasData or nodeRlp.isEmpty:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case nodeRlp.listLen
|
case nodeRlp.listLen
|
||||||
of 2:
|
of 2:
|
||||||
let
|
let
|
||||||
(isLeaf, _) = nodeRlp.extensionNodeKey
|
(isLeaf, k) = nodeRlp.extensionNodeKey
|
||||||
|
key = path & k
|
||||||
value = nodeRlp.listElem(1)
|
value = nodeRlp.listElem(1)
|
||||||
|
|
||||||
if isLeaf:
|
if isLeaf:
|
||||||
|
doAssert(key.len mod 2 == 0)
|
||||||
return value.toBytes
|
return value.toBytes
|
||||||
else:
|
else:
|
||||||
let nextLookup = value.getLookup
|
let nextLookup = getLookup(db, value, key, key.len)
|
||||||
stack.add(nextLookup)
|
stack.add((nextLookup, key))
|
||||||
of 17:
|
of 17:
|
||||||
for i in 0 ..< 16:
|
for i in 0 ..< 16:
|
||||||
var branch = nodeRlp.listElem(i)
|
var branch = nodeRlp.listElem(i)
|
||||||
if not branch.isEmpty:
|
if not branch.isEmpty:
|
||||||
let nextLookup = branch.getLookup
|
var key = path.cloneAndReserveNibble()
|
||||||
stack.add(nextLookup)
|
key.replaceLastNibble(i.byte)
|
||||||
|
let nextLookup = getLookup(db, branch, key, key.len)
|
||||||
|
stack.add((nextLookup, key))
|
||||||
|
|
||||||
var lastElem = nodeRlp.listElem(16)
|
var lastElem = nodeRlp.listElem(16)
|
||||||
if not lastElem.isEmpty:
|
if not lastElem.isEmpty:
|
||||||
|
@ -207,7 +239,7 @@ proc getValuesAux(db: DB, stack: var seq[Rlp]): seq[byte] =
|
||||||
iterator values*(self: HexaryTrie): seq[byte] =
|
iterator values*(self: HexaryTrie): seq[byte] =
|
||||||
var
|
var
|
||||||
nodeRlp = rlpFromBytes keyToLocalBytes(self.db, self.root)
|
nodeRlp = rlpFromBytes keyToLocalBytes(self.db, self.root)
|
||||||
stack = @[nodeRlp]
|
stack = @[(nodeRlp, initNibbleRange([]))]
|
||||||
while stack.len > 0:
|
while stack.len > 0:
|
||||||
yield getValuesAux(self.db, stack)
|
yield getValuesAux(self.db, stack)
|
||||||
|
|
||||||
|
@ -228,15 +260,15 @@ proc getPairsAux(db: DB, stack: var seq[tuple[nodeRlp: Rlp, path: NibblesSeq]]):
|
||||||
doAssert(key.len mod 2 == 0)
|
doAssert(key.len mod 2 == 0)
|
||||||
return (key.getBytes, value.toBytes)
|
return (key.getBytes, value.toBytes)
|
||||||
else:
|
else:
|
||||||
let nextLookup = value.getLookup
|
let nextLookup = getLookup(db, value, key, key.len)
|
||||||
stack.add((nextLookup, key))
|
stack.add((nextLookup, key))
|
||||||
of 17:
|
of 17:
|
||||||
for i in 0 ..< 16:
|
for i in 0 ..< 16:
|
||||||
var branch = nodeRlp.listElem(i)
|
var branch = nodeRlp.listElem(i)
|
||||||
if not branch.isEmpty:
|
if not branch.isEmpty:
|
||||||
let nextLookup = branch.getLookup
|
|
||||||
var key = path.cloneAndReserveNibble()
|
var key = path.cloneAndReserveNibble()
|
||||||
key.replaceLastNibble(i.byte)
|
key.replaceLastNibble(i.byte)
|
||||||
|
let nextLookup = getLookup(db, branch, key, key.len)
|
||||||
stack.add((nextLookup, key))
|
stack.add((nextLookup, key))
|
||||||
|
|
||||||
var lastElem = nodeRlp.listElem(16)
|
var lastElem = nodeRlp.listElem(16)
|
||||||
|
@ -311,14 +343,15 @@ proc getKeys*(self: HexaryTrie): seq[seq[byte]] =
|
||||||
for k in self.keys:
|
for k in self.keys:
|
||||||
result.add k
|
result.add k
|
||||||
|
|
||||||
template getNode(elem: untyped): untyped =
|
template getNode(db: DB, elem: Rlp): untyped =
|
||||||
if elem.isList: @(elem.rawData)
|
if elem.isList: @(elem.rawData)
|
||||||
else: get(db, elem.expectHash)
|
else: dbGet(db, elem.expectHash)
|
||||||
|
|
||||||
proc getBranchAux(db: DB, node: openArray[byte], path: NibblesSeq, output: var seq[seq[byte]]) =
|
proc getBranchAux(db: DB, node: openArray[byte], fullPath: NibblesSeq, pathIndex: int, output: var seq[seq[byte]]) =
|
||||||
var nodeRlp = rlpFromBytes node
|
var nodeRlp = rlpFromBytes node
|
||||||
if not nodeRlp.hasData or nodeRlp.isEmpty: return
|
if not nodeRlp.hasData or nodeRlp.isEmpty: return
|
||||||
|
|
||||||
|
let path = fullPath.slice(pathIndex)
|
||||||
case nodeRlp.listLen
|
case nodeRlp.listLen
|
||||||
of 2:
|
of 2:
|
||||||
let (isLeaf, k) = nodeRlp.extensionNodeKey
|
let (isLeaf, k) = nodeRlp.extensionNodeKey
|
||||||
|
@ -326,16 +359,16 @@ proc getBranchAux(db: DB, node: openArray[byte], path: NibblesSeq, output: var s
|
||||||
if sharedNibbles == k.len:
|
if sharedNibbles == k.len:
|
||||||
let value = nodeRlp.listElem(1)
|
let value = nodeRlp.listElem(1)
|
||||||
if not isLeaf:
|
if not isLeaf:
|
||||||
let nextLookup = value.getNode
|
let nextLookup = getNode(db, value)
|
||||||
output.add nextLookup
|
output.add nextLookup
|
||||||
getBranchAux(db, nextLookup, path.slice(sharedNibbles), output)
|
getBranchAux(db, nextLookup, fullPath, pathIndex + sharedNibbles, output)
|
||||||
of 17:
|
of 17:
|
||||||
if path.len != 0:
|
if path.len != 0:
|
||||||
var branch = nodeRlp.listElem(path[0].int)
|
var branch = nodeRlp.listElem(path[0].int)
|
||||||
if not branch.isEmpty:
|
if not branch.isEmpty:
|
||||||
let nextLookup = branch.getNode
|
let nextLookup = getNode(db, branch)
|
||||||
output.add nextLookup
|
output.add nextLookup
|
||||||
getBranchAux(db, nextLookup, path.slice(1), output)
|
getBranchAux(db, nextLookup, fullPath, pathIndex + 1, output)
|
||||||
else:
|
else:
|
||||||
raise newException(CorruptedTrieDatabase,
|
raise newException(CorruptedTrieDatabase,
|
||||||
"HexaryTrie node with an unexpected number of children")
|
"HexaryTrie node with an unexpected number of children")
|
||||||
|
@ -344,7 +377,7 @@ proc getBranch*(self: HexaryTrie; key: openArray[byte]): seq[seq[byte]] =
|
||||||
result = @[]
|
result = @[]
|
||||||
var node = keyToLocalBytes(self.db, self.root)
|
var node = keyToLocalBytes(self.db, self.root)
|
||||||
result.add node
|
result.add node
|
||||||
getBranchAux(self.db, node, initNibbleRange(key), result)
|
getBranchAux(self.db, node, initNibbleRange(key), 0, result)
|
||||||
|
|
||||||
proc dbDel(t: var HexaryTrie, data: openArray[byte]) =
|
proc dbDel(t: var HexaryTrie, data: openArray[byte]) =
|
||||||
if data.len >= 32: t.prune(data.keccakHash.data)
|
if data.len >= 32: t.prune(data.keccakHash.data)
|
||||||
|
@ -387,9 +420,9 @@ proc replaceValue(data: Rlp, key: NibblesSeq, value: openArray[byte]): seq[byte]
|
||||||
r.append value
|
r.append value
|
||||||
return r.finish()
|
return r.finish()
|
||||||
|
|
||||||
proc isTwoItemNode(self: HexaryTrie; r: Rlp): bool =
|
proc isTwoItemNode(self: HexaryTrie; r: Rlp, fullPath: NibblesSeq, pathIndex: int): bool =
|
||||||
if r.isBlob:
|
if r.isBlob:
|
||||||
let resolved = self.db.get(r)
|
let resolved = getPossiblyMissingNode(self.db, r, fullPath, pathIndex)
|
||||||
let rlp = rlpFromBytes(resolved)
|
let rlp = rlpFromBytes(resolved)
|
||||||
return rlp.isList and rlp.listLen == 2
|
return rlp.isList and rlp.listLen == 2
|
||||||
else:
|
else:
|
||||||
|
@ -408,18 +441,24 @@ proc findSingleChild(r: Rlp; childPos: var byte): Rlp =
|
||||||
return zeroBytesRlp
|
return zeroBytesRlp
|
||||||
inc i
|
inc i
|
||||||
|
|
||||||
proc deleteAt(self: var HexaryTrie; origRlp: Rlp, key: NibblesSeq): seq[byte]
|
proc deleteAt(self: var HexaryTrie;
|
||||||
|
origRlp: Rlp,
|
||||||
|
fullPath: NibblesSeq,
|
||||||
|
pathIndex: int): seq[byte]
|
||||||
{.gcsafe, raises: [RlpError, Defect].}
|
{.gcsafe, raises: [RlpError, Defect].}
|
||||||
|
|
||||||
proc deleteAux(self: var HexaryTrie; rlpWriter: var RlpWriter;
|
proc deleteAux(self: var HexaryTrie;
|
||||||
origRlp: Rlp; path: NibblesSeq): bool =
|
rlpWriter: var RlpWriter;
|
||||||
|
origRlp: Rlp;
|
||||||
|
fullPath: NibblesSeq,
|
||||||
|
pathIndex: int): bool =
|
||||||
if origRlp.isEmpty:
|
if origRlp.isEmpty:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
var toDelete = if origRlp.isList: origRlp
|
var toDelete = if origRlp.isList: origRlp
|
||||||
else: rlpFromBytes self.db.get(origRlp)
|
else: rlpFromBytes getPossiblyMissingNode(self.db, origRlp, fullPath, pathIndex)
|
||||||
|
|
||||||
let b = self.deleteAt(toDelete, path)
|
let b = self.deleteAt(toDelete, fullPath, pathIndex)
|
||||||
|
|
||||||
if b.len == 0:
|
if b.len == 0:
|
||||||
return false
|
return false
|
||||||
|
@ -427,14 +466,14 @@ proc deleteAux(self: var HexaryTrie; rlpWriter: var RlpWriter;
|
||||||
rlpWriter.appendAndSave(b, self.db)
|
rlpWriter.appendAndSave(b, self.db)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc graft(self: var HexaryTrie; r: Rlp): seq[byte] =
|
proc graft(self: var HexaryTrie; r: Rlp, fullPath: NibblesSeq, pathIndexToTheParent: int): seq[byte] =
|
||||||
doAssert r.isList and r.listLen == 2
|
doAssert r.isList and r.listLen == 2
|
||||||
var (_, origPath) = r.extensionNodeKey
|
var (_, origPath) = r.extensionNodeKey
|
||||||
var value = r.listElem(1)
|
var value = r.listElem(1)
|
||||||
|
|
||||||
if not value.isList:
|
if not value.isList:
|
||||||
let nodeKey = value.expectHash
|
let nodeKey = value.expectHash
|
||||||
var resolvedData = self.db.get(nodeKey)
|
var resolvedData = getPossiblyMissingNode(self.db, nodeKey, fullPath, pathIndexToTheParent + origPath.len)
|
||||||
self.prune(nodeKey)
|
self.prune(nodeKey)
|
||||||
value = rlpFromBytes resolvedData
|
value = rlpFromBytes resolvedData
|
||||||
|
|
||||||
|
@ -447,6 +486,8 @@ proc graft(self: var HexaryTrie; r: Rlp): seq[byte] =
|
||||||
return rlpWriter.finish
|
return rlpWriter.finish
|
||||||
|
|
||||||
proc mergeAndGraft(self: var HexaryTrie;
|
proc mergeAndGraft(self: var HexaryTrie;
|
||||||
|
fullPath: NibblesSeq;
|
||||||
|
pathIndexToTheParent: int,
|
||||||
soleChild: Rlp, childPos: byte): seq[byte] =
|
soleChild: Rlp, childPos: byte): seq[byte] =
|
||||||
var output = initRlpList(2)
|
var output = initRlpList(2)
|
||||||
if childPos == 16:
|
if childPos == 16:
|
||||||
|
@ -457,45 +498,61 @@ proc mergeAndGraft(self: var HexaryTrie;
|
||||||
output.append(soleChild)
|
output.append(soleChild)
|
||||||
result = output.finish()
|
result = output.finish()
|
||||||
|
|
||||||
if self.isTwoItemNode(soleChild):
|
if self.isTwoItemNode(soleChild, fullPath, pathIndexToTheParent + 1):
|
||||||
result = self.graft(rlpFromBytes(result))
|
result = self.graft(rlpFromBytes(result), fullPath, pathIndexToTheParent)
|
||||||
|
|
||||||
proc deleteAt(self: var HexaryTrie; origRlp: Rlp, key: NibblesSeq): seq[byte]
|
# If the key is present, returns the RLP bytes for a node that
|
||||||
|
# omits this key. Returns an empty seq if the key is absent.
|
||||||
|
proc deleteAt(self: var HexaryTrie; origRlp: Rlp, fullPath: NibblesSeq, pathIndex: int): seq[byte]
|
||||||
{.gcsafe, raises: [RlpError, Defect].} =
|
{.gcsafe, raises: [RlpError, Defect].} =
|
||||||
if origRlp.isEmpty:
|
if origRlp.isEmpty:
|
||||||
|
# It's empty RLP, so the key is absent, so no change necessary.
|
||||||
return
|
return
|
||||||
|
|
||||||
doAssert origRlp.isTrieBranch
|
doAssert origRlp.isTrieBranch
|
||||||
let origBytes = @(origRlp.rawData)
|
let origBytes = @(origRlp.rawData)
|
||||||
|
let path = fullPath.slice(pathIndex)
|
||||||
if origRlp.listLen == 2:
|
if origRlp.listLen == 2:
|
||||||
let (isLeaf, k) = origRlp.extensionNodeKey
|
let (isLeaf, k) = origRlp.extensionNodeKey
|
||||||
if k == key and isLeaf:
|
if k == path and isLeaf:
|
||||||
|
# This is the leaf for the key we're looking for.
|
||||||
|
# Omitting this key from the leaf means we're
|
||||||
|
# left with empty RLP.
|
||||||
self.dbDel origBytes
|
self.dbDel origBytes
|
||||||
return emptyRlp
|
return emptyRlp
|
||||||
|
|
||||||
if key.startsWith(k):
|
if path.startsWith(k):
|
||||||
var
|
# This extension node gets us *partway* to the desired
|
||||||
rlpWriter = initRlpList(2)
|
# key, but not all the way.
|
||||||
path = origRlp.listElem(0)
|
let path = origRlp.listElem(0)
|
||||||
value = origRlp.listElem(1)
|
let value = origRlp.listElem(1)
|
||||||
|
# Create RLP for a new 2-item node that omits the key we're
|
||||||
|
# trying to delete.
|
||||||
|
var rlpWriter = initRlpList(2)
|
||||||
rlpWriter.append(path)
|
rlpWriter.append(path)
|
||||||
if not self.deleteAux(rlpWriter, value, key.slice(k.len)):
|
if not self.deleteAux(rlpWriter, value, fullPath, pathIndex + k.len):
|
||||||
|
# Key is absent in the value, so never mind.
|
||||||
return
|
return
|
||||||
|
# We don't need the original node anymore, since we're about to
|
||||||
|
# replace it with a modified one.
|
||||||
self.dbDel origBytes
|
self.dbDel origBytes
|
||||||
var finalBytes = rlpWriter.finish
|
var finalBytes = rlpWriter.finish
|
||||||
var rlp = rlpFromBytes(finalBytes)
|
var rlp = rlpFromBytes(finalBytes)
|
||||||
if self.isTwoItemNode(rlp.listElem(1)):
|
# We already knew that *this* node is a 2-item node; now
|
||||||
return self.graft(rlp)
|
# we check to see if the modified *child* is also a 2-item
|
||||||
|
# node, because if so, we can graft it.
|
||||||
|
if self.isTwoItemNode(rlp.listElem(1), fullPath, pathIndex + k.len):
|
||||||
|
return self.graft(rlp, fullPath, pathIndex)
|
||||||
return finalBytes
|
return finalBytes
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
if key.len == 0 and origRlp.listElem(16).isEmpty:
|
if path.len == 0 and origRlp.listElem(16).isEmpty:
|
||||||
self.dbDel origBytes
|
self.dbDel origBytes
|
||||||
var foundChildPos: byte
|
var foundChildPos: byte
|
||||||
let singleChild = origRlp.findSingleChild(foundChildPos)
|
let singleChild = origRlp.findSingleChild(foundChildPos)
|
||||||
if singleChild.hasData and foundChildPos != 16:
|
if singleChild.hasData and foundChildPos != 16:
|
||||||
result = self.mergeAndGraft(singleChild, foundChildPos)
|
result = self.mergeAndGraft(fullPath, pathIndex + 1, singleChild, foundChildPos)
|
||||||
else:
|
else:
|
||||||
var rlpRes = initRlpList(17)
|
var rlpRes = initRlpList(17)
|
||||||
var iter = origRlp
|
var iter = origRlp
|
||||||
|
@ -508,12 +565,12 @@ proc deleteAt(self: var HexaryTrie; origRlp: Rlp, key: NibblesSeq): seq[byte]
|
||||||
return rlpRes.finish
|
return rlpRes.finish
|
||||||
else:
|
else:
|
||||||
var rlpWriter = initRlpList(17)
|
var rlpWriter = initRlpList(17)
|
||||||
let keyHead = int(key[0])
|
let keyHead = int(path[0])
|
||||||
var i = 0
|
var i = 0
|
||||||
var origCopy = origRlp
|
var origCopy = origRlp
|
||||||
for elem in items(origCopy):
|
for elem in items(origCopy):
|
||||||
if i == keyHead:
|
if i == keyHead:
|
||||||
if not self.deleteAux(rlpWriter, elem, key.slice(1)):
|
if not self.deleteAux(rlpWriter, elem, fullPath, pathIndex + 1):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
rlpWriter.append(elem)
|
rlpWriter.append(elem)
|
||||||
|
@ -525,47 +582,48 @@ proc deleteAt(self: var HexaryTrie; origRlp: Rlp, key: NibblesSeq): seq[byte]
|
||||||
var foundChildPos: byte
|
var foundChildPos: byte
|
||||||
let singleChild = resultRlp.findSingleChild(foundChildPos)
|
let singleChild = resultRlp.findSingleChild(foundChildPos)
|
||||||
if singleChild.hasData:
|
if singleChild.hasData:
|
||||||
result = self.mergeAndGraft(singleChild, foundChildPos)
|
result = self.mergeAndGraft(fullPath, pathIndex + 1, singleChild, foundChildPos)
|
||||||
|
|
||||||
proc del*(self: var HexaryTrie; key: openArray[byte]) =
|
proc del*(self: var HexaryTrie; key: openArray[byte]) =
|
||||||
var
|
var
|
||||||
rootBytes = keyToLocalBytes(self.db, self.root)
|
rootBytes = keyToLocalBytes(self.db, self.root)
|
||||||
rootRlp = rlpFromBytes rootBytes
|
rootRlp = rlpFromBytes rootBytes
|
||||||
|
|
||||||
var newRootBytes = self.deleteAt(rootRlp, initNibbleRange(key))
|
var newRootBytes = self.deleteAt(rootRlp, initNibbleRange(key), 0)
|
||||||
if newRootBytes.len > 0:
|
if newRootBytes.len > 0:
|
||||||
if rootBytes.len < 32:
|
if rootBytes.len < 32:
|
||||||
self.prune(self.root.asDbKey)
|
self.prune(self.root.asDbKey)
|
||||||
self.root = self.db.dbPut(newRootBytes)
|
self.root = self.db.dbPut(newRootBytes)
|
||||||
|
|
||||||
proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
|
proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
|
||||||
key: NibblesSeq, value: openArray[byte],
|
fullPath: NibblesSeq, pathIndex: int, value: openArray[byte],
|
||||||
isInline = false): seq[byte]
|
isInline = false): seq[byte]
|
||||||
{.gcsafe, raises: [RlpError, Defect].}
|
{.gcsafe, raises: [RlpError, Defect].}
|
||||||
|
|
||||||
proc mergeAt(self: var HexaryTrie, rlp: Rlp,
|
proc mergeAt(self: var HexaryTrie, rlp: Rlp,
|
||||||
key: NibblesSeq, value: openArray[byte],
|
fullPath: NibblesSeq, pathIndex: int, value: openArray[byte],
|
||||||
isInline = false): seq[byte] =
|
isInline = false): seq[byte] =
|
||||||
self.mergeAt(rlp, rlp.rawData.keccakHash, key, value, isInline)
|
self.mergeAt(rlp, rlp.rawData.keccakHash, fullPath, pathIndex, value, isInline)
|
||||||
|
|
||||||
proc mergeAtAux(self: var HexaryTrie, output: var RlpWriter, orig: Rlp,
|
proc mergeAtAux(self: var HexaryTrie, output: var RlpWriter, orig: Rlp,
|
||||||
key: NibblesSeq, value: openArray[byte]) =
|
fullPath: NibblesSeq, pathIndex: int, value: openArray[byte]) =
|
||||||
var resolved = orig
|
var resolved = orig
|
||||||
var isRemovable = false
|
var isRemovable = false
|
||||||
if not (orig.isList or orig.isEmpty):
|
if not (orig.isList or orig.isEmpty):
|
||||||
resolved = rlpFromBytes self.db.get(orig)
|
resolved = rlpFromBytes getPossiblyMissingNode(self.db, orig, fullPath, pathIndex)
|
||||||
isRemovable = true
|
isRemovable = true
|
||||||
|
|
||||||
let b = self.mergeAt(resolved, key, value, not isRemovable)
|
let b = self.mergeAt(resolved, fullPath, pathIndex, value, not isRemovable)
|
||||||
output.appendAndSave(b, self.db)
|
output.appendAndSave(b, self.db)
|
||||||
|
|
||||||
proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
|
proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
|
||||||
key: NibblesSeq, value: openArray[byte],
|
fullPath: NibblesSeq, pathIndex: int, value: openArray[byte],
|
||||||
isInline = false): seq[byte]
|
isInline = false): seq[byte]
|
||||||
{.gcsafe, raises: [RlpError, Defect].} =
|
{.gcsafe, raises: [RlpError, Defect].} =
|
||||||
|
let path = fullPath.slice(pathIndex)
|
||||||
template origWithNewValue: auto =
|
template origWithNewValue: auto =
|
||||||
self.prune(origHash.data)
|
self.prune(origHash.data)
|
||||||
replaceValue(orig, key, value)
|
replaceValue(orig, path, value)
|
||||||
|
|
||||||
if orig.isEmpty:
|
if orig.isEmpty:
|
||||||
return origWithNewValue()
|
return origWithNewValue()
|
||||||
|
@ -575,15 +633,15 @@ proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
|
||||||
let (isLeaf, k) = orig.extensionNodeKey
|
let (isLeaf, k) = orig.extensionNodeKey
|
||||||
var origValue = orig.listElem(1)
|
var origValue = orig.listElem(1)
|
||||||
|
|
||||||
if k == key and isLeaf:
|
if k == path and isLeaf:
|
||||||
return origWithNewValue()
|
return origWithNewValue()
|
||||||
|
|
||||||
let sharedNibbles = sharedPrefixLen(key, k)
|
let sharedNibbles = sharedPrefixLen(path, k)
|
||||||
|
|
||||||
if sharedNibbles == k.len and not isLeaf:
|
if sharedNibbles == k.len and not isLeaf:
|
||||||
var r = initRlpList(2)
|
var r = initRlpList(2)
|
||||||
r.append orig.listElem(0)
|
r.append orig.listElem(0)
|
||||||
self.mergeAtAux(r, origValue, key.slice(k.len), value)
|
self.mergeAtAux(r, origValue, fullPath, pathIndex + k.len, value)
|
||||||
return r.finish
|
return r.finish
|
||||||
|
|
||||||
if orig.rawData.len >= 32:
|
if orig.rawData.len >= 32:
|
||||||
|
@ -599,7 +657,7 @@ proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
|
||||||
top.append hexPrefixEncode(k.slice(0, sharedNibbles), false)
|
top.append hexPrefixEncode(k.slice(0, sharedNibbles), false)
|
||||||
top.appendAndSave(bottom.finish, self.db)
|
top.appendAndSave(bottom.finish, self.db)
|
||||||
|
|
||||||
return self.mergeAt(rlpFromBytes(top.finish), key, value, true)
|
return self.mergeAt(rlpFromBytes(top.finish), fullPath, pathIndex, value, true)
|
||||||
else:
|
else:
|
||||||
# Create a branch node
|
# Create a branch node
|
||||||
var branches = initRlpList(17)
|
var branches = initRlpList(17)
|
||||||
|
@ -623,22 +681,22 @@ proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
|
||||||
branches.append ""
|
branches.append ""
|
||||||
branches.append ""
|
branches.append ""
|
||||||
|
|
||||||
return self.mergeAt(rlpFromBytes(branches.finish), key, value, true)
|
return self.mergeAt(rlpFromBytes(branches.finish), fullPath, pathIndex, value, true)
|
||||||
else:
|
else:
|
||||||
if key.len == 0:
|
if path.len == 0:
|
||||||
return origWithNewValue()
|
return origWithNewValue()
|
||||||
|
|
||||||
if isInline:
|
if isInline:
|
||||||
self.prune(origHash.data)
|
self.prune(origHash.data)
|
||||||
|
|
||||||
let n = key[0]
|
let n = path[0]
|
||||||
var i = 0
|
var i = 0
|
||||||
var r = initRlpList(17)
|
var r = initRlpList(17)
|
||||||
|
|
||||||
var origCopy = orig
|
var origCopy = orig
|
||||||
for elem in items(origCopy):
|
for elem in items(origCopy):
|
||||||
if i == int(n):
|
if i == int(n):
|
||||||
self.mergeAtAux(r, elem, key.slice(1), value)
|
self.mergeAtAux(r, elem, fullPath, pathIndex + 1, value)
|
||||||
else:
|
else:
|
||||||
r.append(elem)
|
r.append(elem)
|
||||||
inc i
|
inc i
|
||||||
|
@ -648,11 +706,11 @@ proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
|
||||||
proc put*(self: var HexaryTrie; key, value: openArray[byte]) =
|
proc put*(self: var HexaryTrie; key, value: openArray[byte]) =
|
||||||
let root = self.root.hash
|
let root = self.root.hash
|
||||||
|
|
||||||
var rootBytes = self.db.get(root.data)
|
var rootBytes = getPossiblyMissingNode(self.db, root.data, NibblesSeq(), 0)
|
||||||
doAssert rootBytes.len > 0
|
doAssert rootBytes.len > 0
|
||||||
|
|
||||||
let newRootBytes = self.mergeAt(rlpFromBytes(rootBytes), root,
|
let newRootBytes = self.mergeAt(rlpFromBytes(rootBytes), root,
|
||||||
initNibbleRange(key), value)
|
initNibbleRange(key), 0, value)
|
||||||
if rootBytes.len < 32:
|
if rootBytes.len < 32:
|
||||||
self.prune(root.data)
|
self.prune(root.data)
|
||||||
|
|
||||||
|
@ -706,18 +764,6 @@ template maybeKeyToLocalBytes(db: DB, k: TrieNodeKey): Option[seq[byte]] =
|
||||||
else:
|
else:
|
||||||
db.maybeGet(k.asDbKey)
|
db.maybeGet(k.asDbKey)
|
||||||
|
|
||||||
proc maybeGetAux(db: DB, nodeRlp: Rlp, path: NibblesSeq): Option[seq[byte]]
|
|
||||||
{.gcsafe, raises: [RlpError, Defect].}
|
|
||||||
|
|
||||||
proc maybeGetAuxByHash(db: DB, node: TrieNodeKey, path: NibblesSeq): Option[seq[byte]] =
|
|
||||||
let maybeBytes = maybeKeyToLocalBytes(db, node)
|
|
||||||
if maybeBytes.isNone:
|
|
||||||
return none[seq[byte]]()
|
|
||||||
else:
|
|
||||||
let bytes = maybeBytes.get
|
|
||||||
var nodeRlp = rlpFromBytes(bytes)
|
|
||||||
return maybeGetAux(db, nodeRlp, path)
|
|
||||||
|
|
||||||
proc maybeGetLookup(db: DB, elem: Rlp): Option[Rlp] =
|
proc maybeGetLookup(db: DB, elem: Rlp): Option[Rlp] =
|
||||||
if elem.isList:
|
if elem.isList:
|
||||||
some(elem)
|
some(elem)
|
||||||
|
@ -730,7 +776,7 @@ proc maybeGetLookup(db: DB, elem: Rlp): Option[Rlp] =
|
||||||
let bytes = maybeBytes.get
|
let bytes = maybeBytes.get
|
||||||
some(rlpFromBytes(bytes))
|
some(rlpFromBytes(bytes))
|
||||||
|
|
||||||
proc maybeGetAux(db: DB, nodeRlp: Rlp, path: NibblesSeq): Option[seq[byte]]
|
proc maybeGetAux(db: DB, nodeRlp: Rlp, fullPath: NibblesSeq, pathIndex: int): Option[seq[byte]]
|
||||||
{.gcsafe, raises: [RlpError, Defect].} =
|
{.gcsafe, raises: [RlpError, Defect].} =
|
||||||
# FIXME-Adam: do I need to distinguish between these two cases?
|
# FIXME-Adam: do I need to distinguish between these two cases?
|
||||||
if not nodeRlp.hasData:
|
if not nodeRlp.hasData:
|
||||||
|
@ -745,6 +791,7 @@ proc maybeGetAux(db: DB, nodeRlp: Rlp, path: NibblesSeq): Option[seq[byte]]
|
||||||
return some(zero)
|
return some(zero)
|
||||||
# return none[seq[byte]]()
|
# return none[seq[byte]]()
|
||||||
|
|
||||||
|
let path = fullPath.slice(pathIndex)
|
||||||
case nodeRlp.listLen
|
case nodeRlp.listLen
|
||||||
of 2:
|
of 2:
|
||||||
let (isLeaf, k) = nodeRlp.extensionNodeKey
|
let (isLeaf, k) = nodeRlp.extensionNodeKey
|
||||||
|
@ -759,7 +806,7 @@ proc maybeGetAux(db: DB, nodeRlp: Rlp, path: NibblesSeq): Option[seq[byte]]
|
||||||
if maybeNextLookup.isNone:
|
if maybeNextLookup.isNone:
|
||||||
return none[seq[byte]]()
|
return none[seq[byte]]()
|
||||||
else:
|
else:
|
||||||
return maybeGetAux(db, maybeNextLookup.get, path.slice(sharedNibbles))
|
return maybeGetAux(db, maybeNextLookup.get, fullPath, pathIndex + sharedNibbles)
|
||||||
else:
|
else:
|
||||||
raise newException(RlpError, "isLeaf is true but the shared nibbles didn't exhaust the path?")
|
raise newException(RlpError, "isLeaf is true but the shared nibbles didn't exhaust the path?")
|
||||||
else:
|
else:
|
||||||
|
@ -777,13 +824,22 @@ proc maybeGetAux(db: DB, nodeRlp: Rlp, path: NibblesSeq): Option[seq[byte]]
|
||||||
if maybeNextLookup.isNone:
|
if maybeNextLookup.isNone:
|
||||||
return none[seq[byte]]()
|
return none[seq[byte]]()
|
||||||
else:
|
else:
|
||||||
return maybeGetAux(db, maybeNextLookup.get, path.slice(1))
|
return maybeGetAux(db, maybeNextLookup.get, fullPath, pathIndex + 1)
|
||||||
else:
|
else:
|
||||||
raise newException(CorruptedTrieDatabase,
|
raise newException(CorruptedTrieDatabase,
|
||||||
"HexaryTrie node with an unexpected number of children")
|
"HexaryTrie node with an unexpected number of children")
|
||||||
|
|
||||||
|
proc maybeGetAuxByHash(db: DB, node: TrieNodeKey, fullPath: NibblesSeq, pathIndex: int): Option[seq[byte]] =
|
||||||
|
let maybeBytes = maybeKeyToLocalBytes(db, node)
|
||||||
|
if maybeBytes.isNone:
|
||||||
|
return none[seq[byte]]()
|
||||||
|
else:
|
||||||
|
let bytes = maybeBytes.get
|
||||||
|
var nodeRlp = rlpFromBytes(bytes)
|
||||||
|
return maybeGetAux(db, nodeRlp, fullPath, pathIndex)
|
||||||
|
|
||||||
proc maybeGet*(self: HexaryTrie; key: openArray[byte]): Option[seq[byte]] =
|
proc maybeGet*(self: HexaryTrie; key: openArray[byte]): Option[seq[byte]] =
|
||||||
return maybeGetAuxByHash(self.db, self.root, initNibbleRange(key))
|
return maybeGetAuxByHash(self.db, self.root, initNibbleRange(key), 0)
|
||||||
|
|
||||||
proc maybeGet*(self: SecureHexaryTrie; key: openArray[byte]): Option[seq[byte]] =
|
proc maybeGet*(self: SecureHexaryTrie; key: openArray[byte]): Option[seq[byte]] =
|
||||||
return maybeGet(HexaryTrie(self), key.keccakHash.data)
|
return maybeGet(HexaryTrie(self), key.keccakHash.data)
|
||||||
|
|
Loading…
Reference in New Issue