@ethereumjs/block
Advanced tools
Comparing version 5.0.1 to 5.1.0
@@ -5,3 +5,3 @@ import { Trie } from '@ethereumjs/trie'; | ||
import type { BeaconPayloadJson } from './from-beacon-payload.js'; | ||
import type { BlockBytes, BlockData, BlockOptions, ExecutionPayload, JsonBlock, JsonRpcBlock } from './types.js'; | ||
import type { BlockBytes, BlockData, BlockOptions, ExecutionPayload, JsonBlock, JsonRpcBlock, VerkleExecutionWitness } from './types.js'; | ||
import type { Common } from '@ethereumjs/common'; | ||
@@ -19,4 +19,13 @@ import type { TypedTransaction } from '@ethereumjs/tx'; | ||
readonly common: Common; | ||
private cache; | ||
protected keccakFunction: (msg: Uint8Array) => Uint8Array; | ||
/** | ||
* 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 | ||
*/ | ||
readonly executionWitness?: VerkleExecutionWitness | null; | ||
protected cache: { | ||
txTrieRoot?: Uint8Array; | ||
}; | ||
/** | ||
* Returns the withdrawals trie root for array of Withdrawal. | ||
@@ -59,3 +68,3 @@ * @param wts array of Withdrawal to compute the root of | ||
* @param uncles - Optional list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex) | ||
* @param options - An object describing the blockchain | ||
* @param opts - An object describing the blockchain | ||
*/ | ||
@@ -77,3 +86,3 @@ static fromRPC(blockData: JsonRpcBlock, uncles?: any[], opts?: BlockOptions): Block; | ||
*/ | ||
static fromExecutionPayload(payload: ExecutionPayload, options?: BlockOptions): Promise<Block>; | ||
static fromExecutionPayload(payload: ExecutionPayload, opts?: BlockOptions): Promise<Block>; | ||
/** | ||
@@ -85,3 +94,3 @@ * Method to retrieve a block from a beacon payload json | ||
*/ | ||
static fromBeaconPayloadJson(payload: BeaconPayloadJson, options?: BlockOptions): Promise<Block>; | ||
static fromBeaconPayloadJson(payload: BeaconPayloadJson, opts?: BlockOptions): Promise<Block>; | ||
/** | ||
@@ -91,5 +100,5 @@ * This constructor takes the values, validates them, assigns them and freezes the object. | ||
*/ | ||
constructor(header?: BlockHeader, transactions?: TypedTransaction[], uncleHeaders?: BlockHeader[], withdrawals?: Withdrawal[], opts?: BlockOptions); | ||
constructor(header?: BlockHeader, transactions?: TypedTransaction[], uncleHeaders?: BlockHeader[], withdrawals?: Withdrawal[], opts?: BlockOptions, executionWitness?: VerkleExecutionWitness | null); | ||
/** | ||
* 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. | ||
*/ | ||
@@ -137,4 +146,5 @@ raw(): BlockBytes; | ||
* @param onlyHeader if only passed the header, skip validating txTrie and unclesHash (default: false) | ||
* @param verifyTxs if set to `false`, will not check for transaction validation errors (default: true) | ||
*/ | ||
validateData(onlyHeader?: boolean): Promise<void>; | ||
validateData(onlyHeader?: boolean, verifyTxs?: boolean): Promise<void>; | ||
/** | ||
@@ -141,0 +151,0 @@ * Validates that blob gas fee for each transaction is greater than or equal to the |
@@ -22,3 +22,3 @@ "use strict"; | ||
*/ | ||
constructor(header, transactions = [], uncleHeaders = [], withdrawals, opts = {}) { | ||
constructor(header, transactions = [], uncleHeaders = [], withdrawals, opts = {}, executionWitness) { | ||
this.transactions = []; | ||
@@ -29,4 +29,24 @@ this.uncleHeaders = []; | ||
this.common = this.header.common; | ||
this.keccakFunction = this.common.customCrypto.keccak256 ?? keccak_js_1.keccak256; | ||
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; | ||
@@ -47,2 +67,7 @@ if (uncleHeaders.length > 0) { | ||
} | ||
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; | ||
@@ -84,3 +109,3 @@ if (freeze) { | ||
static fromBlockData(blockData = {}, opts) { | ||
const { header: headerData, transactions: txsData, uncleHeaders: uhsData, withdrawals: withdrawalsData, } = blockData; | ||
const { header: headerData, transactions: txsData, uncleHeaders: uhsData, withdrawals: withdrawalsData, executionWitness: executionWitnessData, } = blockData; | ||
const header = header_js_1.BlockHeader.fromHeaderData(headerData, opts); | ||
@@ -115,3 +140,6 @@ // parse transactions | ||
const withdrawals = withdrawalsData?.map(util_1.Withdrawal.fromWithdrawalData); | ||
return new Block(header, transactions, uncleHeaders, withdrawals, opts); | ||
// 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, executionWitness); | ||
} | ||
@@ -138,11 +166,11 @@ /** | ||
static fromValuesArray(values, opts) { | ||
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 (at most 5)`); | ||
} | ||
// 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 = header_js_1.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'); | ||
@@ -183,3 +211,14 @@ } | ||
?.map(util_1.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((0, util_1.bytesToUtf8)(rlp_1.RLP.decode(executionWitnessBytes))); | ||
} | ||
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); | ||
} | ||
@@ -191,3 +230,3 @@ /** | ||
* @param uncles - Optional list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex) | ||
* @param options - An object describing the blockchain | ||
* @param opts - An object describing the blockchain | ||
*/ | ||
@@ -203,4 +242,4 @@ static fromRPC(blockData, uncles, opts) { | ||
*/ | ||
static async fromExecutionPayload(payload, options) { | ||
const { blockNumber: number, receiptsRoot: receiptTrie, prevRandao: mixHash, feeRecipient: coinbase, transactions, withdrawals: withdrawalsData, } = payload; | ||
static async fromExecutionPayload(payload, opts) { | ||
const { blockNumber: number, receiptsRoot: receiptTrie, prevRandao: mixHash, feeRecipient: coinbase, transactions, withdrawals: withdrawalsData, executionWitness, } = payload; | ||
const txs = []; | ||
@@ -210,3 +249,3 @@ for (const [index, serializedTx] of transactions.entries()) { | ||
const tx = tx_1.TransactionFactory.fromSerializedData((0, util_1.hexToBytes)(serializedTx), { | ||
common: options?.common, | ||
common: opts?.common, | ||
}); | ||
@@ -220,6 +259,6 @@ txs.push(tx); | ||
} | ||
const transactionsTrie = await Block.genTransactionsTrieRoot(txs); | ||
const transactionsTrie = await Block.genTransactionsTrieRoot(txs, new trie_1.Trie({ common: opts?.common })); | ||
const withdrawals = withdrawalsData?.map((wData) => util_1.Withdrawal.fromWithdrawalData(wData)); | ||
const withdrawalsRoot = withdrawals | ||
? await Block.genWithdrawalsTrieRoot(withdrawals) | ||
? await Block.genWithdrawalsTrieRoot(withdrawals, new trie_1.Trie({ common: opts?.common })) | ||
: undefined; | ||
@@ -236,3 +275,7 @@ const header = { | ||
// 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 }, opts); | ||
if (block.common.isActivatedEIP(6800) && | ||
(executionWitness === undefined || executionWitness === null)) { | ||
throw Error('Missing executionWitness for EIP-6800 activated executionPayload'); | ||
} | ||
// Verify blockHash matches payload | ||
@@ -251,8 +294,8 @@ if (!(0, util_1.equalsBytes)(block.hash(), (0, util_1.hexToBytes)(payload.blockHash))) { | ||
*/ | ||
static async fromBeaconPayloadJson(payload, options) { | ||
static async fromBeaconPayloadJson(payload, opts) { | ||
const executionPayload = (0, from_beacon_payload_js_1.executionPayloadFromBeaconPayload)(payload); | ||
return Block.fromExecutionPayload(executionPayload, options); | ||
return Block.fromExecutionPayload(executionPayload, opts); | ||
} | ||
/** | ||
* 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. | ||
*/ | ||
@@ -269,2 +312,6 @@ raw() { | ||
} | ||
if (this.executionWitness !== undefined && this.executionWitness !== null) { | ||
const executionWitnessBytes = rlp_1.RLP.encode(JSON.stringify(this.executionWitness)); | ||
bytesArray.push(executionWitnessBytes); | ||
} | ||
return bytesArray; | ||
@@ -294,3 +341,3 @@ } | ||
async genTxTrie() { | ||
return Block.genTransactionsTrieRoot(this.transactions, new trie_1.Trie()); | ||
return Block.genTransactionsTrieRoot(this.transactions, new trie_1.Trie({ common: this.common })); | ||
} | ||
@@ -375,8 +422,11 @@ /** | ||
* @param onlyHeader if only passed the header, skip validating txTrie and unclesHash (default: false) | ||
* @param verifyTxs if set to `false`, will not check for transaction validation errors (default: true) | ||
*/ | ||
async validateData(onlyHeader = false) { | ||
const txErrors = this.getTransactionsValidationErrors(); | ||
if (txErrors.length > 0) { | ||
const msg = this._errorMsg(`invalid transactions: ${txErrors.join(' ')}`); | ||
throw new Error(msg); | ||
async validateData(onlyHeader = false, verifyTxs = true) { | ||
if (verifyTxs) { | ||
const txErrors = this.getTransactionsValidationErrors(); | ||
if (txErrors.length > 0) { | ||
const msg = this._errorMsg(`invalid transactions: ${txErrors.join(' ')}`); | ||
throw new Error(msg); | ||
} | ||
} | ||
@@ -386,2 +436,10 @@ if (onlyHeader) { | ||
} | ||
if (verifyTxs) { | ||
for (const [index, tx] of this.transactions.entries()) { | ||
if (!tx.isSigned()) { | ||
const msg = this._errorMsg(`invalid transactions: transaction at index ${index} is unsigned`); | ||
throw new Error(msg); | ||
} | ||
} | ||
} | ||
if (!(await this.transactionsTrieIsValid())) { | ||
@@ -399,2 +457,12 @@ const msg = this._errorMsg('invalid transaction trie'); | ||
} | ||
// 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`); | ||
} | ||
} | ||
} | ||
@@ -441,3 +509,3 @@ /** | ||
const raw = rlp_1.RLP.encode(uncles); | ||
return (0, util_1.equalsBytes)((0, keccak_js_1.keccak256)(raw), this.header.uncleHash); | ||
return (0, util_1.equalsBytes)(this.keccakFunction(raw), this.header.uncleHash); | ||
} | ||
@@ -452,3 +520,3 @@ /** | ||
} | ||
const withdrawalsRoot = await Block.genWithdrawalsTrieRoot(this.withdrawals); | ||
const withdrawalsRoot = await Block.genWithdrawalsTrieRoot(this.withdrawals, new trie_1.Trie({ common: this.common })); | ||
return (0, util_1.equalsBytes)(withdrawalsRoot, this.header.withdrawalsRoot); | ||
@@ -455,0 +523,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import type { ExecutionPayload } from './types.js'; | ||
import type { ExecutionPayload, VerkleExecutionWitness } from './types.js'; | ||
declare type BeaconWithdrawal = { | ||
@@ -27,2 +27,3 @@ index: string; | ||
parent_beacon_block_root?: string; | ||
execution_witness?: VerkleExecutionWitness; | ||
}; | ||
@@ -29,0 +30,0 @@ /** |
@@ -43,2 +43,7 @@ "use strict"; | ||
} | ||
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; | ||
@@ -45,0 +50,0 @@ } |
import { Common } from '@ethereumjs/common'; | ||
import { Address } from '@ethereumjs/util'; | ||
import type { BlockHeaderBytes, BlockOptions, HeaderData, JsonHeader } from './types.js'; | ||
interface HeaderCache { | ||
hash: Uint8Array | undefined; | ||
} | ||
/** | ||
@@ -29,3 +32,4 @@ * An object that represents the block header. | ||
readonly common: Common; | ||
private cache; | ||
protected keccakFunction: (msg: Uint8Array) => Uint8Array; | ||
protected cache: HeaderCache; | ||
/** | ||
@@ -63,3 +67,3 @@ * EIP-4399: After merge to PoS, `mixHash` supplanted as `prevRandao` | ||
*/ | ||
constructor(headerData: HeaderData, options?: BlockOptions); | ||
constructor(headerData: HeaderData, opts?: BlockOptions); | ||
/** | ||
@@ -190,2 +194,3 @@ * Validates correct buffer lengths, throws if invalid. | ||
} | ||
export {}; | ||
//# sourceMappingURL=header.d.ts.map |
@@ -22,8 +22,8 @@ "use strict"; | ||
*/ | ||
constructor(headerData, options = {}) { | ||
constructor(headerData, opts = {}) { | ||
this.cache = { | ||
hash: undefined, | ||
}; | ||
if (options.common) { | ||
this.common = options.common.copy(); | ||
if (opts.common) { | ||
this.common = opts.common.copy(); | ||
} | ||
@@ -35,3 +35,4 @@ else { | ||
} | ||
const skipValidateConsensusFormat = options.skipConsensusFormatValidation ?? false; | ||
this.keccakFunction = this.common.customCrypto.keccak256 ?? keccak_js_1.keccak256; | ||
const skipValidateConsensusFormat = opts.skipConsensusFormatValidation ?? false; | ||
const defaults = { | ||
@@ -69,3 +70,3 @@ parentHash: (0, util_1.zeros)(32), | ||
const nonce = (0, util_1.toType)(headerData.nonce, util_1.TypeOutput.Uint8Array) ?? defaults.nonce; | ||
const setHardfork = options.setHardfork ?? false; | ||
const setHardfork = opts.setHardfork ?? false; | ||
if (setHardfork === true) { | ||
@@ -144,8 +145,8 @@ this.common.setHardforkBy({ | ||
// block option parameter, we instead set difficulty to this value. | ||
if (options.calcDifficultyFromHeader && | ||
if (opts.calcDifficultyFromHeader && | ||
this.common.consensusAlgorithm() === common_1.ConsensusAlgorithm.Ethash) { | ||
this.difficulty = this.ethashCanonicalDifficulty(options.calcDifficultyFromHeader); | ||
this.difficulty = this.ethashCanonicalDifficulty(opts.calcDifficultyFromHeader); | ||
} | ||
// If cliqueSigner is provided, seal block with provided privateKey. | ||
if (options.cliqueSigner) { | ||
if (opts.cliqueSigner) { | ||
// Ensure extraData is at least length CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL | ||
@@ -157,3 +158,3 @@ const minExtraDataLength = clique_js_1.CLIQUE_EXTRA_VANITY + clique_js_1.CLIQUE_EXTRA_SEAL; | ||
} | ||
this.extraData = this.cliqueSealBlock(options.cliqueSigner); | ||
this.extraData = this.cliqueSealBlock(opts.cliqueSigner); | ||
} | ||
@@ -163,3 +164,3 @@ // Validate consensus format after block is sealed (if applicable) so extraData checks will pass | ||
this._consensusFormatValidation(); | ||
const freeze = options?.freeze ?? true; | ||
const freeze = opts?.freeze ?? true; | ||
if (freeze) { | ||
@@ -264,3 +265,3 @@ Object.freeze(this); | ||
if (this.gasUsed > this.gasLimit) { | ||
const msg = this._errorMsg('Invalid block: too much gas used'); | ||
const msg = this._errorMsg(`Invalid block: too much gas used. Used: ${this.gasUsed}, gas limit: ${this.gasLimit}`); | ||
throw new Error(msg); | ||
@@ -401,11 +402,11 @@ } | ||
if (gasLimit >= maxGasLimit) { | ||
const msg = this._errorMsg('gas limit increased too much'); | ||
const msg = this._errorMsg(`gas limit increased too much. Gas limit: ${gasLimit}, max gas limit: ${maxGasLimit}`); | ||
throw new Error(msg); | ||
} | ||
if (gasLimit <= minGasLimit) { | ||
const msg = this._errorMsg('gas limit decreased too much'); | ||
const msg = this._errorMsg(`gas limit decreased too much. Gas limit: ${gasLimit}, min gas limit: ${minGasLimit}`); | ||
throw new Error(msg); | ||
} | ||
if (gasLimit < this.common.param('gasConfig', 'minGasLimit')) { | ||
const msg = this._errorMsg(`gas limit decreased below minimum gas limit`); | ||
const msg = this._errorMsg(`gas limit decreased below minimum gas limit. Gas limit: ${gasLimit}, minimum gas limit: ${this.common.param('gasConfig', 'minGasLimit')}`); | ||
throw new Error(msg); | ||
@@ -508,2 +509,8 @@ } | ||
} | ||
// 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) { | ||
@@ -524,7 +531,7 @@ rawItems.push((0, util_1.bigIntToUnpaddedBytes)(this.blobGasUsed)); | ||
if (!this.cache.hash) { | ||
this.cache.hash = (0, keccak_js_1.keccak256)(rlp_1.RLP.encode(this.raw())); | ||
this.cache.hash = this.keccakFunction(rlp_1.RLP.encode(this.raw())); | ||
} | ||
return this.cache.hash; | ||
} | ||
return (0, keccak_js_1.keccak256)(rlp_1.RLP.encode(this.raw())); | ||
return this.keccakFunction(rlp_1.RLP.encode(this.raw())); | ||
} | ||
@@ -617,3 +624,3 @@ /** | ||
raw[12] = this.extraData.subarray(0, this.extraData.length - clique_js_1.CLIQUE_EXTRA_SEAL); | ||
return (0, keccak_js_1.keccak256)(rlp_1.RLP.encode(raw)); | ||
return this.keccakFunction(rlp_1.RLP.encode(raw)); | ||
} | ||
@@ -654,3 +661,4 @@ /** | ||
this._requireClique('cliqueSealBlock'); | ||
const signature = (0, util_1.ecsign)(this.cliqueSigHash(), privateKey); | ||
const ecSignFunction = this.common.customCrypto?.ecsign ?? util_1.ecsign; | ||
const signature = ecSignFunction(this.cliqueSigHash(), privateKey); | ||
const signatureB = (0, util_1.concatBytes)(signature.r, signature.s, (0, util_1.bigIntToBytes)(signature.v - util_1.BIGINT_27)); | ||
@@ -774,3 +782,3 @@ const extraDataWithoutSeal = this.extraData.subarray(0, this.extraData.length - clique_js_1.CLIQUE_EXTRA_SEAL); | ||
if (drift <= DAO_ForceExtraDataRange && !(0, util_1.equalsBytes)(this.extraData, DAO_ExtraData)) { | ||
const msg = this._errorMsg("extraData should be 'dao-hard-fork'"); | ||
const msg = this._errorMsg(`extraData should be 'dao-hard-fork', got ${(0, util_1.bytesToUtf8)(this.extraData)} (hex: ${(0, util_1.bytesToHex)(this.extraData)})`); | ||
throw new Error(msg); | ||
@@ -777,0 +785,0 @@ } |
@@ -27,6 +27,6 @@ "use strict"; | ||
if (values.length > 20) { | ||
throw new Error('invalid header. More values than expected were received'); | ||
throw new Error(`invalid header. More values than expected were received. Max: 20, got: ${values.length}`); | ||
} | ||
if (values.length < 15) { | ||
throw new Error('invalid header. Less values than expected were received'); | ||
throw new Error(`invalid header. Less values than expected were received. Min: 15, got: ${values.length}`); | ||
} | ||
@@ -33,0 +33,0 @@ return { |
@@ -64,3 +64,39 @@ import type { BlockHeader } from './header.js'; | ||
} | ||
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 | string; | ||
}[]; | ||
} | ||
/** | ||
* 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. | ||
@@ -101,5 +137,19 @@ */ | ||
withdrawals?: Array<WithdrawalData>; | ||
/** | ||
* EIP-6800: Verkle Proof Data (experimental) | ||
*/ | ||
executionWitness?: VerkleExecutionWitness | null; | ||
} | ||
export declare type WithdrawalsBytes = WithdrawalBytes[]; | ||
export declare type BlockBytes = [BlockHeaderBytes, TransactionsBytes, UncleHeadersBytes] | [BlockHeaderBytes, TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes]; | ||
export declare type ExecutionWitnessBytes = Uint8Array; | ||
export declare 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 declare type BlockHeaderBytes = Uint8Array[]; | ||
@@ -123,2 +173,3 @@ export declare type BlockBodyBytes = [TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes?]; | ||
withdrawals?: JsonRpcWithdrawal[]; | ||
executionWitness?: VerkleExecutionWitness | null; | ||
} | ||
@@ -177,2 +228,3 @@ /** | ||
parentBeaconBlockRoot?: string; | ||
executionWitness?: VerkleExecutionWitness | null; | ||
} | ||
@@ -204,3 +256,4 @@ export declare type WithdrawalV1 = { | ||
parentBeaconBlockRoot?: PrefixedHexString; | ||
executionWitness?: VerkleExecutionWitness | null; | ||
}; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -5,3 +5,3 @@ import { Trie } from '@ethereumjs/trie'; | ||
import type { BeaconPayloadJson } from './from-beacon-payload.js'; | ||
import type { BlockBytes, BlockData, BlockOptions, ExecutionPayload, JsonBlock, JsonRpcBlock } from './types.js'; | ||
import type { BlockBytes, BlockData, BlockOptions, ExecutionPayload, JsonBlock, JsonRpcBlock, VerkleExecutionWitness } from './types.js'; | ||
import type { Common } from '@ethereumjs/common'; | ||
@@ -19,4 +19,13 @@ import type { TypedTransaction } from '@ethereumjs/tx'; | ||
readonly common: Common; | ||
private cache; | ||
protected keccakFunction: (msg: Uint8Array) => Uint8Array; | ||
/** | ||
* 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 | ||
*/ | ||
readonly executionWitness?: VerkleExecutionWitness | null; | ||
protected cache: { | ||
txTrieRoot?: Uint8Array; | ||
}; | ||
/** | ||
* Returns the withdrawals trie root for array of Withdrawal. | ||
@@ -59,3 +68,3 @@ * @param wts array of Withdrawal to compute the root of | ||
* @param uncles - Optional list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex) | ||
* @param options - An object describing the blockchain | ||
* @param opts - An object describing the blockchain | ||
*/ | ||
@@ -77,3 +86,3 @@ static fromRPC(blockData: JsonRpcBlock, uncles?: any[], opts?: BlockOptions): Block; | ||
*/ | ||
static fromExecutionPayload(payload: ExecutionPayload, options?: BlockOptions): Promise<Block>; | ||
static fromExecutionPayload(payload: ExecutionPayload, opts?: BlockOptions): Promise<Block>; | ||
/** | ||
@@ -85,3 +94,3 @@ * Method to retrieve a block from a beacon payload json | ||
*/ | ||
static fromBeaconPayloadJson(payload: BeaconPayloadJson, options?: BlockOptions): Promise<Block>; | ||
static fromBeaconPayloadJson(payload: BeaconPayloadJson, opts?: BlockOptions): Promise<Block>; | ||
/** | ||
@@ -91,5 +100,5 @@ * This constructor takes the values, validates them, assigns them and freezes the object. | ||
*/ | ||
constructor(header?: BlockHeader, transactions?: TypedTransaction[], uncleHeaders?: BlockHeader[], withdrawals?: Withdrawal[], opts?: BlockOptions); | ||
constructor(header?: BlockHeader, transactions?: TypedTransaction[], uncleHeaders?: BlockHeader[], withdrawals?: Withdrawal[], opts?: BlockOptions, executionWitness?: VerkleExecutionWitness | null); | ||
/** | ||
* 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. | ||
*/ | ||
@@ -137,4 +146,5 @@ raw(): BlockBytes; | ||
* @param onlyHeader if only passed the header, skip validating txTrie and unclesHash (default: false) | ||
* @param verifyTxs if set to `false`, will not check for transaction validation errors (default: true) | ||
*/ | ||
validateData(onlyHeader?: boolean): Promise<void>; | ||
validateData(onlyHeader?: boolean, verifyTxs?: boolean): Promise<void>; | ||
/** | ||
@@ -141,0 +151,0 @@ * Validates that blob gas fee for each transaction is greater than or equal to the |
@@ -6,3 +6,3 @@ var _a; | ||
import { BlobEIP4844Transaction, Capability, TransactionFactory } from '@ethereumjs/tx'; | ||
import { BIGINT_0, KECCAK256_RLP, Withdrawal, bigIntToHex, bytesToHex, equalsBytes, fetchFromProvider, getProvider, hexToBytes, intToHex, isHexPrefixed, } from '@ethereumjs/util'; | ||
import { BIGINT_0, KECCAK256_RLP, Withdrawal, bigIntToHex, bytesToHex, bytesToUtf8, equalsBytes, fetchFromProvider, getProvider, hexToBytes, intToHex, isHexPrefixed, } from '@ethereumjs/util'; | ||
import { keccak256 } from 'ethereum-cryptography/keccak.js'; | ||
@@ -20,3 +20,3 @@ import { executionPayloadFromBeaconPayload } from './from-beacon-payload.js'; | ||
*/ | ||
constructor(header, transactions = [], uncleHeaders = [], withdrawals, opts = {}) { | ||
constructor(header, transactions = [], uncleHeaders = [], withdrawals, opts = {}, executionWitness) { | ||
this.transactions = []; | ||
@@ -27,4 +27,24 @@ this.uncleHeaders = []; | ||
this.common = this.header.common; | ||
this.keccakFunction = this.common.customCrypto.keccak256 ?? keccak256; | ||
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; | ||
@@ -45,2 +65,7 @@ if (uncleHeaders.length > 0) { | ||
} | ||
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; | ||
@@ -82,3 +107,3 @@ if (freeze) { | ||
static fromBlockData(blockData = {}, opts) { | ||
const { header: headerData, transactions: txsData, uncleHeaders: uhsData, withdrawals: withdrawalsData, } = blockData; | ||
const { header: headerData, transactions: txsData, uncleHeaders: uhsData, withdrawals: withdrawalsData, executionWitness: executionWitnessData, } = blockData; | ||
const header = BlockHeader.fromHeaderData(headerData, opts); | ||
@@ -113,3 +138,6 @@ // parse transactions | ||
const withdrawals = withdrawalsData?.map(Withdrawal.fromWithdrawalData); | ||
return new Block(header, transactions, uncleHeaders, withdrawals, opts); | ||
// 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, executionWitness); | ||
} | ||
@@ -136,11 +164,11 @@ /** | ||
static fromValuesArray(values, opts) { | ||
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 (at most 5)`); | ||
} | ||
// 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'); | ||
@@ -181,3 +209,14 @@ } | ||
?.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))); | ||
} | ||
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); | ||
} | ||
@@ -189,3 +228,3 @@ /** | ||
* @param uncles - Optional list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex) | ||
* @param options - An object describing the blockchain | ||
* @param opts - An object describing the blockchain | ||
*/ | ||
@@ -201,4 +240,4 @@ static fromRPC(blockData, uncles, opts) { | ||
*/ | ||
static async fromExecutionPayload(payload, options) { | ||
const { blockNumber: number, receiptsRoot: receiptTrie, prevRandao: mixHash, feeRecipient: coinbase, transactions, withdrawals: withdrawalsData, } = payload; | ||
static async fromExecutionPayload(payload, opts) { | ||
const { blockNumber: number, receiptsRoot: receiptTrie, prevRandao: mixHash, feeRecipient: coinbase, transactions, withdrawals: withdrawalsData, executionWitness, } = payload; | ||
const txs = []; | ||
@@ -208,3 +247,3 @@ for (const [index, serializedTx] of transactions.entries()) { | ||
const tx = TransactionFactory.fromSerializedData(hexToBytes(serializedTx), { | ||
common: options?.common, | ||
common: opts?.common, | ||
}); | ||
@@ -218,6 +257,6 @@ txs.push(tx); | ||
} | ||
const transactionsTrie = await Block.genTransactionsTrieRoot(txs); | ||
const transactionsTrie = await Block.genTransactionsTrieRoot(txs, new Trie({ common: opts?.common })); | ||
const withdrawals = withdrawalsData?.map((wData) => Withdrawal.fromWithdrawalData(wData)); | ||
const withdrawalsRoot = withdrawals | ||
? await Block.genWithdrawalsTrieRoot(withdrawals) | ||
? await Block.genWithdrawalsTrieRoot(withdrawals, new Trie({ common: opts?.common })) | ||
: undefined; | ||
@@ -234,3 +273,7 @@ const header = { | ||
// 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 }, opts); | ||
if (block.common.isActivatedEIP(6800) && | ||
(executionWitness === undefined || executionWitness === null)) { | ||
throw Error('Missing executionWitness for EIP-6800 activated executionPayload'); | ||
} | ||
// Verify blockHash matches payload | ||
@@ -249,8 +292,8 @@ if (!equalsBytes(block.hash(), hexToBytes(payload.blockHash))) { | ||
*/ | ||
static async fromBeaconPayloadJson(payload, options) { | ||
static async fromBeaconPayloadJson(payload, opts) { | ||
const executionPayload = executionPayloadFromBeaconPayload(payload); | ||
return Block.fromExecutionPayload(executionPayload, options); | ||
return Block.fromExecutionPayload(executionPayload, opts); | ||
} | ||
/** | ||
* 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. | ||
*/ | ||
@@ -267,2 +310,6 @@ raw() { | ||
} | ||
if (this.executionWitness !== undefined && this.executionWitness !== null) { | ||
const executionWitnessBytes = RLP.encode(JSON.stringify(this.executionWitness)); | ||
bytesArray.push(executionWitnessBytes); | ||
} | ||
return bytesArray; | ||
@@ -292,3 +339,3 @@ } | ||
async genTxTrie() { | ||
return Block.genTransactionsTrieRoot(this.transactions, new Trie()); | ||
return Block.genTransactionsTrieRoot(this.transactions, new Trie({ common: this.common })); | ||
} | ||
@@ -373,8 +420,11 @@ /** | ||
* @param onlyHeader if only passed the header, skip validating txTrie and unclesHash (default: false) | ||
* @param verifyTxs if set to `false`, will not check for transaction validation errors (default: true) | ||
*/ | ||
async validateData(onlyHeader = false) { | ||
const txErrors = this.getTransactionsValidationErrors(); | ||
if (txErrors.length > 0) { | ||
const msg = this._errorMsg(`invalid transactions: ${txErrors.join(' ')}`); | ||
throw new Error(msg); | ||
async validateData(onlyHeader = false, verifyTxs = true) { | ||
if (verifyTxs) { | ||
const txErrors = this.getTransactionsValidationErrors(); | ||
if (txErrors.length > 0) { | ||
const msg = this._errorMsg(`invalid transactions: ${txErrors.join(' ')}`); | ||
throw new Error(msg); | ||
} | ||
} | ||
@@ -384,2 +434,10 @@ if (onlyHeader) { | ||
} | ||
if (verifyTxs) { | ||
for (const [index, tx] of this.transactions.entries()) { | ||
if (!tx.isSigned()) { | ||
const msg = this._errorMsg(`invalid transactions: transaction at index ${index} is unsigned`); | ||
throw new Error(msg); | ||
} | ||
} | ||
} | ||
if (!(await this.transactionsTrieIsValid())) { | ||
@@ -397,2 +455,12 @@ const msg = this._errorMsg('invalid transaction trie'); | ||
} | ||
// 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`); | ||
} | ||
} | ||
} | ||
@@ -439,3 +507,3 @@ /** | ||
const raw = RLP.encode(uncles); | ||
return equalsBytes(keccak256(raw), this.header.uncleHash); | ||
return equalsBytes(this.keccakFunction(raw), this.header.uncleHash); | ||
} | ||
@@ -450,3 +518,3 @@ /** | ||
} | ||
const withdrawalsRoot = await Block.genWithdrawalsTrieRoot(this.withdrawals); | ||
const withdrawalsRoot = await Block.genWithdrawalsTrieRoot(this.withdrawals, new Trie({ common: this.common })); | ||
return equalsBytes(withdrawalsRoot, this.header.withdrawalsRoot); | ||
@@ -453,0 +521,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import type { ExecutionPayload } from './types.js'; | ||
import type { ExecutionPayload, VerkleExecutionWitness } from './types.js'; | ||
declare type BeaconWithdrawal = { | ||
@@ -27,2 +27,3 @@ index: string; | ||
parent_beacon_block_root?: string; | ||
execution_witness?: VerkleExecutionWitness; | ||
}; | ||
@@ -29,0 +30,0 @@ /** |
@@ -40,4 +40,9 @@ import { bigIntToHex } from '@ethereumjs/util'; | ||
} | ||
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; | ||
} | ||
//# sourceMappingURL=from-beacon-payload.js.map |
import { Common } from '@ethereumjs/common'; | ||
import { Address } from '@ethereumjs/util'; | ||
import type { BlockHeaderBytes, BlockOptions, HeaderData, JsonHeader } from './types.js'; | ||
interface HeaderCache { | ||
hash: Uint8Array | undefined; | ||
} | ||
/** | ||
@@ -29,3 +32,4 @@ * An object that represents the block header. | ||
readonly common: Common; | ||
private cache; | ||
protected keccakFunction: (msg: Uint8Array) => Uint8Array; | ||
protected cache: HeaderCache; | ||
/** | ||
@@ -63,3 +67,3 @@ * EIP-4399: After merge to PoS, `mixHash` supplanted as `prevRandao` | ||
*/ | ||
constructor(headerData: HeaderData, options?: BlockOptions); | ||
constructor(headerData: HeaderData, opts?: BlockOptions); | ||
/** | ||
@@ -190,2 +194,3 @@ * Validates correct buffer lengths, throws if invalid. | ||
} | ||
export {}; | ||
//# sourceMappingURL=header.d.ts.map |
import { Chain, Common, ConsensusAlgorithm, ConsensusType, Hardfork } from '@ethereumjs/common'; | ||
import { RLP } from '@ethereumjs/rlp'; | ||
import { Address, BIGINT_0, BIGINT_1, BIGINT_2, BIGINT_27, BIGINT_7, KECCAK256_RLP, KECCAK256_RLP_ARRAY, TypeOutput, bigIntToBytes, bigIntToHex, bigIntToUnpaddedBytes, bytesToBigInt, bytesToHex, concatBytes, ecrecover, ecsign, equalsBytes, hexToBytes, toType, zeros, } from '@ethereumjs/util'; | ||
import { Address, BIGINT_0, BIGINT_1, BIGINT_2, BIGINT_27, BIGINT_7, KECCAK256_RLP, KECCAK256_RLP_ARRAY, TypeOutput, bigIntToBytes, bigIntToHex, bigIntToUnpaddedBytes, bytesToBigInt, bytesToHex, bytesToUtf8, concatBytes, ecrecover, ecsign, equalsBytes, hexToBytes, toType, zeros, } from '@ethereumjs/util'; | ||
import { keccak256 } from 'ethereum-cryptography/keccak.js'; | ||
@@ -19,8 +19,8 @@ import { CLIQUE_EXTRA_SEAL, CLIQUE_EXTRA_VANITY } from './clique.js'; | ||
*/ | ||
constructor(headerData, options = {}) { | ||
constructor(headerData, opts = {}) { | ||
this.cache = { | ||
hash: undefined, | ||
}; | ||
if (options.common) { | ||
this.common = options.common.copy(); | ||
if (opts.common) { | ||
this.common = opts.common.copy(); | ||
} | ||
@@ -32,3 +32,4 @@ else { | ||
} | ||
const skipValidateConsensusFormat = options.skipConsensusFormatValidation ?? false; | ||
this.keccakFunction = this.common.customCrypto.keccak256 ?? keccak256; | ||
const skipValidateConsensusFormat = opts.skipConsensusFormatValidation ?? false; | ||
const defaults = { | ||
@@ -66,3 +67,3 @@ parentHash: zeros(32), | ||
const nonce = toType(headerData.nonce, TypeOutput.Uint8Array) ?? defaults.nonce; | ||
const setHardfork = options.setHardfork ?? false; | ||
const setHardfork = opts.setHardfork ?? false; | ||
if (setHardfork === true) { | ||
@@ -141,8 +142,8 @@ this.common.setHardforkBy({ | ||
// block option parameter, we instead set difficulty to this value. | ||
if (options.calcDifficultyFromHeader && | ||
if (opts.calcDifficultyFromHeader && | ||
this.common.consensusAlgorithm() === ConsensusAlgorithm.Ethash) { | ||
this.difficulty = this.ethashCanonicalDifficulty(options.calcDifficultyFromHeader); | ||
this.difficulty = this.ethashCanonicalDifficulty(opts.calcDifficultyFromHeader); | ||
} | ||
// If cliqueSigner is provided, seal block with provided privateKey. | ||
if (options.cliqueSigner) { | ||
if (opts.cliqueSigner) { | ||
// Ensure extraData is at least length CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL | ||
@@ -154,3 +155,3 @@ const minExtraDataLength = CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL; | ||
} | ||
this.extraData = this.cliqueSealBlock(options.cliqueSigner); | ||
this.extraData = this.cliqueSealBlock(opts.cliqueSigner); | ||
} | ||
@@ -160,3 +161,3 @@ // Validate consensus format after block is sealed (if applicable) so extraData checks will pass | ||
this._consensusFormatValidation(); | ||
const freeze = options?.freeze ?? true; | ||
const freeze = opts?.freeze ?? true; | ||
if (freeze) { | ||
@@ -261,3 +262,3 @@ Object.freeze(this); | ||
if (this.gasUsed > this.gasLimit) { | ||
const msg = this._errorMsg('Invalid block: too much gas used'); | ||
const msg = this._errorMsg(`Invalid block: too much gas used. Used: ${this.gasUsed}, gas limit: ${this.gasLimit}`); | ||
throw new Error(msg); | ||
@@ -398,11 +399,11 @@ } | ||
if (gasLimit >= maxGasLimit) { | ||
const msg = this._errorMsg('gas limit increased too much'); | ||
const msg = this._errorMsg(`gas limit increased too much. Gas limit: ${gasLimit}, max gas limit: ${maxGasLimit}`); | ||
throw new Error(msg); | ||
} | ||
if (gasLimit <= minGasLimit) { | ||
const msg = this._errorMsg('gas limit decreased too much'); | ||
const msg = this._errorMsg(`gas limit decreased too much. Gas limit: ${gasLimit}, min gas limit: ${minGasLimit}`); | ||
throw new Error(msg); | ||
} | ||
if (gasLimit < this.common.param('gasConfig', 'minGasLimit')) { | ||
const msg = this._errorMsg(`gas limit decreased below minimum gas limit`); | ||
const msg = this._errorMsg(`gas limit decreased below minimum gas limit. Gas limit: ${gasLimit}, minimum gas limit: ${this.common.param('gasConfig', 'minGasLimit')}`); | ||
throw new Error(msg); | ||
@@ -505,2 +506,8 @@ } | ||
} | ||
// 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) { | ||
@@ -521,7 +528,7 @@ rawItems.push(bigIntToUnpaddedBytes(this.blobGasUsed)); | ||
if (!this.cache.hash) { | ||
this.cache.hash = keccak256(RLP.encode(this.raw())); | ||
this.cache.hash = this.keccakFunction(RLP.encode(this.raw())); | ||
} | ||
return this.cache.hash; | ||
} | ||
return keccak256(RLP.encode(this.raw())); | ||
return this.keccakFunction(RLP.encode(this.raw())); | ||
} | ||
@@ -614,3 +621,3 @@ /** | ||
raw[12] = this.extraData.subarray(0, this.extraData.length - CLIQUE_EXTRA_SEAL); | ||
return keccak256(RLP.encode(raw)); | ||
return this.keccakFunction(RLP.encode(raw)); | ||
} | ||
@@ -651,3 +658,4 @@ /** | ||
this._requireClique('cliqueSealBlock'); | ||
const signature = ecsign(this.cliqueSigHash(), privateKey); | ||
const ecSignFunction = this.common.customCrypto?.ecsign ?? ecsign; | ||
const signature = ecSignFunction(this.cliqueSigHash(), privateKey); | ||
const signatureB = concatBytes(signature.r, signature.s, bigIntToBytes(signature.v - BIGINT_27)); | ||
@@ -771,3 +779,3 @@ const extraDataWithoutSeal = this.extraData.subarray(0, this.extraData.length - CLIQUE_EXTRA_SEAL); | ||
if (drift <= DAO_ForceExtraDataRange && !equalsBytes(this.extraData, DAO_ExtraData)) { | ||
const msg = this._errorMsg("extraData should be 'dao-hard-fork'"); | ||
const msg = this._errorMsg(`extraData should be 'dao-hard-fork', got ${bytesToUtf8(this.extraData)} (hex: ${bytesToHex(this.extraData)})`); | ||
throw new Error(msg); | ||
@@ -774,0 +782,0 @@ } |
@@ -23,6 +23,6 @@ import { BlobEIP4844Transaction } from '@ethereumjs/tx'; | ||
if (values.length > 20) { | ||
throw new Error('invalid header. More values than expected were received'); | ||
throw new Error(`invalid header. More values than expected were received. Max: 20, got: ${values.length}`); | ||
} | ||
if (values.length < 15) { | ||
throw new Error('invalid header. Less values than expected were received'); | ||
throw new Error(`invalid header. Less values than expected were received. Min: 15, got: ${values.length}`); | ||
} | ||
@@ -29,0 +29,0 @@ return { |
@@ -64,3 +64,39 @@ import type { BlockHeader } from './header.js'; | ||
} | ||
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 | string; | ||
}[]; | ||
} | ||
/** | ||
* 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. | ||
@@ -101,5 +137,19 @@ */ | ||
withdrawals?: Array<WithdrawalData>; | ||
/** | ||
* EIP-6800: Verkle Proof Data (experimental) | ||
*/ | ||
executionWitness?: VerkleExecutionWitness | null; | ||
} | ||
export declare type WithdrawalsBytes = WithdrawalBytes[]; | ||
export declare type BlockBytes = [BlockHeaderBytes, TransactionsBytes, UncleHeadersBytes] | [BlockHeaderBytes, TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes]; | ||
export declare type ExecutionWitnessBytes = Uint8Array; | ||
export declare 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 declare type BlockHeaderBytes = Uint8Array[]; | ||
@@ -123,2 +173,3 @@ export declare type BlockBodyBytes = [TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes?]; | ||
withdrawals?: JsonRpcWithdrawal[]; | ||
executionWitness?: VerkleExecutionWitness | null; | ||
} | ||
@@ -177,2 +228,3 @@ /** | ||
parentBeaconBlockRoot?: string; | ||
executionWitness?: VerkleExecutionWitness | null; | ||
} | ||
@@ -204,3 +256,4 @@ export declare type WithdrawalV1 = { | ||
parentBeaconBlockRoot?: PrefixedHexString; | ||
executionWitness?: VerkleExecutionWitness | null; | ||
}; | ||
//# sourceMappingURL=types.d.ts.map |
{ | ||
"name": "@ethereumjs/block", | ||
"version": "5.0.1", | ||
"version": "5.1.0", | ||
"description": "Provides Block serialization and help functions", | ||
@@ -37,3 +37,4 @@ "keywords": [ | ||
"docs:build": "typedoc --options typedoc.cjs", | ||
"examples": "ts-node ../../scripts/examples-runner.ts -- block", | ||
"examples": "tsx ../../scripts/examples-runner.ts -- block", | ||
"examples:build": "npx embedme README.md", | ||
"lint": "../../config/cli/lint.sh", | ||
@@ -49,8 +50,8 @@ "lint:diff": "../../config/cli/lint-diff.sh", | ||
"dependencies": { | ||
"@ethereumjs/common": "^4.1.0", | ||
"@ethereumjs/rlp": "^5.0.1", | ||
"@ethereumjs/trie": "^6.0.1", | ||
"@ethereumjs/tx": "^5.1.0", | ||
"@ethereumjs/util": "^9.0.1", | ||
"ethereum-cryptography": "^2.1.2" | ||
"@ethereumjs/common": "^4.2.0", | ||
"@ethereumjs/rlp": "^5.0.2", | ||
"@ethereumjs/trie": "^6.1.0", | ||
"@ethereumjs/tx": "^5.2.0", | ||
"@ethereumjs/util": "^9.0.2", | ||
"ethereum-cryptography": "^2.1.3" | ||
}, | ||
@@ -57,0 +58,0 @@ "devDependencies": { |
127
README.md
@@ -40,4 +40,7 @@ # @ethereumjs/block | ||
```typescript | ||
```ts | ||
// ./examples/simple.ts | ||
import { BlockHeader } from '@ethereumjs/block' | ||
import { bytesToHex } from '@ethereumjs/util' | ||
@@ -47,3 +50,2 @@ const headerData = { | ||
parentHash: '0x6bfee7294bf44572b7266358e627f3c35105e1c3851f3de09e6d646f955725a7', | ||
difficulty: 131072, | ||
gasLimit: 8000000, | ||
@@ -53,2 +55,3 @@ timestamp: 1562422144, | ||
const header = BlockHeader.fromHeaderData(headerData) | ||
console.log(`Created block header with hash=${bytesToHex(header.hash())}`) | ||
``` | ||
@@ -60,3 +63,3 @@ | ||
```typescript | ||
```ts | ||
try { | ||
@@ -70,2 +73,6 @@ await block.validateData() | ||
### WASM Crypto Support | ||
This library by default uses JavaScript implementations for the basic standard crypto primitives like hashing or signature verification (for included txs). See `@ethereumjs/common` [README](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/common) for instructions on how to replace with e.g. a more performant WASM implementation by using a shared `common` instance. | ||
### EIP-1559 Blocks | ||
@@ -75,3 +82,5 @@ | ||
```typescript | ||
```ts | ||
// ./examples/1559.ts | ||
import { Block } from '@ethereumjs/block' | ||
@@ -94,11 +103,10 @@ import { Chain, Common, Hardfork } from '@ethereumjs/common' | ||
// gas used is greater than half the gas limit | ||
block.header.calcNextBaseFee().toNumber() // 11 | ||
console.log(Number(block.header.calcNextBaseFee())) // 11 | ||
// So for creating a block with a matching base fee in a certain | ||
// chain context you can do: | ||
const blockWithMatchingBaseFee = Block.fromBlockData( | ||
{ | ||
header: { | ||
baseFeePerGas: parentHeader.calcNextBaseFee(), | ||
baseFeePerGas: block.header.calcNextBaseFee(), | ||
gasLimit: BigInt(100), | ||
@@ -110,2 +118,4 @@ gasUsed: BigInt(60), | ||
) | ||
console.log(Number(blockWithMatchingBaseFee.header.baseFeePerGas)) // 11 | ||
``` | ||
@@ -119,3 +129,5 @@ | ||
```typescript | ||
```ts | ||
// ./examples/withdrawals.ts | ||
import { Block } from '@ethereumjs/block' | ||
@@ -148,2 +160,4 @@ import { Common, Chain } from '@ethereumjs/common' | ||
) | ||
console.log(`Block with ${block.withdrawals!.length} withdrawal(s) created`) | ||
``` | ||
@@ -155,14 +169,53 @@ | ||
This library supports the blob transaction type introduced with [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) as being specified in the [b9a5a11](https://github.com/ethereum/EIPs/commit/b9a5a117ab7e1dc18f937841d00598b527c306e7) EIP version from July 2023 deployed along [4844-devnet-7](https://github.com/ethpandaops/4844-testnet) (July 2023), see PR [#2349](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2349) and following. | ||
This library supports the blob transaction type introduced with [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844). | ||
**Note:** 4844 support is not yet completely stable and there will still be (4844-)breaking changes along all types of library releases. | ||
#### Initialization | ||
To create blocks which include blob transactions you have to active EIP-4844 in the associated `@ethereumjs/common` library: | ||
To create blocks which include blob transactions you have to active EIP-4844 in the associated `@ethereumjs/common` library or use a 4844-including hardfork like `Cancun`: | ||
```typescript | ||
```ts | ||
// ./examples/4844.ts | ||
import { Common, Chain, Hardfork } from '@ethereumjs/common' | ||
import { Block } from '@ethereumjs/block' | ||
import { BlobEIP4844Transaction } from '@ethereumjs/tx' | ||
import { Address, initKZG } from '@ethereumjs/util' | ||
import * as kzg from 'c-kzg' | ||
import { randomBytes } from 'crypto' | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Shanghai, eips: [4844] }) | ||
const main = async () => { | ||
initKZG(kzg, __dirname + '/../../client/src/trustedSetups/official.txt') | ||
const common = new Common({ | ||
chain: Chain.Mainnet, | ||
hardfork: Hardfork.Cancun, | ||
customCrypto: { | ||
kzg, | ||
}, | ||
}) | ||
const blobTx = BlobEIP4844Transaction.fromTxData( | ||
{ blobsData: ['myFirstBlob'], to: Address.fromPrivateKey(randomBytes(32)) }, | ||
{ common } | ||
) | ||
const block = Block.fromBlockData( | ||
{ | ||
header: { | ||
excessBlobGas: 0n, | ||
}, | ||
transactions: [blobTx], | ||
}, | ||
{ | ||
common, | ||
skipConsensusFormatValidation: true, | ||
} | ||
) | ||
console.log( | ||
`4844 block header with excessBlobGas=${block.header.excessBlobGas} created and ${ | ||
block.transactions.filter((tx) => tx.type === 3).length | ||
} blob transactions` | ||
) | ||
} | ||
main() | ||
``` | ||
@@ -184,9 +237,15 @@ | ||
```typescript | ||
```ts | ||
// ./examples/pow.ts | ||
import { Block } from '@ethereumjs/block' | ||
import { Chain, Common } from '@ethereumjs/common' | ||
const common = new Common({ chain: Chain.Mainnet }) | ||
import { Chain, Common, Hardfork } from '@ethereumjs/common' | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Chainstart }) | ||
console.log(common.consensusType()) // 'pow' | ||
console.log(common.consensusAlgorithm()) // 'ethash' | ||
const block = Block.fromBlockData({}, { common }) | ||
Block.fromBlockData({}, { common }) | ||
console.log(`Old Proof-of-Work block created`) | ||
``` | ||
@@ -200,9 +259,15 @@ | ||
```typescript | ||
```ts | ||
// ./examples/clique.ts | ||
import { Block } from '@ethereumjs/block' | ||
import { Chain, Common } from '@ethereumjs/common' | ||
const common = new Common({ chain: Chain.Goerli }) | ||
import { Chain, Common, Hardfork } from '@ethereumjs/common' | ||
const common = new Common({ chain: Chain.Goerli, hardfork: Hardfork.Chainstart }) | ||
console.log(common.consensusType()) // 'poa' | ||
console.log(common.consensusAlgorithm()) // 'clique' | ||
const block = Block.fromBlockData({}, { common }) | ||
Block.fromBlockData({ header: { extraData: new Uint8Array(97) } }, { common }) | ||
console.log(`Old Clique Proof-of-Authority block created`) | ||
``` | ||
@@ -212,3 +277,3 @@ | ||
```typescript | ||
```ts | ||
const cliqueSigner = Buffer.from('PRIVATE_KEY_HEX_STRING', 'hex') | ||
@@ -236,6 +301,10 @@ const block = Block.fromHeaderData(headerData, { cliqueSigner }) | ||
```typescript | ||
```ts | ||
// ./examples/pos.ts | ||
import { Block } from '@ethereumjs/block' | ||
import { Chain, Common, Hardfork } from '@ethereumjs/common' | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Paris }) | ||
import { Chain, Common } from '@ethereumjs/common' | ||
const common = new Common({ chain: Chain.Mainnet }) | ||
const block = Block.fromBlockData( | ||
@@ -247,2 +316,4 @@ { | ||
) | ||
console.log(`Proof-of-Stake (default) block created with hardfork=${block.common.hardfork()}`) | ||
``` | ||
@@ -268,3 +339,3 @@ | ||
```typescript | ||
```ts | ||
import { EthereumJSClass } from '@ethereumjs/[PACKAGE_NAME]' | ||
@@ -275,3 +346,3 @@ ``` | ||
```typescript | ||
```ts | ||
const { EthereumJSClass } = require('@ethereumjs/[PACKAGE_NAME]') | ||
@@ -278,0 +349,0 @@ ``` |
153
src/block.ts
@@ -11,2 +11,3 @@ import { ConsensusType } from '@ethereumjs/common' | ||
bytesToHex, | ||
bytesToUtf8, | ||
equalsBytes, | ||
@@ -34,2 +35,3 @@ fetchFromProvider, | ||
JsonRpcBlock, | ||
VerkleExecutionWitness, | ||
} from './types.js' | ||
@@ -54,4 +56,12 @@ import type { Common } from '@ethereumjs/common' | ||
public readonly common: Common | ||
protected keccakFunction: (msg: Uint8Array) => Uint8Array | ||
private cache: { | ||
/** | ||
* 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 | ||
protected cache: { | ||
txTrieRoot?: Uint8Array | ||
@@ -98,3 +108,5 @@ } = {} | ||
withdrawals: withdrawalsData, | ||
executionWitness: executionWitnessData, | ||
} = blockData | ||
const header = BlockHeader.fromHeaderData(headerData, opts) | ||
@@ -132,4 +144,7 @@ | ||
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) | ||
} | ||
@@ -160,4 +175,6 @@ | ||
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 (at most 5)` | ||
) | ||
} | ||
@@ -167,3 +184,3 @@ | ||
// 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) | ||
@@ -173,3 +190,3 @@ | ||
header.common.isActivatedEIP(4895) && | ||
(values[3] === undefined || !Array.isArray(values[3])) | ||
(withdrawalBytes === undefined || !Array.isArray(withdrawalBytes)) | ||
) { | ||
@@ -219,3 +236,14 @@ throw new Error( | ||
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) | ||
} | ||
@@ -228,3 +256,3 @@ | ||
* @param uncles - Optional list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex) | ||
* @param options - An object describing the blockchain | ||
* @param opts - An object describing the blockchain | ||
*/ | ||
@@ -304,3 +332,3 @@ public static fromRPC(blockData: JsonRpcBlock, uncles?: any[], opts?: BlockOptions) { | ||
payload: ExecutionPayload, | ||
options?: BlockOptions | ||
opts?: BlockOptions | ||
): Promise<Block> { | ||
@@ -314,2 +342,3 @@ const { | ||
withdrawals: withdrawalsData, | ||
executionWitness, | ||
} = payload | ||
@@ -321,3 +350,3 @@ | ||
const tx = TransactionFactory.fromSerializedData(hexToBytes(serializedTx), { | ||
common: options?.common, | ||
common: opts?.common, | ||
}) | ||
@@ -331,6 +360,9 @@ txs.push(tx) | ||
const transactionsTrie = await Block.genTransactionsTrieRoot(txs) | ||
const transactionsTrie = await Block.genTransactionsTrieRoot( | ||
txs, | ||
new Trie({ common: opts?.common }) | ||
) | ||
const withdrawals = withdrawalsData?.map((wData) => Withdrawal.fromWithdrawalData(wData)) | ||
const withdrawalsRoot = withdrawals | ||
? await Block.genWithdrawalsTrieRoot(withdrawals) | ||
? await Block.genWithdrawalsTrieRoot(withdrawals, new Trie({ common: opts?.common })) | ||
: undefined | ||
@@ -348,3 +380,12 @@ const header: HeaderData = { | ||
// 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 }, | ||
opts | ||
) | ||
if ( | ||
block.common.isActivatedEIP(6800) && | ||
(executionWitness === undefined || executionWitness === null) | ||
) { | ||
throw Error('Missing executionWitness for EIP-6800 activated executionPayload') | ||
} | ||
// Verify blockHash matches payload | ||
@@ -369,6 +410,6 @@ if (!equalsBytes(block.hash(), hexToBytes(payload.blockHash))) { | ||
payload: BeaconPayloadJson, | ||
options?: BlockOptions | ||
opts?: BlockOptions | ||
): Promise<Block> { | ||
const executionPayload = executionPayloadFromBeaconPayload(payload) | ||
return Block.fromExecutionPayload(executionPayload, options) | ||
return Block.fromExecutionPayload(executionPayload, opts) | ||
} | ||
@@ -385,9 +426,31 @@ | ||
withdrawals?: Withdrawal[], | ||
opts: BlockOptions = {} | ||
opts: BlockOptions = {}, | ||
executionWitness?: VerkleExecutionWitness | null | ||
) { | ||
this.header = header ?? BlockHeader.fromHeaderData({}, opts) | ||
this.common = this.header.common | ||
this.keccakFunction = this.common.customCrypto.keccak256 ?? keccak256 | ||
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 | ||
@@ -414,2 +477,10 @@ if (uncleHeaders.length > 0) { | ||
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 | ||
@@ -422,3 +493,3 @@ if (freeze) { | ||
/** | ||
* 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. | ||
*/ | ||
@@ -437,2 +508,6 @@ raw(): BlockBytes { | ||
} | ||
if (this.executionWitness !== undefined && this.executionWitness !== null) { | ||
const executionWitnessBytes = RLP.encode(JSON.stringify(this.executionWitness)) | ||
bytesArray.push(executionWitnessBytes as any) | ||
} | ||
return bytesArray | ||
@@ -466,3 +541,3 @@ } | ||
async genTxTrie(): Promise<Uint8Array> { | ||
return Block.genTransactionsTrieRoot(this.transactions, new Trie()) | ||
return Block.genTransactionsTrieRoot(this.transactions, new Trie({ common: this.common })) | ||
} | ||
@@ -557,8 +632,11 @@ | ||
* @param onlyHeader if only passed the header, skip validating txTrie and unclesHash (default: false) | ||
* @param verifyTxs if set to `false`, will not check for transaction validation errors (default: true) | ||
*/ | ||
async validateData(onlyHeader: boolean = false): Promise<void> { | ||
const txErrors = this.getTransactionsValidationErrors() | ||
if (txErrors.length > 0) { | ||
const msg = this._errorMsg(`invalid transactions: ${txErrors.join(' ')}`) | ||
throw new Error(msg) | ||
async validateData(onlyHeader: boolean = false, verifyTxs: boolean = true): Promise<void> { | ||
if (verifyTxs) { | ||
const txErrors = this.getTransactionsValidationErrors() | ||
if (txErrors.length > 0) { | ||
const msg = this._errorMsg(`invalid transactions: ${txErrors.join(' ')}`) | ||
throw new Error(msg) | ||
} | ||
} | ||
@@ -570,2 +648,13 @@ | ||
if (verifyTxs) { | ||
for (const [index, tx] of this.transactions.entries()) { | ||
if (!tx.isSigned()) { | ||
const msg = this._errorMsg( | ||
`invalid transactions: transaction at index ${index} is unsigned` | ||
) | ||
throw new Error(msg) | ||
} | ||
} | ||
} | ||
if (!(await this.transactionsTrieIsValid())) { | ||
@@ -585,2 +674,13 @@ const msg = this._errorMsg('invalid transaction trie') | ||
} | ||
// 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`) | ||
} | ||
} | ||
} | ||
@@ -645,3 +745,3 @@ | ||
const raw = RLP.encode(uncles) | ||
return equalsBytes(keccak256(raw), this.header.uncleHash) | ||
return equalsBytes(this.keccakFunction(raw), this.header.uncleHash) | ||
} | ||
@@ -657,3 +757,6 @@ | ||
} | ||
const withdrawalsRoot = await Block.genWithdrawalsTrieRoot(this.withdrawals!) | ||
const withdrawalsRoot = await Block.genWithdrawalsTrieRoot( | ||
this.withdrawals!, | ||
new Trie({ common: this.common }) | ||
) | ||
return equalsBytes(withdrawalsRoot, this.header.withdrawalsRoot!) | ||
@@ -660,0 +763,0 @@ } |
import { bigIntToHex } from '@ethereumjs/util' | ||
import type { ExecutionPayload } from './types.js' | ||
import type { ExecutionPayload, VerkleExecutionWitness } from './types.js' | ||
@@ -33,2 +33,4 @@ type BeaconWithdrawal = { | ||
parent_beacon_block_root?: string | ||
// the casing of VerkleExecutionWitness remains same camel case for now | ||
execution_witness?: VerkleExecutionWitness | ||
} | ||
@@ -76,4 +78,9 @@ | ||
} | ||
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 | ||
} |
@@ -18,2 +18,3 @@ import { Chain, Common, ConsensusAlgorithm, ConsensusType, Hardfork } from '@ethereumjs/common' | ||
bytesToHex, | ||
bytesToUtf8, | ||
concatBytes, | ||
@@ -69,3 +70,5 @@ ecrecover, | ||
private cache: HeaderCache = { | ||
protected keccakFunction: (msg: Uint8Array) => Uint8Array | ||
protected cache: HeaderCache = { | ||
hash: undefined, | ||
@@ -149,5 +152,5 @@ } | ||
*/ | ||
constructor(headerData: HeaderData, options: BlockOptions = {}) { | ||
if (options.common) { | ||
this.common = options.common.copy() | ||
constructor(headerData: HeaderData, opts: BlockOptions = {}) { | ||
if (opts.common) { | ||
this.common = opts.common.copy() | ||
} else { | ||
@@ -158,4 +161,5 @@ this.common = new Common({ | ||
} | ||
this.keccakFunction = this.common.customCrypto.keccak256 ?? keccak256 | ||
const skipValidateConsensusFormat = options.skipConsensusFormatValidation ?? false | ||
const skipValidateConsensusFormat = opts.skipConsensusFormatValidation ?? false | ||
@@ -200,3 +204,3 @@ const defaults = { | ||
const setHardfork = options.setHardfork ?? false | ||
const setHardfork = opts.setHardfork ?? false | ||
if (setHardfork === true) { | ||
@@ -293,10 +297,10 @@ this.common.setHardforkBy({ | ||
if ( | ||
options.calcDifficultyFromHeader && | ||
opts.calcDifficultyFromHeader && | ||
this.common.consensusAlgorithm() === ConsensusAlgorithm.Ethash | ||
) { | ||
this.difficulty = this.ethashCanonicalDifficulty(options.calcDifficultyFromHeader) | ||
this.difficulty = this.ethashCanonicalDifficulty(opts.calcDifficultyFromHeader) | ||
} | ||
// If cliqueSigner is provided, seal block with provided privateKey. | ||
if (options.cliqueSigner) { | ||
if (opts.cliqueSigner) { | ||
// Ensure extraData is at least length CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL | ||
@@ -309,3 +313,3 @@ const minExtraDataLength = CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL | ||
this.extraData = this.cliqueSealBlock(options.cliqueSigner) | ||
this.extraData = this.cliqueSealBlock(opts.cliqueSigner) | ||
} | ||
@@ -316,3 +320,3 @@ | ||
const freeze = options?.freeze ?? true | ||
const freeze = opts?.freeze ?? true | ||
if (freeze) { | ||
@@ -361,3 +365,5 @@ Object.freeze(this) | ||
if (this.gasUsed > this.gasLimit) { | ||
const msg = this._errorMsg('Invalid block: too much gas used') | ||
const msg = this._errorMsg( | ||
`Invalid block: too much gas used. Used: ${this.gasUsed}, gas limit: ${this.gasLimit}` | ||
) | ||
throw new Error(msg) | ||
@@ -528,3 +534,5 @@ } | ||
if (gasLimit >= maxGasLimit) { | ||
const msg = this._errorMsg('gas limit increased too much') | ||
const msg = this._errorMsg( | ||
`gas limit increased too much. Gas limit: ${gasLimit}, max gas limit: ${maxGasLimit}` | ||
) | ||
throw new Error(msg) | ||
@@ -534,3 +542,5 @@ } | ||
if (gasLimit <= minGasLimit) { | ||
const msg = this._errorMsg('gas limit decreased too much') | ||
const msg = this._errorMsg( | ||
`gas limit decreased too much. Gas limit: ${gasLimit}, min gas limit: ${minGasLimit}` | ||
) | ||
throw new Error(msg) | ||
@@ -540,3 +550,8 @@ } | ||
if (gasLimit < this.common.param('gasConfig', 'minGasLimit')) { | ||
const msg = this._errorMsg(`gas limit decreased below minimum gas limit`) | ||
const msg = this._errorMsg( | ||
`gas limit decreased below minimum gas limit. Gas limit: ${gasLimit}, minimum gas limit: ${this.common.param( | ||
'gasConfig', | ||
'minGasLimit' | ||
)}` | ||
) | ||
throw new Error(msg) | ||
@@ -662,2 +677,10 @@ } | ||
} | ||
// 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) { | ||
@@ -680,7 +703,7 @@ rawItems.push(bigIntToUnpaddedBytes(this.blobGasUsed!)) | ||
if (!this.cache.hash) { | ||
this.cache.hash = keccak256(RLP.encode(this.raw())) | ||
this.cache.hash = this.keccakFunction(RLP.encode(this.raw())) as Uint8Array | ||
} | ||
return this.cache.hash | ||
} | ||
return keccak256(RLP.encode(this.raw())) | ||
return this.keccakFunction(RLP.encode(this.raw())) | ||
} | ||
@@ -784,3 +807,3 @@ | ||
raw[12] = this.extraData.subarray(0, this.extraData.length - CLIQUE_EXTRA_SEAL) | ||
return keccak256(RLP.encode(raw)) | ||
return this.keccakFunction(RLP.encode(raw)) | ||
} | ||
@@ -826,3 +849,4 @@ | ||
const signature = ecsign(this.cliqueSigHash(), privateKey) | ||
const ecSignFunction = this.common.customCrypto?.ecsign ?? ecsign | ||
const signature = ecSignFunction(this.cliqueSigHash(), privateKey) | ||
const signatureB = concatBytes(signature.r, signature.s, bigIntToBytes(signature.v - BIGINT_27)) | ||
@@ -958,3 +982,7 @@ | ||
if (drift <= DAO_ForceExtraDataRange && !equalsBytes(this.extraData, DAO_ExtraData)) { | ||
const msg = this._errorMsg("extraData should be 'dao-hard-fork'") | ||
const msg = this._errorMsg( | ||
`extraData should be 'dao-hard-fork', got ${bytesToUtf8(this.extraData)} (hex: ${bytesToHex( | ||
this.extraData | ||
)})` | ||
) | ||
throw new Error(msg) | ||
@@ -961,0 +989,0 @@ } |
@@ -49,6 +49,10 @@ import { BlobEIP4844Transaction } from '@ethereumjs/tx' | ||
if (values.length > 20) { | ||
throw new Error('invalid header. More values than expected were received') | ||
throw new Error( | ||
`invalid header. More values than expected were received. Max: 20, got: ${values.length}` | ||
) | ||
} | ||
if (values.length < 15) { | ||
throw new Error('invalid header. Less values than expected were received') | ||
throw new Error( | ||
`invalid header. Less values than expected were received. Min: 15, got: ${values.length}` | ||
) | ||
} | ||
@@ -55,0 +59,0 @@ |
@@ -74,3 +74,42 @@ import type { BlockHeader } from './header.js' | ||
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 | string | ||
}[] | ||
} | ||
/** | ||
* 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. | ||
@@ -112,5 +151,10 @@ */ | ||
withdrawals?: Array<WithdrawalData> | ||
/** | ||
* EIP-6800: Verkle Proof Data (experimental) | ||
*/ | ||
executionWitness?: VerkleExecutionWitness | null | ||
} | ||
export type WithdrawalsBytes = WithdrawalBytes[] | ||
export type ExecutionWitnessBytes = Uint8Array | ||
@@ -120,2 +164,13 @@ export type BlockBytes = | ||
| [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[] | ||
@@ -140,2 +195,3 @@ export type BlockBodyBytes = [TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes?] | ||
withdrawals?: JsonRpcWithdrawal[] | ||
executionWitness?: VerkleExecutionWitness | null | ||
} | ||
@@ -199,2 +255,3 @@ | ||
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 | ||
} | ||
@@ -230,2 +287,4 @@ | ||
parentBeaconBlockRoot?: PrefixedHexString // QUANTITY, 64 Bits | ||
// VerkleExecutionWitness is already a hex serialized object | ||
executionWitness?: VerkleExecutionWitness | null // QUANTITY, 64 Bits, null imples not available | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
531654
7038
371
Updated@ethereumjs/common@^4.2.0
Updated@ethereumjs/rlp@^5.0.2
Updated@ethereumjs/trie@^6.1.0
Updated@ethereumjs/tx@^5.2.0
Updated@ethereumjs/util@^9.0.2
Updatedethereum-cryptography@^2.1.3