diff --git a/eth/trie/db.nim b/eth/trie/db.nim index d7f79a5..361be60 100644 --- a/eth/trie/db.nim +++ b/eth/trie/db.nim @@ -1,7 +1,7 @@ {.push raises: [Defect].} import - std/[tables, hashes, sets], + std/[options, tables, hashes, sets], "."/[trie_defs, db_tracing] type @@ -234,6 +234,15 @@ proc contains*(db: TrieDatabaseRef, key: openArray[byte]): bool = if db.containsProc != nil: result = db.containsProc(db.obj, key) +proc maybeGet*(db: TrieDatabaseRef, key: openArray[byte]): Option[seq[byte]] = + # FIXME-Adam: Could duplicate the structure of get, but for now let's just + # do this (I still don't know whether this overall approach makes any sense + # at all.) + if db.contains(key): + some(db.get(key)) + else: + none[seq[byte]]() + # TransactionID imitate subset of JournalDB behaviour # but there is no need to rollback or dispose # TransactionID, because it will be handled elsewhere diff --git a/eth/trie/hexary.nim b/eth/trie/hexary.nim index f8c6289..fecb230 100644 --- a/eth/trie/hexary.nim +++ b/eth/trie/hexary.nim @@ -1,5 +1,5 @@ import - std/tables, + std/[options, tables], nimcrypto/[keccak, hash], ../rlp, "."/[trie_defs, nibbles, db] @@ -688,3 +688,102 @@ proc isValidBranch*(branch: seq[seq[byte]], rootHash: KeccakHash, key, value: se var trie = initHexaryTrie(db, rootHash) result = trie.get(key) == value + + + + +# The code below has a lot of duplication with the code above; I needed +# versions of get/put/del that don't just assume that all the nodes exist. +# Maybe there's some way to eliminate the duplication without screwing +# up performance? But for now I don't want to meddle with the existing +# code, for fear of breaking it. --Adam, Nov. 2022 + +proc db*(self: SecureHexaryTrie): TrieDatabaseRef = HexaryTrie(self).db + +template maybeKeyToLocalBytes(db: DB, k: TrieNodeKey): Option[seq[byte]] = + if k.len < 32: + some(k.getLocalBytes) + else: + 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] = + if elem.isList: + some(elem) + else: + let h = elem.expectHash + let maybeBytes = db.maybeGet(h) + if maybeBytes.isNone: + none[Rlp]() + else: + let bytes = maybeBytes.get + some(rlpFromBytes(bytes)) + +proc maybeGetAux(db: DB, nodeRlp: Rlp, path: NibblesSeq): Option[seq[byte]] + {.gcsafe, raises: [RlpError, Defect].} = + # FIXME-Adam: do I need to distinguish between these two cases? + if not nodeRlp.hasData: + let zero: seq[byte] = @[] + return some(zero) + # return none[seq[byte]]() + if nodeRlp.isEmpty: + # FIXME-Adam: I am REALLY not sure this is the right thing to do. But toGenesisHeader + # failing is a pretty clear indication. So let's try this. I wonder whether the + # above case needs to do this too. + let zero: seq[byte] = @[] + return some(zero) + # return none[seq[byte]]() + + case nodeRlp.listLen + of 2: + let (isLeaf, k) = nodeRlp.extensionNodeKey + let sharedNibbles = sharedPrefixLen(path, k) + + if sharedNibbles == k.len: + let value = nodeRlp.listElem(1) + if sharedNibbles == path.len and isLeaf: + return some(value.toBytes) + elif not isLeaf: + let maybeNextLookup = maybeGetLookup(db, value) + if maybeNextLookup.isNone: + return none[seq[byte]]() + else: + return maybeGetAux(db, maybeNextLookup.get, path.slice(sharedNibbles)) + else: + raise newException(RlpError, "isLeaf is true but the shared nibbles didn't exhaust the path?") + else: + let zero: seq[byte] = @[] + return some(zero) + of 17: + if path.len == 0: + return some(nodeRlp.listElem(16).toBytes) + var branch = nodeRlp.listElem(path[0].int) + if branch.isEmpty: + let zero: seq[byte] = @[] + return some(zero) + else: + let maybeNextLookup = maybeGetLookup(db, branch) + if maybeNextLookup.isNone: + return none[seq[byte]]() + else: + return maybeGetAux(db, maybeNextLookup.get, path.slice(1)) + else: + raise newException(CorruptedTrieDatabase, + "HexaryTrie node with an unexpected number of children") + +proc maybeGet*(self: HexaryTrie; key: openArray[byte]): Option[seq[byte]] = + return maybeGetAuxByHash(self.db, self.root, initNibbleRange(key)) + +proc maybeGet*(self: SecureHexaryTrie; key: openArray[byte]): Option[seq[byte]] = + return maybeGet(HexaryTrie(self), key.keccakHash.data)