Skip to content

Commit

Permalink
evm: verkle updates (#3832)
Browse files Browse the repository at this point in the history
* evm: verkle updates

* vm: uncomment runBlock

* vm: fix historical blockhash handling

* verkle stuffs

---------

Co-authored-by: acolytec3 <[email protected]>
  • Loading branch information
gabrocheleau and acolytec3 authored Jan 13, 2025
1 parent e15b73b commit 15994b9
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 51 deletions.
2 changes: 1 addition & 1 deletion packages/common/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ export class Common {
for (const hfChanges of this.HARDFORK_CHANGES) {
// EIP-referencing HF config (e.g. for berlin)
if ('eips' in hfChanges[1]) {
const hfEIPs = hfChanges[1]['eips'] ?? []
const hfEIPs = hfChanges[1].eips ?? []
for (const eip of hfEIPs) {
this._mergeWithParamsCache(this._params[eip] ?? {})
}
Expand Down
8 changes: 5 additions & 3 deletions packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export class EVM implements EVMInterface {
public blockchain: EVMMockBlockchainInterface
public journal: Journal
public verkleAccessWitness?: VerkleAccessWitness
public systemVerkleAccessWitness?: VerkleAccessWitness

public readonly transientStorage: TransientStorage

Expand Down Expand Up @@ -466,7 +467,9 @@ export class EVM implements EVMInterface {
}

if (this.common.isActivatedEIP(6800)) {
const contractCreateAccessGas = message.accessWitness!.writeAccountBasicData(message.to)
const contractCreateAccessGas =
message.accessWitness!.writeAccountBasicData(message.to) +
message.accessWitness!.readAccountCodeHash(message.to)
gasLimit -= contractCreateAccessGas
if (gasLimit < BIGINT_0) {
if (this.DEBUG) {
Expand Down Expand Up @@ -1100,11 +1103,10 @@ export class EVM implements EVMInterface {
}
toAccount.balance = newBalance
// putAccount as the nonce may have changed for contract creation
const result = this.journal.putAccount(message.to, toAccount)
await this.journal.putAccount(message.to, toAccount)
if (this.DEBUG) {
debug(`Added toAccount (${message.to}) balance (-> ${toAccount.balance})`)
}
return result
}

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/evm/src/opcodes/EIP2929.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export function accessAddressEIP2929(
// if verkle not activated
if (chargeGas && !common.isActivatedEIP(6800)) {
return common.param('coldaccountaccessGas')
} else if (chargeGas && common.isActivatedEIP(6800)) {
// If Verkle is active, then the warmstoragereadGas should still be charged
// This is because otherwise opcodes will have cost 0 (this is thus the base fee)
return common.param('warmstoragereadGas')
}
// Warm: (selfdestruct beneficiary address reads are not charged when warm)
} else if (chargeGas && !isSelfdestruct) {
Expand Down
22 changes: 14 additions & 8 deletions packages/evm/src/opcodes/gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
let charge2929Gas = true
if (
common.isActivatedEIP(6800) &&
runState.interpreter._evm.getPrecompile(address) === undefined
runState.interpreter._evm.getPrecompile(address) === undefined &&
!address.equals(createAddressFromStackBigInt(common.param('systemAddress')))
) {
let coldAccessGas = BIGINT_0
coldAccessGas += runState.env.accessWitness!.readAccountBasicData(address)
Expand Down Expand Up @@ -199,7 +200,8 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
let charge2929Gas = true
if (
common.isActivatedEIP(6800) &&
runState.interpreter._evm.getPrecompile(address) === undefined
runState.interpreter._evm.getPrecompile(address) === undefined &&
!address.equals(createAddressFromStackBigInt(common.param('systemAddress')))
) {
let coldAccessGas = BIGINT_0
coldAccessGas += runState.env.accessWitness!.readAccountBasicData(address)
Expand Down Expand Up @@ -266,7 +268,11 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
const address = createAddressFromStackBigInt(runState.stack.peek()[0])
let charge2929Gas = true

if (common.isActivatedEIP(6800)) {
if (
common.isActivatedEIP(6800) &&
runState.interpreter._evm.getPrecompile(address) === undefined &&
!address.equals(createAddressFromStackBigInt(common.param('systemAddress')))
) {
let coldAccessGas = BIGINT_0
coldAccessGas += runState.env.accessWitness!.readAccountCodeHash(address)

Expand Down Expand Up @@ -562,7 +568,6 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
common.isActivatedEIP(6800) &&
runState.interpreter._evm.getPrecompile(toAddress) === undefined
) {
// TODO: add check if toAddress is not a precompile
const coldAccessGas = runState.env.accessWitness!.readAccountBasicData(toAddress)
if (value !== BIGINT_0) {
const contractAddress = runState.interpreter.getAddress()
Expand Down Expand Up @@ -705,7 +710,6 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
common.isActivatedEIP(6800) &&
runState.interpreter._evm.getPrecompile(toAddress) === undefined
) {
// TODO: add check if toAddress is not a precompile
const coldAccessGas = runState.env.accessWitness!.readAccountBasicData(toAddress)

gas += coldAccessGas
Expand Down Expand Up @@ -914,9 +918,11 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
gas += subMemUsage(runState, outOffset, outLength, common)

let charge2929Gas = true
if (common.isActivatedEIP(6800)) {
const toAddress = createAddressFromStackBigInt(toAddr)
// TODO: add check if toAddress is not a precompile
const toAddress = createAddressFromStackBigInt(toAddr)
if (
common.isActivatedEIP(6800) &&
runState.interpreter._evm.getPrecompile(toAddress) === undefined
) {
const coldAccessGas = runState.env.accessWitness!.readAccountBasicData(toAddress)

gas += coldAccessGas
Expand Down
28 changes: 18 additions & 10 deletions packages/statemanager/src/statefulVerkleStateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
chunkStems[0],
chunkSuffixes.slice(
0,
codeChunks.length <= VERKLE_CODE_OFFSET ? codeChunks.length : VERKLE_CODE_OFFSET,
chunkSuffixes.length <= VERKLE_CODE_OFFSET ? chunkSuffixes.length : VERKLE_CODE_OFFSET,
),
codeChunks.slice(
0,
Expand Down Expand Up @@ -398,13 +398,16 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
for (let x = 0; x < chunks.length; x++) {
if (chunks[x] === undefined) throw new Error(`expected code chunk at ID ${x}, got undefined`)

let lastChunkByteIndex = VERKLE_CODE_CHUNK_SIZE
// Determine code ending byte (if we're on the last chunk)
let sliceEnd = 32
if (x === chunks.length - 1) {
// On the last chunk, the end of the slice is either codeSize (if only one chunk) or codeSize % chunkSize
sliceEnd = (x === 0 ? codeSize : codeSize % VERKLE_CODE_CHUNK_SIZE) + 1
// On the last chunk, the slice either ends on a partial chunk (if codeSize doesn't exactly fit in full chunks), or a full chunk
lastChunkByteIndex = codeSize % VERKLE_CODE_CHUNK_SIZE || VERKLE_CODE_CHUNK_SIZE
}
code.set(chunks[x]!.slice(1, sliceEnd), code.byteOffset + x * VERKLE_CODE_CHUNK_SIZE)
code.set(
chunks[x]!.slice(1, lastChunkByteIndex + 1),
code.byteOffset + x * VERKLE_CODE_CHUNK_SIZE,
)
}
this._caches?.code?.put(address, code)

Expand Down Expand Up @@ -598,10 +601,14 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
// we can only compare the actual code because to compare the first byte would
// be very tricky and impossible in certain scenarios like when the previous code chunk
// was not accessed and hence not even provided in the witness
// We are left-padding with two zeroes to get a 32-byte length, but these bytes should not be considered reliable
return bytesToHex(
setLengthRight(
code.slice(codeOffset, codeOffset + VERKLE_CODE_CHUNK_SIZE),
VERKLE_CODE_CHUNK_SIZE,
setLengthLeft(
setLengthRight(
code.slice(codeOffset, codeOffset + VERKLE_CODE_CHUNK_SIZE),
VERKLE_CODE_CHUNK_SIZE,
),
VERKLE_CODE_CHUNK_SIZE + 1,
),
)
}
Expand Down Expand Up @@ -645,7 +652,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface {

const { chunkKey } = accessedState
accessedChunks.set(chunkKey, true)
const computedValue: PrefixedHexString | null | undefined =
let computedValue: PrefixedHexString | null | undefined =
await this.getComputedValue(accessedState)
if (computedValue === undefined) {
this.DEBUG &&
Expand All @@ -670,7 +677,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
// if the access type is code, then we can't match the first byte because since the computed value
// doesn't has the first byte for push data since previous chunk code itself might not be available
if (accessedState.type === VerkleAccessedStateType.Code) {
// computedValue = computedValue !== null ? `0x${computedValue.slice(4)}` : null
computedValue = computedValue !== null ? `0x${computedValue.slice(4)}` : null
canonicalValue = canonicalValue !== null ? `0x${canonicalValue.slice(4)}` : null
} else if (
accessedState.type === VerkleAccessedStateType.Storage &&
Expand All @@ -680,6 +687,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
canonicalValue = ZEROVALUE
}

this._debug(`computed ${computedValue} canonical ${canonicalValue}`)
if (computedValue !== canonicalValue) {
if (type === VerkleAccessedStateType.BasicData) {
this.DEBUG &&
Expand Down
16 changes: 9 additions & 7 deletions packages/vm/src/runBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise<RunBlockResu
throw new Error(msg)
}
}

if (vm.common.isActivatedEIP(6800)) {
if (vm.evm.verkleAccessWitness === undefined) {
throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`)
Expand Down Expand Up @@ -445,6 +446,7 @@ async function applyBlock(vm: VM, block: Block, opts: RunBlockOpts): Promise<App
if (vm.DEBUG) {
debug(`Apply transactions`)
}

const blockResults = await applyTransactions(vm, block, opts)

if (enableProfiler) {
Expand Down Expand Up @@ -750,24 +752,24 @@ export async function rewardAccount(
): Promise<Account> {
let account = await evm.stateManager.getAccount(address)
if (account === undefined) {
if (common.isActivatedEIP(6800) === true) {
if (evm.verkleAccessWitness === undefined) {
if (common.isActivatedEIP(6800) === true && reward !== BIGINT_0) {
if (evm.systemVerkleAccessWitness === undefined) {
throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`)
}
evm.verkleAccessWitness.readAccountHeader(address)
evm.systemVerkleAccessWitness.writeAccountHeader(address)
}
account = new Account()
}
account.balance += reward
await evm.journal.putAccount(address, account)

if (common.isActivatedEIP(6800) === true) {
if (evm.verkleAccessWitness === undefined) {
if (common.isActivatedEIP(6800) === true && reward !== BIGINT_0) {
if (evm.systemVerkleAccessWitness === undefined) {
throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`)
}
// use vm utility to build access but the computed gas is not charged and hence free
evm.verkleAccessWitness.writeAccountBasicData(address)
evm.verkleAccessWitness.readAccountCodeHash(address)
evm.systemVerkleAccessWitness.writeAccountBasicData(address)
evm.systemVerkleAccessWitness.readAccountCodeHash(address)
}
return account
}
Expand Down
42 changes: 20 additions & 22 deletions packages/vm/src/runTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export async function runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {
}
emitEVMProfile(logs.precompiles, 'Precompile performance')
emitEVMProfile(logs.opcodes, 'Opcodes performance')
;(<EVM>vm.evm).clearPerformanceLogs()
; (<EVM>vm.evm).clearPerformanceLogs()
}
}
}
Expand Down Expand Up @@ -224,8 +224,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {
const caller = tx.getSenderAddress()
if (vm.DEBUG) {
debug(
`New tx run hash=${
opts.tx.isSigned() ? bytesToHex(opts.tx.hash()) : 'unsigned'
`New tx run hash=${opts.tx.isSigned() ? bytesToHex(opts.tx.hash()) : 'unsigned'
} sender=${caller}`,
)
}
Expand Down Expand Up @@ -274,8 +273,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {
const baseFeePerGas = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas!
if (maxFeePerGas < baseFeePerGas) {
const msg = _errorMsg(
`Transaction's ${
'maxFeePerGas' in tx ? 'maxFeePerGas' : 'gasPrice'
`Transaction's ${'maxFeePerGas' in tx ? 'maxFeePerGas' : 'gasPrice'
} (${maxFeePerGas}) is less than the block's baseFeePerGas (${baseFeePerGas})`,
vm,
block,
Expand Down Expand Up @@ -552,10 +550,8 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {

if (vm.DEBUG) {
debug(
`Running tx=${
tx.isSigned() ? bytesToHex(tx.hash()) : 'unsigned'
} with caller=${caller} gasLimit=${gasLimit} to=${
to?.toString() ?? 'none'
`Running tx=${tx.isSigned() ? bytesToHex(tx.hash()) : 'unsigned'
} with caller=${caller} gasLimit=${gasLimit} to=${to?.toString() ?? 'none'
} value=${value} data=${short(data)}`,
)
}
Expand Down Expand Up @@ -593,8 +589,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {
const { executionGasUsed, exceptionError, returnValue } = results.execResult
debug('-'.repeat(100))
debug(
`Received tx execResult: [ executionGasUsed=${executionGasUsed} exceptionError=${
exceptionError !== undefined ? `'${exceptionError.error}'` : 'none'
`Received tx execResult: [ executionGasUsed=${executionGasUsed} exceptionError=${exceptionError !== undefined ? `'${exceptionError.error}'` : 'none'
} returnValue=${short(returnValue)} gasRefund=${results.gasRefund ?? 0} ]`,
)
}
Expand Down Expand Up @@ -661,28 +656,34 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {
}

let minerAccount = await state.getAccount(miner)
const minerAccountExists = minerAccount !== undefined
if (minerAccount === undefined) {
if (vm.common.isActivatedEIP(6800)) {
if (vm.evm.verkleAccessWitness === undefined) {
throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`)
}
vm.evm.verkleAccessWitness.readAccountHeader(miner)
}
minerAccount = new Account()
// Add the miner account to the system verkle access witness
vm.evm.systemVerkleAccessWitness?.writeAccountHeader(miner)
}
// add the amount spent on gas to the miner's account
results.minerValue = vm.common.isActivatedEIP(1559)
? results.totalGasSpent * inclusionFeePerGas!
: results.amountSpent
minerAccount.balance += results.minerValue

if (vm.common.isActivatedEIP(6800)) {
if (vm.common.isActivatedEIP(6800) && results.minerValue !== BIGINT_0) {
if (vm.evm.verkleAccessWitness === undefined) {
throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`)
}
// use vm utility to build access but the computed gas is not charged and hence free
vm.evm.verkleAccessWitness.writeAccountBasicData(miner)
vm.evm.verkleAccessWitness.readAccountCodeHash(miner)
if (minerAccountExists) {
// use vm utility to build access but the computed gas is not charged and hence free
vm.evm.verkleAccessWitness.writeAccountBasicData(miner)
vm.evm.verkleAccessWitness.readAccountCodeHash(miner)
} else {
vm.evm.verkleAccessWitness.writeAccountHeader(miner)
}
}

// Put the miner account into the state. If the balance of the miner account remains zero, note that
Expand Down Expand Up @@ -795,8 +796,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {
await vm._emit('afterTx', event)
if (vm.DEBUG) {
debug(
`tx run finished hash=${
opts.tx.isSigned() ? bytesToHex(opts.tx.hash()) : 'unsigned'
`tx run finished hash=${opts.tx.isSigned() ? bytesToHex(opts.tx.hash()) : 'unsigned'
} sender=${caller}`,
)
}
Expand Down Expand Up @@ -856,10 +856,8 @@ export async function generateTxReceipt(
let receipt
if (vm.DEBUG) {
debug(
`Generate tx receipt transactionType=${
tx.type
} cumulativeBlockGasUsed=${cumulativeGasUsed} bitvector=${short(baseReceipt.bitvector)} (${
baseReceipt.bitvector.length
`Generate tx receipt transactionType=${tx.type
} cumulativeBlockGasUsed=${cumulativeGasUsed} bitvector=${short(baseReceipt.bitvector)} (${baseReceipt.bitvector.length
} bytes) logs=${baseReceipt.logs.length}`,
)
}
Expand Down

0 comments on commit 15994b9

Please sign in to comment.