Skip to content

Commit

Permalink
StateManager: stateless verkle state manager (#3139)
Browse files Browse the repository at this point in the history
Co-authored-by: harkamal <[email protected]>
  • Loading branch information
gabrocheleau and g11tech authored Dec 6, 2023
1 parent 1bb7bc2 commit aac2bef
Show file tree
Hide file tree
Showing 49 changed files with 3,583 additions and 155 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

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

96 changes: 87 additions & 9 deletions packages/block/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Withdrawal,
bigIntToHex,
bytesToHex,
bytesToUtf8,
equalsBytes,
fetchFromProvider,
getProvider,
Expand All @@ -30,6 +31,7 @@ import type {
HeaderData,
JsonBlock,
JsonRpcBlock,
VerkleExecutionWitness,
} from './types.js'
import type { Common } from '@ethereumjs/common'
import type {
Expand All @@ -50,6 +52,13 @@ export class Block {
public readonly withdrawals?: Withdrawal[]
public readonly common: Common

/**
* EIP-6800: Verkle Proof Data (experimental)
* null implies that the non default executionWitness might exist but not available
* and will not lead to execution of the block via vm with verkle stateless manager
*/
public readonly executionWitness?: VerkleExecutionWitness | null

private cache: {
txTrieRoot?: Uint8Array
} = {}
Expand Down Expand Up @@ -92,6 +101,7 @@ export class Block {
transactions: txsData,
uncleHeaders: uhsData,
withdrawals: withdrawalsData,
executionWitness: executionWitnessData,
} = blockData
const header = BlockHeader.fromHeaderData(headerData, opts)

Expand Down Expand Up @@ -125,8 +135,11 @@ export class Block {
}

const withdrawals = withdrawalsData?.map(Withdrawal.fromWithdrawalData)
// The witness data is planned to come in rlp serialized bytes so leave this
// stub till that time
const executionWitness = executionWitnessData

return new Block(header, transactions, uncleHeaders, withdrawals, opts)
return new Block(header, transactions, uncleHeaders, withdrawals, opts, executionWitness)
}

/**
Expand All @@ -152,18 +165,18 @@ export class Block {
* @param opts
*/
public static fromValuesArray(values: BlockBytes, opts?: BlockOptions) {
if (values.length > 4) {
throw new Error('invalid block. More values than expected were received')
if (values.length > 5) {
throw new Error(`invalid block. More values=${values.length} than expected were received`)
}

// First try to load header so that we can use its common (in case of setHardfork being activated)
// to correctly make checks on the hardforks
const [headerData, txsData, uhsData, withdrawalBytes] = values
const [headerData, txsData, uhsData, withdrawalBytes, executionWitnessBytes] = values
const header = BlockHeader.fromValuesArray(headerData, opts)

if (
header.common.isActivatedEIP(4895) &&
(values[3] === undefined || !Array.isArray(values[3]))
(withdrawalBytes === undefined || !Array.isArray(withdrawalBytes))
) {
throw new Error(
'Invalid serialized block input: EIP-4895 is active, and no withdrawals were provided as array'
Expand Down Expand Up @@ -208,7 +221,18 @@ export class Block {
}))
?.map(Withdrawal.fromWithdrawalData)

return new Block(header, transactions, uncleHeaders, withdrawals, opts)
// executionWitness are not part of the EL fetched blocks via eth_ bodies method
// they are currently only available via the engine api constructed blocks
let executionWitness
if (header.common.isActivatedEIP(6800) && executionWitnessBytes !== undefined) {
executionWitness = JSON.parse(bytesToUtf8(RLP.decode(executionWitnessBytes) as Uint8Array))
} else {
// don't assign default witness if eip 6800 is implemented as it leads to incorrect
// assumptions while executing the block. if not present in input implies its unavailable
executionWitness = null
}

return new Block(header, transactions, uncleHeaders, withdrawals, opts, executionWitness)
}

/**
Expand Down Expand Up @@ -300,6 +324,7 @@ export class Block {
feeRecipient: coinbase,
transactions,
withdrawals: withdrawalsData,
executionWitness,
} = payload

const txs = []
Expand Down Expand Up @@ -331,7 +356,16 @@ export class Block {
}

// we are not setting setHardfork as common is already set to the correct hf
const block = Block.fromBlockData({ header, transactions: txs, withdrawals }, options)
const block = Block.fromBlockData(
{ header, transactions: txs, withdrawals, executionWitness },
options
)
if (
block.common.isActivatedEIP(6800) &&
(executionWitness === undefined || executionWitness === null)
) {
throw Error('Missing executionWitness for EIP-6800 activated executionPayload')
}
// Verify blockHash matches payload
if (!equalsBytes(block.hash(), hexToBytes(payload.blockHash))) {
const validationError = `Invalid blockHash, expected: ${
Expand Down Expand Up @@ -366,13 +400,34 @@ export class Block {
transactions: TypedTransaction[] = [],
uncleHeaders: BlockHeader[] = [],
withdrawals?: Withdrawal[],
opts: BlockOptions = {}
opts: BlockOptions = {},
executionWitness?: VerkleExecutionWitness | null
) {
this.header = header ?? BlockHeader.fromHeaderData({}, opts)
this.common = this.header.common

this.transactions = transactions
this.withdrawals = withdrawals ?? (this.common.isActivatedEIP(4895) ? [] : undefined)
this.executionWitness = executionWitness
// null indicates an intentional absence of value or unavailability
// undefined indicates that the executionWitness should be initialized with the default state
if (this.common.isActivatedEIP(6800) && this.executionWitness === undefined) {
this.executionWitness = {
stateDiff: [],
verkleProof: {
commitmentsByPath: [],
d: '0x',
depthExtensionPresent: '0x',
ipaProof: {
cl: [],
cr: [],
finalEvaluation: '0x',
},
otherStems: [],
},
}
}

this.uncleHeaders = uncleHeaders
if (uncleHeaders.length > 0) {
this.validateUncles()
Expand All @@ -394,14 +449,22 @@ export class Block {
throw new Error('Cannot have a withdrawals field if EIP 4895 is not active')
}

if (
!this.common.isActivatedEIP(6800) &&
executionWitness !== undefined &&
executionWitness !== null
) {
throw new Error(`Cannot have executionWitness field if EIP 6800 is not active `)
}

const freeze = opts?.freeze ?? true
if (freeze) {
Object.freeze(this)
}
}

/**
* Returns a Array of the raw Bytes Arays of this block, in order.
* Returns a Array of the raw Bytes Arrays of this block, in order.
*/
raw(): BlockBytes {
const bytesArray = <BlockBytes>[
Expand All @@ -415,6 +478,10 @@ export class Block {
if (withdrawalsRaw) {
bytesArray.push(withdrawalsRaw)
}
if (this.executionWitness !== undefined && this.executionWitness !== null) {
const executionWitnessBytes = RLP.encode(JSON.stringify(this.executionWitness))
bytesArray.push(executionWitnessBytes as any)
}
return bytesArray
}

Expand Down Expand Up @@ -559,6 +626,17 @@ export class Block {
const msg = this._errorMsg('invalid withdrawals trie')
throw new Error(msg)
}

// Validation for Verkle blocks
// Unnecessary in this implementation since we're providing defaults if those fields are undefined
if (this.common.isActivatedEIP(6800)) {
if (this.executionWitness === undefined) {
throw new Error(`Invalid block: missing executionWitness`)
}
if (this.executionWitness === null) {
throw new Error(`Invalid block: ethereumjs stateless client needs executionWitness`)
}
}
}

/**
Expand Down
9 changes: 8 additions & 1 deletion packages/block/src/from-beacon-payload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { bigIntToHex } from '@ethereumjs/util'

import type { ExecutionPayload } from './types.js'
import type { ExecutionPayload, VerkleExecutionWitness } from './types.js'

type BeaconWithdrawal = {
index: string
Expand Down Expand Up @@ -30,6 +30,8 @@ export type BeaconPayloadJson = {
blob_gas_used?: string
excess_blob_gas?: string
parent_beacon_block_root?: string
// the casing of VerkleExecutionWitness remains same camel case for now
execution_witness?: VerkleExecutionWitness
}

/**
Expand Down Expand Up @@ -72,6 +74,11 @@ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJson): E
if (payload.parent_beacon_block_root !== undefined && payload.parent_beacon_block_root !== null) {
executionPayload.parentBeaconBlockRoot = payload.parent_beacon_block_root
}
if (payload.execution_witness !== undefined && payload.execution_witness !== null) {
// the casing structure in payload is already camel case, might be updated in
// kaustinen relaunch
executionPayload.executionWitness = payload.execution_witness
}

return executionPayload
}
10 changes: 9 additions & 1 deletion packages/block/src/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { keccak256 } from 'ethereum-cryptography/keccak.js'
import { CLIQUE_EXTRA_SEAL, CLIQUE_EXTRA_VANITY } from './clique.js'
import { fakeExponential, valuesArrayToHeaderData } from './helpers.js'

import type { BlockHeaderBytes, BlockOptions, HeaderData, JsonHeader } from './types.js'
import type { BlockHeaderBytes, BlockOptions, HeaderData, JsonHeader } from './types'
import type { CliqueConfig } from '@ethereumjs/common'
import type { BigIntLike } from '@ethereumjs/util'

Expand Down Expand Up @@ -647,6 +647,14 @@ export class BlockHeader {
if (this.common.isActivatedEIP(4895) === true) {
rawItems.push(this.withdrawalsRoot!)
}

// in kaunstinen 2 verkle is scheduled after withdrawals, will eventually be post deneb hopefully
if (this.common.isActivatedEIP(6800) === true) {
// execution witness is not mandatory part of the the block so nothing to push here
// but keep this comment segment for clarity regarding the same and move it according as per the
// HF sequence eventually planned
}

if (this.common.isActivatedEIP(4844) === true) {
rawItems.push(bigIntToUnpaddedBytes(this.blobGasUsed!))
rawItems.push(bigIntToUnpaddedBytes(this.excessBlobGas!))
Expand Down
59 changes: 59 additions & 0 deletions packages/block/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,45 @@ export interface BlockOptions {
skipConsensusFormatValidation?: boolean
}

export interface VerkleProof {
commitmentsByPath: PrefixedHexString[]
d: PrefixedHexString
depthExtensionPresent: PrefixedHexString
ipaProof: {
cl: PrefixedHexString[]
cr: PrefixedHexString[]
finalEvaluation: PrefixedHexString
}
otherStems: PrefixedHexString[]
}

export interface VerkleStateDiff {
stem: PrefixedHexString
suffixDiffs: {
currentValue: PrefixedHexString | null
newValue: PrefixedHexString | null
suffix: number
}[]
}

/**
* Experimental, object format could eventual change.
* An object that provides the state and proof necessary for verkle stateless execution
* */
export interface VerkleExecutionWitness {
/**
* An array of state diffs.
* Each item corresponding to state accesses or state modifications of the block.
* In the current design, it also contains the resulting state of the block execution (post-state).
*/
stateDiff: VerkleStateDiff[]
/**
* The verkle proof for the block.
* Proves that the provided stateDiff belongs to the canonical verkle tree.
*/
verkleProof: VerkleProof
}

/**
* A block header's data.
*/
Expand Down Expand Up @@ -108,13 +147,29 @@ export interface BlockData {
transactions?: Array<TxData[TransactionType]>
uncleHeaders?: Array<HeaderData>
withdrawals?: Array<WithdrawalData>
/**
* EIP-6800: Verkle Proof Data (experimental)
*/
executionWitness?: VerkleExecutionWitness | null
}

export type WithdrawalsBytes = WithdrawalBytes[]
export type ExecutionWitnessBytes = Uint8Array

export type BlockBytes =
| [BlockHeaderBytes, TransactionsBytes, UncleHeadersBytes]
| [BlockHeaderBytes, TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes]
| [
BlockHeaderBytes,
TransactionsBytes,
UncleHeadersBytes,
WithdrawalsBytes,
ExecutionWitnessBytes
]

/**
* BlockHeaderBuffer is a Buffer array, except for the Verkle PreState which is an array of prestate arrays.
*/
export type BlockHeaderBytes = Uint8Array[]
export type BlockBodyBytes = [TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes?]
/**
Expand All @@ -134,6 +189,7 @@ export interface JsonBlock {
transactions?: JsonTx[]
uncleHeaders?: JsonHeader[]
withdrawals?: JsonRpcWithdrawal[]
executionWitness?: VerkleExecutionWitness | null
}

/**
Expand Down Expand Up @@ -192,6 +248,7 @@ export interface JsonRpcBlock {
blobGasUsed?: string // If EIP-4844 is enabled for this block, returns the blob gas used for the block
excessBlobGas?: string // If EIP-4844 is enabled for this block, returns the excess blob gas for the block
parentBeaconBlockRoot?: string // If EIP-4788 is enabled for this block, returns parent beacon block root
executionWitness?: VerkleExecutionWitness | null // If Verkle is enabled for this block
}

// Note: all these strings are 0x-prefixed
Expand Down Expand Up @@ -222,4 +279,6 @@ export type ExecutionPayload = {
blobGasUsed?: PrefixedHexString // QUANTITY, 64 Bits
excessBlobGas?: PrefixedHexString // QUANTITY, 64 Bits
parentBeaconBlockRoot?: PrefixedHexString // QUANTITY, 64 Bits
// VerkleExecutionWitness is already a hex serialized object
executionWitness?: VerkleExecutionWitness | null // QUANTITY, 64 Bits, null imples not available
}
Loading

0 comments on commit aac2bef

Please sign in to comment.