@ethereumjs/block
Advanced tools
Comparing version 4.3.0 to 5.0.0-rc.1
{ | ||
"name": "@ethereumjs/block", | ||
"version": "4.3.0", | ||
"version": "5.0.0-rc.1", | ||
"description": "Provides Block serialization and help functions", | ||
@@ -19,4 +19,11 @@ "keywords": [ | ||
"author": "mjbecze (mb@ethdev.com)", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"type": "commonjs", | ||
"main": "dist/cjs/index.js", | ||
"module": "dist/esm/index.js", | ||
"exports": { | ||
".": { | ||
"import": "./dist/esm/index.js", | ||
"require": "./dist/cjs/index.js" | ||
} | ||
}, | ||
"files": [ | ||
@@ -29,4 +36,5 @@ "dist", | ||
"clean": "../../config/cli/clean-package.sh", | ||
"coverage": "../../config/cli/coverage.sh", | ||
"docs:build": "typedoc --options typedoc.js", | ||
"coverage": "npx vitest run --coverage.enabled --coverage.reporter=lcov", | ||
"docs:build": "typedoc --options typedoc.cjs", | ||
"examples": "ts-node ../../scripts/examples-runner.ts -- block", | ||
"lint": "../../config/cli/lint.sh", | ||
@@ -36,23 +44,21 @@ "lint:diff": "../../config/cli/lint-diff.sh", | ||
"prepublishOnly": "../../config/cli/prepublish.sh", | ||
"tape": "tape -r ts-node/register", | ||
"test": "npm run test:node && npm run test:browser", | ||
"test:browser": "karma start karma.conf.js", | ||
"test:node": "npm run tape -- test/*.spec.ts", | ||
"test:browser": "npx vitest run --config=vitest.config.browser.ts --browser.name=webkit --browser.provider=playwright --browser.headless", | ||
"test:node": "npx vitest run", | ||
"tsc": "../../config/cli/ts-compile.sh" | ||
}, | ||
"dependencies": { | ||
"@ethereumjs/common": "^3.2.0", | ||
"@ethereumjs/rlp": "^4.0.1", | ||
"@ethereumjs/trie": "^5.1.0", | ||
"@ethereumjs/tx": "^4.2.0", | ||
"@ethereumjs/util": "^8.1.0", | ||
"ethereum-cryptography": "^2.0.0" | ||
"@ethereumjs/common": "4.0.0-rc.1", | ||
"@ethereumjs/rlp": "5.0.0-rc.1", | ||
"@ethereumjs/trie": "6.0.0-rc.1", | ||
"@ethereumjs/tx": "5.0.0-rc.1", | ||
"@ethereumjs/util": "9.0.0-rc.1", | ||
"ethereum-cryptography": "^2.1.2" | ||
}, | ||
"devDependencies": { | ||
"@types/lru-cache": "^5.1.0", | ||
"testdouble": "^3.17.2" | ||
"c-kzg": "^2.1.0" | ||
}, | ||
"engines": { | ||
"node": ">=14" | ||
"node": ">=18" | ||
} | ||
} |
@@ -9,2 +9,4 @@ # @ethereumjs/block | ||
Note: this README has been updated containing the changes from our next breaking release round [UNRELEASED] targeted for Summer 2023. See the README files from the [maintenance-v6](https://github.com/ethereumjs/ethereumjs-monorepo/tree/maintenance-v6/) branch for documentation matching our latest releases. | ||
| Implements schema and functions related to Ethereum's block. | | ||
@@ -32,6 +34,6 @@ | ------------------------------------------------------------ | | ||
- `Block.fromBlockData(blockData: BlockData = {}, opts?: BlockOptions)` | ||
- `Block.fromRLPSerializedBlock(serialized: Buffer, opts?: BlockOptions)` | ||
- `Block.fromValuesArray(values: BlockBuffer, opts?: BlockOptions)` | ||
- `Block.fromRLPSerializedBlock(serialized: Uint8Array, opts?: BlockOptions)` | ||
- `Block.fromValuesArray(values: BlockBytes, opts?: BlockOptions)` | ||
- `Block.fromRPC(blockData: JsonRpcBlock, uncles?: any[], opts?: BlockOptions)` | ||
- `Block.fromEthersProvider(provider: ethers.providers.JsonRpcProvider | string, blockTag: string | bigint, opts: BlockOptions)` | ||
- `Block.fromJsonRpcProvider(provider: string | EthersProvider, blockTag: string | bigint, opts: BlockOptions)` | ||
@@ -107,7 +109,7 @@ For `BlockHeader` instantiation analog factory methods exists, see API docs linked below. | ||
EIP-1559 blocks have an extra `baseFeePerGas` field (default: `BigInt(7)`) and can encompass `FeeMarketEIP1559Transaction` txs (type `2`) (supported by `@ethereumjs/tx` `v3.2.0` or higher) as well as `Transaction` legacy txs (internal type `0`) and `AccessListEIP2930Transaction` txs (type `1`). | ||
EIP-1559 blocks have an extra `baseFeePerGas` field (default: `BigInt(7)`) and can encompass `FeeMarketEIP1559Transaction` txs (type `2`) (supported by `@ethereumjs/tx` `v3.2.0` or higher) as well as `LegacyTransaction` legacy txs (internal type `0`) and `AccessListEIP2930Transaction` txs (type `1`). | ||
### EIP-4895 Beacon Chain Withdrawals Blocks (experimental) | ||
### EIP-4895 Beacon Chain Withdrawals Blocks | ||
Starting with the `v4.1.0` release there is (experimental) support for [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895) beacon chain withdrawals. Withdrawals support can be activated by initializing a respective `Common` object and then use the `withdrawals` data option to pass in system-level withdrawal operations together with a matching `withdrawalsRoot` (mandatory when `EIP-4895` is activated) along Block creation, see the following example: | ||
Starting with the `v4.1.0` release there is support for [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895) beacon chain withdrawals. Withdrawals support can be activated by initializing a `Common` object with a hardfork set to `shanghai` (default) or higher and then use the `withdrawals` data option to pass in system-level withdrawal operations together with a matching `withdrawalsRoot` (mandatory when `EIP-4895` is activated) along Block creation, see the following example: | ||
@@ -117,6 +119,6 @@ ```typescript | ||
import { Common, Chain } from '@ethereumjs/common' | ||
import { Address } from '@ethereumjs/util' | ||
import { Address, hexToBytes } from '@ethereumjs/util' | ||
import type { WithdrawalData } from '@ethereumjs/util' | ||
const common = new Common({ chain: Chain.Mainnet, eips: [4895] }) | ||
const common = new Common({ chain: Chain.Mainnet }) | ||
@@ -126,3 +128,3 @@ const withdrawal = <WithdrawalData>{ | ||
validatorIndex: BigInt(0), | ||
address: new Address(Buffer.from('20'.repeat(20), 'hex')), | ||
address: new Address(hexToBytes(`0x${'20'.repeat(20)}`)), | ||
amount: BigInt(1000), | ||
@@ -134,5 +136,4 @@ } | ||
header: { | ||
withdrawalsRoot: Buffer.from( | ||
'69f28913c562b0d38f8dc81e72eb0d99052444d301bf8158dc1f3f94a4526357', | ||
'hex' | ||
withdrawalsRoot: hexToBytes( | ||
'0x69f28913c562b0d38f8dc81e72eb0d99052444d301bf8158dc1f3f94a4526357' | ||
), | ||
@@ -148,8 +149,10 @@ }, | ||
Validation of the withdrawals trie can be manually triggered with the newly introduced async `Block.validateWithdrawalsTrie()` method. | ||
Validation of the withdrawals trie can be manually triggered with the newly introduced async `Block.withdrawalsTrieIsValid()` method. | ||
### EIP-4844 Shard Blob Transaction Blocks (experimental) | ||
### EIP-4844 Shard Blob Transaction Blocks | ||
This library supports an experimental version of the blob transaction type introduced with [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) as being specified in the [01d3209](https://github.com/ethereum/EIPs/commit/01d320998d1d53d95f347b5f43feaf606f230703) EIP version from February 8, 2023 and deployed along `eip4844-devnet-4` (January 2023) starting with `v4.2.0`. | ||
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. | ||
**Note:** 4844 support is not yet completely stable and there will still be (4844-)breaking changes along all types of library releases. | ||
#### Initialization | ||
@@ -214,4 +217,4 @@ | ||
- `BlockHeader.cliqueIsEpochTransition(): boolean` | ||
- `BlockHeader.cliqueExtraVanity(): Buffer` | ||
- `BlockHeader.cliqueExtraSeal(): Buffer` | ||
- `BlockHeader.cliqueExtraVanity(): Uint8Array` | ||
- `BlockHeader.cliqueExtraSeal(): Uint8Array` | ||
- `BlockHeader.cliqueEpochTransitionSigners(): Address[]` | ||
@@ -232,3 +235,3 @@ - `BlockHeader.cliqueVerifySignature(signerList: Address[]): boolean` | ||
import { Chain, Common, Hardfork } from '@ethereumjs/common' | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Merge }) | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Paris }) | ||
const block = Block.fromBlockData( | ||
@@ -242,2 +245,8 @@ { | ||
## Browser | ||
With the breaking release round in Summer 2023 we have added hybrid ESM/CJS builds for all our libraries (see section below) and have eliminated many of the caveats which had previously prevented a frictionless browser usage. | ||
It is now easily possible to run a browser build of one of the EthereumJS libraries within a modern browser using the provided ESM build. For a setup example see [./examples/browser.html](./examples/browser.html). | ||
## API | ||
@@ -249,2 +258,26 @@ | ||
### Hybrid CJS/ESM Builds | ||
With the breaking releases from Summer 2023 we have started to ship our libraries with both CommonJS (`cjs` folder) and ESM builds (`esm` folder), see `package.json` for the detailed setup. | ||
If you use an ES6-style `import` in your code files from the ESM build will be used: | ||
```typescript | ||
import { EthereumJSClass } from '@ethereumjs/[PACKAGE_NAME]' | ||
``` | ||
If you use Node.js specific `require` the CJS build will be used: | ||
```typescript | ||
const { EthereumJSClass } = require('@ethereumjs/[PACKAGE_NAME]') | ||
``` | ||
Using ESM will give you additional advantages over CJS beyond browser usage like static code analysis / Tree Shaking which CJS can not provide. | ||
### Buffer -> Uint8Array | ||
With the breaking releases from Summer 2023 we have removed all Node.js specific `Buffer` usages from our libraries and replace these with [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) representations, which are available both in Node.js and the browser (`Buffer` is a subclass of `Uint8Array`). | ||
We have converted existing Buffer conversion methods to Uint8Array conversion methods in the [@ethereumjs/util](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/util) `bytes` module, see the respective README section for guidance. | ||
### BigInt Support | ||
@@ -251,0 +284,0 @@ |
346
src/block.ts
import { ConsensusType } from '@ethereumjs/common' | ||
import { RLP } from '@ethereumjs/rlp' | ||
import { Trie } from '@ethereumjs/trie' | ||
import { Capability, TransactionFactory } from '@ethereumjs/tx' | ||
import { BlobEIP4844Transaction, Capability, TransactionFactory } from '@ethereumjs/tx' | ||
import { | ||
KECCAK256_RLP, | ||
Withdrawal, | ||
arrToBufArr, | ||
bigIntToHex, | ||
bufArrToArr, | ||
bufferToHex, | ||
bytesToHex, | ||
equalsBytes, | ||
fetchFromProvider, | ||
getProvider, | ||
hexToBytes, | ||
intToHex, | ||
isHexPrefixed, | ||
} from '@ethereumjs/util' | ||
import { keccak256 } from 'ethereum-cryptography/keccak' | ||
import { keccak256 } from 'ethereum-cryptography/keccak.js' | ||
import { blockFromRpc } from './from-rpc' | ||
import { BlockHeader } from './header' | ||
import { executionPayloadFromBeaconPayload } from './from-beacon-payload.js' | ||
import { blockFromRpc } from './from-rpc.js' | ||
import { BlockHeader } from './header.js' | ||
import type { BlockBuffer, BlockData, BlockOptions, JsonBlock, JsonRpcBlock } from './types' | ||
import type { BeaconPayloadJson } from './from-beacon-payload.js' | ||
import type { | ||
BlockBytes, | ||
BlockData, | ||
BlockOptions, | ||
ExecutionPayload, | ||
HeaderData, | ||
JsonBlock, | ||
JsonRpcBlock, | ||
} from './types.js' | ||
import type { Common } from '@ethereumjs/common' | ||
import type { | ||
FeeMarketEIP1559Transaction, | ||
Transaction, | ||
LegacyTransaction, | ||
TxOptions, | ||
TypedTransaction, | ||
} from '@ethereumjs/tx' | ||
import type { WithdrawalBuffer } from '@ethereumjs/util' | ||
import type { EthersProvider, WithdrawalBytes } from '@ethereumjs/util' | ||
@@ -41,3 +51,3 @@ /** | ||
public readonly txTrie = new Trie() | ||
public readonly _common: Common | ||
public readonly common: Common | ||
@@ -52,3 +62,3 @@ /** | ||
for (const [i, wt] of wts.entries()) { | ||
await trie.put(Buffer.from(RLP.encode(i)), arrToBufArr(RLP.encode(wt.raw()))) | ||
await trie.put(RLP.encode(i), RLP.encode(wt.raw())) | ||
} | ||
@@ -66,3 +76,3 @@ return trie.root() | ||
for (const [i, tx] of txs.entries()) { | ||
await trie.put(Buffer.from(RLP.encode(i)), tx.serialize()) | ||
await trie.put(RLP.encode(i), tx.serialize()) | ||
} | ||
@@ -92,4 +102,4 @@ return trie.root() | ||
...opts, | ||
// Use header common in case of hardforkByBlockNumber being activated | ||
common: header._common, | ||
// Use header common in case of setHardfork being activated | ||
common: header.common, | ||
} as TxOptions) | ||
@@ -102,14 +112,11 @@ transactions.push(tx) | ||
const uncleOpts: BlockOptions = { | ||
hardforkByBlockNumber: true, | ||
...opts, | ||
// Use header common in case of hardforkByBlockNumber being activated | ||
common: header._common, | ||
// Use header common in case of setHardfork being activated | ||
common: header.common, | ||
// Disable this option here (all other options carried over), since this overwrites the provided Difficulty to an incorrect value | ||
calcDifficultyFromHeader: undefined, | ||
// This potentially overwrites hardforkBy options but we will set them cleanly just below | ||
hardforkByTTD: undefined, | ||
} | ||
// Uncles are obsolete post-merge, any hardfork by option implies hardforkByBlockNumber | ||
if (opts?.hardforkByTTD !== undefined) { | ||
uncleOpts.hardforkByBlockNumber = true | ||
// Uncles are obsolete post-merge, any hardfork by option implies setHardfork | ||
if (opts?.setHardfork !== undefined) { | ||
uncleOpts.setHardfork = true | ||
} | ||
@@ -123,3 +130,3 @@ for (const uhData of uhsData ?? []) { | ||
return new Block(header, transactions, uncleHeaders, opts, withdrawals) | ||
return new Block(header, transactions, uncleHeaders, withdrawals, opts) | ||
} | ||
@@ -133,4 +140,4 @@ | ||
*/ | ||
public static fromRLPSerializedBlock(serialized: Buffer, opts?: BlockOptions) { | ||
const values = arrToBufArr(RLP.decode(Uint8Array.from(serialized))) as BlockBuffer | ||
public static fromRLPSerializedBlock(serialized: Uint8Array, opts?: BlockOptions) { | ||
const values = RLP.decode(Uint8Array.from(serialized)) as BlockBytes | ||
@@ -145,3 +152,3 @@ if (!Array.isArray(values)) { | ||
/** | ||
* Static constructor to create a block from an array of Buffer values | ||
* Static constructor to create a block from an array of Bytes values | ||
* | ||
@@ -151,9 +158,14 @@ * @param values | ||
*/ | ||
public static fromValuesArray(values: BlockBuffer, opts?: BlockOptions) { | ||
public static fromValuesArray(values: BlockBytes, opts?: BlockOptions) { | ||
if (values.length > 4) { | ||
throw new Error('invalid block. More values 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 header = BlockHeader.fromValuesArray(headerData, opts) | ||
if ( | ||
opts?.common !== undefined && | ||
opts?.common?.isActivatedEIP(4895) && | ||
header.common.isActivatedEIP(4895) && | ||
(values[3] === undefined || !Array.isArray(values[3])) | ||
@@ -166,6 +178,2 @@ ) { | ||
const [headerData, txsData, uhsData, withdrawalsBuffer] = values | ||
const header = BlockHeader.fromValuesArray(headerData, opts) | ||
// parse transactions | ||
@@ -177,4 +185,4 @@ const transactions = [] | ||
...opts, | ||
// Use header common in case of hardforkByBlockNumber being activated | ||
common: header._common, | ||
// Use header common in case of setHardfork being activated | ||
common: header.common, | ||
}) | ||
@@ -187,14 +195,11 @@ ) | ||
const uncleOpts: BlockOptions = { | ||
hardforkByBlockNumber: true, | ||
...opts, | ||
// Use header common in case of hardforkByBlockNumber being activated | ||
common: header._common, | ||
// Use header common in case of setHardfork being activated | ||
common: header.common, | ||
// Disable this option here (all other options carried over), since this overwrites the provided Difficulty to an incorrect value | ||
calcDifficultyFromHeader: undefined, | ||
// This potentially overwrites hardforkBy options but we will set them cleanly just below | ||
hardforkByTTD: undefined, | ||
} | ||
// Uncles are obsolete post-merge, any hardfork by option implies hardforkByBlockNumber | ||
if (opts?.hardforkByTTD !== undefined) { | ||
uncleOpts.hardforkByBlockNumber = true | ||
// Uncles are obsolete post-merge, any hardfork by option implies setHardfork | ||
if (opts?.setHardfork !== undefined) { | ||
uncleOpts.setHardfork = true | ||
} | ||
@@ -205,3 +210,3 @@ for (const uncleHeaderData of uhsData ?? []) { | ||
const withdrawals = (withdrawalsBuffer as WithdrawalBuffer[]) | ||
const withdrawals = (withdrawalBytes as WithdrawalBytes[]) | ||
?.map(([index, validatorIndex, address, amount]) => ({ | ||
@@ -215,3 +220,3 @@ index, | ||
return new Block(header, transactions, uncleHeaders, opts, withdrawals) | ||
return new Block(header, transactions, uncleHeaders, withdrawals, opts) | ||
} | ||
@@ -231,4 +236,4 @@ | ||
/** | ||
* Method to retrieve a block from the provider and format as a {@link Block} | ||
* @param provider an Ethers JsonRPCProvider | ||
* Method to retrieve a block from a JSON-RPC provider and format as a {@link Block} | ||
* @param provider either a url for a remote provider or an Ethers JsonRpcProvider object | ||
* @param blockTag block hash or block number to be run | ||
@@ -238,4 +243,4 @@ * @param opts {@link BlockOptions} | ||
*/ | ||
public static fromEthersProvider = async ( | ||
provider: any, | ||
public static fromJsonRpcProvider = async ( | ||
provider: string | EthersProvider, | ||
blockTag: string | bigint, | ||
@@ -261,3 +266,5 @@ opts: BlockOptions | ||
blockTag === 'earliest' || | ||
blockTag === 'pending' | ||
blockTag === 'pending' || | ||
blockTag === 'finalized' || | ||
blockTag === 'safe' | ||
) { | ||
@@ -293,2 +300,76 @@ blockData = await fetchFromProvider(providerUrl, { | ||
/** | ||
* Method to retrieve a block from an execution payload | ||
* @param execution payload constructed from beacon payload | ||
* @param opts {@link BlockOptions} | ||
* @returns the block constructed block | ||
*/ | ||
public static async fromExecutionPayload( | ||
payload: ExecutionPayload, | ||
options?: BlockOptions | ||
): Promise<Block> { | ||
const { | ||
blockNumber: number, | ||
receiptsRoot: receiptTrie, | ||
prevRandao: mixHash, | ||
feeRecipient: coinbase, | ||
transactions, | ||
withdrawals: withdrawalsData, | ||
} = payload | ||
const txs = [] | ||
for (const [index, serializedTx] of transactions.entries()) { | ||
try { | ||
const tx = TransactionFactory.fromSerializedData(hexToBytes(serializedTx), { | ||
common: options?.common, | ||
}) | ||
txs.push(tx) | ||
} catch (error) { | ||
const validationError = `Invalid tx at index ${index}: ${error}` | ||
throw validationError | ||
} | ||
} | ||
const transactionsTrie = await Block.genTransactionsTrieRoot(txs) | ||
const withdrawals = withdrawalsData?.map((wData) => Withdrawal.fromWithdrawalData(wData)) | ||
const withdrawalsRoot = withdrawals | ||
? await Block.genWithdrawalsTrieRoot(withdrawals) | ||
: undefined | ||
const header: HeaderData = { | ||
...payload, | ||
number, | ||
receiptTrie, | ||
transactionsTrie, | ||
withdrawalsRoot, | ||
mixHash, | ||
coinbase, | ||
} | ||
// we are not setting setHardfork as common is already set to the correct hf | ||
const block = Block.fromBlockData({ header, transactions: txs, withdrawals }, options) | ||
// Verify blockHash matches payload | ||
if (!equalsBytes(block.hash(), hexToBytes(payload.blockHash))) { | ||
const validationError = `Invalid blockHash, expected: ${ | ||
payload.blockHash | ||
}, received: ${bytesToHex(block.hash())}` | ||
throw Error(validationError) | ||
} | ||
return block | ||
} | ||
/** | ||
* Method to retrieve a block from a beacon payload json | ||
* @param payload json of a beacon beacon fetched from beacon apis | ||
* @param opts {@link BlockOptions} | ||
* @returns the block constructed block | ||
*/ | ||
public static async fromBeaconPayloadJson( | ||
payload: BeaconPayloadJson, | ||
options?: BlockOptions | ||
): Promise<Block> { | ||
const executionPayload = executionPayloadFromBeaconPayload(payload) | ||
return Block.fromExecutionPayload(executionPayload, options) | ||
} | ||
/** | ||
* This constructor takes the values, validates them, assigns them and freezes the object. | ||
@@ -301,14 +382,14 @@ * Use the static factory methods to assist in creating a Block object from varying data types and options. | ||
uncleHeaders: BlockHeader[] = [], | ||
opts: BlockOptions = {}, | ||
withdrawals?: Withdrawal[] | ||
withdrawals?: Withdrawal[], | ||
opts: BlockOptions = {} | ||
) { | ||
this.header = header ?? BlockHeader.fromHeaderData({}, opts) | ||
this._common = this.header._common | ||
this.common = this.header.common | ||
this.transactions = transactions | ||
this.withdrawals = withdrawals ?? (this._common.isActivatedEIP(4895) ? [] : undefined) | ||
this.withdrawals = withdrawals ?? (this.common.isActivatedEIP(4895) ? [] : undefined) | ||
this.uncleHeaders = uncleHeaders | ||
if (uncleHeaders.length > 0) { | ||
this.validateUncles() | ||
if (this._common.consensusType() === ConsensusType.ProofOfAuthority) { | ||
if (this.common.consensusType() === ConsensusType.ProofOfAuthority) { | ||
const msg = this._errorMsg( | ||
@@ -319,3 +400,3 @@ 'Block initialization with uncleHeaders on a PoA network is not allowed' | ||
} | ||
if (this._common.consensusType() === ConsensusType.ProofOfStake) { | ||
if (this.common.consensusType() === ConsensusType.ProofOfStake) { | ||
const msg = this._errorMsg( | ||
@@ -328,3 +409,3 @@ 'Block initialization with uncleHeaders on a PoS network is not allowed' | ||
if (!this._common.isActivatedEIP(4895) && withdrawals !== undefined) { | ||
if (!this.common.isActivatedEIP(4895) && withdrawals !== undefined) { | ||
throw new Error('Cannot have a withdrawals field if EIP 4895 is not active') | ||
@@ -340,10 +421,10 @@ } | ||
/** | ||
* Returns a Buffer Array of the raw Buffers of this block, in order. | ||
* Returns a Array of the raw Bytes Arays of this block, in order. | ||
*/ | ||
raw(): BlockBuffer { | ||
const bufferArray = <BlockBuffer>[ | ||
raw(): BlockBytes { | ||
const bytesArray = <BlockBytes>[ | ||
this.header.raw(), | ||
this.transactions.map((tx) => | ||
tx.supports(Capability.EIP2718TypedTransaction) ? tx.serialize() : tx.raw() | ||
) as Buffer[], | ||
) as Uint8Array[], | ||
this.uncleHeaders.map((uh) => uh.raw()), | ||
@@ -353,5 +434,5 @@ ] | ||
if (withdrawalsRaw) { | ||
bufferArray.push(withdrawalsRaw) | ||
bytesArray.push(withdrawalsRaw) | ||
} | ||
return bufferArray | ||
return bytesArray | ||
} | ||
@@ -362,3 +443,3 @@ | ||
*/ | ||
hash(): Buffer { | ||
hash(): Uint8Array { | ||
return this.header.hash() | ||
@@ -377,4 +458,4 @@ } | ||
*/ | ||
serialize(): Buffer { | ||
return Buffer.from(RLP.encode(bufArrToArr(this.raw()))) | ||
serialize(): Uint8Array { | ||
return RLP.encode(this.raw()) | ||
} | ||
@@ -393,14 +474,15 @@ | ||
* and do a check on the root hash. | ||
* @returns True if the transaction trie is valid, false otherwise | ||
*/ | ||
async validateTransactionsTrie(): Promise<boolean> { | ||
async transactionsTrieIsValid(): Promise<boolean> { | ||
let result | ||
if (this.transactions.length === 0) { | ||
result = this.header.transactionsTrie.equals(KECCAK256_RLP) | ||
result = equalsBytes(this.header.transactionsTrie, KECCAK256_RLP) | ||
return result | ||
} | ||
if (this.txTrie.root().equals(KECCAK256_RLP)) { | ||
if (equalsBytes(this.txTrie.root(), KECCAK256_RLP)) { | ||
await this.genTxTrie() | ||
} | ||
result = this.txTrie.root().equals(this.header.transactionsTrie) | ||
result = equalsBytes(this.txTrie.root(), this.header.transactionsTrie) | ||
return result | ||
@@ -411,15 +493,14 @@ } | ||
* Validates transaction signatures and minimum gas requirements. | ||
* | ||
* @param stringError - If `true`, a string with the indices of the invalid txs is returned. | ||
* @returns {string[]} an array of error strings | ||
*/ | ||
validateTransactions(): boolean | ||
validateTransactions(stringError: false): boolean | ||
validateTransactions(stringError: true): string[] | ||
validateTransactions(stringError = false) { | ||
getTransactionsValidationErrors(): string[] { | ||
const errors: string[] = [] | ||
let dataGasUsed = BigInt(0) | ||
const dataGasLimit = this.common.param('gasConfig', 'maxDataGasPerBlock') | ||
const dataGasPerBlob = this.common.param('gasConfig', 'dataGasPerBlob') | ||
// eslint-disable-next-line prefer-const | ||
for (let [i, tx] of this.transactions.entries()) { | ||
const errs = <string[]>tx.validate(true) | ||
if (this._common.isActivatedEIP(1559) === true) { | ||
const errs = tx.getValidationErrors() | ||
if (this.common.isActivatedEIP(1559) === true) { | ||
if (tx.supports(Capability.EIP1559FeeMarket)) { | ||
@@ -431,3 +512,3 @@ tx = tx as FeeMarketEIP1559Transaction | ||
} else { | ||
tx = tx as Transaction | ||
tx = tx as LegacyTransaction | ||
if (tx.gasPrice < this.header.baseFeePerGas!) { | ||
@@ -438,2 +519,12 @@ errs.push('tx unable to pay base fee (non EIP-1559 tx)') | ||
} | ||
if (this.common.isActivatedEIP(4844) === true) { | ||
if (tx instanceof BlobEIP4844Transaction) { | ||
dataGasUsed += BigInt(tx.numBlobs()) * dataGasPerBlob | ||
if (dataGasUsed > dataGasLimit) { | ||
errs.push( | ||
`tx causes total data gas of ${dataGasUsed} to exceed maximum data gas per block of ${dataGasLimit}` | ||
) | ||
} | ||
} | ||
} | ||
if (errs.length > 0) { | ||
@@ -444,6 +535,22 @@ errors.push(`errors at tx ${i}: ${errs.join(', ')}`) | ||
return stringError ? errors : errors.length === 0 | ||
if (this.common.isActivatedEIP(4844) === true) { | ||
if (dataGasUsed !== this.header.dataGasUsed) { | ||
errors.push(`invalid dataGasUsed expected=${this.header.dataGasUsed} actual=${dataGasUsed}`) | ||
} | ||
} | ||
return errors | ||
} | ||
/** | ||
* Validates transaction signatures and minimum gas requirements. | ||
* @returns True if all transactions are valid, false otherwise | ||
*/ | ||
transactionsAreValid(): boolean { | ||
const errors = this.getTransactionsValidationErrors() | ||
return errors.length === 0 | ||
} | ||
/** | ||
* Validates the block data, throwing if invalid. | ||
@@ -458,3 +565,3 @@ * This can be checked on the Block itself without needing access to any parent block | ||
async validateData(onlyHeader: boolean = false): Promise<void> { | ||
const txErrors = this.validateTransactions(true) | ||
const txErrors = this.getTransactionsValidationErrors() | ||
if (txErrors.length > 0) { | ||
@@ -469,4 +576,3 @@ const msg = this._errorMsg(`invalid transactions: ${txErrors.join(' ')}`) | ||
const validateTxTrie = await this.validateTransactionsTrie() | ||
if (!validateTxTrie) { | ||
if (!(await this.transactionsTrieIsValid())) { | ||
const msg = this._errorMsg('invalid transaction trie') | ||
@@ -476,3 +582,3 @@ throw new Error(msg) | ||
if (!this.validateUnclesHash()) { | ||
if (!this.uncleHashIsValid()) { | ||
const msg = this._errorMsg('invalid uncle hash') | ||
@@ -482,3 +588,3 @@ throw new Error(msg) | ||
if (this._common.isActivatedEIP(4895) && !(await this.validateWithdrawalsTrie())) { | ||
if (this.common.isActivatedEIP(4895) && !(await this.withdrawalsTrieIsValid())) { | ||
const msg = this._errorMsg('invalid withdrawals trie') | ||
@@ -490,8 +596,57 @@ throw new Error(msg) | ||
/** | ||
* Validates that data gas fee for each transaction is greater than or equal to the | ||
* dataGasPrice for the block and that total data gas in block is less than maximum | ||
* data gas per block | ||
* @param parentHeader header of parent block | ||
*/ | ||
validateBlobTransactions(parentHeader: BlockHeader) { | ||
if (this.common.isActivatedEIP(4844)) { | ||
const dataGasLimit = this.common.param('gasConfig', 'maxDataGasPerBlock') | ||
const dataGasPerBlob = this.common.param('gasConfig', 'dataGasPerBlob') | ||
let dataGasUsed = BigInt(0) | ||
for (const tx of this.transactions) { | ||
if (tx instanceof BlobEIP4844Transaction) { | ||
const dataGasPrice = this.header.getDataGasPrice() | ||
if (tx.maxFeePerDataGas < dataGasPrice) { | ||
throw new Error( | ||
`blob transaction maxFeePerDataGas ${ | ||
tx.maxFeePerDataGas | ||
} < than block data gas price ${dataGasPrice} - ${this.errorStr()}` | ||
) | ||
} | ||
dataGasUsed += BigInt(tx.versionedHashes.length) * dataGasPerBlob | ||
if (dataGasUsed > dataGasLimit) { | ||
throw new Error( | ||
`tx causes total data gas of ${dataGasUsed} to exceed maximum data gas per block of ${dataGasLimit}` | ||
) | ||
} | ||
} | ||
} | ||
if (this.header.dataGasUsed !== dataGasUsed) { | ||
throw new Error( | ||
`block dataGasUsed mismatch: have ${this.header.dataGasUsed}, want ${dataGasUsed}` | ||
) | ||
} | ||
const expectedExcessDataGas = parentHeader.calcNextExcessDataGas() | ||
if (this.header.excessDataGas !== expectedExcessDataGas) { | ||
throw new Error( | ||
`block excessDataGas mismatch: have ${this.header.excessDataGas}, want ${expectedExcessDataGas}` | ||
) | ||
} | ||
} | ||
} | ||
/** | ||
* Validates the uncle's hash. | ||
* @returns true if the uncle's hash is valid, false otherwise. | ||
*/ | ||
validateUnclesHash(): boolean { | ||
uncleHashIsValid(): boolean { | ||
const uncles = this.uncleHeaders.map((uh) => uh.raw()) | ||
const raw = RLP.encode(bufArrToArr(uncles)) | ||
return Buffer.from(keccak256(raw)).equals(this.header.uncleHash) | ||
const raw = RLP.encode(uncles) | ||
return equalsBytes(keccak256(raw), this.header.uncleHash) | ||
} | ||
@@ -501,9 +656,10 @@ | ||
* Validates the withdrawal root | ||
* @returns true if the withdrawals trie root is valid, false otherwise | ||
*/ | ||
async validateWithdrawalsTrie(): Promise<boolean> { | ||
if (!this._common.isActivatedEIP(4895)) { | ||
async withdrawalsTrieIsValid(): Promise<boolean> { | ||
if (!this.common.isActivatedEIP(4895)) { | ||
throw new Error('EIP 4895 is not activated') | ||
} | ||
const withdrawalsRoot = await Block.genWithdrawalsTrieRoot(this.withdrawals!) | ||
return withdrawalsRoot.equals(this.header.withdrawalsRoot!) | ||
return equalsBytes(withdrawalsRoot, this.header.withdrawalsRoot!) | ||
} | ||
@@ -532,3 +688,3 @@ | ||
// Header does not count an uncle twice. | ||
const uncleHashes = this.uncleHeaders.map((header) => header.hash().toString('hex')) | ||
const uncleHashes = this.uncleHeaders.map((header) => bytesToHex(header.hash())) | ||
if (!(new Set(uncleHashes).size === uncleHashes.length)) { | ||
@@ -582,3 +738,3 @@ const msg = this._errorMsg('duplicate uncles') | ||
try { | ||
hash = bufferToHex(this.hash()) | ||
hash = bytesToHex(this.hash()) | ||
} catch (e: any) { | ||
@@ -589,3 +745,3 @@ hash = 'error' | ||
try { | ||
hf = this._common.hardfork() | ||
hf = this.common.hardfork() | ||
} catch (e: any) { | ||
@@ -592,0 +748,0 @@ hf = 'error' |
import { TransactionFactory } from '@ethereumjs/tx' | ||
import { TypeOutput, setLengthLeft, toBuffer, toType } from '@ethereumjs/util' | ||
import { TypeOutput, setLengthLeft, toBytes, toType } from '@ethereumjs/util' | ||
import { blockHeaderFromRpc } from './header-from-rpc' | ||
import { blockHeaderFromRpc } from './header-from-rpc.js' | ||
import { Block } from './index' | ||
import { Block } from './index.js' | ||
import type { BlockOptions, JsonRpcBlock } from './index' | ||
import type { TxData, TypedTransaction } from '@ethereumjs/tx' | ||
import type { BlockOptions, JsonRpcBlock } from './index.js' | ||
import type { TypedTransaction } from '@ethereumjs/tx' | ||
@@ -24,3 +24,3 @@ function normalizeTxParams(_txParams: any) { | ||
txParams.to !== null && txParams.to !== undefined | ||
? setLengthLeft(toBuffer(txParams.to), 20) | ||
? setLengthLeft(toBytes(txParams.to), 20) | ||
: null | ||
@@ -49,6 +49,6 @@ | ||
const transactions: TypedTransaction[] = [] | ||
const opts = { common: header._common } | ||
const opts = { common: header.common } | ||
for (const _txParams of blockParams.transactions ?? []) { | ||
const txParams = normalizeTxParams(_txParams) | ||
const tx = TransactionFactory.fromTxData(txParams as TxData, opts) | ||
const tx = TransactionFactory.fromTxData(txParams, opts) | ||
transactions.push(tx) | ||
@@ -55,0 +55,0 @@ } |
@@ -1,5 +0,5 @@ | ||
import { BlockHeader } from './header' | ||
import { numberToHex } from './helpers' | ||
import { BlockHeader } from './header.js' | ||
import { numberToHex } from './helpers.js' | ||
import type { BlockOptions, JsonRpcBlock } from './types' | ||
import type { BlockOptions, JsonRpcBlock } from './types.js' | ||
@@ -31,2 +31,4 @@ /** | ||
withdrawalsRoot, | ||
dataGasUsed, | ||
excessDataGas, | ||
} = blockParams | ||
@@ -53,2 +55,4 @@ | ||
withdrawalsRoot, | ||
dataGasUsed, | ||
excessDataGas, | ||
}, | ||
@@ -55,0 +59,0 @@ options |
@@ -8,24 +8,26 @@ import { Chain, Common, ConsensusAlgorithm, ConsensusType, Hardfork } from '@ethereumjs/common' | ||
TypeOutput, | ||
arrToBufArr, | ||
bigIntToBuffer, | ||
bigIntToBytes, | ||
bigIntToHex, | ||
bigIntToUnpaddedBuffer, | ||
bufArrToArr, | ||
bufferToBigInt, | ||
bufferToHex, | ||
bigIntToUnpaddedBytes, | ||
bytesToBigInt, | ||
bytesToHex, | ||
concatBytes, | ||
ecrecover, | ||
ecsign, | ||
equalsBytes, | ||
hexToBytes, | ||
toType, | ||
zeros, | ||
} from '@ethereumjs/util' | ||
import { keccak256 } from 'ethereum-cryptography/keccak' | ||
import { keccak256 } from 'ethereum-cryptography/keccak.js' | ||
import { CLIQUE_EXTRA_SEAL, CLIQUE_EXTRA_VANITY } from './clique' | ||
import { valuesArrayToHeaderData } from './helpers' | ||
import { CLIQUE_EXTRA_SEAL, CLIQUE_EXTRA_VANITY } from './clique.js' | ||
import { fakeExponential, valuesArrayToHeaderData } from './helpers.js' | ||
import type { BlockHeaderBuffer, BlockOptions, HeaderData, JsonHeader } from './types' | ||
import type { BlockHeaderBytes, BlockOptions, HeaderData, JsonHeader } from './types.js' | ||
import type { CliqueConfig } from '@ethereumjs/common' | ||
import type { BigIntLike } from '@ethereumjs/util' | ||
interface HeaderCache { | ||
hash: Buffer | undefined | ||
hash: Uint8Array | undefined | ||
} | ||
@@ -39,9 +41,9 @@ | ||
export class BlockHeader { | ||
public readonly parentHash: Buffer | ||
public readonly uncleHash: Buffer | ||
public readonly parentHash: Uint8Array | ||
public readonly uncleHash: Uint8Array | ||
public readonly coinbase: Address | ||
public readonly stateRoot: Buffer | ||
public readonly transactionsTrie: Buffer | ||
public readonly receiptTrie: Buffer | ||
public readonly logsBloom: Buffer | ||
public readonly stateRoot: Uint8Array | ||
public readonly transactionsTrie: Uint8Array | ||
public readonly receiptTrie: Uint8Array | ||
public readonly logsBloom: Uint8Array | ||
public readonly difficulty: bigint | ||
@@ -52,10 +54,12 @@ public readonly number: bigint | ||
public readonly timestamp: bigint | ||
public readonly extraData: Buffer | ||
public readonly mixHash: Buffer | ||
public readonly nonce: Buffer | ||
public readonly extraData: Uint8Array | ||
public readonly mixHash: Uint8Array | ||
public readonly nonce: Uint8Array | ||
public readonly baseFeePerGas?: bigint | ||
public readonly withdrawalsRoot?: Buffer | ||
public readonly withdrawalsRoot?: Uint8Array | ||
public readonly dataGasUsed?: bigint | ||
public readonly excessDataGas?: bigint | ||
public readonly parentBeaconBlockRoot?: Uint8Array | ||
public readonly _common: Common | ||
public readonly common: Common | ||
@@ -70,3 +74,3 @@ private cache: HeaderCache = { | ||
get prevRandao() { | ||
if (this._common.isActivatedEIP(4399) === false) { | ||
if (this.common.isActivatedEIP(4399) === false) { | ||
const msg = this._errorMsg( | ||
@@ -96,12 +100,12 @@ 'The prevRandao parameter can only be accessed when EIP-4399 is activated' | ||
*/ | ||
public static fromRLPSerializedHeader(serializedHeaderData: Buffer, opts: BlockOptions = {}) { | ||
const values = arrToBufArr(RLP.decode(Uint8Array.from(serializedHeaderData))) | ||
public static fromRLPSerializedHeader(serializedHeaderData: Uint8Array, opts: BlockOptions = {}) { | ||
const values = RLP.decode(serializedHeaderData) | ||
if (!Array.isArray(values)) { | ||
throw new Error('Invalid serialized header input. Must be array') | ||
} | ||
return BlockHeader.fromValuesArray(values as Buffer[], opts) | ||
return BlockHeader.fromValuesArray(values as Uint8Array[], opts) | ||
} | ||
/** | ||
* Static constructor to create a block header from an array of Buffer values | ||
* Static constructor to create a block header from an array of Bytes values | ||
* | ||
@@ -111,14 +115,26 @@ * @param values | ||
*/ | ||
public static fromValuesArray(values: BlockHeaderBuffer, opts: BlockOptions = {}) { | ||
public static fromValuesArray(values: BlockHeaderBytes, opts: BlockOptions = {}) { | ||
const headerData = valuesArrayToHeaderData(values) | ||
const { number, baseFeePerGas } = headerData | ||
const { number, baseFeePerGas, excessDataGas, dataGasUsed, parentBeaconBlockRoot } = headerData | ||
const header = BlockHeader.fromHeaderData(headerData, opts) | ||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions | ||
if (opts.common?.isActivatedEIP(1559) && baseFeePerGas === undefined) { | ||
const eip1559ActivationBlock = bigIntToBuffer(opts.common?.eipBlock(1559)!) | ||
if (header.common.isActivatedEIP(1559) && baseFeePerGas === undefined) { | ||
const eip1559ActivationBlock = bigIntToBytes(header.common.eipBlock(1559)!) | ||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions | ||
if (eip1559ActivationBlock && eip1559ActivationBlock.equals(number! as Buffer)) { | ||
if (eip1559ActivationBlock && equalsBytes(eip1559ActivationBlock, number as Uint8Array)) { | ||
throw new Error('invalid header. baseFeePerGas should be provided') | ||
} | ||
} | ||
return BlockHeader.fromHeaderData(headerData, opts) | ||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions | ||
if (header.common.isActivatedEIP(4844)) { | ||
if (excessDataGas === undefined) { | ||
throw new Error('invalid header. excessDataGas should be provided') | ||
} else if (dataGasUsed === undefined) { | ||
throw new Error('invalid header. dataGasUsed should be provided') | ||
} | ||
} | ||
if (header.common.isActivatedEIP(4788) && parentBeaconBlockRoot === undefined) { | ||
throw new Error('invalid header. parentBeaconBlockRoot should be provided') | ||
} | ||
return header | ||
} | ||
@@ -134,5 +150,5 @@ /** | ||
if (options.common) { | ||
this._common = options.common.copy() | ||
this.common = options.common.copy() | ||
} else { | ||
this._common = new Common({ | ||
this.common = new Common({ | ||
chain: Chain.Mainnet, // default | ||
@@ -142,8 +158,2 @@ }) | ||
if (options.hardforkByBlockNumber !== undefined && options.hardforkByTTD !== undefined) { | ||
throw new Error( | ||
`The hardforkByBlockNumber and hardforkByTTD options can't be used in conjunction` | ||
) | ||
} | ||
const skipValidateConsensusFormat = options.skipConsensusFormatValidation ?? false | ||
@@ -164,3 +174,3 @@ | ||
timestamp: BigInt(0), | ||
extraData: Buffer.from([]), | ||
extraData: new Uint8Array(0), | ||
mixHash: zeros(32), | ||
@@ -170,12 +180,13 @@ nonce: zeros(8), | ||
const parentHash = toType(headerData.parentHash, TypeOutput.Buffer) ?? defaults.parentHash | ||
const uncleHash = toType(headerData.uncleHash, TypeOutput.Buffer) ?? defaults.uncleHash | ||
const parentHash = toType(headerData.parentHash, TypeOutput.Uint8Array) ?? defaults.parentHash | ||
const uncleHash = toType(headerData.uncleHash, TypeOutput.Uint8Array) ?? defaults.uncleHash | ||
const coinbase = new Address( | ||
toType(headerData.coinbase ?? defaults.coinbase, TypeOutput.Buffer) | ||
toType(headerData.coinbase ?? defaults.coinbase, TypeOutput.Uint8Array) | ||
) | ||
const stateRoot = toType(headerData.stateRoot, TypeOutput.Buffer) ?? defaults.stateRoot | ||
const stateRoot = toType(headerData.stateRoot, TypeOutput.Uint8Array) ?? defaults.stateRoot | ||
const transactionsTrie = | ||
toType(headerData.transactionsTrie, TypeOutput.Buffer) ?? defaults.transactionsTrie | ||
const receiptTrie = toType(headerData.receiptTrie, TypeOutput.Buffer) ?? defaults.receiptTrie | ||
const logsBloom = toType(headerData.logsBloom, TypeOutput.Buffer) ?? defaults.logsBloom | ||
toType(headerData.transactionsTrie, TypeOutput.Uint8Array) ?? defaults.transactionsTrie | ||
const receiptTrie = | ||
toType(headerData.receiptTrie, TypeOutput.Uint8Array) ?? defaults.receiptTrie | ||
const logsBloom = toType(headerData.logsBloom, TypeOutput.Uint8Array) ?? defaults.logsBloom | ||
const difficulty = toType(headerData.difficulty, TypeOutput.BigInt) ?? defaults.difficulty | ||
@@ -186,9 +197,18 @@ const number = toType(headerData.number, TypeOutput.BigInt) ?? defaults.number | ||
const timestamp = toType(headerData.timestamp, TypeOutput.BigInt) ?? defaults.timestamp | ||
const extraData = toType(headerData.extraData, TypeOutput.Buffer) ?? defaults.extraData | ||
const mixHash = toType(headerData.mixHash, TypeOutput.Buffer) ?? defaults.mixHash | ||
const nonce = toType(headerData.nonce, TypeOutput.Buffer) ?? defaults.nonce | ||
const extraData = toType(headerData.extraData, TypeOutput.Uint8Array) ?? defaults.extraData | ||
const mixHash = toType(headerData.mixHash, TypeOutput.Uint8Array) ?? defaults.mixHash | ||
const nonce = toType(headerData.nonce, TypeOutput.Uint8Array) ?? defaults.nonce | ||
const hardforkByBlockNumber = options.hardforkByBlockNumber ?? false | ||
if (hardforkByBlockNumber || options.hardforkByTTD !== undefined) { | ||
this._common.setHardforkByBlockNumber(number, options.hardforkByTTD, timestamp) | ||
const setHardfork = options.setHardfork ?? false | ||
if (setHardfork === true) { | ||
this.common.setHardforkBy({ | ||
blockNumber: number, | ||
timestamp, | ||
}) | ||
} else if (typeof setHardfork !== 'boolean') { | ||
this.common.setHardforkBy({ | ||
blockNumber: number, | ||
td: setHardfork as BigIntLike, | ||
timestamp, | ||
}) | ||
} | ||
@@ -198,9 +218,11 @@ | ||
const hardforkDefaults = { | ||
baseFeePerGas: this._common.isActivatedEIP(1559) | ||
? number === this._common.hardforkBlock(Hardfork.London) | ||
? this._common.param('gasConfig', 'initialBaseFee') | ||
baseFeePerGas: this.common.isActivatedEIP(1559) | ||
? number === this.common.hardforkBlock(Hardfork.London) | ||
? this.common.param('gasConfig', 'initialBaseFee') | ||
: BigInt(7) | ||
: undefined, | ||
withdrawalsRoot: this._common.isActivatedEIP(4895) ? KECCAK256_RLP : undefined, | ||
excessDataGas: this._common.isActivatedEIP(4844) ? BigInt(0) : undefined, | ||
withdrawalsRoot: this.common.isActivatedEIP(4895) ? KECCAK256_RLP : undefined, | ||
dataGasUsed: this.common.isActivatedEIP(4844) ? BigInt(0) : undefined, | ||
excessDataGas: this.common.isActivatedEIP(4844) ? BigInt(0) : undefined, | ||
parentBeaconBlockRoot: this.common.isActivatedEIP(4788) ? zeros(32) : undefined, | ||
} | ||
@@ -211,11 +233,16 @@ | ||
const withdrawalsRoot = | ||
toType(headerData.withdrawalsRoot, TypeOutput.Buffer) ?? hardforkDefaults.withdrawalsRoot | ||
toType(headerData.withdrawalsRoot, TypeOutput.Uint8Array) ?? hardforkDefaults.withdrawalsRoot | ||
const dataGasUsed = | ||
toType(headerData.dataGasUsed, TypeOutput.BigInt) ?? hardforkDefaults.dataGasUsed | ||
const excessDataGas = | ||
toType(headerData.excessDataGas, TypeOutput.BigInt) ?? hardforkDefaults.excessDataGas | ||
const parentBeaconBlockRoot = | ||
toType(headerData.parentBeaconBlockRoot, TypeOutput.Uint8Array) ?? | ||
hardforkDefaults.parentBeaconBlockRoot | ||
if (!this._common.isActivatedEIP(1559) && baseFeePerGas !== undefined) { | ||
if (!this.common.isActivatedEIP(1559) && baseFeePerGas !== undefined) { | ||
throw new Error('A base fee for a block can only be set with EIP1559 being activated') | ||
} | ||
if (!this._common.isActivatedEIP(4895) && withdrawalsRoot !== undefined) { | ||
if (!this.common.isActivatedEIP(4895) && withdrawalsRoot !== undefined) { | ||
throw new Error( | ||
@@ -226,2 +253,18 @@ 'A withdrawalsRoot for a header can only be provided with EIP4895 being activated' | ||
if (!this.common.isActivatedEIP(4844)) { | ||
if (headerData.dataGasUsed !== undefined) { | ||
throw new Error('data gas used can only be provided with EIP4844 activated') | ||
} | ||
if (headerData.excessDataGas !== undefined) { | ||
throw new Error('excess data gas can only be provided with EIP4844 activated') | ||
} | ||
} | ||
if (!this.common.isActivatedEIP(4788) && parentBeaconBlockRoot !== undefined) { | ||
throw new Error( | ||
'A parentBeaconBlockRoot for a header can only be provided with EIP4788 being activated' | ||
) | ||
} | ||
this.parentHash = parentHash | ||
@@ -244,3 +287,5 @@ this.uncleHash = uncleHash | ||
this.withdrawalsRoot = withdrawalsRoot | ||
this.dataGasUsed = dataGasUsed | ||
this.excessDataGas = excessDataGas | ||
this.parentBeaconBlockRoot = parentBeaconBlockRoot | ||
this._genericFormatValidation() | ||
@@ -254,3 +299,3 @@ this._validateDAOExtraData() | ||
options.calcDifficultyFromHeader && | ||
this._common.consensusAlgorithm() === ConsensusAlgorithm.Ethash | ||
this.common.consensusAlgorithm() === ConsensusAlgorithm.Ethash | ||
) { | ||
@@ -266,3 +311,3 @@ this.difficulty = this.ethashCanonicalDifficulty(options.calcDifficultyFromHeader) | ||
const remainingLength = minExtraDataLength - this.extraData.length | ||
this.extraData = Buffer.concat([this.extraData, Buffer.alloc(remainingLength)]) | ||
this.extraData = concatBytes(this.extraData, new Uint8Array(remainingLength)) | ||
} | ||
@@ -285,3 +330,3 @@ | ||
*/ | ||
_genericFormatValidation() { | ||
protected _genericFormatValidation() { | ||
const { parentHash, stateRoot, transactionsTrie, receiptTrie, mixHash, nonce } = this | ||
@@ -326,3 +371,3 @@ | ||
// Validation for EIP-1559 blocks | ||
if (this._common.isActivatedEIP(1559) === true) { | ||
if (this.common.isActivatedEIP(1559) === true) { | ||
if (typeof this.baseFeePerGas !== 'bigint') { | ||
@@ -332,3 +377,3 @@ const msg = this._errorMsg('EIP1559 block has no base fee field') | ||
} | ||
const londonHfBlock = this._common.hardforkBlock(Hardfork.London) | ||
const londonHfBlock = this.common.hardforkBlock(Hardfork.London) | ||
if ( | ||
@@ -339,3 +384,3 @@ typeof londonHfBlock === 'bigint' && | ||
) { | ||
const initialBaseFee = this._common.param('gasConfig', 'initialBaseFee') | ||
const initialBaseFee = this.common.param('gasConfig', 'initialBaseFee') | ||
if (this.baseFeePerGas !== initialBaseFee) { | ||
@@ -348,3 +393,3 @@ const msg = this._errorMsg('Initial EIP1559 block does not have initial base fee') | ||
if (this._common.isActivatedEIP(4895) === true) { | ||
if (this.common.isActivatedEIP(4895) === true) { | ||
if (this.withdrawalsRoot === undefined) { | ||
@@ -361,2 +406,17 @@ const msg = this._errorMsg('EIP4895 block has no withdrawalsRoot field') | ||
} | ||
if (this.common.isActivatedEIP(4788) === true) { | ||
if (this.parentBeaconBlockRoot === undefined) { | ||
const msg = this._errorMsg('EIP4788 block has no parentBeaconBlockRoot field') | ||
throw new Error(msg) | ||
} | ||
if (this.parentBeaconBlockRoot?.length !== 32) { | ||
const msg = this._errorMsg( | ||
`parentBeaconBlockRoot must be 32 bytes, received ${ | ||
this.parentBeaconBlockRoot!.length | ||
} bytes` | ||
) | ||
throw new Error(msg) | ||
} | ||
} | ||
} | ||
@@ -368,12 +428,12 @@ | ||
*/ | ||
_consensusFormatValidation() { | ||
protected _consensusFormatValidation() { | ||
const { nonce, uncleHash, difficulty, extraData, number } = this | ||
const hardfork = this._common.hardfork() | ||
const hardfork = this.common.hardfork() | ||
// Consensus type dependent checks | ||
if (this._common.consensusAlgorithm() === ConsensusAlgorithm.Ethash) { | ||
if (this.common.consensusAlgorithm() === ConsensusAlgorithm.Ethash) { | ||
// PoW/Ethash | ||
if ( | ||
number > BigInt(0) && | ||
this.extraData.length > this._common.paramByHardfork('vm', 'maxExtraDataSize', hardfork) | ||
this.extraData.length > this.common.paramByHardfork('vm', 'maxExtraDataSize', hardfork) | ||
) { | ||
@@ -385,3 +445,3 @@ // Check length of data on all post-genesis blocks | ||
} | ||
if (this._common.consensusAlgorithm() === ConsensusAlgorithm.Clique) { | ||
if (this.common.consensusAlgorithm() === ConsensusAlgorithm.Clique) { | ||
// PoA/Clique | ||
@@ -414,3 +474,3 @@ const minLength = CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL | ||
// MixHash format | ||
if (!this.mixHash.equals(Buffer.alloc(32))) { | ||
if (!equalsBytes(this.mixHash, new Uint8Array(32))) { | ||
const msg = this._errorMsg(`mixHash must be filled with zeros, received ${this.mixHash}`) | ||
@@ -421,10 +481,10 @@ throw new Error(msg) | ||
// Validation for PoS blocks (EIP-3675) | ||
if (this._common.consensusType() === ConsensusType.ProofOfStake) { | ||
if (this.common.consensusType() === ConsensusType.ProofOfStake) { | ||
let error = false | ||
let errorMsg = '' | ||
if (!uncleHash.equals(KECCAK256_RLP_ARRAY)) { | ||
errorMsg += `, uncleHash: ${uncleHash.toString( | ||
'hex' | ||
)} (expected: ${KECCAK256_RLP_ARRAY.toString('hex')})` | ||
if (!equalsBytes(uncleHash, KECCAK256_RLP_ARRAY)) { | ||
errorMsg += `, uncleHash: ${bytesToHex(uncleHash)} (expected: ${bytesToHex( | ||
KECCAK256_RLP_ARRAY | ||
)})` | ||
error = true | ||
@@ -439,9 +499,9 @@ } | ||
if (extraData.length > 32) { | ||
errorMsg += `, extraData: ${extraData.toString( | ||
'hex' | ||
errorMsg += `, extraData: ${bytesToHex( | ||
extraData | ||
)} (cannot exceed 32 bytes length, received ${extraData.length} bytes)` | ||
error = true | ||
} | ||
if (!nonce.equals(zeros(8))) { | ||
errorMsg += `, nonce: ${nonce.toString('hex')} (expected: ${zeros(8).toString('hex')})` | ||
if (!equalsBytes(nonce, zeros(8))) { | ||
errorMsg += `, nonce: ${bytesToHex(nonce)} (expected: ${bytesToHex(zeros(8))})` | ||
error = true | ||
@@ -451,3 +511,3 @@ } | ||
if (error) { | ||
const msg = this._errorMsg(`Invalid PoS block${errorMsg}`) | ||
const msg = this._errorMsg(`Invalid PoS block: ${errorMsg}`) | ||
throw new Error(msg) | ||
@@ -468,3 +528,3 @@ } | ||
// to adopt to the new gas target centered logic | ||
const londonHardforkBlock = this._common.hardforkBlock(Hardfork.London) | ||
const londonHardforkBlock = this.common.hardforkBlock(Hardfork.London) | ||
if ( | ||
@@ -475,10 +535,10 @@ typeof londonHardforkBlock === 'bigint' && | ||
) { | ||
const elasticity = this._common.param('gasConfig', 'elasticityMultiplier') | ||
const elasticity = this.common.param('gasConfig', 'elasticityMultiplier') | ||
parentGasLimit = parentGasLimit * elasticity | ||
} | ||
const gasLimit = this.gasLimit | ||
const hardfork = this._common.hardfork() | ||
const hardfork = this.common.hardfork() | ||
const a = | ||
parentGasLimit / this._common.paramByHardfork('gasConfig', 'gasLimitBoundDivisor', hardfork) | ||
parentGasLimit / this.common.paramByHardfork('gasConfig', 'gasLimitBoundDivisor', hardfork) | ||
const maxGasLimit = parentGasLimit + a | ||
@@ -497,3 +557,3 @@ const minGasLimit = parentGasLimit - a | ||
if (gasLimit < this._common.paramByHardfork('gasConfig', 'minGasLimit', hardfork)) { | ||
if (gasLimit < this.common.paramByHardfork('gasConfig', 'minGasLimit', hardfork)) { | ||
const msg = this._errorMsg( | ||
@@ -510,3 +570,3 @@ `gas limit decreased below minimum gas limit for hardfork=${hardfork}` | ||
public calcNextBaseFee(): bigint { | ||
if (this._common.isActivatedEIP(1559) === false) { | ||
if (this.common.isActivatedEIP(1559) === false) { | ||
const msg = this._errorMsg( | ||
@@ -518,3 +578,3 @@ 'calcNextBaseFee() can only be called with EIP1559 being activated' | ||
let nextBaseFee: bigint | ||
const elasticity = this._common.param('gasConfig', 'elasticityMultiplier') | ||
const elasticity = this.common.param('gasConfig', 'elasticityMultiplier') | ||
const parentGasTarget = this.gasLimit / elasticity | ||
@@ -526,3 +586,3 @@ | ||
const gasUsedDelta = this.gasUsed - parentGasTarget | ||
const baseFeeMaxChangeDenominator = this._common.param( | ||
const baseFeeMaxChangeDenominator = this.common.param( | ||
'gasConfig', | ||
@@ -538,3 +598,3 @@ 'baseFeeMaxChangeDenominator' | ||
const gasUsedDelta = parentGasTarget - this.gasUsed | ||
const baseFeeMaxChangeDenominator = this._common.param( | ||
const baseFeeMaxChangeDenominator = this.common.param( | ||
'gasConfig', | ||
@@ -555,9 +615,53 @@ 'baseFeeMaxChangeDenominator' | ||
/** | ||
* Returns a Buffer Array of the raw Buffers in this header, in order. | ||
* Returns the price per unit of data gas for a blob transaction in the current/pending block | ||
* @returns the price in gwei per unit of data gas spent | ||
*/ | ||
raw(): BlockHeaderBuffer { | ||
getDataGasPrice(): bigint { | ||
if (this.excessDataGas === undefined) { | ||
throw new Error('header must have excessDataGas field populated') | ||
} | ||
return fakeExponential( | ||
this.common.param('gasPrices', 'minDataGasPrice'), | ||
this.excessDataGas, | ||
this.common.param('gasConfig', 'dataGasPriceUpdateFraction') | ||
) | ||
} | ||
/** | ||
* Returns the total fee for data gas spent for including blobs in block. | ||
* | ||
* @param numBlobs number of blobs in the transaction/block | ||
* @returns the total data gas fee for numBlobs blobs | ||
*/ | ||
calcDataFee(numBlobs: number): bigint { | ||
const dataGasPerBlob = this.common.param('gasConfig', 'dataGasPerBlob') | ||
const dataGasUsed = dataGasPerBlob * BigInt(numBlobs) | ||
const dataGasPrice = this.getDataGasPrice() | ||
return dataGasUsed * dataGasPrice | ||
} | ||
/** | ||
* Calculates the excess data gas for next (hopefully) post EIP 4844 block. | ||
*/ | ||
public calcNextExcessDataGas(): bigint { | ||
// The validation of the fields and 4844 activation is already taken care in BlockHeader constructor | ||
const targetGasConsumed = (this.excessDataGas ?? BigInt(0)) + (this.dataGasUsed ?? BigInt(0)) | ||
const targetDataGasPerBlock = this.common.param('gasConfig', 'targetDataGasPerBlock') | ||
if (targetGasConsumed <= targetDataGasPerBlock) { | ||
return BigInt(0) | ||
} else { | ||
return targetGasConsumed - targetDataGasPerBlock | ||
} | ||
} | ||
/** | ||
* Returns a Uint8Array Array of the raw Bytes in this header, in order. | ||
*/ | ||
raw(): BlockHeaderBytes { | ||
const rawItems = [ | ||
this.parentHash, | ||
this.uncleHash, | ||
this.coinbase.buf, | ||
this.coinbase.bytes, | ||
this.stateRoot, | ||
@@ -567,7 +671,7 @@ this.transactionsTrie, | ||
this.logsBloom, | ||
bigIntToUnpaddedBuffer(this.difficulty), | ||
bigIntToUnpaddedBuffer(this.number), | ||
bigIntToUnpaddedBuffer(this.gasLimit), | ||
bigIntToUnpaddedBuffer(this.gasUsed), | ||
bigIntToUnpaddedBuffer(this.timestamp ?? BigInt(0)), | ||
bigIntToUnpaddedBytes(this.difficulty), | ||
bigIntToUnpaddedBytes(this.number), | ||
bigIntToUnpaddedBytes(this.gasLimit), | ||
bigIntToUnpaddedBytes(this.gasUsed), | ||
bigIntToUnpaddedBytes(this.timestamp ?? BigInt(0)), | ||
this.extraData, | ||
@@ -578,12 +682,16 @@ this.mixHash, | ||
if (this._common.isActivatedEIP(1559) === true) { | ||
rawItems.push(bigIntToUnpaddedBuffer(this.baseFeePerGas!)) | ||
if (this.common.isActivatedEIP(1559) === true) { | ||
rawItems.push(bigIntToUnpaddedBytes(this.baseFeePerGas!)) | ||
} | ||
if (this._common.isActivatedEIP(4895) === true) { | ||
if (this.common.isActivatedEIP(4895) === true) { | ||
rawItems.push(this.withdrawalsRoot!) | ||
} | ||
if (this._common.isActivatedEIP(4844) === true) { | ||
rawItems.push(bigIntToUnpaddedBuffer(this.excessDataGas!)) | ||
if (this.common.isActivatedEIP(4844) === true) { | ||
rawItems.push(bigIntToUnpaddedBytes(this.dataGasUsed!)) | ||
rawItems.push(bigIntToUnpaddedBytes(this.excessDataGas!)) | ||
} | ||
if (this.common.isActivatedEIP(4788) === true) { | ||
rawItems.push(this.parentBeaconBlockRoot!) | ||
} | ||
@@ -596,6 +704,6 @@ return rawItems | ||
*/ | ||
hash(): Buffer { | ||
hash(): Uint8Array { | ||
if (Object.isFrozen(this)) { | ||
if (!this.cache.hash) { | ||
this.cache.hash = Buffer.from(keccak256(RLP.encode(bufArrToArr(this.raw())))) | ||
this.cache.hash = keccak256(RLP.encode(this.raw())) | ||
} | ||
@@ -605,3 +713,3 @@ return this.cache.hash | ||
return Buffer.from(keccak256(RLP.encode(bufArrToArr(this.raw())))) | ||
return keccak256(RLP.encode(this.raw())) | ||
} | ||
@@ -616,4 +724,4 @@ | ||
private _requireClique(name: string) { | ||
if (this._common.consensusAlgorithm() !== ConsensusAlgorithm.Clique) { | ||
protected _requireClique(name: string) { | ||
if (this.common.consensusAlgorithm() !== ConsensusAlgorithm.Clique) { | ||
const msg = this._errorMsg( | ||
@@ -632,7 +740,7 @@ `BlockHeader.${name}() call only supported for clique PoA networks` | ||
ethashCanonicalDifficulty(parentBlockHeader: BlockHeader): bigint { | ||
if (this._common.consensusType() !== ConsensusType.ProofOfWork) { | ||
if (this.common.consensusType() !== ConsensusType.ProofOfWork) { | ||
const msg = this._errorMsg('difficulty calculation is only supported on PoW chains') | ||
throw new Error(msg) | ||
} | ||
if (this._common.consensusAlgorithm() !== ConsensusAlgorithm.Ethash) { | ||
if (this.common.consensusAlgorithm() !== ConsensusAlgorithm.Ethash) { | ||
const msg = this._errorMsg( | ||
@@ -643,8 +751,8 @@ 'difficulty calculation currently only supports the ethash algorithm' | ||
} | ||
const hardfork = this._common.hardfork() | ||
const hardfork = this.common.hardfork() | ||
const blockTs = this.timestamp | ||
const { timestamp: parentTs, difficulty: parentDif } = parentBlockHeader | ||
const minimumDifficulty = this._common.paramByHardfork('pow', 'minimumDifficulty', hardfork) | ||
const minimumDifficulty = this.common.paramByHardfork('pow', 'minimumDifficulty', hardfork) | ||
const offset = | ||
parentDif / this._common.paramByHardfork('pow', 'difficultyBoundDivisor', hardfork) | ||
parentDif / this.common.paramByHardfork('pow', 'difficultyBoundDivisor', hardfork) | ||
let num = this.number | ||
@@ -655,5 +763,5 @@ | ||
if (this._common.hardforkGteHardfork(hardfork, Hardfork.Byzantium) === true) { | ||
if (this.common.hardforkGteHardfork(hardfork, Hardfork.Byzantium) === true) { | ||
// max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99) (EIP100) | ||
const uncleAddend = parentBlockHeader.uncleHash.equals(KECCAK256_RLP_ARRAY) ? 1 : 2 | ||
const uncleAddend = equalsBytes(parentBlockHeader.uncleHash, KECCAK256_RLP_ARRAY) ? 1 : 2 | ||
let a = BigInt(uncleAddend) - (blockTs - parentTs) / BigInt(9) | ||
@@ -668,9 +776,9 @@ const cutoff = BigInt(-99) | ||
if (this._common.hardforkGteHardfork(hardfork, Hardfork.Byzantium) === true) { | ||
if (this.common.hardforkGteHardfork(hardfork, Hardfork.Byzantium) === true) { | ||
// Get delay as parameter from common | ||
num = num - this._common.param('pow', 'difficultyBombDelay') | ||
num = num - this.common.param('pow', 'difficultyBombDelay') | ||
if (num < BigInt(0)) { | ||
num = BigInt(0) | ||
} | ||
} else if (this._common.hardforkGteHardfork(hardfork, Hardfork.Homestead) === true) { | ||
} else if (this.common.hardforkGteHardfork(hardfork, Hardfork.Homestead) === true) { | ||
// 1 - (block_timestamp - parent_timestamp) // 10 | ||
@@ -686,3 +794,3 @@ let a = BigInt(1) - (blockTs - parentTs) / BigInt(10) | ||
// pre-homestead | ||
if (parentTs + this._common.paramByHardfork('pow', 'durationLimit', hardfork) > blockTs) { | ||
if (parentTs + this.common.paramByHardfork('pow', 'durationLimit', hardfork) > blockTs) { | ||
dif = offset + parentDif | ||
@@ -712,4 +820,4 @@ } else { | ||
const raw = this.raw() | ||
raw[12] = this.extraData.slice(0, this.extraData.length - CLIQUE_EXTRA_SEAL) | ||
return Buffer.from(keccak256(RLP.encode(bufArrToArr(raw)))) | ||
raw[12] = this.extraData.subarray(0, this.extraData.length - CLIQUE_EXTRA_SEAL) | ||
return keccak256(RLP.encode(raw)) | ||
} | ||
@@ -723,3 +831,3 @@ | ||
this._requireClique('cliqueIsEpochTransition') | ||
const epoch = BigInt((this._common.consensusConfig() as CliqueConfig).epoch) | ||
const epoch = BigInt((this.common.consensusConfig() as CliqueConfig).epoch) | ||
// Epoch transition block if the block number has no | ||
@@ -734,5 +842,5 @@ // remainder on the division by the epoch length | ||
*/ | ||
cliqueExtraVanity(): Buffer { | ||
cliqueExtraVanity(): Uint8Array { | ||
this._requireClique('cliqueExtraVanity') | ||
return this.extraData.slice(0, CLIQUE_EXTRA_VANITY) | ||
return this.extraData.subarray(0, CLIQUE_EXTRA_VANITY) | ||
} | ||
@@ -744,5 +852,5 @@ | ||
*/ | ||
cliqueExtraSeal(): Buffer { | ||
cliqueExtraSeal(): Uint8Array { | ||
this._requireClique('cliqueExtraSeal') | ||
return this.extraData.slice(-CLIQUE_EXTRA_SEAL) | ||
return this.extraData.subarray(-CLIQUE_EXTRA_SEAL) | ||
} | ||
@@ -755,14 +863,17 @@ | ||
*/ | ||
private cliqueSealBlock(privateKey: Buffer) { | ||
private cliqueSealBlock(privateKey: Uint8Array) { | ||
this._requireClique('cliqueSealBlock') | ||
const signature = ecsign(this.cliqueSigHash(), privateKey) | ||
const signatureB = Buffer.concat([ | ||
const signatureB = concatBytes( | ||
signature.r, | ||
signature.s, | ||
bigIntToBuffer(signature.v - BigInt(27)), | ||
]) | ||
bigIntToBytes(signature.v - BigInt(27)) | ||
) | ||
const extraDataWithoutSeal = this.extraData.slice(0, this.extraData.length - CLIQUE_EXTRA_SEAL) | ||
const extraData = Buffer.concat([extraDataWithoutSeal, signatureB]) | ||
const extraDataWithoutSeal = this.extraData.subarray( | ||
0, | ||
this.extraData.length - CLIQUE_EXTRA_SEAL | ||
) | ||
const extraData = concatBytes(extraDataWithoutSeal, signatureB) | ||
return extraData | ||
@@ -788,8 +899,8 @@ } | ||
const end = this.extraData.length - CLIQUE_EXTRA_SEAL | ||
const signerBuffer = this.extraData.slice(start, end) | ||
const signerBytes = this.extraData.subarray(start, end) | ||
const signerList: Buffer[] = [] | ||
const signerList: Uint8Array[] = [] | ||
const signerLength = 20 | ||
for (let start = 0; start <= signerBuffer.length - signerLength; start += signerLength) { | ||
signerList.push(signerBuffer.slice(start, start + signerLength)) | ||
for (let start = 0; start <= signerBytes.length - signerLength; start += signerLength) { | ||
signerList.push(signerBytes.subarray(start, start + signerLength)) | ||
} | ||
@@ -821,8 +932,8 @@ return signerList.map((buf) => new Address(buf)) | ||
// Reasonable default for default blocks | ||
if (extraSeal.length === 0 || extraSeal.equals(Buffer.alloc(65).fill(0))) { | ||
if (extraSeal.length === 0 || equalsBytes(extraSeal, new Uint8Array(65))) { | ||
return Address.zero() | ||
} | ||
const r = extraSeal.slice(0, 32) | ||
const s = extraSeal.slice(32, 64) | ||
const v = bufferToBigInt(extraSeal.slice(64, 65)) + BigInt(27) | ||
const r = extraSeal.subarray(0, 32) | ||
const s = extraSeal.subarray(32, 64) | ||
const v = bytesToBigInt(extraSeal.subarray(64, 65)) + BigInt(27) | ||
const pubKey = ecrecover(this.cliqueSigHash(), v, r, s) | ||
@@ -835,4 +946,4 @@ return Address.fromPublicKey(pubKey) | ||
*/ | ||
serialize(): Buffer { | ||
return Buffer.from(RLP.encode(bufArrToArr(this.raw()))) | ||
serialize(): Uint8Array { | ||
return RLP.encode(this.raw()) | ||
} | ||
@@ -845,13 +956,13 @@ | ||
const withdrawalAttr = this.withdrawalsRoot | ||
? { withdrawalsRoot: '0x' + this.withdrawalsRoot.toString('hex') } | ||
? { withdrawalsRoot: bytesToHex(this.withdrawalsRoot) } | ||
: {} | ||
const jsonDict: JsonHeader = { | ||
parentHash: '0x' + this.parentHash.toString('hex'), | ||
uncleHash: '0x' + this.uncleHash.toString('hex'), | ||
parentHash: bytesToHex(this.parentHash), | ||
uncleHash: bytesToHex(this.uncleHash), | ||
coinbase: this.coinbase.toString(), | ||
stateRoot: '0x' + this.stateRoot.toString('hex'), | ||
transactionsTrie: '0x' + this.transactionsTrie.toString('hex'), | ||
stateRoot: bytesToHex(this.stateRoot), | ||
transactionsTrie: bytesToHex(this.transactionsTrie), | ||
...withdrawalAttr, | ||
receiptTrie: '0x' + this.receiptTrie.toString('hex'), | ||
logsBloom: '0x' + this.logsBloom.toString('hex'), | ||
receiptTrie: bytesToHex(this.receiptTrie), | ||
logsBloom: bytesToHex(this.logsBloom), | ||
difficulty: bigIntToHex(this.difficulty), | ||
@@ -862,12 +973,16 @@ number: bigIntToHex(this.number), | ||
timestamp: bigIntToHex(this.timestamp), | ||
extraData: '0x' + this.extraData.toString('hex'), | ||
mixHash: '0x' + this.mixHash.toString('hex'), | ||
nonce: '0x' + this.nonce.toString('hex'), | ||
extraData: bytesToHex(this.extraData), | ||
mixHash: bytesToHex(this.mixHash), | ||
nonce: bytesToHex(this.nonce), | ||
} | ||
if (this._common.isActivatedEIP(1559) === true) { | ||
if (this.common.isActivatedEIP(1559) === true) { | ||
jsonDict.baseFeePerGas = bigIntToHex(this.baseFeePerGas!) | ||
} | ||
if (this._common.isActivatedEIP(4844) === true) { | ||
if (this.common.isActivatedEIP(4844) === true) { | ||
jsonDict.dataGasUsed = bigIntToHex(this.dataGasUsed!) | ||
jsonDict.excessDataGas = bigIntToHex(this.excessDataGas!) | ||
} | ||
if (this.common.isActivatedEIP(4788) === true) { | ||
jsonDict.parentBeaconBlockRoot = bytesToHex(this.parentBeaconBlockRoot!) | ||
} | ||
return jsonDict | ||
@@ -880,14 +995,14 @@ } | ||
*/ | ||
private _validateDAOExtraData() { | ||
if (this._common.hardforkIsActiveOnBlock(Hardfork.Dao, this.number) === false) { | ||
protected _validateDAOExtraData() { | ||
if (this.common.hardforkIsActiveOnBlock(Hardfork.Dao, this.number) === false) { | ||
return | ||
} | ||
const DAOActivationBlock = this._common.hardforkBlock(Hardfork.Dao) | ||
const DAOActivationBlock = this.common.hardforkBlock(Hardfork.Dao) | ||
if (DAOActivationBlock === null || this.number < DAOActivationBlock) { | ||
return | ||
} | ||
const DAO_ExtraData = Buffer.from('64616f2d686172642d666f726b', 'hex') | ||
const DAO_ExtraData = hexToBytes('0x64616f2d686172642d666f726b') | ||
const DAO_ForceExtraDataRange = BigInt(9) | ||
const drift = this.number - DAOActivationBlock | ||
if (drift <= DAO_ForceExtraDataRange && !this.extraData.equals(DAO_ExtraData)) { | ||
if (drift <= DAO_ForceExtraDataRange && !equalsBytes(this.extraData, DAO_ExtraData)) { | ||
const msg = this._errorMsg("extraData should be 'dao-hard-fork'") | ||
@@ -904,3 +1019,3 @@ throw new Error(msg) | ||
try { | ||
hash = bufferToHex(this.hash()) | ||
hash = bytesToHex(this.hash()) | ||
} catch (e: any) { | ||
@@ -911,3 +1026,3 @@ hash = 'error' | ||
try { | ||
hf = this._common.hardfork() | ||
hf = this.common.hardfork() | ||
} catch (e: any) { | ||
@@ -914,0 +1029,0 @@ hf = 'error' |
@@ -0,5 +1,6 @@ | ||
import { BlobEIP4844Transaction } from '@ethereumjs/tx' | ||
import { TypeOutput, isHexString, toType } from '@ethereumjs/util' | ||
import type { BlockHeader } from './header' | ||
import type { BlockHeaderBuffer, HeaderData } from './types' | ||
import type { BlockHeaderBytes, HeaderData } from './types.js' | ||
import type { TypedTransaction } from '@ethereumjs/tx' | ||
@@ -23,3 +24,3 @@ /** | ||
export function valuesArrayToHeaderData(values: BlockHeaderBuffer): HeaderData { | ||
export function valuesArrayToHeaderData(values: BlockHeaderBytes): HeaderData { | ||
const [ | ||
@@ -43,6 +44,8 @@ parentHash, | ||
withdrawalsRoot, | ||
dataGasUsed, | ||
excessDataGas, | ||
parentBeaconBlockRoot, | ||
] = values | ||
if (values.length > 18) { | ||
if (values.length > 20) { | ||
throw new Error('invalid header. More values than expected were received') | ||
@@ -72,3 +75,5 @@ } | ||
withdrawalsRoot, | ||
dataGasUsed, | ||
excessDataGas, | ||
parentBeaconBlockRoot, | ||
} | ||
@@ -85,28 +90,10 @@ } | ||
/** | ||
* Calculates the excess data gas for a post EIP 4844 block given the parent block header. | ||
* @param parent header for the parent block | ||
* @param newBlobs number of blobs contained in block | ||
* @returns the excess data gas for the prospective next block | ||
* | ||
* Note: This function expects that it is only being called on a valid block as it does not have | ||
* access to the "current" block's common instance to verify if 4844 is active or not. | ||
*/ | ||
export const calcExcessDataGas = (parent: BlockHeader, newBlobs: number) => { | ||
if (!parent._common.isActivatedEIP(4844)) { | ||
// If 4844 isn't active on header, assume this is the first post-fork block so excess data gas is 0 | ||
return BigInt(0) | ||
export const getNumBlobs = (transactions: TypedTransaction[]) => { | ||
let numBlobs = 0 | ||
for (const tx of transactions) { | ||
if (tx instanceof BlobEIP4844Transaction) { | ||
numBlobs += tx.versionedHashes.length | ||
} | ||
} | ||
if (parent.excessDataGas === undefined) { | ||
// Given 4844 is active on parent block, we expect it to have an excessDataGas field | ||
throw new Error('parent header does not contain excessDataGas field') | ||
} | ||
const consumedDataGas = BigInt(newBlobs) * parent._common.param('gasConfig', 'dataGasPerBlob') | ||
const targetDataGasPerBlock = parent._common.param('gasConfig', 'targetDataGasPerBlock') | ||
if (parent.excessDataGas + consumedDataGas < targetDataGasPerBlock) return BigInt(0) | ||
else { | ||
return parent.excessDataGas + consumedDataGas - targetDataGasPerBlock | ||
} | ||
return numBlobs | ||
} | ||
@@ -123,37 +110,7 @@ | ||
output += numerator_accum | ||
numerator_accum = BigInt(Math.floor(Number((numerator_accum * numerator) / (denominator * i)))) | ||
numerator_accum = (numerator_accum * numerator) / (denominator * i) | ||
i++ | ||
} | ||
return BigInt(Math.floor(Number(output / denominator))) | ||
} | ||
/** | ||
* Returns the price per unit of data gas for a blob transaction in the current/pending block | ||
* @param header the parent header for the current block (or current head of the chain) | ||
* @returns the price in gwei per unit of data gas spent | ||
*/ | ||
export const getDataGasPrice = (header: BlockHeader) => { | ||
if (header.excessDataGas === undefined) { | ||
throw new Error('parent header must have excessDataGas field populated') | ||
} | ||
return fakeExponential( | ||
header._common.param('gasPrices', 'minDataGasPrice'), | ||
header.excessDataGas, | ||
header._common.param('gasConfig', 'dataGasPriceUpdateFraction') | ||
) | ||
return output / denominator | ||
} | ||
/** | ||
* Returns the total fee for data gas spent on `numBlobs` in the current/pending block | ||
* @param numBlobs | ||
* @param parent parent header of the current/pending block | ||
* @returns the total data gas fee for a transaction assuming it contains `numBlobs` | ||
*/ | ||
export const calcDataFee = (numBlobs: number, parent: BlockHeader) => { | ||
if (parent.excessDataGas === undefined) { | ||
throw new Error('parent header must have excessDataGas field populated') | ||
} | ||
const totalDataGas = parent._common.param('gasConfig', 'dataGasPerBlob') * BigInt(numBlobs) | ||
const dataGasPrice = getDataGasPrice(parent) | ||
return totalDataGas * dataGasPrice | ||
} |
@@ -1,10 +0,4 @@ | ||
export { Block } from './block' | ||
export { BlockHeader } from './header' | ||
export { | ||
calcDataFee, | ||
calcExcessDataGas, | ||
getDataGasPrice, | ||
getDifficulty, | ||
valuesArrayToHeaderData, | ||
} from './helpers' | ||
export * from './types' | ||
export { Block } from './block.js' | ||
export { BlockHeader } from './header.js' | ||
export { getDifficulty, valuesArrayToHeaderData } from './helpers.js' | ||
export * from './types.js' |
112
src/types.ts
@@ -1,16 +0,11 @@ | ||
import type { BlockHeader } from './header' | ||
import type { BlockHeader } from './header.js' | ||
import type { Common } from '@ethereumjs/common' | ||
import type { JsonRpcTx, JsonTx, TransactionType, TxData } from '@ethereumjs/tx' | ||
import type { | ||
AccessListEIP2930TxData, | ||
FeeMarketEIP1559TxData, | ||
JsonRpcTx, | ||
JsonTx, | ||
TxData, | ||
} from '@ethereumjs/tx' | ||
import type { | ||
AddressLike, | ||
BigIntLike, | ||
BufferLike, | ||
BytesLike, | ||
JsonRpcWithdrawal, | ||
WithdrawalBuffer, | ||
PrefixedHexString, | ||
WithdrawalBytes, | ||
WithdrawalData, | ||
@@ -38,19 +33,12 @@ } from '@ethereumjs/util' | ||
/** | ||
* Determine the HF by the block number | ||
* Set the hardfork either by timestamp (for HFs from Shanghai onwards) or by block number | ||
* for older Hfs. | ||
* | ||
* Additionally it is possible to pass in a specific TD value to support live-Merge-HF | ||
* transitions. Note that this should only be needed in very rare and specific scenarios. | ||
* | ||
* Default: `false` (HF is set to whatever default HF is set by the {@link Common} instance) | ||
*/ | ||
hardforkByBlockNumber?: boolean | ||
setHardfork?: boolean | BigIntLike | ||
/** | ||
* Determine the HF by total difficulty (Merge HF) | ||
* | ||
* This option is a superset of `hardforkByBlockNumber` (so only use one of both options) | ||
* and determines the HF by both the block number and the TD. | ||
* | ||
* Since the TTD is only a threshold the block number will in doubt take precedence (imagine | ||
* e.g. both Merge and Shanghai HF blocks set and the block number from the block provided | ||
* pointing to a Shanghai block: this will lead to set the HF as Shanghai and not the Merge). | ||
*/ | ||
hardforkByTTD?: BigIntLike | ||
/** | ||
* If a preceding {@link BlockHeader} (usually the parent header) is given the preceding | ||
@@ -80,3 +68,3 @@ * header will be used to calculate the difficulty for this block and the calculated | ||
*/ | ||
cliqueSigner?: Buffer | ||
cliqueSigner?: Uint8Array | ||
/** | ||
@@ -92,9 +80,9 @@ * Skip consensus format validation checks on header if set. Defaults to false. | ||
export interface HeaderData { | ||
parentHash?: BufferLike | ||
uncleHash?: BufferLike | ||
parentHash?: BytesLike | ||
uncleHash?: BytesLike | ||
coinbase?: AddressLike | ||
stateRoot?: BufferLike | ||
transactionsTrie?: BufferLike | ||
receiptTrie?: BufferLike | ||
logsBloom?: BufferLike | ||
stateRoot?: BytesLike | ||
transactionsTrie?: BytesLike | ||
receiptTrie?: BytesLike | ||
logsBloom?: BytesLike | ||
difficulty?: BigIntLike | ||
@@ -105,8 +93,10 @@ number?: BigIntLike | ||
timestamp?: BigIntLike | ||
extraData?: BufferLike | ||
mixHash?: BufferLike | ||
nonce?: BufferLike | ||
extraData?: BytesLike | ||
mixHash?: BytesLike | ||
nonce?: BytesLike | ||
baseFeePerGas?: BigIntLike | ||
withdrawalsRoot?: BufferLike | ||
withdrawalsRoot?: BytesLike | ||
dataGasUsed?: BigIntLike | ||
excessDataGas?: BigIntLike | ||
parentBeaconBlockRoot?: BytesLike | ||
} | ||
@@ -122,3 +112,3 @@ | ||
header?: HeaderData | ||
transactions?: Array<TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData> | ||
transactions?: Array<TxData[TransactionType]> | ||
uncleHeaders?: Array<HeaderData> | ||
@@ -128,14 +118,14 @@ withdrawals?: Array<WithdrawalData> | ||
export type WithdrawalsBuffer = WithdrawalBuffer[] | ||
export type WithdrawalsBytes = WithdrawalBytes[] | ||
export type BlockBuffer = | ||
| [BlockHeaderBuffer, TransactionsBuffer, UncleHeadersBuffer] | ||
| [BlockHeaderBuffer, TransactionsBuffer, UncleHeadersBuffer, WithdrawalsBuffer] | ||
export type BlockHeaderBuffer = Buffer[] | ||
export type BlockBodyBuffer = [TransactionsBuffer, UncleHeadersBuffer, WithdrawalsBuffer?] | ||
export type BlockBytes = | ||
| [BlockHeaderBytes, TransactionsBytes, UncleHeadersBytes] | ||
| [BlockHeaderBytes, TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes] | ||
export type BlockHeaderBytes = Uint8Array[] | ||
export type BlockBodyBytes = [TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes?] | ||
/** | ||
* TransactionsBuffer can be an array of serialized txs for Typed Transactions or an array of Buffer Arrays for legacy transactions. | ||
* TransactionsBytes can be an array of serialized txs for Typed Transactions or an array of Uint8Array Arrays for legacy transactions. | ||
*/ | ||
export type TransactionsBuffer = Buffer[][] | Buffer[] | ||
export type UncleHeadersBuffer = Buffer[][] | ||
export type TransactionsBytes = Uint8Array[][] | Uint8Array[] | ||
export type UncleHeadersBytes = Uint8Array[][] | ||
@@ -176,3 +166,5 @@ /** | ||
withdrawalsRoot?: string | ||
dataGasUsed?: string | ||
excessDataGas?: string | ||
parentBeaconBlockRoot?: string | ||
} | ||
@@ -207,3 +199,35 @@ | ||
withdrawalsRoot?: string // If EIP-4895 is enabled for this block, the root of the withdrawal trie of the block. | ||
dataGasUsed?: string // If EIP-4844 is enabled for this block, returns the data gas used for the block | ||
excessDataGas?: string // If EIP-4844 is enabled for this block, returns the excess data gas for the block | ||
parentBeaconBlockRoot?: string // If EIP-4788 is enabled for this block, returns parent beacon block root | ||
} | ||
// Note: all these strings are 0x-prefixed | ||
export type WithdrawalV1 = { | ||
index: PrefixedHexString // Quantity, 8 Bytes | ||
validatorIndex: PrefixedHexString // Quantity, 8 bytes | ||
address: PrefixedHexString // DATA, 20 bytes | ||
amount: PrefixedHexString // Quantity, 32 bytes | ||
} | ||
// Note: all these strings are 0x-prefixed | ||
export type ExecutionPayload = { | ||
parentHash: PrefixedHexString // DATA, 32 Bytes | ||
feeRecipient: PrefixedHexString // DATA, 20 Bytes | ||
stateRoot: PrefixedHexString // DATA, 32 Bytes | ||
receiptsRoot: PrefixedHexString // DATA, 32 bytes | ||
logsBloom: PrefixedHexString // DATA, 256 Bytes | ||
prevRandao: PrefixedHexString // DATA, 32 Bytes | ||
blockNumber: PrefixedHexString // QUANTITY, 64 Bits | ||
gasLimit: PrefixedHexString // QUANTITY, 64 Bits | ||
gasUsed: PrefixedHexString // QUANTITY, 64 Bits | ||
timestamp: PrefixedHexString // QUANTITY, 64 Bits | ||
extraData: PrefixedHexString // DATA, 0 to 32 Bytes | ||
baseFeePerGas: PrefixedHexString // QUANTITY, 256 Bits | ||
blockHash: PrefixedHexString // DATA, 32 Bytes | ||
transactions: PrefixedHexString[] // Array of DATA - Array of transaction rlp strings, | ||
withdrawals?: WithdrawalV1[] // Array of withdrawal objects | ||
dataGasUsed?: PrefixedHexString // QUANTITY, 64 Bits | ||
excessDataGas?: PrefixedHexString // QUANTITY, 64 Bits | ||
parentBeaconBlockRoot?: PrefixedHexString // QUANTITY, 64 Bits | ||
} |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
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
490006
1
88
6556
302
1
1
+ Added@ethereumjs/common@4.0.0-rc.1(transitive)
+ Added@ethereumjs/rlp@5.0.0-rc.1(transitive)
+ Added@ethereumjs/trie@6.0.0-rc.1(transitive)
+ Added@ethereumjs/tx@5.0.0-rc.1(transitive)
+ Added@ethereumjs/util@9.0.0-rc.1(transitive)
+ Addedcrc@4.3.2(transitive)
+ Addedlru-cache@10.4.3(transitive)
- Removed@ethereumjs/common@3.2.0(transitive)
- Removed@ethereumjs/rlp@4.0.1(transitive)
- Removed@ethereumjs/trie@5.1.0(transitive)
- Removed@ethereumjs/tx@4.2.0(transitive)
- Removed@ethereumjs/util@8.1.0(transitive)
- Removedcrc-32@1.2.2(transitive)
- Removedmicro-ftch@0.3.1(transitive)
Updated@ethereumjs/rlp@5.0.0-rc.1
Updated@ethereumjs/trie@6.0.0-rc.1
Updated@ethereumjs/tx@5.0.0-rc.1
Updated@ethereumjs/util@9.0.0-rc.1
Updatedethereum-cryptography@^2.1.2