Skip to content
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

statemanager: add put/delete test case #3787

Merged
merged 14 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 3 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 23 additions & 1 deletion packages/statemanager/test/statefulVerkleStateManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('Verkle Tree API tests', () => {
beforeAll(async () => {
verkleCrypto = await loadVerkleCrypto()
})
it('should put/get/delete an account (with no storage/code from the trie', async () => {
it('should put/get/delete an account (with no storage/code from the trie)', async () => {
const trie = await createVerkleTree()
const sm = new StatefulVerkleStateManager({ trie, verkleCrypto })
const address = createAddressFromString('0x9e5ef720fa2cdfa5291eb7e711cfd2e62196f4b3')
Expand All @@ -37,6 +37,28 @@ describe('Verkle Tree API tests', () => {
const deletedAccount = await sm.getAccount(address)
assert.equal(deletedAccount, undefined)
})

it('should return same stateRoot when putting and then deleting account', async () => {
const trie = await createVerkleTree()
const sm = new StatefulVerkleStateManager({ trie, verkleCrypto })

const address1 = createAddressFromString('0x9e5ef720fa2cdfa5291eb7e711cfd2e62196f4b3')
const account1 = createAccount({ nonce: 3n, balance: 0xfffn })
const address2 = createAddressFromString('0x9e5ef720fa2cdfa5291eb7e711cfd2e62196f4b4')
const account2 = createAccount({ nonce: 4n, balance: 0xffen })

await sm.putAccount(address1, account1)
const stateRootAfterPutAccount1 = await sm.getStateRoot()

// Put and then delete the account2
await sm.putAccount(address2, account2)
await sm.deleteAccount(address2)

// StateRoot should return to the initial stateRoot
const stateRootAfterDeleteAccount = await sm.getStateRoot()
assert.deepEqual(stateRootAfterPutAccount1, stateRootAfterDeleteAccount)
})

it('should put and get code', async () => {
const trie = await createVerkleTree()
const sm = new StatefulVerkleStateManager({ trie, verkleCrypto })
Expand Down
12 changes: 7 additions & 5 deletions packages/verkle/src/node/internalNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { ChildNode, VerkleNodeOptions } from './types.js'

export class InternalVerkleNode extends BaseVerkleNode<VerkleNodeType.Internal> {
// 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)
public children: Array<ChildNode>
public children: Array<ChildNode | null>
public type = VerkleNodeType.Internal

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

// Updates the commitment value for a child node at the corresponding index
setChild(childIndex: number, child: ChildNode) {
setChild(childIndex: number, child: ChildNode | null) {
// Get previous child commitment at `index`
const oldChildReference =
this.children[childIndex] !== null
Expand All @@ -26,14 +26,16 @@ export class InternalVerkleNode extends BaseVerkleNode<VerkleNodeType.Internal>
path: new Uint8Array(),
}
// Updates the commitment to the child node at `index`
this.children[childIndex] = { ...child }
this.children[childIndex] = child !== null ? { ...child } : null
// Updates the overall node commitment based on the update to this child
this.commitment = this.verkleCrypto.updateCommitment(
this.commitment,
childIndex,
// The hashed child commitments are used when updating the internal node commitment
this.verkleCrypto.hashCommitment(oldChildReference.commitment),
this.verkleCrypto.hashCommitment(child.commitment),
this.verkleCrypto.hashCommitment(oldChildReference!.commitment),
this.verkleCrypto.hashCommitment(
child !== null ? child.commitment : this.verkleCrypto.zeroCommitment,
),
)
}

Expand Down
62 changes: 60 additions & 2 deletions packages/verkle/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { bytesToHex, concatBytes, equalsBytes } from '@ethereumjs/util'

import { LeafVerkleNode, LeafVerkleNodeValue, decodeVerkleNode } from './node/index.js'
import {
InternalVerkleNode,
LeafVerkleNode,
LeafVerkleNodeValue,
decodeVerkleNode,
} from './node/index.js'

import type { ChildNode } from './node/index.js'
import type { VerkleTree } from './verkleTree.js'
import type { PrefixedHexString } from '@ethereumjs/util'

/**
* Recursively walks down the tree from a given starting node and returns all the leaf values
* @param tree - The verkle tree
* @param startingNode - The starting node
* @returns An array of key-value pairs containing the tree keys and associated values
*/
export const dumpLeafValues = async (
tree: VerkleTree,
startingNode: Uint8Array,
Expand All @@ -28,9 +40,55 @@ export const dumpLeafValues = async (
} else {
const childPaths = node.children
.filter((value) => value !== null)
.map((value) => dumpLeafValues(tree, tree['verkleCrypto'].hashCommitment(value.commitment)))
.map((value) => dumpLeafValues(tree, tree['verkleCrypto'].hashCommitment(value!.commitment)))

const res = (await Promise.all(childPaths)).filter((val) => val !== undefined)
return res.flat(1) as [PrefixedHexString, PrefixedHexString][]
}
}
/**
* Recursively walks down the tree from a given starting node and returns all the node paths and hashes
* @param tree - The verkle tree
* @param startingNode - The starting node
* @returns An array of key-value pairs containing the tree paths and associated hashes
*/
export const dumpNodeHashes = async (
tree: VerkleTree,
startingNode: Uint8Array,
): Promise<[PrefixedHexString, PrefixedHexString][] | undefined> => {
let entries: [PrefixedHexString, PrefixedHexString][] = []
// Retrieve starting node from DB
const rawNode = await tree['_db'].get(startingNode)
if (rawNode === undefined) return
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why do we want to return undefined here instead of [] ?


const node = decodeVerkleNode(rawNode, tree['verkleCrypto'])
// If current node is root, push '0x' for path and node hash for commitment
equalsBytes(startingNode, tree.root()) && entries.push(['0x', bytesToHex(startingNode)])
if (node instanceof InternalVerkleNode) {
const children = node.children.filter((value) => value !== null) as ChildNode[]

// Push non-null children paths and hashes
for (const child of children) {
entries.push([
bytesToHex(child.path),
bytesToHex(tree['verkleCrypto'].hashCommitment(child.commitment)),
])
}

// Recursively call dumpNodeHashes on each child node
const childPaths = (
await Promise.all(
children.map((value) =>
dumpNodeHashes(tree, tree['verkleCrypto'].hashCommitment(value.commitment)),
),
)
)
.filter((val) => val !== undefined)
.flat(1)

// Add all child paths and hashes to entries
entries = [...entries, ...childPaths] as [PrefixedHexString, PrefixedHexString][]
}

return entries
}
Loading
Loading