Skip to content

Commit 0440d5b

Browse files
statemanager: add put/delete test case (#3787)
* statemanager: add put/delete test case * sm: clearer test vector * Update leaf node test to confirm that hash changes as expected * Add new util method for dumping node hashes * Partial fix for null child nodes * Collapse internal nodes when deleting values * verkle: improve type * verkle: clarify dumpNodeHashes and add comments * verkle: minor type improvements and fixes * monorepo: package lock update * verkle: remove only modifier * lint --------- Co-authored-by: acolytec3 <[email protected]>
1 parent e38dccc commit 0440d5b

File tree

9 files changed

+318
-133
lines changed

9 files changed

+318
-133
lines changed

package-lock.json

Lines changed: 3 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/statemanager/test/statefulVerkleStateManager.spec.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe('Verkle Tree API tests', () => {
2424
beforeAll(async () => {
2525
verkleCrypto = await loadVerkleCrypto()
2626
})
27-
it('should put/get/delete an account (with no storage/code from the trie', async () => {
27+
it('should put/get/delete an account (with no storage/code from the trie)', async () => {
2828
const trie = await createVerkleTree()
2929
const sm = new StatefulVerkleStateManager({ trie, verkleCrypto })
3030
const address = createAddressFromString('0x9e5ef720fa2cdfa5291eb7e711cfd2e62196f4b3')
@@ -37,6 +37,28 @@ describe('Verkle Tree API tests', () => {
3737
const deletedAccount = await sm.getAccount(address)
3838
assert.equal(deletedAccount, undefined)
3939
})
40+
41+
it('should return same stateRoot when putting and then deleting account', async () => {
42+
const trie = await createVerkleTree()
43+
const sm = new StatefulVerkleStateManager({ trie, verkleCrypto })
44+
45+
const address1 = createAddressFromString('0x9e5ef720fa2cdfa5291eb7e711cfd2e62196f4b3')
46+
const account1 = createAccount({ nonce: 3n, balance: 0xfffn })
47+
const address2 = createAddressFromString('0x9e5ef720fa2cdfa5291eb7e711cfd2e62196f4b4')
48+
const account2 = createAccount({ nonce: 4n, balance: 0xffen })
49+
50+
await sm.putAccount(address1, account1)
51+
const stateRootAfterPutAccount1 = await sm.getStateRoot()
52+
53+
// Put and then delete the account2
54+
await sm.putAccount(address2, account2)
55+
await sm.deleteAccount(address2)
56+
57+
// StateRoot should return to the initial stateRoot
58+
const stateRootAfterDeleteAccount = await sm.getStateRoot()
59+
assert.deepEqual(stateRootAfterPutAccount1, stateRootAfterDeleteAccount)
60+
})
61+
4062
it('should put and get code', async () => {
4163
const trie = await createVerkleTree()
4264
const sm = new StatefulVerkleStateManager({ trie, verkleCrypto })

packages/verkle/src/node/internalNode.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { ChildNode, VerkleNodeOptions } from './types.js'
77

88
export class InternalVerkleNode extends BaseVerkleNode<VerkleNodeType.Internal> {
99
// Array of tuples of uncompressed commitments (i.e. 64 byte Uint8Arrays) to child nodes along with the path to that child (i.e. the partial stem)
10-
public children: Array<ChildNode>
10+
public children: Array<ChildNode | null>
1111
public type = VerkleNodeType.Internal
1212

1313
constructor(options: VerkleNodeOptions[VerkleNodeType.Internal]) {
@@ -16,7 +16,7 @@ export class InternalVerkleNode extends BaseVerkleNode<VerkleNodeType.Internal>
1616
}
1717

1818
// Updates the commitment value for a child node at the corresponding index
19-
setChild(childIndex: number, child: ChildNode) {
19+
setChild(childIndex: number, child: ChildNode | null) {
2020
// Get previous child commitment at `index`
2121
const oldChildReference =
2222
this.children[childIndex] !== null
@@ -26,14 +26,16 @@ export class InternalVerkleNode extends BaseVerkleNode<VerkleNodeType.Internal>
2626
path: new Uint8Array(),
2727
}
2828
// Updates the commitment to the child node at `index`
29-
this.children[childIndex] = { ...child }
29+
this.children[childIndex] = child !== null ? { ...child } : null
3030
// Updates the overall node commitment based on the update to this child
3131
this.commitment = this.verkleCrypto.updateCommitment(
3232
this.commitment,
3333
childIndex,
3434
// The hashed child commitments are used when updating the internal node commitment
35-
this.verkleCrypto.hashCommitment(oldChildReference.commitment),
36-
this.verkleCrypto.hashCommitment(child.commitment),
35+
this.verkleCrypto.hashCommitment(oldChildReference!.commitment),
36+
this.verkleCrypto.hashCommitment(
37+
child !== null ? child.commitment : this.verkleCrypto.zeroCommitment,
38+
),
3739
)
3840
}
3941

packages/verkle/src/util.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
import { bytesToHex, concatBytes, equalsBytes } from '@ethereumjs/util'
22

3-
import { LeafVerkleNode, LeafVerkleNodeValue, decodeVerkleNode } from './node/index.js'
3+
import {
4+
InternalVerkleNode,
5+
LeafVerkleNode,
6+
LeafVerkleNodeValue,
7+
decodeVerkleNode,
8+
} from './node/index.js'
49

10+
import type { ChildNode } from './node/index.js'
511
import type { VerkleTree } from './verkleTree.js'
612
import type { PrefixedHexString } from '@ethereumjs/util'
713

14+
/**
15+
* Recursively walks down the tree from a given starting node and returns all the leaf values
16+
* @param tree - The verkle tree
17+
* @param startingNode - The starting node
18+
* @returns An array of key-value pairs containing the tree keys and associated values
19+
*/
820
export const dumpLeafValues = async (
921
tree: VerkleTree,
1022
startingNode: Uint8Array,
@@ -28,9 +40,55 @@ export const dumpLeafValues = async (
2840
} else {
2941
const childPaths = node.children
3042
.filter((value) => value !== null)
31-
.map((value) => dumpLeafValues(tree, tree['verkleCrypto'].hashCommitment(value.commitment)))
43+
.map((value) => dumpLeafValues(tree, tree['verkleCrypto'].hashCommitment(value!.commitment)))
3244

3345
const res = (await Promise.all(childPaths)).filter((val) => val !== undefined)
3446
return res.flat(1) as [PrefixedHexString, PrefixedHexString][]
3547
}
3648
}
49+
/**
50+
* Recursively walks down the tree from a given starting node and returns all the node paths and hashes
51+
* @param tree - The verkle tree
52+
* @param startingNode - The starting node
53+
* @returns An array of key-value pairs containing the tree paths and associated hashes
54+
*/
55+
export const dumpNodeHashes = async (
56+
tree: VerkleTree,
57+
startingNode: Uint8Array,
58+
): Promise<[PrefixedHexString, PrefixedHexString][] | undefined> => {
59+
let entries: [PrefixedHexString, PrefixedHexString][] = []
60+
// Retrieve starting node from DB
61+
const rawNode = await tree['_db'].get(startingNode)
62+
if (rawNode === undefined) return
63+
64+
const node = decodeVerkleNode(rawNode, tree['verkleCrypto'])
65+
// If current node is root, push '0x' for path and node hash for commitment
66+
equalsBytes(startingNode, tree.root()) && entries.push(['0x', bytesToHex(startingNode)])
67+
if (node instanceof InternalVerkleNode) {
68+
const children = node.children.filter((value) => value !== null) as ChildNode[]
69+
70+
// Push non-null children paths and hashes
71+
for (const child of children) {
72+
entries.push([
73+
bytesToHex(child.path),
74+
bytesToHex(tree['verkleCrypto'].hashCommitment(child.commitment)),
75+
])
76+
}
77+
78+
// Recursively call dumpNodeHashes on each child node
79+
const childPaths = (
80+
await Promise.all(
81+
children.map((value) =>
82+
dumpNodeHashes(tree, tree['verkleCrypto'].hashCommitment(value.commitment)),
83+
),
84+
)
85+
)
86+
.filter((val) => val !== undefined)
87+
.flat(1)
88+
89+
// Add all child paths and hashes to entries
90+
entries = [...entries, ...childPaths] as [PrefixedHexString, PrefixedHexString][]
91+
}
92+
93+
return entries
94+
}

0 commit comments

Comments
 (0)