Skip to content

Commit c6ff99a

Browse files
Add trie.del (#3486)
* add trie.del function and tests * lint * Add last put to test * test: adjust test case description * Allow zeroes to be written for non-existent leafnode * clean up utility methods * address feedback, lint * fixes * Update packages/verkle/test/verkle.spec.ts * return zeroes when leaf value is "deleted" * fix test --------- Co-authored-by: Gabriel Rocheleau <[email protected]>
1 parent e08c229 commit c6ff99a

File tree

6 files changed

+92
-58
lines changed

6 files changed

+92
-58
lines changed

packages/verkle/src/node/leafNode.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { equalsBytes, intToBytes, setLengthLeft, setLengthRight } from '@ethereumjs/util'
22

33
import { BaseVerkleNode } from './baseVerkleNode.js'
4+
import { NODE_WIDTH, VerkleLeafNodeValue, VerkleNodeType } from './types.js'
45
import {
5-
NODE_WIDTH,
6-
VerkleLeafNodeValue,
7-
VerkleNodeType,
6+
createCValues,
87
createDefaultLeafValues,
98
createDeletedLeafValue,
109
createUntouchedLeafValue,
11-
} from './types.js'
12-
import { createCValues } from './util.js'
10+
} from './util.js'
1311

1412
import type { VerkleNodeOptions } from './types.js'
1513
import type { VerkleCrypto } from '@ethereumjs/util'
@@ -127,8 +125,10 @@ export class LeafNode extends BaseVerkleNode<VerkleNodeType.Leaf> {
127125
const value = this.values[index]
128126
switch (value) {
129127
case VerkleLeafNodeValue.Untouched:
130-
case VerkleLeafNodeValue.Deleted:
131128
return undefined
129+
case VerkleLeafNodeValue.Deleted:
130+
// Return zeroes if a value is "deleted" (i.e. overwitten with zeroes)
131+
return new Uint8Array(32)
132132
default:
133133
return value
134134
}

packages/verkle/src/node/types.ts

-12
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,3 @@ export interface VerkleNodeOptions {
5151
}
5252

5353
export const NODE_WIDTH = 256
54-
55-
export const createUntouchedLeafValue = () => new Uint8Array(32)
56-
57-
export const createDeletedLeafValue = () => {
58-
const bytes = new Uint8Array(32)
59-
// Set the 129th bit to 1 directly by setting the 17th byte (index 16) to 0x80
60-
bytes[16] = 0x80
61-
62-
return bytes
63-
}
64-
65-
export const createDefaultLeafValues = () => new Array(256).fill(0)

packages/verkle/src/node/util.ts

+44-35
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
import { RLP } from '@ethereumjs/rlp'
2-
import { bigIntToBytes, bytesToBigInt, setLengthRight } from '@ethereumjs/util'
2+
import { setLengthRight } from '@ethereumjs/util'
33

44
import { InternalNode } from './internalNode.js'
55
import { LeafNode } from './leafNode.js'
6-
import {
7-
VerkleLeafNodeValue,
8-
type VerkleNode,
9-
VerkleNodeType,
10-
createDeletedLeafValue,
11-
createUntouchedLeafValue,
12-
} from './types.js'
6+
import { VerkleLeafNodeValue, type VerkleNode, VerkleNodeType } from './types.js'
137

148
import type { VerkleCrypto } from '@ethereumjs/util'
159

@@ -37,19 +31,46 @@ export function isRawNode(node: Uint8Array | Uint8Array[]): node is Uint8Array[]
3731
return Array.isArray(node) && !(node instanceof Uint8Array)
3832
}
3933

34+
export function isLeafNode(node: VerkleNode): node is LeafNode {
35+
return node.type === VerkleNodeType.Leaf
36+
}
37+
38+
export function isInternalNode(node: VerkleNode): node is InternalNode {
39+
return node.type === VerkleNodeType.Internal
40+
}
41+
42+
export const createUntouchedLeafValue = () => new Uint8Array(32)
43+
44+
/**
45+
* Generates a 32 byte array of zeroes and sets the 129th bit to 1, which the EIP
46+
* refers to as the leaf marker to indicate a leaf value that has been touched previously
47+
* and contains only zeroes
48+
*
49+
* Note: this value should only used in the commitment update process
50+
*
51+
* @returns a 32 byte array of zeroes with the 129th bit set to 1
52+
*/
53+
export const createDeletedLeafValue = () => {
54+
const bytes = new Uint8Array(32)
55+
// Set the 129th bit to 1 directly by setting the 17th byte (index 16) to 0x80
56+
bytes[16] = 0x80
57+
58+
return bytes
59+
}
60+
61+
export const createDefaultLeafValues = () => new Array(256).fill(0)
62+
4063
/***
41-
* Converts 128 32byte values of a leaf node into 16 byte values for generating a commitment for half of a
42-
* leaf node's values
43-
* @param values - an array of Uint8Arrays representing the first or second set of 128 values stored by the verkle trie leaf node
44-
* @param deletedValues - an array of booleans where a value of true at a given position indicates a value
45-
* that is being deleted - should always be false if generating C2 values
46-
* Returns an array of 256 16byte UintArrays with the leaf marker set for each value that is deleted
64+
* Converts 128 32byte values of a leaf node into an array of 256 32 byte values representing
65+
* the first and second 16 bytes of each value right padded with zeroes for generating a
66+
* commitment for half of a leaf node's values
67+
* @param values - an array of Uint8Arrays representing the first or second set of 128 values
68+
* stored by the verkle trie leaf node
69+
* Returns an array of 256 32 byte UintArrays with the leaf marker set for each value that is
70+
* deleted
4771
*/
48-
export const createCValues = (
49-
values: (Uint8Array | VerkleLeafNodeValue)[],
50-
deletedValues = new Array(128).fill(false)
51-
) => {
52-
if (values.length !== 128 || deletedValues.length !== 128)
72+
export const createCValues = (values: (Uint8Array | VerkleLeafNodeValue)[]) => {
73+
if (values.length !== 128)
5374
throw new Error(`got wrong number of values, expected 128, got ${values.length}`)
5475
const expandedValues: Uint8Array[] = new Array(256)
5576
for (let x = 0; x < 128; x++) {
@@ -59,30 +80,18 @@ export const createCValues = (
5980
case VerkleLeafNodeValue.Untouched: // Leaf value that has never been written before
6081
val = createUntouchedLeafValue()
6182
break
62-
case VerkleLeafNodeValue.Deleted: // Leaf value that has been overwritten with zeros (i.e. a deleted value)
83+
case VerkleLeafNodeValue.Deleted: // Leaf value that has been written with zeros (either zeroes or a deleted value)
6384
val = createDeletedLeafValue()
64-
6585
break
6686
default:
6787
val = retrievedValue
6888
break
6989
}
7090
// We add 16 trailing zeros to each value since all commitments are padded to an array of 32 byte values
71-
expandedValues[x * 2] = setLengthRight(
72-
deletedValues[x] === true
73-
? bigIntToBytes(bytesToBigInt(val.subarray(0, 16)) + BigInt(2 ** 128))
74-
: val.slice(0, 16),
75-
32
76-
)
77-
// TODO: Decide if we should use slice or subarray here (i.e. do we need to copy these slices or not)
91+
// TODO: Determine whether we need to apply the leaf marker (i.e. set 129th bit) for all written values
92+
// regardless of whether the value stored is zero or not
93+
expandedValues[x * 2] = setLengthRight(val.slice(0, 16), 32)
7894
expandedValues[x * 2 + 1] = setLengthRight(val.slice(16), 32)
7995
}
8096
return expandedValues
8197
}
82-
export function isLeafNode(node: VerkleNode): node is LeafNode {
83-
return node.type === VerkleNodeType.Leaf
84-
}
85-
86-
export function isInternalNode(node: VerkleNode): node is InternalNode {
87-
return node.type === VerkleNodeType.Internal
88-
}

packages/verkle/src/verkleTree.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import { loadVerkleCrypto } from 'verkle-cryptography-wasm'
1414
import { CheckpointDB } from './db/checkpoint.js'
1515
import { InternalNode } from './node/internalNode.js'
1616
import { LeafNode } from './node/leafNode.js'
17-
import { type VerkleNode } from './node/types.js'
18-
import { decodeNode, isLeafNode } from './node/util.js'
17+
import { VerkleLeafNodeValue, type VerkleNode } from './node/types.js'
18+
import { createDeletedLeafValue, decodeNode, isLeafNode } from './node/util.js'
1919
import {
2020
type Proof,
2121
ROOT_DB_KEY,
@@ -255,7 +255,13 @@ export class VerkleTree {
255255
this.DEBUG && this.debug(`Creating new leaf node at stem: ${bytesToHex(stem)}`, ['PUT'])
256256
}
257257
// Update value in leaf node and push to putStack
258-
leafNode.setValue(suffix, value)
258+
if (equalsBytes(value, createDeletedLeafValue())) {
259+
// Special case for when the deleted leaf value or zeroes is passed to `put`
260+
// Writing the deleted leaf value to the suffix indicated in the key
261+
leafNode.setValue(suffix, VerkleLeafNodeValue.Deleted)
262+
} else {
263+
leafNode.setValue(suffix, value)
264+
}
259265
this.DEBUG &&
260266
this.debug(
261267
`Updating value for suffix: ${suffix} at leaf node with stem: ${bytesToHex(stem)}`,
@@ -319,6 +325,12 @@ export class VerkleTree {
319325
await this.saveStack(putStack)
320326
}
321327

328+
async del(key: Uint8Array): Promise<void> {
329+
const stem = key.slice(0, 31)
330+
const suffix = key[key.length - 1]
331+
this.DEBUG && this.debug(`Stem: ${bytesToHex(stem)}; Suffix: ${suffix}`, ['DEL'])
332+
await this.put(key, createDeletedLeafValue())
333+
}
322334
/**
323335
* Helper method for updating or creating the parent internal node for a given leaf node
324336
* @param leafNode the child leaf node that will be referenced by the new/updated internal node

packages/verkle/test/leafNode.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe('verkle node - leaf', () => {
5858
node.setValue(0, setLengthLeft(Uint8Array.from([5]), 32))
5959
assert.deepEqual(node.getValue(0), setLengthLeft(Uint8Array.from([5]), 32))
6060
node.setValue(0, VerkleLeafNodeValue.Deleted)
61-
assert.equal(node.getValue(0), undefined)
61+
assert.deepEqual(node.getValue(0), new Uint8Array(32))
6262
})
6363

6464
it('should update a commitment when setting a value', async () => {

packages/verkle/test/verkle.spec.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { assert, beforeAll, describe, it } from 'vitest'
55
import {
66
InternalNode,
77
LeafNode,
8+
VerkleLeafNodeValue,
89
VerkleNodeType,
910
decodeNode,
1011
matchingBytesLength,
@@ -211,7 +212,7 @@ describe('Verkle tree', () => {
211212
assert.deepEqual(val2, hexToBytes(values[2]), 'confirm values[2] can be retrieved from trie')
212213
})
213214

214-
it('should put values and find them', async () => {
215+
it('should sequentially put->find->delete->put values', async () => {
215216
const keys = [
216217
// Two keys with the same stem but different suffixes
217218
'0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d01',
@@ -241,5 +242,29 @@ describe('Verkle tree', () => {
241242
assert.deepEqual(await trie.get(hexToBytes(keys[0])), hexToBytes(values[0]))
242243
assert.deepEqual(await trie.get(hexToBytes(keys[2])), hexToBytes(values[2]))
243244
assert.deepEqual(await trie.get(hexToBytes(keys[3])), hexToBytes(values[3]))
245+
246+
await trie.del(hexToBytes(keys[0]))
247+
assert.deepEqual(await trie.get(hexToBytes(keys[0])), new Uint8Array(32))
248+
249+
await trie.put(hexToBytes(keys[0]), hexToBytes(values[0]))
250+
assert.deepEqual(await trie.get(hexToBytes(keys[0])), hexToBytes(values[0]))
251+
})
252+
it('should put zeros in leaf node when del called with stem that was not in the trie before', async () => {
253+
const keys = ['0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d01']
254+
255+
const trie = await VerkleTree.create({
256+
verkleCrypto,
257+
db: new MapDB<Uint8Array, Uint8Array>(),
258+
})
259+
260+
await trie['_createRootNode']()
261+
assert.deepEqual(await trie.get(hexToBytes(keys[0])), undefined)
262+
await trie.del(hexToBytes(keys[0]))
263+
const res = await trie.findPath(hexToBytes(keys[0]).slice(0, 31))
264+
assert.ok(res.node !== null)
265+
assert.deepEqual(
266+
(res.node as LeafNode).values[hexToBytes(keys[0])[31]],
267+
VerkleLeafNodeValue.Deleted
268+
)
244269
})
245270
})

0 commit comments

Comments
 (0)