@ethereumjs/vm
Advanced tools
Comparing version 7.1.0 to 7.2.0
@@ -0,7 +1,9 @@ | ||
import type { Common } from '@ethereumjs/common'; | ||
export declare class Bloom { | ||
bitvector: Uint8Array; | ||
keccakFunction: (msg: Uint8Array) => Uint8Array; | ||
/** | ||
* Represents a Bloom filter. | ||
*/ | ||
constructor(bitvector?: Uint8Array); | ||
constructor(bitvector?: Uint8Array, common?: Common); | ||
/** | ||
@@ -8,0 +10,0 @@ * Adds an element to a bit vector of a 64 byte bloom filter. |
@@ -11,3 +11,9 @@ "use strict"; | ||
*/ | ||
constructor(bitvector) { | ||
constructor(bitvector, common) { | ||
if (common?.customCrypto.keccak256 !== undefined) { | ||
this.keccakFunction = common.customCrypto.keccak256; | ||
} | ||
else { | ||
this.keccakFunction = keccak_js_1.keccak256; | ||
} | ||
if (!bitvector) { | ||
@@ -27,3 +33,3 @@ this.bitvector = (0, util_1.zeros)(BYTE_SIZE); | ||
add(e) { | ||
e = (0, keccak_js_1.keccak256)(e); | ||
e = this.keccakFunction(e); | ||
const mask = 2047; // binary 11111111111 | ||
@@ -43,3 +49,3 @@ for (let i = 0; i < 3; i++) { | ||
check(e) { | ||
e = (0, keccak_js_1.keccak256)(e); | ||
e = this.keccakFunction(e); | ||
const mask = 2047; // binary 11111111111 | ||
@@ -46,0 +52,0 @@ let match = true; |
@@ -93,3 +93,3 @@ "use strict"; | ||
async transactionsTrie() { | ||
return block_1.Block.genTransactionsTrieRoot(this.transactions); | ||
return block_1.Block.genTransactionsTrieRoot(this.transactions, new trie_1.Trie({ common: this.vm.common })); | ||
} | ||
@@ -100,3 +100,3 @@ /** | ||
logsBloom() { | ||
const bloom = new index_js_1.Bloom(); | ||
const bloom = new index_js_1.Bloom(undefined, this.vm.common); | ||
for (const txResult of this.transactionResults) { | ||
@@ -115,3 +115,3 @@ // Combine blooms via bitwise OR | ||
} | ||
const receiptTrie = new trie_1.Trie(); | ||
const receiptTrie = new trie_1.Trie({ common: this.vm.common }); | ||
for (const [i, txResult] of this.transactionResults.entries()) { | ||
@@ -133,3 +133,3 @@ const tx = this.transactions[i]; | ||
: util_1.Address.zero(); | ||
await (0, runBlock_js_1.rewardAccount)(this.vm.evm, coinbase, reward); | ||
await (0, runBlock_js_1.rewardAccount)(this.vm.evm, coinbase, reward, this.vm.common); | ||
} | ||
@@ -150,3 +150,3 @@ /** | ||
// converted to wei | ||
await (0, runBlock_js_1.rewardAccount)(this.vm.evm, address, amount * util_1.GWEI_TO_WEI); | ||
await (0, runBlock_js_1.rewardAccount)(this.vm.evm, address, amount * util_1.GWEI_TO_WEI, this.vm.common); | ||
} | ||
@@ -245,3 +245,3 @@ } | ||
const withdrawalsRoot = this.withdrawals | ||
? await block_1.Block.genWithdrawalsTrieRoot(this.withdrawals) | ||
? await block_1.Block.genWithdrawalsTrieRoot(this.withdrawals, new trie_1.Trie({ common: this.vm.common })) | ||
: undefined; | ||
@@ -248,0 +248,0 @@ const receiptTrie = await this.receiptTrie(); |
@@ -5,2 +5,3 @@ import { TransactionType } from '@ethereumjs/tx'; | ||
import type { VM } from './vm.js'; | ||
import type { Common } from '@ethereumjs/common'; | ||
import type { EVMInterface } from '@ethereumjs/evm'; | ||
@@ -13,3 +14,3 @@ /** | ||
export declare function calculateMinerReward(minerReward: bigint, ommersNum: number): bigint; | ||
export declare function rewardAccount(evm: EVMInterface, address: Address, reward: bigint): Promise<Account>; | ||
export declare function rewardAccount(evm: EVMInterface, address: Address, reward: bigint, common?: Common): Promise<Account>; | ||
/** | ||
@@ -16,0 +17,0 @@ * Returns the encoded tx receipt. |
@@ -7,2 +7,3 @@ "use strict"; | ||
const rlp_1 = require("@ethereumjs/rlp"); | ||
const statemanager_1 = require("@ethereumjs/statemanager"); | ||
const trie_1 = require("@ethereumjs/trie"); | ||
@@ -78,2 +79,17 @@ const tx_1 = require("@ethereumjs/tx"); | ||
} | ||
if (this.common.isActivatedEIP(6800)) { | ||
if (!(state instanceof statemanager_1.StatelessVerkleStateManager)) { | ||
throw Error(`StatelessVerkleStateManager needed for execution of verkle blocks`); | ||
} | ||
if (this.DEBUG) { | ||
debug(`Initializing StatelessVerkleStateManager executionWitness`); | ||
} | ||
; | ||
this._opts.stateManager.initVerkleExecutionWitness(block.executionWitness); | ||
} | ||
else { | ||
if (state instanceof statemanager_1.StatelessVerkleStateManager) { | ||
throw Error(`StatelessVerkleStateManager can't execute merkle blocks`); | ||
} | ||
} | ||
// check for DAO support and if we should apply the DAO fork | ||
@@ -133,3 +149,4 @@ if (this.common.hardforkIsActiveOnBlock(common_1.Hardfork.Dao, block.header.number) === true && | ||
} | ||
else { | ||
else if (this.common.isActivatedEIP(6800) === false) { | ||
// Only validate the following headers if verkle blocks aren't activated | ||
if ((0, util_1.equalsBytes)(result.receiptsRoot, block.header.receiptTrie) === false) { | ||
@@ -160,6 +177,13 @@ if (this.DEBUG) { | ||
} | ||
const msg = _errorMsg('invalid block stateRoot', this, block); | ||
const msg = _errorMsg(`invalid block stateRoot, got: ${(0, util_1.bytesToHex)(stateRoot)}, want: ${(0, util_1.bytesToHex)(block.header.stateRoot)}`, this, block); | ||
throw new Error(msg); | ||
} | ||
} | ||
else if (this.common.isActivatedEIP(6800) === true) { | ||
// If verkle is activated, only validate the post-state | ||
if (this._opts.stateManager.verifyPostState() === false) { | ||
throw new Error(`Verkle post state verification failed on block ${block.header.number}`); | ||
} | ||
debug(`Verkle post state verification succeeded`); | ||
} | ||
if (enableProfiler) { | ||
@@ -276,3 +300,3 @@ // eslint-disable-next-line no-console | ||
if ((await this.stateManager.getAccount(parentBeaconBlockRootAddress)) === undefined) { | ||
await this.stateManager.putAccount(parentBeaconBlockRootAddress, new util_1.Account()); | ||
await this.evm.journal.putAccount(parentBeaconBlockRootAddress, new util_1.Account()); | ||
} | ||
@@ -295,3 +319,3 @@ await this.stateManager.putContractStorage(parentBeaconBlockRootAddress, (0, util_1.setLengthLeft)((0, util_1.bigIntToBytes)(timestampIndex), 32), (0, util_1.bigIntToBytes)(timestamp)); | ||
} | ||
const bloom = new index_js_1.Bloom(); | ||
const bloom = new index_js_1.Bloom(undefined, this.common); | ||
// the total amount of gas used processing these transactions | ||
@@ -301,3 +325,3 @@ let gasUsed = util_1.BIGINT_0; | ||
if (block.transactions.length !== 0) { | ||
receiptTrie = new trie_1.Trie(); | ||
receiptTrie = new trie_1.Trie({ common: this.common }); | ||
} | ||
@@ -370,3 +394,3 @@ const receipts = []; | ||
// such that the account is touched and marked for cleanup if it is empty | ||
await rewardAccount(this.evm, address, amount * util_1.GWEI_TO_WEI); | ||
await rewardAccount(this.evm, address, amount * util_1.GWEI_TO_WEI, this.common); | ||
} | ||
@@ -387,3 +411,3 @@ } | ||
const reward = calculateOmmerReward(ommer.number, block.header.number, minerReward); | ||
const account = await rewardAccount(this.evm, ommer.coinbase, reward); | ||
const account = await rewardAccount(this.evm, ommer.coinbase, reward, this.common); | ||
if (this.DEBUG) { | ||
@@ -395,3 +419,3 @@ debug(`Add uncle reward ${reward} to account ${ommer.coinbase} (-> ${account.balance})`); | ||
const reward = calculateMinerReward(minerReward, ommers.length); | ||
const account = await rewardAccount(this.evm, block.header.coinbase, reward); | ||
const account = await rewardAccount(this.evm, block.header.coinbase, reward, this.common); | ||
if (this.DEBUG) { | ||
@@ -417,5 +441,9 @@ debug(`Add miner reward ${reward} to account ${block.header.coinbase} (-> ${account.balance})`); | ||
exports.calculateMinerReward = calculateMinerReward; | ||
async function rewardAccount(evm, address, reward) { | ||
async function rewardAccount(evm, address, reward, common) { | ||
let account = await evm.stateManager.getAccount(address); | ||
if (account === undefined) { | ||
if (common?.isActivatedEIP(6800) === true) { | ||
; | ||
evm.stateManager.accessWitness.touchAndChargeProofOfAbsence(address); | ||
} | ||
account = new util_1.Account(); | ||
@@ -425,2 +453,7 @@ } | ||
await evm.journal.putAccount(address, account); | ||
if (common?.isActivatedEIP(6800) === true) { | ||
// use this utility to build access but the computed gas is not charged and hence free | ||
; | ||
evm.stateManager.accessWitness.touchTxExistingAndComputeGas(address, { sendsValue: true }); | ||
} | ||
return account; | ||
@@ -483,3 +516,3 @@ } | ||
} | ||
const trie = new trie_1.Trie(); | ||
const trie = new trie_1.Trie({ common: block.common }); | ||
for (const [i, tx] of block.transactions.entries()) { | ||
@@ -486,0 +519,0 @@ await trie.put(rlp_1.RLP.encode(i), tx.serialize()); |
@@ -6,2 +6,3 @@ "use strict"; | ||
const common_1 = require("@ethereumjs/common"); | ||
const statemanager_1 = require("@ethereumjs/statemanager"); | ||
const tx_1 = require("@ethereumjs/tx"); | ||
@@ -17,4 +18,8 @@ const util_1 = require("@ethereumjs/util"); | ||
const balanceNonceLabel = 'Balance/Nonce checks and update'; | ||
const executionLabel = 'Execution'; | ||
const logsGasBalanceLabel = 'Logs, gas usage, account/miner balances'; | ||
const cleanupAndReceiptsLabel = 'Accounts clean up, access list, journal/cache cleanup, receipts'; | ||
const accountsCleanUpLabel = 'Accounts clean up'; | ||
const accessListLabel = 'Access list label'; | ||
const journalCacheCleanUpLabel = 'Journal/cache cleanup'; | ||
const receiptsLabel = 'Receipts'; | ||
const entireTxLabel = 'Entire tx'; | ||
@@ -144,2 +149,10 @@ /** | ||
const state = this.stateManager; | ||
let stateAccesses; | ||
if (this.common.isActivatedEIP(6800)) { | ||
if (!(this.stateManager instanceof statemanager_1.StatelessVerkleStateManager)) { | ||
throw Error(`StatelessVerkleStateManager needed for execution of verkle blocks`); | ||
} | ||
stateAccesses = this.stateManager.accessWitness; | ||
} | ||
let txAccesses = stateAccesses?.shallowCopy(); | ||
const { tx, block } = opts; | ||
@@ -194,3 +207,3 @@ if (!block) { | ||
if (maxFeePerGas < baseFeePerGas) { | ||
const msg = _errorMsg(`Transaction's maxFeePerGas (${maxFeePerGas}) is less than the block's baseFeePerGas (${baseFeePerGas})`, this, block, tx); | ||
const msg = _errorMsg(`Transaction's ${'maxFeePerGas' in tx ? 'maxFeePerGas' : 'gasPrice'} (${maxFeePerGas}) is less than the block's baseFeePerGas (${baseFeePerGas})`, this, block, tx); | ||
throw new Error(msg); | ||
@@ -220,4 +233,20 @@ } | ||
} | ||
let upfrontAwGas = util_1.BIGINT_0; | ||
if (this.common.isActivatedEIP(6800)) { | ||
upfrontAwGas += txAccesses.touchTxOriginAndComputeGas(caller); | ||
const sendsValue = tx.value !== util_1.BIGINT_0; | ||
if (tx.to !== undefined) { | ||
upfrontAwGas += txAccesses.touchTxExistingAndComputeGas(tx.to, { sendsValue }); | ||
debug(`Sender upfront awGas requirement for non contract creation tx is ${upfrontAwGas}`); | ||
} | ||
else { | ||
const contractTo = new util_1.Address((0, util_1.generateAddress)(caller.bytes, (0, util_1.bigIntToBytes)(nonce))); | ||
upfrontAwGas += txAccesses.touchAndChargeContractCreateInit(contractTo, { sendsValue }); | ||
debug(`Sender upfront awGas requirement is contract creation at=${(0, util_1.short)(contractTo.bytes)} is ${upfrontAwGas}`); | ||
} | ||
// reset txAccesses to remove the caches so that access gas can be correctly consumed inside the evm run | ||
txAccesses = stateAccesses?.shallowCopy(); | ||
} | ||
// Check balance against upfront tx cost | ||
const upFrontCost = tx.getUpfrontCost(block.header.baseFeePerGas); | ||
const upFrontCost = tx.getUpfrontCost(block.header.baseFeePerGas) + upfrontAwGas; | ||
if (balance < upFrontCost) { | ||
@@ -322,7 +351,7 @@ if (opts.skipBalance === true && fromAccount.balance < upFrontCost) { | ||
} | ||
let executionTimerPrecise; | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(balanceNonceLabel); | ||
// eslint-disable-next-line no-console | ||
console.log('[ For execution see detailed table output ]'); | ||
executionTimerPrecise = performance.now(); | ||
} | ||
@@ -345,5 +374,13 @@ /* | ||
blobVersionedHashes, | ||
accessWitness: txAccesses, | ||
})); | ||
if (this.common.isActivatedEIP(6800)) { | ||
stateAccesses?.merge(txAccesses); | ||
} | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.log(`${executionLabel}: ${performance.now() - executionTimerPrecise}ms`); | ||
// eslint-disable-next-line no-console | ||
console.log('[ For execution details see table output ]'); | ||
// eslint-disable-next-line no-console | ||
console.time(logsGasBalanceLabel); | ||
@@ -363,3 +400,3 @@ } | ||
// Generate the bloom for the tx | ||
results.bloom = txLogsBloom(results.execResult.logs); | ||
results.bloom = txLogsBloom(results.execResult.logs, this.common); | ||
if (this.DEBUG) { | ||
@@ -417,2 +454,6 @@ debug(`Generated tx bloom with logs=${results.execResult.logs?.length}`); | ||
if (minerAccount === undefined) { | ||
if (this.common.isActivatedEIP(6800)) { | ||
; | ||
state.accessWitness.touchAndChargeProofOfAbsence(miner); | ||
} | ||
minerAccount = new util_1.Account(); | ||
@@ -426,2 +467,9 @@ } | ||
minerAccount.balance += results.minerValue; | ||
if (this.common.isActivatedEIP(6800)) { | ||
// use this utility to build access but the computed gas is not charged and hence free | ||
; | ||
state.accessWitness.touchTxExistingAndComputeGas(miner, { | ||
sendsValue: true, | ||
}); | ||
} | ||
// Put the miner account into the state. If the balance of the miner account remains zero, note that | ||
@@ -438,3 +486,3 @@ // the state.putAccount function puts this into the "touched" accounts. This will thus be removed when | ||
// eslint-disable-next-line no-console | ||
console.time(cleanupAndReceiptsLabel); | ||
console.time(accountsCleanUpLabel); | ||
} | ||
@@ -459,2 +507,8 @@ /* | ||
} | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(accountsCleanUpLabel); | ||
// eslint-disable-next-line no-console | ||
console.time(accessListLabel); | ||
} | ||
if (opts.reportAccessList === true && this.common.isActivatedEIP(2930)) { | ||
@@ -477,4 +531,16 @@ // Convert the Map to the desired type | ||
} | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(accessListLabel); | ||
// eslint-disable-next-line no-console | ||
console.time(journalCacheCleanUpLabel); | ||
} | ||
await this.evm.journal.cleanup(); | ||
state.originalStorageCache.clear(); | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(journalCacheCleanUpLabel); | ||
// eslint-disable-next-line no-console | ||
console.time(receiptsLabel); | ||
} | ||
// Generate the tx receipt | ||
@@ -486,3 +552,3 @@ const gasUsed = opts.blockGasUsed !== undefined ? opts.blockGasUsed : block.header.gasUsed; | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(cleanupAndReceiptsLabel); | ||
console.timeEnd(receiptsLabel); | ||
} | ||
@@ -507,4 +573,4 @@ /** | ||
*/ | ||
function txLogsBloom(logs) { | ||
const bloom = new index_js_1.Bloom(); | ||
function txLogsBloom(logs, common) { | ||
const bloom = new index_js_1.Bloom(undefined, common); | ||
if (logs) { | ||
@@ -511,0 +577,0 @@ for (let i = 0; i < logs.length; i++) { |
@@ -0,7 +1,9 @@ | ||
import type { Common } from '@ethereumjs/common'; | ||
export declare class Bloom { | ||
bitvector: Uint8Array; | ||
keccakFunction: (msg: Uint8Array) => Uint8Array; | ||
/** | ||
* Represents a Bloom filter. | ||
*/ | ||
constructor(bitvector?: Uint8Array); | ||
constructor(bitvector?: Uint8Array, common?: Common); | ||
/** | ||
@@ -8,0 +10,0 @@ * Adds an element to a bit vector of a 64 byte bloom filter. |
@@ -8,3 +8,9 @@ import { zeros } from '@ethereumjs/util'; | ||
*/ | ||
constructor(bitvector) { | ||
constructor(bitvector, common) { | ||
if (common?.customCrypto.keccak256 !== undefined) { | ||
this.keccakFunction = common.customCrypto.keccak256; | ||
} | ||
else { | ||
this.keccakFunction = keccak256; | ||
} | ||
if (!bitvector) { | ||
@@ -24,3 +30,3 @@ this.bitvector = zeros(BYTE_SIZE); | ||
add(e) { | ||
e = keccak256(e); | ||
e = this.keccakFunction(e); | ||
const mask = 2047; // binary 11111111111 | ||
@@ -40,3 +46,3 @@ for (let i = 0; i < 3; i++) { | ||
check(e) { | ||
e = keccak256(e); | ||
e = this.keccakFunction(e); | ||
const mask = 2047; // binary 11111111111 | ||
@@ -43,0 +49,0 @@ let match = true; |
@@ -90,3 +90,3 @@ import { Block } from '@ethereumjs/block'; | ||
async transactionsTrie() { | ||
return Block.genTransactionsTrieRoot(this.transactions); | ||
return Block.genTransactionsTrieRoot(this.transactions, new Trie({ common: this.vm.common })); | ||
} | ||
@@ -97,3 +97,3 @@ /** | ||
logsBloom() { | ||
const bloom = new Bloom(); | ||
const bloom = new Bloom(undefined, this.vm.common); | ||
for (const txResult of this.transactionResults) { | ||
@@ -112,3 +112,3 @@ // Combine blooms via bitwise OR | ||
} | ||
const receiptTrie = new Trie(); | ||
const receiptTrie = new Trie({ common: this.vm.common }); | ||
for (const [i, txResult] of this.transactionResults.entries()) { | ||
@@ -130,3 +130,3 @@ const tx = this.transactions[i]; | ||
: Address.zero(); | ||
await rewardAccount(this.vm.evm, coinbase, reward); | ||
await rewardAccount(this.vm.evm, coinbase, reward, this.vm.common); | ||
} | ||
@@ -147,3 +147,3 @@ /** | ||
// converted to wei | ||
await rewardAccount(this.vm.evm, address, amount * GWEI_TO_WEI); | ||
await rewardAccount(this.vm.evm, address, amount * GWEI_TO_WEI, this.vm.common); | ||
} | ||
@@ -242,3 +242,3 @@ } | ||
const withdrawalsRoot = this.withdrawals | ||
? await Block.genWithdrawalsTrieRoot(this.withdrawals) | ||
? await Block.genWithdrawalsTrieRoot(this.withdrawals, new Trie({ common: this.vm.common })) | ||
: undefined; | ||
@@ -245,0 +245,0 @@ const receiptTrie = await this.receiptTrie(); |
@@ -5,2 +5,3 @@ import { TransactionType } from '@ethereumjs/tx'; | ||
import type { VM } from './vm.js'; | ||
import type { Common } from '@ethereumjs/common'; | ||
import type { EVMInterface } from '@ethereumjs/evm'; | ||
@@ -13,3 +14,3 @@ /** | ||
export declare function calculateMinerReward(minerReward: bigint, ommersNum: number): bigint; | ||
export declare function rewardAccount(evm: EVMInterface, address: Address, reward: bigint): Promise<Account>; | ||
export declare function rewardAccount(evm: EVMInterface, address: Address, reward: bigint, common?: Common): Promise<Account>; | ||
/** | ||
@@ -16,0 +17,0 @@ * Returns the encoded tx receipt. |
import { Block } from '@ethereumjs/block'; | ||
import { ConsensusType, Hardfork } from '@ethereumjs/common'; | ||
import { RLP } from '@ethereumjs/rlp'; | ||
import { StatelessVerkleStateManager } from '@ethereumjs/statemanager'; | ||
import { Trie } from '@ethereumjs/trie'; | ||
@@ -74,2 +75,17 @@ import { TransactionType } from '@ethereumjs/tx'; | ||
} | ||
if (this.common.isActivatedEIP(6800)) { | ||
if (!(state instanceof StatelessVerkleStateManager)) { | ||
throw Error(`StatelessVerkleStateManager needed for execution of verkle blocks`); | ||
} | ||
if (this.DEBUG) { | ||
debug(`Initializing StatelessVerkleStateManager executionWitness`); | ||
} | ||
; | ||
this._opts.stateManager.initVerkleExecutionWitness(block.executionWitness); | ||
} | ||
else { | ||
if (state instanceof StatelessVerkleStateManager) { | ||
throw Error(`StatelessVerkleStateManager can't execute merkle blocks`); | ||
} | ||
} | ||
// check for DAO support and if we should apply the DAO fork | ||
@@ -129,3 +145,4 @@ if (this.common.hardforkIsActiveOnBlock(Hardfork.Dao, block.header.number) === true && | ||
} | ||
else { | ||
else if (this.common.isActivatedEIP(6800) === false) { | ||
// Only validate the following headers if verkle blocks aren't activated | ||
if (equalsBytes(result.receiptsRoot, block.header.receiptTrie) === false) { | ||
@@ -156,6 +173,13 @@ if (this.DEBUG) { | ||
} | ||
const msg = _errorMsg('invalid block stateRoot', this, block); | ||
const msg = _errorMsg(`invalid block stateRoot, got: ${bytesToHex(stateRoot)}, want: ${bytesToHex(block.header.stateRoot)}`, this, block); | ||
throw new Error(msg); | ||
} | ||
} | ||
else if (this.common.isActivatedEIP(6800) === true) { | ||
// If verkle is activated, only validate the post-state | ||
if (this._opts.stateManager.verifyPostState() === false) { | ||
throw new Error(`Verkle post state verification failed on block ${block.header.number}`); | ||
} | ||
debug(`Verkle post state verification succeeded`); | ||
} | ||
if (enableProfiler) { | ||
@@ -271,3 +295,3 @@ // eslint-disable-next-line no-console | ||
if ((await this.stateManager.getAccount(parentBeaconBlockRootAddress)) === undefined) { | ||
await this.stateManager.putAccount(parentBeaconBlockRootAddress, new Account()); | ||
await this.evm.journal.putAccount(parentBeaconBlockRootAddress, new Account()); | ||
} | ||
@@ -289,3 +313,3 @@ await this.stateManager.putContractStorage(parentBeaconBlockRootAddress, setLengthLeft(bigIntToBytes(timestampIndex), 32), bigIntToBytes(timestamp)); | ||
} | ||
const bloom = new Bloom(); | ||
const bloom = new Bloom(undefined, this.common); | ||
// the total amount of gas used processing these transactions | ||
@@ -295,3 +319,3 @@ let gasUsed = BIGINT_0; | ||
if (block.transactions.length !== 0) { | ||
receiptTrie = new Trie(); | ||
receiptTrie = new Trie({ common: this.common }); | ||
} | ||
@@ -364,3 +388,3 @@ const receipts = []; | ||
// such that the account is touched and marked for cleanup if it is empty | ||
await rewardAccount(this.evm, address, amount * GWEI_TO_WEI); | ||
await rewardAccount(this.evm, address, amount * GWEI_TO_WEI, this.common); | ||
} | ||
@@ -381,3 +405,3 @@ } | ||
const reward = calculateOmmerReward(ommer.number, block.header.number, minerReward); | ||
const account = await rewardAccount(this.evm, ommer.coinbase, reward); | ||
const account = await rewardAccount(this.evm, ommer.coinbase, reward, this.common); | ||
if (this.DEBUG) { | ||
@@ -389,3 +413,3 @@ debug(`Add uncle reward ${reward} to account ${ommer.coinbase} (-> ${account.balance})`); | ||
const reward = calculateMinerReward(minerReward, ommers.length); | ||
const account = await rewardAccount(this.evm, block.header.coinbase, reward); | ||
const account = await rewardAccount(this.evm, block.header.coinbase, reward, this.common); | ||
if (this.DEBUG) { | ||
@@ -410,5 +434,9 @@ debug(`Add miner reward ${reward} to account ${block.header.coinbase} (-> ${account.balance})`); | ||
} | ||
export async function rewardAccount(evm, address, reward) { | ||
export async function rewardAccount(evm, address, reward, common) { | ||
let account = await evm.stateManager.getAccount(address); | ||
if (account === undefined) { | ||
if (common?.isActivatedEIP(6800) === true) { | ||
; | ||
evm.stateManager.accessWitness.touchAndChargeProofOfAbsence(address); | ||
} | ||
account = new Account(); | ||
@@ -418,2 +446,7 @@ } | ||
await evm.journal.putAccount(address, account); | ||
if (common?.isActivatedEIP(6800) === true) { | ||
// use this utility to build access but the computed gas is not charged and hence free | ||
; | ||
evm.stateManager.accessWitness.touchTxExistingAndComputeGas(address, { sendsValue: true }); | ||
} | ||
return account; | ||
@@ -474,3 +507,3 @@ } | ||
} | ||
const trie = new Trie(); | ||
const trie = new Trie({ common: block.common }); | ||
for (const [i, tx] of block.transactions.entries()) { | ||
@@ -477,0 +510,0 @@ await trie.put(RLP.encode(i), tx.serialize()); |
import { Block } from '@ethereumjs/block'; | ||
import { ConsensusType, Hardfork } from '@ethereumjs/common'; | ||
import { StatelessVerkleStateManager } from '@ethereumjs/statemanager'; | ||
import { BlobEIP4844Transaction, Capability, isBlobEIP4844Tx } from '@ethereumjs/tx'; | ||
import { Account, Address, BIGINT_0, KECCAK256_NULL, bytesToHex, bytesToUnprefixedHex, equalsBytes, hexToBytes, short, } from '@ethereumjs/util'; | ||
import { Account, Address, BIGINT_0, KECCAK256_NULL, bigIntToBytes, bytesToHex, bytesToUnprefixedHex, equalsBytes, generateAddress, hexToBytes, short, } from '@ethereumjs/util'; | ||
import debugDefault from 'debug'; | ||
@@ -13,4 +14,8 @@ import { Bloom } from './bloom/index.js'; | ||
const balanceNonceLabel = 'Balance/Nonce checks and update'; | ||
const executionLabel = 'Execution'; | ||
const logsGasBalanceLabel = 'Logs, gas usage, account/miner balances'; | ||
const cleanupAndReceiptsLabel = 'Accounts clean up, access list, journal/cache cleanup, receipts'; | ||
const accountsCleanUpLabel = 'Accounts clean up'; | ||
const accessListLabel = 'Access list label'; | ||
const journalCacheCleanUpLabel = 'Journal/cache cleanup'; | ||
const receiptsLabel = 'Receipts'; | ||
const entireTxLabel = 'Entire tx'; | ||
@@ -139,2 +144,10 @@ /** | ||
const state = this.stateManager; | ||
let stateAccesses; | ||
if (this.common.isActivatedEIP(6800)) { | ||
if (!(this.stateManager instanceof StatelessVerkleStateManager)) { | ||
throw Error(`StatelessVerkleStateManager needed for execution of verkle blocks`); | ||
} | ||
stateAccesses = this.stateManager.accessWitness; | ||
} | ||
let txAccesses = stateAccesses?.shallowCopy(); | ||
const { tx, block } = opts; | ||
@@ -189,3 +202,3 @@ if (!block) { | ||
if (maxFeePerGas < baseFeePerGas) { | ||
const msg = _errorMsg(`Transaction's maxFeePerGas (${maxFeePerGas}) is less than the block's baseFeePerGas (${baseFeePerGas})`, this, block, tx); | ||
const msg = _errorMsg(`Transaction's ${'maxFeePerGas' in tx ? 'maxFeePerGas' : 'gasPrice'} (${maxFeePerGas}) is less than the block's baseFeePerGas (${baseFeePerGas})`, this, block, tx); | ||
throw new Error(msg); | ||
@@ -215,4 +228,20 @@ } | ||
} | ||
let upfrontAwGas = BIGINT_0; | ||
if (this.common.isActivatedEIP(6800)) { | ||
upfrontAwGas += txAccesses.touchTxOriginAndComputeGas(caller); | ||
const sendsValue = tx.value !== BIGINT_0; | ||
if (tx.to !== undefined) { | ||
upfrontAwGas += txAccesses.touchTxExistingAndComputeGas(tx.to, { sendsValue }); | ||
debug(`Sender upfront awGas requirement for non contract creation tx is ${upfrontAwGas}`); | ||
} | ||
else { | ||
const contractTo = new Address(generateAddress(caller.bytes, bigIntToBytes(nonce))); | ||
upfrontAwGas += txAccesses.touchAndChargeContractCreateInit(contractTo, { sendsValue }); | ||
debug(`Sender upfront awGas requirement is contract creation at=${short(contractTo.bytes)} is ${upfrontAwGas}`); | ||
} | ||
// reset txAccesses to remove the caches so that access gas can be correctly consumed inside the evm run | ||
txAccesses = stateAccesses?.shallowCopy(); | ||
} | ||
// Check balance against upfront tx cost | ||
const upFrontCost = tx.getUpfrontCost(block.header.baseFeePerGas); | ||
const upFrontCost = tx.getUpfrontCost(block.header.baseFeePerGas) + upfrontAwGas; | ||
if (balance < upFrontCost) { | ||
@@ -317,7 +346,7 @@ if (opts.skipBalance === true && fromAccount.balance < upFrontCost) { | ||
} | ||
let executionTimerPrecise; | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(balanceNonceLabel); | ||
// eslint-disable-next-line no-console | ||
console.log('[ For execution see detailed table output ]'); | ||
executionTimerPrecise = performance.now(); | ||
} | ||
@@ -340,5 +369,13 @@ /* | ||
blobVersionedHashes, | ||
accessWitness: txAccesses, | ||
})); | ||
if (this.common.isActivatedEIP(6800)) { | ||
stateAccesses?.merge(txAccesses); | ||
} | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.log(`${executionLabel}: ${performance.now() - executionTimerPrecise}ms`); | ||
// eslint-disable-next-line no-console | ||
console.log('[ For execution details see table output ]'); | ||
// eslint-disable-next-line no-console | ||
console.time(logsGasBalanceLabel); | ||
@@ -358,3 +395,3 @@ } | ||
// Generate the bloom for the tx | ||
results.bloom = txLogsBloom(results.execResult.logs); | ||
results.bloom = txLogsBloom(results.execResult.logs, this.common); | ||
if (this.DEBUG) { | ||
@@ -412,2 +449,6 @@ debug(`Generated tx bloom with logs=${results.execResult.logs?.length}`); | ||
if (minerAccount === undefined) { | ||
if (this.common.isActivatedEIP(6800)) { | ||
; | ||
state.accessWitness.touchAndChargeProofOfAbsence(miner); | ||
} | ||
minerAccount = new Account(); | ||
@@ -421,2 +462,9 @@ } | ||
minerAccount.balance += results.minerValue; | ||
if (this.common.isActivatedEIP(6800)) { | ||
// use this utility to build access but the computed gas is not charged and hence free | ||
; | ||
state.accessWitness.touchTxExistingAndComputeGas(miner, { | ||
sendsValue: true, | ||
}); | ||
} | ||
// Put the miner account into the state. If the balance of the miner account remains zero, note that | ||
@@ -433,3 +481,3 @@ // the state.putAccount function puts this into the "touched" accounts. This will thus be removed when | ||
// eslint-disable-next-line no-console | ||
console.time(cleanupAndReceiptsLabel); | ||
console.time(accountsCleanUpLabel); | ||
} | ||
@@ -454,2 +502,8 @@ /* | ||
} | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(accountsCleanUpLabel); | ||
// eslint-disable-next-line no-console | ||
console.time(accessListLabel); | ||
} | ||
if (opts.reportAccessList === true && this.common.isActivatedEIP(2930)) { | ||
@@ -472,4 +526,16 @@ // Convert the Map to the desired type | ||
} | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(accessListLabel); | ||
// eslint-disable-next-line no-console | ||
console.time(journalCacheCleanUpLabel); | ||
} | ||
await this.evm.journal.cleanup(); | ||
state.originalStorageCache.clear(); | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(journalCacheCleanUpLabel); | ||
// eslint-disable-next-line no-console | ||
console.time(receiptsLabel); | ||
} | ||
// Generate the tx receipt | ||
@@ -481,3 +547,3 @@ const gasUsed = opts.blockGasUsed !== undefined ? opts.blockGasUsed : block.header.gasUsed; | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(cleanupAndReceiptsLabel); | ||
console.timeEnd(receiptsLabel); | ||
} | ||
@@ -502,4 +568,4 @@ /** | ||
*/ | ||
function txLogsBloom(logs) { | ||
const bloom = new Bloom(); | ||
function txLogsBloom(logs, common) { | ||
const bloom = new Bloom(undefined, common); | ||
if (logs) { | ||
@@ -506,0 +572,0 @@ for (let i = 0; i < logs.length; i++) { |
{ | ||
"name": "@ethereumjs/vm", | ||
"version": "7.1.0", | ||
"version": "7.2.0", | ||
"description": "An Ethereum VM implementation", | ||
@@ -43,3 +43,4 @@ "keywords": [ | ||
"docs:build": "typedoc --options typedoc.cjs", | ||
"examples": "ts-node ../../scripts/examples-runner.ts -- vm", | ||
"examples": "tsx ../../scripts/examples-runner.ts -- vm", | ||
"examples:build": "npx embedme README.md", | ||
"formatTest": "node ./scripts/formatTest", | ||
@@ -62,17 +63,17 @@ "lint": "../../config/cli/lint.sh", | ||
"test:state:slow": "npm run test:state -- --runSkipped=slow", | ||
"tester": "ts-node ./test/tester --stack-size=1500", | ||
"tester": "tsx ./test/tester --stack-size=1500", | ||
"tsc": "../../config/cli/ts-compile.sh" | ||
}, | ||
"dependencies": { | ||
"@ethereumjs/block": "^5.0.1", | ||
"@ethereumjs/blockchain": "^7.0.1", | ||
"@ethereumjs/common": "^4.1.0", | ||
"@ethereumjs/evm": "^2.1.0", | ||
"@ethereumjs/rlp": "^5.0.1", | ||
"@ethereumjs/statemanager": "^2.1.0", | ||
"@ethereumjs/trie": "^6.0.1", | ||
"@ethereumjs/tx": "^5.1.0", | ||
"@ethereumjs/util": "^9.0.1", | ||
"@ethereumjs/block": "^5.1.1", | ||
"@ethereumjs/blockchain": "^7.1.0", | ||
"@ethereumjs/common": "^4.2.0", | ||
"@ethereumjs/evm": "^2.2.0", | ||
"@ethereumjs/rlp": "^5.0.2", | ||
"@ethereumjs/statemanager": "^2.2.1", | ||
"@ethereumjs/trie": "^6.1.1", | ||
"@ethereumjs/tx": "^5.2.1", | ||
"@ethereumjs/util": "^9.0.2", | ||
"debug": "^4.3.3", | ||
"ethereum-cryptography": "^2.1.2" | ||
"ethereum-cryptography": "^2.1.3" | ||
}, | ||
@@ -79,0 +80,0 @@ "devDependencies": { |
195
README.md
@@ -35,3 +35,5 @@ # @ethereumjs/vm | ||
```typescript | ||
```ts | ||
// ./examples/runTx.ts | ||
import { Address } from '@ethereumjs/util' | ||
@@ -42,14 +44,20 @@ import { Chain, Common, Hardfork } from '@ethereumjs/common' | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Shanghai }) | ||
const vm = await VM.create({ common }) | ||
const main = async () => { | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Shanghai }) | ||
const vm = await VM.create({ common }) | ||
const tx = LegacyTransaction.fromTxData({ | ||
gasLimit: BigInt(21000), | ||
value: BigInt(1), | ||
to: Address.zero(), | ||
v: BigInt(37), | ||
r: BigInt('62886504200765677832366398998081608852310526822767264927793100349258111544447'), | ||
s: BigInt('21948396863567062449199529794141973192314514851405455194940751428901681436138'), | ||
}) | ||
await vm.runTx({ tx, skipBalance: true }) | ||
const tx = LegacyTransaction.fromTxData({ | ||
gasLimit: BigInt(21000), | ||
gasPrice: BigInt(1000000000), | ||
value: BigInt(1), | ||
to: Address.zero(), | ||
v: BigInt(37), | ||
r: BigInt('62886504200765677832366398998081608852310526822767264927793100349258111544447'), | ||
s: BigInt('21948396863567062449199529794141973192314514851405455194940751428901681436138'), | ||
}) | ||
const res = await vm.runTx({ tx, skipBalance: true }) | ||
console.log(res.totalGasSpent) // 21000n - gas cost for simple ETH transfer | ||
} | ||
main() | ||
``` | ||
@@ -65,24 +73,53 @@ | ||
```typescript | ||
```ts | ||
// ./examples/buildBlock.ts | ||
import { Block } from '@ethereumjs/block' | ||
import { Chain, Common, Hardfork } from '@ethereumjs/common' | ||
import { LegacyTransaction } from '@ethereumjs/tx' | ||
import { Account, Address, bytesToHex, hexToBytes, randomBytes } from '@ethereumjs/util' | ||
import { VM } from '@ethereumjs/vm' | ||
const common = new Common({ chain: Chain.Mainnet }) | ||
const vm = await VM.create({ common }) | ||
const main = async () => { | ||
const common = new Common({ chain: Chain.Mainnet }) | ||
const vm = await VM.create({ common }) | ||
const blockBuilder = await vm.buildBlock({ | ||
parentBlock, // the parent @ethereumjs/block Block | ||
headerData, // header values for the new block | ||
blockOpts: { calcDifficultyFromHeader: parentBlock.header, freeze: false }, | ||
}) | ||
const parentBlock = Block.fromBlockData( | ||
{ header: { number: 1n } }, | ||
{ skipConsensusFormatValidation: true } | ||
) | ||
const headerData = { | ||
number: 2n, | ||
} | ||
const blockBuilder = await vm.buildBlock({ | ||
parentBlock, // the parent @ethereumjs/block Block | ||
headerData, // header values for the new block | ||
blockOpts: { | ||
calcDifficultyFromHeader: parentBlock.header, | ||
freeze: false, | ||
skipConsensusFormatValidation: true, | ||
putBlockIntoBlockchain: false, | ||
}, | ||
}) | ||
const tx = LegacyTransaction.fromTxData() | ||
await blockBuilder.addTransaction(tx) | ||
const pk = hexToBytes('0x26f81cbcffd3d23eace0bb4eac5274bb2f576d310ee85318b5428bf9a71fc89a') | ||
const address = Address.fromPrivateKey(pk) | ||
const account = new Account(0n, 0xfffffffffn) | ||
await vm.stateManager.putAccount(address, account) // create a sending account and give it a big balance | ||
const tx = LegacyTransaction.fromTxData({ gasLimit: 0xffffff, gasPrice: 75n }).sign(pk) | ||
await blockBuilder.addTransaction(tx) | ||
// Add more transactions | ||
// Add more transactions | ||
const block = await blockBuilder.build() | ||
const block = await blockBuilder.build() | ||
console.log(`Built a block with hash ${bytesToHex(block.hash())}`) | ||
} | ||
main() | ||
``` | ||
### WASM Crypto Support | ||
This library by default uses JavaScript implementations for the basic standard crypto primitives like hashing or signature verification (for included txs). See `@ethereumjs/common` [README](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/common) for instructions on how to replace with e.g. a more performant WASM implementation by using a shared `common` instance. | ||
## Example | ||
@@ -92,4 +129,4 @@ | ||
1. [./examples/run-blockchain](./examples/run-blockchain.cts): Loads tests data, including accounts and blocks, and runs all of them in the VM. | ||
1. [./examples/run-solidity-contract](./examples/run-solidity-contract.cts): Compiles a Solidity contract, and calls constant and non-constant functions. | ||
1. [./examples/run-blockchain](./examples/run-blockchain.ts): Loads tests data, including accounts and blocks, and runs all of them in the VM. | ||
1. [./examples/run-solidity-contract](./examples/run-solidity-contract.ts): Compiles a Solidity contract, and calls constant and non-constant functions. | ||
@@ -116,3 +153,3 @@ All of the examples have their own `README.md` explaining how to run them. | ||
```typescript | ||
```ts | ||
import { EthereumJSClass } from '@ethereumjs/[PACKAGE_NAME]' | ||
@@ -123,3 +160,3 @@ ``` | ||
```typescript | ||
```ts | ||
const { EthereumJSClass } = require('@ethereumjs/[PACKAGE_NAME]') | ||
@@ -150,4 +187,4 @@ ``` | ||
```typescript | ||
vm.evm.runCode() // or | ||
```ts | ||
vm.evm.runCode() | ||
vm.evm.events.on('step', function (data) { | ||
@@ -180,13 +217,21 @@ console.log(`Opcode: ${data.opcode.name}\tStack: ${data.stack}`) | ||
```typescript | ||
```ts | ||
// ./examples/runGoerliBlock.ts | ||
import { Block } from '@ethereumjs/block' | ||
import { Chain, Common } from '@ethereumjs/common' | ||
import { hexToBytes } from '@ethereumjs/util' | ||
import { VM } from '@ethereumjs/vm' | ||
import { bytesToHex, hexToBytes } from '@ethereumjs/util' | ||
import { VM } from '../src/vm.js' | ||
import goerliBlock2 from './testData/goerliBlock2.json' | ||
const common = new Common({ chain: Chain.Goerli }) | ||
const vm = await VM.create({ common, setHardfork: true }) | ||
const main = async () => { | ||
const common = new Common({ chain: Chain.Goerli, hardfork: 'london' }) | ||
const vm = await VM.create({ common, setHardfork: true }) | ||
const serialized = hexToBytes('0xf901f7a06bfee7294bf4457...') | ||
const block = Block.fromRLPSerializedBlock(serialized, { setHardfork: true }) | ||
const result = await vm.runBlock(block) | ||
const block = Block.fromRPC(goerliBlock2, undefined, { common }) | ||
const result = await vm.runBlock({ block, generate: true, skipHeaderValidation: true }) // we skip header validaiton since we are running a block without the full Ethereum history available | ||
console.log(`The state root for Goerli block 2 is ${bytesToHex(result.stateRoot)}`) | ||
} | ||
main() | ||
``` | ||
@@ -200,8 +245,13 @@ | ||
```typescript | ||
```ts | ||
// ./examples/runTx.ts#L1-L8 | ||
import { Address } from '@ethereumjs/util' | ||
import { Chain, Common, Hardfork } from '@ethereumjs/common' | ||
import { LegacyTransaction } from '@ethereumjs/tx' | ||
import { VM } from '@ethereumjs/vm' | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Berlin }) | ||
const vm = await VM.create({ common }) | ||
const main = async () => { | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Shanghai }) | ||
const vm = await VM.create({ common }) | ||
``` | ||
@@ -219,12 +269,26 @@ | ||
```typescript | ||
```ts | ||
// ./examples/vmWithGenesisState.ts | ||
import { Blockchain } from '@ethereumjs/blockchain' | ||
import { Chain, Common } from '@ethereumjs/common' | ||
import { Chain } from '@ethereumjs/common' | ||
import { getGenesis } from '@ethereumjs/genesis' | ||
import { Address } from '@ethereumjs/util' | ||
import { VM } from '@ethereumjs/vm' | ||
const genesisState = getGenesis(Chain.Mainnet) | ||
const main = async () => { | ||
const genesisState = getGenesis(Chain.Mainnet) | ||
const blockchain = await Blockchain.create({ genesisState }) | ||
const vm = await VM.create({ blockchain, genesisState }) | ||
const blockchain = await Blockchain.create({ genesisState }) | ||
const vm = await VM.create({ blockchain, genesisState }) | ||
const account = await vm.stateManager.getAccount( | ||
Address.fromString('0x000d836201318ec6899a67540690382780743280') | ||
) | ||
console.log( | ||
`This balance for account 0x000d836201318ec6899a67540690382780743280 in this chain's genesis state is ${Number( | ||
account?.balance | ||
)}` | ||
) | ||
} | ||
main() | ||
``` | ||
@@ -245,8 +309,14 @@ | ||
```typescript | ||
```ts | ||
// ./examples/vmWithEIPs.ts | ||
import { Chain, Common } from '@ethereumjs/common' | ||
import { VM } from '@ethereumjs/vm' | ||
const common = new Common({ chain: Chain.Mainnet, eips: [2537] }) | ||
const vm = await VM.create({ common }) | ||
const main = async () => { | ||
const common = new Common({ chain: Chain.Mainnet, eips: [3074] }) | ||
const vm = await VM.create({ common }) | ||
console.log(`EIP 3074 is active in the VM - ${vm.common.isActivatedEIP(3074)}`) | ||
} | ||
main() | ||
``` | ||
@@ -262,11 +332,20 @@ | ||
To run VM/EVM related EIP-4844 functionality you have to active the EIP in the associated `@ethereumjs/common` library: | ||
To run VM/EVM related EIP-4844 functionality you have to activate the EIP in the associated `@ethereumjs/common` library: | ||
```typescript | ||
```ts | ||
// ./examples/vmWith4844.ts | ||
import { Common, Chain, Hardfork } from '@ethereumjs/common' | ||
import { VM } from '../src/vm.js' | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Shanghai, eips: [4844] }) | ||
const main = async () => { | ||
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Shanghai, eips: [4844] }) | ||
const vm = await VM.create({ common }) | ||
console.log(`4844 is active in the VM - ${vm.common.isActivatedEIP(4844)}`) | ||
} | ||
main() | ||
``` | ||
EIP-4844 comes with a new opcode `DATAHASH` and adds a new point evaluation precompile at address `0x14` in the underlying `@ethereumjs/evm` package. | ||
EIP-4844 comes with a new opcode `BLOBHASH` and adds a new point evaluation precompile at address `0x14` in the underlying `@ethereumjs/evm` package. | ||
@@ -337,3 +416,3 @@ **Note:** Usage of the point evaluation precompile needs a manual KZG library installation and global initialization, see [KZG Setup](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/tx/README.md#kzg-setup) for instructions. | ||
```shell | ||
DEBUG=ethjs,vm:tx ts-node test.ts | ||
DEBUG=ethjs,vm:tx tsx test.ts | ||
``` | ||
@@ -344,3 +423,3 @@ | ||
```shell | ||
DEBUG=ethjs,vm:*,vm:*:* ts-node test.ts | ||
DEBUG=ethjs,vm:*,vm:*:* tsx test.ts | ||
``` | ||
@@ -351,3 +430,3 @@ | ||
```shell | ||
DEBUG=ethjs,vm:*:gas ts-node test.ts | ||
DEBUG=ethjs,vm:*:gas tsx test.ts | ||
``` | ||
@@ -358,3 +437,3 @@ | ||
```shell | ||
DEBUG=ethjs,vm:*,vm:*:*,-vm:state ts-node test.ts | ||
DEBUG=ethjs,vm:*,vm:*:*,-vm:state tsx test.ts | ||
``` | ||
@@ -365,3 +444,3 @@ | ||
```shell | ||
DEBUG=ethjs,vm:tx,vm:evm,vm:ops:sstore,vm:*:gas ts-node test.ts | ||
DEBUG=ethjs,vm:tx,vm:evm,vm:ops:sstore,vm:*:gas tsx test.ts | ||
``` | ||
@@ -368,0 +447,0 @@ |
import { zeros } from '@ethereumjs/util' | ||
import { keccak256 } from 'ethereum-cryptography/keccak.js' | ||
import type { Common } from '@ethereumjs/common' | ||
const BYTE_SIZE = 256 | ||
@@ -8,2 +10,3 @@ | ||
bitvector: Uint8Array | ||
keccakFunction: (msg: Uint8Array) => Uint8Array | ||
@@ -13,3 +16,8 @@ /** | ||
*/ | ||
constructor(bitvector?: Uint8Array) { | ||
constructor(bitvector?: Uint8Array, common?: Common) { | ||
if (common?.customCrypto.keccak256 !== undefined) { | ||
this.keccakFunction = common.customCrypto.keccak256 | ||
} else { | ||
this.keccakFunction = keccak256 | ||
} | ||
if (!bitvector) { | ||
@@ -28,3 +36,3 @@ this.bitvector = zeros(BYTE_SIZE) | ||
add(e: Uint8Array) { | ||
e = keccak256(e) | ||
e = this.keccakFunction(e) | ||
const mask = 2047 // binary 11111111111 | ||
@@ -46,3 +54,3 @@ | ||
check(e: Uint8Array): boolean { | ||
e = keccak256(e) | ||
e = this.keccakFunction(e) | ||
const mask = 2047 // binary 11111111111 | ||
@@ -49,0 +57,0 @@ let match = true |
@@ -135,3 +135,3 @@ import { Block } from '@ethereumjs/block' | ||
public async transactionsTrie() { | ||
return Block.genTransactionsTrieRoot(this.transactions) | ||
return Block.genTransactionsTrieRoot(this.transactions, new Trie({ common: this.vm.common })) | ||
} | ||
@@ -143,3 +143,3 @@ | ||
public logsBloom() { | ||
const bloom = new Bloom() | ||
const bloom = new Bloom(undefined, this.vm.common) | ||
for (const txResult of this.transactionResults) { | ||
@@ -159,3 +159,3 @@ // Combine blooms via bitwise OR | ||
} | ||
const receiptTrie = new Trie() | ||
const receiptTrie = new Trie({ common: this.vm.common }) | ||
for (const [i, txResult] of this.transactionResults.entries()) { | ||
@@ -179,3 +179,3 @@ const tx = this.transactions[i] | ||
: Address.zero() | ||
await rewardAccount(this.vm.evm, coinbase, reward) | ||
await rewardAccount(this.vm.evm, coinbase, reward, this.vm.common) | ||
} | ||
@@ -196,3 +196,3 @@ | ||
// converted to wei | ||
await rewardAccount(this.vm.evm, address, amount * GWEI_TO_WEI) | ||
await rewardAccount(this.vm.evm, address, amount * GWEI_TO_WEI, this.vm.common) | ||
} | ||
@@ -310,3 +310,3 @@ } | ||
const withdrawalsRoot = this.withdrawals | ||
? await Block.genWithdrawalsTrieRoot(this.withdrawals) | ||
? await Block.genWithdrawalsTrieRoot(this.withdrawals, new Trie({ common: this.vm.common })) | ||
: undefined | ||
@@ -313,0 +313,0 @@ const receiptTrie = await this.receiptTrie() |
import { Block } from '@ethereumjs/block' | ||
import { ConsensusType, Hardfork } from '@ethereumjs/common' | ||
import { RLP } from '@ethereumjs/rlp' | ||
import { StatelessVerkleStateManager } from '@ethereumjs/statemanager' | ||
import { Trie } from '@ethereumjs/trie' | ||
@@ -36,2 +37,3 @@ import { TransactionType } from '@ethereumjs/tx' | ||
import type { VM } from './vm.js' | ||
import type { Common } from '@ethereumjs/common' | ||
import type { EVM, EVMInterface } from '@ethereumjs/evm' | ||
@@ -64,2 +66,3 @@ | ||
const state = this.stateManager | ||
const { root } = opts | ||
@@ -123,2 +126,18 @@ const clearCache = opts.clearCache ?? true | ||
if (this.common.isActivatedEIP(6800)) { | ||
if (!(state instanceof StatelessVerkleStateManager)) { | ||
throw Error(`StatelessVerkleStateManager needed for execution of verkle blocks`) | ||
} | ||
if (this.DEBUG) { | ||
debug(`Initializing StatelessVerkleStateManager executionWitness`) | ||
} | ||
;(this._opts.stateManager as StatelessVerkleStateManager).initVerkleExecutionWitness( | ||
block.executionWitness | ||
) | ||
} else { | ||
if (state instanceof StatelessVerkleStateManager) { | ||
throw Error(`StatelessVerkleStateManager can't execute merkle blocks`) | ||
} | ||
} | ||
// check for DAO support and if we should apply the DAO fork | ||
@@ -132,2 +151,3 @@ if ( | ||
} | ||
await this.evm.journal.checkpoint() | ||
@@ -144,3 +164,4 @@ await _applyDAOHardfork(this.evm) | ||
let result | ||
let result: Awaited<ReturnType<typeof applyBlock>> | ||
try { | ||
@@ -191,3 +212,4 @@ result = await applyBlock.bind(this)(block, opts) | ||
block = Block.fromBlockData(blockData, { common: this.common }) | ||
} else { | ||
} else if (this.common.isActivatedEIP(6800) === false) { | ||
// Only validate the following headers if verkle blocks aren't activated | ||
if (equalsBytes(result.receiptsRoot, block.header.receiptTrie) === false) { | ||
@@ -230,5 +252,17 @@ if (this.DEBUG) { | ||
} | ||
const msg = _errorMsg('invalid block stateRoot', this, block) | ||
const msg = _errorMsg( | ||
`invalid block stateRoot, got: ${bytesToHex(stateRoot)}, want: ${bytesToHex( | ||
block.header.stateRoot | ||
)}`, | ||
this, | ||
block | ||
) | ||
throw new Error(msg) | ||
} | ||
} else if (this.common.isActivatedEIP(6800) === true) { | ||
// If verkle is activated, only validate the post-state | ||
if ((this._opts.stateManager as StatelessVerkleStateManager).verifyPostState() === false) { | ||
throw new Error(`Verkle post state verification failed on block ${block.header.number}`) | ||
} | ||
debug(`Verkle post state verification succeeded`) | ||
} | ||
@@ -370,3 +404,3 @@ | ||
if ((await this.stateManager.getAccount(parentBeaconBlockRootAddress)) === undefined) { | ||
await this.stateManager.putAccount(parentBeaconBlockRootAddress, new Account()) | ||
await this.evm.journal.putAccount(parentBeaconBlockRootAddress, new Account()) | ||
} | ||
@@ -399,3 +433,3 @@ | ||
const bloom = new Bloom() | ||
const bloom = new Bloom(undefined, this.common) | ||
// the total amount of gas used processing these transactions | ||
@@ -406,3 +440,3 @@ let gasUsed = BIGINT_0 | ||
if (block.transactions.length !== 0) { | ||
receiptTrie = new Trie() | ||
receiptTrie = new Trie({ common: this.common }) | ||
} | ||
@@ -486,3 +520,3 @@ | ||
// such that the account is touched and marked for cleanup if it is empty | ||
await rewardAccount(this.evm, address, amount * GWEI_TO_WEI) | ||
await rewardAccount(this.evm, address, amount * GWEI_TO_WEI, this.common) | ||
} | ||
@@ -504,3 +538,3 @@ } | ||
const reward = calculateOmmerReward(ommer.number, block.header.number, minerReward) | ||
const account = await rewardAccount(this.evm, ommer.coinbase, reward) | ||
const account = await rewardAccount(this.evm, ommer.coinbase, reward, this.common) | ||
if (this.DEBUG) { | ||
@@ -512,3 +546,3 @@ debug(`Add uncle reward ${reward} to account ${ommer.coinbase} (-> ${account.balance})`) | ||
const reward = calculateMinerReward(minerReward, ommers.length) | ||
const account = await rewardAccount(this.evm, block.header.coinbase, reward) | ||
const account = await rewardAccount(this.evm, block.header.coinbase, reward, this.common) | ||
if (this.DEBUG) { | ||
@@ -543,6 +577,12 @@ debug(`Add miner reward ${reward} to account ${block.header.coinbase} (-> ${account.balance})`) | ||
address: Address, | ||
reward: bigint | ||
reward: bigint, | ||
common?: Common | ||
): Promise<Account> { | ||
let account = await evm.stateManager.getAccount(address) | ||
if (account === undefined) { | ||
if (common?.isActivatedEIP(6800) === true) { | ||
;( | ||
evm.stateManager as StatelessVerkleStateManager | ||
).accessWitness!.touchAndChargeProofOfAbsence(address) | ||
} | ||
account = new Account() | ||
@@ -552,2 +592,10 @@ } | ||
await evm.journal.putAccount(address, account) | ||
if (common?.isActivatedEIP(6800) === true) { | ||
// use this utility to build access but the computed gas is not charged and hence free | ||
;(evm.stateManager as StatelessVerkleStateManager).accessWitness!.touchTxExistingAndComputeGas( | ||
address, | ||
{ sendsValue: true } | ||
) | ||
} | ||
return account | ||
@@ -617,3 +665,3 @@ } | ||
} | ||
const trie = new Trie() | ||
const trie = new Trie({ common: block.common }) | ||
for (const [i, tx] of block.transactions.entries()) { | ||
@@ -620,0 +668,0 @@ await trie.put(RLP.encode(i), tx.serialize()) |
101
src/runTx.ts
import { Block } from '@ethereumjs/block' | ||
import { ConsensusType, Hardfork } from '@ethereumjs/common' | ||
import { StatelessVerkleStateManager } from '@ethereumjs/statemanager' | ||
import { BlobEIP4844Transaction, Capability, isBlobEIP4844Tx } from '@ethereumjs/tx' | ||
@@ -9,5 +10,7 @@ import { | ||
KECCAK256_NULL, | ||
bigIntToBytes, | ||
bytesToHex, | ||
bytesToUnprefixedHex, | ||
equalsBytes, | ||
generateAddress, | ||
hexToBytes, | ||
@@ -31,3 +34,3 @@ short, | ||
import type { VM } from './vm.js' | ||
import type { AccessList, AccessListItem } from '@ethereumjs/common' | ||
import type { AccessList, AccessListItem, Common } from '@ethereumjs/common' | ||
import type { EVM } from '@ethereumjs/evm' | ||
@@ -48,4 +51,8 @@ import type { | ||
const balanceNonceLabel = 'Balance/Nonce checks and update' | ||
const executionLabel = 'Execution' | ||
const logsGasBalanceLabel = 'Logs, gas usage, account/miner balances' | ||
const cleanupAndReceiptsLabel = 'Accounts clean up, access list, journal/cache cleanup, receipts' | ||
const accountsCleanUpLabel = 'Accounts clean up' | ||
const accessListLabel = 'Access list label' | ||
const journalCacheCleanUpLabel = 'Journal/cache cleanup' | ||
const receiptsLabel = 'Receipts' | ||
const entireTxLabel = 'Entire tx' | ||
@@ -209,2 +216,11 @@ | ||
let stateAccesses | ||
if (this.common.isActivatedEIP(6800)) { | ||
if (!(this.stateManager instanceof StatelessVerkleStateManager)) { | ||
throw Error(`StatelessVerkleStateManager needed for execution of verkle blocks`) | ||
} | ||
stateAccesses = (this.stateManager as StatelessVerkleStateManager).accessWitness | ||
} | ||
let txAccesses = stateAccesses?.shallowCopy() | ||
const { tx, block } = opts | ||
@@ -277,3 +293,5 @@ | ||
const msg = _errorMsg( | ||
`Transaction's maxFeePerGas (${maxFeePerGas}) is less than the block's baseFeePerGas (${baseFeePerGas})`, | ||
`Transaction's ${ | ||
'maxFeePerGas' in tx ? 'maxFeePerGas' : 'gasPrice' | ||
} (${maxFeePerGas}) is less than the block's baseFeePerGas (${baseFeePerGas})`, | ||
this, | ||
@@ -311,4 +329,25 @@ block, | ||
let upfrontAwGas = BIGINT_0 | ||
if (this.common.isActivatedEIP(6800)) { | ||
upfrontAwGas += txAccesses!.touchTxOriginAndComputeGas(caller) | ||
const sendsValue = tx.value !== BIGINT_0 | ||
if (tx.to !== undefined) { | ||
upfrontAwGas += txAccesses!.touchTxExistingAndComputeGas(tx.to, { sendsValue }) | ||
debug(`Sender upfront awGas requirement for non contract creation tx is ${upfrontAwGas}`) | ||
} else { | ||
const contractTo = new Address(generateAddress(caller.bytes, bigIntToBytes(nonce))) | ||
upfrontAwGas += txAccesses!.touchAndChargeContractCreateInit(contractTo, { sendsValue }) | ||
debug( | ||
`Sender upfront awGas requirement is contract creation at=${short( | ||
contractTo.bytes | ||
)} is ${upfrontAwGas}` | ||
) | ||
} | ||
// reset txAccesses to remove the caches so that access gas can be correctly consumed inside the evm run | ||
txAccesses = stateAccesses?.shallowCopy() | ||
} | ||
// Check balance against upfront tx cost | ||
const upFrontCost = tx.getUpfrontCost(block.header.baseFeePerGas) | ||
const upFrontCost = tx.getUpfrontCost(block.header.baseFeePerGas) + upfrontAwGas | ||
if (balance < upFrontCost) { | ||
@@ -444,7 +483,7 @@ if (opts.skipBalance === true && fromAccount.balance < upFrontCost) { | ||
} | ||
let executionTimerPrecise: number | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(balanceNonceLabel) | ||
// eslint-disable-next-line no-console | ||
console.log('[ For execution see detailed table output ]') | ||
executionTimerPrecise = performance.now() | ||
} | ||
@@ -476,6 +515,15 @@ | ||
blobVersionedHashes, | ||
accessWitness: txAccesses, | ||
})) as RunTxResult | ||
if (this.common.isActivatedEIP(6800)) { | ||
stateAccesses?.merge(txAccesses!) | ||
} | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.log(`${executionLabel}: ${performance.now() - executionTimerPrecise!}ms`) | ||
// eslint-disable-next-line no-console | ||
console.log('[ For execution details see table output ]') | ||
// eslint-disable-next-line no-console | ||
console.time(logsGasBalanceLabel) | ||
@@ -502,3 +550,3 @@ } | ||
// Generate the bloom for the tx | ||
results.bloom = txLogsBloom(results.execResult.logs) | ||
results.bloom = txLogsBloom(results.execResult.logs, this.common) | ||
if (this.DEBUG) { | ||
@@ -562,2 +610,5 @@ debug(`Generated tx bloom with logs=${results.execResult.logs?.length}`) | ||
if (minerAccount === undefined) { | ||
if (this.common.isActivatedEIP(6800)) { | ||
;(state as StatelessVerkleStateManager).accessWitness!.touchAndChargeProofOfAbsence(miner) | ||
} | ||
minerAccount = new Account() | ||
@@ -572,2 +623,9 @@ } | ||
if (this.common.isActivatedEIP(6800)) { | ||
// use this utility to build access but the computed gas is not charged and hence free | ||
;(state as StatelessVerkleStateManager).accessWitness!.touchTxExistingAndComputeGas(miner, { | ||
sendsValue: true, | ||
}) | ||
} | ||
// Put the miner account into the state. If the balance of the miner account remains zero, note that | ||
@@ -585,3 +643,3 @@ // the state.putAccount function puts this into the "touched" accounts. This will thus be removed when | ||
// eslint-disable-next-line no-console | ||
console.time(cleanupAndReceiptsLabel) | ||
console.time(accountsCleanUpLabel) | ||
} | ||
@@ -608,2 +666,9 @@ | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(accountsCleanUpLabel) | ||
// eslint-disable-next-line no-console | ||
console.time(accessListLabel) | ||
} | ||
if (opts.reportAccessList === true && this.common.isActivatedEIP(2930)) { | ||
@@ -628,5 +693,19 @@ // Convert the Map to the desired type | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(accessListLabel) | ||
// eslint-disable-next-line no-console | ||
console.time(journalCacheCleanUpLabel) | ||
} | ||
await this.evm.journal.cleanup() | ||
state.originalStorageCache.clear() | ||
if (enableProfiler) { | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(journalCacheCleanUpLabel) | ||
// eslint-disable-next-line no-console | ||
console.time(receiptsLabel) | ||
} | ||
// Generate the tx receipt | ||
@@ -645,3 +724,3 @@ const gasUsed = opts.blockGasUsed !== undefined ? opts.blockGasUsed : block.header.gasUsed | ||
// eslint-disable-next-line no-console | ||
console.timeEnd(cleanupAndReceiptsLabel) | ||
console.timeEnd(receiptsLabel) | ||
} | ||
@@ -673,4 +752,4 @@ | ||
*/ | ||
function txLogsBloom(logs?: any[]): Bloom { | ||
const bloom = new Bloom() | ||
function txLogsBloom(logs?: any[], common?: Common): Bloom { | ||
const bloom = new Bloom(undefined, common) | ||
if (logs) { | ||
@@ -677,0 +756,0 @@ for (let i = 0; i < logs.length; i++) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
8045
470
597180
Updated@ethereumjs/block@^5.1.1
Updated@ethereumjs/common@^4.2.0
Updated@ethereumjs/evm@^2.2.0
Updated@ethereumjs/rlp@^5.0.2
Updated@ethereumjs/trie@^6.1.1
Updated@ethereumjs/tx@^5.2.1
Updated@ethereumjs/util@^9.0.2
Updatedethereum-cryptography@^2.1.3