Skip to content

Commit

Permalink
Trie: add partialPath parameter to trie.findPath() (ethereumjs#3305)
Browse files Browse the repository at this point in the history
* trie: add optional "partialPath" parameter to findPath

* trie: use partialPath input in findPath stack

* trie: start findPath walk from end of partialPath

* trie: identify starting point in debug log

* trie: test findPath with partial

* trie: test findPath on secure trie

---------

Co-authored-by: Holger Drewes <[email protected]>
  • Loading branch information
ScottyPoi and holgerd77 authored Mar 11, 2024
1 parent 435606e commit 891ee51
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 4 deletions.
27 changes: 23 additions & 4 deletions packages/trie/src/trie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,11 +596,23 @@ export class Trie {
* @param key - the search key
* @param throwIfMissing - if true, throws if any nodes are missing. Used for verifying proofs. (default: false)
*/
async findPath(key: Uint8Array, throwIfMissing = false): Promise<Path> {
async findPath(
key: Uint8Array,
throwIfMissing = false,
partialPath: {
stack: TrieNode[]
} = {
stack: [],
}
): Promise<Path> {
const targetKey = bytesToNibbles(key)
const keyLen = targetKey.length
const stack: TrieNode[] = Array.from({ length: keyLen })
let progress = 0
for (let i = 0; i < partialPath.stack.length - 1; i++) {
stack[i] = partialPath.stack[i]
progress += stack[i] instanceof BranchNode ? 1 : (<ExtensionNode>stack[i]).keyLength()
}
this.DEBUG && this.debug(`Target (${targetKey.length}): [${targetKey}]`, ['FIND_PATH'])
let result: Path | null = null

Expand Down Expand Up @@ -672,10 +684,17 @@ export class Trie {
walkController.allChildren(node, keyProgress)
}
}

const startingNode = partialPath.stack[partialPath.stack.length - 1]
const start = startingNode !== undefined ? this.hash(startingNode?.serialize()) : this.root()
try {
this.DEBUG && this.debug(`Walking trie from root: ${bytesToHex(this.root())}`, ['FIND_PATH'])
await this.walkTrie(this.root(), onFound)
this.DEBUG &&
this.debug(
`Walking trie from ${startingNode === undefined ? 'ROOT' : 'NODE'}: ${bytesToHex(
start as Uint8Array
)}`,
['FIND_PATH']
)
await this.walkTrie(start, onFound)
} catch (error: any) {
if (error.message !== 'Missing node in DB' || throwIfMissing) {
throw error
Expand Down
70 changes: 70 additions & 0 deletions packages/trie/test/trie/findPath.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { randomBytes } from '@ethereumjs/util'
import { assert, describe, it } from 'vitest'

import { Trie } from '../../src/index.js'

describe('TRIE > findPath', async () => {
const keys = Array.from({ length: 200 }, () => randomBytes(8))
const trie = new Trie()
for (const [i, k] of keys.entries()) {
await trie.put(k, Uint8Array.from([i, i]))
}
const rootNode = await trie.lookupNode(trie.root())
for (const [idx, k] of keys.slice(0, 10).entries()) {
const val = await trie.get(k)
it('should find values for key', async () => {
assert.deepEqual(val, Uint8Array.from([idx, idx]))
})
trie['debug']('FIND PATH ORIGINAL:' + '-'.repeat(20))
const path = await trie.findPath(k)
it('should find path for key', async () => {
assert.isNotNull(path.node)
assert.deepEqual(path.stack[0], rootNode)
assert.deepEqual(path.node?.value(), Uint8Array.from([idx, idx]))
})
trie['debug'](`FINDING PARTIAL PATHS: ` + path.stack.length + '-'.repeat(20))
for (let i = 1; i <= path.stack.length - 1; i++) {
trie['debug']('FIND PATH PARTIAL: ' + i + '-'.repeat(20))
const pathFromPartial = await trie.findPath(k, false, { stack: path.stack.slice(0, i) })
it(`should find path for key from partial stack (${i}/${path.stack.length})`, async () => {
assert.deepEqual(path, pathFromPartial)
assert.isNotNull(pathFromPartial.node)
assert.deepEqual(pathFromPartial.stack[0], rootNode)
assert.deepEqual(pathFromPartial.node?.value(), Uint8Array.from([idx, idx]))
assert.equal(path.stack.length, pathFromPartial.stack.length)
})
}
}
})
describe('TRIE (secure) > findPath', async () => {
const keys = Array.from({ length: 1000 }, () => randomBytes(20))
const trie = new Trie({ useKeyHashing: true })
for (const [i, k] of keys.entries()) {
await trie.put(k, Uint8Array.from([i, i]))
}
const rootNode = await trie.lookupNode(trie.root())
for (const [idx, k] of keys.slice(0, 10).entries()) {
const val = await trie.get(k)
it('should find value for key', async () => {
assert.deepEqual(val, Uint8Array.from([idx, idx]))
})
const path = await trie.findPath(trie['hash'](k))
it('should find path for key', async () => {
assert.isNotNull(path.node)
assert.deepEqual(path.stack[0], rootNode)
assert.deepEqual(path.node?.value(), Uint8Array.from([idx, idx]))
})
for (let i = 2; i <= path.stack.length - 1; i++) {
const pathFromPartial = await trie.findPath(trie['hash'](k), false, {
stack: path.stack.slice(0, i),
})
it(`should find path for key from partial stack (${i}/${path.stack.length})`, async () => {
assert.deepEqual(path, pathFromPartial)
assert.isNotNull(pathFromPartial.node)
assert.deepEqual(pathFromPartial.stack[0], rootNode)
assert.deepEqual(pathFromPartial.node?.value(), Uint8Array.from([idx, idx]))
assert.equal(path.stack.length, pathFromPartial.stack.length)
})
}
}
})

0 comments on commit 891ee51

Please sign in to comment.