@ethereumjs/vm
Version:
An Ethereum VM implementation
1,036 lines (944 loc) • 36.4 kB
text/typescript
import { createBlock, genRequestsRoot } from '@ethereumjs/block'
import { ConsensusType, Hardfork } from '@ethereumjs/common'
import { type EVM, type EVMInterface, VerkleAccessWitness } from '@ethereumjs/evm'
import { MerklePatriciaTrie } from '@ethereumjs/mpt'
import { RLP } from '@ethereumjs/rlp'
import { type StatelessVerkleStateManager, verifyVerkleStateProof } from '@ethereumjs/statemanager'
import { TransactionType } from '@ethereumjs/tx'
import {
Account,
Address,
BIGINT_0,
BIGINT_1,
BIGINT_8,
EthereumJSErrorWithoutCode,
GWEI_TO_WEI,
KECCAK256_RLP,
bigIntToAddressBytes,
bigIntToBytes,
bytesToHex,
concatBytes,
createAddressFromString,
equalsBytes,
hexToBytes,
intToBytes,
setLengthLeft,
short,
unprefixedHexToBytes,
} from '@ethereumjs/util'
import debugDefault from 'debug'
import { sha256 } from 'ethereum-cryptography/sha256.js'
import { Bloom } from './bloom/index.ts'
import { emitEVMProfile } from './emitEVMProfile.ts'
import { runTx } from './index.ts'
import { accumulateRequests } from './requests.ts'
import type { Block } from '@ethereumjs/block'
import type { Common } from '@ethereumjs/common'
import type { CLRequest, CLRequestType, PrefixedHexString } from '@ethereumjs/util'
import type {
AfterBlockEvent,
ApplyBlockResult,
PostByzantiumTxReceipt,
PreByzantiumTxReceipt,
RunBlockOpts,
RunBlockResult,
RunTxResult,
TxReceipt,
} from './types.ts'
import type { VM } from './vm.ts'
const debug = debugDefault('vm:block')
const parentBeaconBlockRootAddress = createAddressFromString(
'0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02',
)
let enableProfiler = false
const stateRootCPLabel = 'New state root, DAO HF, checkpoints, block validation'
const processTxsLabel = 'Tx processing [ use per-tx profiler for more details ]'
const withdrawalsRewardsCommitLabel = 'Withdrawals, Rewards, EVM journal commit'
const entireBlockLabel = 'Entire block'
/**
* Processes the `block` running all of the transactions it contains and updating the miner's account
*
* vm method modifies the state. If `generate` is `true`, the state modifications will be
* reverted if an exception is raised. If it's `false`, it won't revert if the block's header is
* invalid. If an error is thrown from an event handler, the state may or may not be reverted.
*
* @param {VM} vm
* @param {RunBlockOpts} opts - Default values for options:
* - `generate`: false
*/
export async function runBlock(vm: VM, opts: RunBlockOpts): Promise<RunBlockResult> {
if (vm['_opts'].profilerOpts?.reportAfterBlock === true) {
enableProfiler = true
// eslint-disable-next-line no-console
console.time(entireBlockLabel)
}
const stateManager = vm.stateManager
const { root } = opts
const clearCache = opts.clearCache ?? true
const setHardfork = opts.setHardfork ?? false
let { block } = opts
const generateFields = opts.generate === true
if (enableProfiler) {
const title = `Profiler run - Block ${block.header.number} (${bytesToHex(block.hash())} with ${
block.transactions.length
} txs`
// eslint-disable-next-line no-console
console.log(title)
// eslint-disable-next-line no-console
console.time(stateRootCPLabel)
}
/**
* The `beforeBlock` event.
*
* @event Event: beforeBlock
* @type {Object}
* @property {Block} block emits the block that is about to be processed
*/
await vm._emit('beforeBlock', block)
if (setHardfork !== false || vm['_setHardfork'] !== false) {
const setHardforkUsed = setHardfork ?? vm['_setHardfork']
if (setHardforkUsed === true) {
vm.common.setHardforkBy({
blockNumber: block.header.number,
timestamp: block.header.timestamp,
})
}
}
if (vm.DEBUG) {
debug('-'.repeat(100))
debug(
`Running block hash=${bytesToHex(block.hash())} number=${
block.header.number
} hardfork=${vm.common.hardfork()}`,
)
}
// Set state root if provided
if (root) {
if (vm.DEBUG) {
debug(`Set provided state root ${bytesToHex(root)} clearCache=${clearCache}`)
}
await stateManager.setStateRoot(root, clearCache)
}
if (vm.common.isActivatedEIP(6800) || vm.common.isActivatedEIP(7864)) {
// Initialize the access witness
if (vm.common.customCrypto.verkle === undefined) {
throw Error('verkleCrypto required when EIP-6800 is active')
}
vm.evm.verkleAccessWitness = new VerkleAccessWitness({
verkleCrypto: vm.common.customCrypto.verkle,
})
vm.evm.systemVerkleAccessWitness = new VerkleAccessWitness({
verkleCrypto: vm.common.customCrypto.verkle,
})
if (typeof stateManager.initVerkleExecutionWitness !== 'function') {
throw Error(`VerkleStateManager needed for execution of verkle blocks`)
}
if (vm.DEBUG) {
debug(`Initializing executionWitness`)
}
if (clearCache) {
stateManager.clearCaches()
}
// Populate the execution witness
stateManager.initVerkleExecutionWitness!(block.header.number, block.executionWitness)
// Check if statemanager is a Verkle State Manager (stateless and stateful both have verifyVerklePostState)
if ('verifyVerklePostState' in stateManager) {
// Update the stateRoot cache
await stateManager.setStateRoot(block.header.stateRoot)
if (verifyVerkleStateProof(stateManager as StatelessVerkleStateManager) === true) {
if (vm.DEBUG) {
debug(`Verkle proof verification succeeded`)
}
} else {
throw Error(`Verkle proof verification failed`)
}
}
} else {
if (typeof stateManager.initVerkleExecutionWitness === 'function') {
throw Error(`StatelessVerkleStateManager can't execute merkle blocks`)
}
}
// check for DAO support and if we should apply the DAO fork
if (
vm.common.hardforkIsActiveOnBlock(Hardfork.Dao, block.header.number) &&
block.header.number === vm.common.hardforkBlock(Hardfork.Dao)!
) {
if (vm.DEBUG) {
debug(`Apply DAO hardfork`)
}
await vm.evm.journal.checkpoint()
await _applyDAOHardfork(vm.evm)
await vm.evm.journal.commit()
}
// Checkpoint state
await vm.evm.journal.checkpoint()
if (vm.DEBUG) {
debug(`block checkpoint`)
}
let result: ApplyBlockResult
try {
result = await applyBlock(vm, block, opts)
if (vm.DEBUG) {
debug(
`Received block results gasUsed=${result.gasUsed} bloom=${short(result.bloom.bitvector)} (${
result.bloom.bitvector.length
} bytes) receiptsRoot=${bytesToHex(result.receiptsRoot)} receipts=${
result.receipts.length
} txResults=${result.results.length}`,
)
}
} catch (err: any) {
await vm.evm.journal.revert()
if (vm.DEBUG) {
debug(`block checkpoint reverted`)
}
if (enableProfiler) {
// eslint-disable-next-line no-console
console.timeEnd(withdrawalsRewardsCommitLabel)
}
throw err
}
let requestsHash: Uint8Array | undefined
let requests: CLRequest<CLRequestType>[] | undefined
if (block.common.isActivatedEIP(7685)) {
const sha256Function = vm.common.customCrypto.sha256 ?? sha256
requests = await accumulateRequests(vm, result.results)
requestsHash = genRequestsRoot(requests, sha256Function)
}
// Persist state
await vm.evm.journal.commit()
if (vm.DEBUG) {
debug(`block checkpoint committed`)
}
const stateRoot = await stateManager.getStateRoot()
// Given the generate option, either set resulting header
// values to the current block, or validate the resulting
// header values against the current block.
if (generateFields) {
const logsBloom = result.bloom.bitvector
const gasUsed = result.gasUsed
const receiptTrie = result.receiptsRoot
const transactionsTrie = await _genTxTrie(block)
const generatedFields = {
stateRoot,
logsBloom,
gasUsed,
receiptTrie,
transactionsTrie,
requestsHash,
}
const blockData = {
...block,
header: { ...block.header, ...generatedFields },
}
block = createBlock(blockData, { common: vm.common })
} else {
if (vm.common.isActivatedEIP(7685)) {
if (!equalsBytes(block.header.requestsHash!, requestsHash!)) {
if (vm.DEBUG)
debug(
`Invalid requestsHash received=${bytesToHex(
block.header.requestsHash!,
)} expected=${bytesToHex(requestsHash!)}`,
)
const msg = _errorMsg('invalid requestsHash', vm, block)
throw EthereumJSErrorWithoutCode(msg)
}
}
// Check if statemanager is a StatelessVerkleStateManager by checking for a method only on StatelessVerkleStateManager API
if (!('verifyVerklePostState' in vm.stateManager)) {
// Only validate the following headers if Stateless isn't activated
if (equalsBytes(result.receiptsRoot, block.header.receiptTrie) === false) {
if (vm.DEBUG) {
debug(
`Invalid receiptTrie received=${bytesToHex(result.receiptsRoot)} expected=${bytesToHex(
block.header.receiptTrie,
)}`,
)
}
const msg = _errorMsg('invalid receiptTrie', vm, block)
throw EthereumJSErrorWithoutCode(msg)
}
if (!(equalsBytes(result.bloom.bitvector, block.header.logsBloom) === true)) {
if (vm.DEBUG) {
debug(
`Invalid bloom received=${bytesToHex(result.bloom.bitvector)} expected=${bytesToHex(
block.header.logsBloom,
)}`,
)
}
const msg = _errorMsg('invalid bloom', vm, block)
throw EthereumJSErrorWithoutCode(msg)
}
if (result.gasUsed !== block.header.gasUsed) {
if (vm.DEBUG) {
debug(`Invalid gasUsed received=${result.gasUsed} expected=${block.header.gasUsed}`)
}
const msg = _errorMsg('invalid gasUsed', vm, block)
throw EthereumJSErrorWithoutCode(msg)
}
if (!(equalsBytes(stateRoot, block.header.stateRoot) === true)) {
if (vm.DEBUG) {
debug(
`Invalid stateRoot received=${bytesToHex(stateRoot)} expected=${bytesToHex(
block.header.stateRoot,
)}`,
)
}
const msg = _errorMsg(
`invalid block stateRoot, got: ${bytesToHex(stateRoot)}, want: ${bytesToHex(
block.header.stateRoot,
)}`,
vm,
block,
)
throw EthereumJSErrorWithoutCode(msg)
}
}
if (vm.common.isActivatedEIP(6800)) {
if (vm.evm.verkleAccessWitness === undefined) {
throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`)
}
// If verkle is activated and executing statelessly, only validate the post-state
if (
(await vm['_opts'].stateManager!.verifyVerklePostState!(vm.evm.verkleAccessWitness)) ===
false
) {
throw EthereumJSErrorWithoutCode(
`Verkle post state verification failed on block ${block.header.number}`,
)
}
debug(`Verkle post state verification succeeded`)
} else if (vm.common.isActivatedEIP(7864)) {
if (vm.evm.binaryTreeAccessWitness === undefined) {
throw Error(`binaryTreeAccessWitness required if binary tree (EIP-7864) is activated`)
}
// If binary tree is activated and executing statelessly, only validate the post-state
if (
(await vm['_opts'].stateManager!.verifyBinaryTreePostState!(
vm.evm.binaryTreeAccessWitness,
)) === false
) {
throw EthereumJSErrorWithoutCode(
`Binary tree post state verification failed on block ${block.header.number}`,
)
}
debug(`Binary tree post state verification succeeded`)
}
}
if (enableProfiler) {
// eslint-disable-next-line no-console
console.timeEnd(withdrawalsRewardsCommitLabel)
}
const results: RunBlockResult = {
receipts: result.receipts,
logsBloom: result.bloom.bitvector,
results: result.results,
stateRoot,
gasUsed: result.gasUsed,
receiptsRoot: result.receiptsRoot,
preimages: result.preimages,
requestsHash,
requests,
}
const afterBlockEvent: AfterBlockEvent = { ...results, block }
/**
* The `afterBlock` event
*
* @event Event: afterBlock
* @type {AfterBlockEvent}
* @property {AfterBlockEvent} result emits the results of processing a block
*/
await vm._emit('afterBlock', afterBlockEvent)
if (vm.DEBUG) {
debug(
`Running block finished hash=${bytesToHex(block.hash())} number=${
block.header.number
} hardfork=${vm.common.hardfork()}`,
)
}
if (enableProfiler) {
// eslint-disable-next-line no-console
console.timeEnd(entireBlockLabel)
const logs = (vm.evm as EVM).getPerformanceLogs()
if (logs.precompiles.length === 0 && logs.opcodes.length === 0) {
// eslint-disable-next-line no-console
console.log('No block txs with precompile or opcode execution.')
}
emitEVMProfile(logs.precompiles, 'Precompile performance')
emitEVMProfile(logs.opcodes, 'Opcodes performance')
;(vm.evm as EVM).clearPerformanceLogs()
}
return results
}
/**
* Validates and applies a block, computing the results of
* applying its transactions. vm method doesn't modify the
* block itself. It computes the block rewards and puts
* them on state (but doesn't persist the changes).
* @param {Block} block
* @param {RunBlockOpts} opts
*/
async function applyBlock(vm: VM, block: Block, opts: RunBlockOpts): Promise<ApplyBlockResult> {
// Validate block
if (opts.skipBlockValidation !== true) {
if (block.header.gasLimit >= BigInt('0x8000000000000000')) {
const msg = _errorMsg('Invalid block with gas limit greater than (2^63 - 1)', vm, block)
throw EthereumJSErrorWithoutCode(msg)
} else {
if (vm.DEBUG) {
debug(`Validate block`)
}
// TODO: decide what block validation method is appropriate here
if (opts.skipHeaderValidation !== true) {
if (typeof (vm.blockchain as any).validateHeader === 'function') {
await (vm.blockchain as any).validateHeader(block.header)
} else {
throw EthereumJSErrorWithoutCode(
'cannot validate header: blockchain has no `validateHeader` method',
)
}
}
await block.validateData()
}
}
if (vm.common.isActivatedEIP(4788)) {
if (vm.DEBUG) {
debug(`accumulate parentBeaconBlockRoot`)
}
await accumulateParentBeaconBlockRoot(
vm,
block.header.parentBeaconBlockRoot!,
block.header.timestamp,
)
}
if (vm.common.isActivatedEIP(2935)) {
if (vm.DEBUG) {
debug(`accumulate parentBlockHash `)
}
await accumulateParentBlockHash(vm, block.header.number, block.header.parentHash)
}
if (enableProfiler) {
// eslint-disable-next-line no-console
console.timeEnd(stateRootCPLabel)
}
// Apply transactions
if (vm.DEBUG) {
debug(`Apply transactions`)
}
const blockResults = await applyTransactions(vm, block, opts)
if (enableProfiler) {
// eslint-disable-next-line no-console
console.time(withdrawalsRewardsCommitLabel)
}
// Add txResult preimages to the blockResults preimages
// Also add the coinbase preimage
if (opts.reportPreimages === true) {
if (vm.evm.stateManager.getAppliedKey === undefined) {
throw EthereumJSErrorWithoutCode(
'applyBlock: evm.stateManager.getAppliedKey can not be undefined if reportPreimages is true',
)
}
blockResults.preimages.set(
bytesToHex(vm.evm.stateManager.getAppliedKey(block.header.coinbase.toBytes())),
block.header.coinbase.toBytes(),
)
for (const txResult of blockResults.results) {
if (txResult.preimages !== undefined) {
for (const [key, preimage] of txResult.preimages) {
blockResults.preimages.set(key, preimage)
}
}
}
}
if (vm.common.isActivatedEIP(4895)) {
if (opts.reportPreimages === true) vm.evm.journal.startReportingPreimages!()
await assignWithdrawals(vm, block)
if (opts.reportPreimages === true && vm.evm.journal.preimages !== undefined) {
for (const [key, preimage] of vm.evm.journal.preimages) {
blockResults.preimages.set(key, preimage)
}
}
await vm.evm.journal.cleanup()
}
// Pay ommers and miners
if (block.common.consensusType() === ConsensusType.ProofOfWork) {
await assignBlockRewards(vm, block)
}
// Merge system AccessWitness with AccessWitness
if (vm.common.isActivatedEIP(6800) && vm.evm.systemVerkleAccessWitness !== undefined) {
vm.evm.systemVerkleAccessWitness?.commit()
if (vm.DEBUG) {
debug('Verkle access witness aggregate costs:')
vm.evm.verkleAccessWitness?.debugWitnessCost()
debug('System verkle access witness aggregate costs:')
vm.evm.systemVerkleAccessWitness?.debugWitnessCost()
}
vm.evm.verkleAccessWitness?.merge(vm.evm.systemVerkleAccessWitness)
}
if (vm.common.isActivatedEIP(7864) && vm.evm.systemBinaryTreeAccessWitness !== undefined) {
vm.evm.systemBinaryTreeAccessWitness?.commit()
if (vm.DEBUG) {
debug('Binary tree access witness aggregate costs:')
vm.evm.binaryTreeAccessWitness?.debugWitnessCost()
debug('System binary tree access witness aggregate costs:')
vm.evm.systemBinaryTreeAccessWitness?.debugWitnessCost()
}
vm.evm.binaryTreeAccessWitness?.merge(vm.evm.systemBinaryTreeAccessWitness)
}
return blockResults
}
/**
* vm method runs the logic of EIP 2935 (save blockhashes to state)
* It will put the `parentHash` of the block to the storage slot of `block.number - 1` of the history storage contract.
* vm contract is used to retrieve BLOCKHASHes in EVM if EIP 2935 is activated.
* In case that the previous block of `block` is pre-EIP-2935 (so we are on the EIP 2935 fork block), additionally
* also add the currently available past blockhashes which are available by BLOCKHASH (so, the past 256 block hashes)
* @param vm The VM to run on
* @param block The current block to save the parent block hash of
*/
export async function accumulateParentBlockHash(
vm: VM,
currentBlockNumber: bigint,
parentHash: Uint8Array,
) {
if (!vm.common.isActivatedEIP(2935)) {
throw EthereumJSErrorWithoutCode(
'Cannot call `accumulateParentBlockHash`: EIP 2935 is not active',
)
}
const historyAddress = new Address(bigIntToAddressBytes(vm.common.param('historyStorageAddress')))
const historyServeWindow = vm.common.param('historyServeWindow')
// getAccount with historyAddress will throw error as witnesses are not bundled
// but we need to put account so as to query later for slot
const code = await vm.stateManager.getCode(historyAddress)
if (code.length === 0) {
// Exit early, system contract has no code so no storage is written
return
}
async function putBlockHash(vm: VM, hash: Uint8Array, number: bigint) {
// ringKey is the key the hash is actually put in (it is a ring buffer)
const ringKey = number % historyServeWindow
// generate access witness
if (vm.common.isActivatedEIP(6800)) {
if (vm.evm.systemVerkleAccessWitness === undefined) {
throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`)
}
// Add to system verkle access witness so that it doesn't warm up tx accesses
vm.evm.systemVerkleAccessWitness.writeAccountStorage(historyAddress, ringKey)
} else if (vm.common.isActivatedEIP(7864)) {
if (vm.evm.systemBinaryTreeAccessWitness === undefined) {
throw Error(`systemBinaryTreeAccessWitness required if binary tree (EIP-7864) is activated`)
}
// Add to system binary tree access witness so that it doesn't warm up tx accesses
vm.evm.systemBinaryTreeAccessWitness.writeAccountStorage(historyAddress, ringKey)
}
const key = setLengthLeft(bigIntToBytes(ringKey), 32)
await vm.stateManager.putStorage(historyAddress, key, hash)
}
await putBlockHash(vm, parentHash, currentBlockNumber - BIGINT_1)
// do cleanup if the code was not deployed
await vm.evm.journal.cleanup()
}
export async function accumulateParentBeaconBlockRoot(vm: VM, root: Uint8Array, timestamp: bigint) {
if (!vm.common.isActivatedEIP(4788)) {
throw EthereumJSErrorWithoutCode(
'Cannot call `accumulateParentBeaconBlockRoot`: EIP 4788 is not active',
)
}
// Save the parentBeaconBlockRoot to the beaconroot stateful precompile ring buffers
const historicalRootsLength = BigInt(vm.common.param('historicalRootsLength'))
const timestampIndex = timestamp % historicalRootsLength
const timestampExtended = timestampIndex + historicalRootsLength
/**
* Note: (by Gabriel)
* Get account will throw an error in stateless execution b/c witnesses are not bundled
* But we do need an account so we are able to put the storage
*/
const code = await vm.stateManager.getCode(parentBeaconBlockRootAddress)
if (code.length === 0) {
// Exit early, system contract has no code so no storage is written
// TODO: verify with Gabriel that this is fine regarding verkle (should we put an empty account?)
return
}
await vm.stateManager.putStorage(
parentBeaconBlockRootAddress,
setLengthLeft(bigIntToBytes(timestampIndex), 32),
bigIntToBytes(timestamp),
)
await vm.stateManager.putStorage(
parentBeaconBlockRootAddress,
setLengthLeft(bigIntToBytes(timestampExtended), 32),
root,
)
// do cleanup if the code was not deployed
await vm.evm.journal.cleanup()
}
/**
* Applies the transactions in a block, computing the receipts
* as well as gas usage and some relevant data. vm method is
* side-effect free (it doesn't modify the block nor the state).
* @param {Block} block
* @param {RunBlockOpts} opts
*/
async function applyTransactions(vm: VM, block: Block, opts: RunBlockOpts) {
if (enableProfiler) {
// eslint-disable-next-line no-console
console.time(processTxsLabel)
}
const bloom = new Bloom(undefined, vm.common)
// the total amount of gas used processing these transactions
let gasUsed = BIGINT_0
let receiptTrie: MerklePatriciaTrie | undefined = undefined
if (block.transactions.length !== 0) {
receiptTrie = new MerklePatriciaTrie({ common: vm.common })
}
const receipts: TxReceipt[] = []
const txResults: RunTxResult[] = []
/*
* Process transactions
*/
for (let txIdx = 0; txIdx < block.transactions.length; txIdx++) {
const tx = block.transactions[txIdx]
let maxGasLimit
if (vm.common.isActivatedEIP(1559)) {
maxGasLimit = block.header.gasLimit * vm.common.param('elasticityMultiplier')
} else {
maxGasLimit = block.header.gasLimit
}
const gasLimitIsHigherThanBlock = maxGasLimit < tx.gasLimit + gasUsed
if (gasLimitIsHigherThanBlock) {
const msg = _errorMsg('tx has a higher gas limit than the block', vm, block)
throw EthereumJSErrorWithoutCode(msg)
}
// Run the tx through the VM
const { skipBalance, skipNonce, skipHardForkValidation, reportPreimages } = opts
const txRes = await runTx(vm, {
tx,
block,
skipBalance,
skipNonce,
skipHardForkValidation,
blockGasUsed: gasUsed,
reportPreimages,
})
txResults.push(txRes)
if (vm.DEBUG) {
debug('-'.repeat(100))
}
// Add to total block gas usage
gasUsed += txRes.totalGasSpent
if (vm.DEBUG) {
debug(`Add tx gas used (${txRes.totalGasSpent}) to total block gas usage (-> ${gasUsed})`)
}
// Combine blooms via bitwise OR
bloom.or(txRes.bloom)
// Add receipt to trie to later calculate receipt root
receipts.push(txRes.receipt)
const encodedReceipt = encodeReceipt(txRes.receipt, tx.type)
await receiptTrie!.put(RLP.encode(txIdx), encodedReceipt)
}
if (enableProfiler) {
// eslint-disable-next-line no-console
console.timeEnd(processTxsLabel)
}
const receiptsRoot = receiptTrie !== undefined ? receiptTrie.root() : KECCAK256_RLP
return {
bloom,
gasUsed,
preimages: new Map<PrefixedHexString, Uint8Array>(),
receiptsRoot,
receipts,
results: txResults,
}
}
async function assignWithdrawals(vm: VM, block: Block): Promise<void> {
const withdrawals = block.withdrawals!
for (const withdrawal of withdrawals) {
const { address, amount } = withdrawal
// Withdrawal amount is represented in Gwei so needs to be
// converted to wei
// Note: event if amount is 0, still reward the account
// such that the account is touched and marked for cleanup if it is empty
await rewardAccount(vm.evm, address, amount * GWEI_TO_WEI, vm.common)
}
}
/**
* Calculates block rewards for miner and ommers and puts
* the updated balances of their accounts to state.
*/
async function assignBlockRewards(vm: VM, block: Block): Promise<void> {
if (vm.DEBUG) {
debug(`Assign block rewards`)
}
const minerReward = vm.common.param('minerReward')
const ommers = block.uncleHeaders
// Reward ommers
for (const ommer of ommers) {
const reward = calculateOmmerReward(ommer.number, block.header.number, minerReward)
const account = await rewardAccount(vm.evm, ommer.coinbase, reward, vm.common)
if (vm.DEBUG) {
debug(`Add uncle reward ${reward} to account ${ommer.coinbase} (-> ${account.balance})`)
}
}
// Reward miner
const reward = calculateMinerReward(minerReward, ommers.length)
const account = await rewardAccount(vm.evm, block.header.coinbase, reward, vm.common)
if (vm.DEBUG) {
debug(`Add miner reward ${reward} to account ${block.header.coinbase} (-> ${account.balance})`)
}
}
function calculateOmmerReward(
ommerBlockNumber: bigint,
blockNumber: bigint,
minerReward: bigint,
): bigint {
const heightDiff = blockNumber - ommerBlockNumber
let reward = ((BIGINT_8 - heightDiff) * minerReward) / BIGINT_8
if (reward < BIGINT_0) {
reward = BIGINT_0
}
return reward
}
export function calculateMinerReward(minerReward: bigint, ommersNum: number): bigint {
// calculate nibling reward
const niblingReward = minerReward / BigInt(32)
const totalNiblingReward = niblingReward * BigInt(ommersNum)
const reward = minerReward + totalNiblingReward
return reward
}
export async function rewardAccount(
evm: EVMInterface,
address: Address,
reward: bigint,
common: Common,
): Promise<Account> {
let account = await evm.stateManager.getAccount(address)
if (account === undefined) {
if (common.isActivatedEIP(6800) === true && reward !== BIGINT_0) {
if (evm.systemVerkleAccessWitness === undefined) {
throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`)
}
evm.systemVerkleAccessWitness.writeAccountHeader(address)
}
if (common.isActivatedEIP(7864) === true && reward !== BIGINT_0) {
if (evm.systemBinaryTreeAccessWitness === undefined) {
throw Error(`systemBinaryTreeAccessWitness required if binary tree (EIP-7864) is activated`)
}
evm.systemBinaryTreeAccessWitness.writeAccountHeader(address)
}
account = new Account()
}
account.balance += reward
await evm.journal.putAccount(address, account)
if (common.isActivatedEIP(6800) === true && reward !== BIGINT_0) {
if (evm.systemVerkleAccessWitness === undefined) {
throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`)
}
// use vm utility to build access but the computed gas is not charged and hence free
evm.systemVerkleAccessWitness.writeAccountBasicData(address)
evm.systemVerkleAccessWitness.readAccountCodeHash(address)
}
if (common.isActivatedEIP(7864) === true && reward !== BIGINT_0) {
if (evm.systemBinaryTreeAccessWitness === undefined) {
throw Error(`systemBinaryTreeAccessWitness required if binary tree (EIP-7864) is activated`)
}
evm.systemBinaryTreeAccessWitness.writeAccountBasicData(address)
evm.systemBinaryTreeAccessWitness.readAccountCodeHash(address)
}
return account
}
/**
* Returns the encoded tx receipt.
*/
export function encodeReceipt(
receipt: TxReceipt,
txType: (typeof TransactionType)[keyof typeof TransactionType],
) {
const encoded = RLP.encode([
(receipt as PreByzantiumTxReceipt).stateRoot ??
((receipt as PostByzantiumTxReceipt).status === 0 ? Uint8Array.from([]) : hexToBytes('0x01')),
bigIntToBytes(receipt.cumulativeBlockGasUsed),
receipt.bitvector,
receipt.logs,
])
if (txType === TransactionType.Legacy) {
return encoded
}
// Serialize receipt according to EIP-2718:
// `typed-receipt = tx-type || receipt-data`
return concatBytes(intToBytes(txType), encoded)
}
/**
* Apply the DAO fork changes to the VM
*/
async function _applyDAOHardfork(evm: EVMInterface) {
const state = evm.stateManager
/* DAO account list */
const DAOAccountList = DAOConfig.DAOAccounts
const DAORefundContract = DAOConfig.DAORefundContract
const DAORefundContractAddress = new Address(unprefixedHexToBytes(DAORefundContract))
if ((await state.getAccount(DAORefundContractAddress)) === undefined) {
await evm.journal.putAccount(DAORefundContractAddress, new Account())
}
let DAORefundAccount = await state.getAccount(DAORefundContractAddress)
if (DAORefundAccount === undefined) {
DAORefundAccount = new Account()
}
for (const addr of DAOAccountList) {
// retrieve the account and add it to the DAO's Refund accounts' balance.
const address = new Address(unprefixedHexToBytes(addr))
let account = await state.getAccount(address)
if (account === undefined) {
account = new Account()
}
DAORefundAccount.balance += account.balance
// clear the accounts' balance
account.balance = BIGINT_0
await evm.journal.putAccount(address, account)
}
// finally, put the Refund Account
await evm.journal.putAccount(DAORefundContractAddress, DAORefundAccount)
}
async function _genTxTrie(block: Block) {
if (block.transactions.length === 0) {
return KECCAK256_RLP
}
const trie = new MerklePatriciaTrie({ common: block.common })
for (const [i, tx] of block.transactions.entries()) {
await trie.put(RLP.encode(i), tx.serialize())
}
return trie.root()
}
/**
* Internal helper function to create an annotated error message
*
* @param msg Base error message
* @hidden
*/
function _errorMsg(msg: string, vm: VM, block: Block) {
const blockErrorStr = 'errorStr' in block ? block.errorStr() : 'block'
const errorMsg = `${msg} (${vm.errorStr()} -> ${blockErrorStr})`
return errorMsg
}
const DAOConfig = {
DAOAccounts: [
'd4fe7bc31cedb7bfb8a345f31e668033056b2728',
'b3fb0e5aba0e20e5c49d252dfd30e102b171a425',
'2c19c7f9ae8b751e37aeb2d93a699722395ae18f',
'ecd135fa4f61a655311e86238c92adcd779555d2',
'1975bd06d486162d5dc297798dfc41edd5d160a7',
'a3acf3a1e16b1d7c315e23510fdd7847b48234f6',
'319f70bab6845585f412ec7724b744fec6095c85',
'06706dd3f2c9abf0a21ddcc6941d9b86f0596936',
'5c8536898fbb74fc7445814902fd08422eac56d0',
'6966ab0d485353095148a2155858910e0965b6f9',
'779543a0491a837ca36ce8c635d6154e3c4911a6',
'2a5ed960395e2a49b1c758cef4aa15213cfd874c',
'5c6e67ccd5849c0d29219c4f95f1a7a93b3f5dc5',
'9c50426be05db97f5d64fc54bf89eff947f0a321',
'200450f06520bdd6c527622a273333384d870efb',
'be8539bfe837b67d1282b2b1d61c3f723966f049',
'6b0c4d41ba9ab8d8cfb5d379c69a612f2ced8ecb',
'f1385fb24aad0cd7432824085e42aff90886fef5',
'd1ac8b1ef1b69ff51d1d401a476e7e612414f091',
'8163e7fb499e90f8544ea62bbf80d21cd26d9efd',
'51e0ddd9998364a2eb38588679f0d2c42653e4a6',
'627a0a960c079c21c34f7612d5d230e01b4ad4c7',
'f0b1aa0eb660754448a7937c022e30aa692fe0c5',
'24c4d950dfd4dd1902bbed3508144a54542bba94',
'9f27daea7aca0aa0446220b98d028715e3bc803d',
'a5dc5acd6a7968a4554d89d65e59b7fd3bff0f90',
'd9aef3a1e38a39c16b31d1ace71bca8ef58d315b',
'63ed5a272de2f6d968408b4acb9024f4cc208ebf',
'6f6704e5a10332af6672e50b3d9754dc460dfa4d',
'77ca7b50b6cd7e2f3fa008e24ab793fd56cb15f6',
'492ea3bb0f3315521c31f273e565b868fc090f17',
'0ff30d6de14a8224aa97b78aea5388d1c51c1f00',
'9ea779f907f0b315b364b0cfc39a0fde5b02a416',
'ceaeb481747ca6c540a000c1f3641f8cef161fa7',
'cc34673c6c40e791051898567a1222daf90be287',
'579a80d909f346fbfb1189493f521d7f48d52238',
'e308bd1ac5fda103967359b2712dd89deffb7973',
'4cb31628079fb14e4bc3cd5e30c2f7489b00960c',
'ac1ecab32727358dba8962a0f3b261731aad9723',
'4fd6ace747f06ece9c49699c7cabc62d02211f75',
'440c59b325d2997a134c2c7c60a8c61611212bad',
'4486a3d68fac6967006d7a517b889fd3f98c102b',
'9c15b54878ba618f494b38f0ae7443db6af648ba',
'27b137a85656544b1ccb5a0f2e561a5703c6a68f',
'21c7fdb9ed8d291d79ffd82eb2c4356ec0d81241',
'23b75c2f6791eef49c69684db4c6c1f93bf49a50',
'1ca6abd14d30affe533b24d7a21bff4c2d5e1f3b',
'b9637156d330c0d605a791f1c31ba5890582fe1c',
'6131c42fa982e56929107413a9d526fd99405560',
'1591fc0f688c81fbeb17f5426a162a7024d430c2',
'542a9515200d14b68e934e9830d91645a980dd7a',
'c4bbd073882dd2add2424cf47d35213405b01324',
'782495b7b3355efb2833d56ecb34dc22ad7dfcc4',
'58b95c9a9d5d26825e70a82b6adb139d3fd829eb',
'3ba4d81db016dc2890c81f3acec2454bff5aada5',
'b52042c8ca3f8aa246fa79c3feaa3d959347c0ab',
'e4ae1efdfc53b73893af49113d8694a057b9c0d1',
'3c02a7bc0391e86d91b7d144e61c2c01a25a79c5',
'0737a6b837f97f46ebade41b9bc3e1c509c85c53',
'97f43a37f595ab5dd318fb46e7a155eae057317a',
'52c5317c848ba20c7504cb2c8052abd1fde29d03',
'4863226780fe7c0356454236d3b1c8792785748d',
'5d2b2e6fcbe3b11d26b525e085ff818dae332479',
'5f9f3392e9f62f63b8eac0beb55541fc8627f42c',
'057b56736d32b86616a10f619859c6cd6f59092a',
'9aa008f65de0b923a2a4f02012ad034a5e2e2192',
'304a554a310c7e546dfe434669c62820b7d83490',
'914d1b8b43e92723e64fd0a06f5bdb8dd9b10c79',
'4deb0033bb26bc534b197e61d19e0733e5679784',
'07f5c1e1bc2c93e0402f23341973a0e043f7bf8a',
'35a051a0010aba705c9008d7a7eff6fb88f6ea7b',
'4fa802324e929786dbda3b8820dc7834e9134a2a',
'9da397b9e80755301a3b32173283a91c0ef6c87e',
'8d9edb3054ce5c5774a420ac37ebae0ac02343c6',
'0101f3be8ebb4bbd39a2e3b9a3639d4259832fd9',
'5dc28b15dffed94048d73806ce4b7a4612a1d48f',
'bcf899e6c7d9d5a215ab1e3444c86806fa854c76',
'12e626b0eebfe86a56d633b9864e389b45dcb260',
'a2f1ccba9395d7fcb155bba8bc92db9bafaeade7',
'ec8e57756626fdc07c63ad2eafbd28d08e7b0ca5',
'd164b088bd9108b60d0ca3751da4bceb207b0782',
'6231b6d0d5e77fe001c2a460bd9584fee60d409b',
'1cba23d343a983e9b5cfd19496b9a9701ada385f',
'a82f360a8d3455c5c41366975bde739c37bfeb8a',
'9fcd2deaff372a39cc679d5c5e4de7bafb0b1339',
'005f5cee7a43331d5a3d3eec71305925a62f34b6',
'0e0da70933f4c7849fc0d203f5d1d43b9ae4532d',
'd131637d5275fd1a68a3200f4ad25c71a2a9522e',
'bc07118b9ac290e4622f5e77a0853539789effbe',
'47e7aa56d6bdf3f36be34619660de61275420af8',
'acd87e28b0c9d1254e868b81cba4cc20d9a32225',
'adf80daec7ba8dcf15392f1ac611fff65d94f880',
'5524c55fb03cf21f549444ccbecb664d0acad706',
'40b803a9abce16f50f36a77ba41180eb90023925',
'fe24cdd8648121a43a7c86d289be4dd2951ed49f',
'17802f43a0137c506ba92291391a8a8f207f487d',
'253488078a4edf4d6f42f113d1e62836a942cf1a',
'86af3e9626fce1957c82e88cbf04ddf3a2ed7915',
'b136707642a4ea12fb4bae820f03d2562ebff487',
'dbe9b615a3ae8709af8b93336ce9b477e4ac0940',
'f14c14075d6c4ed84b86798af0956deef67365b5',
'ca544e5c4687d109611d0f8f928b53a25af72448',
'aeeb8ff27288bdabc0fa5ebb731b6f409507516c',
'cbb9d3703e651b0d496cdefb8b92c25aeb2171f7',
'6d87578288b6cb5549d5076a207456a1f6a63dc0',
'b2c6f0dfbb716ac562e2d85d6cb2f8d5ee87603e',
'accc230e8a6e5be9160b8cdf2864dd2a001c28b6',
'2b3455ec7fedf16e646268bf88846bd7a2319bb2',
'4613f3bca5c44ea06337a9e439fbc6d42e501d0a',
'd343b217de44030afaa275f54d31a9317c7f441e',
'84ef4b2357079cd7a7c69fd7a37cd0609a679106',
'da2fef9e4a3230988ff17df2165440f37e8b1708',
'f4c64518ea10f995918a454158c6b61407ea345c',
'7602b46df5390e432ef1c307d4f2c9ff6d65cc97',
'bb9bc244d798123fde783fcc1c72d3bb8c189413',
'807640a13483f8ac783c557fcdf27be11ea4ac7a',
],
DAORefundContract: 'bf4ed7b27f1d666546e30d74d50d173d20bca754',
}