Skip to content

Verkle Implementation: Build out Trie Processing #3430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 58 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0b03444
add verkle crypto to trie
acolytec3 May 10, 2024
f468ce2
Merge remote-tracking branch 'origin/master' into verkle-trie-build-out
acolytec3 May 13, 2024
c1ce5eb
More spam
acolytec3 May 13, 2024
a317698
Merge remote-tracking branch 'origin/master' into verkle-trie-build-out
acolytec3 May 15, 2024
1670155
Update leafNode.create [no ci]
acolytec3 May 15, 2024
55d644f
Merge remote-tracking branch 'origin/master' into verkle-trie-build-o…
acolytec3 May 16, 2024
974d1ae
small removal [no ci]
acolytec3 May 16, 2024
570f8c4
Merge remote-tracking branch 'origin/master' into verkle-trie-build-o…
acolytec3 May 17, 2024
430d801
Update trie.get to check existence first
acolytec3 May 20, 2024
fa34dde
Update `put` when inserting a new leaf node [no ci]
acolytec3 May 20, 2024
b2806cf
more halfway stuff [no ci]
acolytec3 May 21, 2024
f9e019e
Remove unnecessary commit [no ci]
acolytec3 May 31, 2024
74676b0
update verkle crypto dep [no ci]
acolytec3 Jun 3, 2024
a267be1
Add helper to create leaf values as 16 bytes
acolytec3 Jun 3, 2024
e3ab994
Changes to support c1 and c2 [no ci]
acolytec3 Jun 3, 2024
6e7e91e
update new leaf node commitment correctly [no ci]
acolytec3 Jun 4, 2024
4a716a7
Merge remote-tracking branch 'origin/master' into verkle-trie-build-o…
acolytec3 Jun 4, 2024
a2ed90e
Changes needed to make `put` work [no ci]
acolytec3 Jun 6, 2024
1902ebc
Begin fixing internal node implementation [no ci]
acolytec3 Jun 6, 2024
541240c
move verkleCrypto to verkleNode constructor opts
acolytec3 Jun 7, 2024
76da96c
address feedback
acolytec3 Jun 7, 2024
52d50a8
WIP [no ci]
acolytec3 Jun 7, 2024
86dd9e9
Finish naive findpath implementation
acolytec3 Jun 10, 2024
189b112
Update internal node layout
acolytec3 Jun 11, 2024
60219ae
WIP [no ci]
acolytec3 Jun 11, 2024
bbd0e96
Partial implementation of put [no ci]
acolytec3 Jun 11, 2024
2301d2c
update verkle crypto [no ci]
acolytec3 Jun 12, 2024
dcdd778
Update this.root [no ci]
acolytec3 Jun 12, 2024
cf33476
Clean up db opt typing [no ci]
acolytec3 Jun 12, 2024
b9285e2
API clean/comments [no ci]
acolytec3 Jun 12, 2024
1dcc64d
fix logic bug for nonexistent path [no ci]
acolytec3 Jun 12, 2024
f42ac23
Describe logic for updating tree along path [no ci]
acolytec3 Jun 12, 2024
e4e257a
Update `put` to use `saveStack` [no ci]
acolytec3 Jun 13, 2024
1b1706d
WIP [no ci]
acolytec3 Jun 13, 2024
96f99aa
revise leafNode.create API [no ci]
acolytec3 Jun 13, 2024
e4bcb66
more updates to put/get [no ci]
acolytec3 Jun 13, 2024
27144c0
More wip [no ci]
acolytec3 Jun 13, 2024
7f4ead6
Fix bug in internalNode deserialization [no ci]
acolytec3 Jun 14, 2024
abc442d
Add more comments [no ci]
acolytec3 Jun 14, 2024
eeb2ec8
remove duplicative function [no ci]
acolytec3 Jun 14, 2024
e7e81bf
more wip [no ci]
acolytec3 Jun 14, 2024
991f4d2
Add code to produce a 2 layer tree [no ci]
acolytec3 Jun 15, 2024
58e5298
wip [no ci]
acolytec3 Jun 17, 2024
06350d0
Add some initial debug logs [no ci]
acolytec3 Jun 17, 2024
df22e18
More progress [no ci]
acolytec3 Jun 17, 2024
69267f7
more half-working fixes [no ci]
acolytec3 Jun 18, 2024
911c5b1
Merge remote-tracking branch 'origin/master' into verkle-trie-build-out
acolytec3 Jun 19, 2024
a38cc80
Fix typing issues and remove walk controller
acolytec3 Jun 19, 2024
ce343ae
Remove walk controller export [no ci]
acolytec3 Jun 19, 2024
9c7b214
Add new test to demonstrate putting values in the trie [no ci]
acolytec3 Jun 19, 2024
ae09bdb
Add comment
acolytec3 Jun 19, 2024
05bfebd
Remove obsolete references
acolytec3 Jun 19, 2024
0b4904c
lint
acolytec3 Jun 19, 2024
cd65a9a
Remove references to depth and unused API components
acolytec3 Jun 20, 2024
1ca879e
Merge branch 'master' into verkle-trie-build-out
acolytec3 Jun 20, 2024
3ab0700
Update packages/verkle/src/node/internalNode.ts
acolytec3 Jun 21, 2024
720ab09
Address feedback
acolytec3 Jun 21, 2024
b47fa49
fix tests
acolytec3 Jun 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions electra.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
participants:
- el_type: ethereumjs
el_image: ethereumjs:local
cl_type: grandine
cl_image: ethpandaops/grandine:feature-electra
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem like that should belong to this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't. Accidentally included and will remove.


- el_type: ethereumjs
el_image: ethereumjs:local
cl_type: grandine
cl_image: ethpandaops/grandine:feature-electra

network_params:
electra_fork_epoch: 1
electra_fork_version: "0x50000038"
#global_log_level: debug
additional_services:
- dora
- tx_spammer
- blob_spammer

snooper_enabled: true
35 changes: 27 additions & 8 deletions packages/verkle/src/node/internalNode.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { equalsBytes } from '@ethereumjs/util'
import { BIGINT_0, bytesToBigInt, equalsBytes } from '@ethereumjs/util'

import { POINT_IDENTITY } from '../util/crypto.js'

import { BaseVerkleNode } from './baseVerkleNode.js'
import { LeafNode } from './leafNode.js'
import { NODE_WIDTH, VerkleNodeType } from './types.js'

import type { VerkleCrypto } from '../types.js'
import type { VerkleNode, VerkleNodeOptions } from './types.js'

export class InternalNode extends BaseVerkleNode<VerkleNodeType.Internal> {
Expand All @@ -14,9 +15,9 @@ export class InternalNode extends BaseVerkleNode<VerkleNodeType.Internal> {
public copyOnWrite: Record<string, Uint8Array>
public type = VerkleNodeType.Internal

/* TODO: options.children is not actually used here */
constructor(options: VerkleNodeOptions[VerkleNodeType.Internal]) {
super(options)
// TODO: Decide whether to fill with null or not - personal opinion - should never use null
this.children = options.children ?? new Array(NODE_WIDTH).fill(null)
this.copyOnWrite = options.copyOnWrite ?? {}
}
Expand Down Expand Up @@ -63,19 +64,30 @@ export class InternalNode extends BaseVerkleNode<VerkleNodeType.Internal> {
return this.children?.[index] ?? null
}

insert(key: Uint8Array, value: Uint8Array, resolver: () => void): void {
insert(
key: Uint8Array,
value: Uint8Array,
resolver: () => void,
verkleCrypto?: VerkleCrypto
): void {
const values = new Array<Uint8Array>(NODE_WIDTH)
values[key[31]] = value
this.insertStem(key.slice(0, 31), values, resolver)
this.insertStem(key.slice(0, 31), values, resolver, verkleCrypto!)
}

insertStem(stem: Uint8Array, values: Uint8Array[], resolver: () => void): void {
insertStem(
stem: Uint8Array,
values: Uint8Array[],
resolver: () => void,
verkleCrypto: VerkleCrypto
): void {
// Index of the child pointed by the next byte in the key
const childIndex = stem[this.depth]

const child = this.children[childIndex]

if (child instanceof LeafNode) {
// TODO: Understand the intent of what cowChild is suppoded to do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cow stands for "copy-on-write". As far as I recall cowChild is used as a record to mark the children that have been modified, in order to stack the commitment updates at the end since that is way more efficient.

this.cowChild(childIndex)
if (equalsBytes(child.stem, stem)) {
return child.insertMultiple(stem, values)
Expand All @@ -93,19 +105,26 @@ export class InternalNode extends BaseVerkleNode<VerkleNodeType.Internal> {

const nextByteInInsertedKey = stem[this.depth + 1]
if (nextByteInInsertedKey === nextByteInExistingKey) {
return newBranch.insertStem(stem, values, resolver)
return newBranch.insertStem(stem, values, resolver, verkleCrypto)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return newBranch.insertStem(stem, values, resolver, verkleCrypto)
return newBranch.insertStem(stem, values, resolver)

}

// Next word differs, so this was the last level.
// Insert it directly into its final slot.
const leafNode = LeafNode.create(stem, values)
// TODO: Determine if this is how to create the correct commitment
let commitment = verkleCrypto.zeroCommitment
for (const [idx, value] of values.entries()) {
if (bytesToBigInt(value) > BIGINT_0)
commitment = verkleCrypto.updateCommitment(commitment, idx, new Uint8Array(32), value)
}
const leafNode = LeafNode.create(stem, values, this.depth + 1, commitment)

// TODO - Why is the leaf node set at depth + 2 instead of + 1)?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this is because the leaf is inserted in the position of the suffix level commitment. So we have:

depth n : internal node:
depth n+1: the extension level commitment
depth n+2: the suffix level commitment

See this picture for how that corresponds to the structure:
image

leafNode.setDepth(this.depth + 2)
newBranch.cowChild(nextByteInInsertedKey)
newBranch.children[nextByteInInsertedKey] = leafNode
} else if (child instanceof InternalNode) {
this.cowChild(childIndex)
return child.insertStem(stem, values, resolver)
return child.insertStem(stem, values, resolver, verkleCrypto)
} else {
throw new Error('Invalid node type')
}
Expand Down
20 changes: 12 additions & 8 deletions packages/verkle/src/node/leafNode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { BaseVerkleNode } from './baseVerkleNode.js'
import { NODE_WIDTH, VerkleNodeType } from './types.js'

Expand All @@ -7,9 +6,9 @@ import type { VerkleNodeOptions } from './types.js'

export class LeafNode extends BaseVerkleNode<VerkleNodeType.Leaf> {
public stem: Uint8Array
public values: Uint8Array[]
public c1: Point
public c2: Point
public values: Uint8Array[] // Array of 256 possible values represented as 32 byte Uint8Arrays
public c1?: Point
public c2?: Point
public type = VerkleNodeType.Leaf

constructor(options: VerkleNodeOptions[VerkleNodeType.Leaf]) {
Expand All @@ -21,8 +20,13 @@ export class LeafNode extends BaseVerkleNode<VerkleNodeType.Leaf> {
this.c2 = options.c2
}

static create(stem: Uint8Array, values: Uint8Array[]): LeafNode {
throw new Error('Not implemented')
static create(
stem: Uint8Array,
values: Uint8Array[],
depth: number,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove depth here too?

commitment: Uint8Array
): LeafNode {
return new LeafNode({ stem, values, depth, commitment })
}

static fromRawNode(rawNode: Uint8Array[], depth: number): LeafNode {
Expand Down Expand Up @@ -73,8 +77,8 @@ export class LeafNode extends BaseVerkleNode<VerkleNodeType.Leaf> {
new Uint8Array([VerkleNodeType.Leaf]),
this.stem,
this.commitment,
this.c1.bytes(),
this.c2.bytes(),
this.c1?.bytes() ?? new Uint8Array(),
this.c2?.bytes() ?? new Uint8Array(),
...this.values,
]
}
Expand Down
4 changes: 2 additions & 2 deletions packages/verkle/src/node/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ interface VerkleInternalNodeOptions extends BaseVerkleNodeOptions {
interface VerkleLeafNodeOptions extends BaseVerkleNodeOptions {
stem: Uint8Array
values: Uint8Array[]
c1: Point
c2: Point
c1?: Point
c2?: Point
}

export interface VerkleNodeOptions {
Expand Down
4 changes: 4 additions & 0 deletions packages/verkle/src/util/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,7 @@ export const getTreeKeyForStorageSlot = async (

return concatBytes(getStem(verkleCrypto, address, treeIndex), toBytes(subIndex))
}

export const verifyKeyLength = (key: Uint8Array) => {
if (key.length !== 32) throw new Error(`expected key with length 32l; got ${key.length}`)
}
46 changes: 29 additions & 17 deletions packages/verkle/src/verkleTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import {
type VerkleTreeOptsWithDefaults,
} from './types.js'
import { WalkController, matchingBytesLength } from './util/index.js'
import { verifyKeyLength } from './util/keys.js'

import type { VerkleNode } from './node/types.js'
import type { FoundNodeFunction } from './types.js'
import type { FoundNodeFunction, VerkleCrypto } from './types.js'
import type { BatchDBOp, DB, PutBatch } from '@ethereumjs/util'

interface Path {
Expand Down Expand Up @@ -43,7 +44,7 @@ export class VerkleTree {
protected _lock = new Lock()
protected _root: Uint8Array

protected verkleCrypto: any
protected verkleCrypto: VerkleCrypto
/**
* Creates a new verkle tree.
* @param opts Options for instantiating the verkle tree
Expand Down Expand Up @@ -153,14 +154,16 @@ export class VerkleTree {
* @returns A Promise that resolves to `Uint8Array` if a value was found or `null` if no value was found.
*/
async get(key: Uint8Array, throwIfMissing = false): Promise<Uint8Array | null> {
const node = await this.findLeafNode(key, throwIfMissing)
if (node !== null) {
const keyLastByte = key[key.length - 1]

// The retrieved leaf node contains an array of 256 possible values.
// The index of the value we want is at the key's last byte
return node.values?.[keyLastByte] ?? null
}
verifyKeyLength(key)
const node = await this._db.get(key)
if (node !== undefined && node instanceof LeafNode)
if (node instanceof LeafNode) {
const keyLastByte = key[key.length - 1]

// The retrieved leaf node contains an array of 256 possible values.
// The index of the value we want is at the key's last byte
return node.values?.[keyLastByte] ?? null
}

return null
}
Expand All @@ -173,14 +176,22 @@ export class VerkleTree {
* @returns A Promise that resolves once value is stored.
*/
async put(key: Uint8Array, value: Uint8Array): Promise<void> {
verifyKeyLength(key)
await this._db.put(key, value)

// Find or create the leaf node
const leafNode = await this.findLeafNode(key, false)
if (leafNode === null) {
let leafNode = await this.findLeafNode(key, false)
if (!(leafNode instanceof LeafNode)) {
// If leafNode is missing, create it
// leafNode = LeafNode.create()
throw new Error('Not implemented')
const values = new Array(256) as Uint8Array[] // Create new empty array of 256 values
const suffix = key[31]
values[suffix] = value // Set value at key suffix

// Generate a commitment for the new leaf node, using the zero commitment as a base
const commitment = this.verkleCrypto.zeroCommitment
// TODO: Confirm the old/new scalar values we're passing in here are correct
this.verkleCrypto.updateCommitment(commitment, suffix, new Uint8Array(32), value)
leafNode = LeafNode.create(key.slice(0, 31), values, leafNode.length, commitment)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm not following this, but where is the part of the code where if the leafNode exists, we update it? I'm just seeing us create a new leaf if it does not exist in the trie.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, this is still very incomplete. This is where I'm currently focused. We need to actually pull parent nodes out of the DB and update the child commitment value in their values array and then update their commitment.

// Walk up the tree and update internal nodes
Expand Down Expand Up @@ -280,13 +291,14 @@ export class VerkleTree {
* @param key - the search key
* @param throwIfMissing - if true, throws if any nodes are missing. Used for verifying proofs. (default: false)
*/
async findLeafNode(key: Uint8Array, throwIfMissing = false): Promise<LeafNode | null> {
const { node } = await this.findPath(key, throwIfMissing)
async findLeafNode(key: Uint8Array, throwIfMissing = false): Promise<LeafNode | VerkleNode[]> {
const { node, stack } = await this.findPath(key, throwIfMissing)
if (!(node instanceof LeafNode)) {
if (throwIfMissing) {
throw new Error('leaf node not found')
}
return null
// return depth of remaining nodes
return stack
}
return node
}
Expand Down
18 changes: 16 additions & 2 deletions packages/verkle/test/leafNode.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { equalsBytes, randomBytes } from '@ethereumjs/util'
import { equalsBytes, hexToBytes, randomBytes } from '@ethereumjs/util'
import { assert, describe, it } from 'vitest'

import { VerkleNodeType } from '../src/node/index.js'
import { LeafNode } from '../src/node/leafNode.js'

import type { Point } from '../src/types.js'
import type { PrefixedHexString } from '@ethereumjs/util'

describe('verkle node - leaf', () => {
it('constructor should create an leaf node', async () => {
Expand Down Expand Up @@ -38,5 +39,18 @@ describe('verkle node - leaf', () => {
assert.equal(node.depth, depth, 'depth should be set')
})

it.todo('create method should create an leaf node')
it('create method should create an leaf node', () => {
const presentKeys = ['0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d01'].map(
(key) => hexToBytes(key as PrefixedHexString)
)

// Corresponding values for the present keys
const values = ['0x320122e8584be00d000000000000000000000000000000000000000000000000'].map(
(key) => hexToBytes(key as PrefixedHexString)
)
const stem = presentKeys[0].slice(0, 31)
const nodeData = values
const node = LeafNode.create(stem, nodeData, 0, new Uint8Array(32))
assert.ok(node instanceof LeafNode)
})
})
12 changes: 9 additions & 3 deletions packages/verkle/test/verkle.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { equalsBytes, hexToBytes } from '@ethereumjs/util'
import { MapDB, equalsBytes, hexToBytes } from '@ethereumjs/util'
import { loadVerkleCrypto } from 'verkle-cryptography-wasm'
import { assert, describe, it } from 'vitest'

import { VerkleTree } from '../src/verkleTree.js'
Expand Down Expand Up @@ -46,8 +47,13 @@ const absentKeys = [
].map((key) => hexToBytes(key as PrefixedHexString))

describe('Verkle tree', () => {
it.todo('should insert and retrieve values', async () => {
const tree = new VerkleTree()
it.only('should insert and retrieve values', async () => {
const verkleCrypto = await loadVerkleCrypto()
const tree = await VerkleTree.create({
verkleCrypto,
})
const db = new MapDB<Uint8Array, Uint8Array>()
tree.database(db)
for (let i = 0; i < presentKeys.length; i++) {
await tree.put(presentKeys[i], values[i])
}
Expand Down