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

verkle: migrate to verkle-cryptography-wasm #3356

Merged
merged 11 commits into from
Apr 16, 2024
2 changes: 2 additions & 0 deletions .github/workflows/browser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,6 @@ jobs:
- run: npm run test:browser -w=@ethereumjs/statemanager
- run: npm run test:browser -w=@ethereumjs/evm
- run: npm run test:browser -w=@ethereumjs/vm
- run: npm run test:browser -w=@ethereumjs/verkle


23 changes: 16 additions & 7 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion packages/block/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { Block } from './block.js'
export { executionPayloadFromBeaconPayload } from './from-beacon-payload.js'
export { type BeaconPayloadJson, executionPayloadFromBeaconPayload } from './from-beacon-payload.js'
export { BlockHeader } from './header.js'
export { getDifficulty, valuesArrayToHeaderData } from './helpers.js'
export * from './types.js'
3 changes: 1 addition & 2 deletions packages/client/src/execution/vmexecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,8 @@ export class VMExecution extends Execution {
if (this.verkleVM !== undefined) {
return
}

this.config.logger.info(`Setting up verkleVM`)
const stateManager = new StatelessVerkleStateManager()
const stateManager = await StatelessVerkleStateManager.create()
this.verkleVM = await VM.create({
common: this.config.execCommon,
blockchain: this.chain.blockchain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,51 @@ import * as td from 'testdouble'
import { assert, describe, it } from 'vitest'

import blocks from '../../testdata/blocks/kaustinen4.json'
import genesisJSON from '../../testdata/geth-genesis/kaustinen5.json'
import genesisJSON from '../../testdata/geth-genesis/kaustinen6.json'
import { getRpcClient, setupChain } from '../helpers.js'

import type { Chain } from '../../../src/blockchain'
import type { BeaconPayloadJson } from '@ethereumjs/block'
import type { Common } from '@ethereumjs/common'
import type { HttpClient } from 'jayson/promise'
const genesisVerkleStateRoot = '0x382960711d9ccf58b9db20122e2253eb9bfa99d513f8c9d4e85b55971721f4de'
const genesisVerkleBlockHash = '0x086326f2922364dba375e7c9bed375d622845615c0974ffd1d3be0e34edbfbc3'
const genesisVerkleStateRoot = '0x1fbf85345a3cbba9a6d44f991b721e55620a22397c2a93ee8d5011136ac300ee'
const genesisVerkleBlockHash = '0x3fe165c03e7a77d1e3759362ebeeb16fd964cb411ce11fbe35c7032fab5b9a8a'

/**
* One can run this test in two formats:
* 1. On the saved blocks, comma separated which are limited (353,368,374,467)
* One can run this test in this format:
* 1. Directly pull slots from a kaustinen beacon url
* `TEST_ONLINE_SLOTS=15 PEER_BEACON_URL=https://beacon.verkle-gen-devnet-6.ethpandaops.io DEBUG=ethjs,vm:*,evm:*,statemanager:verkle* npx vitest run test/rpc/engine/kaustinen6.spec.ts`
*
* However there are other ways to run the test with save data and testvectors but they are from old versions but
* can be updated to make it work
*
* a. On the saved blocks, comma separated (were produced for kaustinen4 )
* `TEST_SAVED_NUMBERS=353,368,374,467 npx vitest run test/rpc/engine/kaustinen5.spec.ts`
* 2. Directly pull slots from a kaustinen beacon url
* `TEST_ONLINE_SLOTS=15 PEER_BEACON_URL=https://beacon.verkle-gen-devnet-5.ethpandaops.io DEBUG=ethjs,vm:*,evm:*,statemanager:verkle* npx vitest run test/rpc/engine/kaustinen5.spec.ts`
* 3. Geth produced testvectors
* `TEST_GETH_VEC_DIR=test/testdata/gethk5vecs DEBUG=ethjs,vm:*,evm:*,statemanager:verkle* npx vitest run test/rpc/engine/kaustinen5.spec.ts`
* b. Geth produced testvectors (were produced for kaustinen5)
* `TEST_GETH_VEC_DIR=test/testdata/gethk5vecs DEBUG=ethjs,vm:*,evm:*,statemanager:verkle* npx vitest run test/rpc/engine/kaustinen6.spec.ts`
*/

const originalValidate = (BlockHeader as any).prototype._consensusFormatValidation

async function fetchExecutionPayload(
peerBeaconUrl: string,
slot: number | string
): Promise<BeaconPayloadJson> {
const beaconBlock = await (await fetch(`${peerBeaconUrl}/eth/v2/beacon/blocks/${slot}`)).json()
return beaconBlock.data.message.body.execution_payload
): Promise<BeaconPayloadJson | undefined> {
let beaconPayload: BeaconPayloadJson | undefined = undefined
try {
const beaconBlock = await (await fetch(`${peerBeaconUrl}/eth/v2/beacon/blocks/${slot}`)).json()
beaconPayload = beaconBlock.data.message.body.execution_payload
// eslint-disable-next-line no-empty
} catch (_e) {}

return beaconPayload
}

async function runBlock(
{ chain, rpc, common }: { chain: Chain; rpc: HttpClient; common: Common },
{ execute, parent }: { execute: any; parent: any },
isBeaconData: boolean
isBeaconData: boolean,
context: any
) {
const blockCache = chain.blockCache

Expand All @@ -51,6 +62,11 @@ async function runBlock(
const executePayload =
isBeaconData === true ? executionPayloadFromBeaconPayload(execute as any) : execute
const res = await rpc.request('engine_newPayloadV2', [executePayload])

// if the block was not executed mark as skip so it shows in test
if (res.result.status === 'ACCEPTED') {
context.skip()
}
assert.equal(res.result.status, 'VALID', 'valid status should be received')
}

Expand All @@ -75,21 +91,22 @@ describe(`valid verkle network setup`, async () => {
const savedTestCases = process.env.TEST_SAVED_NUMBERS?.split(',') ?? []

for (const testCase of savedTestCases) {
it(`run saved block ${testCase}`, async () => {
it(`run saved block ${testCase}`, async (context) => {
let testData
let isBeaconData
if (process.env.SAVED_DATA_DIR !== undefined) {
const fileName = `${process.env.SAVED_DATA_DIR}/${testCase}.json`
testData = JSON.parse(readFileSync(fileName))[testCase]
testData = JSON.parse(readFileSync(fileName, 'utf8'))[testCase]
isBeaconData = false
} else {
// @ts-expect-error -- Typescript complains that `testCase` can't index the `blocks` object
testData = blocks[testCase]
isBeaconData = true
}
if (testData === undefined) {
throw Error('unavailable data')
}
await runBlock({ common, chain, rpc }, testData, isBeaconData)
await runBlock({ common, chain, rpc }, testData, isBeaconData, context)
})
}

Expand All @@ -107,9 +124,14 @@ describe(`valid verkle network setup`, async () => {
let parent = await fetchExecutionPayload(process.env.PEER_BEACON_URL, startSlot - 1)
for (let i = startSlot; i <= endSlot; i++) {
const execute = await fetchExecutionPayload(process.env.PEER_BEACON_URL, i)
it(`run fetched block slot: ${i} number: ${execute.block_number}`, async () => {
if (execute === undefined) {
// may be there was no block on this slot
continue
}

it(`run fetched block slot: ${i} number: ${execute.block_number}`, async (context) => {
try {
await runBlock({ common, chain, rpc }, { parent, execute }, true)
await runBlock({ common, chain, rpc }, { parent, execute }, true, context)
} finally {
parent = execute
}
Expand All @@ -123,8 +145,8 @@ describe(`valid verkle network setup`, async () => {
let parent = gethVecs[0]
for (let i = 1; i < gethVecs.length; i++) {
const execute = gethVecs[i]
it(`run geth vector: ${execute.blockNumber}`, async () => {
await runBlock({ common, chain, rpc }, { parent, execute }, false)
it(`run geth vector: ${execute.blockNumber}`, async (context) => {
await runBlock({ common, chain, rpc }, { parent, execute }, false, context)
parent = execute
})
}
Expand All @@ -139,8 +161,8 @@ describe(`valid verkle network setup`, async () => {

async function loadGethVectors(vectorsDirPath: string, opts: { common: Common }) {
// set chain id to 1 for geth vectors
opts.common._chainParams.chainId = BigInt(1)
const stateDiffVec = JSON.parse(readFileSync(`${vectorsDirPath}/statediffs.json`))
opts.common['_chainParams'].chainId = BigInt(1)
const stateDiffVec = JSON.parse(readFileSync(`${vectorsDirPath}/statediffs.json`, 'utf8'))
const executionWitness0 = {
stateDiff: [],
verkleProof: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -897,5 +897,5 @@
"nonce": "0x1234",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "1711712640"
"timestamp": "1712918460"
}
10 changes: 5 additions & 5 deletions packages/common/src/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,23 +570,23 @@ export const chains: ChainsDict = {
'enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.holesky.ethdisco.net',
],
},
kaustinen5: {
name: 'kaustinen5',
kaustinen6: {
name: 'kaustinen6',
chainId: 69420,
networkId: 69420,
defaultHardfork: 'prague',
consensus: {
type: 'pos',
algorithm: 'casper',
},
comment: 'Verkle kaustinen testnet 3 (likely temporary, do not hard-wire into production code)',
comment: 'Verkle kaustinen testnet 6 (likely temporary, do not hard-wire into production code)',
url: 'https://github.com/eth-clients/kaustinen/',
genesis: {
difficulty: '0x01',
extraData: '0x',
gasLimit: '0x17D7840',
nonce: '0x0000000000001234',
timestamp: '0x6606a9bc',
timestamp: '0x66190fbc',
},
hardforks: [
{
Expand Down Expand Up @@ -646,7 +646,7 @@ export const chains: ChainsDict = {
{
name: 'prague',
block: null,
timestamp: '1711712640',
timestamp: '1712848500',
},
],
bootstrapNodes: [],
Expand Down
8 changes: 4 additions & 4 deletions packages/common/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export enum Chain {
Goerli = 5,
Sepolia = 11155111,
Holesky = 17000,
Kaustinen5 = 69420,
Kaustinen6 = 69420,
}

/**
Expand Down Expand Up @@ -44,10 +44,10 @@ export const ChainGenesis: Record<Chain, GenesisState> = {
blockNumber: BIGINT_0,
stateRoot: hexToBytes('0x69d8c9d72f6fa4ad42d4702b433707212f90db395eb54dc20bc85de253788783'),
},
[Chain.Kaustinen5]: {
name: 'kaustinen5',
[Chain.Kaustinen6]: {
name: 'kaustinen6',
blockNumber: BIGINT_0,
stateRoot: hexToBytes('0x382960711d9ccf58b9db20122e2253eb9bfa99d513f8c9d4e85b55971721f4de'),
stateRoot: hexToBytes('0x1fbf85345a3cbba9a6d44f991b721e55620a22397c2a93ee8d5011136ac300ee'),
},
}

Expand Down
8 changes: 4 additions & 4 deletions packages/evm/src/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,11 @@ export class Interpreter {
this._runState.env.chargeCodeAccesses === true
) {
const contract = this._runState.interpreter.getAddress()

if (
!(this._runState.stateManager as StatelessVerkleStateManager).checkChunkWitnessPresent(
contract,
programCounter
)
!(await (
this._runState.stateManager as StatelessVerkleStateManager
).checkChunkWitnessPresent(contract, programCounter))
) {
throw Error(`Invalid witness with missing codeChunk for pc=${programCounter}`)
}
Expand Down
1 change: 0 additions & 1 deletion packages/evm/test/precompiles/0f-bls12-g2mul.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ describe('Precompiles: BLS12-G2-MUL', () => {
const evm = await EVM.create({
common,
})
console.log(getActivePrecompiles(common))
const BLS12G2MUL = getActivePrecompiles(common).get('000000000000000000000000000000000000000f')!

for (const testVector of testData) {
Expand Down
2 changes: 1 addition & 1 deletion packages/genesis/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('genesis test', () => {
const chainIds = Object.keys(ChainGenesis)
for (const chainId of chainIds) {
// Kaustinen can have an empty genesis state since verkle blocks contain their pre-state
if (Number(chainId) === Chain.Kaustinen5) continue
if (Number(chainId) === Chain.Kaustinen6) continue

const { name, stateRoot: expectedRoot } = ChainGenesis[chainId as unknown as Chain]

Expand Down
3 changes: 2 additions & 1 deletion packages/statemanager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"debug": "^4.3.3",
"ethereum-cryptography": "^2.1.3",
"js-sdsl": "^4.1.4",
"lru-cache": "10.1.0"
"lru-cache": "10.1.0",
"verkle-cryptography-wasm": "^0.4.0"
},
"devDependencies": {
"@ethereumjs/block": "^5.2.0",
Expand Down
20 changes: 15 additions & 5 deletions packages/statemanager/src/accessWitness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getKey, getStem } from '@ethereumjs/verkle'
import debugDefault from 'debug'

import type { Address, PrefixedHexString } from '@ethereumjs/util'
import type { VerkleCrypto } from '@ethereumjs/verkle'

const { debug: createDebugLogger } = debugDefault
const debug = createDebugLogger('statemanager:verkle:aw')
Expand Down Expand Up @@ -72,13 +73,18 @@ export type AccessedStateWithAddress = AccessedState & {
export class AccessWitness {
stems: Map<PrefixedHexString, StemAccessEvent & StemMeta>
chunks: Map<PrefixedHexString, ChunkAccessEvent>

verkleCrypto: VerkleCrypto
constructor(
opts: {
verkleCrypto?: VerkleCrypto
stems?: Map<PrefixedHexString, StemAccessEvent & StemMeta>
chunks?: Map<PrefixedHexString, ChunkAccessEvent>
} = {}
) {
if (opts.verkleCrypto === undefined) {
throw new Error('verkle crypto required')
}
this.verkleCrypto = opts.verkleCrypto
this.stems = opts.stems ?? new Map<PrefixedHexString, StemAccessEvent & StemMeta>()
this.chunks = opts.chunks ?? new Map<PrefixedHexString, ChunkAccessEvent>()
}
Expand Down Expand Up @@ -164,7 +170,7 @@ export class AccessWitness {
return gas
}

touchCodeChunksRangeOnReadAndChargeGas(contact: Address, startPc: number, endPc: number) {
touchCodeChunksRangeOnReadAndChargeGas(contact: Address, startPc: number, endPc: number): bigint {
let gas = BIGINT_0
for (let chunkNum = Math.floor(startPc / 31); chunkNum <= Math.floor(endPc / 31); chunkNum++) {
const { treeIndex, subIndex } = getTreeIndicesForCodeChunk(chunkNum)
Expand All @@ -173,7 +179,11 @@ export class AccessWitness {
return gas
}

touchCodeChunksRangeOnWriteAndChargeGas(contact: Address, startPc: number, endPc: number) {
touchCodeChunksRangeOnWriteAndChargeGas(
contact: Address,
startPc: number,
endPc: number
): bigint {
let gas = BIGINT_0
for (let chunkNum = Math.floor(startPc / 31); chunkNum <= Math.floor(endPc / 31); chunkNum++) {
const { treeIndex, subIndex } = getTreeIndicesForCodeChunk(chunkNum)
Expand Down Expand Up @@ -251,7 +261,7 @@ export class AccessWitness {
// i.e. no fill cost is charged right now
const chunkFill = false

const accessedStemKey = getStem(address, treeIndex)
const accessedStemKey = getStem(this.verkleCrypto, address, treeIndex)
const accessedStemHex = bytesToHex(accessedStemKey)
let accessedStem = this.stems.get(accessedStemHex)
if (accessedStem === undefined) {
Expand Down Expand Up @@ -291,7 +301,7 @@ export class AccessWitness {

/**Create a shallow copy, could clone some caches in future for optimizations */
shallowCopy(): AccessWitness {
return new AccessWitness()
return new AccessWitness({ verkleCrypto: this.verkleCrypto })
}

merge(accessWitness: AccessWitness): void {
Expand Down
Loading
Loading